pub struct ModelIndex { /* private fields */ }Expand description
Case-insensitive lookup index over a TabularDatabase.
Build it once, after ingestion, with ModelIndex::build. Building never fails:
duplicate names are invalid in a valid model but tolerated here, with the first
occurrence kept and later ones ignored.
Per-table names are nested under their table rather than keyed by a
(table, name) pair, so a lookup folds each half once and allocates no tuple —
this is the DAX lexer’s hot path, one call per reference in every expression.
Implementations§
Source§impl ModelIndex
impl ModelIndex
Sourcepub fn build(db: &TabularDatabase) -> Self
pub fn build(db: &TabularDatabase) -> Self
Indexes every table, column, measure, hierarchy, and shared expression.
Runs in one pass over the model, folding each name once. On a duplicate folded name the first occurrence is kept.
Sourcepub fn resolve_table(&self, name: &str) -> Option<TableHandle>
pub fn resolve_table(&self, name: &str) -> Option<TableHandle>
Looks up a table by name, case-insensitively.
Sourcepub fn resolve_qualified(&self, table: &str, name: &str) -> Option<Resolved>
pub fn resolve_qualified(&self, table: &str, name: &str) -> Option<Resolved>
Resolves a qualified reference, Table[Name].
The named table’s columns are tried first. Falling through to a measure is
deliberate and conservative: measure names are model-global, so a qualified
reference carrying a wrong or stale table prefix — 'Dato'[Total Sales] for a
measure that lives on Sales, or a prefix naming a table that no longer
exists — still keeps that measure alive. Marking one object used too many is
safe; marking one too few deletes live code.
§Examples
// The model has one measure, `Total`, whose home table is `Sales`.
let index = ModelIndex::build(&db);
// A stale prefix still keeps it alive: measure names are model-global.
assert!(index.resolve_qualified("Dato", "Total").is_some());
assert!(index.resolve_qualified("No Such Table", "total").is_some());
// A name that matches nothing resolves to nothing.
assert!(index.resolve_qualified("Sales", "Nope").is_none());Sourcepub fn resolve_column(&self, table: &str, name: &str) -> Option<ColumnHandle>
pub fn resolve_column(&self, table: &str, name: &str) -> Option<ColumnHandle>
Looks up a column on a specific table — how M field access
#"Sales"[Amount] resolves. Unlike resolve_qualified
there is no measure fallback: an M expression cannot reference a
measure, so a measure of the same name must not be kept alive by one.
Sourcepub fn resolve_columns(&self, name: &str) -> Vec<ColumnHandle>
pub fn resolve_columns(&self, name: &str) -> Vec<ColumnHandle>
Finds every column of a name, on every table — how M resolves the
string arguments of its column-centric built-ins and an unqualified
[Name] field access.
M string arguments carry no row context: Table.NestedJoin(Source, "Key", …) can name any table’s column, and a lexer cannot tell which
without a full dataflow analysis. The conservative direction is to keep
all candidates alive; the result is sorted by table, then column,
so it is deterministic for a given model.
let index = ModelIndex::build(&db);
// Both tables have a `Key` column; a merge step naming "Key" keeps
// both alive.
assert_eq!(index.resolve_columns("key").len(), 2);
// An unknown name is data, not an error.
assert!(index.resolve_columns("Ukendt").is_empty());Sourcepub fn resolve_unqualified(
&self,
name: &str,
home_table: Option<&str>,
) -> UnqualifiedMatches
pub fn resolve_unqualified( &self, name: &str, home_table: Option<&str>, ) -> UnqualifiedMatches
Resolves an unqualified reference, [Name], to all its candidates.
home_table is the row-context table of the expression the reference was
found in; pass None where there is none. See UnqualifiedMatches for why
both a measure and a column can come back at once.
§Examples
// `Antal` is a measure on `Sales` and, separately, a column of `Dato`.
let index = ModelIndex::build(&db);
// Inside a `Dato` row context both are live candidates, so both come back.
let ambiguous = index.resolve_unqualified("ANTAL", Some("Dato"));
assert!(ambiguous.measure.is_some());
assert!(ambiguous.column.is_some());
// With no row context there is no column candidate to consider.
assert!(index.resolve_unqualified("antal", None).column.is_none());
// An unknown name is data, not an error.
assert!(index.resolve_unqualified("Ukendt", Some("Dato")).is_empty());Sourcepub fn resolve_hierarchy(
&self,
table: &str,
name: &str,
) -> Option<HierarchyHandle>
pub fn resolve_hierarchy( &self, table: &str, name: &str, ) -> Option<HierarchyHandle>
Looks up a hierarchy on a specific table, as written in ISINSCOPE('Date'[Calendar])
or in a PBIR hierarchy binding. Hierarchy names are only unique per table, so
there is no unqualified form and no cross-table fallback.
Sourcepub fn resolve_expression(&self, name: &str) -> Option<ExpressionHandle>
pub fn resolve_expression(&self, name: &str) -> Option<ExpressionHandle>
Looks up a model-level shared M expression by name — how one M query references a parameter or another query.
Sourcepub fn resolve_function(&self, name: &str) -> Option<FunctionHandle>
pub fn resolve_function(&self, name: &str) -> Option<FunctionHandle>
Looks up a user-defined DAX function by name — how a DAX expression calls it. Function names are model-global.
Trait Implementations§
Source§impl Clone for ModelIndex
impl Clone for ModelIndex
Source§fn clone(&self) -> ModelIndex
fn clone(&self) -> ModelIndex
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more