diff --git a/_site/staticwebapp.config.json b/_site/staticwebapp.config.json deleted file mode 100644 index 56b596620..000000000 --- a/_site/staticwebapp.config.json +++ /dev/null @@ -1 +0,0 @@ -{"routes":[{"route":"/","redirect":"/en/","statusCode":301},{"route":"/index.html","redirect":"/en/","statusCode":301},{"route":"/en/references/release-notes","redirect":"/en/references/release-notes/3_26_0.html","statusCode":302},{"route":"/en/te3/other/release-notes","redirect":"/en/references/release-notes/3_26_0.html","statusCode":302},{"route":"/es/references/release-notes","redirect":"/es/references/release-notes/3_26_0.html","statusCode":302},{"route":"/es/te3/other/release-notes","redirect":"/es/references/release-notes/3_26_0.html","statusCode":302},{"route":"/zh/references/release-notes","redirect":"/zh/references/release-notes/3_26_0.html","statusCode":302},{"route":"/zh/te3/other/release-notes","redirect":"/zh/references/release-notes/3_26_0.html","statusCode":302},{"route":"/references/release-notes","redirect":"/en/references/release-notes/3_26_0.html","statusCode":302},{"route":"/Advanced-Scripting.html","redirect":"/en/how-tos/Advanced-Scripting.html","statusCode":301},{"route":"/Best-Practice-Analyzer.html","redirect":"/en/features/Best-Practice-Analyzer.html","statusCode":301},{"route":"/Command-line-Options.html","redirect":"/en/features/Command-line-Options.html","statusCode":301},{"route":"/Custom-Actions.html","redirect":"/en/tutorials/creating-macros.html","statusCode":301},{"route":"/FormatDax.html","redirect":"/en/references/FormatDax.html","statusCode":301},{"route":"/Importing-Tables.html","redirect":"/en/how-tos/Importing-Tables.html","statusCode":301},{"route":"/Power-BI-Desktop-Integration.html","redirect":"/en/getting-started/Power-BI-Desktop-Integration.html","statusCode":301},{"route":"/Useful-script-snippets.html","redirect":"/en/features/Useful-script-snippets.html","statusCode":301},{"route":"/Workspace-Database.html","redirect":"/en/tutorials/workspace-mode.html","statusCode":301},{"route":"/common/Datasets/direct-lake-dataset.html","redirect":"/en/features/Semantic-Model/direct-lake-sql-model.html","statusCode":301},{"route":"/eula","redirect":"/en/security/te3-eula.html","statusCode":301},{"route":"/onboarding/general-introduction.html","redirect":"/en/getting-started/general-introduction.html","statusCode":301},{"route":"/onboarding/index.html","redirect":"/en/getting-started/index.html","statusCode":301},{"route":"/onboarding/installation.html","redirect":"/en/getting-started/installation.html","statusCode":301},{"route":"/other/downloads.html","redirect":"/en/references/downloads.html","statusCode":301},{"route":"/privacy-policy.html","redirect":"/en/security/privacy-policy.html","statusCode":301},{"route":"/projects/te3","redirect":"/en/","statusCode":301},{"route":"/projects/te3/en/latest","redirect":"/en/","statusCode":301},{"route":"/projects/te3/en/latest/downloads.html","redirect":"/en/references/downloads.html","statusCode":301},{"route":"/projects/te3/en/latest/editions.html","redirect":"/en/getting-started/editions.html","statusCode":301},{"route":"/projects/te3/en/latest/getting-started.html","redirect":"/en/getting-started/getting-started.html","statusCode":301},{"route":"/projects/te3/en/latest/security-privacy.html","redirect":"/en/security/security-privacy.html","statusCode":301},{"route":"/roslyn","redirect":"/en/how-tos/Advanced-Scripting.html#compiling-with-roslyn","statusCode":301},{"route":"/te2/Advanced-Scripting.html","redirect":"/en/how-tos/Advanced-Scripting.html","statusCode":301},{"route":"/te2/Best-Practice-Analyzer.html","redirect":"/en/features/Best-Practice-Analyzer.html","statusCode":301},{"route":"/te2/Getting-Started.html","redirect":"/en/getting-started/Getting-Started-te2.html","statusCode":301},{"route":"/te2/Importing-Tables.html","redirect":"/en/how-tos/Importing-Tables.html","statusCode":301},{"route":"/te2/Power-BI-Desktop-Integration.html","redirect":"/en/getting-started/Power-BI-Desktop-Integration.html","statusCode":301},{"route":"/te2/Useful-script-snippets.html","redirect":"/en/features/Useful-script-snippets.html","statusCode":301},{"route":"/te3/downloads.html","redirect":"/en/references/downloads.html","statusCode":301},{"route":"/te3/editions.html","redirect":"/en/getting-started/editions.html","statusCode":301},{"route":"/te3/features/csharp-scripts.html","redirect":"/en/features/csharp-scripts.html","statusCode":301},{"route":"/te3/features/dax-debugger.html","redirect":"/en/features/dax-debugger.html","statusCode":301},{"route":"/te3/features/dax-editor.html","redirect":"/en/features/dax-editor.html","statusCode":301},{"route":"/te3/features/dax-scripts.html","redirect":"/en/features/dax-scripts.html","statusCode":301},{"route":"/te3/features/tmdl.html","redirect":"/en/features/tmdl.html","statusCode":301},{"route":"/te3/getting-started.html","redirect":"/en/getting-started/getting-started.html","statusCode":301},{"route":"/te3/index.html","redirect":"/en/troubleshooting/licensing-activation.html","statusCode":301},{"route":"/te3/logo.svg","redirect":"/en/logo.svg","statusCode":301},{"route":"/te3/other/downloads.html","redirect":"/en/references/downloads.html","statusCode":301},{"route":"/te3/other/release-history.html","redirect":"/en/references/release-history.html","statusCode":301},{"route":"/te3/tutorials/workspace-mode.html","redirect":"/en/tutorials/workspace-mode.html","statusCode":301},{"route":"/tmdl","redirect":"/en/features/tmdl.html","statusCode":301},{"route":"/tmuo","redirect":"/en/references/user-options.html","statusCode":301},{"route":"/user-options.html","redirect":"/en/references/user-options.html","statusCode":301},{"route":"/workspace","redirect":"/en/tutorials/workspace-mode.html","statusCode":301}],"responseOverrides":{"404":{"rewrite":"/404.html"}}} \ No newline at end of file diff --git a/configuration/filterConfig.yml b/configuration/filterConfig.yml index a7f328cb6..e5a14ebba 100644 --- a/configuration/filterConfig.yml +++ b/configuration/filterConfig.yml @@ -32,6 +32,10 @@ apiRules: - exclude: uidRegex: ^TabularEditor\.SemanticBridge\.Platforms\.Databricks\.Mapping type: Namespace +# ANTLR-generated SQL lexer/parser/visitor types (public by generation, not public API) +- exclude: + uidRegex: ^TabularEditor\.SemanticBridge\.Platforms\.Databricks\.Sql + type: Namespace - exclude: uidRegex: ^TabularEditor\.SemanticBridge\.Platforms\.Databricks\.MetricView\.Extensions type: Namespace diff --git a/content/_apiSource/SemanticBridge.dll b/content/_apiSource/SemanticBridge.dll index bc51edf82..96e84400a 100644 Binary files a/content/_apiSource/SemanticBridge.dll and b/content/_apiSource/SemanticBridge.dll differ diff --git a/content/_apiSource/SemanticBridge.xml b/content/_apiSource/SemanticBridge.xml index b62ada949..51251a0b2 100644 --- a/content/_apiSource/SemanticBridge.xml +++ b/content/_apiSource/SemanticBridge.xml @@ -4,272 +4,419 @@ SemanticBridge - + - Base record for model objects with common properties + Structural surface for a body. Lives + alongside the platform-keyed Expressions dict on + : the AAST is the structural shape; + the dict carries the per-platform source text that the AAST does not + yet model. wraps any shape the AAST + doesn't yet model structurally. - + - Base record for model objects with common properties + Double-dispatch hook. Each concrete subtype calls + visitor.Visit(this), routing to the visitor overload that + accepts that specific type. - - - - - - - - - - - - - - - These are defined such that the success case is the lowest integral value. - - - + - Key of the field fragment that represents the primary key for this dimension. - If not set, no field will be marked as a key. + A reference to another measure. is the + resolved ; the cross-reference is opaque + for equivalency (consistent with model-wide convention that + NodeIds are not [EquivalencyComponent]). Two + nodes are therefore always structurally + equivalent regardless of which measure they target — the + differentiator across two such measures is the + platform-keyed Expressions dict on + . - + - Ensure FactKey, DimensionKey, and RelationshipKey are all set, and that - the relationships are valid according to cardinality constraints: - fact to dim is N:1 and left-to-right. Additionally ensure that the - referenced fact knows about this dimension reference. + A reference to another measure. is the + resolved ; the cross-reference is opaque + for equivalency (consistent with model-wide convention that + NodeIds are not [EquivalencyComponent]). Two + nodes are therefore always structurally + equivalent regardless of which measure they target — the + differentiator across two such measures is the + platform-keyed Expressions dict on + . - - - - Used as a placeholder source for dummy fact - + + - + - Used as a placeholder for dimension references during construction. - Since Fact is a record, this creates a minimal instance rather than inheriting. + Catch-all for expression shapes the AAST does not yet model + structurally. is structural — two + nodes are equivalent iff their + payload matches. - + - Used as a placeholder for fields during construction of an ITableLike + Catch-all for expression shapes the AAST does not yet model + structurally. is structural — two + nodes are equivalent iff their + payload matches. - - - Always call after resolving fields - - + + - + - Platform-specific expression for derived fields. - When set, the fragment builds to a DerivedField instead of a Field. + Constant-valued expression. The intermediate is abstract; the + concrete leaves carry their typed payload. Each platform emitter + renders per its target language's literal syntax (DAX uses + "..." for strings, BLANK() for null, etc.). - - - Gets the keys of all fields in this table-like fragment. - + + A string literal payload. - - - Ensure fragment is a valid part of a model. - - A collection of build errors found during resolution. - - Intended to be used as part of the model build process. - If errors are returned, then the model cannot have a fully successful - build; partial success is possible - + + A string literal payload. - - - Resolve left and right fields, check that cardinalities, kind, and - direction are set. If direction is not set, check whether sides are - fact and dimension: set to dimension filters fact. - - - Build errors: - - if missing or nonexistent field references - - if not a fact and dimension - - if any property is unset that is required for an Root.Relationship - - + + - + - A field derived from a platform-specific expression. - Similar to CalculatedMeasure, this represents a computed value - that cannot be directly translated to DAX. + A numeric literal payload. Single decimal-valued shape covers + both integer and fractional source literals — the emitter + formats per the value (no trailing zeros for whole numbers). - + - Mapping of platforms to platform-specific expressions implementing - the field calculation. + A numeric literal payload. Single decimal-valued shape covers + both integer and fractional source literals — the emitter + formats per the value (no trailing zeros for whole numbers). - - - Data type of the derived field. - + + - - - The table containing this field. - + + A boolean literal payload. - - - Constructor for DerivedField. - - Friendly name for display to users - Name for internal use by the OLAP engine - Prose describing the object - The table containing this field - Data type of the field - - Mapping of platforms to platform-specific expressions implementing - the calculation - + + A boolean literal payload. - - - Determines equality by comparing scalar properties and Expressions - dictionary contents. Excludes parent Table to avoid circular references. - - The other DerivedField to compare with - - True if both objects have equal scalar properties and equivalent Expressions - + + - + - Generates hash code based on scalar properties and Expressions - dictionary contents. Excludes parent Table to avoid circular references. + The null literal. Carries no payload; emitter renders per + target-language null form (DAX BLANK(), SQL NULL). - A hash code that remains consistent for equivalent objects - + + + + - Accepts a visitor for processing this DerivedField. + Binary arithmetic operation. is constrained to + values that have direct cross-platform + equivalents; richer operator sets (comparison, logical, etc.) wait + for future slices. and are + recursive AAST children — structural equivalence walks into them via + . - + - Context for analysis + Binary arithmetic operation. is constrained to + values that have direct cross-platform + equivalents; richer operator sets (comparison, logical, etc.) wait + for future slices. and are + recursive AAST children — structural equivalence walks into them via + . - + + + + - Collection of descriptive properties + Explicit parenthesization. Preserves user-written grouping that the + tree shape alone wouldn't capture. The emitter renders + (Inner) verbatim — no precedence smarts; the AAST is + trusted to carry the intended grouping. - + - Technical name of the primary key field for this dimension. - Null if no primary key is explicitly defined. + Explicit parenthesization. Preserves user-written grouping that the + tree shape alone wouldn't capture. The emitter renders + (Inner) verbatim — no precedence smarts; the AAST is + trusted to carry the intended grouping. - + + + + - Collection of hierarchies + Count of all rows in — DAX + COUNTROWS('Table'). Produced by the SQL→AAST translator for + COUNT(*) and COUNT(<non-null literal>); these + shapes are semantically equivalent (both count every row) and + collapse to a single AAST form. is typed as + so the same node serves fact and (future) + dim row counts; it is opaque for equivalency — the cross-reference + follows the same convention as . - + - Reference to source system and queryable or embedded query + Count of all rows in — DAX + COUNTROWS('Table'). Produced by the SQL→AAST translator for + COUNT(*) and COUNT(<non-null literal>); these + shapes are semantically equivalent (both count every row) and + collapse to a single AAST form. is typed as + so the same node serves fact and (future) + dim row counts; it is opaque for equivalency — the cross-reference + follows the same convention as . - + + + + + + Aggregation of a single column — DAX + SUM('Fact'[col]), COUNT('Fact'[col]), etc. Produced + by the SQL→AAST translator for SUM/AVG/MIN/ + MAX/COUNT/COUNT(DISTINCT) over a single + column reference. Unlike , this aggregates + a specific field rather than the whole table. + is structural (different aggregation + functions produce structurally-distinct expressions); + is opaque for equivalency — same convention + as and + . + + + + + Aggregation of a single column — DAX + SUM('Fact'[col]), COUNT('Fact'[col]), etc. Produced + by the SQL→AAST translator for SUM/AVG/MIN/ + MAX/COUNT/COUNT(DISTINCT) over a single + column reference. Unlike , this aggregates + a specific field rather than the whole table. + is structural (different aggregation + functions produce structurally-distinct expressions); + is opaque for equivalency — same convention + as and + . + + + + + + - Determines equality by comparing scalar properties and collection - contents order-independently. + Operators allowed in . + Restricted to the basic arithmetic set that has direct equivalents + in both Databricks SQL and DAX. - The other Dimension to compare with - - True if both objects have equal scalar properties and equivalent collections. - - + - Generates hash code based on scalar properties and collection contents. - Fields property is not included since it's computed from Attributes. + Aggregation types for measures - A hash code that remains consistent for equivalent objects - + - Descriptive property in a dimension + A computed column on an . Carries one or more + platform-keyed source expressions (e.g., the Databricks SQL the field + was authored as). Target-platform emitters consult their key to obtain + the expression to translate. Doesn't reference a + — derived fields exist only at the + dimensional-model level. + + + + + + + + + + + + + + Reference to the owning ; resolve via + Root.GetTable. + + The data type of this field's computed values. + + Platform-format string applied at emit time (e.g., "#,##0.00", + "yyyy-MM-dd"); empty string when no format opinion. + + + Platform-keyed source expressions for this field. null coalesces + to ImmutableDictionary.Empty at construction. + - + - Creates a DimensionAttribute from any IField, setting the dimension as the parent table. + A computed column on an . Carries one or more + platform-keyed source expressions (e.g., the Databricks SQL the field + was authored as). Target-platform emitters consult their key to obtain + the expression to translate. Doesn't reference a + — derived fields exist only at the + dimensional-model level. + + + + + + + + + + + + + + Reference to the owning ; resolve via + Root.GetTable. + + The data type of this field's computed values. + + Platform-format string applied at emit time (e.g., "#,##0.00", + "yyyy-MM-dd"); empty string when no format opinion. + + + Platform-keyed source expressions for this field. null coalesces + to ImmutableDictionary.Empty at construction. + - + - Data type of the attribute + - + - Field reference in the source; the TechnicalName of the source field. + Reference to the owning ; resolve via + Root.GetTable. - + + The data type of this field's computed values. + + - Indicates if this attribute is a key + Platform-format string applied at emit time (e.g., "#,##0.00", + "yyyy-MM-dd"); empty string when no format opinion. - + + + + + + + + + + + Platform-keyed source expressions; never null. + + + + + - The underlying field this attribute wraps. - Used by visitors to determine if special handling is needed (e.g., DerivedField). + A categorical, descriptive context table joined to a . + References its data source by . Fields on the + dimension live in Root.Fields with their OwnerId set to + this dimension's id; relationships between this dimension and a fact + are first-class objects on Root (future). + + + + + + + + + + + + + + Reference to the dimension's data source; resolve via + Root.GetSource. + - + - Determines equality by comparing scalar properties and underlying field type, - excluding parent Dimension to avoid circular references. + A categorical, descriptive context table joined to a . + References its data source by . Fields on the + dimension live in Root.Fields with their OwnerId set to + this dimension's id; relationships between this dimension and a fact + are first-class objects on Root (future). - The other DimensionAttribute to compare with - - True if both objects have equal scalar properties (excluding parent reference) - + + + + + + + + + + + + + + Reference to the dimension's data source; resolve via + Root.GetSource. + - + - Generates hash code based on scalar properties excluding parent - Dimension to avoid circular references. + - A hash code that remains consistent for equivalent objects - + - Determines equality by comparing scalar properties and child references, - excluding parent Fact to avoid circular references. + Reference to the dimension's data source; resolve via + Root.GetSource. - The other DimensionReference to compare with - - True if both objects have equal scalar properties and equivalent child references - - + + + + + + + + + + + + + - Generates hash code based on scalar properties and child references, - excluding parent Fact to avoid circular references. + Cardinality of a field in a join/relationship - A hash code that remains consistent for equivalent objects @@ -281,772 +428,1495 @@ Permission levels for object rules - + - Represents a business event or measurement context + Marks a property as a structural component of equivalence comparison. + Properties without this attribute are ignored by + — typically the + object's own identity and any cross-reference NodeIds it carries. - + - Collection of quantifiable values + A business event or measurement context. The central table in a + dimensional model. References its data source by + + + + + + + + + + + + + Categorization of the fact (Transactional, Snapshot, etc.). + + Reference to the fact's data source; resolve via Root.GetSource. + - + - Collection of dimension references + A business event or measurement context. The central table in a + dimensional model. References its data source by + + + + + + + + + + + + + Categorization of the fact (Transactional, Snapshot, etc.). + + Reference to the fact's data source; resolve via Root.GetSource. + - + - Type of fact + - - - Reference to source system and queryable or embedded query - + + Categorization of the fact (Transactional, Snapshot, etc.). - + - Determines equality by comparing scalar properties and collection contents order-independently. + Reference to the fact's data source; resolve via Root.GetSource. - The other Fact to compare with - True if both objects have equal scalar properties and equivalent collections - - - Generates hash code based on scalar properties and collection contents. - - A hash code that remains consistent for equivalent objects + + - - - Type of fact table - + + - - - Additivity types for measures - + + - - - Aggregation types for measures - + + - + - Determines equality by comparing scalar properties excluding parent - Table to avoid circular references. + Default base for + implementations that want the standard abort-and-fallback semantics: + unimplemented Visit overloads throw + , and the outermost emit catches + once at the root and invokes . + + New-platform minimum viable emitter: derive, override + , ship. Incrementally override per-shape + Visit overloads as structural emission for each construct + is implemented; unoverridden ones fall through to the fallback + automatically. + + + Recursive code in concrete overloads should call + Visit(child), not child.Accept(this) — the + Visit(AbstractExpression) override routes through Accept, + keeping the dispatch detail in one place. An + raised from any nested call propagates freely; do not catch. + - The other Field to compare with - - True if both objects have equal scalar properties (excluding - parent reference) - - - - Generates hash code based on scalar properties excluding parent - Table to avoid circular references. - - A hash code that remains consistent for equivalent objects + + - + - Logical path for drill-down analysis + Polymorphic-dispatch entry. Routes through the node's + to the matching + concrete-typed Visit overload. - + - Ordered collection of dimension attributes + Default abort. Overridden by derived classes that can render this + construct structurally; otherwise the emit is unwound to the root + and takes over. - - - Indicates if hierarchy has inconsistent depth - + + - - - Determines equality by comparing scalar properties and Levels - collection contents. - - The other Hierarchy to compare with - True if both objects have equal scalar properties and equivalent Levels + + - - - Generates hash code based on scalar properties and Levels - collection contents. - - A hash code that remains consistent for equivalent objects + + - - - Common properties for all model objects - + + - - - Friendly name for display to users - + + - - - Name for internal use in formulas by the OLAP engine, potentially the same as Name - + + - - - Prose describing the object - + + - - - Represents a data source system that can be referenced by model objects - + + + + + - + - Type-specific identifier for this source system + Run the visitor over , catching any + raised at any depth and + returning instead. The single place + where the abort signal is consumed — concrete Visit overloads + should propagate, not catch. - + - Represents a source reference with query information + Thrown by an Visit + method to abort the in-progress emission and trigger the visitor's + . Used to + unwind partial work from deeply-nested constructs (e.g., a BinaryOp + whose right operand cannot be emitted cleanly) so the final output is + either fully structural or the platform-appropriate fallback placeholder + — never a mix of the two. - + - Reference to the source system + Double-dispatch visitor over nodes. + New AAST node types add a Visit overload here and every + implementation must update — compile-time enforcement that all + emitters / analyzers handle every node shape. Mirrors the + pattern: the inherited + Visit(AbstractExpression) entry method is the polymorphic + dispatcher (implementations forward through the node's Accept); + concrete overloads do the per-shape work. Recursive code in concrete + overloads should call Visit(child), never child.Accept(this). - + - Database or catalog name + Target-platform-appropriate placeholder produced when emission + aborts. Typically wraps the whole original source in a comment so + the user sees their intent preserved while the emitted body is + functionally inert. Invoked once at the root by + + when any nested Visit raises an + . - Marker interface for Abstract Model objects that can accept visitors. - All Abstract Model domain objects (Root, Fact, Dimension, Measure, etc.) - implement this. This interface enables the visitor pattern for - traversing the Abstract Model object graph. + The Accept half of the abstract-model visitor pattern. + Implementers route a visitor's polymorphic dispatch through their + concrete-typed Visit overload by the double-dispatch + convention visitor.Visit(this). - Accepts a visitor that produces a result of type TResult. - The concrete type dispatches to the appropriate Visit overload on - the visitor based on its runtime type. + Dispatches to the concrete-typed + Visit overload that matches this record's runtime type. - The type of value produced by the visitor - The visitor to accept - The result produced by the visitor - Visitor interface for the Abstract Model object graph. - Extends the generic IVisitor pattern with Abstract Model-specific - visitation methods. This interface enables type-safe transformation of - Abstract Model objects while supporting generic orchestration through - the base IVisitor interface. + Visitor over records. One + Visit overload per concrete record type, which gives + compile-time exhaustiveness across every implementer: a new record + type cannot be processed without first adding a Visit overload + here. The inherited Visit(IModelObject) from + is the polymorphic-dispatch + entry point — implementations forward through the visitable's + Accept to the appropriate concrete Visit overload. - The type of value produced by visiting Abstract Model objects + The value each Visit overload returns — e.g., a TOM + Model for the abstract-to-TOM emitter, or a diagnostic-bearing + result for a validator. - + - Visits a Root (the top-level Abstract Model container). + Marker indicating a type supports structural equivalence. The comparison + itself is provided by the extension method + , which reflects over + properties tagged with . + Implementers carry no boilerplate beyond declaring this interface. + + Structural equivalence compares own scalar properties only; it + deliberately ignores both the object's own Id and any + cross-reference NodeIds it carries (e.g., OwnerSourceId, + SourceSystemId) — those are allocation-specific to a particular + Root and don't carry meaning across independently-built trees. + Contrast with default record equality (==), which compares + everything. Cross-reference resolution (verifying that two records' + SourceSystemIds point at equivalent SourceSystems across two + Roots) is a graph-level concern handled by Root.EquivalentTo; + a single IEquivalent instance has no Root context. + The where TSelf : IEquivalent<TSelf> constraint is the + closest C# gets to a Self type and is what restricts the + extension method to legitimate self-comparisons. Family interfaces + (e.g., ISource) declare IEquivalent<self-interface> + so that interface-typed references can be compared polymorphically + without any per-concrete-type boilerplate — the extension method + dispatches on runtime type. + + + The implementing type — by convention, the record's own type or the + family interface for polymorphic comparison. + - + - Visits a Fact (business event or measurement context). + Reflection-driven equivalence operation. The marked-property list per + runtime type is computed once and cached; per-call cost is one + PropertyInfo.GetValue + value comparison pair per component. + Runtime-type dispatch makes a single extension overload work for both + concrete-typed and family-interface-typed call sites. - + - Visits a Dimension (context for analysis). + True when and + have the same runtime type and agree on every property of that + type tagged with . - + - Visits a DimensionReference (link between Fact and Dimension). + Equality for property values participating in equivalence. Handles + dictionary content comparison (order-independent, key + value + equality) so records carrying IReadOnlyDictionary-shaped + properties (e.g., DerivedField.Expressions) get sensible + value-equality without per-type custom EquivalentTo. - + - Visits a SimpleMeasure (quantifiable value with simple aggregation). + Capability shared by every concrete abstract-model record: typed + identity plus descriptive metadata. Each implementer declares its own + typed *Id property in its primary constructor and satisfies + via explicit interface implementation. - + - Visits a CalculatedMeasure (quantifiable value with - platform-specific calculation). + The typed identity of this object, boxed as + for polymorphic access. Concrete records expose this as their own + typed id (e.g., FactId, SourceId). + + Id is immutable: set once at construction by Root and never + changed afterward. Implementers expose Id as a get-only + property with no init accessor, so + record with { Id = ... } is a compile error. To modify other + properties of a model object, use Root.UpdateX with a lambda + that preserves the original Id. + - - - Visits a SimpleRelationship. - + + Friendly name for display. - + - Visits a RolePlayingRelationship. + Name for internal use by the OLAP engine, potentially the same as + . + + If a platform does not distinguish between technical and friendly + names, then we expect that TechnicalName will be set with + the value of the friendly name. + - - - Visits a DimensionAttribute (descriptive property in a dimension). - + + Prose describing the object. - + - Visits a Hierarchy (logical drill-down path). + Identity capability shared by every typed *Id value-struct + record. The underlying integer is sequentially allocated per + collection by Root's AddX methods (the first Fact receives + FactId(0), the second FactId(1), and so on) and + exists only within the scope of a single Root's lifecycle. - + - Visits a Field. + Opaque integer identity, unique among ids of the same type within + the lifetime of a single Root. Allocated sequentially by Root's + AddX methods, starting from 0 per collection; treat as an + identifier, not a meaningful number. The typed *Id wrapper + (e.g., FactId vs SourceId) prevents cross-collection + confusion at the type level; ids do not need to be globally unique. + Stable across with updates of the owning record. - + - Visits a DerivedField (field with platform-specific expression). + Identifies an ISource (e.g., QueryableSource or QuerySource) + within a Root. + + + - + - Visits a QueryableSource (source referring to a table/view). + Identifies an ISource (e.g., QueryableSource or QuerySource) + within a Root. + + + - + - Visits a QuerySource (source using custom query). + - - - Visits a Perspective (named subset of model objects). - + + Identifies an ISourceSystem within a Root. + + + - - - Visits a SecurityModel (access control specifications). - + + Identifies an ISourceSystem within a Root. + + + - + - Visits a Role (security role definition). + - + - Base record for measures with common properties + Marker for any id that identifies an within a + Root. Per-kind concrete types (, future + DimensionId) implement this so cross-references to "any table" + can be typed polymorphically while each table kind keeps its own dense + collection and allocation counter. - - - Display formatting - + + Identifies a within a Root. + + + - - - Constructor for base Measure record - - Friendly name for display to users - Name for internal use in formulas by the OLAP engine - Prose describing the object - Display formatting - Data type of the measure + + Identifies a within a Root. + + + - + - Accepts a visitor for the appropriate measure subtype. + - - - Quantifiable value in a fact, with a simple aggregation - + + Identifies a within a Root. + + + - - - Field reference - + + Identifies a within a Root. + + + - + - Table containing the field + - + - Additivity type + Identifies an (SourceField, ReferenceField, or + DerivedField) within a Root. All field kinds share one + FieldId space and one Root.Fields collection. + + + - + - Collection of dimensions over which a semi-additive measure is additive + Identifies an (SourceField, ReferenceField, or + DerivedField) within a Root. All field kinds share one + FieldId space and one Root.Fields collection. + + + - + - Aggregation type + - + - Constructor for SimpleMeasure + Identifies the Root of an abstract semantic model. Singleton in + practice — there is one Root per model and nothing cross-references it + by id — but typed for consistency with other model objects. - Friendly name for display to users - Name for internal use in formulas by the OLAP engine - Prose describing the object - Display formatting - Data type of the measure - Field reference - Table containing the field - Additivity type - - Collection of dimensions over which a semi-additive measure is additive + + - Aggregation type - - - - Determines equality by comparing scalar properties and - AdditiveOverDimensions collection contents. - - The other SimpleMeasure to compare with - - True if both objects have equal scalar properties and equivalent collections - - + - Generates hash code based on scalar properties and - AdditiveOverDimensions collection contents. + Identifies the Root of an abstract semantic model. Singleton in + practice — there is one Root per model and nothing cross-references it + by id — but typed for consistency with other model objects. - A hash code that remains consistent for equivalent objects + + + - + - Quantifiable value in a fact, with a platform-specific calculation + - Currently no parsing, validation, or attempted translation for these - + - Mapping of platforms to platform-specific expressions implementing - the calculation + Identifies a (Simple or Calculated) within a + Root. All measure kinds share one MeasureId space and one + Root.Measures collection. + + + - + - Constructor for CalculatedMeasure + Identifies a (Simple or Calculated) within a + Root. All measure kinds share one MeasureId space and one + Root.Measures collection. - Friendly name for display to users - Name for internal use in formulas by the OLAP engine - Prose describing the object - Display formatting - Data type of the measure - - Mapping of platforms to platform-specific expressions implementing - the calculation + + - + - Determines equality by comparing scalar properties and Expressions - dictionary contents. + - The other CalculatedMeasure to compare with - - True if both objects have equal scalar properties and equivalent Expressions - - + - Generates hash code based on scalar properties and Expressions - dictionary contents. + Identifies a within a Root. The model + holds a flat collection of relationships on Root.Relationships + — role-playing and snowflake patterns express themselves as + multiple entries rather than as + polymorphic id types. - A hash code that remains consistent for equivalent objects + + + - + - Named subset of model objects for specific usage + Identifies a within a Root. The model + holds a flat collection of relationships on Root.Relationships + — role-playing and snowflake patterns express themselves as + multiple entries rather than as + polymorphic id types. + + + - + - Collection of included model objects + - + - Documentation of intended users + Represents a data source system that a source connects to. Source + systems are platform-specific and represent the connection details + necessary for that system. - - - Determines equality by comparing scalar properties and ModelObjects - collection contents. - - The other Perspective to compare with - - True if both objects have equal scalar properties and equivalent ModelObjects - + + Typed identity narrowing . - + - Generates hash code based on scalar properties and ModelObjects - collection contents. + Type-specific identifier for this source system. Used as the + dispatch key for cross-platform source-rendering strategies. - A hash code that remains consistent for equivalent objects - + - Cardinality of a field in a join/relationship + A data source referenced by a Fact or Dimension. This is roughly + equivalent to a partition definition. - - - Abstract representation of a dimensional model - + + Typed identity, narrowing . - + - Collection of facts in the model + Reference to the source system this source connects to. - + - Collection of dimensions in the model + A table-like object in the dimensional model: a or + Dimension. Each concrete kind has its own dense collection on + and its own typed id (, etc.), + but cross-references to "any table" use the + marker so they remain polymorphic over kinds. No IEquivalent + declaration here — Tables collections are split-typed, so equivalence + compares concrete-typed elements directly. - - - Collection of perspectives in the model - + + Typed identity, narrowing . - + - Security model + A column-like object: a (a source's physical + column), a (a table column that maps to a + source column), or a (a computed table + column). All field kinds share one space and one + Root.Fields collection. - - - Determines equality by comparing scalar properties and collection contents order-independently. - - The other Root to compare with - True if both objects have equal scalar properties and equivalent collections + + Typed identity, narrowing . - - - Generates hash code based on scalar properties and collection contents. - - A hash code that remains consistent for equivalent objects + + The data type of this field's values. - + - Creates a dimension reference between a fact and dimension with a - simple equi-join relationship. + A quantitative computation on a . + is the only measure type, with + expressions carried for downstream translation. An empty + collection signals the + "no expression" / placeholder case; the Abstract→TOM emitter + materializes it as a placeholder body plus a diagnostic. - Name of the fact to add the dimension reference to - Name of the dimension to reference - Foreign key field in the fact - Primary key field in the dimension - - Filter direction for the relationship (defaults to RightFiltersLeft) + + + + + + + + + + + + + + Reference to the owning ; resolve via + Root.GetFact. - The created dimension reference + + Platform-format string applied at emit time (e.g., "#,##0.00"); + empty string when the caller has no opinion. + + The data type of this measure's result. - + - Creates a dimension reference between a fact and dimension with a - simple equi-join relationship, looking up fields by name. + A quantitative computation on a . + is the only measure type, with + expressions carried for downstream translation. An empty + collection signals the + "no expression" / placeholder case; the Abstract→TOM emitter + materializes it as a placeholder body plus a diagnostic. - Name of the fact to add the dimension reference to - Name of the dimension to reference - Name of the foreign key field in the fact - Name of the primary key field in the dimension - - Filter direction for the relationship (defaults to RightFiltersLeft) + + + + + + + + + + + - The created dimension reference + + Reference to the owning ; resolve via + Root.GetFact. + + + Platform-format string applied at emit time (e.g., "#,##0.00"); + empty string when the caller has no opinion. + + The data type of this measure's result. - + - Creates a dimension reference between a fact and dimension with the - specified relationship. + - Name of the fact to add the dimension reference to - Name of the dimension to reference - The relationship defining how fact and dimension connect - The created dimension reference - + - Row level security rule + Reference to the owning ; resolve via + Root.GetFact. - + - Row level security rule + Platform-format string applied at emit time (e.g., "#,##0.00"); + empty string when the caller has no opinion. - - - Target table or dimension - + + The data type of this measure's result. - - - Filter expression - + + + + + + + + + + + - + - Object level security rule + A measure expressed as a platform-keyed expression string. Target + emitters consult their platform key to obtain the source to + translate (or carry verbatim with a warning). An empty + collection signals the placeholder + case — the emitter materializes a placeholder body and emits a + diagnostic. + + Platform-keyed source expressions for this measure. null + coalesces to ImmutableDictionary.Empty at construction. + - + - Object level security rule + A measure expressed as a platform-keyed expression string. Target + emitters consult their platform key to obtain the source to + translate (or carry verbatim with a warning). An empty + collection signals the placeholder + case — the emitter materializes a placeholder body and emits a + diagnostic. + + Platform-keyed source expressions for this measure. null + coalesces to ImmutableDictionary.Empty at construction. + - + + Platform-keyed source expressions; never null. + + - Target object + Structural AAST body produced by the platform's + SQL→AAST translator. Downstream emitters render via the AAST + when its shape is structural; otherwise they fall back to + for verbatim text. Null at + construction coalesces to + with empty verbatim — the safest default when a caller hasn't + supplied a structural body. - + - Permission level + Pre-rendered, platform-specific representation of an unsupported + windowing / cumulative / semiadditive specification carried + verbatim from the source measure (e.g. the Databricks window spec + re-serialized to its source YAML). null when the source + declares no such spec. + + When set, the windowing semantics are not representable in the + target language, so the target emitter must not emit + as a live expression. Instead it documents + the original source, the (window-unaware) attempted + translation, and this spec as an inert comment, leaving the + measure for the user to author manually. + + TODO (#6347): replace this carry-through with real windowed / + cumulative / semiadditive measure translation. - + + + + - Security role definition + Construction-time validation and defaulting for the + Name / TechnicalName contract shared by every + record. Target visitors are responsible + for canonicalizing both values to their target's identifier rules; + the abstract model imposes no syntax requirements beyond non-empty + TechnicalName. - + - Expressions restricting data access by dimension values + Returns when it is non-empty and + non-whitespace; throws otherwise. + Caller name is captured automatically for the exception message. - + - Settings controlling object visibility + Returns when non-empty/whitespace; + otherwise returns . - + - Users who belong to a given role + A source that refers to a specific named queryable object in a source + system, e.g., a table or view. Contrast with QuerySource (custom + query text). + + + + + + + + + + + + + + + + The database or catalog name within the source system. + The schema name within the database. + + The name of the queryable object (table or view) within the schema. + - + - Determines equality by comparing scalar properties and collection contents. + A source that refers to a specific named queryable object in a source + system, e.g., a table or view. Contrast with QuerySource (custom + query text). - The other Role to compare with - - True if both objects have equal scalar properties and equivalent collections - + + + + + + + + + + + + + + + + The database or catalog name within the source system. + The schema name within the database. + + The name of the queryable object (table or view) within the schema. + + + + + + + + + + + + + + The database or catalog name within the source system. + + + The schema name within the database. + + + + The name of the queryable object (table or view) within the schema. + + + + + + + + + + - + + + + - Generates hash code based on scalar properties and collection contents. + A source whose contents are defined by a query expression rather than a + named object in the source system. Contrast with + (a named table or view). - A hash code that remains consistent for equivalent objects + + + + + + + + + + + + + + + + The query expression that produces this source's rows. - + - Access control specifications + A source whose contents are defined by a query expression rather than a + named object in the source system. Contrast with + (a named table or view). + + + + + + + + + + + + + + + + The query expression that produces this source's rows. - + - Collection of roles + - + - Determines equality by comparing scalar properties and Roles - collection contents. + - The other SecurityModel to compare with - True if both objects have equal scalar properties and equivalent Roles - + + The query expression that produces this source's rows. + + + + + + + + + + + + + + - Generates hash code based on scalar properties and Roles - collection contents. + A column on an (Fact or Dimension) that maps to a + . The reference is by typed + ; the legacy stringly-typed wiring via + TechnicalName matching is gone. defaults + to the referenced source field's data type at construction time when + not specified explicitly; type-compatibility validation is a downstream + analyzer concern, not enforced here. - A hash code that remains consistent for equivalent objects + + + + + + + + + + + + + + Reference to the owning (Fact or Dimension); + resolve via Root.GetTable. + + + Reference to the underlying this field maps + to; resolve via Root.GetField. + + The data type of this field. + + Platform-format string applied at emit time (e.g., "#,##0.00", + "yyyy-MM-dd"); empty string when no format opinion. + - + - Represents a source that refers to a specific queryable object (table/view) + A column on an (Fact or Dimension) that maps to a + . The reference is by typed + ; the legacy stringly-typed wiring via + TechnicalName matching is gone. defaults + to the referenced source field's data type at construction time when + not specified explicitly; type-compatibility validation is a downstream + analyzer concern, not enforced here. + + + + + + + + + + + + + + Reference to the owning (Fact or Dimension); + resolve via Root.GetTable. + + + Reference to the underlying this field maps + to; resolve via Root.GetField. + + The data type of this field. + + Platform-format string applied at emit time (e.g., "#,##0.00", + "yyyy-MM-dd"); empty string when no format opinion. + - + - Determines equality by comparing scalar properties and Fields - dictionary contents. + - The other QueryableSource to compare with - True if both objects have equal scalar properties and equivalent Fields - + - Generates hash code based on scalar properties and Fields - dictionary contents. + Reference to the owning (Fact or Dimension); + resolve via Root.GetTable. - A hash code that remains consistent for equivalent objects - + + + Reference to the underlying this field maps + to; resolve via Root.GetField. + + + + The data type of this field. + + + + Platform-format string applied at emit time (e.g., "#,##0.00", + "yyyy-MM-dd"); empty string when no format opinion. + + + + + + + + + + + + + + + - Represents a source that uses a custom query + An edge between two s — typically a fact's + foreign key and a dimension's primary key. The model holds a flat + collection of relationships on Root.Relationships; role-playing + and snowflake patterns express themselves as multiple + entries in this collection rather than as + polymorphic relationship variants. + + + + + + + + + + + + + + The "left side" of the edge. For a conventional fact ↔ dim + relationship, this is the fact-side foreign-key + . + + + The "right side" of the edge. For a conventional fact ↔ dim + relationship, this is the dim-side primary-key + . + + + Multiplicity of the left side. for + the conventional fact-side FK. + + + Multiplicity of the right side. for + the conventional dim-side PK. + + + Which side filters which. + for the + conventional dim-filters-fact. + - + - Determines equality by comparing scalar properties and Fields - dictionary contents. + An edge between two s — typically a fact's + foreign key and a dimension's primary key. The model holds a flat + collection of relationships on Root.Relationships; role-playing + and snowflake patterns express themselves as multiple + entries in this collection rather than as + polymorphic relationship variants. - The other QuerySource to compare with - True if both objects have equal scalar properties and equivalent Fields + + + + + + + + + + + + + + The "left side" of the edge. For a conventional fact ↔ dim + relationship, this is the fact-side foreign-key + . + + + The "right side" of the edge. For a conventional fact ↔ dim + relationship, this is the dim-side primary-key + . + + + Multiplicity of the left side. for + the conventional fact-side FK. + + + Multiplicity of the right side. for + the conventional dim-side PK. + + + Which side filters which. + for the + conventional dim-filters-fact. + - + - Generates hash code based on scalar properties and Fields - dictionary contents. + - A hash code that remains consistent for equivalent objects - + - Databricks source system + The "left side" of the edge. For a conventional fact ↔ dim + relationship, this is the fact-side foreign-key + . - - Friendly name for display to users - Name for internal use by the OLAP engine - Description of the source system - Databricks host address - HTTP path for the Databricks workspace - Optional database name - Optional schema name - Optional port number (default is 443) + + + The "right side" of the edge. For a conventional fact ↔ dim + relationship, this is the dim-side primary-key + . + - + - Host name or IP address + Multiplicity of the left side. for + the conventional fact-side FK. - + - HTTP path for the Databricks workspace + Multiplicity of the right side. for + the conventional dim-side PK. - + - Port number (default is 443) + Which side filters which. + for the + conventional dim-filters-fact. - + + + + + + + + + + - + - Generates an M (Power Query) expression for the specified source. - Dispatches to type-specific generation methods based on ISource implementation. + The top-level container of an abstract semantic model. Holds every + model object in flat top-level collections keyed only by typed + *Id references — there are no parent-owned child collections; + navigation across the graph is via Root's lookup methods + (, , etc.). - - The source to generate an M expression for (QueryableSource or QuerySource) - - M expression string that can be used in a Tabular Model partition - - Thrown when the source type is not supported + + Root is an immutable record. "Mutation" methods ( + et al.) are pure functions returning a tuple of + (newRoot, addedObject). Cross-collection invariants are checked + at the construction site; violations throw an exception which a + platform-level wrapper catches and converts to a + TransformationResult diagnostic at the public-API boundary. + Storage is dense per-collection: each AddX allocates the next id + as new TId(Collection.Length) before appending, so ids equal + array indices and lookups are direct indexing. The typed *Id + structs prevent cross-collection confusion at the type level; ids are + not globally unique. Deletion is not a part of the current API. + + + + + The top-level container of an abstract semantic model. Holds every + model object in flat top-level collections keyed only by typed + *Id references — there are no parent-owned child collections; + navigation across the graph is via Root's lookup methods + (, , etc.). + + + Root is an immutable record. "Mutation" methods ( + et al.) are pure functions returning a tuple of + (newRoot, addedObject). Cross-collection invariants are checked + at the construction site; violations throw an exception which a + platform-level wrapper catches and converts to a + TransformationResult diagnostic at the public-API boundary. + Storage is dense per-collection: each AddX allocates the next id + as new TId(Collection.Length) before appending, so ids equal + array indices and lookups are direct indexing. The typed *Id + structs prevent cross-collection confusion at the type level; ids are + not globally unique. Deletion is not a part of the current API. + + + + + + + + + + + + + + The empty Root. Use as the starting point for incremental + construction: (root, var fact) = Root.Empty.AddFact(...). + + + + + Add a to this Root. Returns the new + Root and the newly-allocated source with its assigned + . + + + + + Add a to this Root. Returns the new Root + and the newly-allocated source with its assigned + . + + + + Add a to this Root. + + Thrown when does not yet exist in + . - + + Add a to this Root. + + Thrown when does not yet exist in + . + + + - Generates M expression for a QueryableSource (table/view reference). - Creates Power Query M code that references a Databricks catalog object. + Add an to this Root via a factory that + receives the newly-allocated . Open to + any concrete ISourceSystem implementation — source platforms + (Databricks, Snowflake, etc.) supply the factory. + + + + + Add a to this Root. Owns by the supplied + . + + + Thrown when does not yet exist in + . + + + + + Add a to this Root mapping the + specified table column to a . When + is omitted, the resulting field's + DataType defaults to the referenced source field's + DataType; compatibility validation is a downstream concern. + + + Thrown when does not resolve to a table + in this Root, or when does not + exist in . + + + + + Add a (computed table column) to this + Root. + + + Thrown when does not resolve to a table + in this Root. + + + + + Add a — an expression-based + measure — to this Root. An empty or null + signals the placeholder case; the + Abstract→TOM emitter materializes it as a placeholder body plus a + diagnostic. + + + Thrown when does not resolve to a + in this Root. + + + + + Add a to this Root. The model holds a + flat collection of edges; role-playing and snowflake chains express + themselves as multiple Relationship entries with distinct + / + pairs. + + + Thrown when or + does not exist in + . + + + + + Update an existing field by id via a typed updater lambda. Replaces + the field at its dense-array slot and returns the new Root. Enforces + that the field at is of type + and that the updater preserves the original + Id (see ). + + + The concrete kind expected at + . Constrained to class because all + field records are reference types. + + + Thrown when does not resolve in + . + + + Thrown when the field at is not of type + , or when the updater returns a field with + a different Id. + + + + + Resolve a polymorphic reference to its + . Dispatches on the id's concrete kind. + + + + True when the polymorphic id resolves to a table in this Root. + + + + Fields owned by — only + instances live under a source. - - The queryable source containing database, schema, and object names - - M expression referencing the Databricks object - + - Generates M expression for a QuerySource (custom SQL query). - Wraps a SQL query in Databricks connection context. + Fields owned by — a mix of + and . - The query source containing the SQL query text - M expression that executes the query against Databricks - + - Determines equality by comparing scalar properties and Properties dictionary contents. + Measures owned by . - The other GenericSourceSystem to compare with - True if both objects have equal scalar properties and equivalent Properties - + - Generates hash code based on scalar properties and Properties dictionary contents. + Structural equivalence with another Root. Two Roots are equivalent + if their collections contain pairwise-equivalent objects and all + scalar root-level properties are equal. - A hash code that remains consistent for equivalent objects - + - ODBC source system + Cross-reference walk over an AAST body. Mirrors the pointer + resolution that does for + /: + each opaque Target on the body resolves through its owning + Root, and the resolved , , + or on each side is compared structurally + via its own EquivalentTo. Body shape was already validated + by the caller's — this + pass only verifies that the AAST pointers point at equivalent + records. - - Friendly name for display to users - Name for internal use by the OLAP engine - Description of the source system - DSN name or connection identifier + + + does not declare + at the family level (its concrete + kinds live in split-typed collections), so equivalence between two + ITable values dispatches on concrete kind. + - + - DSN name or connection identifier + True when every item in finds at least + one item in for which + returns true. O(n^2) by design: + scale-appropriate for the abstract model's collection sizes, and + keeps the implementation honestly dependent on the production + EquivalentTo rather than on any external key. - + - + - SQL Server source system + A physical column on an . A leaf field — it + doesn't reference any other field. Table-side fields + () map to a SourceField via + SourceFieldId. + + + + + + + + + + + + + + Reference to the owning source; resolve via Root.GetSource. + + The data type of this column. - - Friendly name for display to users - Name for internal use by the OLAP engine - Description of the source system - Server DNS name or IP address - Optional port number (default is 1433) + + + A physical column on an . A leaf field — it + doesn't reference any other field. Table-side fields + () map to a SourceField via + SourceFieldId. + + + + + + + + + + + + + + + Reference to the owning source; resolve via Root.GetSource. + + The data type of this column. - + - Server DNS name or IP address + - + - Port number for SQL Server connection + Reference to the owning source; resolve via Root.GetSource. - + + The data type of this column. + + + + + + + + + + + @@ -1088,6 +1958,37 @@ Generic visitor interface for traversing object graphs. + + + Result type for any transformation step in the Semantic Bridge. + + + + + + Diagnostic messages related to this result. Success cases may carry + warning and other non-failure informational messages. + + + + + Add diagnostic messages to the result. This is useful for multi-phase + validations or processing. + + The same result with additional diagnostics includes + + + + A successful transformation or validation. Any non-failure + diagnostics are available in `Diagnostics` for review. + + + + + The transformation or validation failed. Messages explaining why + are available in `Diagnostics` for review. + + Databricks MetricView operations and state management service. @@ -1161,7 +2062,7 @@ The validation function receives both the object and the validation context. - Type of MetricView object the rule applies to (View, Join, Dimension, or Measure) + Type of MetricView object the rule applies to (View, Join, Field, or Measure) Unique name for the validation rule Category for organizational purposes (e.g., "Naming", "Structure") @@ -1172,12 +2073,20 @@ A validation rule that can be used with + + + Unique name for the validation rule + Category for organizational purposes + Validation function + Minimum spec version this rule applies to (e.g. "1.1") + Creates a validation rule for a specific type of MetricView object with simple predicate validation. - Type of MetricView object the rule applies to (View, Join, Dimension, or Measure) + Type of MetricView object the rule applies to (View, Join, Field, or Measure) Unique name for the validation rule Category for organizational purposes (e.g., "Naming", "Structure") @@ -1187,6 +2096,14 @@ A validation rule that can be used with + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the object is invalid + Predicate function that returns true when the object is invalid + Minimum spec version this rule applies to (e.g. "1.1") + Creates a validation rule for the root View object. @@ -1197,6 +2114,14 @@ Predicate function to determine if the view is invalid. A validation rule for use in validation. + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the view is invalid + Predicate function that returns true when the view is invalid + Minimum spec version this rule applies to (e.g. "1.1") + Creates a validation rule specifically for Join objects. @@ -1207,15 +2132,31 @@ Predicate function that returns true when the join is invalid A validation rule for Join objects - + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the join is invalid + Predicate function that returns true when the join is invalid + Minimum spec version this rule applies to (e.g. "1.1") + + - Creates a validation rule specifically for Dimension objects. + Creates a validation rule specifically for Field objects. Unique name for the validation rule Category for organizational purposes - Error message returned when the dimension is invalid - Predicate function that returns true when the dimension is invalid - A validation rule for Dimension objects + Error message returned when the field is invalid + Predicate function that returns true when the field is invalid + A validation rule for Field objects + + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the field is invalid + Predicate function that returns true when the field is invalid + Minimum spec version this rule applies to (e.g. "1.1") @@ -1227,7 +2168,15 @@ Predicate function that returns true when the measure is invalid A validation rule for Measure objects - + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the measure is invalid + Predicate function that returns true when the measure is invalid + Minimum spec version this rule applies to (e.g. "1.1") + + Converts the currently loaded Databricks MetricView to a Tabular Model. @@ -1236,6 +2185,7 @@ Databricks HTTP path for connection. Diagnostic messages from validation and conversion. If true, abort on validation errors before creating TOM objects. + The name of the database to be used for any native SQL queries True if conversion succeeded, false if critical errors occurred. Thrown when no MetricView is currently loaded. @@ -1257,41 +2207,248 @@ Thrown when no MetricView is currently loaded. - + - Marker interface for all MetricView domain objects. - Used for type safety and identification in public API + Legacy name for . Produces + a ValidationRule<Dimension> via the generic + MakeValidationRule<T> factory with T inferred + as . The validation visitor picks up + ValidationRule<Dimension> rules alongside + ValidationRule<Field> rules and applies both against + each field in . - + + + Minimum spec version this rule applies to (e.g. "1.1"). + + - Interface for validation rules that can be applied to metric view objects. - ValidMetricViewRules encapsulate a specific validation check that produces - diagnostic messages when validation fails. + Databricks source system definition. Carries connection details + (, ) and optional + default-context fields (, , + ). + + + + + + + + + + + + + Databricks host name or address. + HTTP path for the Databricks workspace. + Optional default catalog/database. + Optional default schema. + Port number (default 443). - + - Gets the unique name of the validation rule. - Names should be descriptive and indicate what the rule validates. + Databricks source system definition. Carries connection details + (, ) and optional + default-context fields (, , + ). + + + + + + + + + + + + + Databricks host name or address. + HTTP path for the Databricks workspace. + Optional default catalog/database. + Optional default schema. + Port number (default 443). - + - Gets the category of the validation rule for organizational purposes. - Categories help group related rules (e.g., "Names", "Structure", "Expressions"). + - + + Databricks host name or address. + + + HTTP path for the Databricks workspace. + + + Optional default catalog/database. + + + Optional default schema. + + + Port number (default 443). + + + + + + + + + + + + + + + + + - Gets the type of object this rule is designed to validate. - This allows for efficient filtering and application of rules. + Convenience extension for adding a + to a . Wraps Root.AddSourceSystem with the + Databricks-specific construction. - + - Validates the target object using the provided read-only context. - ValidMetricViewRules receive a read-only view to prevent accidental + Thrown when a version-gated property is set on a MetricView object + whose version does not support it. + + + + + Builds an AAST from a parsed + . Structural shapes the visitor recognizes + (currently only ) produce + matching AAST nodes; anything else falls through the base class's + abort path to , which collapses the body to + carrying the original + verbatim source for downstream platform-keyed emission. + + + + + Translate to an AAST in the context of + a measure named at + . is + the original source text (used as the + payload when the + translation can't structurally model the shape). + looks up a declared + measure by name (case-insensitive in production); returns null for + unknown names. + is the fact this measure belongs + to — used when a node's structural translation references the + owning table (e.g., + for COUNT(*)). + A metric view's measure body always reduces over the fact (dims + are left-outer-joined), so the owner is constrained to + ; the AAST node's + stays + for emit-side flexibility. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + COUNT(*) or COUNT(<non-null literal>), no + DISTINCT. COUNT(NULL) is excluded — it returns 0 in + SQL regardless of row count, so it's semantically not a row-count. + + + + + One of SUM/AVG/AVERAGE/MIN/MAX/ + COUNT over a single column reference. DISTINCT is + only accepted in combination with COUNT (becomes + ); on any other + aggregation it's rejected. + + + + + Surface-only peek at the top-level structural aggregation kind of + : returns the same answer + would build the + AAST node from — without resolving the column ref, so callers can + read intent even when the ref is unresolved and the AAST falls + back to . Used by + the visitor to preserve aggregation-driven measure-type inference + in the dim-unresolved recovery case. + + + + + Marker interface for all MetricView domain objects. + Used for type safety and identification in public API + + + + + Interface for validation rules that can be applied to metric view objects. + ValidMetricViewRules encapsulate a specific validation check that produces + diagnostic messages when validation fails. + + + + + Gets the unique name of the validation rule. + Names should be descriptive and indicate what the rule validates. + + + + + Gets the category of the validation rule for organizational purposes. + Categories help group related rules (e.g., "Names", "Structure", "Expressions"). + + + + + Gets the type of object this rule is designed to validate. + This allows for efficient filtering and application of rules. + + + + + Gets the version constraint for this rule, declaring which metric view + spec versions the rule applies to. Defaults to . + + + + + Validates the target object using the provided read-only context. + ValidMetricViewRules receive a read-only view to prevent accidental mutation of validation state. The object to validate @@ -1314,7 +2471,7 @@ Accepts a visitor that produces a result of type TResult. The concrete type dispatches to the appropriate Visit overload on - the visitor based on its runtime type (View, Join, Dimension, or + the visitor based on its runtime type (View, Join, Field, or Measure). The type of value produced by the visitor @@ -1340,9 +2497,9 @@ Visits a Join (dimension relationship) in the MetricView. - + - Visits a Dimension (attribute) in the MetricView. + Visits a Field (attribute) in the MetricView. @@ -1350,1293 +2507,3529 @@ Visits a Measure (metric) in the MetricView. - + - Entry point for generic visitor pattern orchestration. - Delegates to the visitable object's Accept method for double dispatch, - which calls back to the appropriate type-specific Visit method (View, Join, Dimension, or Measure). - This method enables generic orchestration code to work with MetricView objects - through the IVisitor interface while maintaining complete type safety. - The compiler enforces that only IMetricViewVisitable objects can be passed to this method. + Shared SQL-expression analysis primitive consumed by + , , and + Join.On analysis. Captures the parsed expression tree, the resolved + column references (deduped + canonicalized against the context's + implicit table), and parser/translator diagnostics. - The MetricView object to visit (View, Join, Dimension, or Measure) - The builder with fragments added based on the visited object + + Each carries the table the column + effectively belongs to. Bare column references and references whose + qualifier matches the implicit table (case-insensitively) are reported + with the implicit-table literal as Table; explicit qualifiers + that do not match are preserved as written. Dedup is by record + equality on (Table, ColumnName). + - + - Create relationship and dimension reference fragments from the join. - Ensure that the fields referenced are in the fact and dimension. - Ensure the dimension exists. + Shared SQL-expression analysis primitive consumed by + , , and + Join.On analysis. Captures the parsed expression tree, the resolved + column references (deduped + canonicalized against the context's + implicit table), and parser/translator diagnostics. - - + + Each carries the table the column + effectively belongs to. Bare column references and references whose + qualifier matches the implicit table (case-insensitively) are reported + with the implicit-table literal as Table; explicit qualifiers + that do not match are preserved as written. Dedup is by record + equality on (Table, ColumnName). + - + - Helper for managing field fragments during Metric View to Abstract Model mapping. - Handles field lookup, creation, and type updates for fields referenced by measures. + A column reference from the analyzed expression, resolved against + the context's implicit table. is the canonical + table this column effectively belongs to; + is the column name as written in the SQL. - - This helper encapsulates the logic for ensuring fields exist in both the fact table - and its source, managing data type updates when measures require Decimal types, - and searching for existing fields by name or technical name. - - - Creates a new FieldFragmentHelper for managing field fragments. - - The model builder containing all fragments - The fact fragment to manage fields for - The source fragment for the fact - + - Helper for managing field fragments during Metric View to Abstract Model mapping. - Handles field lookup, creation, and type updates for fields referenced by measures. + A column reference from the analyzed expression, resolved against + the context's implicit table. is the canonical + table this column effectively belongs to; + is the column name as written in the SQL. + + + + + Equality on (Table, ColumnName) using + . Databricks + SQL identifiers are case-insensitive, so refs differing only in + casing represent the same column. + + + + + Parse , extract resolved column + references against (deduped by + (Table, ColumnName)), and build diagnostics from the parse + result. identifies what is being + analyzed for kind-aware diagnostic factory selection. + + + + + MEASURE references are only legal in measure bodies. In any other + context (field expression, join ON clause) every occurrence becomes a + pre-flight diagnostic. One per call site — per-occurrence visibility + lets the user fix each one in isolation. + + + + + Routing decision for a field's resulting field: the fact table, or + a specific dimension table identified by name. FieldTarget.Fact + is the default when the analyzer cannot conclusively resolve refs to a + single non-source dim table (errors, source refs, multi-dim refs, zero + refs, parse errors, defense-in-depth fallback all route to Fact). + + + + + The field being visited and the abstract-model field kind it + becomes. + + + + + The field being visited and the abstract-model field kind it + becomes. + + + + + The field expression resolves to a single column reference: the + resulting abstract is a ReferenceField pointing at a SourceField + named . + + + + + The field expression resolves to a single column reference: the + resulting abstract is a ReferenceField pointing at a SourceField + named . + + + + + The field expression is complex: the resulting abstract is a + DerivedField carrying the verbatim . + lists the columns the + expression depends on; the visitor ensures each one as a + ReferenceField + SourceField pair before creating the DerivedField. + + + + + The field expression is complex: the resulting abstract is a + DerivedField carrying the verbatim . + lists the columns the + expression depends on; the visitor ensures each one as a + ReferenceField + SourceField pair before creating the DerivedField. + + + + + The field expression is empty or whitespace-only: the resulting + abstract is a DerivedField with empty Expressions. Downstream + Abstract→TOM emits DERIVED_FIELD_NO_EXPRESSION and a + placeholder column body. + + + + + The field expression is empty or whitespace-only: the resulting + abstract is a DerivedField with empty Expressions. Downstream + Abstract→TOM emits DERIVED_FIELD_NO_EXPRESSION and a + placeholder column body. + + + + + Result of analyzing a Metric View for + translation into the abstract model. Composed atop a shared + (parsing, ref extraction, parse-level + diagnostics). Adds the field-specific kind decision + () and routing decision + (). + + + + + Result of analyzing a Metric View for + translation into the abstract model. Composed atop a shared + (parsing, ref extraction, parse-level + diagnostics). Adds the field-specific kind decision + () and routing decision + (). - - This helper encapsulates the logic for ensuring fields exist in both the fact table - and its source, managing data type updates when measures require Decimal types, - and searching for existing fields by name or technical name. - - - Creates a new FieldFragmentHelper for managing field fragments. - - The model builder containing all fragments - The fact fragment to manage fields for - The source fragment for the fact - + - Finds the field fragment key for a given column reference. - Searches fact fields first, then dimension fields. - Matches by technical name or name. + Analyze a Metric View field to determine the abstract-model + field shape it should produce. - The column reference to find - The field fragment key if found, otherwise null - + - Ensures a field exists for a measure, creating one if needed. - For aggregations that imply numeric values (SUM, AVG, MIN, MAX), updates - existing String fields to Decimal. COUNT aggregations don't imply a - particular field type, so the field's DataType is preserved. + Routes a Simple subject's target based on the single column ref's + qualifier. Unqualified or matching the implicit table -> Fact. Any + other qualifier -> Dimension named after that qualifier (case + preserved from the SQL). + + + + + Field-Root operations used by the MV→Abstract visitor: pair-creation + primitives ( for user-declared subjects, + for Expr-derived refs), the + placement-resolver wrapper (), and + post-hoc type opinion (). - Column reference from the measure expression - The aggregation type of the measure - Field fragment key for the field - This method handles two scenarios: - - Field exists (e.g., created by a dimension): Updates DataType to Decimal - Field doesn't exist: Creates new field with Decimal type - + The shared core is private — only the + two named frontends call it. This enforces at the type level that + callers commit to declaration vs. upstream intent rather than reaching + past the abstraction. - + + + Materialize or reuse a + + pair. Idempotent by + on + (case-insensitive). Bails (returns the existing id unchanged) when + any field already sits at that TechnicalName, including a + (bail rule for the derived-aggregation + case). Newly-created fields use ; + callers apply type opinions separately via + . and + are applied to a newly-created + ReferenceField only; existing fields are returned unchanged. When + is empty, the field's Name falls + back to TechnicalName via Names.Or. SourceField description stays + empty (internal, never user-visible). + + + + + Materialize or refine a + + pair for a user-declared MV subject + (Subject.Simple from an MV field). Caller supplies the full + metadata bundle, the resolved placement, the visitor's set of + already-declared field ids (consulted to distinguish placeholders + from declarations), and the diagnostics list (for collision + reporting). + + When an existing field at + is a placeholder (not in ) + with the same source binding, the declaration's metadata is + applied to it — empty slots filled in, existing non-empty + metadata preserved. The FieldId is unchanged; relationships + and other references survive. + + + The returned FieldId is added to + when the result is a + ReferenceField, so later calls can distinguish this declared + field from placeholders. + + + + - Updates an existing field fragment and its corresponding source field - to the specified DataType, but only if the current type is String. - Non-String types (e.g., Integer keys) are preserved. + Compute a dedup-suffix name by repeating + separated by underscores until the + result is unique among fields on + (case-insensitive). Pathological collisions yield ugly names like + pk_pk_pk; that's accepted in exchange for trivial dedup + logic. - Key of the field fragment to update - New data type for the field - + - Creates new field fragments (fact and source) for a measure that - references a non-existent field. - Both Name and TechnicalName are set to the column name. + Materialize or reuse a + + pair for an implicitly-discovered + reference (a column extracted from an Expr or ON clause). Has no + metadata to apply — the caller is an upstream-ref site, not a + user-declared subject. Resulting field's display name falls back to + TechnicalName via Names.Or. + + Lookup is by TechnicalName first. When the name-match is a + declaration whose source binding differs from + (the result of an + rename collision), the upstream + ref isn't asking for that declaration — it's asking for the + field backed by the requested SQL column. Falls back to + finding any non-declared ReferenceField backed by that + SourceField. + - Column reference from the measure expression - Key of the created fact field fragment - + - Traverses a MetricView object graph and returns all nodes using the visitor pattern. + Placement resolver for : resolves + against Root.Dimensions, delegates to + on the resolved branch (source-side + -> Fact, dim-side -> matching dim), and falls back to a Fact-side + placeholder named "table.column" + plus an UNRESOLVED_DIMENSION_REFERENCE warning on the + unresolved branch. Newly-created fields are always + ; type opinions flow through + separately. - + - Returns all objects in the MetricView graph. + Sets a field's . Updates a + AND its linked + in lockstep, OR a on its own (no + SourceField behind it). Direct hits on a + id are a no-op — SourceField data types flow through the + ReferenceField lockstep, not direct mutation. The caller decides + whether to call; there's no "no opinion" sentinel inside the + function. Silently swallows a stale or unknown + rather than throw — id-integrity bugs + are caught upstream by the / + returns the visitor threads through. - + - Static wrapper/convenience class for use in C# scripts + Sets a field's Format. Applies to + and alike; has + no Format (it's internal, never user-visible). Silently swallows a + stale or unknown , matching the + robustness pattern. - + - Loads a MetricView from a YAML file + Result of analyzing a Metric View for + translation into the abstract model. Composed atop a shared + (parsing, ref extraction, parse-level + diagnostics). The record itself is the kind discriminator: a measure + analyzed expression resolves to , + or . - full path to the yaml file on disk - an unvalidated MetricView - - thrown when the YAML is invalid, but doesn't check for internal - consistency of the model - - + - Deserialize a MetricView from a YAML string + Result of analyzing a Metric View for + translation into the abstract model. Composed atop a shared + (parsing, ref extraction, parse-level + diagnostics). The record itself is the kind discriminator: a measure + analyzed expression resolves to , + or . - the YAML string to deserialize - an unvalidated MetricView - - thrown when the YAML is invalid, but doesn't check for internal - consistency of the model - - + - Serialize the MetricView to YAML and save to disk + The measure expression is complex: multi-arg aggregation, nested + function, arithmetic, CASE, an unknown function, or anything else + that doesn't fit the Simple pattern. The resulting abstract is a + calculated measure carrying the verbatim . + lists the columns the expression + depends on so the visitor can ensure them before creating the + calculated measure. - the MetricView to serialize - the path to save the YAML file - + - Serialize a MetricView to a YAML string + The measure expression is complex: multi-arg aggregation, nested + function, arithmetic, CASE, an unknown function, or anything else + that doesn't fit the Simple pattern. The resulting abstract is a + calculated measure carrying the verbatim . + lists the columns the expression + depends on so the visitor can ensure them before creating the + calculated measure. - the MetricView to serialize - the serialized YAML string - + - The default validation rules to use when validating MetricViews. - Automatically used when calling `Validate(MetricView)` overload + The measure expression is empty or whitespace-only: the resulting + abstract is a placeholder calculated measure with no expression. + Downstream Abstract→TOM emits the placeholder body and a warning. - + - Validate a MetricView using the provided validator + The measure expression is empty or whitespace-only: the resulting + abstract is a placeholder calculated measure with no expression. + Downstream Abstract→TOM emits the placeholder body and a warning. - the MetricView to validate - the validator to use - enumerable of diagnostic messages resulting from the validation rules - + - Validate a MetricView using the default set of validation rules + Analyze a Metric View measure to determine the abstract-model + measure shape it should produce. After the + AAST landed, classification + collapsed to (empty body) and + (everything else): structural recognition + happens at AAST-build time in , not here. - MetricView to validate - enumerable of diagnostic messages resulting from the validation rules - The set of rules is available as `MetricViews.DefaultValidationRules` - + - Validate a MetricView using the provided set of rules + Emitted when an MV format spec cannot be translated to TOM + FormatString without information loss. Covers two flavors: + (a) lossy translation — a best-effort FormatString is emitted but + some user intent is dropped (e.g., locale_short_month maps to + Long Date, losing the "short" specificity); (b) untranslatable + drop with ISO fallback — no FormatString equivalent exists, and + the translator falls back to an ISO date/time form so the user + gets an unambiguous rendering rather than nothing. Warning + severity in both cases — the user's explicit format intent is + not fully honored. - the MetricView to validate - the set of validation rules to apply - enumerable of diagnostic messages resulting from the validation rules - does not use any of the default rules unless you explicitly include these - + - Make a new MetricViewValidationVisitor with the default set of rules + Emitted when a user-declared field collides with an existing + implementation-detail placeholder field at the same + on the target table, with a + different SQL source binding. The placeholder is renamed to + (preserving the relationship + wiring that referenced it); the declaration takes the original + name with its declared source. Information severity — the rename + is a deterministic accommodation of the user's explicit + declaration, not a problem to fix. - a reusable MetricViewValidationVisitor to use as a validator - + - Make a new MetricViewValidationVisitor with the provided set of - rules, optionally including the default rules + Emitted by EnsureField when a column reference's table + qualifier does not resolve to a known dimension (and is not + "source"). The visitor recovers by placing a placeholder + + on the Fact preserving the original ref text; this diagnostic + surfaces that the dim wiring is missing so the user can either + declare the join or fix the typo. - the set of validation rules to use in the validator - - whether to include the default rules (default: `true`) + + + + Emitted when a join's On clause is empty or whitespace. + The dim and its source are created, but no FK/PK pair is wired — + downstream model is structurally incomplete on the join side. + + + + + Emitted when a join's On clause can't be parsed or doesn't + take the expected shape of a single equality (e.g., a different + comparison operator, unparseable SQL). The dim and its source + are created, but no FK/PK pair is wired. + + + + + Emitted when a join's On clause is a binary equality but + the two refs don't pair a source-qualified column with a dim- + qualified column — either both refs land on the same side, or + one of them carries an unrelated qualifier. The dim and its + source are created, but no FK/PK pair is wired. + + + + + Emitted when a join declares cardinality: one_to_many. The + v1.1 spec treats the joined table as an independent fact source + aggregated separately at the source grain; the translator does + not currently model that shape. The joined table is still mapped + so it stays visible in the resulting model, but the FK/PK + relationship to the source is skipped. Measures that reference + columns from this table must be reviewed and evaluated by hand + — without the relationship the generated DAX cannot bring those + columns into context and will produce wrong or empty results at + refresh. + + + + + Emitted when a join would create a dimension whose name collides + with an existing dimension — e.g. the same join name nested under + two different parents. Join names are unique across a metric view + (enforced by the UniqueJoinName validation rule), so this only fires + when validation was bypassed; the duplicate dimension is skipped (the + existing one stands) and translation continues. Warning severity — + the model is incomplete for the skipped branch. + + + + + Emitted when a dimension expression references a joined table as a + whole-row or nested struct value (e.g. customer or + customer.nation, where the referenced name is itself a join) + rather than a scalar column. Tabular has no column equivalent for a + struct/row value: the column is still emitted so the model stays + buildable, but it binds to a non-existent source column and will not + resolve at refresh. Warning severity — the reference is valid in the + metric view but untranslatable here. + + + + + Used for surviving Unknown nodes — constructs that parsed cleanly + into the AST but for which we have no translation path. Matches the + downstream Tabular-side "could not be translated" diagnostic so the + end user sees a single, consistent message regardless of which + pipeline stage gave up. + + + + + Used when ANTLR's lexer or parser reports an error against a + dimension expression — i.e., the source SQL itself didn't parse. + Distinct from , which signals + a parsed-but-unhandled construct. As the parser grows in coverage + this code becomes a clear "the user's SQL is the problem" signal + rather than "we don't translate this yet." + + canonical dimension path (anchor for navigation) + + the offending source slice — preferably the tight span of the + surviving Unknown that overlapped this error; falls back to the + parser's own context text otherwise - a reusable MetricViewValidationVisitor to use as a validator + parser/lexer error context (typically short) + parser/lexer error message (typically longer) - + - Creates a validation rule for a specific type of MetricView object. + Defense-in-depth diagnostic for the case-analysis fallback in + FieldAnalysis.Analyze. The case analysis is exhaustive + today, so this should never fire; if it does, future grammar or + visitor changes have broken our routing invariants. We surface a + distinct code so the failure is identifiable in support requests, + and we route the dimension to a Fact field as the safe default + rather than throw. + + dimension name (for the user-facing message) + canonical dimension path (anchor for navigation) + + diagnostic context — typically the dimension expression and a brief + description of which routing branches were considered + + + + + Kind-aware overload of . + identifies what is being analyzed + (typeof(Field), typeof(Measure), + typeof(Join)) for future kind-aware code/message branching; + currently delegates to the dimension-flavored factory. + + + + + Kind-aware overload of . + identifies what is being analyzed + for future kind-aware code/message branching; currently delegates + to the dimension-flavored factory. + + + + + Emitted when a MEASURE(name) reference appears in an + expression context that does not permit it (dimension expressions + and join ON clauses). The expression continues to flow through the + existing best-effort emission path; this diagnostic surfaces the + structural violation pre-flight so the user can correct the MV. + One diagnostic is emitted per occurrence — per-call-site visibility + is preferred over a single summary so each site can be fixed in + isolation. + + + + + Emitted when a MEASURE(name) reference cannot be resolved to + a measure declared in the Root under construction. The MV spec + forbids forward references, so this typically indicates a typo or + a missing measure declaration. The AAST collapses to + Unstructured(verbatim); downstream emission falls back to + the platform-keyed source text with the usual untranslated warning. + + + + + Create a warning diagnostic with a templatized message indicating + that the warning originated during deserialization. + + + + + Used when the format specification is missing the required `type` + property in yaml. + + + + + Use when the metric view is missing required `version` property in + yaml. + + + + + The format type provided (yaml `type` property) is not known. + + + + + Format abbreviation value didn't match any known abbreviation. + The format keeps no abbreviation (null). + + + + + date_format was omitted on a Date/DateTime format. + Defaults to year_month_day. + + + + + date_format value didn't match any known DateFormatSpec. + Defaults to year_month_day. + + + + + time_format was omitted on a DateTime format. + Defaults to locale_hour_minute_second. + + + + + time_format value didn't match any known TimeFormatSpec. + Defaults to locale_hour_minute_second. + + + + + decimal_places.type was omitted. Defaults to all. + + + + + decimal_places.type value didn't match any known + DecimalType. Defaults to all. + + + + + semiadditive was omitted on a window spec. + Defaults to Last. + + + + + semiadditive value didn't match any known + SemiadditiveType. Defaults to Last. + + + + + materialization.mode was omitted. Defaults to relaxed. + + + + + materialization.mode value didn't match any known + MaterializationMode. Defaults to relaxed. + + + + + type was omitted on a materialized view spec. + Defaults to unaggregated. + + + + + type value on a materialized view spec didn't match + any known MaterializedViewType. Defaults to unaggregated. + + + + + Setting a versioned property raised a + MetricViewVersionException through reflection — the + property exists but isn't valid for the view's declared version. + The message text comes from the underlying exception. + + + + + Emitted when a metric view's YAML uses the legacy + dimensions: keyword at the top level on a v1.1-or-later + view. The canonical form starting in v1.1 is fields:; both + keywords continue to work, so this is informational. Fires at most + once per file. Scope is the top-level keyword only — the + materialization block's dimensions: field-name list is + canonical at all versions and does not trigger this diagnostic. + + + + + Emitted when a metric view's YAML uses the fields: keyword + at the top level on a pre-v1.1 view. The fields: alias + became canonical in v1.1; on earlier versions dimensions: + is canonical. We don't refuse the read — Databricks doesn't + document fields: as illegal in older versions — but + surface the mismatch as a soft warning. Fires at most once per + file. + + + + + The value of a join's cardinality: field was not a known + value. The join's cardinality + defaults to on this + path. + + + + + A metric view's YAML declared both top-level fields: and + dimensions:. The two keywords are aliases for the same + collection; declaring both is ambiguous, so deserialization + fails. The fields/dimensions block is skipped on this path + (the resulting view has no fields) and the diagnostic is + surfaced as a . + + + + + Visitor that maps a MetricView () to an + abstract-model . Constructs the Root directly via + Root.AddX pure-functional operations — no builder or fragment + intermediate layer. Accumulates the in-progress Root across Visit calls + via ; each Visit returns the (updated) Root. + Diagnostics encountered during traversal are exposed via + . + + + + + Visitor that maps a MetricView () to an + abstract-model . Constructs the Root directly via + Root.AddX pure-functional operations — no builder or fragment + intermediate layer. Accumulates the in-progress Root across Visit calls + via ; each Visit returns the (updated) Root. + Diagnostics encountered during traversal are exposed via + . + + + + + An empty tagged for MetricView translation. + Shared between the visitor's internal seed state and test setup + helpers so expected/actual roots agree on Name and + TechnicalName trivially. + + + + + Wires this join's foreign-key/primary-key + pair and the resulting . The upstream side + of the ON clause is the parent join's table: the metric-view source + ("source") for a top-level join, or the parent dimension for a + nested (snowflake) join. Best-effort — a malformed ON clause emits a + classified JOIN_ON_* diagnostic and returns without wiring; the + dimension table created by the caller stands. + + + + + Translates a user-declared MV format and applies it (plus the + resulting inferred DataType) to the abstract field at + . No-op when + is null. Used by every dim-side branch (Subject.Simple normal, + Subject.Simple recovery, Subject.Derived, Subject.Placeholder) + after the field has been created or refined. + + + + + Translates a user-declared MV measure format to a TOM FormatString + and appends any lossy-translation diagnostics. Returns empty when + no format is declared. Caller is responsible for using the result + as the measure's Format value. + + + + + Builds an body for a measure + about to be added as . Routes + through , which resolves MEASURE + references against the in-progress Root (case-insensitive + name match) and falls back to + for shapes it + doesn't yet model structurally. Diagnostics from the translation + are appended to . + + + + + Resolves a SQL to its + in the in-progress Root. The caller is + expected to have already ensured the field (via + ); this lookup is + read-only and returns null when the ref doesn't resolve, which + causes the AAST builder to abort and fall back. + + Table resolution mirrors + 's rule: bare or + source-qualified refs land on the owning Fact; + dim-qualified refs target the named dim. Field resolution + within the table follows source binding first (so a renamed + placeholder whose TechnicalName no longer matches the + SQL column still resolves correctly), preferring the field + whose own TechnicalName also matches the SQL column + (the canonical placeholder over a same-source user + declaration). Falls back to a name match for + targets, which have no source + binding. + + + + + + Walks the AAST yielding every + reachable from . Used by the measure + visitor to apply per-field type opinions after the body has been + translated. + + + + + Apply the aggregation's field-type opinion to the field at + via + : String → Decimal + under SUM/AVG/MIN/MAX, no opinion under counts, key Integer + preserved against non-date opinions. + + + + + Picks the opinion the body + contributes to the measure's DataType inference: a top-level + column aggregation contributes its kind; a top-level row count + contributes ; anything else + contributes nothing (caller falls back to format-driven type or + the Decimal default). + + + + + Case-insensitive name lookup against the in-progress + cache. Forward references are + forbidden by the MV spec, so the cache is authoritative at the + moment of translation — every previously-emitted measure has been + indexed. + + + + + Re-serializes a measure's window specification(s) to a source-shaped + YAML fragment, or returns null when the measure declares none. + Carried on the abstract measure as + . + TODO (#6347): drop once windowed measures are translated natively. + + + + + Appends an derived from + to , linked to + and labeled with + . parsed as a + 3-part identifier produces a ; anything + else produces a carrying the string + verbatim. + + + + + Traverses a MetricView object graph and returns all nodes using the visitor pattern. + + + + + Returns all objects in the MetricView graph. + + + + + Parses a 3-part SQL table name into its constituent parts. + + The source string to parse + Database, schema, and table names if parsing succeeds; null otherwise + + + + Static wrapper/convenience class for use in C# scripts + + + + + Loads a MetricView from a YAML file + + full path to the yaml file on disk + an unvalidated MetricView + + Thrown in exceptional failures of deserialization; we should always see a result + + + + + Deserialize a MetricView from a YAML string + + the YAML string to deserialize + an unvalidated MetricView + + thrown in exceptional failures of deserialization; we should always see a result + + + + + Serialize the MetricView to YAML and save to disk + + the MetricView to serialize + the path to save the YAML file + + + + Serialize a MetricView to a YAML string + + the MetricView to serialize + the serialized YAML string + + + + The default validation rules to use when validating MetricViews. + Automatically used when calling `Validate(MetricView)` overload + + + + + Validate a MetricView using the provided validator + + the MetricView to validate + the validator to use + enumerable of diagnostic messages resulting from the validation rules + + + + Validate a MetricView using the default set of validation rules + + MetricView to validate + enumerable of diagnostic messages resulting from the validation rules + The set of rules is available as `MetricViews.DefaultValidationRules` + + + + Validate a MetricView using the provided set of rules + + the MetricView to validate + the set of validation rules to apply + enumerable of diagnostic messages resulting from the validation rules + does not use any of the default rules unless you explicitly include these + + + + Make a new MetricViewValidationVisitor with the default set of rules + + a reusable MetricViewValidationVisitor to use as a validator + + + + Make a new MetricViewValidationVisitor with the provided set of + rules, optionally including the default rules + + the set of validation rules to use in the validator + + whether to include the default rules (default: `true`) + + a reusable MetricViewValidationVisitor to use as a validator + + + + Creates a validation rule for a specific type of MetricView object. + + + type of MetricView object the rule applies to. One of `MetricView`, + `MetricViewField`, `MetricViewJoin`, `MetricViewMeasure` + + name of the rule + category for the rule (you can use any text) + validation function + a validation rule for use in a validator + + the validation function `validate` is a function of two arguments: + 1. the metric view object to which the rule is applied + 2. the validation context + validation context is a `IReadOnlyValidationContext` object which + provides: + - `PathStack`: identifies the exact object currently being + examined, with all of its parents (in reverse order from this + object to the root). In general you probably don't need to do + much with this. + - `JoinNames`: set of strings of all join names already seen + during validation + - `FieldNames`: set of strings of all field names already + seen + - `MeasureNames`: set of strings of all measure names already seen + - `MakeError(message, property)`: function to create a diagnostic + error. **You should use this to create the errors you return**. + The first arg is a string describing the error. The second arg + is an optional property name (e.g. "Name") to append to the path. + **Only use the name of a MetricView object property**, not any + arbitrary string. + Your function must return an enumerable of DiagnosticMessage objects. + If there are no problems, return an empty enumerable (e.g. `[]`). + If the object is invalid according to the rule (your conditional + checks), then return an enumerable with one or more errors created + with the context's `MakeError` method. You can inspect any properties + of the object (the first arg to your function) or anything in the + context object. + + + + + Unique name for the validation rule + Category for organizational purposes + Validation function + Minimum spec version this rule applies to (e.g. "1.1") + + + + Create a validation rule for a specific MetricView object type + + + type of MetricView object the rule applies to. One of `MetricView`, + `MetricViewField`, `MetricViewJoin`, `MetricViewMeasure` + + name of the rule + category for the rule (you can use any text) + the message returned in the error if the object is invalid + a predicate function to determine if the object is invalid + a validation rule for use in a validator + + the function `isInvalid` is a simple predicate function that can + only inspect the properties of the current MetricView object. If you + need to inspect other context, you should use the other overload of + `MakeValidationRule` + + + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the object is invalid + Predicate function that returns true when the object is invalid + Minimum spec version this rule applies to (e.g. "1.1") + + + + Create a validation rule for the root MetricView object + + name of the rule + category for the rule (you can use any text) + the message returned in the error if the object is invalid + a predicate function to determine if the object is invalid + a validation rule for use in a validator + + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the object is invalid + Predicate function that returns true when the object is invalid + Minimum spec version this rule applies to (e.g. "1.1") + + + + Create a validation rule for MetricViewJoin objects + + name of the rule + category for the rule (you can use any text) + + the message returned in the error if the MetricViewJoin is invalid + + + a predicate function to determine if the MetricViewJoin is invalid + + a validation rule for use in a validator + + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the object is invalid + Predicate function that returns true when the object is invalid + Minimum spec version this rule applies to (e.g. "1.1") + + + + Create a validation rule for MetricViewField objects + + name of the rule + category for the rule (you can use any text) + + the message returned in the error if the MetricViewField is invalid + + + a predicate function to determine if the MetricViewField is invalid + + a validation rule for use in a validator + + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the object is invalid + Predicate function that returns true when the object is invalid + Minimum spec version this rule applies to (e.g. "1.1") + + + + Create a validation rule for MetricViewMeasure objects + + name of the rule + category for the rule (you can use any text) + + the message returned in the error if the MetricViewMeasure is invalid + + + a predicate function to determine if the MetricViewMeasure is invalid + + a validation rule for use in a validator + + + + Unique name for the validation rule + Category for organizational purposes + Error message returned when the object is invalid + Predicate function that returns true when the object is invalid + Minimum spec version this rule applies to (e.g. "1.1") + + + + Companion to that routes through the + AbstractModel.Next visitor pipeline. Same return contract and + diagnostic semantics; the two coexist while the new pipeline is + completed. + + + + + DTO for Databricks Metric View YAML serialization. + Uses snake_case naming convention via YamlDotNet configuration. + + + + + DTO for field definitions in YAML + + + + + DTO for measure definitions in YAML + + + + + DTO for join definitions in YAML + + + + + DTO for the v1.1 rely: map on joins. + + + + + DTO for format specification in YAML + + + + + DTO for decimal places specification + + + + + DTO for window specification + + + + + DTO for materialization configuration + + + + + DTO for materialized view specification + + + + + Represents a field definition in a Databricks Metric View + + + A field in a Metric View is a single field. There is no namespacing + or delineation of separate named collections of fields. + + + + + Masks the runtime type name introduced by + the legacy shim so logs, debugger tooltips, and default + ToString() consumers always see the canonical "Field" label + regardless of which subtype currently backs the instance. + + + + + The YAML name for the field + + + + + + + + The YAML string representing the expression for this field, + either a field reference or scalar SQL expression. + + + + + Description of the field. Requires version 1.1+. + + + + + Human-readable display name. Requires version 1.1+. + + + + + Alternative names for AI/search. Requires version 1.1+. + + + + + Display format specification. Requires version 1.1+. + + + + + Legacy name for . Retained as an obsolete subclass + so external code still compiles during the deprecation window. Every + instance constructed via + or is actually a + at runtime, so a cast ((Dimension)field) succeeds and returns the + same reference. + + + + + Databricks Metric View format specifications + + + + + The type of format (i.e., "number", "currency", "percentage", + "byte", "date", "datetime") + + + + + Date: Use date format for date values with various display options. + + + + + + + + Controls the way the date is displayed + + + + + Controls whether single digit numbers are preceded by a zero + + + + + DateTime format specification + + + When working with a `DateTime` type, at least one of `DateFormat` + or `TimeFormat` must specify a value other than `NoDate` or + `NoTime`. + + + + + + + + Time format specification + + + + + Controls the way the date is displayed + + + + + > + Date is hidden (only valid for DateTime, not for Date) + + + + + Displays the date with an abbreviated month + + + + + Displays the date with the full name of the month + + + + + Formats the date as YYYY-MM-DD + + + + + Displays the date with a month as a number + + + + + Formats the date as a year and a week number. For example, `2025-W1` + + + + + Controls the way the time is displayed + + + + + Time is hidden + + + + + Displays the hour and minute + + + + + Displays the hour, minute, and second + + + + + + + + + + + + + + Number: Use plain number format for general numeric values with optional + decimal place control and abbreviation options. + + + + + + + + Abbreviation style: none, compact, or scientific + + + + + Currency: Use currency format for monetary values with ISO-4217 currency codes. + + + + + + + + ISO 4217 currency code (e.g., "USD", "EUR", "JPY") + + + + + Percentage: Use percentage format for ratio values expressed as percentages. + + + + + + + + Byte: Use byte format for data size values displayed with appropriate + byte units (KB, MB, GB, etc.). + + + + + + + + Specifies decimal place formatting. + + + + + Type of decimal formatting: "max", "exact", or "all" + + + + + Number of decimal places (0-10). Null if not specified. + + + + + Decimal places configuration + + + + + Whether to hide the group separator (e.g., commas in 1,000) + + + + + The type of format (i.e., "number", "currency", "percentage", + "byte", "date", "datetime") + + + + + Represents a join definition in a Databricks Metric View + + + + + Parent join if this is a nested join, null if this is a top-level join. + + + + + Source table or query for the join + + + + + Optional SQL boolean expression for the join condition + + + + + Optional list of column names to use for join + + + + + Child joins that are part of this join definition + + + + + Controls the relationship between the source and the joined table. + Per spec, cardinality: defaults to many_to_one: the + joined table acts as a dimension lookup. Set + to aggregate the joined + table as a separate fact source. Three states round-trip faithfully: + null (YAML omitted; spec-equivalent to many_to_one), + (explicit), and + . Requires YAML spec 1.1+. + + + + + Optimizer hints about the join's relationship to its source. + Currently the map only carries ; + the spec is shaped as an extensible map for future keys. + Conservatively gated to YAML spec 1.1+: rely: was not + part of the v0.1 spec, so emitting it on a v0.1 view would + produce YAML that pre-v1.1 tooling can't read. The current + spec's feature-availability table ties rely.at_most_one_match + to DBR 18.1 without restating a YAML version requirement; we + keep the YAML gate as a compatibility restriction. + + + + + Name of the joined table + + + + + + + + Adds a child join to this join definition + + + + + Optimizer hints about a join's relationship to its source. Today this + carries only ; the Databricks spec models + rely: as an extensible map so additional keys may appear in + future spec revisions. + An explicitly-empty rely: {} in YAML deserializes to a non-null + with all-null fields, distinct from an omitted + rely: (which leaves null). Both states + round-trip faithfully. Faithful round-trips are a goal of our Metric + View serialization and deserialization. + + + + + When true, declares that the join has no fan-out on the + "one" side: on a many-to-one join, each source row matches at most + one row in the joined table; on a one-to-many join, each joined + row matches at most one source row. Databricks recommends setting + rely on both cardinalities when the constraint holds: it + lets the engine skip unnecessary joins and reduce data scanned, + especially for queries that filter on fields from the joined + table. Not validated at runtime; if the asserted side actually + fans out, measures return incorrect results. + + + + + The cardinality of a join's relationship between source and joined + table. Drives whether the joined table is treated as a dimension + lookup () or as an independent fact source + that the engine aggregates separately (). + Per the Databricks v1.1 spec, omitting the field defaults to + ; additionally + requires Databricks Runtime 18.1+. + + + + + Each source row matches at most one row in the joined table — + the joined table acts as a dimension lookup. Default per spec + when cardinality: is omitted. + + + + + The joined table can have multiple matching rows per source + row; the engine aggregates it independently at the source grain + as a separate fact source. + + + + + Specifies materialization configuration for accelerating metric view queries. + This is an experimental feature. + + + + + Refresh schedule (e.g., "every 6 hours") + + + + + Materialization mode. Currently only "relaxed" is supported. + + + + + List of materialized view specifications + + + + + Specifies a single materialized view within the materialization configuration. + + + + + Name of the materialization + + + + + Type of materialization: aggregated or unaggregated + + + + + List of dimension names to materialize (for aggregated type) + + + + + List of measure names to materialize (for aggregated type) + + + + + Materialization mode. Currently only "relaxed" is supported by Databricks. + + + + + Type of materialized view specification. + + + + + Represents a measure definition in a Databricks Metric View + + + + + + + + Aggregate SQL expression that defines the measure + + + + + Description of the measure. Requires version 1.1+. + + + + + Human-readable display name. Requires version 1.1+. + + + + + Alternative names for AI/search. Requires version 1.1+. + + + + + Display format specification. Requires version 1.1+. + + + + + Window specifications for windowed, cumulative, or semiadditive aggregations. + Requires version 1.1+. This is an experimental feature. + + + + + A collection of MetricView objects with name-based indexing. + Implements IReadOnlyList for compatibility while providing O(1) name lookups. + + The type of MetricView object in the collection + + + + Creates a new collection for a named object type + + + + + Gets the item at the specified index. + + + + + Gets the item with the specified name. + + If no item with the specified name exists + + + + Gets the number of items in the collection. + + + + + Returns an enumerator that iterates through the collection. + + + + + Returns an enumerator that iterates through the collection. + + + + + Finds an item by name, returning null if not found. + + The name to search for + The item with the specified name, or null if not found + + + + Determines whether an item with the specified name exists in the collection. + + The name to search for + True if an item with the specified name exists + + + + Adds an item to the collection. + + The item to add + If an item with the same name already exists + + + + Removes an item from the collection. + + The item to remove + True if the item was removed, false if not found + + + + Removes all items from the collection. + + + + + Internal abstract base class for all MetricView domain objects. + Implements both the public marker interface and the internal visitable interface. + + + + + Reference to the root View object. + For View itself, this points to itself. + Used for version validation in property setters. + + + + + Escape backslashes and quotes for building paths that are valid C#. + + name of some metric view object + + A string that will be valid in a lookup in a C# snippet such as: + `Model.Fields["a \"dangerous\" name"]` + + + + + Indicates whether this object has been deleted from its parent collection. + Deleted objects become "zombies" - they retain their data but are detached from the model. + + + + + Deletes this object from its parent collection. + After deletion, the object becomes a zombie (IsDeleted=true, View=null). + + + Thrown if the object is not attached to a View or is already deleted. + + + + + Marks this object as deleted. + Called by subclasses after removing from collection. + + + + + Accepts a visitor that produces a result of type TResult. + Must be implemented by derived domain objects. + + + + + Validates that the current model version supports the property being set. + Call this in property setters for version-gated properties. + + + Auto-populated by compiler via CallerMemberName + + + Thrown if the property requires a higher version than the current model + + + + + Provides version comparison utilities for Databricks Metric View specifications. + Unknown versions are treated as greater than all known versions (forward compatible). + + + + + Known metric view specification versions in order + + + + + Version 0.1 - Initial release (Databricks Runtime 16.4 through 17.1) + + + + + Version 1.1 - Adds semantic metadata, comments (Databricks Runtime 17.2+) + + + + + Default version when not specified in YAML (Databricks defaults to 1.1) + + + + + Compares two version strings. + Unknown versions sort greater than all known versions. + + + Negative if left < right, zero if equal, positive if left > right + + + + + Returns true if the version is at least the specified minimum version. + + + + + Returns true if is strictly greater than + , using full version-number comparison. + Patch-level strings ("1.1.1") ARE greater than their parent + minor version ("1.1") — any version bump from Databricks + means a spec change we should respect. Unparseable strings on + either side fall back to the array-index ordering used by + . + + + + + Returns true if the version string is a known version. + + + + + Gets the index of a version in the known versions array. + Unknown versions return int.MaxValue (sort after all known). + + + + + Specifies the minimum metric view version required for a property. + Used for setter validation when setting v1.1+ properties on older versions. + + + Creates a new MinVersion attribute with the specified minimum version. + + The minimum version required (e.g., "1.1") + + + + Specifies the minimum metric view version required for a property. + Used for setter validation when setting v1.1+ properties on older versions. + + + Creates a new MinVersion attribute with the specified minimum version. + + The minimum version required (e.g., "1.1") + + + + The minimum version required for this property. + + + + + Provides cached lookups for MinVersion attributes on properties. + + + + + Gets the minimum version required for a property, or null if no minimum. + + The type containing the property + The name of the property + The minimum version string, or null if no MinVersion attribute + + + + Checks if the property requires a minimum version and throws if not met. + + The type containing the property + The name of the property + The current model version + + Thrown if the property requires a higher version than current + + + + + Handles deserialization of Databricks Metric Views from YAML + + + + + Deserializes a YAML string into a MetricView object + + The YAML string to deserialize + A MetricView object + If YAML is invalid or cannot be deserialized + + A TransformationResult, either: + - success containing the view and any non-failure-inducing + diagnostic messages + - failure containing a list of errors that caused deserialization + or validation to fail. There is no view available in this case. + + + + + Maps a DTO to the domain model, wiring up parent pointers + + + + + Handles serialization of Databricks Metric Views to YAML + + + + + Serializes a MetricView object to a YAML string + + The MetricView object to serialize + A YAML string representation + + + + Version-aware default keyword choice for a view with no explicit + . Conservative: v1.1 and earlier + emit dimensions:; anything strictly greater than v1.1 emits + fields:. The fields: alias was introduced after the + v1.1 spec was published, so defaulting v1.1 to dimensions: + stays compatible with tooling that targets the original v1.1 + spec. + + + + + Serializes a measure's window specification(s) to a YAML fragment + using the same naming convention and DTO mapping as a full view, so + the output matches the window: block as authored in the + source metric view. Used to embed the spec verbatim in a fallback + comment when window measures are encountered (see #6347). + + The window specifications to serialize. + A YAML string representation of the window list. + + + + Maps the domain model to a DTO for serialization + + + + + Represents a Databricks Metric View definition + + + + + Version of the metric view specification + + + + + The source data for the metric view, playing the role of a single fact + + + + + Optional SQL boolean expression that applies to all queries; a WHERE + + + + + Description of the metric view. Requires version 1.1+. + + + + + Materialization configuration for query acceleration. Requires version 1.1+. + This is an experimental feature. + + + + + Optional join definitions for the metric view + + + A join is also the place where dimension sources are defined; this + information exists nowhere else. + + + + + Array of field definitions + + + + + Array of measure definitions + + + + + Adds a new field to the metric view + + + + + Adds a new measure to the metric view + + + + + Adds a new join to the metric view + + + + + Adds a new join to the metric view with parentName as the parent join + + + + + Legacy name for . Allocates a fresh + on each access because + is invariant — the same + instance cannot satisfy both <Field> and + <Dimension>. The cast to is + safe because constructs every field as a + at runtime. Item references are the same + instances stored in , so mutations through + either surface are visible through the other. + + + + + Internal factory used by (and indirectly + ) to construct every + instance as a at runtime. + + + + The load-bearing piece of the legacy shim. Because the + getter casts items to + and returns + one, every instance backing a entry + must actually be a subclass at runtime. + The compile-time return type stays so + canonical callers see the canonical type; only the runtime + instance type differs. + + + Implications while the shim lives: + + + field.GetType() returns typeof(Dimension), + not typeof(Field), for any field constructed via + or . + + + Default object.ToString() would surface the + runtime Dimension type name; + is overridden to mask this + in logs and debugger tooltips. + + + Reflection-based code that inspects the runtime type + name (custom serializers, reflection-based assertions, + some debugger views) will observe Dimension. + The legacy (Dimension)field cast succeeds + without copying state — this is why we construct + eagerly rather than wrapping per-access. + + + + + When is eventually removed, this + factory and its callers revert to constructing + directly. The runtime type flips back — + any external code that hardcoded the runtime type name to + "Dimension" will then need to update. + + + + + + Legacy name for . The returned reference is + typed as so call sites that assign to a + Dimension-typed local continue to compile; the underlying + instance is the same one stored in . + + + + + Specifies window measure behavior for windowed, cumulative, or + semiadditive aggregations. + + + This is an experimental feature in Databricks. + The Semantic Bridge performs minimal validation or enforcement of the + window specification. + + + + + Aggregation strategy for semiadditive measures. + + + + + Makes sure that the first cumulative value is used when + aggregating over fields + + + + + Makes sure that the last cumulative value is used when + aggregating over fields + + + + + The field name that determines the ordering of the window. + + + + + Defines the extent of the window. + Values: "current", "cumulative", "trailing N unit", "leading N unit", "all" + + + + + Specifies how to summarize the measure when the order field is not + included in the query's `GROUP BY`. + + + + + Optional offset that shifts the window frame backward or forward + along the field by a fixed interval. Treated + as an opaque string: we round-trip the source form verbatim and + do not validate. + Per the v1.1 spec the form is <n> <period> + where n is a signed integer (negative looks backward, + positive looks forward) and period is one of day, + days, month, months, year, or + years. Examples: -12 month, 1 year, + -3 days, 7 day. + Spec constraints we do not enforce: must be + a date or timestamp column; offset has no effect when + is all; a shifted frame outside the + available data evaluates to NULL at query time. + + + + + Parses Databricks SQL expressions into an + AST using and an ANTLR visitor. + + + + Tokenize the SQL expression in `input` + Token stream omitting whitespace and comments + + + + Parse the input expression and return the AST root with any errors. + + + + + Cache for parse results; most recently visited non-Unknown. Acts + as an anchor for error-recovery spans. + + + + + Ensure we wrap all concrete visit methods and catch those antlr + trees which have not been handled. + + + + + Route child visits through Visit instead of Accept so our error + handling and caching in Visit applies at every depth. ANTLR's + default VisitChildren uses Accept which bypasses Visit. + + + + + Aggressive aggregation: a non-null aggregate means we are inside + of a rule without an explicit visitor + override. Wrap the running aggregate plus the next child into an + that preserves structurally- + recognized subtrees as Inner children. Existing Unknown + wrappers are flattened so we never produce + Unknown(Unknown(...)). Effect: any unhandled grammar rule + surfaces as Unknown spanning its full source extent, with all + recognized inner expressions reachable via the standard + walk. + + + + + Find the first default-channel token at or after the last + successfully visited node. Falls back to last good end, then to + start of input. + + + + + Create a non-overlapping, contiguous span for the tree node. + + + + + Create a TextSpan for the whole range of the parser rule context. + Intended for use in known valid parser visit methods only. + + + Thrown when the context includes a null Stop token, which means + we're trying to make a span for a context where we can't. Should + never throw in normal operation; this is just extra defensiveness. + + + + + Dispatch on the matched terminal token to produce the corresponding + typed subtype with the parsed + payload. Numeric and string parsing happens once here so downstream + consumers (AAST builders, validators) don't need to re-derive value + from the raw span text. + + + + + Strip the outer single quotes from a SQL string literal and + unescape embedded '' to a single quote. + + + + + Parse a numeric token (DECIMAL / FLOAT / REAL) into a + . Uses so + exponential notation (1.5e2) is accepted alongside the + simpler integer/fractional forms. + + + + + Create AST node for any binary expression. All have the same 3 parts. + + + + + Best available context span for a parser error: the enclosing rule + if available, otherwise the whole line. + + + + + Extract a TextSpan covering the entire line (1-based) from _input. + + + + + Double-dispatch visitor over nodes. One + Visit overload per concrete leaf type — adding a new leaf to + the SQL AST forces every visitor to consider it (either override or + inherit the default abort from ). + + Recursive code in concrete overloads should call + Visit(child), not child.Accept(this) — the + Visit(SqlExpression) dispatcher routes through Accept, + keeping the dispatch detail in one place. An + raised from any nested + Visit propagates freely; only the outermost emit (via + ) + catches it and invokes . + + + + + + Visitor-specific placeholder produced when emission aborts. For an + SQL-to-AAST builder this is typically + wrapping + the verbatim source; for other consumers (validators, dumpers) it + can be whatever default the visitor's result type calls for. + Implementors MUST override; the abstract base class declares this + abstract so the omission fails at compile time. + + + + + A parse error reported by the ANTLR parser during expression parsing. + + + + + ANTLR's formatted error description. + + + + + Surrounding context of source text where the failure occurred. + + + + + An error that comes from ANTLR's lexer pass + + + + + An error that comes from ANTLR's lexer pass + + + + + + + + + + + The position of the character in the line that caused the lexer + failure. - - type of MetricView object the rule applies to. One of `MetricView`, - `MetricViewDimension`, `MetricViewJoin`, `MetricViewMeasure` - - name of the rule - category for the rule (you can use any text) - validation function - a validation rule for use in a validator - - the validation function `validate` is a function of two arguments: - 1. the metric view object to which the rule is applied - 2. the validation context - validation context is a `IReadOnlyValidationContext` object which - provides: - - `PathStack`: identifies the exact object currently being - examined, with all of its parents (in reverse order from this - object to the root). In general you probably don't need to do - much with this. - - `JoinNames`: set of strings of all join names already seen - during validation - - `DimensionNames`: set of strings of all dimension names already - seen - - `MeasureNames`: set of strings of all measure names already seen - - `MakeError(message, property)`: function to create a diagnostic - error. **You should use this to create the errors you return**. - The first arg is a string describing the error. The second arg - is an optional property name (e.g. "Name") to append to the path. - **Only use the name of a MetricView object property**, not any - arbitrary string. - Your function must return an enumerable of DiagnosticMessage objects. - If there are no problems, return an empty enumerable (e.g. `[]`). - If the object is invalid according to the rule (your conditional - checks), then return an enumerable with one or more errors created - with the context's `MakeError` method. You can inspect any properties - of the object (the first arg to your function) or anything in the - context object. - - + - Create a validation rule for a specific MetricView object type + An error that comes from ANTLR's parse of the grammar - - type of MetricView object the rule applies to. One of `MetricView`, - `MetricViewDimension`, `MetricViewJoin`, `MetricViewMeasure` - - name of the rule - category for the rule (you can use any text) - the message returned in the error if the object is invalid - a predicate function to determine if the object is invalid - a validation rule for use in a validator - - the function `isInvalid` is a simple predicate function that can - only inspect the properties of the current MetricView object. If you - need to inspect other context, you should use the other overload of - `MakeValidationRule` - - + - Create a validation rule for the root MetricView object + An error that comes from ANTLR's parse of the grammar - name of the rule - category for the rule (you can use any text) - the message returned in the error if the object is invalid - a predicate function to determine if the object is invalid - a validation rule for use in a validator - + + + + + + + - Create a validation rule for MetricViewJoin objects + The token that caused the parse failure. - name of the rule - category for the rule (you can use any text) - - the message returned in the error if the MetricViewJoin is invalid - - - a predicate function to determine if the MetricViewJoin is invalid - - a validation rule for use in a validator - + - Create a validation rule for MetricViewDimension objects + A semantic error from our visit and interpretation of metric view + logic - name of the rule - category for the rule (you can use any text) - - the message returned in the error if the MetricViewDimension is invalid - - - a predicate function to determine if the MetricViewDimension is invalid - - a validation rule for use in a validator + + - + - Create a validation rule for MetricViewMeasure objects + A semantic error from our visit and interpretation of metric view + logic - name of the rule - category for the rule (you can use any text) - - the message returned in the error if the MetricViewMeasure is invalid - - - a predicate function to determine if the MetricViewMeasure is invalid - - a validation rule for use in a validator + + + + + + + + - + - Converts a Databricks MetricView to a Tabular Model. + Result of parsing an expression string into an AST. + Always contains an expression (ANTLR recovers on errors). + Errors, if any, indicate where recovery occurred. - - The target TOM model to populate with tables, columns, measures, - and relationships - - The source MetricView to convert - Databricks host name for connection - Databricks HTTP path for connection - - A list of diagnostic messages indicating details of partial or full - failues. May be populated even when the method returns true and - does create a TOM model. - - - if true, abort on validation errors before creating any TOM objects - - - - + - Represents a dimension definition in a Databricks Metric View + Result of parsing an expression string into an AST. + Always contains an expression (ANTLR recovers on errors). + Errors, if any, indicate where recovery occurred. - - A dimension in a Metric View is a single field. There is no namespacing - or delineation of separate named collections of fields. - - + - The YAML name for the dimension field + AST node for a Databricks SQL expression. All variants carry a + covering their extent in the original source. - + - The YAML string representing the expression for this dimension, - either a field reference or scalar SQL expression. + AST node for a Databricks SQL expression. All variants carry a + covering their extent in the original source. - + - Lightweight value representation of a single column reference within a - Metric View expression. The parser populates only - when a single (non‑backticked) dot qualification is present (or when a - backticked token acts as table side). For undotted identifiers (or - identifiers whose dot is inside backticks) is the - empty string. Formatting policy is intentionally minimal: only quote - when required. + All direct children of the SqlExpression node. - - Optional table/alias portion (empty string if not present or not applicable). - - - The logical column identifier (never null; empty only for a - default-initialized value). - - + - Lightweight value representation of a single column reference within a - Metric View expression. The parser populates only - when a single (non‑backticked) dot qualification is present (or when a - backticked token acts as table side). For undotted identifiers (or - identifiers whose dot is inside backticks) is the - empty string. Formatting policy is intentionally minimal: only quote - when required. + Double-dispatch hook. Each concrete subtype calls + visitor.Visit(this), routing to the visitor overload that + accepts that specific shape. Implementors should never call + Accept directly; use Visit(node) on the visitor + instead, which routes through the base + Visit(SqlExpression) dispatcher. - - Optional table/alias portion (empty string if not present or not applicable). - - - The logical column identifier (never null; empty only for a - default-initialized value). - - + + + Collect this node and all descendants in left-to-right DFS order. + + + + + All descendants (excluding self) in left-to-right DFS order. + + + + + A column reference in a Databricks SQL expression — either a bare + column name or a table-qualified column name. + + + + + A column reference in a Databricks SQL expression — either a bare + column name or a table-qualified column name. + + + + + A SQL column name, e.g. `field` in `SELECT field FROM aTable`. + + + + + + + + + + + A qualified SQL column name: one or more dotted qualifiers + preceding the column. A 2-part ref (t.field) carries a + single qualifier; a multi-part snowflake path (a.b.field) + carries the full chain. The leaf qualifier () + is the column's immediate parent. + + + + + The dotted qualifiers preceding the column, in source order; + at least one. The last is the column's immediate parent. + + + + + The leaf qualifier — the column's immediate parent. For a + 2-part ref it is the only qualifier; for a multi-part path it + is the deepest one. + + + + Convenience constructor for the common 2-part ref. + + + + + + + + - Optional table/alias portion (empty string if not present or not applicable). + Case-insensitive equivalence check on the identifier values. - + - The logical column identifier (never null; empty only for a - default-initialized value). + Returns the table name for qualified refs, or + for unqualified refs. An unqualified column means different things depending + on where it appears in a metric view: in a field expression it refers to + the source table, while in a join ON clause it refers to the join table itself. - + - Render the reference using minimal quoting rules consistent with - the parser. Rules per side (table / column): + Render the reference using quoting rules as follows: - If empty -> omitted (for table) or empty string for column. - - Any token containing a dot is always backticked (avoids - ambiguity and preserves original backticked literals like - `orders.amount`). + - Any token containing a dot is always backticked (e.g. `a.b` is one part) + - Any token that is also a keyword is backticked, despite Databricks + allowing more permissive naming - Bare identifier pattern [A-Za-z_][A-Za-z0-9_]* AND not a reserved word -> emitted bare. - - Otherwise wrapped in backticks (parser strips existing backticks - so internal backticks not expected). - + + + The argument content of a function call — either a list of + expressions or a wildcard (*). + + + + Zero or more expression arguments. + + + Zero or more expression arguments. + + + Wildcard argument: *. + + - Lightweight, heuristic parser for extracting column identifiers from - Databricks Metric View expressions. Focused only on documented patterns - (bare identifiers and backtick-delimited identifiers) and deliberately - avoids full SQL parsing. Silent on malformed input, returning what can - be confidently identified. + A function call: SUM(amount), COUNT(*), + COUNT(DISTINCT col1, col2), etc. - + - Databricks reserved keywords that must be backtick-quoted to be - part of an identifier. + A function call: SUM(amount), COUNT(*), + COUNT(DISTINCT col1, col2), etc. - + + + + + + + - Attempt to determine if an expression is exactly a single column - reference. Returns a structured - (table + column). Supports: - * Bare identifier (non-reserved) - * Backticked identifier (any content except stray backticks) - * Single dotted qualification outside backticks (table.column) - with rules: - - At most one dot outside backticks (multi-dot => throw - ) - - No whitespace adjacent to the dot (spaced forms return false) - - BOTH sides must be bare (non-reserved) or individually - backticked. A bare reserved keyword is rejected on EITHER side; - backticks required to treat it literally. - * Entire expression backticked is treated as undotted column even - if inner text contains dots - All other forms return false (silent failure posture) except - multi-dot which throws. + A MEASURE reference: MEASURE(measure_name). References + another measure by name within a measure expression. + is a + rather than a so the inner name does + not surface as a in descendant walks. - + - Convenience wrapper over . + A MEASURE reference: MEASURE(measure_name). References + another measure by name within a measure expression. + is a + rather than a so the inner name does + not surface as a in descendant walks. - + + + + + + + + Binary operator: a + b, x AND y. + + + Binary operator: a + b, x AND y. + + + + + + + + + Unary operator: -a, NOT x. + + + Unary operator: -a, NOT x. + + + + + + + + - Extract distinct structured column references (first-seen order, - case-insensitive distinctness via linear scan). Dotted - (table.column) pairs are produced only when syntactically valid with - a single dot outside backticks and no whitespace adjacency. - Multi-dot contiguous sequences (a.b.c or variants mixing - backticked segments) throw . - Spaced-dot patterns yield separate undotted identifiers. Backticked - whole tokens with internal dots may still act as table side when - followed immediately by ".identifier" (e.g. - `orders.amount`.field -> table:"orders.amount", column:"field"). If - a backticked left side is followed by a bare reserved right side - (e.g. `orders`.CASE) both sides are skipped (zero references) to - avoid surfacing an unusable partial reference. Bare left reserved - (CASE.amount) is ignored and the right side is surfaced undotted. - Bare reserved right (orders.CASE) causes only the left side to be - surfaced undotted. Backticked right sides always pass regardless of - reserved status. Function heads (identifier immediately followed - by '(') are excluded. + CASE expression. Two forms share and + on the base. - + - Extract distinct structured column references with their source positions - (first-seen order, case-insensitive distinctness via linear scan). - Returns position information for each reference, enabling delimiter-aware parsing. + CASE expression. Two forms share and + on the base. - - Position spans include the entire reference from start to end, including: - - Backticks (if present) - - Table qualification and dot (if present) - - Column name - Follows the same parsing rules as . - - + + Searched: CASE WHEN cond THEN result ... END. + + + Searched: CASE WHEN cond THEN result ... END. + + + + + + + + + Simple: CASE expr WHEN value THEN result ... END. + + + Simple: CASE expr WHEN value THEN result ... END. + + + + + + + + + Parenthesized expression: (expr). + + + Parenthesized expression: (expr). + + + + + + + + - Parse one side of a potential dotted identifier (used only by - single-column detection). Bare reserved words are rejected on - BOTH sides; backticks required to treat them literally. + Literal value. Abstract intermediate; each concrete subtype carries + the typed payload extracted by the parser visitor — so consumers + don't have to re-parse the span text. - + - Remove all block (`/* ... */`) and line (`-- ...\n`) comments from - expr. Handles nested comments and normalizes line endings to `\n`. + Literal value. Abstract intermediate; each concrete subtype carries + the typed payload extracted by the parser visitor — so consumers + don't have to re-parse the span text. - Databricks SQL expression - - A string with all comments removed, with '\n' line endings, and all - leading and trailing whitespace removed (this leading and trailing - space is trimmed after comment removal). Leading whitespace on an - individual line is not removed. - - + + + + + String literal — payload with quote-escaping already resolved. + + + String literal — payload with quote-escaping already resolved. + + + + + - Represents the position and length of a column reference in source text. - Used for delimiter-aware parsing and error reporting. + Numeric literal. Covers DECIMAL, FLOAT, and REAL grammar + productions; the source-level kind difference doesn't survive + into the AST. - + - Zero-based index where the column reference starts in the source string. + Numeric literal. Covers DECIMAL, FLOAT, and REAL grammar + productions; the source-level kind difference doesn't survive + into the AST. - + + + + + Boolean literal — TRUE or FALSE. + + + Boolean literal — TRUE or FALSE. + + + + + + Null literal — NULL. + + + Null literal — NULL. + + + + + - Length of the column reference in characters, including any backticks or qualifiers. + Fallback for parse errors or unrecognized constructs. May wrap + structurally-recognized subtrees that were preserved while the + surrounding rule itself was not understood. - + - Zero-based index of the character after the last character of the reference. + Fallback for parse errors or unrecognized constructs. May wrap + structurally-recognized subtrees that were preserved while the + surrounding rule itself was not understood. - + + Terminal-Unknown convenience: no preserved subtrees. + + + + + + + + - Checks if a given position falls within this span. + Recognized-invalid construct with a specific diagnostic. + Unlike , this means we matched a known-bad + pattern and can describe the problem precisely. - Zero-based character position to check - True if position is within [Start, End), false otherwise - + - Creates a new span with the given start position and length. + Recognized-invalid construct with a specific diagnostic. + Unlike , this means we matched a known-bad + pattern and can describe the problem precisely. - Zero-based start position - Length in characters - - Thrown when start or length is negative - - + + + + - A column reference with associated source position information. - Used when you need both the parsed reference and its location in source text. + Default base for + implementations that want the standard abort-and-fallback semantics: + unimplemented Visit overloads throw + , and the outermost emit catches + once at the root and invokes . + + New-visitor minimum viable shape: derive, override + , ship. Override per-shape Visit + overloads incrementally for the constructs the visitor knows how + to translate; everything else falls through to the abort and + then to at the root. + + + Recursive code in concrete overloads should call + Visit(child), not child.Accept(this) — the + override routes through + Accept, keeping the dispatch detail in one place. An + raised from any nested + call propagates freely; do not catch it inside a Visit overload. + - + + + + - The parsed column reference (table name and column name). + Polymorphic-dispatch entry. Routes through the node's + to the matching + concrete-typed Visit overload. - + - The position and length of this reference in the source text. + Default abort. Overridden by derived classes that can translate + this construct structurally; otherwise the emit is unwound to the + root and takes over. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - The table name (may be empty for unqualified references). + Run the visitor over , catching any + raised at any depth and + returning instead. The single place where + the abort signal is consumed — concrete Visit overloads should + propagate, not catch. - + - The column name. + An identifier in Databricks SQL — bare or backtick-quoted. + Wraps a and provides clean access to + the identifier value without quoting characters. - + - Whether this is a qualified reference (table.column). + An identifier in Databricks SQL — bare or backtick-quoted. + Wraps a and provides clean access to + the identifier value without quoting characters. - + - Zero-based start position in source text. + The identifier text with backtick quoting removed if present. - + - Length in characters. + True if the identifier was backtick-quoted in the source. - + - Zero-based end position (exclusive). + A slice of original expression text. Start is inclusive, End is + exclusive. Carries a reference to the source so it is self-contained. - + - Creates a new reference with position information. + The raw text of this span in the original source. - The parsed column reference - The position span in source text - + - Creates a new reference with position information. + Extension members for ANTLR tokens to support keyword-as-identifier handling. - The parsed column reference - Zero-based start position - Length in characters + + Databricks does not enforce reserved keywords by default, so keywords + like KEY, TABLE, SCHEMA can appear as identifiers in dotted-name contexts. + See: https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-identifiers + - + - Checks if a given position falls within this reference's span. + All keyword strings from the lexer grammar, case-insensitive. + Derived from the vocabulary at startup — no manual maintenance. + Used by formatting logic to decide whether a bare identifier + needs backtick quoting. - Zero-based character position - True if position is within this span, false otherwise - + + + + + + + - Converts to SQL syntax (ignores position information). + True if the token is an identifier or a keyword that can serve + as an unquoted identifier in Databricks SQL. - SQL representation of the column reference - + - Represents the semantic analysis of a Databricks Metric View dimension. - Contains information about whether the dimension is a simple field reference - or a derived expression, along with field references and target table determination. + Returns the token's text with backtick quoting removed if present. - - - This is a discriminated union represented through inheritance. - Use pattern matching to determine whether the dimension is simple or derived, - then access the appropriate properties. - - - A simple dimension is one that directly references a single column from - the source or a joined table. A derived dimension contains a complex - expression that must be represented as a calculated column in the target. - - - + - The name of the dimension as it appears in the Metric View. + A condition/result pair inside a SQL CASE expression. - + - The complete expression string from the dimension. + A condition/result pair inside a SQL CASE expression. - + - Analysis result for a simple dimension that directly references a column. + This class provides an empty implementation of , + which can be extended to create a visitor which only needs to handle a subset + of the available methods. + The return type of the visit operation. - + - The single field reference that this dimension maps to. + Visit a parse tree produced by . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - Analysis result for a derived dimension with a complex expression - that must be represented as a calculated column. + Visit a parse tree produced by the orExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - All column references found in the dimension expression. + Visit a parse tree produced by the columnRefExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - The target table for the derived field. - This is determined by the source of the referenced fields: - - If all fields are from a single dimension, the derived field goes on that dimension - - If fields are from multiple tables or the source, the derived field goes on the fact + Visit a parse tree produced by the measureRefExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - Represents the target table for a derived field. + Visit a parse tree produced by the parenExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - The derived field should be placed on the fact table. + Visit a parse tree produced by the searchedCaseExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - The derived field should be placed on a specific dimension table. + Visit a parse tree produced by the concatExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - The name of the dimension (join alias) where the derived field belongs. + Visit a parse tree produced by the simpleCaseExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - Extension methods for analyzing and converting Databricks Metric View dimensions. + Visit a parse tree produced by the notExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - Analyzes a dimension expression to determine if it's a simple field reference - or a complex derived expression, and identifies the target table. + Visit a parse tree produced by the unaryMinusExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + - The Metric View dimension to analyze - - A DimensionAnalysis indicating whether the dimension is simple or derived, - with appropriate field references and target table information. - + The parse tree. + The visitor result. - + - Determines the target table for a derived field based on the tables - referenced by its constituent fields. + Visit a parse tree produced by the addExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + - The column references from the expression - - A DerivedFieldTarget indicating whether the field should go on the fact - or a specific dimension table. - - - Target determination rules: - - All fields from source/empty table -> Fact - All fields from a single dimension -> that Dimension - Fields from multiple dimensions -> Fact - Mix of source and dimension fields -> Fact - - + The parse tree. + The visitor result. - + - Parses join fields from MetricViewJoin. Handles 'Using' and 'On'. - Returns (factFieldKey, dimensionFieldKey) tuple or null if parsing fails. + Visit a parse tree produced by the literalExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - Extracts LHS and RHS from an equality join expression + Visit a parse tree produced by the compExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + - - - - We do not handle inequality joins or fields with spaces in their names. - We expect fully qualified names on both sides. - See: https://docs.databricks.com/aws/en/metric-views/yaml-ref#joins - + The parse tree. + The visitor result. - + - Creates a SourceFragment for a join's dimension source + Visit a parse tree produced by the functionCallExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - - - Pattern:
- ^\\s*(?<table1>\\w+)\\.(?<field1>\\w+)\\s*=\\s*(?<table2>\\w+)\\.(?<field2>\\w+)\\s*$
- Explanation:
- - ○ Match if at the beginning of the string.
- ○ Match a whitespace character atomically any number of times.
- ○ "table1" capture group.
- ○ Match a word character atomically at least once.
- ○ Match '.'.
- ○ "field1" capture group.
- ○ Match a word character atomically at least once.
- ○ Match a whitespace character atomically any number of times.
- ○ Match '='.
- ○ Match a whitespace character atomically any number of times.
- ○ "table2" capture group.
- ○ Match a word character atomically at least once.
- ○ Match '.'.
- ○ "field2" capture group.
- ○ Match a word character atomically at least once.
- ○ Match a whitespace character greedily any number of times.
- ○ Match if at the end of the string or if before an ending newline.
-
-
+ + + Visit a parse tree produced by the mulExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + + The parse tree. + The visitor result. - + - Represents the semantic analysis of a Databricks Metric View measure. - Contains information about the measure's type, aggregation function, - and field references extracted from the expression. + Visit a parse tree produced by the unaryPlusExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + - - - This is a discriminated union represented through inheritance. - Use pattern matching or type checks to determine whether the measure - is simple or calculated, then access the appropriate properties. - - - A simple measure is one that can be translated to a basic aggregation in the - target platform. It must: - - - Use a supported aggregation function (SUM, COUNT, MIN, MAX, AVG, COUNT DISTINCT) - Reference exactly one field - Have no complex expressions (arithmetic, nested functions, etc.) - - - Calculated measures contain more complex expressions that cannot be automatically - translated and must be handled platform-specifically. - - + The parse tree. + The visitor result. - + - The name of the measure as it appears in the Metric View. + Visit a parse tree produced by the andExpr + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - The complete expression string from the measure. + Visit a parse tree produced by . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - Analysis result for a simple measure that can be directly translated - to a platform-specific aggregation. + Visit a parse tree produced by . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - The aggregation type (SUM, COUNT, MIN, MAX, AVG, DISTINCT COUNT). + Visit a parse tree produced by . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - The single field reference that this measure aggregates. + Visit a parse tree produced by . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - Analysis result for a calculated measure with a complex expression - that cannot be automatically translated. + Visit a parse tree produced by the multiDotColumn + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - All column references found in the measure expression. - May contain zero or more references depending on the expression. + Visit a parse tree produced by the qualifiedColumn + labeled alternative in . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - Analyzes a Databricks Metric View measure to determine its semantic properties. + Visit a parse tree produced by the unqualifiedColumn + labeled alternative in . + + The default implementation returns the result of calling + on . + - The measure to analyze - - A - either - or . Use pattern matching or type checks - to determine which and access the appropriate properties. - - - - A simple measure is one that can be translated to a basic aggregation in the - target platform. It must: - - - Use a supported aggregation function (SUM, COUNT, MIN, MAX, AVG, COUNT DISTINCT) - Reference exactly one field - Have no complex expressions (arithmetic, nested functions, etc.) - - - Calculated measures contain more complex expressions that cannot be automatically - translated and must be handled platform-specifically. - - + The parse tree. + The visitor result. - + - Parses an aggregation function name from a measure expression and - maps it to AggregationType. - Supports: SUM, COUNT, MIN, MAX, AVG/AVERAGE, COUNT(DISTINCT ...) + Visit a parse tree produced by . + + The default implementation returns the result of calling + on . + + The parse tree. + The visitor result. - + - Determines if a measure expression is a simple aggregation over a - single field. A simple aggregation has the pattern: - AGG_FUNCTION(field_reference) or COUNT(DISTINCT field_reference) + Visit a parse tree produced by . + + The default implementation returns the result of calling + on . + - - - Uses a two-step validation approach: - 1. Extract all column references from the expression using robust ColumnReferenceParser - 2. Validate that expression structure is exactly FUNCTION(column) with no extra content - - - This approach handles all edge cases while preserving original quoting/formatting: - - - Backticked identifiers with any content (including parentheses) - Reserved word validation - Qualified references (table.column) - Superfluous quoting (validates structure, not exact string match) - DISTINCT COUNT patterns - - + The parse tree. + The visitor result. - - - Pattern:
- ^(SUM|COUNT|MIN|MAX|AVG|AVERAGE)\\s*\\(
- Options:
- RegexOptions.IgnoreCase
- Explanation:
- - ○ Match if at the beginning of the string.
- ○ 1st capture group.
- ○ Match with 4 alternative expressions.
- ○ Match a sequence of expressions.
- ○ Match a character in the set [Ss].
- ○ Match a character in the set [Uu].
- ○ Match a character in the set [Mm].
- ○ Match a sequence of expressions.
- ○ Match a character in the set [Cc].
- ○ Match a character in the set [Oo].
- ○ Match a character in the set [Uu].
- ○ Match a character in the set [Nn].
- ○ Match a character in the set [Tt].
- ○ Match a sequence of expressions.
- ○ Match a character in the set [Mm].
- ○ Match with 2 alternative expressions.
- ○ Match a sequence of expressions.
- ○ Match a character in the set [Ii\u0130].
- ○ Match a character in the set [Nn].
- ○ Match a sequence of expressions.
- ○ Match a character in the set [Aa].
- ○ Match a character in the set [Xx].
- ○ Match a sequence of expressions.
- ○ Match a character in the set [Aa].
- ○ Match a character in the set [Vv].
- ○ Match with 2 alternative expressions.
- ○ Match a character in the set [Gg].
- ○ Match a sequence of expressions.
- ○ Match a character in the set [Ee].
- ○ Match a character in the set [Rr].
- ○ Match a character in the set [Aa].
- ○ Match a character in the set [Gg].
- ○ Match a character in the set [Ee].
- ○ Match a whitespace character atomically any number of times.
- ○ Match '('.
-
-
+ + + Visit a parse tree produced by . + + The default implementation returns the result of calling + on . + + + The parse tree. + The visitor result. - - - Pattern:
- ^COUNT\\s*\\(\\s*DISTINCT\\s+
- Options:
- RegexOptions.IgnoreCase
- Explanation:
- - ○ Match if at the beginning of the string.
- ○ Match a character in the set [Cc].
- ○ Match a character in the set [Oo].
- ○ Match a character in the set [Uu].
- ○ Match a character in the set [Nn].
- ○ Match a character in the set [Tt].
- ○ Match a whitespace character atomically any number of times.
- ○ Match '('.
- ○ Match a whitespace character atomically any number of times.
- ○ Match a character in the set [Dd].
- ○ Match a character in the set [Ii\u0130].
- ○ Match a character in the set [Ss].
- ○ Match a character in the set [Tt].
- ○ Match a character in the set [Ii\u0130].
- ○ Match a character in the set [Nn].
- ○ Match a character in the set [Cc].
- ○ Match a character in the set [Tt].
- ○ Match a whitespace character atomically at least once.
-
-
+ + + This interface defines a complete generic visitor for a parse tree produced + by . + + The return type of the visit operation. - + - Identifies the aggregation pattern and returns where content starts. + Visit a parse tree produced by . - The expression to analyze - The position where content starts after the aggregation pattern, or null if no pattern found + The parse tree. + The visitor result. - + - Validates that the closing paren is at the end of the expression. - Only whitespace is allowed after the closing paren. + Visit a parse tree produced by the orExpr + labeled alternative in . - The expression to validate - The index of the closing parenthesis - True if closing paren is properly positioned, false otherwise + The parse tree. + The visitor result. - + - Extracts and trims content between two positions. + Visit a parse tree produced by the columnRefExpr + labeled alternative in . - The expression containing the content - Start position (inclusive) - End position (exclusive) - The extracted and trimmed content, or null if invalid range or empty + The parse tree. + The visitor result. - + - Finds the matching closing paren for an opening paren at given index. - Handles nested parentheses correctly and skips parentheses inside - excluded spans (e.g., backticked identifiers). + Visit a parse tree produced by the measureRefExpr + labeled alternative in . - The string to search within - The index of the opening parenthesis - - Spans to exclude from paren matching (e.g., backticked identifiers). - Any parentheses within these spans are ignored. - - The index of the matching closing parenthesis, or -1 if not found + The parse tree. + The visitor result. - + - Checks if a position falls within any excluded span. + Visit a parse tree produced by the parenExpr + labeled alternative in . - The position to check - The list of excluded spans - True if position is within any span, false otherwise + The parse tree. + The visitor result. - + - Extracts the content inside aggregation function parentheses. - For DISTINCT COUNT, treats DISTINCT as part of the aggregation spec. - AGG_FUNCTION(content) -> returns "content" - COUNT(DISTINCT content) -> returns "content" (DISTINCT is part of - aggregation). - Returns null if pattern doesn't match or has invalid structure. + Visit a parse tree produced by the searchedCaseExpr + labeled alternative in . - - Uses position-aware column reference parsing to correctly handle - parentheses inside backticked identifiers (e.g., `Field) Name`). - - The aggregation expression to parse - The content between parentheses, or null if extraction fails + The parse tree. + The visitor result. - + - Validates that expression has the structure of a simple aggregation. - Extracts content between parens and validates it's a single column reference. - Since ExtractAggregationContent now treats DISTINCT as part of the aggregation, - no special handling is needed for DISTINCT COUNT. + Visit a parse tree produced by the concatExpr + labeled alternative in . + The parse tree. + The visitor result. - + - Converts a Databricks Metric View Measure to a MeasureFragment, - handling field lookup/creation and determining measure type. + Visit a parse tree produced by the simpleCaseExpr + labeled alternative in . - The Metric View measure to convert - The Metric View containing the measure - The fact fragment to associate the measure with - The model builder for fragment management - A configured MeasureFragment ready to be added to the model - - - This method bridges the gap between measure analysis and fragment creation: - - - Analyzes the measure using - For simple measures: ensures field exists and creates aggregation fragment - For calculated measures: creates expression-based fragment - - - Simple measures get a FieldKey and AggregationType. - Calculated measures get an Expression and no AggregationType. - - + The parse tree. + The visitor result. - + - Determines the appropriate DataType for a simple measure based on its aggregation type. - Count and DistinctCount produce integer results; other aggregations produce Decimal. + Visit a parse tree produced by the notExpr + labeled alternative in . + The parse tree. + The visitor result. - + - Extension methods for traversing a MetricView object graph by canonical - path string. + Visit a parse tree produced by the unaryMinusExpr + labeled alternative in . + The parse tree. + The visitor result. - + - Parses a 3-part SQL table name into its constituent parts. + Visit a parse tree produced by the addExpr + labeled alternative in . - The source string to parse - Database, schema, and table names if parsing succeeds; null otherwise + The parse tree. + The visitor result. - + - Creates a SourceFragment with queryable properties if the source is - a 3-part table name, otherwise creates one with a query property. + Visit a parse tree produced by the literalExpr + labeled alternative in . - The source string to create a fragment for - The fragment key - The fragment name - The source system key reference - A configured SourceFragment + The parse tree. + The visitor result. - + - Removes backticks from identifier if present. + Visit a parse tree produced by the compExpr + labeled alternative in . - The identifier to clean - The cleaned identifier without surrounding backticks - - - - Pattern:
- ^(?<database>[a-zA-Z_][a-zA-Z0-9_]*|`[^`]+`)\\.(?<schema>[a-zA-Z_][a-zA-Z0-9_]*|`[^`]+`)\\.(?<table>[a-zA-Z_][a-zA-Z0-9_]*|`[^`]+`)$
- Options:
- RegexOptions.Compiled
- Explanation:
- - ○ Match if at the beginning of the string.
- ○ "database" capture group.
- ○ Match with 2 alternative expressions.
- ○ Match a sequence of expressions.
- ○ Match a character in the set [A-Z_a-z].
- ○ Match a character in the set [0-9A-Z_a-z] atomically any number of times.
- ○ Match a sequence of expressions.
- ○ Match '`'.
- ○ Match a character other than '`' atomically at least once.
- ○ Match '`'.
- ○ Match '.'.
- ○ "schema" capture group.
- ○ Match with 2 alternative expressions.
- ○ Match a sequence of expressions.
- ○ Match a character in the set [A-Z_a-z].
- ○ Match a character in the set [0-9A-Z_a-z] atomically any number of times.
- ○ Match a sequence of expressions.
- ○ Match '`'.
- ○ Match a character other than '`' atomically at least once.
- ○ Match '`'.
- ○ Match '.'.
- ○ "table" capture group.
- ○ Match with 2 alternative expressions.
- ○ Match a sequence of expressions.
- ○ Match a character in the set [A-Z_a-z].
- ○ Match a character in the set [0-9A-Z_a-z] atomically any number of times.
- ○ Match a sequence of expressions.
- ○ Match '`'.
- ○ Match a character other than '`' atomically at least once.
- ○ Match '`'.
- ○ Match if at the end of the string or if before an ending newline.
-
-
+ The parse tree. + The visitor result.
- + - Represents a join definition in a Databricks Metric View + Visit a parse tree produced by the functionCallExpr + labeled alternative in . + The parse tree. + The visitor result. - + - Name of the joined table + Visit a parse tree produced by the mulExpr + labeled alternative in . + The parse tree. + The visitor result. - + - Source table or query for the join + Visit a parse tree produced by the unaryPlusExpr + labeled alternative in . + The parse tree. + The visitor result. - + - Optional SQL boolean expression for the join condition + Visit a parse tree produced by the andExpr + labeled alternative in . + The parse tree. + The visitor result. - + - Optional list of column names to use for join + Visit a parse tree produced by . + The parse tree. + The visitor result. - + - Child joins that are part of this join definition + Visit a parse tree produced by . + The parse tree. + The visitor result. - + - Represents a measure definition in a Databricks Metric View + Visit a parse tree produced by . + The parse tree. + The visitor result. - + - Aggregate SQL expression that defines the measure + Visit a parse tree produced by . + The parse tree. + The visitor result. - + - Internal abstract base class for all MetricView domain objects. - Implements both the public marker interface and the internal visitable interface. + Visit a parse tree produced by the multiDotColumn + labeled alternative in . + The parse tree. + The visitor result. - + - Accepts a visitor that produces a result of type TResult. - Must be implemented by derived domain objects. + Visit a parse tree produced by the qualifiedColumn + labeled alternative in . + The parse tree. + The visitor result. - + - Validator for MetricView objects after YAML deserialization. Collects - all validation errors. Intentionally a very limited set of rules. We - don't own the metric view spec, so this should be somewhat permissive - and only return an error for very obvious issues that should be - impossible. + Visit a parse tree produced by the unqualifiedColumn + labeled alternative in . + The parse tree. + The visitor result. - + - Handles deserialization of Databricks Metric Views from YAML + Visit a parse tree produced by . + The parse tree. + The visitor result. - + - Deserializes a YAML string into a MetricView object + Visit a parse tree produced by . - The YAML string to deserialize - A MetricView object - If YAML is invalid or cannot be deserialized - - If semantic validation fails, contains one or more - for each validation error. - + The parse tree. + The visitor result. - + - Handles serialization of Databricks Metric Views to YAML + Visit a parse tree produced by . + The parse tree. + The visitor result. - + - Serializes a MetricView object to a YAML string + Renders sources to Power Query (M) + for inclusion in a Tabular partition. Mirrors the structure produced by + the Power BI Databricks connector: + Databricks.Catalogs(host, path) followed by + Database / Schema / Object navigation through the catalog tree. - The MetricView object to serialize - A YAML string representation - + + + + + + + - Represents a Databricks Metric View definition + Translates an MV spec into a TOM + FormatString value. TOM FormatString uses VBA-style + FORMAT_STRING semantics (per the MDX FORMAT_STRING contents spec): + lowercase mm for month, lowercase nn for minute (to avoid + month/minute ambiguity), and VBA-style named formats + (Long Date, Short Date, Long Time, + Short Time) which render locale-aware at query time. + + The translator returns a tuple of the resulting format string and + any diagnostics. Cleanly-translatable cases emit a precise string + with no diagnostics. Imperfect cases emit a best-effort string and + a + diagnostic. Cases without any sensible equivalent fall back to ISO + date/time or empty string, with a diagnostic. + - + - Version of the metric view specification + Maps an ISO 4217 currency code to a FormatString prefix. Single- + character symbols (e.g., $, ) embed directly; + multi-character symbols (e.g., kr, R$) and unknown + codes wrap in quotes with a trailing space to separate the label + from the digits. - + - The source data for the metric view, playing the role of a single fact + ISO 4217 code → currency symbol, derived from OS locale data via + . Same source the validation layer uses + for KnownCurrencyCodes; first-seen wins so the mapping is + deterministic regardless of culture enumeration order. - + - Optional SQL boolean expression that applies to all queries; a WHERE + Decides a field's or measure's from an MV + spec, the field's existing DataType, key status, + and aggregation context. The two entry points encode the asymmetry + between field inference (which preserves an existing non-String type + and respects key precedence) and measure inference (which always + produces a concrete result type from the aggregation default). - + - Optional join definitions for the metric view + Format-driven DataType opinion shared by both field and measure + inference. Returns null when no format opinion applies + (null or unknown format). - - A join is also the place where dimension sources are defined; this - information exists nowhere else. - - + - Array of dimension definitions + Returns the DataType to apply to a field, or null when no + change is needed. Precedence: date-family format > key Integer > + non-date format > String-promotion-from-aggregation. Returning + null tells the caller to leave the field's current DataType + untouched (which preserves any earlier opinion, e.g., a key + Integer set by Visit(Join) or a format-driven type set by an + earlier dim visit). - + - Array of measure definitions + Returns the DataType to apply to a measure. Format opinion wins + over aggregation default; when no format is declared, Count and + DistinctCount produce Integer measures and other aggregations + (or no aggregation — Calculated / Placeholder measures) produce + Decimal. @@ -2645,6 +6038,30 @@ implementation of MetricViewValidationVisitor. + + + Known ISO 4217 currency codes derived from OS locale data via + RegionInfo and based on .NET System.Globalization. + + + > + This is NOT a complete ISO 4217 table — it covers active currencies + that map to a real OS locale/region, but omits historical codes + (e.g. DEM) and non-country codes (e.g. XAU for gold, XDR for SDR). + Because of these limitations, this should ONLY be used for + warnings, never errors. + + + + + List of rules used to validate a Metric View. Used in deserialization. + + + These rules are derived from a reading of public Databricks + documentation at time of Semantic Bridge development. There are no + guarantees that these are complete. + + Returns validation rules for the specified type. @@ -2711,9 +6128,9 @@ traversing any child joins. - + - Validates a MetricViewDimension object by applying all applicable rules. + Validates a MetricView Field object by applying all applicable rules. @@ -2731,19 +6148,42 @@ type-appropriate rules to be applied to `target` The object to validate - + Reset all state in the validation context to ensure idempotent operation. + Filters rules by version constraint against the view's spec version. - + - Read-only view of validation context state for consumption by validation rules + Legacy rule collection that lets ValidationRule<Dimension> + instances (produced by MakeValidationRuleForDimension or by + MakeValidationRule<Dimension>) fire against + instances. Generic invariance prevents the + canonical _fieldRules filter from picking them up; this + collection bridges that gap until is + removed. + + + + + Populates from the same + source the canonical rule lists use, filtered by version constraint. + Called from Reset. - + - Read-only view of the stack tracking the current path in the object graph. + Applies every rule to a field. The + cast (Dimension)field is safe because every field is + constructed as a at runtime via + 's legacy factory. Called from + Visit(Field). + + + + + Read-only view of validation context state for consumption by validation rules @@ -2751,26 +6191,49 @@ Read-only view of join names seen during traversal for uniqueness validation - + - Read-only view of dimension names seen during traversal for + Read-only view of field names seen during traversal for uniqueness validation + + + Legacy name for . Returns the same set; + retained as a default-implemented alias so external rule lambdas + that read ctx.DimensionNames still compile. + + Read-only view of measure names seen during traversal for uniqueness validation - + - Creates a diagnostic error message with the current path context + Creates a diagnostic error message with the current path context. + Errors should indicate a problem that will prevent further correct + functioning. + unique identifying code The error message + The Metric View object currently being validated The property that has the error A DiagnosticMessage with Error severity + + + Creates a diagnostic warning message with the current path context. + Warnings should indicate a problem that deserves attention, but + doesn't necessarily prevent correct functioning. + + unique identifying code + The warning message + The Metric View object currently being validated + The property that has the warning + A DiagnosticMessage with Warning severity + Mutable validation context for internal validation orchestration. @@ -2779,19 +6242,14 @@ validation framework. - - - Mutable stack tracking the current path in the object graph - - Mutable set of join names seen during traversal for uniqueness validation - + - Mutable set of dimension names seen during traversal for + Mutable set of field names seen during traversal for uniqueness validation @@ -2813,26 +6271,14 @@ capabilities within the same assembly. - - - Creates a diagnostic error message with the current path context - - The error message - The property that has the error - A DiagnosticMessage with Error severity + + - - - Resets the context state for a new validation run, ensuring idempotency - internal only. Public consumers only see the readonly interface. - + + - - - Combines the path stack elements into a dot-notation path string - - Optional property to append to the path - The combined path as a string + + @@ -2853,6 +6299,11 @@ Gets the type of object this rule is designed to validate. + + + Version constraint for this rule. Defaults to . + + Validates the target object if it matches the expected type. @@ -2864,188 +6315,387 @@ empty collection if validation passes - + - Maps Abstract Model DataType to Tabular Object Model DataType. + Declares which metric view spec versions a validation rule applies to. + Used by to filter rules at traversal time. - + - Extension methods for mapping SimpleMeasure to Tabular Model DAX + Matches all versions. This is the default for rules that are not version-specific. - + - Generates a DAX expression for a SimpleMeasure. - Returns a diagnostic message on failure. + Matches versions greater than or equal to the specified minimum. + Unknown versions are treated as greater than all known versions. - The SimpleMeasure to generate DAX for - The TOM model context - - map of Abstract model names to names of created tables in TOM model - - The generated DAX expression, or null on failure - A diagnostic message if validation fails, otherwise null - + + + Matches versions greater than or equal to the specified minimum. + Unknown versions are treated as greater than all known versions. + + + + + Matches only the explicitly listed versions. + + + + + Matches only the explicitly listed versions. + + + + + Returns true if this constraint matches the given version string. + + + + + Composition root for first-party + entries. New source + platforms register their providers here; consumers wanting to override + a default re-register against the same source-system type after calling + . + + + + + Build a registry populated with every first-party Tabular provider. + + + + + Emits DAX for an body. Structural + shapes the emitter recognizes (currently + ) produce clean DAX; + anything else falls back through + 's abort path to + , which wraps the original source as a DAX + comment block via the source measure's Expressions dict. + + + + + Render to DAX in the context of + . + returns the emitted + for a given , or + null if the referenced measure has not been emitted yet; + is the canonical + bracketed-and-escaped DAX reference form. + is the analogous lookup for + → + , consulted by + emission; + is the canonical + single-quoted-and-escaped DAX table reference form. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Maps Abstract Model DataType to Tabular Object Model DataType. + + + - Maps an AggregationType to the corresponding DAX function name. - Returns null if the aggregation type is not supported. + Non-generic facet of used + by for type-erased + dispatch. Providers should not declare this interface directly - derive + from , which supplies + the non-generic dispatch by switching over and + calling the typed overloads on + . - + - Extension methods for mapping SimpleRelationship to Tabular Model relationships. + Render an M (Power Query) expression for + against . - + - Creates a TOM relationship from a SimpleRelationship. - Returns a diagnostic message on failure. + Contract implemented by each source platform to render its sources into + Tabular-target M expressions. One implementation per concrete + type; registered into + at composition time. + + The concrete source-system type this provider handles. + - + - Determines the direction of a relationship based on cardinality. - Returns the many-side field, one-side field, and their respective tables. + Render M for a against + . - + - Maps Abstract Model FilterDirection to TOM CrossFilteringBehavior. + Render M for a against + . - Visitor that maps from an Abstract Model to a Tabular (TOM) Model. - Traverses the Abstract Model object graph and mutates the provided TOM Model. + Visitor that maps an abstract-model to a Tabular + (TOM) . The source Root, target TOM + model, and are supplied at + construction; Visit(Root) drives the traversal and returns the + populated TOM model. carries scoped context + from Visit(Fact) down to Visit(QueryableSource); the + registry resolves M-rendering for each visited source. - - Creates a new visitor that will populate the specified Tabular model. - - The TOMWrapper Model to populate with Abstract Model data - + - Visitor that maps from an Abstract Model to a Tabular (TOM) Model. - Traverses the Abstract Model object graph and mutates the provided TOM Model. + Visitor that maps an abstract-model to a Tabular + (TOM) . The source Root, target TOM + model, and are supplied at + construction; Visit(Root) drives the traversal and returns the + populated TOM model. carries scoped context + from Visit(Fact) down to Visit(QueryableSource); the + registry resolves M-rendering for each visited source. - - Creates a new visitor that will populate the specified Tabular model. - - The TOMWrapper Model to populate with Abstract Model data - + - Entry point for generic visitor pattern orchestration. - Delegates to the visitable object's Accept method for double dispatch. + Polymorphic + lookup. Routes to + and to + so AAST nodes carrying an + opaque ITableId (e.g., + ) can resolve uniformly + without caring which kind of table they target. - + - Visits the Root of the Abstract Model. - Traverses Facts, Dimensions, Perspectives, and SecurityModel. + Builds the inert DAX comment body for a measure whose source carried + an unsupported window spec (see #6347). Documents the original source + expression, the attempted (window-unaware) DAX translation when one is + available, and the window spec re-serialized as YAML. The whole body + is commented out so the measure returns BLANK until the user authors + it. + + is the result of + : clean DAX when the body translated + structurally, otherwise a fallback comment block (signaled by an + untranslated / no-expression diagnostic in + ). Only the former is surfaced + as a translation; the latter collapses to "not available". + - + - Visits a Fact. - Creates a TOM Table and sets up context for child visitors. - Traverses Source, Fields, Measures, and DimensionReferences. + Diagnostic codes and factories emitted by the abstract→Tabular + visitor. Centralizes severity, code, message template, and path + shape so visitor methods stay focused on traversal. Messages are + inlined here for now; a future pass can lift them into the resource + file alongside the rest of the visitor's localized strings. - + - Visits a Dimension. - Creates a TOM Table and sets up context for child visitors. - Traverses Source , Attributes, and Hierarchies. + Emitted when a 's + does not resolve to a + in the current Root (id missing or + points at a non-source field type). The column is still emitted + with the diagnostic code itself as the SourceColumn + placeholder, so the model remains buildable and the broken + column surfaces at refresh time with an obviously identifiable + marker. - + - Visits a DimensionReference. - Sets context for relationship visitor and delegates relationship creation. + Emitted alongside a successfully-rendered + whose source-flavor expression could not be translated to DAX. The + original expression is preserved as a comment in the + CalculatedColumn.Expression (so the model is buildable and + round-trippable) and this diagnostic surfaces to the user that the + column will return blank values at refresh unless they author DAX + for it. - + - Visits a SimpleMeasure. - Generates a DAX expression based on aggregation type and creates a TOM measure. + Emitted alongside a placeholder calculated column when a + has no entries in its + dictionary. The visitor + still emits the column (with a one-line "no source expression" + comment as the body) so the model is buildable and the field's + intended slot is visible; this diagnostic surfaces that the + column has no source SQL to translate and will return blank + values at refresh. - + - Visits a CalculatedMeasure. + Emitted when a carries an + (a windowed / + cumulative / semiadditive spec from the source). The windowing + semantics aren't representable yet, so the measure is left inert with + a comment documenting the original SQL, the window-unaware DAX, and + the window spec. This diagnostic surfaces that the measure needs + manual authoring before it returns correct values. + TODO (#6347): remove once windowed measures translate natively. - + - Visits a SimpleRelationship. - Creates a TOM relationship between the current table and the dimension table. + Emitted when a 's AAST root is an + whose + does not + resolve to a TOM-emitted measure name at emit time. The visitor + falls back to the platform-keyed Expressions dict (or + placeholder body if empty); this diagnostic surfaces the + resolution failure so the user can correct the upstream reference. - + - Visits a RolePlayingRelationship. - Currently not implemented - role-playing dimensions require special handling. + Emitted when any AAST node carrying an + reference cannot resolve its target to a TOM-emitted table at + emit time. Tables are normally visited before measures so this + should not happen — surface it loudly when it does so the wiring + gap is identifiable. The visitor falls back to the platform-keyed + verbatim source. - + - Visits a DimensionAttribute. - Creates a data column using the attribute's name and referencing - the source field, or delegates to DerivedField visitor if underlying field is derived. + Emitted when any AAST node carrying a + reference cannot resolve its target to a TOM-emitted column at + emit time. Fields are normally visited before measures so this + should not happen — surface it loudly when it does so the wiring + gap is identifiable. The visitor falls back to the platform-keyed + verbatim source. - + - Visits a Hierarchy. - Traverses Levels (which are DimensionAttributes). + Emitted alongside a successfully-rendered + whose source-flavor expression could not be translated to DAX. The + original expression is preserved as a comment in the + Measure.Expression (so the model is buildable and + round-trippable) and this diagnostic surfaces to the user that the + measure will return blank values until they author DAX for it. - + - Visits a Field. - Fields on sources are metadata and don't create columns. - Fields on facts create data columns. + Emitted alongside a placeholder TOM measure when a + has no entries in its + dictionary. The + visitor still emits the measure (with a one-line "no source + expression" comment as the body) so the model is buildable and the + measure's intended slot is visible; this diagnostic surfaces that + the measure has no source SQL to translate and will return blank + values until DAX is authored. - + - Visits a DerivedField. - Creates a TOM CalculatedColumn with the original expression in a DAX comment. + Emitted when a has both sides set to + Many or both sides set to One. The relationship is + skipped so the model stays buildable; the diagnostic surfaces the + invalid pair so the user can correct one side. - + - Visits a QueryableSource. - Creates a TOM M partition with generated expression from the source system. - Must be called within a table context (set by Visit(Fact) or Visit(Dimension)). + Emitted when one of a 's + endpoints does not resolve to a TOM column + (the field was never visited, or points at a + with no table-side counterpart). The relationship is skipped so the + model stays buildable. - + - Visits a QuerySource. - Creates a TOM M partition with generated expression from the source system. - Must be called within a table context (set by Visit(Fact) or Visit(Dimension)). + Default base class for + implementations. Provides "not supported" defaults for every per-source + overload — implementations override only the source types they support. + Centralises the non-generic dispatch over so + can resolve providers by + runtime type without per-platform + switching. + + The concrete source-system type this provider handles. + + + + + + + - + - Visits a Perspective. - Note: ModelObjects in perspectives are not visitable, so we don't traverse them. + Registry of instances keyed by the + concrete runtime type they handle. The + Abstract-to-Tabular visitor consults this registry to resolve + M-rendering for each source it visits; one entry per source platform. + + Re-registering the same replaces the prior + entry — the natural mechanism for consumer-side overrides of first-party + defaults supplied by . + - + - Visits a SecurityModel. - Traverses Roles. + Register a typed provider for source-system type + , replacing any prior registration for the + same type. - + - Visits a Role. + Resolve a provider by the runtime type of + and render M for . + + Thrown when no provider is registered for 's + runtime type. + @@ -3303,6 +6953,11 @@ Looks up a localized string similar to Multiple join conditions are not yet supported. Join '{0}' has {1} conditions.. + + + Looks up a localized string similar to No relationship created for join '{0}' (on: '{1}'). The join expression could not be resolved to fact and dimension fields.. + + Looks up a localized string similar to We do not currently support snowflake joins. Join: {0}. @@ -3379,74 +7034,160 @@ Looks up a localized string similar to Measure '{0}' contains a complex expression that could not be translated to DAX. The original expression has been preserved as a comment.. + + + Looks up a localized string similar to (no DAX translation available). + + + + + + Looks up a localized string similar to Measure '{0}' references field id {1} which has not been emitted as a column; falling back to verbatim source.. + + + + + Looks up a localized string similar to Calculated measure '{0}' has no source expressions; emitting placeholder body.. + + + + + Looks up a localized string similar to Measure '{0}' references measure id {1} which has not been emitted; falling back to verbatim source.. + + + + + Looks up a localized string similar to Measure '{0}' contains an untranslatable expression; original preserved as a comment.. + + + + + Looks up a localized string similar to Measure '{0}' uses a window specification that is not currently supported; it has been left inert with the original definition preserved as a comment.. + + + + + Looks up a localized string similar to ReferenceField '{0}' references field id {1} which is missing or not a SourceField; emitting column with diagnostic code as SourceColumn placeholder.. + + + + + Looks up a localized string similar to Relationship '{0}' {1} endpoint (field id {2}) is missing or has no table-side column. Relationship skipped.. + + + + + Looks up a localized string similar to Relationship '{0}' has invalid cardinality ({1}/{2}); expected one Many and one One. Relationship skipped.. + + + + + Looks up a localized string similar to Measure '{0}' references table id {1} which has not been emitted; falling back to verbatim source.. + + Looks up a localized string similar to Multiple dots in column reference are unsupported (multi-part qualification).. - + + + Looks up a localized string similar to Length must be non-negative. + + + + + Looks up a localized string similar to Start position must be non-negative. + + + + + Looks up a localized string similar to No MetricView is currently loaded. Call Load() or Deserialize() first.. + + + + + Looks up a localized string similar to DataType '{0}' is not supported for mapping to Tabular Model.. + + + + + Looks up a localized string similar to Join '{0}' must use exactly one column in the 'using' clause for the MVP implementation. + + + + + Looks up a localized string similar to Measure '{0}' must use a simple aggregate function (SUM, COUNT, MIN, MAX, AVG) for the MVP implementation. + + + + + Looks up a localized string similar to Join '{0}' must use a simple equality condition with table prefixes (e.g. 'source.column = dimension.column'). + + + - Looks up a localized string similar to Length must be non-negative. + Looks up a localized string similar to Duplicate field name: '{0}'. - + - Looks up a localized string similar to Start position must be non-negative. + Looks up a localized string similar to Duplicate join name: '{0}'. - + - Looks up a localized string similar to No MetricView is currently loaded. Call Load() or Deserialize() first.. + Looks up a localized string similar to Duplicate measure name: '{0}'. - + - Looks up a localized string similar to DataType '{0}' is not supported for mapping to Tabular Model.. + Looks up a localized string similar to Expressions. - + - Looks up a localized string similar to Join '{0}' must use exactly one column in the 'using' clause for the MVP implementation. + Looks up a localized string similar to Field '{0}' currency_code '{1}' is not a recognized ISO 4217 code. - + - Looks up a localized string similar to Measure '{0}' must use a simple aggregate function (SUM, COUNT, MIN, MAX, AVG) for the MVP implementation. + Looks up a localized string similar to Field '{0}' date format uses no_date, which is only valid for date_time. - + - Looks up a localized string similar to Join '{0}' must use a simple equality condition with table prefixes (e.g. 'source.column = dimension.column'). + Looks up a localized string similar to Field '{0}' date_time format hides both date and time. - + - Looks up a localized string similar to Dimension '{0}' expr cannot be empty. + Looks up a localized string similar to Field '{0}' decimal_places must be between 0 and 10. - + - Looks up a localized string similar to Dimension name cannot be empty. + Looks up a localized string similar to Field '{0}' display_name exceeds 255 characters. - + - Looks up a localized string similar to Duplicate dimension name: '{0}'. + Looks up a localized string similar to Field '{0}' expr cannot be empty. - + - Looks up a localized string similar to Duplicate join name: '{0}'. + Looks up a localized string similar to Field '{0}' has a synonym exceeding 255 characters. - + - Looks up a localized string similar to Duplicate measure name: '{0}'. + Looks up a localized string similar to Field '{0}' has more than 10 synonyms. - + - Looks up a localized string similar to Expressions. + Looks up a localized string similar to Field name cannot be empty. @@ -3479,6 +7220,41 @@ Looks up a localized string similar to Measure '{0}' expr cannot be empty. + + + Looks up a localized string similar to Measure '{0}' currency_code '{1}' is not a recognized ISO 4217 code. + + + + + Looks up a localized string similar to Measure '{0}' date format uses no_date, which is only valid for date_time. + + + + + Looks up a localized string similar to Measure '{0}' date_time format hides both date and time. + + + + + Looks up a localized string similar to Measure '{0}' decimal_places must be between 0 and 10. + + + + + Looks up a localized string similar to Measure '{0}' display_name exceeds 255 characters. + + + + + Looks up a localized string similar to Measure '{0}' has a synonym exceeding 255 characters. + + + + + Looks up a localized string similar to Measure '{0}' has more than 10 synonyms. + + Looks up a localized string similar to Measure name cannot be empty. @@ -3494,6 +7270,11 @@ Looks up a localized string similar to Structure. + + + Looks up a localized string similar to View source cannot be empty. + + Looks up a localized string similar to dimension expr cannot be empty. @@ -3651,6 +7432,16 @@ Looks up a localized string similar to Warning, facts should be LHS of relationships. '{0}'. + + + Looks up a localized string similar to Malformed dotted column reference '{0}'; a dotted reference must not contain an empty or trailing part.. + + + + + Looks up a localized string similar to Malformed MEASURE reference '{0}'; expected the form MEASURE(<measure_name>).. + + Looks up a localized string similar to Cannot add the same field twice: {0}. @@ -3731,6 +7522,17 @@ Looks up a localized string similar to Cannot access table before it is set.. + + + Looks up a localized string similar to error context: {0} + error message: {1}. + + + + + Looks up a localized string similar to Unhandled construct: {0}. + + Looks up a localized string similar to {0} error: Field not associated with any table.. @@ -3746,6 +7548,36 @@ Looks up a localized string similar to Field '{0}' references an invalid table-like object '{1}'.. + + + Looks up a localized string similar to byte auto-scaling (KB/MB/GB) has no FormatString equivalent; emitting plain numeric. + + + + + Looks up a localized string similar to locale_short_month maps to Long Date; short-month specificity not preserved. + + + + + Looks up a localized string similar to year_week has no FormatString equivalent; falling back to ISO date. + + + + + Looks up a localized string similar to date_time composition can't preserve locale-driven rendering; falling back to ISO composite. + + + + + Looks up a localized string similar to date_time has neither a date nor a time component. + + + + + Looks up a localized string similar to abbreviation: compact has no FormatString equivalent; emitting plain numeric. + + Looks up a localized string similar to Unsupported Join.On expression. We support 'source.field = dim.field'. Got '{0}'.. @@ -3811,6 +7643,187 @@ Looks up a localized string similar to Measure.Resolve: Field '{0}' not found.. + + + Looks up a localized string similar to An item with name '{0}' already exists in the collection.. + + + + + Looks up a localized string similar to Missing required date_format value. Defaulting to year_month_day.. + + + + + Looks up a localized string similar to Missing decimal_places type. Defaulting to all.. + + + + + Looks up a localized string similar to Missing format type. Ignoring format.. + + + + + Looks up a localized string similar to Missing materialization mode. Defaulting to relaxed.. + + + + + Looks up a localized string similar to Missing materialized view type. Defaulting to unaggregated.. + + + + + Looks up a localized string similar to Missing semiadditive value. Defaulting to Last.. + + + + + Looks up a localized string similar to Missing time_format value. Defaulting to locale_hour_minute_second.. + + + + + Looks up a localized string similar to Unknown abbreviation value. Ignoring.. + + + + + Looks up a localized string similar to Unknown date_format value. Defaulting to year_month_day.. + + + + + Looks up a localized string similar to Unknown decimal_places type. Defaulting to all.. + + + + + Looks up a localized string similar to Unknown format type. Ignoring format.. + + + + + Looks up a localized string similar to Unknown materialization mode. Defaulting to relaxed.. + + + + + Looks up a localized string similar to Unknown materialized view type. Defaulting to unaggregated.. + + + + + Looks up a localized string similar to Unknown semiadditive value. Defaulting to Last.. + + + + + Looks up a localized string similar to Unknown time_format value. Defaulting to locale_hour_minute_second.. + + + + + Looks up a localized string similar to There was a problem during metric view deserialization. {0}. + + + + + Looks up a localized string similar to Join '{0}' would create a dimension whose name collides with an existing dimension; the duplicate is skipped. Join names must be unique across the metric view.. + + + + + Looks up a localized string similar to Field '{0}' renamed to '{1}' to make room for the user-declared dim with the same name but a different source binding. The renamed field remains in the model and continues to back any relationship that referenced it.. + + + + + Looks up a localized string similar to Field '{0}' has an unexpected configuration that wasn't fully analyzed. It has been imported as a derived fact field for safety. Please verify the result.. + + + + + Looks up a localized string similar to error context: {0} + error message: {1}. + + + + + Looks up a localized string similar to Failed to parse field expression: '{0}'. + + + + + Looks up a localized string similar to Format translation is lossy: {0}. + + + + + Looks up a localized string similar to Join '{0}' has no 'on' clause; the dim and its source are created, but no FK/PK pair is wired.. + + + + + Looks up a localized string similar to Join '{0}' on-clause '{1}' doesn't pair an upstream (source or parent join) column with a dim column; the dim and its source are created, but no FK/PK pair is wired.. + + + + + Looks up a localized string similar to Join '{0}' on-clause '{1}' is not a recognized equality between an upstream column and a dim column; the dim and its source are created, but no FK/PK pair is wired.. + + + + + Looks up a localized string similar to MEASURE references are not permitted in {0} expressions; found '{1}' in '{2}'.. + + + + + Looks up a localized string similar to The `dimensions:` keyword is deprecated on YAML spec 1.1+; the canonical form is `fields:`. Both keywords continue to work in YAML definitions.. + + + + + Looks up a localized string similar to A metric view cannot declare both `fields:` and `dimensions:` at the top level. These keywords are aliases - use only one.. + + + + + Looks up a localized string similar to The `fields:` keyword is the canonical form starting in YAML spec 1.1. This view's version is `{0}`. The `dimensions:` keyword is canonical at this version. Both keywords are accepted on reading YAML definitions.. + + + + + Looks up a localized string similar to Metric view YAML missing `version` property. Using default: {0}. + + + + + Looks up a localized string similar to Unknown join cardinality `{0}`. Expected `many_to_one` or `one_to_many`; defaulting to `many_to_one`.. + + + + + Looks up a localized string similar to Join `{0}` declares `cardinality: one_to_many`, which the translator does not model. The table is still mapped, but the relationship is skipped - measures that reference its columns may produce wrong or empty results and must be reviewed by hand.. + + + + + Looks up a localized string similar to Dimension '{0}' references '{1}' as a whole-row or nested struct value, which has no Tabular column equivalent; the column is emitted but will not resolve at refresh.. + + + + + Looks up a localized string similar to Reference '{0}.{1}' targets a dimension '{2}' that is not declared as a join; emitting a placeholder DerivedField on Fact preserving the original reference.. + + + + + Looks up a localized string similar to MEASURE reference '{0}' in '{1}' does not resolve to a declared measure; falling back to verbatim source.. + + Looks up a localized string similar to Failed to deserialize MetricView from YAML string. @@ -3836,6 +7849,11 @@ Looks up a localized string similar to Failed to validate MetricView. + + + Looks up a localized string similar to {0} must be non-empty. + + Looks up a localized string similar to Attempted dictionary key lookup on non-collection type '{0}'.. @@ -3886,6 +7904,11 @@ Looks up a localized string similar to Node not found in MetricView graph.. + + + Looks up a localized string similar to A qualified column reference requires at least one qualifier.. + + Looks up a localized string similar to Left field '{0}' not found in built objects. Fields must be built before relationships.. @@ -3936,6 +7959,31 @@ Looks up a localized string similar to Relationship must be between a fact and a dimension.. + + + Looks up a localized string similar to No fact with id {0}. + + + + + Looks up a localized string similar to No source with id {0}. + + + + + Looks up a localized string similar to No field with id {0} to reference. + + + + + Looks up a localized string similar to No table with id {0}. + + + + + Looks up a localized string similar to No field with id {0}. + + Looks up a localized string similar to Fact '{0}' already has a reference to dimension '{1}' with relationship on fields '{2}' and '{3}'. @@ -4016,6 +8064,11 @@ Looks up a localized string similar to Source fragment has no valid object or query defined.. + + + Looks up a localized string similar to Metric View identifiers with more than one dot are not supported.. + + Looks up a localized string similar to Service was successfully initialized and can be accessed through the `Success` record's `Service` property.. @@ -4026,6 +8079,11 @@ Looks up a localized string similar to Cannot use validation rule for {0} on node of {1}.. + + + Looks up a localized string similar to No join with name found: {0}.. + + Extension methods for collection equality comparison that supports order-independent @@ -4105,148 +8163,5 @@ of internal ordering. - - Custom -derived type for the JoinOnRegex method. - - - Cached, thread-safe singleton instance. - - - Initializes the instance. - - - Provides a factory for creating instances to be used by methods on . - - - Creates an instance of a used by methods on . - - - Provides the runner that contains the custom logic implementing the specified regular expression. - - - Scan the starting from base.runtextstart for the next match. - The text being scanned by the regular expression. - - - Search starting from base.runtextpos for the next location a match could possibly start. - The text being scanned by the regular expression. - true if a possible match was found; false if no more matches are possible. - - - Determine whether at base.runtextpos is a match for the regular expression. - The text being scanned by the regular expression. - true if the regular expression matches at the current position; otherwise, false. - - - Custom -derived type for the SqlAggRegex method. - - - Cached, thread-safe singleton instance. - - - Initializes the instance. - - - Provides a factory for creating instances to be used by methods on . - - - Creates an instance of a used by methods on . - - - Provides the runner that contains the custom logic implementing the specified regular expression. - - - Scan the starting from base.runtextstart for the next match. - The text being scanned by the regular expression. - - - Search starting from base.runtextpos for the next location a match could possibly start. - The text being scanned by the regular expression. - true if a possible match was found; false if no more matches are possible. - - - Determine whether at base.runtextpos is a match for the regular expression. - The text being scanned by the regular expression. - true if the regular expression matches at the current position; otherwise, false. - - - Custom -derived type for the DistinctCountRegex method. - - - Cached, thread-safe singleton instance. - - - Initializes the instance. - - - Provides a factory for creating instances to be used by methods on . - - - Creates an instance of a used by methods on . - - - Provides the runner that contains the custom logic implementing the specified regular expression. - - - Scan the starting from base.runtextstart for the next match. - The text being scanned by the regular expression. - - - Search starting from base.runtextpos for the next location a match could possibly start. - The text being scanned by the regular expression. - true if a possible match was found; false if no more matches are possible. - - - Determine whether at base.runtextpos is a match for the regular expression. - The text being scanned by the regular expression. - true if the regular expression matches at the current position; otherwise, false. - - - Custom -derived type for the SqlObjectPattern method. - - - Cached, thread-safe singleton instance. - - - Initializes the instance. - - - Provides a factory for creating instances to be used by methods on . - - - Creates an instance of a used by methods on . - - - Provides the runner that contains the custom logic implementing the specified regular expression. - - - Scan the starting from base.runtextstart for the next match. - The text being scanned by the regular expression. - - - Search starting from base.runtextpos for the next location a match could possibly start. - The text being scanned by the regular expression. - true if a possible match was found; false if no more matches are possible. - - - Determine whether at base.runtextpos is a match for the regular expression. - The text being scanned by the regular expression. - true if the regular expression matches at the current position; otherwise, false. - - - Helper methods used by generated -derived implementations. - - - Default timeout value set in , or if none was set. - - - Whether is non-infinite. - - - Determines whether the character is part of the [\w] set. - - - Supports searching for characters in or not in "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz". - diff --git a/content/_apiSource/TOMWrapper.dll b/content/_apiSource/TOMWrapper.dll index c6a9d32af..050e9cf80 100644 Binary files a/content/_apiSource/TOMWrapper.dll and b/content/_apiSource/TOMWrapper.dll differ diff --git a/content/_apiSource/TOMWrapper.xml b/content/_apiSource/TOMWrapper.xml index e7b5b25fd..752bdd4f2 100644 --- a/content/_apiSource/TOMWrapper.xml +++ b/content/_apiSource/TOMWrapper.xml @@ -95,6 +95,37 @@ CTOR - only called from static factory methods on the class + + + Lightweight, read-only snapshot of a database on an Analysis Services / Power BI server, + sourced from the DBSCHEMA_CATALOGS DMV. Use this instead of enumerating + when you only need name/ID/CL/modified-date, since the + TOM enumeration causes models to be swapped into memory on the server. + + + + Server-assigned database identifier (DATABASE_ID). + + + Database (catalog) display name (CATALOG_NAME). + + + Compatibility level of the database (COMPATIBILITY_LEVEL). + + + UTC timestamp of the last metadata modification (DATE_MODIFIED). Null when not requested. + + + Database description (DESCRIPTION). Null when not requested, empty string when set but empty. + + + + Returns the list of databases on the server by querying the DBSCHEMA_CATALOGS DMV. + Unlike enumerating , this does not cause models to be + swapped into memory on the server (critical for the Power BI service, which may evict + other models to honor the implicit load). + + Deploys the specified database to the specified target server and database ID, using the specified options. @@ -107,6 +138,12 @@ + + + Compares RefreshPolicy expressions between source model and destination model. + Returns list of changes where SourceExpression or PollingExpression changed. + + This method transforms a JObject representing a CreateOrReplace TMSL script, so that the script points @@ -286,6 +323,33 @@ not be deployed, they are stripped from the TMSL script. + + + Represents a change detected in a table's RefreshPolicy expressions. + + + + + Represents a change detected in a table's RefreshPolicy expressions. + + + + + Event args for the RefreshPolicyChangePrompt callback, containing detected changes + and allowing the caller to approve or deny the deployment. + + + + + List of tables with RefreshPolicy expression changes detected. + + + + + Set to true to approve deploying the changes (which will reset incremental refresh partitions). + Set to false or leave default to cancel the deployment. + + Return a list of tokens representing the DAX expression on the current object. @@ -611,6 +675,17 @@ Removes all columns from the collection of related columns + + + A more specific version of that maps directly to specific TOMWrapper types, which + all are creatable (i.e. they have a public static `CreateNew` method). + + + + + A dictionary that specifies the parent of each individual . + + Specifies the aggregate function to be used by reporting tools to summarize column values. @@ -956,6 +1031,31 @@ Enforces fallback. Uses DirectQuery unconditionally. + + + Controls how the AnalysisServices engine builds and persists DirectLake specific indexes. + This enum is only supported when the compatibility level of the database is at Preview or above. + + + + The semantic model might build in-memory index during data load or query execution. + + + + + The semantic model will build and persist the index only when explicitly requested via a refresh indexes operation. Refresh types other than refresh indexes do not perform indexing. + + + + + The semantic model builds and persists indexes as part of refresh full, refresh calculate and refresh indexes operations. + + + + + Applies the semantic model default index building and persistence behavior. + + Encoding hint to suggest whether a column should use hash encoding. @@ -976,24 +1076,19 @@ Value encoding - + - Evaluation behavior for calculated column. - This enum is only supported when the compatibility level of the database is at Preview or above. + Expression Context for calculated column. + This enum is only supported when the compatibility level of the database is at 1705 or above. - + - Default value, used for backward compatibility. Maps to Static or Dynamic depending on the table properties. + Expression uses only standard functions. - + - Evaluation is performed during processing and the result is materialized. - - - - - Evaluation is performed dynamically and the result is not materialized. + Expression can include user-context functions. @@ -1071,29 +1166,29 @@ Do not reference this member directly in your code. It supports the Analysis Services infrastructure. - + - Column index's building behavior. + Controls how the AnalysisServices engine builds and persists indexes. This enum is only supported when the compatibility level of the database is at Preview or above. - + - Default behavior - the server will not build string column's suffix index + Indexing is disabled. The semantic model will not perform indexing during refresh operations. - + - The server will build but not persist string column's suffix index + The semantic model might build in-memory index during data load or query execution. - + - The server will build and persist string column's suffix index by RefreshIndex command + The semantic model will build and persist the index only when explicitly requested via a refresh indexes operation. Refresh types other than refresh indexes do not perform indexing. - + - The server will build and persist string column's suffix index by either RefreshRecalc, RefreshFull or RefreshIndex command + The semantic model builds and persists indexes as part of refresh full, refresh calculate and refresh indexes operations. @@ -1426,6 +1521,11 @@ Defragment the data in the specified table. As data is added to or removed from a table, the dictionaries of each column can become polluted with values that no longer exist in the actual column values. The defragment option will clean up the values in the dictionaries that are no longer used. + + + Refresh indexes. + This value is only supported when the compatibility level of the database is at Preview or above. + An enumeration of possible values for defining cardinality on either side of a table relationship. @@ -2040,10 +2140,10 @@ The DAX expression that is evaluated for the calculated column. - + - Evaluation behavior for calculated column. - This property is only supported when the compatibility level of the database is at Preview or above. + Expression context for calculated column. + This property is only supported when the compatibility level of the database is at 1705 or above. @@ -2309,7 +2409,7 @@ A tag that represents the lineage of the source for the object. This property is only supported when the compatibility level of the database is at 1550 or above. - + This property allows building and persisting of string column's index. A column may use one of the following values: Off, Auto, Explicit or Full. Default value for the property is Auto. Setting it to Auto will build an index but not persist it. Setting it to Explicit will build and persist an index by running command RefreshIndex. Setting it to Full will build and persist an index by running either RefreshRecalc, RefreshFull or RefreshIndex command. This property is only supported when the compatibility level of the database is at Preview or above. @@ -2333,9 +2433,9 @@ - + Collection of perspectives in which this Column is visible. - + @@ -2578,9 +2678,9 @@ Sets the SourceLineageTag property of all objects in the collection at once. - + - Sets the StringIndex property of all objects in the collection at once. + Sets the StringIndexingBehavior property of all objects in the collection at once. @@ -2914,6 +3014,9 @@ Deletes this column mapping from the calendar. + + + Collection class for CalendarColumnGroup. Provides convenient properties for setting a property on multiple objects at once. @@ -3002,6 +3105,9 @@ A string that represents the object. The format is either "(Unassigned)" if the primary column is null, or "PrimaryColumn.DaxObjectName (TimeUnit)" with the time unit formatted. + + + Represents a group of columns that are related to time information. @@ -3213,9 +3319,9 @@ The parent table of the current Hierarchy. - + Collection of perspectives in which this Hierarchy is visible. - + @@ -3948,9 +4054,9 @@ The parent table of the current Measure. - + Collection of perspectives in which this Measure is visible. - + @@ -4300,9 +4406,14 @@ - The policy that applies to the users with read permissions only and controls their access to various metadata elements. + The policy applies to users with read permissions only and controls their access to various metadata elements. This property is only supported when the compatibility level of the database is at 1703 or above. + + + The default indexing behavior for Direct Lake. + This property is only supported when the compatibility level of the database is at Preview or above. + A reference to a default measure. @@ -5865,10 +5976,15 @@ An indication whether the table is excluded from the automatic aggregations feature. This property is only supported when the compatibility level of the database is at 1572 or above. + + + Table indexing behavior for Direct Lake. + This property is only supported when the compatibility level of the database is at Preview or above. + - + Collection of perspectives in which this Table is visible. - + @@ -6280,6 +6396,11 @@ Sets the ExcludeFromAutomaticAggregations property of all objects in the collection at once. + + + Sets the DirectLakeIndexingBehavior property of all objects in the collection at once. + + Represents an individual Windows user account or a Windows security group. @@ -7428,6 +7549,12 @@ The column to check True if the column is in a time-related column group; otherwise, false + + + + + + Moves this calendar to a different table by deleting it and recreating it on the target table. @@ -7498,6 +7625,9 @@ clicked. + + + The Expression of this Calculated Table. @@ -8271,6 +8401,13 @@ + + + -1 indicates that the TabularModelHandler is not connected, i.e. the TOM.Database object + does not have a connection to a TOM.Server object. Any other value indicates connection + (the value itself is tracked by the server). + + Begins a batch update @@ -8441,6 +8578,16 @@ When this is set to true, the TOMWrapper will not allow you to create DirectLake partitions. + + + When true, and synchronously runs and waits for a full + semantic re-analysis before returning, so subsequent mutations see a consistent + dependency graph (e.g. auto-fixup on a rename will correctly find DAX references to + the renamed object). The default (false) preserves async UI behaviour where the slight + delay before the analysis settles is acceptable; the CLI sets this to true so a + follow-up command never observes mid-rebuild state. + + A TabularObject is a wrapper for the Microsoft.AnalysisServices.Tabular.NamedMetadataObject class. @@ -8732,6 +8879,36 @@ Returns true if no changes have been done relative to the last checkpoint set. + + + The number of items in the UndoStack including batch action begin/end markers + + + + + The number of atomic actions in the UndoStack (doesn't include batch action begin/end markers) + + + + + The number of steps in the UndoStack. This only counts actions at the root level (i.e. a batch action is only counted once, regardless of how many actions is inside the batch) + + + + + The number of items in the RedoStack including batch action begin/end markers + + + + + The number of atomic actions in the RedoStack (doesn't include batch action begin/end markers) + + + + + The number of steps in the RedoStack. This only counts actions at the root level (i.e. a batch action is only counted once, regardless of how many actions is inside the batch) + + Rolls back all changes done to the model. @@ -9049,6 +9226,11 @@ {0}. + + + Looks up a localized string similar to Unable to query databases from server: {0}. + + A strongly-typed resource class, for looking up localized strings, etc. @@ -9085,47 +9267,6 @@ Looks up a localized string similar to Error while removing unused files from TMDL folder structure: {0}. - - - This Collection Editor for Cultures doesn't allow new cultures to be defined without setting the 'Language' property - - - - - A CollectionEditor that automatically refreshes the parent PropertyGrid when the CollectionEditor form is closed. - Moreover, it will automatically call BeginUpdate and EndUpdate on the TabularModelHandler. - - - - - A CollectionEditor that automatically refreshes the parent PropertyGrid when the CollectionEditor form is closed. - Moreover, it will automatically call BeginUpdate and EndUpdate on the TabularModelHandler. - - - - - Provide the initial collection of items to be shown in the CollectionEditor form. - Note: items are not added/removed from this collection while the user is interacting - with the CollectionEditor form. Instead, the SetEditableCollection method is called - when the user hits the "OK" button. If you need to perform special actions when items - are added/removed, override the CreateInstance and DestroyInstance methods. - - - - - - - Override this method to update the collection property of the object being edited - This is only called when the user hits the "OK" button in the CollectionEditor form. - You may also throw an exception here to prevent the user from closing the form. - - - - - - A CollectionEditor that works natively with TabularObjectCollections, and automatically refreshes the parent PropertyGrid when the CollectionEditor form is closed. - - Provide a structured way to talk about currencies, capturing the symbol, @@ -9292,42 +9433,28 @@ complex class that can be converter to and from the FormatString string. - + - The CustomEditors static class, can be used to register custom editors that should be used when - a specified property is edited in the PropertyGrid. An editor provided this way, must implement - the ICustomEditor interface. - - An example of a custom editor is the FormDisplayFolderSelect which displays a hierarchical view - of Display Folders in a table. The editor is used for editing Display Folder strings in the - property grid. - - Properties that should be editable in a Custom Editors, must be decorated with the Editor attribute - like so: - - [Editor(typeof(CustomDialogEditor), typeof(System.Drawing.Design.UITypeEditor))] - public string FormatString { get; set; } + Stub for IExpandableIndexer. On Windows, this enables dictionary-type + properties to be expanded in the PropertyGrid. - + - This interface must be implemented by dictionary-type properties on a class, such as - annotations, translations, etc. + Stub for IDropDownProperties. On Windows, this enables properties to + show as dropdown lists in the PropertyGrid. - + - Gets the value to display for a given key. Note, this is not the value of the object represented by the key. + Stub for IndexerConverter. The real implementation lives in DictionaryProperty.cs + which is WinForms-coupled and excluded from cross-platform builds. - - - + - This interface must be implemented by objects containing one or more property that - should show up as a drop-down list in the PropertyGrid. The interface provides a - means for the DropDown typeconverter to get the list of string items to display in - the drop down. + Stub for DataCoverageDefinitionConverter. The real implementation lives in + AlternateOfEditor.cs which is WinForms-coupled. @@ -9348,20 +9475,6 @@ indicating to clients that it is not necessary to show an explicit "Reset" menu option, when the action is available. - - - The DesignerHost is a custom implementation of IDesignerHost and ISite, which is needed to hookup - any PropertyGrid components used in the UI to enable proper Undo/Redo functionality. Otherwise, - when changing the properties of multiple objects at once, the operation is not batched, meaning - that the UI will be updated when a property value is set for every object in the selection. This - will cause slow updates when many objects are selected, and furthermore, undoing the entire operation - is a hassle, as one Undo-action is needed per object. - - To set up a PropertyGrid to use this host, use the following code: - - MyPropertyGrid.Site = new DesignerHost(); - - Call this when the UpdateLocks reach 0 to apply all cached changes @@ -9611,6 +9724,12 @@ + + + Returns the server-assigned object ID of the underlying TOM metadata object, + or null if no ID has been assigned (e.g. the model is not connected to a server). + + Extension methods for @@ -9694,5 +9813,30 @@ Throws an exception of the provided new name is invalid for the specified object. + + + Stub for System.Drawing.Design.UITypeEditor (Windows Forms only). + + + + + Stub for System.ComponentModel.Design.MultilineStringEditor (Windows Forms only). + + + + + Stub for System.Windows.Forms.MessageBoxButtons (Windows Forms only). + + + + + Stub for System.Windows.Forms.MessageBoxIcon (Windows Forms only). + + + + + Stub for System.Windows.Forms.DialogResult (Windows Forms only). + + diff --git a/content/_apiSource/TabularEditor3.Shared.dll b/content/_apiSource/TabularEditor3.Shared.dll index 2085564f7..00f163717 100644 Binary files a/content/_apiSource/TabularEditor3.Shared.dll and b/content/_apiSource/TabularEditor3.Shared.dll differ diff --git a/content/_apiSource/TabularEditor3.Shared.xml b/content/_apiSource/TabularEditor3.Shared.xml index 0b8594c6f..fcc47b938 100644 --- a/content/_apiSource/TabularEditor3.Shared.xml +++ b/content/_apiSource/TabularEditor3.Shared.xml @@ -82,6 +82,233 @@ + + Whether the script is considered safe (no #r directives and only safe namespaces). + + + Whether the script contains #r (reference) directives. + + + List of namespaces used that are not in the safe list. + + + Human-readable warning message for unsafe scripts. + + + + The result of a consent request. + + + + Whether consent was granted. + + + The duration for which consent was granted. + + + Creates a denied result. + + + Creates a granted result with specified duration. + + + + Duration options for consent. + + + + Consent was denied. + + + Allow for this single request only. + + + Allow for the remainder of this session. + + + Allow for this specific model. + + + Always allow (update preference). + + + + Service for managing user consent for AI data sharing. + + + + + Checks if consent is already granted for the specified type, or requests it from the user. + + The type of data being shared. + A description of what data will be shared (for the consent dialog). + The consent result. + + + + Clears turn-scoped consents. Call this when a turn completes so that + one-time ("Allow") consents do not carry over to the next turn. + + + + + Gets the script handle for an existing document by its ID. + Returns null if the document doesn't exist or is not a script view. + + + + + Replaces the content of an existing script view. + + + + + Shows a tool call progress indicator in the UI (bottom of chat). + + The name of the tool being executed. + + + + Hides the tool call progress indicator (bottom of chat). + + + + + Gets a tool call marker tag that can be injected into the message stream. + This allows tool calls to appear inline in the conversation history. + + The internal tool name (e.g., "execute_dax_query"). + Unique identifier for the tool call (used for completion updates). + User-friendly display name for the tool. + Optional details to show in tooltip (e.g., the DAX query being executed). + A marker string like <toolcall tool="..." id="..." display="..." details="..."/> + + + + Marks a tool call as complete in the UI, stopping the animation and showing "Done!". + + The call ID of the tool call to mark complete. + + + + Shows a consent request in the chat UI. + + Unique identifier for this consent request. + The title of the consent request. + The message explaining what consent is being requested. + The available consent options. + + + + Removes a consent request from the chat UI (after user responds or on timeout). + + The request ID to remove. + + + + Sets the available custom instructions for the /instruction-id autocomplete popup. + + + + + Updates the context window usage indicator in the UI. + + Total tokens used so far (input + output). + Maximum context window size. + Input tokens from the latest API call (context pressure). + Accumulated output tokens this turn (context pressure). + Input tokens read from prompt cache (context pressure). + Input tokens used to create cache entries (context pressure). + Cumulative input tokens billed across all API calls. + Cumulative output tokens billed across all API calls. + Cumulative cached input tokens across all API calls. + Cumulative cache creation tokens across all API calls. + + + + Pushes the current database connection state to the chat UI. + When not connected, DAX artifact "Execute" buttons are disabled. + + + + + Navigates to the TOM object referenced by the given string in the TOM Explorer. + + The object reference (e.g., "Sales", "[Total Amount]", "Sales[Amount]"). + + + + Represents a custom instruction available for explicit invocation via /instruction-id. + + + + The unique skill identifier (used as /skill-id). + + + The display name of the skill. + + + Optional description shown in the autocomplete popup. + + + + Represents an option in a consent request. + + + + The display label for the option. + + + The value returned when this option is selected. + + + Whether this is the primary/recommended option. + + + + Configures the consent system with callbacks for preferences and UI. + + + + + Sets the document access provider for AI tools to access open documents. + + + + + Sets the BPA analyzer for AI tools. + + + + + Stops the current generation/response in progress. + + + + + Re-pushes available skills to the chat interop (call after WebView is ready). + + + + + Provides read-only access to the user's macro library for AI tools. + + + + + Gets the current list of macros. + + + + + Represents a UI action that the AI should be able to request, such as launching the "New model dialog", or the "Preferences" dialog. + + + + + Represents a UI action that the AI should be able to request, such as launching the "New model dialog", or the "Preferences" dialog. + + Form.ActiveForm is null when Tabular Editor does not have focus (this can happen for example when debugging, @@ -95,12 +322,41 @@ Contains Azure AD client IDs and scopes used for authentication. + + Azure Analysis Services scope pattern. + + + Default Power BI / Fabric XMLA scope (commercial cloud). + Gets the Power BI scopes based on the Power BI REST API endpoint URL. Different cloud environments (commercial, government, Germany, China) require different scopes. + + + Determine the appropriate auth scope from a server connection string. + Supports Power BI XMLA (multi-cloud), Azure AS, and localhost. + Server URLs follow patterns like: + powerbi://api.powerbi.com/v1.0/myorg/Workspace + powerbi://api.powerbigov.us/v1.0/myorg/Workspace + asazure://region.asazure.windows.net/server + localhost:port + + + + + Raised just before an interactive browser/WAM sign-in dialog is shown. + Subscribers should hide any overlay UI (e.g. wait forms) so the auth dialog is visible. + + + + + Raised after an interactive sign-in dialog has completed (success or failure). + Subscribers should restore any overlay UI that was hidden. + + Gets an ExtendedFabricClient object - either from the cache (i.e. the most recently used one) or by creating a new one if the cache is empty. @@ -119,6 +375,61 @@ Handles Azure.Identity, MSAL (used by TOM), SqlClient, and OleDb authentication cancellations. + + + Returns the localized display name for the given Context, using Smart.Format for pluralization. + Singular contexts (e.g. Table, Measure) return the singular form; group/collection contexts + (e.g. Tables, PartitionCollection) return the plural form. + + + + + Returns the Smart.Format template string for the given Context, or null if not mapped. + This is the single source of truth for Context-to-localized-name mapping. + Resolved against . + + + + + Culture-aware variant of . Resolves the resource in the + specified , allowing callers (e.g. AI context injection) to force + invariant/English output regardless of the UI culture. + + + + + Returns the Smart.Format template string for the localized type name of the given object. + Use with and a count to get singular/plural form. + Handles concrete subtypes (CalculatedTable, CalculatedColumn, etc.) that don't have Context equivalents. + Resolved against . + + + + + Culture-aware variant of . Passing a specific + (e.g. ) forces the resource lookup + into that culture so callers can produce language-stable output independent of the current UI culture. + + + + + Returns the Smart.Format template string for the localized type name of the given ObjectType. + Use with and a count to get singular/plural form. + Delegates to the Context mapping for types that have a corresponding Context value. + Resolved against . + + + + + Culture-aware variant of . + + + + + Maps an to the corresponding singular value. + Returns for types that have no direct Context mapping (Model, Group, Folder). + + Provides a set of methods for changing multiple object properties at once, as well @@ -297,12 +608,21 @@ Returns a string with a short description of the types of objects in the collection. + Resolved against . The collection to summarize Set to false to always show the types of objects in the collection + + + Culture-aware variant of . Resolves all + resource strings and casing through the supplied so callers can produce a + language-stable summary independent of the current UI culture. Pass + to obtain the English/neutral form (e.g. for feeding the AI assistant, whose prompts and skills are English-only). + + Convenient extension method for converting an to a , the latter containing @@ -526,6 +846,11 @@ The name of the current table group (if exactly one table group is selected, or if one or more tables within a single table group is selected) + + + The currently selected item (if exactly one item is selected in the explorer tree). + + The number of objects directly selected in the Explorer Tree (not counting any child objects). @@ -579,6 +904,11 @@ he currently selected KPI. + + + All currently selected KPIs. + + The currently selected levels. @@ -604,6 +934,11 @@ The currently selected object (if exactly one object is selected in the explorer tree). + + + All currently selected objects. + + All currently selected columns (including columns within selected Display Folders). @@ -639,11 +974,21 @@ All currently selected roles. + + + The currently selected role (if exactly one role is selected in the explorer tree). + + All currently selected relationships. + + + The currently selected relationship (if exactly one relationship is selected in the explorer tree). + + All currently selected perspectives. @@ -664,6 +1009,11 @@ All currently selected table permissions. + + + The currently selected table permission (if exactly one table permission is selected in the explorer tree). + + The currently selected calculation group (if exactly one calculation group is selected in the explorer tree.) @@ -689,6 +1039,11 @@ All currently selected calculated table columns (including calculated table columns within selected Display Folders). + + + The currently selected calculated table column (if exactly one calculated table column is selected in the explorer tree). + + The currently selected data column (if exactly one data column is selected in the explorer tree). @@ -762,7 +1117,12 @@ - Gets the currently selected display folders in the explorer tree. + All currently selected display folders in the explorer tree. + + + + + The currently selected folder (if exactly one folder is selected in the explorer tree). @@ -792,6 +1152,126 @@ resource lookups using this strongly typed resource class. + + + Looks up a localized string similar to Read documents. + + + + + Looks up a localized string similar to The AI assistant needs the content from open documents. + + + + + Looks up a localized string similar to This setting controls whether the AI assistant will have access to read the text content of any documents (scripts, queries, etc.) that are currently open in Tabular Editor.. + + + + + Looks up a localized string similar to Modify documents. + + + + + Looks up a localized string similar to The AI assistant needs to modify content in open documents. + + + + + Looks up a localized string similar to This setting controls whether the AI assistant will be able to modify the content of any documents (scripts, queries, etc.) that are currently open in Tabular Editor.. + + + + + Looks up a localized string similar to Edit BPA rules. + + + + + Looks up a localized string similar to The AI assistant needs to add or modify Best Practice Analyzer rules. + + + + + Looks up a localized string similar to This setting controls whether the AI assistant will be able to add or modify Best Practice Analyzer rules in your local user rule collection.. + + + + + Looks up a localized string similar to The AI assistant needs to access this data. + + + + + Looks up a localized string similar to Read macros. + + + + + Looks up a localized string similar to The AI assistant needs to read macros from your macro library. + + + + + Looks up a localized string similar to This setting controls whether the AI assistant will be able to read the macros in your macro library, including their C# code.. + + + + + Looks up a localized string similar to Model metadata. + + + + + Looks up a localized string similar to The AI assistant needs to access model metadata. + + + + + Looks up a localized string similar to This setting controls whether the AI assistant will have access to the metadata of the semantic model currently loaded in Tabular Editor (table names, column names, measure definitions, etc.).. + + + + + Looks up a localized string similar to Query data. + + + + + Looks up a localized string similar to The AI assistant needs to execute DAX queries and read results. + + + + + Looks up a localized string similar to This setting controls whether the AI will be able to execute DAX queries against the semantic model and read the results of those queries.. + + + + + Looks up a localized string similar to Anthropic. + + + + + Looks up a localized string similar to Azure OpenAI. + + + + + Looks up a localized string similar to Custom (OpenAI-compatible). + + + + + Looks up a localized string similar to None (AI disabled). + + + + + Looks up a localized string similar to OpenAI. + + Looks up a localized string similar to Integrated. @@ -963,8 +1443,8 @@ Looks up a localized string similar to Hidden columns that are not used for sorting or in hierarchies should have their IsAvailableInMdx property set to false. This prevents unnecessary attribute hierarchy creation, reducing processing time and memory consumption. - - Reference: https://blog.crossjoin.co.uk/2018/07/02/isavailableinmdx-ssas-tabular/. + + NOTE: Ignore this rule if the columns is to be used in Excel PivotTable and Analyze in Excel.. @@ -1124,6 +1604,16 @@ Looks up a localized string similar to Trim object names. + + + Looks up a localized string similar to User-defined function names should contain a dot or underscore separator (e.g., 'MyLib.MyFunction' or 'MyLib_MyFunction') to form a compound/namespaced name. Simple names without a separator are at risk of conflicting with new DAX functions introduced in the future, which could break existing DAX expressions that reference the UDF.. + + + + + Looks up a localized string similar to User-defined functions should use compound names. + + Looks up a localized string similar to Visible calculation groups should have descriptions to explain their purpose and how the calculation items modify measure behavior. Descriptions appear in tooltips in Power BI Desktop and can be used to generate automated data documentation.. @@ -1219,27 +1709,102 @@ Looks up a localized string similar to File upload to the DAX Optimizer service is disabled by a local policy.. - + - Looks up a localized string similar to Disabled due to low model compatibility level ({0} < {1}). + Looks up a localized string similar to Authors:. - + - Looks up a localized string similar to Semantic Model (Default). + Looks up a localized string similar to Description. - + - Looks up a localized string similar to Error. + Looks up a localized string similar to Details. - - - Looks up a localized string similar to An error occurred while trying to deserialize the User Options (.tmuo) file: - - {0}. - + + + Looks up a localized string similar to Disabled due to low model compatibility level ({0} < {1}). + + + + + Looks up a localized string similar to Downloads:. + + + + + Looks up a localized string similar to Install. + + + + + Looks up a localized string similar to Installed. + + + + + Looks up a localized string similar to Owners:. + + + + + Looks up a localized string similar to Project URL:. + + + + + Looks up a localized string similar to Provider:. + + + + + Looks up a localized string similar to Publish Date:. + + + + + Looks up a localized string similar to Release Notes. + + + + + Looks up a localized string similar to Remove. + + + + + Looks up a localized string similar to Repository URL:. + + + + + Looks up a localized string similar to Tags:. + + + + + Looks up a localized string similar to Up-to-date. + + + + + Looks up a localized string similar to Semantic Model (Default). + + + + + Looks up a localized string similar to Error. + + + + + Looks up a localized string similar to An error occurred while trying to deserialize the User Options (.tmuo) file: + + {0}. + @@ -1336,6 +1901,131 @@ Looks up a localized string similar to OAuth. + + + Looks up a localized string similar to {0:Calculated Column|Calculated Columns}. + + + + + Looks up a localized string similar to {0:Calculated Table|Calculated Tables}. + + + + + Looks up a localized string similar to {0:Calculated Table Column|Calculated Table Columns}. + + + + + Looks up a localized string similar to {0:Calculation Group Table|Calculation Group Tables}. + + + + + Looks up a localized string similar to {0:Calculation Item|Calculation Items}. + + + + + Looks up a localized string similar to {0:Calendar|Calendars}. + + + + + Looks up a localized string similar to {0:Column|Columns}. + + + + + Looks up a localized string similar to {0:Data Source|Data Sources}. + + + + + Looks up a localized string similar to Folder. + + + + + Looks up a localized string similar to {0:Function|Functions}. + + + + + Looks up a localized string similar to Group. + + + + + Looks up a localized string similar to {0:Hierarchy|Hierarchies}. + + + + + Looks up a localized string similar to {0:KPI|KPIs}. + + + + + Looks up a localized string similar to {0:Level|Levels}. + + + + + Looks up a localized string similar to {0:Measure|Measures}. + + + + + Looks up a localized string similar to Model. + + + + + Looks up a localized string similar to objects. + + + + + Looks up a localized string similar to {0:Partition|Partitions}. + + + + + Looks up a localized string similar to {0:Perspective|Perspectives}. + + + + + Looks up a localized string similar to {0:Relationship|Relationships}. + + + + + Looks up a localized string similar to {0:Role|Roles}. + + + + + Looks up a localized string similar to {0:Shared Expression|Shared Expressions}. + + + + + Looks up a localized string similar to {0:Table|Tables}. + + + + + Looks up a localized string similar to {0:Table Permission|Table Permissions}. + + + + + Looks up a localized string similar to {0:Translation|Translations}. + + Looks up a localized string similar to {0} refresh of {1:partition {2}|{} partitions}. @@ -1381,6 +2071,31 @@ Looks up a localized string similar to Scalar expression. + + + Looks up a localized string similar to {0} {1}. + + + + + Looks up a localized string similar to Nothing. + + + + + Looks up a localized string similar to (Nothing selected). + + + + + Looks up a localized string similar to 1 {0}. + + + + + Looks up a localized string similar to {0} "{1}". + + Looks up a localized string similar to SemanticBridge service failed to initialize. @@ -1489,16 +2204,203 @@ Looks up a localized string similar to Warning. + + + AI consent state for this model. + + This class ensures that a connection string is encrypted/decrypted in case it contains sensitive data. + + + Result of evaluating a single BPA rule against a model object. + + + + + The fix expression for this rule (if any). + + + + + Reference to the violating TOM object (for fix expression execution). + Not serialized to JSON. + + + + + Whether this violation can be auto-fixed. + + + + + Result of a BPA rule that failed to compile or evaluate. + + + + + Complete result of running BPA analysis on a model. + + + + + Hook for redirecting script messages (Info / Warning / Error / Output) in + command-line / headless hosts. When a sink is registered through + ScriptHelper.InitScriptHelper and is true, + all script messaging routes through the sink instead of writing directly to + . The UI app passes null (legacy behavior preserved). + + + + Called when a script invokes Info(message). + + + Called when a script invokes Warning(message). + + + Called when a script invokes Error(message). + + + Called when a script invokes Output(value). + + + + Root container for MacroActions.json. Shared schema between TE3 desktop and CLI. + + + + + Parsed folder path (from backslash-separated ). Not serialised. + + + + + Display name without the folder prefix. Not serialised. + + + + + Parse folder hierarchy from a backslash-separated into + and . Used by both TE3 desktop + (via the macro tree UI) and the CLI (`te macro list`/`run`). + + + + + Script-accessible Best Practice Analyzer helper. Exposed as ScriptHost.Bpa. + Delegates are wired by the host (CLI or TE3 UI) at startup. + + + + + Delegate that runs BPA analysis and returns results. + Signature: (tableFilter) -> . + + + + + Delegate that returns the current set of BPA rules (built-in + model). + + + + + Run BPA analysis on the current model. Optionally filter to specific tables. + + + + + Get the current set of BPA rules (built-in + model-embedded, deduplicated). + + + + + Export BPA analysis results to a CSV file at the specified path. + + + + + Shared constants and helpers for C# script compilation. + Used by both TE3 desktop and CLI script engines. + + + + + Core usings available in all TE3/CLI scripts. + Each engine may add its own additional usings on top. + + + + + Preprocessor symbols for TE3 script compilation: + + - `TE3` + - `TE3_3_10_OR_GREATER` + - `TE3_3_11_OR_GREATER` + - ... + - `TE3_3_X_OR_GREATER` (where X is the current minor version number of TE3) + + For TE CLI script compilation, we ONLY define the following symbol: + + - `TECLI` + + This allows script authors to have sections of code in their script that only gets compiled under a specific version + Tabular Editor 3 or the CLI. That way, a script can contain UI code that gets compiled only when the script is executed + in TE3 and ommitted when the script is compiled/executed in TE CLI. + + + + + Apply CSharpParseOptions to ScriptOptions via reflection. + The WithParseOptions method is internal in Roslyn. + + + + + Performs a quick safety analysis on a C# script without full compilation. + Checks for #r reference directives and unsafe using statements. + + The C# script code to analyze. + A quick safety analysis result. + + + + Result of a quick script safety analysis (without full compilation). + + + + Whether the script contains #r reference directives. + + + Namespaces used in using statements that are not in the safe list. + + + Whether the script appears safe (no reference directives and no unsafe namespaces). + + + Generates a human-readable safety warning message. + + + + Formatted error string suitable for human display: (line,col): error: message + or (line,col): warning: message. When is non-positive the + position prefix is omitted. + + Contains various extension methods which are commonly used in scripts + + + Optional sink for command-line / headless hosts. When non-null and + is true, /// + route through the sink instead of writing directly to . + + Toggle the "Please wait" spinner on/off. Useful if you want to display custom dialogs / UI to the end user. @@ -1744,6 +2646,31 @@ An object that represents the current selection in the TOM Explorer. + + + Best Practice Analyzer scripting helper. Use Bpa.Analyze(), Bpa.Rules, Bpa.ExportCsv(). + + + Delegates ( / ) are wired + by the host application during startup. If unwired, calls throw . + + + + + VertiPaq Analyzer read helper. Use Vpa.Summary, Vpa.Tables, Vpa.Columns, etc. + Call first to populate the data. + + + Provider delegates are wired by the host application during startup. If unwired, + collections return empty and returns null. + + + + + Visualization report builder. Use Viz.CreateReport() to create reports from scripts. + Commands are accumulated during script execution and processed by the frontend afterwards. + + SemanticBridge service for cross-platform semantic model operations. @@ -2174,6 +3101,195 @@ top of the code file, where as the "using" directives for each macro is added within the namespace of that class. + + + Script-accessible visualization builder. Exposed as ScriptHost.Viz. + Accumulates objects that the frontend processes into store actions. + Thread safety: is static; cleared at request start, drained at request end. + + + + + Clear accumulated commands. Called before script execution. + + + + + Get and clear accumulated commands. Called after script execution. + + + + + Create a new visualization report with the given name. Returns a builder for fluent configuration. + + + + + Fluent builder for configuring a viz report. + + + + Add a page to the report. + + + Set the color palette by preset name (e.g. "default", "warm-earth", "ocean"). + + + Set a custom color palette from hex color strings. + + + Set the report font family. + + + + Fluent builder for configuring a viz page. + + + + + Add a visual to the page. Supported types: bar, line, donut, kpi, table, matrix, + scatter, histogram, heatmap, radar, sankey, waterfall, treemap, choropleth, distribution. + Position and size are optional (auto-layout if omitted). + + + + Set the page name. + + + Add a page-level filter (slicer) for the specified table column. + + + + Fluent builder for configuring a visual within a page. + + + + + Add a field to a well. Wells: rows, values, legend, targets. + objectType: "Column" (default) or "Measure". + aggregation: "Sum" (default), "Count", "Average", "Min", "Max", "None". + + + + Set the visual title. + + + + Set a visual option (e.g. "horizontal" = true, "showLegend" = false). + Value must be a primitive type (bool, int, double, string). + + + + Set the visual size explicitly. + + + + A serializable command emitted by the viz scripting API. + The frontend viz-script-bridge processes these into store actions. + + + + + A serializable command emitted by the viz scripting API. + The frontend viz-script-bridge processes these into store actions. + + + + + Script-accessible VPA (VertiPaq Analyzer) read helper. Exposed as ScriptHost.Vpa. + Provides read access to previously collected VPA statistics. + Call first to populate the data. + + + The data-source delegates are wired by the host (CLI or TE3 UI) during startup. + In environments where no provider has been wired, properties return empty results. + + + + + Delegate that returns the VPA summary. Returns null if not collected. + + + + + Lightweight delegate that checks if VPA stats have been collected. + + + + + Delegate that returns per-table VPA statistics. + + + + + Delegate that returns per-column VPA statistics. + + + + + Delegate that returns relationship VPA statistics. + + + + + Delegate that returns per-partition VPA statistics. + + + + + Model-level VPA summary (size, counts, dates). Null if stats not yet collected. + + + + + Per-table statistics. Empty list if stats not yet collected. + + + + + Per-column statistics. Empty list if stats not yet collected. + + + + + Relationship statistics. Empty list if stats not yet collected. + + + + + Per-partition statistics. Empty list if stats not yet collected. + + + + + Whether VPA statistics have been collected for the current model. + + + + + Model-level VertiPaq Analyzer summary. Returned by . + + + + + Per-table VertiPaq Analyzer statistics. Returned by . + + + + + Per-column VertiPaq Analyzer statistics. Returned by . + + + + + Per-relationship VertiPaq Analyzer statistics. Returned by . + + + + + Per-partition VertiPaq Analyzer statistics. Returned by . + + Represents an exception that may contain PII/sensitive information in the message text. When the exception is sent to the telemetry service, @@ -2185,6 +3301,244 @@ A message that has been stripped of sensitive information. + + + Endpoint URL for Azure OpenAI (e.g., "https://your-resource.openai.azure.com") + or Custom provider base URL (e.g., "https://openrouter.ai/api/v1"). + + + + + Model name or deployment ID. If empty, uses the provider's default model. + + + + + The DefaultMaxOutputTokens config setting used when creating the Anthropic IChatClient. + When the value is set to 0, we use the maximum number of output tokens available for any + given Anthropic model. Set the value to any value greater than 0 to override the defaults. + + + + + Show the "Context:" selection indicator above assistant replies. + + + + + Show the "Custom Instructions:" / "Invoked:" skill pills above assistant replies. + + + + + Show the "Searching knowledge base..." indicator during tool calls. + + + + + Automatically compact conversations when context window usage exceeds the threshold. + + + + + Percentage of context window usage that triggers auto-compaction (50-90). + + + + + Gets the effective model to use, falling back to the provider's default if not specified. + + + + + Whether or not to display the "Script preview" dialog after C# script execution + + + + + Whether to check for knowledge base updates when the AI assistant starts. + + + + + For OpenAI endpoints, users can specify an optional Organization (in case they belong to multiple organizations) + + + + + For OpenAI endpoints, users can specify an optional Project (to direct billing to that project) + + + + + Returns the default model for the provider, or null if user must specify. + + + + + Defines the type of data being sent to the AI provider. + + + + DAX query execution results. + + + Reading content from open document editors. + + + Modifying content in open document editors. + + + Accessing model metadata. + + + Adding or modifying Best Practice Analyzer rules. + + + Reading macros from the user's macro library. + + + + Provides metadata and display information for consent types. + + + + + All available consent types. + + + + + Gets a description for a consent type. + + + + + Gets the label text for the preferences dialog. + + + + + Gets the tooltip text for the preferences dialog. + + + + + Returns true if the consent type supports per-model consent storage. + Only QueryData and ModelMetadata support "ForThisModel" consent option. + + + + + Stores per-model AI consent state. Stored in UserOptions (.tmuo file). + + + + + The set of consent types that have been granted for this model. + + + + + When consent was last granted. + + + + + Checks if consent has been granted for a specific type. + + + + + Grants consent for a specific type. + + + + + Returns true if any consent has been granted. + + + + + Stores global AI consent preferences. Only stores "Always" consents. + Stored in Preferences.json. + + + + + The set of consent types that are always granted (without prompting). + + + + + Checks if a consent type is always granted. + + + + + Grants "always" consent for a specific type. + + + + + Revokes "always" consent for a specific type. + + + + + User overlay directory path. + + + + + Represents information about an open document in the editor. + + + + Unique identifier for this document instance. + + + Document type: "dax-query", "csharp", or "dax-script". + + + Display title shown in the editor tab. + + + Whether the document has unsaved changes. + + + Optional file path if the document is saved to disk. + + + The document content (only populated if requested and consented). + + + + Provides access to open documents in the editor for AI tools. + + + + + Gets a list of open documents in the editor. + + If true, includes the document content (requires consent). + List of open document information. + + + + Updates the content of an open document. + + The document ID to update. + The new content for the document. + True if the update was successful, false if the document was not found. + + + + Gets the content of a specific open document. + + The document ID. + The document content, or null if the document was not found. + A dictionary indicating whether a given WorkspaceID is a PPU workspace or not @@ -2209,7 +3563,13 @@ TODO: Handle DirectQuery / user-delegated credentials - + + + Builds a Databricks ODBC connection string builder with driver, host, and auth parameters. + Does not include Catalog/Schema. Callers may add additional parameters before calling ToString(). + + + This object must be constructed on the UI thread, as it uses the WindowsFormsSynchronizationContext @@ -2550,6 +3910,25 @@ + + + Disables all AI functionality (when flag is set, the TabularEditor3.AI module isn't loaded) + + + + + Specifically disables the AI Chat feature, aka. AI Assistant. Other AI features may still be available. + + + + + Removes known Power BI Desktop/Designer suffixes from a window title string. + Only removes suffixes that appear at the end of the string to avoid incorrectly + truncating model names that legitimately contain " - ". + + The window title string to process + The window title with known suffixes removed, or the original string if no suffix is present + Indicate whether the system credentials of the application are sent with requests. @@ -2571,6 +3950,13 @@ Stores an encrypted version of the user password. Use the string Decrypt() extension method to decrypt. + + + Maximum number of (filtered) rows the data preview will sort by a column that has no attribute hierarchy + when no primary key / WINDOW support is available. Above this, sorting falls back to a plain in-memory TOPN + that materializes the whole table, so we refuse instead of risking an out-of-memory error. + + Specifies the options for retaining sorting and filtering settings in grid views. @@ -2591,6 +3977,11 @@ Always: Sorting and filtering are never reset if the columns still exist. + + + Path to the Tabular Editor 3 local application data folder, typically `%LocalAppData%\TabularEditor3` + + Provides additional configuration options for data refresh operations. @@ -2708,6 +4099,12 @@ Checks if the given exception represents an authentication cancellation by the user. + + + True if the connection uses DatabricksMultiCloud.Catalogs (i.e. non-Azure Databricks). + Azure AD authentication is not supported for multicloud connections. + + Indicates that every identifier must be enclosed in a pair of backticks @@ -2772,6 +4169,19 @@ 3) Name + + + True if at least one partition on the table is in DirectQuery mode (considering the model's DefaultMode). + DirectQuery tables don't support paginated data preview (TOPNSKIP only works with Skip=0). + + + + + True if the table has a primary key - i.e. a column marked IsKey, or the table sits on the "one" side + of a one-to-many relationship. This is a prerequisite for paginating via the WINDOW function when + sorting by a column that has no attribute hierarchy. + + If an EVALUATE statement uses a START AT clause, or if the main table expression returned is surrounded by "TOPN", the assumption is that diff --git a/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation.png b/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation.png index 4e314581b..c8aafcd24 100644 Binary files a/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation.png and b/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation.png differ diff --git a/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation2.png b/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation2.png index 5772cdf0e..4e034f04d 100644 Binary files a/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation2.png and b/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation2.png differ diff --git a/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation3.png b/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation3.png index 64e15f96d..b62312756 100644 Binary files a/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation3.png and b/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation3.png differ diff --git a/content/features/semantic-bridge-metric-view-fields-and-dimensions.md b/content/features/semantic-bridge-metric-view-fields-and-dimensions.md new file mode 100644 index 000000000..1470756cd --- /dev/null +++ b/content/features/semantic-bridge-metric-view-fields-and-dimensions.md @@ -0,0 +1,130 @@ +--- +uid: semantic-bridge-metric-view-fields-and-dimensions +title: Fields and Dimensions in Metric Views +author: Greg Baldini +updated: 2026-06-25 +applies_to: + products: + - product: Tabular Editor 2 + none: true + - product: Tabular Editor 3 + since: 3.26.2 + editions: + - edition: Desktop + none: true + - edition: Business + none: true + - edition: Enterprise + full: true +--- +# Fields and Dimensions in Metric Views + + + +In spring 2026, Databricks redefined a canonical top-level key in the Metric View YAML specification from `dimensions` (now legacy) to `fields`. +These both refer to the collection of columns—whether direct references to source columns or defined by a SQL expression—that is available to query in the Metric View. +[According to Databricks, `fields` is to be preferred, but both terms remain valid](https://learn.microsoft.com/en-us/azure/databricks/business-semantics/metric-views/yaml-reference#dimensions). +We have updated the Metric View object model in the Semantic Bridge to align with this. +Serialization and deserialization continue to work with either key in conformance with the Databricks spec. +We offer backward-compatibility shims in the object model for the old "dimension"-associated names. +Users of the object model in C# scripts should migrate to "field"-associated names when they can. + +**Who this affects**: anyone writing Metric View YAML by hand, anyone using the Metric View object model in C# scripts in Tabular Editor. + +## Versioning + +This change came after the v1.1 spec was published, and with no new spec version. +As such, we take a conservative approach in the Semantic Bridge. +We treat `dimensions` as the default for v0.1 and v1.1 Metric Views. +In the future, we will treat `fields` as the default for any newer-versioned Metric Views. +This is out of caution and a desire to offer the most interoperability with any other tools that may not be up to date with the latest published spec from Databricks. + +## Serialization and deserialization + +Per Databricks documentation, both keys remain valid for serializing Metric Views. + +We emit a warning upon deserialization when a Metric View's top-level keyword is not what was documented for that version. +The canonical keyword is `dimensions` before v1.1 and `fields` from v1.1 onward. +So we warn when a pre-v1.1 (v0.1) Metric View uses `fields`, and when a v1.1 or later Metric View uses `dimensions`. +The canonical pairings, `dimensions` before v1.1 and `fields` at v1.1 and later, deserialize without a warning. +Emitting `dimensions` by default at v1.1 while also warning about it upon deserialization is deliberate: +we emit based on the earliest documented v1.1 spec to avoid breaking any other tooling that is not up to date with the latest spec, +and we warn because the latest spec now prefers `fields`. +These warnings do not affect any operations you might want to perform with the Semantic Bridge or the Metric View object model. + +When we read a Metric View definition, we track the keyword used in the YAML, so that we can preserve the same keyword upon re-serializing the definition. +This guarantees that Metric View definitions have round-trip fidelity in our deserializer and serializer; e.g., if you have a v0.1 Metric View using `fields`, we will serialize it using the same keyword so you get the same YAML back out. + +The Semantic Bridge default of `dimensions` for v0.1 and v1.1 comes into play if you deserialize a Metric View with neither of `dimensions` or `fields` defined. +In this case, we apply our default logic if you add fields to the Metric View via a C# script and then later serialize the Metric View to YAML. + +We will continue to support both keywords in all Metric View versions unless a future Databricks spec update indicates otherwise. +You can continue to freely use either as you prefer, with notes about the warnings and defaults above for serialization and deserialization. + +We treat the case of both keys in a definition as an error and will fail to deserialize such a Metric View. +We are aware of no way to generate such a case other than by hand-editing YAML; certainly you cannot accidentally do this via the Semantic Bridge or any operations we expose. +Such a Metric View definition, which uses both `dimensions` and `fields`, will need manual remediation. + +An important note on the `materialization` block of the Metric View YAML definition: this section of YAML continues to use only `dimensions` regardless of the top-level key used. +[See the Databricks documentation for authoritative guidance on materialization](https://learn.microsoft.com/en-us/azure/databricks/business-semantics/metric-views/yaml-reference#materialization). + +Finally, there is no behavior or semantic difference in using either of `dimensions` or `fields`. +These keywords are simply synonyms, with Databricks guidance that `fields` is to be preferred. + +## Metric View object model API change: `Dimension` to `Field` + +In light of Databricks's guidance that `fields` is to be preferred, we have aligned to this throughout the Semantic Bridge. +We ship a [Metric View object model for programmatic interaction with a Metric View](xref:semantic-bridge-metric-view-object-model), necessary for implementing the translations in the Semantic Bridge. +We have deprecated the `Dimension` object, and all associated methods and properties that used "dimension" or "dimensions" in their name. +We have created a new `Field` object, and new "field"-named methods and properties. +The `Dimension` object and associated methods and properties will now give you a warning about their obsolete state. +All `Dimension`-based code will continue to work, but we may remove these after a suitable amount of time has passed. +Like Databricks, we recommend that you use `Field` and associated methods for all new work. + +In terms of implementation, all `Dimension`-based code passes through or mirrors the implementation of the `Field`-based code. +While we recommend using `Field`, you can use both interchangeably. +In general, migrating from `Dimension` to `Field` should be transparent. + +A technical note, [`Dimension`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Dimension) is a subclass of `Field`. +As such, there are a few ways in which you might observe differences between `Field` and `Dimension` code, and there are reasonable workarounds. +To write code that survives `Dimension`'s removal, branch and declare against `Field`; never name or test the concrete `Dimension` type. Given a field `f`: + +| Avoid | Use instead | Why it breaks when `Dimension` is removed | +|------------------------------------------------------------------|--------------------------------------------------------|---------------------------------------------------------------------------| +| `f is Dimension` | `f is Field` | `Dimension` stops compiling; `is Field` is true in both eras | +| `f is Dimension x` | `f is Field x` | same | +| `case Dimension x:` | `case Field x:` | same | +| `(Dimension)f`, `f as Dimension` | use `f` directly as a `Field` (no cast) | the cast target disappears; `f` already is a `Field` | +| `f.GetType() == typeof(Dimension)` | `f is Field` | `typeof(Dimension)` stops compiling | +| `f.GetType() == typeof(Field)` | `f is Field` | false now (runtime type is `Dimension`), true later, so it silently flips | +| `f.GetType().Name == "Dimension"` (or `== "Field"`) | `f is Field`; for a label, `f.ToString()` or `f.Name` | the type-name string is `"Dimension"` now, `"Field"` later | +| `Dimension x = ...`, `List`, `IEnumerable` | `Field x = ...`, `view.Fields`, `IReadOnlyList` | the `Dimension` type name goes away | +| `typeof(Dimension)`, `nameof(Dimension)` | `typeof(Field)`, `nameof(Field)` | the `Dimension` symbol is removed | +| `MakeValidationRule(...)` | `MakeValidationRule(...)` | the type argument references a removed type | + +> [!NOTE] +> The object model deprecation of the `Dimension` type and any future removal of this type and associated methods will have no effect on serializing or deserializing with either YAML keyword. + +## Name reference: `Dimension` to `Field` + +The following table lists each deprecated `Dimension`-based name and its canonical `Field`-based replacement. The legacy names still compile (with an obsolete warning) and behave identically; prefer the canonical names in new scripts. + +| Legacy name (obsolete) | Canonical name | Where | +|-----------------------------------------------------------------|-------------------------------------------------------------|------------------------------------------------------------------------| +| `MetricView.Dimension` (type) | `MetricView.Field` (type) | Object model | +| `view.Dimensions` | `view.Fields` | `View` collection | +| `view.Dimensions["name"]` | `view.Fields["name"]` | Name-based indexing into the collection | +| `view.AddDimension(name, expr)` | `view.AddField(name, expr)` | `View` method | +| `SemanticBridge.MetricView.MakeValidationRuleForDimension(...)` | `SemanticBridge.MetricView.MakeValidationRuleForField(...)` | Validation rule helper (both overloads, with and without `minVersion`) | +| `context.DimensionNames` | `context.FieldNames` | Context passed to a validation rule | + +## Related + +- @semantic-bridge +- @semantic-bridge-metric-view-object-model +- @semantic-bridge-metric-view-validation +- [Metric View API](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView) diff --git a/content/features/semantic-bridge-metric-view-object-model.md b/content/features/semantic-bridge-metric-view-object-model.md index 62ec8efc7..20d424970 100644 --- a/content/features/semantic-bridge-metric-view-object-model.md +++ b/content/features/semantic-bridge-metric-view-object-model.md @@ -2,7 +2,7 @@ uid: semantic-bridge-metric-view-object-model title: Semantic Bridge Metric View Object Model author: Greg Baldini -updated: 2026-04-17 +updated: 2026-06-29 applies_to: products: - product: Tabular Editor 2 @@ -24,24 +24,22 @@ SUMMARY: Overview of the Metric View object model built into the Semantic Bridge --> > [!NOTE] -> The Semantic Bridge as released in 3.25.0 is in public preview. -> It has limitations as documented below, and the API and feature surface area are subject to change. -> The object model here conspicuously lacks many affordances available in the TOMWrapper which users may be familiar with from C# scripts that manipulate a Tabular model. -> As noted in the [limitations of the Semantic Bridge](xref:semantic-bridge#public-preview-limitations), we currently only support Metric View v0.1 metadata. +> The Semantic Bridge is in public preview. +> The 3.25.0 release supports Metric View v0.1 metadata, and the 3.26.2 release supports Metric View v1.1 metadata. The Semantic Bridge includes an object model representing a Databricks Metric View. This allows you to work with Metric Views programmatically through C# scripts, similar to how you work with a Tabular model through the TOMWrapper. Other than the [import GUI](xref:semantic-bridge#interface), all access to and interaction with a Metric View is through C# scripts. -All content in this document is referring to C# code that you will use in a [C# script](xref:csharp-scripts). +All content in this document is referring to C# code that you would use in a [C# script](xref:csharp-scripts). ## Loading and accessing the Metric View -You can load a Metric view with [`SemanticBridge.MetricView.Load`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Load_System_String_) or [`SemanticBridge.MetricView.Deserialize`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Deserialize_System_String_). -This stores the deserialized Metric View as [`SemanticBridge.MetricView.Model`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Model). +You can load a Metric View with [`SemanticBridge.MetricView.Load`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Load%2A) or [`SemanticBridge.MetricView.Deserialize`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Deserialize%2A). +This stores the deserialized Metric View as [`SemanticBridge.MetricView.Model`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Model). This property returns a [`View`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View) object, which is the root of the Metric View object graph. -```csharp +```csharp {compile} // Load a Metric View from disk SemanticBridge.MetricView.Load("C:/path/to/metricview.yaml"); @@ -55,74 +53,94 @@ This means that you can load a Metric View once, and reference it from subsequen There is only ever a single Metric View loaded, and it is available in all scripts as `SemanticBridge.MetricView.Model` as mentioned above. This behavior is similar to the Tabular model in C# scripts, which is always available simply as `Model`. +[!INCLUDE [sample](../how-tos/includes/sample-metricview.md)] + ## Domain objects -The object model consists of four main types that correspond to the structure of a Metric View YAML file: -We do not repeat the entire specification here, so we encourage you to reference the [Databricks Metric View documentation](https://learn.microsoft.com/en-us/azure/databricks/metric-views/). +The object model consists of four main types that correspond to the structure of a Metric View YAML file. +We do not repeat the entire specification here, so we encourage you to reference the [Databricks Metric View documentation](https://learn.microsoft.com/en-us/azure/databricks/business-semantics/) +and our [own API reference for the object model](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView). + +| API Reference | Description | +|----------------------------------------------------------------------------------------|------------------------------------------------------------| +| [`View`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View) | The root object representing the entire Metric View | +| [`Join`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Join) | A join definition connecting a dimension table to the fact | +| [`Field`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Field) | A field definition (column) in the Metric View | +| [`Measure`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Measure) | An aggregation definition representing business logic | -| API Reference | Description | -|--------------------------------------------------------------------------------------------|------------------------------------------------------------| -| [`View`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View) | The root object representing the entire Metric View | -| [`Join`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Join) | A join definition connecting a dimension table to the fact | -| [`Dimension`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Dimension) | A field definition (column) in the Metric View | -| [`Measure`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Measure) | An aggregation definition representing business logic | +Most properties and attributes of a Metric View have a structured representation in the object model, +but we defer discussion of these in this document, +as those are all direct representations of the Metric View spec and documented in our API reference, mentioned above. > [!NOTE] -> In the object model, we follow C# naming conventions, and so use `PascalCase` for all type and property names in the object model. +> The object model was introduced in Tabular Editor 3.25.0 with support for Metric View v0.1. +> Support for Metric View v1.1 was added in Tabular Editor 3.26.2; +> this includes the `Comment` and `Materialization` properties on the `View`, +> `Cardinality` and `Rely` on `Join`, +> `Comment`, `DisplayName`, `Synonyms`, and `Format` on `Field` and `Measure`, +> `Window` on `Measure`. + +> [!NOTE] +> In the object model, we follow C# naming conventions: `PascalCase` for all type and property names. > The Metric View YAML specification follows a naming convention of `snake_case`. -> In general, we focus on the C# object model that is a component of the Semantic Bridge. -> Other than changing the case, we do not change any naming convention from the YAML. +> Serialization and deserialization convert between these, so C# scripts use `PascalCase` and the YAML we read and write stays spec-compliant `snake_case`. ### View -The [`View`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View) object is the root of the Metric View and contains: +[The `View`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View) object is the root of the Metric View and contains: -- `Version`: The Metric View specification version (e.g., "0.1") +- `Version`: The Metric View specification version (e.g., "1.1") - `Source`: The source data for the fact table (e.g., "catalog.schema.table") - `Filter`: Optional SQL boolean expression that applies to all queries -- `Joins`: Collection of join definitions -- `Dimensions`: Collection of dimension (field) definitions -- `Measures`: Collection of measure definitions +- `Comment`: Optional description of the Metric View +- `Joins`: Collection of join definitions; non-null empty collection if there are no `Join`s +- `Fields`: Collection of field definitions; non-null empty collection if there are no `Field`s +- `Measures`: Collection of measure definitions; non-null empty collection if there are no `Measure`s +- `Materialization`: Materialization configuration for query acceleration when hosted on Databricks; [see the `Materialization` API reference](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Materialization) -```csharp +```csharp {run id=view-props setup=mv-sample after=none output=true} var sb = new System.Text.StringBuilder(); var view = SemanticBridge.MetricView.Model; sb.AppendLine($"Version: {view.Version}"); sb.AppendLine($"Source: {view.Source}"); sb.AppendLine($"Filter: {view.Filter ?? "(none)"}"); -sb.AppendLine($"Joins: {view.Joins?.Count ?? 0}"); -sb.AppendLine($"Dimensions: {view.Dimensions?.Count ?? 0}"); -sb.AppendLine($"Measures: {view.Measures?.Count ?? 0}"); +sb.AppendLine($"Joins: {view.Joins.Count}"); +sb.AppendLine($"Fields: {view.Fields.Count}"); +sb.AppendLine($"Measures: {view.Measures.Count}"); Output(sb.ToString()); ``` -#### `View` translation and validation - -The `View.Source` property becomes the fact table in the Tabular model, named `'Fact'`. -If the `Source` is a 3-part table or view reference, it is translated to an M partition that accesses the SQL object by name. -If the `Source` is not a 3-part table or view reference, it is translated to an M partition with an embedded SQL query, with the entirety of the `Source` string as the SQL query. -The `Filter` property is ignored for purposes of translation. +**Output** -For purposes of evaluating validation rules, the `View` is checked first, then each collection is validated in order: `Joins`, then `Dimensions`, then `Measures`. -Validation of the fact table, `Source` is done in a validation rule for the `View` object. +``` +Version: 1.1 +Source: sales.fact.orders +Filter: (none) +Joins: 3 +Fields: 6 +Measures: 6 +``` ### Join -A [`Join`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Join) represents a dimension table that is joined to the fact table: +[A `Join`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Join) represents a dimension table that is joined to the fact table: - `Name`: Name of the joined table (used as an alias) - `Source`: Source table or query for the join (e.g., "catalog.schema.dimension_table") - `On`: Optional SQL boolean expression for the join condition - `Using`: Optional list of column names for the join (alternative to `On`) - `Joins`: Child joins (for snowflake schemas) +- `ParentJoin`: if this is a nested join, then `ParentJoin` is a pointer to the parent, otherwise null +- `Cardinality`: controls the relationship between `View.Source` or `ParentJoin` and this `Join`; [see the `JoinCardinality` API reference](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.JoinCardinality) +- `Rely`: Optimizer hints about the `Join`'s relationship to its parent; [see the `Rely` API reference](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Rely) -```csharp +```csharp {run id=join-props setup=mv-sample after=none output=true} var sb = new System.Text.StringBuilder(); var view = SemanticBridge.MetricView.Model; -foreach (var join in view.Joins ?? []) +foreach (var join in view.Joins) { sb.AppendLine($"Join: {join.Name}"); sb.AppendLine($" Source: {join.Source}"); @@ -135,70 +153,78 @@ foreach (var join in view.Joins ?? []) Output(sb.ToString()); ``` -#### `Join` translation and validation - -Nested `Join`s are not supported, i.e., only a strict star schema can be translated. -Only `On` joins with a single-field equijoin are supported for translation. -`Join`s each become a Tabular table, with an M partition defined according to the same rules as for the `View.Source` property. +**Output** -`Join`s are validated in the order they appear in the Metric View definition. +``` +Join: product + Source: sales.dim.product + On: source.product_id = product.product_id +Join: customer + Source: sales.dim.customer + On: source.customer_id = customer.customer_id +Join: date + Source: sales.dim.date + On: source.order_date = date.date_key +``` -### Dimension +### Field -A [`Dimension`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Dimension) represents a field (column) in the Metric View: +[A `Field`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Field) represents a field (column) in the Metric View: -- `Name`: The display name for the dimension -- `Expr`: The SQL expression defining the dimension (either a column reference or a SQL expression) +- `Name`: The name of the field, referenced in Metric View expressions +- `Expr`: The SQL expression defining the field (either a column reference or a SQL expression) +- `Comment`: Optional description of the field +- `DisplayName`: Optional human-readable display name for the field +- `Synonyms`: Optional alternative names for the field, used by AI and BI tools +- `Format`: Optional display format specification for the field's values; [see the `Format` API reference](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Format) -```csharp +```csharp {run id=field-props setup=mv-sample after=none output=true} var sb = new System.Text.StringBuilder(); var view = SemanticBridge.MetricView.Model; -foreach (var dim in view.Dimensions ?? []) +foreach (var field in view.Fields) { - sb.AppendLine($"Dimension: {dim.Name}"); - sb.AppendLine($" Expression: {dim.Expr}"); + sb.AppendLine($"Field: {field.Name}"); + sb.AppendLine($" Expression: {field.Expr}"); } Output(sb.ToString()); ``` -#### `Dimension` translation and validation +**Output** -Each `Dimension` becomes a column in the Tabular model. -If the `Expr` is an unqualified field reference, it is added to the fact table. -If the `Expr` is a qualified reference (e.g., `table.field`), then it is added to the table created for the `Join` with the same name as the table-part of the qualified reference; if the table-part is `source`, it is added to the fact table. -In both the qualified and unqualified field reference cases, the field is added as a [`TOMWrapper.DataColumn`](xref:TabularEditor.TOMWrapper.DataColumn). -If the `Expr` is a SQL expression, then it is added as [`TOMWrapper.CalculatedColumn`](xref:TabularEditor.TOMWrapper.CalculatedColumn). -When the `Expr` is a SQL expression, we attempt to extract all field references; if all field references share the same table-part, then we add it to the table created for that `Join`, otherwise we add it to the fact table. -We do not translate SQL expressions for `Dimension.Expr` properties; the SQL expression is included as a comment in the DAX expression for the `CalculatedColumn`. -It is up to the user to translate these expressions. -We attempt to identify all field references in the SQL expression and add those to the Tabular model as `DataColumn`s if they do not already exist as a Metric View `Dimension`. - -Some examples: - -| `Expr` | Translated as type | Added to table | Note | -|-------------------------------------------------------|--------------------|-----------------|------------------------------------------------------------------------------| -| `field1` | `DataColumn` | `'Fact'` | unqualified field references are equivalent to those qualified with `source` | -| `source.field2` | `DataColumn` | `'Fact'` | `source` is a reference to the `View.Source` property, aka the fact table | -| `dimCustomer.key` | `DataColumn` | `'dimCustomer'` | there must be a `Join` whose `Name` property is `dimCustomer` | -| `CONCAT(dimCustomer.FirstName, dimCustomer.LastName)` | `CalculatedColumn` | `'dimCustomer'` | all table-parts of the qualified name refer to the same name | -| `CONCAT(dimGeo.Country, dimCustomer.Address)` | `CalculatedColumn` | `'Fact'` | there are multiple distinct table-parts | - -`Dimension`s are validated in the order they appear in the Metric View definition. +``` +Field: product_name + Expression: product.product_name +Field: product_category + Expression: product.category +Field: customer_segment + Expression: customer.segment +Field: order_date + Expression: date.full_date +Field: order_year + Expression: date.year +Field: order_month + Expression: date.month_name +``` ### Measure -A [`Measure`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Measure) represents a named aggregation with business logic: +[A `Measure`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Measure) represents a named aggregation with business logic: -- `Name`: The display name for the measure +- `Name`: The name of the measure, referenced in Metric View expressions as `MEASURE()` - `Expr`: The SQL aggregate expression defining the measure +- `Comment`: Optional description of the measure +- `DisplayName`: Optional human-readable display name for the measure +- `Synonyms`: Optional alternative names for the measure, used by AI and BI tools +- `Format`: Optional display format specification for the measure's values; [see the `Format` API reference](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Format) +- `Window`: Optional list of window specifications for windowed or semi-additive aggregation; [see the `Window` API reference](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Window) -```csharp +```csharp {run id=measure-props setup=mv-sample after=none output=true} var sb = new System.Text.StringBuilder(); var view = SemanticBridge.MetricView.Model; -foreach (var measure in view.Measures ?? []) +foreach (var measure in view.Measures) { sb.AppendLine($"Measure: {measure.Name}"); sb.AppendLine($" Expression: {measure.Expr}"); @@ -207,28 +233,29 @@ foreach (var measure in view.Measures ?? []) Output(sb.ToString()); ``` -#### `Measure` translation and validation +**Output** -All measures are added to the fact table. -Simple aggregations are translated into DAX expressions. -A simple aggregation is a single aggregation of a single field (e.g. `SUM(table.field)`). -Supported aggregations are sum, count, distinct count, max, min, and average. -Other expressions are passed through as a comment in the DAX expression of the Tabular measure. -We attempt to identify all field references in the SQL expression and add those to the Tabular model as `DataColumn`s if they do not already exist as a Metric View `Dimension`. - -> [!WARNING] -> SQL and DAX are different languages with different semantics. -> It is possible that an automatically translated measure does not express the same computation in both Databricks Metric Views and Tabular models. -> It is up to the user to verify all code works as expected. - -`Measure`s are validated in the order they appear in the Metric View definition. +``` +Measure: total_revenue + Expression: SUM(revenue) +Measure: gross_margin + Expression: SUM(revenue) - SUM(cost) +Measure: order_count + Expression: COUNT(*) +Measure: avg_order_value + Expression: AVG(revenue) +Measure: revenue_to_budget + Expression: (SUM(revenue) - SUM(budget)) / SUM(budget) +Measure: unique_customers + Expression: COUNT(DISTINCT customer_id) +``` ## Using directives When working with the Metric View object model in C# scripts, you may need to add a using directive to avoid naming conflicts with similarly-named types in the Tabular Object Model. We recommend aliasing the namespace: -```csharp +```csharp {compile} // Alias to avoid conflicts with TOM types like Measure using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; @@ -236,62 +263,75 @@ SemanticBridge.MetricView.Load("C:/path/to/metricview.yaml"); var view = SemanticBridge.MetricView.Model; // Now you can reference types explicitly -foreach (MetricView.Dimension dim in view.Dimensions ?? []) +foreach (MetricView.Field field in view.Fields) { // ... } ``` -## Complete example +## Interacting with the object model -Here is a complete script that loads a Metric View and outputs a summary of its contents: +This document describes the patterns of using the object model. +See [the Semantic Bridge how-tos for detailed copy and paste-able examples](xref:semantic-bridge-how-tos). -```csharp -using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; +### `View` parent pointer -// Load the Metric View +All core Metric View objects described in this document inherit from [`MetricViewObjectBase`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.MetricViewObjectBase) for their core functionality. +Among other things, this means that each holds a `View` pointer back up to the Metric View they are defined in. +This allows you to inspect the whole Metric View when holding any of these objects. + +```csharp {compile} SemanticBridge.MetricView.Load("C:/path/to/metricview.yaml"); -var sb = new System.Text.StringBuilder(); -var view = SemanticBridge.MetricView.Model; +var v = SemanticBridge.MetricView.Model; // alias the Metric View as v just for concision +var f = v.Fields.FirstOrDefault(); // f is the first field defined in the Metric View +Output(f.View == v); // the field, f, lets you navigate up to the containing view +``` -// sb.AppendLine summary -sb.AppendLine("=== Metric View Summary ==="); -sb.AppendLine($"Version: {view.Version}"); -sb.AppendLine($"Source: {view.Source}"); +### Adding objects -if (view.Joins != null && view.Joins.Count > 0) -{ - sb.AppendLine($"\nJoins ({view.Joins.Count}):"); - foreach (var join in view.Joins) - { - sb.AppendLine($" - {join.Name} -> {join.Source}"); - } -} +You never instantiate a `View`, `Join`, `Field`, or `Measure` directly. +Instead, deserialize or load a base `View`, or use the various `Add` methods: -if (view.Dimensions != null && view.Dimensions.Count > 0) -{ - sb.AppendLine($"\nDimensions ({view.Dimensions.Count}):"); - foreach (var dim in view.Dimensions) - { - sb.AppendLine($" - {dim.Name}: {dim.Expr}"); - } -} +- New Metric View: + - [`Deserialize`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Deserialize%2A) YAML in a string + - [`Load`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Load%2A) a YAML file from disk +- Add objects + - [`view.AddJoin`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View.AddJoin%2A) + - [`view.AddField`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View.AddField%2A) + - [`view.AddMeasure`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View.AddMeasure%2A) -if (view.Measures != null && view.Measures.Count > 0) -{ - sb.AppendLine($"\nMeasures ({view.Measures.Count}):"); - foreach (var measure in view.Measures) - { - sb.AppendLine($" - {measure.Name}: {measure.Expr}"); - } -} +`Deserialize` and `Load` both set the global `SemanticBridge.MetricView.Model` so you can interact with it in scripts. +The `Add` methods all return the new object just added so that you can interact with it and set additional properties; +this mirrors the interaction with TOM objects you are already familiar with in C# scripts. -Output(sb.ToString()); -``` +### Modify properties + +The Metric View object model is mutable throughout, so you can simply set properties directly. +C# autocompletion in Tabular Editor 3 will help with finding the right properties and types to use. +All properties and their types are in [the API documentation](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView). + +### Accessing objects by name + +The root `View` contains collections for `Joins`, `Fields`, and `Measures`. +Each `Join` contains a child `Joins` collection. +Each of these can be indexed by name; +this looks up the child object by its `Name` property. +This lookup is case insensitive, matching the default in Databricks SQL. + +### Metric View versions + +We track the [Databricks Metric View documentation](https://learn.microsoft.com/en-us/azure/databricks/business-semantics/) to stay up to date with the specification. +All properties are annotated with the version that they were introduced. +Thanks to this, the object model will raise exceptions and surface diagnostics if you attempt to set a property that is not allowed for a given version of the spec. +We recommend always running [`SemanticBridge.MetricView.Validate();`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Validate) after modifying a Metric View in a C# script; +this will check all default validation rules for correctness. ## References - [`MetricView` namespace API documentation](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView) -- @semantic-bridge-how-tos -- [Databricks Metric View documentation](https://learn.microsoft.com/en-us/azure/databricks/metric-views/) -- [Databricks Metric View YAML specification](https://learn.microsoft.com/en-us/azure/databricks/metric-views/data-modeling/syntax) +- @semantic-bridge-metric-view-fields-and-dimensions +- @semantic-bridge-metric-view-validation +- @semantic-bridge-metric-view-tabular-translation +- [Semantic Bridge how-tos for detailed examples](xref:semantic-bridge-how-tos) +- [Databricks Metric View documentation](https://learn.microsoft.com/en-us/azure/databricks/business-semantics/) +- [Databricks Metric View YAML specification](https://learn.microsoft.com/en-us/azure/databricks/business-semantics/metric-views/yaml-reference) diff --git a/content/features/semantic-bridge-metric-view-tabular-translation.md b/content/features/semantic-bridge-metric-view-tabular-translation.md new file mode 100644 index 000000000..ec96f22d5 --- /dev/null +++ b/content/features/semantic-bridge-metric-view-tabular-translation.md @@ -0,0 +1,242 @@ +--- +uid: semantic-bridge-metric-view-tabular-translation +title: Metric View to Tabular translation +author: Greg Baldini +updated: 2026-06-30 +applies_to: + products: + - product: Tabular Editor 2 + none: true + - product: Tabular Editor 3 + since: 3.25.0 + editions: + - edition: Desktop + none: true + - edition: Business + none: true + - edition: Enterprise + full: true +--- +# Metric View to Tabular translation + + + +> [!NOTE] +> The Semantic Bridge is in public preview. +> The 3.25.0 release supports Metric View v0.1 metadata, and the 3.26.2 release supports Metric View v1.1 metadata. +> Limitations are described below. + +This page describes how translation works when importing a Metric View definition into a Tabular model. + +## Translation process + +Translating a Metric View to a Tabular model happens in several steps: + +1. Read the YAML from disk +2. Deserialize the YAML +3. Validate that the deserialized YAML represents a valid Metric View +4. If it is a valid Metric View, store it as the currently loaded Metric View, similar to how there is a loaded Tabular model that you interact with. + If it is not a valid Metric View, the process stops here and diagnostic messages are available. +5. Analyze the Metric View and attempt to transform it to an intermediate representation +6. Attempt to transform the intermediate representation to a Tabular model + +The import GUI handles all of this for you, but you can also use C# scripts to customize different steps of the process and operate on the Metric View programmatically, similarly to how you are used to doing with a Tabular model. +Specifically, you can + +- load a Metric View from disk with [`SemanticBridge.MetricView.Load`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Load%2A): loading makes it available in C# scripts as [`SemanticBridge.MetricView.Model`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Model), but does not import the structure into the Tabular model +- deserialize a Metric View from a string with [`SemanticBridge.MetricView.Deserialize`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Deserialize%2A): similar to loading, the model is available as [`SemanticBridge.MetricView.Model`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Model), but is not imported +- save a Metric View to disk with [`SemanticBridge.MetricView.Save`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Save%2A) +- serialize a Metric View to a string with [`SemanticBridge.MetricView.Serialize`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Serialize%2A). +- validate a Metric View using a system that is similar to the [Best Practice Analyzer](xref:best-practice-analyzer) with [`SemanticBridge.MetricView.Validate`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Validate%2A) + - you can create your own custom validation rules with [`SemanticBridge.MetricView.MakeValidationRule`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.MakeValidationRule%2A) and its simpler versions +- import a Metric View to Tabular with [`SemanticBridge.MetricView.ImportToTabularFromFile`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.ImportToTabularFromFile%2A), which does the exact same as the import GUI, or [`SemanticBridge.MetricView.ImportToTabular`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.ImportToTabular%2A), which is similar, but operates on the currently loaded Metric View, rather than reading one from disk. + +### Per object translation notes + +The four items below, `View`, `Join`, `Field`, and `Measure`, are the core objects of a Metric View definition that become TOM objects. +Other metadata in the Metric View definition are either ignored or modify exactly how these objects are translated. + +> [!NOTE] +> The translation is performed upon the Metric View object model, so we discuss everything in these terms. +> See [the Metric View object model docs](xref:semantic-bridge-metric-view-object-model) for specifics of the object model and how it aligns to the YAML spec. + +#### `View` translation + +- translate + - `Source`: becomes the single fact table, named 'Fact' in the TOM model + - `Comment`: becomes TOM `Model.Description` + - `Joins`: see `Join` + - `Fields`: see `Field` + - `Measures`: see `Measure` +- do not translate + - `Filter` + - `Materialization` + +If the `Source` is a 3-part table or view reference, it is translated to an M partition that accesses the SQL object by name. +If the `Source` is not a 3-part table or view reference, it is translated to an M partition with an embedded SQL query, with the entirety of the `Source` string as the SQL query. + +The `Filter` property is ignored for purposes of translation; +if you need the logic included in `Filter`, you will have to manually add this. +The `Filter` expression applies to all queries against the Metric View, and so a full automated translation would require joining all tables named in `Joins` in generated M code in TOM. + +Any defined `Materialization` is ignored for the purposes of translation; +these are query optimization metadata for executing queries on Databricks and not relevant to a TOM model. + +#### `Join` translation + +- translated + - `Name`: becomes TOM table name + - `Source`: becomes M partition on table + - `On`: becomes a TOM relationship + - `Joins`: become additional TOM tables + - `Cardinality` +- untranslated + - `Using` + - `Rely` + +`Join`s each become a TOM table, with an M partition defined according to the same rules as for the `View.Source` property. + +`On` equijoins (e.g., `source.fk = dimTable.pk`) become TOM relationships. +Any other predicate in an `On` property is not translated as a relationship. + +Trees of `Join`s in a Metric View are translated as TOM tables in a chain of N:1 relationships, where the cardinalities are supported (see note on cardinality below). +This represents a snowflake model schema. + +`Cardinality` of `ManyToOne` is translated as a TOM N:1 relationship. +An unpopulated `Cardinality` or a `Join` without this property set is treated as `ManyToOne` by default, in accordance with Databricks docs. +Other values for `Cardinality` are not yet supported for translation as a relationship. + +`Using` joins are not supported for translation; these do not yield a TOM relationship. + +`Rely` is not propagated into the TOM model in any way. + +In cases where a TOM relationship is not created, we still create a TOM table and translate all Metric View `Fields` to TOM columns as described elsewhere. + +> [!NOTE] +> Databricks has recently introduced a new pattern using `OneToMany` cardinality against multiple `Join` sub-trees to implement a multi-fact model. +> We do not yet translate this pattern fully: we bring over all tables, fields, and measures, but do not create all relationships. +> A diagnostic warning is shown when importing a model following this pattern. + +#### `Field` translation + +- translated + - `Name` + - `DisplayName` + - `Expr` + - `Comment`: becomes TOM column's `Description` property + - `Format`: becomes TOM column's `FormatString` property; see section below on `Format` translation +- untranslated + - `Synonyms` + +Each `Field` becomes a column in the Tabular model. + +The TOM column's `Name` is `Field.DisplayName` if it is populated, +otherwise it is `Field.Name`. + +If the `Expr` is an unqualified field reference, it is added to the fact table. +If the `Expr` is a qualified reference (e.g., `table.field`), +then it is added to the table created for the `Join` with the same name as the table-part of the qualified reference; +if the table-part is `source`, it is added to the fact table. +In both the qualified and unqualified field reference cases, +the field is added as a [`TOMWrapper.DataColumn`](xref:TabularEditor.TOMWrapper.DataColumn). +If the `Expr` is a SQL expression, +then it is added as [`TOMWrapper.CalculatedColumn`](xref:TabularEditor.TOMWrapper.CalculatedColumn). +When the `Expr` is a SQL expression, we extract all field references; +if all field references share the same table-part, +then we add it to the table created for that `Join`, +otherwise we add it to the fact table. +We identify all field references in the SQL expression and add those to the Tabular model as `DataColumn`s if they do not already exist as a Metric View `Field`. +We do not translate SQL expressions for `Field.Expr` properties; +the SQL expression is included as a comment in the DAX expression for the `CalculatedColumn`. +It is up to the user to translate these expressions. + +Some examples: + +| `Expr` | Translated as type | Added to table | Note | +|-------------------------------------------------------|--------------------|-----------------|------------------------------------------------------------------------------| +| `field1` | `DataColumn` | `'Fact'` | unqualified field references are equivalent to those qualified with `source` | +| `source.field2` | `DataColumn` | `'Fact'` | `source` is a reference to the `View.Source` property, aka the fact table | +| `dimCustomer.key` | `DataColumn` | `'dimCustomer'` | there must be a `Join` whose `Name` property is `dimCustomer` | +| `CONCAT(dimCustomer.FirstName, dimCustomer.LastName)` | `CalculatedColumn` | `'dimCustomer'` | all table-parts of the qualified name refer to the same name | +| `CONCAT(dimGeo.Country, dimCustomer.Address)` | `CalculatedColumn` | `'Fact'` | there are multiple distinct table-parts | + + +#### `Measure` translation + +- translated + - `Name` + - `DisplayName` + - `Expr`: becomes TOM measure's `Expression` property; see section below on SQL -> DAX translation + - `Comment`: becomes TOM measure's `Description` property + - `Format`: becomes TOM measure's `FormatString` property; see section below on `Format` translation +- untranslated + - `Synonyms` + - `Window` + +All measures are added to the fact table. + +The TOM measure's `Name` is the Metric View's `Measure.DisplayName` if it exists, +otherwise it is the Metric View's `Measure.Name`. + +`Expr` is translated to DAX or passed through as a comment in cases where we cannot automatically translate the measure. +We identify all field references in the SQL expression and add those to the Tabular model as `DataColumn`s if they do not already exist as a Metric View `Field`. + +Window specifications are not translated and cause fallback to a DAX comment, regardless of the SQL in `Expr`. + +### `Format` translation + +A Metric View `Format` is translated to a TOM `FormatString` on the object that carries it. +The target is a VBA-style format string, as used in TOM models. +The translation is best-effort: +if we can create a format string that exactly matches the configuration of the `Format`, then we do so; +if we cannot create an exact equivalent, then we fall back to an approximate equivalent and emit a warning you can review after import. + +Currency, percentage, and number formats translate cleanly: +currency becomes a currency-symbol prefix on a grouped numeric format, +percentage becomes a percent format that honors the declared decimal places, +and number honors the declared decimal places and group separator, with the scientific abbreviation becoming an exponential format. + +Year-month-day dates translate cleanly to an ISO date format; +locale long-month and locale numeric-month dates translate cleanly to the `Long Date` and `Short Date` named formats; +and hour-minute and hour-minute-second times translate cleanly to the `Short Time` and `Long Time` named formats. + +The remaining formats cannot be precisely translated and emit a warning: +the compact number abbreviation and the byte format fall back to a plain numeric format; +the locale short-month date falls back to `Long Date`; +the year-week date falls back to an ISO date; +and a combined date-and-time format falls back to an ISO composite. + +### SQL -> DAX translation + +Metric Views provide a structured layer on top of SQL expressions, and so part of translating a Metric View is translating SQL to DAX and M in the Tabular model. +Supported aggregations are sum, count, distinct count, max, min, and average. +Basic arithmetic, common counting patterns, measure references, and parenthesis precedence are all supported for SQL->DAX translation. + +> [!WARNING] +> Note that SQL and DAX are different languages with different semantics. +> We can make no guarantee that a translated measure will behave identically between the Metric View SQL and the Tabular DAX we generate. +> Basic aggregates defined on fact table fields should behave the same, whereas aggregates defined on fields in dimension tables are more likely to produce undesired results. + +## Common terms across Metric Views and Tabular models + +For those of our users who may be unfamiliar with either Metric Views or Tabular models, we provide an incomplete rosetta stone below. +We refer to the names of Metric View objects based on their representation in YAML, and Tabular based on the name of the type of object in TMDL/TMSL. + +| General term | Name in Tabular | Name in Metric View | Description | Note | +|----------------------|-----------------|-----------------------|------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| fact | table | source | A table holding foreign keys to dimensions and quantitative values to be aggregated | a Metric View has a single fact which is unnamed and captured as the root-level `source` attribute in YAML. Tabular models do not differentiate between types of tables: whether a table is a fact can only be inferred | +| dimension | table | join | A table holding descriptive attributes and one primary key to which the fact is related | Tabular models do not differentiate, so the role of "dimension" is inferred only, as with a fact. | +| partition | partition | source (join only) | An object for data management, holding a subset of data in a table | Tabular model tables can have many partitions and must have at least one. The Metric View fact, as mentioned above is defined purely as a source, but Metric View joins also have a `source` property, which acts roughly like a partition | +| field | column | field | A column in a table | | +| measure | measure | measure | A quantitative value that is aggregated according to business logic in the model | Measures in a Tabular model are written in DAX, and in a Metric View in SQL | +| join or relationship | relationship | join.on or join.using | A correspondence between key fields in two tables, a foreign key in one and primary key in the other | Relationships are explicit objects in a Tabular model, and implicitly defined as a property of the `join` object in Metric View YAML | + +## Additional references + +- @semantic-bridge +- @semantic-bridge-metric-view-object-model +- @semantic-bridge-metric-view-validation +- @semantic-bridge-how-tos +- [Metric View API docs](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView) diff --git a/content/features/semantic-bridge-metric-view-validation.md b/content/features/semantic-bridge-metric-view-validation.md index 8652e7328..459ae17be 100644 --- a/content/features/semantic-bridge-metric-view-validation.md +++ b/content/features/semantic-bridge-metric-view-validation.md @@ -2,7 +2,7 @@ uid: semantic-bridge-metric-view-validation title: Semantic Bridge Metric View Validation author: Greg Baldini -updated: 2026-04-17 +updated: 2026-07-01 applies_to: products: - product: Tabular Editor 2 @@ -24,7 +24,8 @@ SUMMARY: Describes the validation framework for Metric Views in the Semantic Bri --> There is a validation framework built into the Semantic Bridge to allow users to validate and define rules to check a Metric View before importing it to Tabular. -This validation is shared at every stage of the translation pipeline, from first deserializing the Metric View, through to errors in translation to DAX and Tabular. +This diagnostic reporting is shared at every stage of the translation pipeline, +from first deserializing the Metric View, through to errors in translation to DAX and Tabular. > [!NOTE] > The Semantic Bridge is currently in public preview, so interfaces may change as the feature matures. @@ -38,7 +39,8 @@ There are several phases of validation 2. acting on the loaded Metric View 3. upon translating the Metric View to Tabular -The first and third are automatic and internal to the Semantic Bridge, but the second is where users can provide their own validation rules. +The first and third are automatic and internal to the Semantic Bridge, +but the second is where users can provide their own validation rules. Validation is a process of evaluating each of a set of validation rules against all objects in the Metric View. A validation rule is defined to apply to exactly one type of Metric View object, e.g. a `Join` or `Measure`. @@ -46,14 +48,14 @@ After a validation is complete, all diagnostics from rule violations are returne ## Anatomy of a validation rule -Validation rules are all instances of [`IMetricViewValidationRule`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.Interfaces.IMetricViewValidationRule.html). +Validation rules are all instances of [`IMetricViewValidationRule`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.Interfaces.IMetricViewValidationRule). Rather than dig into that interface, it is easier to understand and work with validation rules with the helper methods: -- [`MakeValidationRuleForDimension`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_MakeValidationRuleForDimension_System_String_System_String_System_String_System_Func_TabularEditor_SemanticBridge_Platforms_Databricks_MetricView_Dimension_System_Boolean__) -- [`MakeValidationRuleForJoin`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_MakeValidationRuleForJoin_System_String_System_String_System_String_System_Func_TabularEditor_SemanticBridge_Platforms_Databricks_MetricView_Join_System_Boolean__) -- [`MakeValidationRuleForMeasure`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_MakeValidationRuleForMeasure_System_String_System_String_System_String_System_Func_TabularEditor_SemanticBridge_Platforms_Databricks_MetricView_Measure_System_Boolean__) -- [`MakeValidationRuleForView`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_MakeValidationRuleForView_System_String_System_String_System_String_System_Func_TabularEditor_SemanticBridge_Platforms_Databricks_MetricView_View_System_Boolean__) -- [`MakeValidationRule`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_MakeValidationRule__1_System_String_System_String_System_Func___0_TabularEditor_SemanticBridge_Platforms_Databricks_Validation_IReadOnlyValidationContext_System_Collections_Generic_IEnumerable_TabularEditor_SemanticBridge_Orchestration_DiagnosticMessage___) +- [`MakeValidationRuleForField`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.MakeValidationRuleForField%2A) +- [`MakeValidationRuleForJoin`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.MakeValidationRuleForJoin%2A) +- [`MakeValidationRuleForMeasure`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.MakeValidationRuleForMeasure%2A) +- [`MakeValidationRuleForView`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.MakeValidationRuleForView%2A) +- [`MakeValidationRule`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.MakeValidationRule%2A) The first four are all special purpose to make a rule for the object type in their name. They offer a simplified interface where you provide: @@ -65,32 +67,36 @@ They offer a simplified interface where you provide: The name and category are intended to make it easier to deal with collections of rules, as you will do in C# scripts that utilize custom rules. +Each of these helpers also has an overload with a final `minVersion` argument. +This argument would take a version string, such as "0.1" or "1.1". +Rules with `minVersion` set are only evaluated for Metric Views at or above that version. + This is easier to understand with an example: -```csharp -// create a rule to check for underscores in dimension names -var myRule = SemanticBridge.MetricView.MakeValidationRuleForDimension( +```csharp {compile} +// create a rule to check for underscores in field names +var myRule = SemanticBridge.MetricView.MakeValidationRuleForField( "no_underscores", "naming", - "Do not include underscores in dimension names. Use user-friendly names with spaces.", - (dimension) => dimension.Name.Contains('_') + "Do not include underscores in field names. Use user-friendly names with spaces.", + (field) => field.Name.Contains('_') ); ``` -This makes a rule that will apply to all [Metric View `Dimension`s](/api/TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Dimension.html). +This makes a rule that will apply to all [Metric View `Field`s](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Field). The rule is named (ironically) "no_underscores". It has a category of "naming", to indicate that it has to do with how we name things. -The message you will see when the rule is violated is, "Do not include underscores in dimension names. Use user-friendly names with spaces." -The last argument defines a function that will be called for each Metric View dimension in the model; its body is a boolean expression that returns `true` for a Metric View dimension with an underscore in its `Name` property. +The message you will see when the rule is violated is, "Do not include underscores in field names. Use user-friendly names with spaces." +The last argument defines a function that will be called for each Metric View field in the model; its body is a boolean expression that returns `true` for a Metric View field with an underscore in its `Name` property. Here's a full script that defines a Metric View inline, and then deserializes and validates it, showing how this rule is used. -```csharp +```csharp {run id=simple setup=none after=none output=true} // create a new simple Metric View SemanticBridge.MetricView.Deserialize(""" - version: 0.1 + version: 1.1 source: database.schema.table - dimensions: + fields: - name: first_field expr: source.first_field - name: another field with no underscores @@ -98,22 +104,37 @@ SemanticBridge.MetricView.Deserialize(""" """); // create a new validation rule -var myRule = SemanticBridge.MetricView.MakeValidationRuleForDimension( +var myRule = SemanticBridge.MetricView.MakeValidationRuleForField( "no_underscores", "naming", - "Do not include underscores in dimension names. Use user-friendly names with spaces.", - (dimension) => dimension.Name.Contains('_') + "Do not include underscores in field names. Use user-friendly names with spaces.", + (field) => field.Name.Contains('_') ); // run validation with the rule defined above and output the diagnostic messages -Output(SemanticBridge.MetricView.Validate([myRule])); +var sb = new System.Text.StringBuilder(); +foreach (var d in SemanticBridge.MetricView.Validate([myRule])) +{ + sb.AppendLine($"[{d.Severity}] {d.Code} {d.Path}"); + sb.AppendLine($" {d.Context} {d.Message}"); + sb.AppendLine(); +} +Output(sb.ToString()); +``` + +**Output** + +``` +[Error] no_underscores Model.Fields["first_field"] + Do not include underscores in field names. Use user-friendly names with spaces. ``` -You can see that one of the fields defined as a Metric View dimension has an underscore in its name. +You can see that one of the Metric View fields has an underscore in its name. When you run the script, you can see one diagnostic message after validating with the rule we defined. You can see the details that are provided in the diagnostic message: -- Code, Context: not used when you use one of these helper methods to make your rule +- Code: the name you assign to your rule +- Context: not set by these helpers - Message: the message you defined in the rule - Path: a representation of where you find that object in the Metric View - Severity: set to Error by default with these helpers @@ -122,42 +143,65 @@ You can see the details that are provided in the diagnostic message: If you want more control over the diagnostic message and more flexibility in the function for your validation, you can use `MakeValidationRule` mentioned above to make a contextual validation rule. -```csharp +```csharp {run id=contextual setup=none after=none output=true} // necessary to use the Metric View object model // aliasing to avoid conflicts with same-named TOM objects using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; // create a new simple Metric View SemanticBridge.MetricView.Deserialize(""" - version: 0.1 + version: 1.1 source: database.schema.table - dimensions: - - name: same_field - expr: source.same_field - - name: same_field - expr: source.same_field + fields: + - name: customer + expr: source.customer_id + - name: repeat_customer + expr: source.customer_id """); // create a new validation rule -var myRule = SemanticBridge.MetricView.MakeValidationRule( - "no_duplicate_dimensions", - "naming", - (dimension, context) => - context.DimensionNames.Contains(dimension.Name) - ? [context.MakeError($"{dimension.Name} appears more than once as a dimension")] - : [] - ); +var myRule = SemanticBridge.MetricView.MakeValidationRule( + "no_aliased_fields", + "modeling", + (field, context) => + { + var original = context.FieldNames.FirstOrDefault(seen => field.View.Fields[seen].Expr == field.Expr); + return original == null + ? [] + : [context.MakeError( + "field_alias", + $"Field '{field.Name}' reuses source expression '{field.Expr}', already used by field '{original}'.", + field)]; + }); // run validation with the rule defined above and output the diagnostic messages -Output(SemanticBridge.MetricView.Validate([myRule])); +var sb = new System.Text.StringBuilder(); +foreach (var d in SemanticBridge.MetricView.Validate([myRule])) +{ + sb.AppendLine($"[{d.Severity}] {d.Code} {d.Path}"); + sb.AppendLine($" {d.Context} {d.Message}"); + sb.AppendLine(); +} +Output(sb.ToString()); +``` + +**Output** + +``` +[Error] field_alias Model.Fields["repeat_customer"] + Field 'repeat_customer' reuses source expression 'source.customer_id', already used by field 'customer'. ``` -This helper method requires you to pass the object type as a type parameter, and the validation function now is a two-parameter function, defined with the signature `(objectType, context)`. +This helper method requires you to pass the object type as a type parameter, and the validation function now is a two-parameter function, defined with the signature `(metricViewObject, context)`. The first parameter is the Metric View object that the rule is evaluated for. -The second parameter is an [`IReadOnlyValidationContext`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.Validation.IReadOnlyValidationContext.html). -This context object holds collections with the names of already-checked objects; this makes it useful to check for duplicate names. -The context object also has a helper method to make a new diagnostic message; the benefit here is that your message doesn't have to be a hard-coded string, but can include properties of the object you are checking. -You can see in this example that we include the duplicated Metric View dimension name in the message. +The second parameter is an [`IReadOnlyValidationContext`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.Validation.IReadOnlyValidationContext). +This context object holds collections with the names of already-checked objects; +this means we can use it to inspect only objects already validated. +The context object also has helper methods to make a new diagnostic message; +the benefit here is that your message doesn't have to be a hard-coded string, +but can include properties of the object you are checking. +We use `MakeError`, and the context object also includes a `MakeWarning`. +You can see in this example that we include in the message both the offending field and the field it aliases. ![output from one field violating the more complex validation rule](~/content/assets/images/features/semantic-bridge/semantic-bridge-metric-view-validation2.png) @@ -165,7 +209,7 @@ You can see in this example that we include the duplicated Metric View dimension It is a good idea to make many simple rules, rather than fewer, more complex rules. The validation process is very light-weight, so there are not performance concerns from a proliferation of rules. -For example, if you want to make sure that Metric View dimension names are not `camelCased`, not `kebab-cased` and not `snake_cased`, it is better to make three separate rules, rather than trying to check for each of those conditions in a single rule. +For example, if you want to make sure that Metric View field names are not `camelCased`, not `kebab-cased` and not `snake_cased`, it is better to make three separate rules, rather than trying to check for each of those conditions in a single rule. This allows each rule to be simple, and for the messages to be very specific, and therefore more easily actionable. In general, once you have a rule that catches a specific issue, it is better to leave that alone, rather than editing it. @@ -173,7 +217,7 @@ If you find that the rule is missing some condition you'd like to catch, just ad You can save many different rules in a C# script for re-use with different Metric Views. Because [a loaded Metric View is accessible in multiple scripts](xref:semantic-bridge-metric-view-object-model#loading-and-accessing-the-metric-view) you can save C# scripts that only define rules and then call `SemanticBridge.MetricView.Validate`, and re-use those validation scripts easily. -See the image below, where the script on the left, "load-mv.csx" has already been run, to load a Metric View to Tabular Editor. +See the image below, where the script on the left, "deserialize-mv.csx" has already been run, to load a Metric View to Tabular Editor. Then, the second script, on the right, "run-rules.csx", is run second to validate. This second script could be one that you keep around for all of your Metric Views. @@ -182,50 +226,74 @@ This second script could be one that you keep around for all of your Metric View The scripts are copied below for convenience, but are just rearrangements of scripts we saw above. -**"load-mv.csx"** +**"deserialize-mv.csx"** -```csharp +```csharp {run id=deserialize setup=none after=none output=false} // create a new simple Metric View SemanticBridge.MetricView.Deserialize(""" - version: 0.1 + version: 1.1 source: database.schema.table - dimensions: - - name: same_field - expr: source.same_field - - name: same_field - expr: source.same_field + fields: + - name: customer + expr: source.customer_id + - name: repeat_customer + expr: source.customer_id """); ``` **"run-rules.csx"** -```csharp +```csharp {run id=run-rules setup=none after=deserialize output=true} // necessary to use the Metric View object model // aliasing to avoid conflicts with same-named TOM objects using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; //create a simple validation rule -var simpleRule = SemanticBridge.MetricView.MakeValidationRuleForDimension( +var simpleRule = SemanticBridge.MetricView.MakeValidationRuleForField( "no_underscores", "naming", - "Do not include underscores in dimension names. Use user-friendly names with spaces.", - (dimension) => dimension.Name.Contains('_') + "Do not include underscores in field names. Use user-friendly names with spaces.", + (field) => field.Name.Contains('_') ); // create a contextual validation rule -var contextualRule = SemanticBridge.MetricView.MakeValidationRule( - "no_duplicate_dimensions", - "naming", - (dimension, context) => - context.DimensionNames.Contains(dimension.Name) - ? [context.MakeError($"{dimension.Name} appears more than once as a dimension")] - : [] - ); +var contextualRule = SemanticBridge.MetricView.MakeValidationRule( + "no_aliased_fields", + "modeling", + (field, context) => + { + var original = context.FieldNames.FirstOrDefault(seen => field.View.Fields[seen].Expr == field.Expr); + return original == null + ? [] + : [context.MakeError( + "field_alias", + $"Field '{field.Name}' reuses source expression '{field.Expr}', already used by field '{original}'.", + field)]; + }); // run validation with the rules defined above and output the diagnostic messages -Output(SemanticBridge.MetricView.Validate([simpleRule, contextualRule])); +var sb = new System.Text.StringBuilder(); +foreach (var d in SemanticBridge.MetricView.Validate([simpleRule, contextualRule])) +{ + sb.AppendLine($"[{d.Severity}] {d.Code} {d.Path}"); + sb.AppendLine($" {d.Context} {d.Message}"); + sb.AppendLine(); +} +Output(sb.ToString()); +``` + +**Output** + +``` +[Error] no_underscores Model.Fields["repeat_customer"] + Do not include underscores in field names. Use user-friendly names with spaces. + +[Error] field_alias Model.Fields["repeat_customer"] + Field 'repeat_customer' reuses source expression 'source.customer_id', already used by field 'customer'. ``` ## References +- @semantic-bridge-metric-view-object-model +- @semantic-bridge-metric-view-fields-and-dimensions - @semantic-bridge-how-tos diff --git a/content/features/semantic-bridge.md b/content/features/semantic-bridge.md index 01cc47c1b..e7092b2ed 100644 --- a/content/features/semantic-bridge.md +++ b/content/features/semantic-bridge.md @@ -2,7 +2,7 @@ uid: semantic-bridge title: Semantic Bridge author: Greg Baldini -updated: 2026-04-17 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -24,7 +24,8 @@ SUMMARY: Overview of the Semantic Bridge feature - a multi-platform semantic mod --> > [!NOTE] -> The Semantic Bridge as released in 3.25.0 is in public preview. It has limitations as documented below, and the API and feature surface area are subject to change. +> The Semantic Bridge is in public preview. +> It has limitations as documented below, and the API and feature surface area are subject to change. The Semantic Bridge is a semantic model compiler, with the capability to translate the structure and expressions of a semantic model from one platform to another. This allows you to reuse business logic on multiple data platforms, supporting end users and meeting them where they consume the data. @@ -81,9 +82,16 @@ After either success type, you can use undo/redo functionality like normal in Ta If you click on **View Diagnostics**, you can see a list of messages describing the issues in translation. These diagnostics are available for review later by outputting them from a C# script: -```csharp +```csharp {compile} // Show all diagnostic messages from the last attempted import of a Metric View -SemanticBridge.MetricView.ImportDiagnostics.Output(); +var sb = new System.Text.StringBuilder(); +foreach (var d in SemanticBridge.MetricView.ImportDiagnostics) +{ + sb.AppendLine($"[{d.Severity}] {d.Code} {d.Path}"); + sb.AppendLine($" {d.Context} {d.Message}"); + sb.AppendLine(); +} +Output(sb.ToString()); ``` ![Import diagnostics](~/content/assets/images/features/semantic-bridge/semantic-bridge-import-diagnostics.png) @@ -94,86 +102,22 @@ SemanticBridge.MetricView.ImportDiagnostics.Output(); Viewing diagnostics for a failure is the same as for success with issues. -## Translation process - -Translating a Metric View to a Tabular model happens in several steps: - -1. Read the YAML from disk -2. Deserialize the YAML -3. Validate that the deserialized YAML represents a valid Metric View -4. If it is a valid Metric View, store it as a the currently loaded Metric View, similar to how there is a loaded Tabular model that you interact with. - If it is not a valid Metric View, the process stops here and messages are available. -4. Analyze the Metric View and attempt to transform it to an intermediate representation -5. Attempt to transform the intermediate representation to a Tabular model - -The import GUI described above handles all of this for you, but you can also use C# scripts to customize different steps of the process and operate on the Metric View programatically, similarly to how you are used to doing with a Tabular model. -Specifically, you can - -- load a Metric View from disk with [`SemanticBridge.MetricView.Load`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Load_System_String_): loading makes it available in C# scripts as [`SemanticBridge.MetricView.Model`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Model), but does not import the structure into the Tabular model -- deserialize a Metric view from a string with [`SemanticBridge.MetricView.Deserialize`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Deserialize_System_String_): similar to loading, the model is available as [`SemanticBridge.MetricView.Model`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Model), but is not imported -- save a Metric View to disk with [`SemanticBridge.MetricView.Save`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Save_System_String_) -- serialize a Metric View to a string with [`SemanticBridge.MetricView.Serialize`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Serialize). -- validate a Metric View using a system that is similar to the [Best Practice Analyzer](xref:best-practice-analyzer) with [`SemanticBridge.MetricView.Validate`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_Validate) - - you can create your own custom validation rules with [`SemanticBridge.MetricView.MakeValidationRule`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_MakeValidationRule__1_System_String_System_String_System_Func___0_TabularEditor_SemanticBridge_Platforms_Databricks_Validation_IReadOnlyValidationContext_System_Collections_Generic_IEnumerable_TabularEditor_SemanticBridge_Orchestration_DiagnosticMessage___) and its simpler versions -- import a Metric View to Tabular with [`SemanticBridge.MetricView.ImportToTabularFromFile`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_ImportToTabularFromFile_System_String_TabularEditor_TOMWrapper_Model_System_String_System_String_System_Collections_Generic_List_TabularEditor_SemanticBridge_Orchestration_DiagnosticMessage___System_Boolean_), which does the exact same as the GUI shown above, or [`SemanticBridge.MetricView.ImportToTabular`](/api/TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.html#TabularEditor_SemanticBridge_Platforms_Databricks_DatabricksMetricViewService_ImportToTabular_TabularEditor_TOMWrapper_Model_System_String_System_String_System_Collections_Generic_List_TabularEditor_SemanticBridge_Orchestration_DiagnosticMessage___System_Boolean_), which is similar, but operates on the currently loaded Metric View, rather than reading one from disk. - - -## Public Preview Limitations +## Limitations ### Supported platforms In the public preview, we support translations from a Databricks Metric View to a Tabular model. -Specifically we support the following surface area of a Databricks Metric View: - -- v0.1 Metric View properties: - - supported: - - `source`: the source of the fact table - - `joins`: collection of tables left-joined to the fact - - `dimensions`: flat collection of fields from any table, either the single fact or any of the many joins - - `measures`: flat collection of named aggregations representing business logic - - unsupported: - - `filter`: a SQL filter expression for the Metric View - -All v1.1 metadata is not supported in the public preview. -Any v1.1 metadata is silently ignored upon deserialization of a Metric View, so it will not be visible in a C# script and it will not affect the translation to Tabular in any way. - -> [!WARNING] -> Because the v1.1 metadata is silently ignored, a Metric View that you deserialize and then serialize will be missing this metadata. -> Be careful not to overwrite a v1.1 source YAML file from a C# script, as that will remove all v1.1 metadata. - -### Limitations on translation from SQL - -Metric Views provide a structured layer on top of SQL expressions, and so part of translating a Metric View is translating SQL to DAX and M in the Tabular model. - -- Metric View `joins` with nested `joins` are not supported. - In other words, only strict star schemas are supported for translation; snowflake models are not supported -- Metric View `joins` with `using` join criteria are not supported; only equijoins on a single key field using the `on` property are supported. -- Metric View `dimensions` with SQL expressions are not translated to M or DAX; they are presented as Tabular model calculated columns with their SQL expression commented out -- Metric View `measures` with non-basic aggregations are not translated to DAX; they are presented as a Tabular model measure with their SQL expression commented out - - The only aggregations supported are sum, max, min, average, count, and distinct count. - - SQL comments in a basic aggregation are not preserved in DAX - -> [!WARNING] -> Note that SQL and DAX are different languages with different semantics. -> We can make no guarantee that a translated measure will behave identically between the Metric View SQL and the Tabular DAX we generate. -> Basic aggregates defined on fact table fields should behave the same, whereas aggregates defined on fields in dimension tables are more likely to produce undesired results. ### Connectivity -The public preview does not connect to any platforms besides Tabular, but works entirely with local files. -You must create your Metric View YAML on your own and then put it where Tabular Editor can see it. - -### C# API - -The C# interface has been built to optimize for the automatic translation workflow. -As such, there are limited affordances for interacting with the currently loaded Metric View, and certain types of operations are clunky. +The public preview does not connect to any platforms besides Fabric, Power BI, and Analysis Services. +Working with models from other platforms, e.g., Databricks Metric Views, is based on local source files, such as a Metric View YAML definition. ## Appendix on nomenclature It can be confusing to discuss things when talking about the Semantic Bridge, as there are many words that have both generic and specific meanings, depending what level of abstraction we are talking about and which platform we are discussing. For example, the term "semantic model" is both generic, referring to the concept of a collection of data and business logic in some form suitable for supporting business reporting and analytical needs, and also the name Microsoft has adopted for referring to their specific implementation of this generic concept in Power BI and Fabric. Thus, a semantic model might generically refer to a Databricks Metric View, an OLAP / Multidimensional Cube, a Power BI semantic model, or a model hosted in another platform's semantic layer. -Similarly, "dimension" refers to a concept in dimensional modeling, but it is also the name of a specific type of object in a Metric View. Because of this, we have adopted the following definitions and standards in our documentation to maintain clarity and sanity. > [!NOTE] @@ -182,7 +126,7 @@ Because of this, we have adopted the following definitions and standards in our ### Definitions - *Semantic model*: when used on its own always refers to the generic concept of a collection of data, metadata, and business logic to support reporting and analytics. - If and only if it is immediately preceded by "Fabric" or "Power BI", then it is referring to that artifact type in that platform, specifically a Tabular model that is saved as TMDL or BIM and using M and DAX; we tend to prefer to use the term Tabular model to refer to the Power BI / Fabric semantic model to avoid this confusion where possible, because the Tabular model is shared across Power BI / Fabric as well as Analysis Serviced Tabular. + If and only if it is immediately preceded by "Fabric" or "Power BI", then it is referring to that artifact type in that platform, specifically a Tabular model that is saved as TMDL or BIM and using M and DAX; we tend to prefer to use the term Tabular model to refer to the Power BI / Fabric semantic model to avoid this confusion where possible, because the Tabular model is shared across Power BI / Fabric as well as Analysis Services Tabular. - *Platform*: a technology solution that has a semantic layer, on which a generic semantic model is hosted. Databricks Metric Views represent a platform; Fabric / Power BI represent a platform; Analysis Services Tabular is a platform; Analysis Services Multidimensional is a platform which we have no support for in the Semantic Bridge today. - *Serialization format*: a way to represent a semantic model on disk in a textual format. @@ -197,25 +141,15 @@ There are many terms that exist generally in discussion of a dimensional model o For example, the term "measure" refers generically to a quantitative value that is aggregated in a dimensional model to represent a business metric of interest, but it also refers to a specific kind of object in both Databricks Metric Views and Tabular models; in a Metric View, a measure is a named SQL expression that defines an aggregation in the Metric View, and in a Tabular model a measure is a named DAX expression that defines an aggregation in the Tabular model. It is impossible to discuss the work of the Semantic Bridge without talking about multiple meanings of such words at once. For example, we talk about translating a Metric View measure to a Tabular measure. -As such, **we always refer to an object in a specific platform's model by saying the platform and the object, e.g. "Metric View measure" or "Tabular measure"**. +As such, **we always refer to an object in a specific platform's model by saying the platform and the object, e.g., "Metric View measure" or "Tabular measure"; "Metric View field" or "TOM column".** If the term is ever used without being accompanied by a platform's name, then we are discussing the idea generically. -### Common terms across Metric Views and Tabular models - -For those of our users who may be unfamiliar with either Metric Views or Tabular models, we provide an incomplete rosetta stone below. -We refer to the names of Metric View objects based on their representation in YAML, and Tabular based on the name of the type of object in TMDL/TMSL. - -| General term | Name in Tabular | Name in Metric View | Description | Note | -|----------------------|-----------------|-----------------------|------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| fact | table | source | A table holding foreign keys to dimensions and quantitative values to be aggregated | a Metric View has a single fact which is unnamed and captured as the root-level `source` attribute in YAML. Tabular models do not differentiate between types of tables: whether a table is a fact can only be inferred | -| dimension | table | join | A table holding descriptive attributes and one primary key to which the fact is related | Tabular models do not differentiate, so the role of "dimension" is inferred only, as with a fact. | -| partition | partition | source (join only) | An object for data management, holding a subset of data in a table | Tabular model tables can have many partitions and must have at least one. The Metric View fact, as mentioned above is defined purely as a source, but Metric View joins also have a `source` property, which acts roughly like a partition | -| field | column | dimension | A column in a table | | -| measure | measure | measure | A quantitative value that is aggregated according to business logic in the model | Measures in a Tabular model are written in DAX, and in a Metric View in SQL | -| join or relationship | relationship | join.on or join.using | A correspondence between key fields in two tables, a foreign key in one and primary key in the other | Relationships are explicit objects in a Tabular model, and implicitly defined as a property of the `join` object in Metric View YAML | - ## Additional Resources -- [Databricks Metric View documentation](https://learn.microsoft.com/en-us/azure/databricks/metric-views/) -- [Databricks Metric View YAML reference](https://learn.microsoft.com/en-us/azure/databricks/metric-views/data-modeling/syntax) +- [Databricks Metric View documentation](https://learn.microsoft.com/en-us/azure/databricks/business-semantics/) +- [Databricks Metric View YAML reference](https://learn.microsoft.com/en-us/azure/databricks/business-semantics/metric-views/yaml-reference) +- @semantic-bridge-metric-view-tabular-translation +- @semantic-bridge-metric-view-object-model +- @semantic-bridge-metric-view-validation +- @semantic-bridge-metric-view-fields-and-dimensions - @semantic-bridge-how-tos diff --git a/content/features/toc.md b/content/features/toc.md index 8b46f7d82..1d6c6e7c9 100644 --- a/content/features/toc.md +++ b/content/features/toc.md @@ -76,7 +76,9 @@ ### [Known Limitations](xref:te-cli-limitations) ## @command-line-options -# Semantic Bridge for cross-platform translations +# [Semantic Bridge for cross-platform translations](xref:semantic-bridge) ## @semantic-bridge ## @semantic-bridge-metric-view-object-model +## @semantic-bridge-metric-view-tabular-translation ## @semantic-bridge-metric-view-validation +## @semantic-bridge-metric-view-fields-and-dimensions diff --git a/content/how-tos/includes/sample-metricview-deserialize.md b/content/how-tos/includes/sample-metricview-deserialize.md deleted file mode 100644 index 2aeb794a6..000000000 --- a/content/how-tos/includes/sample-metricview-deserialize.md +++ /dev/null @@ -1,43 +0,0 @@ -## Deserialize Metric View for these code samples - -This how-to uses a sample e-commerce Metric View representing sales data with three dimension tables (product, customer, date) joined to a fact table (orders). -Run the snippet below first, if you'd like to follow along with the code in the rest of this how-to - -```csharp -SemanticBridge.MetricView.Deserialize(""" - version: 0.1 - source: sales.fact.orders - joins: - - name: product - source: sales.dim.product - on: source.product_id = product.product_id - - name: customer - source: sales.dim.customer - on: source.customer_id = customer.customer_id - - name: date - source: sales.dim.date - on: source.order_date = date.date_key - dimensions: - - name: product_name - expr: product.product_name - - name: product_category - expr: product.category - - name: customer_segment - expr: customer.segment - - name: order_date - expr: date.full_date - - name: order_year - expr: date.year - - name: order_month - expr: date.month_name - measures: - - name: total_revenue - expr: SUM(revenue) - - name: order_count - expr: COUNT(order_id) - - name: avg_order_value - expr: AVG(revenue) - - name: unique_customers - expr: COUNT(DISTINCT customer_id) - """); -``` diff --git a/content/how-tos/includes/sample-metricview.md b/content/how-tos/includes/sample-metricview.md index ae47b0125..883819be7 100644 --- a/content/how-tos/includes/sample-metricview.md +++ b/content/how-tos/includes/sample-metricview.md @@ -1,38 +1,41 @@ +## Load the sample Metric View for these code samples + +Before starting, make sure you have Tabular Editor 3 open and have a Tabular model opened, or create a new model. + This how-to uses a sample e-commerce Metric View representing sales data with three dimension tables (product, customer, date) joined to a fact table (orders). +Use either method below to load it (either "download and load" or "copy and deserialize"), +then follow along with the rest of this how-to. +You can run either command in the same C# script as the rest of this example, +or you can run it first, in its own C# script, and the rest of the example in its own C# script. + + + +# [Download and load](#tab/load) + +[Download `sample-metricview.yaml`](https://raw.githubusercontent.com/TabularEditor/TabularEditorDocs/main/content/how-tos/includes/sample-metricview.yaml) +and load it by path: + +```csharp +SemanticBridge.MetricView.Load("C:/path/to/sample-metricview.yaml"); +``` + +# [Copy and deserialize](#tab/deserialize) + +Copy the definition below and pass it to `Deserialize` as a string: -```yaml -version: 0.1 -source: sales.fact.orders -joins: - - name: product - source: sales.dim.product - on: source.product_id = product.product_id - - name: customer - source: sales.dim.customer - on: source.customer_id = customer.customer_id - - name: date - source: sales.dim.date - on: source.order_date = date.date_key -dimensions: - - name: product_name - expr: product.product_name - - name: product_category - expr: product.category - - name: customer_segment - expr: customer.segment - - name: order_date - expr: date.full_date - - name: order_year - expr: date.year - - name: order_month - expr: date.month_name -measures: - - name: total_revenue - expr: SUM(revenue) - - name: order_count - expr: COUNT(order_id) - - name: avg_order_value - expr: AVG(revenue) - - name: unique_customers - expr: COUNT(DISTINCT customer_id) +```csharp +SemanticBridge.MetricView.Deserialize(""" + + """); ``` + +[!code-yaml[Sample Metric View](sample-metricview.yaml)] + + +*** diff --git a/content/how-tos/includes/sample-metricview.yaml b/content/how-tos/includes/sample-metricview.yaml index df457333b..628d2489e 100644 --- a/content/how-tos/includes/sample-metricview.yaml +++ b/content/how-tos/includes/sample-metricview.yaml @@ -1,20 +1,24 @@ -version: 0.1 +version: 1.1 source: sales.fact.orders joins: - name: product source: sales.dim.product on: source.product_id = product.product_id + cardinality: many_to_one - name: customer source: sales.dim.customer on: source.customer_id = customer.customer_id + cardinality: many_to_one - name: date source: sales.dim.date on: source.order_date = date.date_key -dimensions: + cardinality: many_to_one +fields: - name: product_name expr: product.product_name - name: product_category expr: product.category + display_name: 'Product Category' - name: customer_segment expr: customer.segment - name: order_date @@ -26,9 +30,26 @@ dimensions: measures: - name: total_revenue expr: SUM(revenue) + display_name: 'Total Revenue' + format: + type: currency + currency_code: USD + decimal_places: + type: exact + places: 2 + - name: gross_margin + expr: SUM(revenue) - SUM(cost) - name: order_count - expr: COUNT(order_id) + expr: COUNT(*) - name: avg_order_value expr: AVG(revenue) + - name: revenue_to_budget + expr: (SUM(revenue) - SUM(budget)) / SUM(budget) + display_name: 'Revenue vs Budget' + format: + type: percentage + decimal_places: + type: max + places: 1 - name: unique_customers expr: COUNT(DISTINCT customer_id) diff --git a/content/how-tos/semantic-bridge-add-object.md b/content/how-tos/semantic-bridge-add-object.md index 918d69d55..585328979 100644 --- a/content/how-tos/semantic-bridge-add-object.md +++ b/content/how-tos/semantic-bridge-add-object.md @@ -2,7 +2,7 @@ uid: semantic-bridge-add-object title: Add an Object to a Metric View author: Greg Baldini -updated: 2025-01-27 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -19,56 +19,93 @@ applies_to: --- # Add an object to a Metric View -This how-to demonstrates how to add a new Metric View dimension (field) to a loaded Metric View. +This how-to demonstrates adding new objects to a loaded Metric View and setting their properties. Similar patterns apply to all Metric View collections. -[!INCLUDE [deserialize](includes/sample-metricview-deserialize.md)] +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. -## Create a new Metric View Dimension object +[!INCLUDE [sample](includes/sample-metricview.md)] -Use the Metric View `Dimension` constructor to create a new Metric View dimension: +## Add a field -```csharp +Use [`AddField`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View.AddField%2A) to create and return a new `Field` you can manipulate. + +```csharp {run id=addfield setup=mv-sample after=none output=true} +var sb = new System.Text.StringBuilder(); +var view = SemanticBridge.MetricView.Model; + +sb.AppendLine($"Fields before adding: {view.Fields.Count}"); + +var field = view.AddField("customer_city", "customer.city"); + +sb.AppendLine($"Fields after adding: {view.Fields.Count}"); +Output(sb.ToString()); +``` + +**Output** + +``` +Fields before adding: 6 +Fields after adding: 7 +``` + +## Add and configure a `Join` + +[`AddJoin`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View.AddJoin%2A) +works similarly to `AddField`: it constructs the object, adds it to the Metric View, and returns it so you can set further properties. +Set the cardinality with the [`JoinCardinality`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.JoinCardinality) enum. + +```csharp {run id=addjoin setup=mv-sample after=none output=false} using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; -var newDimension = new MetricView.Dimension -{ - Name = "customer_city", - Expr = "customer.city" -}; +var view = SemanticBridge.MetricView.Model; + +// add a join, then set its remaining properties +var supplier = view.AddJoin("supplier", "sales.dim.supplier"); +supplier.On = "source.supplier_id = supplier.supplier_id"; +supplier.Cardinality = MetricView.JoinCardinality.ManyToOne; ``` -## Add to the Metric View +`AddJoin` is also a method on any existing `Join`. +You would use this to create nested joins, for example, `supplier.AddJoin("region", "sales.dim.region")`, +which models a snowflake dimension. -The Metric View `Dimensions` property is an `IList`, so you can use `Add()`: +## Add and configure a `Measure` -```csharp +[`AddMeasure`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View.AddMeasure%2A) works similarly to the other `Add` methods. + +Some properties, such as a field or measure `Format`, have their own types you need to construct to set the property. +Create the [`Format`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.Format) variant you want, such as `Format.Currency` or `Format.Percentage`, and assign it. + +```csharp {run id=addmeasure setup=mv-sample after=none output=true} using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; var sb = new System.Text.StringBuilder(); -sb.AppendLine($"Dimensions before adding: {SemanticBridge.MetricView.Model.Dimensions.Count}"); - -var newDimension = new MetricView.Dimension -{ - Name = "customer_city", - Expr = "customer.city" -}; +var view = SemanticBridge.MetricView.Model; -SemanticBridge.MetricView.Model.Dimensions.Add(newDimension); +// add a new measure, then give it a currency format +var totalCost = view.AddMeasure("total_cost", "SUM(cost)"); +totalCost.Format = new MetricView.Format.Currency { CurrencyCode = "USD" }; -sb.AppendLine($"Dimensions after adding: {SemanticBridge.MetricView.Model.Dimensions.Count}"); +// read the format back off the measure +sb.AppendLine($"{totalCost.Name} format: {totalCost.Format}"); Output(sb.ToString()); ``` **Output** ``` -Dimensions before adding: 8 -Dimensions after adding: 9 +total_cost format: Currency { Type = Currency, DecimalPlaces = , HideGroupSeparator = , Abbreviation = , CurrencyCode = USD } ``` +## Next steps + +- [Remove objects from a Metric View](xref:semantic-bridge-remove-object) +- [Rename a field](xref:semantic-bridge-rename-objects) +- [Serialize a Metric View to YAML](xref:semantic-bridge-serialize) + ## See also -- @semantic-bridge-remove-object -- @semantic-bridge-rename-objects -- @semantic-bridge-serialize +- [Metric View Object Model](xref:semantic-bridge-metric-view-object-model) diff --git a/content/how-tos/semantic-bridge-how-tos.md b/content/how-tos/semantic-bridge-how-tos.md index d6e0288e0..dddea8d2f 100644 --- a/content/how-tos/semantic-bridge-how-tos.md +++ b/content/how-tos/semantic-bridge-how-tos.md @@ -2,7 +2,7 @@ uid: semantic-bridge-how-tos title: Semantic Bridge How-Tos author: Greg Baldini -updated: 2025-01-27 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -23,21 +23,26 @@ These how-tos are focused on interacting with the [Databricks Metric View object ## Getting Started -- @semantic-bridge-load-inspect - Load a Metric View and explore its structure -- @semantic-bridge-import - Import a Metric View to Tabular and view diagnostics +- @semantic-bridge-load-inspect: Load a Metric View and explore its structure +- @semantic-bridge-metric-view-import-from-file: Import a Metric View directly from a YAML file +- @semantic-bridge-import: Import a loaded Metric View to Tabular and view diagnostics ## Validation -- @semantic-bridge-validate-default - Validate with built-in rules -- @semantic-bridge-validate-simple-rules - Create predicate-based validation rules for naming conventions -- @semantic-bridge-validate-contextual-rules - Create rules with cross-object checks like duplicate detection +- @semantic-bridge-validate-default: Validate with built-in rules +- @semantic-bridge-validate-simple-rules: Create predicate-based validation rules for naming conventions +- @semantic-bridge-validate-contextual-rules: Create rules with cross-object checks, such as names reused across object types ## Manipulating the Object Model -- @semantic-bridge-add-object - Add a new object to a Metric View -- @semantic-bridge-remove-object - Remove objects from a Metric View -- @semantic-bridge-rename-objects - Rename objects using copy-modify pattern +- @semantic-bridge-add-object: Add objects to a Metric View and set their properties +- @semantic-bridge-remove-object: Remove objects from a Metric View +- @semantic-bridge-rename-objects: Rename a field in a Metric View ## Serialization -- @semantic-bridge-serialize - Serialize a Metric View back to YAML +- @semantic-bridge-serialize: Serialize a Metric View back to YAML + +## Troubleshooting + +- @semantic-bridge-metric-view-handle-failures: Handle invalid input, missing files, and imports that don't complete diff --git a/content/how-tos/semantic-bridge-import.md b/content/how-tos/semantic-bridge-import.md index 098f00110..89dd79ecc 100644 --- a/content/how-tos/semantic-bridge-import.md +++ b/content/how-tos/semantic-bridge-import.md @@ -2,7 +2,7 @@ uid: semantic-bridge-import title: Import a Metric View and View Diagnostics author: Greg Baldini -updated: 2025-01-27 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -17,39 +17,29 @@ applies_to: - edition: Enterprise full: true --- -# Import a Metric View and View Diagnostics +# Import a Metric View and view diagnostics -This how-to demonstrates how to import a Metric View into a Tabular model using C# scripts, and how to view diagnostic messages from the import process. +This how-to demonstrates importing a loaded Metric View into a Tabular model with a C# script, and reviewing the diagnostic messages the import produces. -## Prerequisites +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. -You must have a Tabular model open in Tabular Editor before importing. This can be: +[!INCLUDE [sample](includes/sample-metricview.md)] -- A new, empty model -- An existing model you want to enhance with objects from the Metric View +> [!NOTE] +> Each example below imports into the open Tabular model. +> To run more than one, we recommend that you undo the import after each example (Edit>Undo in the menu, or CTRL-z in the TOM Explorer). +> If you run each import one after the other, you will get multiple translated copies of the Metric View. -## Import methods +## Import the loaded Metric View -There are two import methods: +`ImportToTabular` translates the currently loaded Metric View into the open Tabular model. +The Databricks hostname and HTTP path are used when we build the M partition expressions; +for a quick test you can pass placeholder values and fix them before refreshing data. -| Method | Description | -|---------------------------|------------------------------------------------| -| `ImportToTabularFromFile` | Loads from a file path and imports in one step | -| `ImportToTabular` | Imports the currently loaded Metric View | - -Both methods require: - -- The target Tabular `Model` -- Databricks hostname (for M partition expressions) -- Databricks HTTP path (for M partition expressions) - -## Import from file - -Use `ImportToTabularFromFile` to load and import in one operation: - -```csharp -var success = SemanticBridge.MetricView.ImportToTabularFromFile( - "C:/MetricViews/sales-metrics.yaml", +```csharp {run id=import setup=mv-sample after=none output=true} +var success = SemanticBridge.MetricView.ImportToTabular( Model, "your-workspace.azuredatabricks.net", "/sql/1.0/warehouses/abc123def456", @@ -57,159 +47,115 @@ var success = SemanticBridge.MetricView.ImportToTabularFromFile( ); var sb = new System.Text.StringBuilder(); -if (success) -{ - sb.AppendLine("Import successful!"); - sb.AppendLine($"Diagnostics: {diagnostics.Count}"); -} -else +sb.AppendLine($"Imported {Model.AllColumns.Count()} fields and {Model.AllMeasures.Count()} measures."); +sb.AppendLine(success ? "Import successful." : "Import failed."); +sb.AppendLine($"Diagnostics: {diagnostics.Count}"); +foreach (var diag in diagnostics) { - sb.AppendLine("Import failed."); - sb.AppendLine($"Errors: {diagnostics.Count}"); + sb.AppendLine($" [{diag.Severity}] {diag.Code}: {diag.Message}"); } - Output(sb.ToString()); ``` -## Import a loaded Metric View - -If you've already loaded a Metric View (for inspection or modification), use `ImportToTabular`: - -```csharp -// Load the Metric View first -SemanticBridge.MetricView.Load("C:/MetricViews/sales-metrics.yaml"); - -// Optionally inspect or modify it -var view = SemanticBridge.MetricView.Model; - -var sb = new System.Text.StringBuilder(); -sb.AppendLine($"Importing Metric View with {view.Dimensions.Count} dimensions and {view.Measures.Count} measures"); - -// Import to Tabular -var success = SemanticBridge.MetricView.ImportToTabular( - Model, - "your-workspace.azuredatabricks.net", - "/sql/1.0/warehouses/abc123def456", - out var diagnostics -); +**Output:** -if (success) -{ - sb.AppendLine("Import successful!"); -} -else -{ - sb.AppendLine("Import failed."); -} - -Output(sb.ToString()); ``` - -## Using placeholder connection values - -If you're testing the translation without a real Databricks connection, you can use placeholder values: - -```csharp -var success = SemanticBridge.MetricView.ImportToTabularFromFile( - "C:/MetricViews/sales-metrics.yaml", - Model, - "placeholder-host", - "placeholder-path", - out var diagnostics -); - -var sb = new System.Text.StringBuilder(); -sb.AppendLine("Import complete (with placeholder connection values)"); -sb.AppendLine("Note: Update the M partition expressions before refreshing data."); -Output(sb.ToString()); +Imported 15 fields and 6 measures. +Import successful. +Diagnostics: 0 ``` -## View diagnostics after import +Note that the number of fields imported includes join keys and implicit column references from the Metric View definition, +so it is larger than the number of explicit `Fields` in the Metric View definition. -You can access diagnostics from the last import at any time using `ImportDiagnostics`. -This example assumes that you have previously run an import, either via GUI or C# script. +## Review the last import's diagnostics -```csharp -var diagnostics = SemanticBridge.MetricView.ImportDiagnostics; +The diagnostics from the most recent import are available at any time through `ImportDiagnostics`, including after an import done through the GUI. -var sb = new System.Text.StringBuilder(); -sb.AppendLine("LAST IMPORT DIAGNOSTICS"); -sb.AppendLine("-----------------------"); -sb.AppendLine(""); -sb.AppendLine($"Total issues: {diagnostics.Count}"); -sb.AppendLine(""); - -foreach (var diag in diagnostics) -{ - sb.AppendLine($"[{diag.Severity}] {diag.Message}"); -} - -Output(sb.ToString()); +```csharp {compile} +foreach (var d in SemanticBridge.MetricView.ImportDiagnostics) + Output($"[{d.Severity}] {d.Code}: {d.Message}"); ``` -## Output diagnostics directly - -For quick inspection, you can output the diagnostics collection directly: - -```csharp -// Output all diagnostics from the last import -SemanticBridge.MetricView.ImportDiagnostics.Output(); -``` +## See a translation diagnostic -## Complete workflow example +Some Metric View constructs cannot be translated to Tabular. +A window measure, for example, is not translated to DAX: +the import creates a placeholder TOM measure with the original Metric View definition in a comment +and reports a diagnostic warning to you. -Load, validate, and import with full diagnostic reporting: +Add a window spec to a measure, then import to see the diagnostic: -```csharp -var sb = new System.Text.StringBuilder(); +```csharp {run id=window-diagnostic setup=mv-sample after=none output=true} +using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; -// Load the Metric View -SemanticBridge.MetricView.Load("C:/MetricViews/sales-metrics.yaml"); var view = SemanticBridge.MetricView.Model; -sb.AppendLine("METRIC VIEW SUMMARY"); -sb.AppendLine("-------------------"); -sb.AppendLine($"Source: {view.Source}"); -sb.AppendLine($"Joins: {view.Joins?.Count ?? 0}"); -sb.AppendLine($"Dimensions: {view.Dimensions.Count}"); -sb.AppendLine($"Measures: {view.Measures.Count}"); -sb.AppendLine(""); - -// Validate first -var validationDiags = SemanticBridge.MetricView.Validate().ToList(); -sb.AppendLine("VALIDATION"); -sb.AppendLine("----------"); -sb.AppendLine($"Issues: {validationDiags.Count}"); -sb.AppendLine(""); - -// Import +// add a window spec +view.Measures["total_revenue"].Window = +[ + new MetricView.Window + { + Order = "order_date", + Range = "trailing 3 month", + Semiadditive = MetricView.Window.SemiadditiveType.Last + } +]; + var success = SemanticBridge.MetricView.ImportToTabular( Model, "your-workspace.azuredatabricks.net", "/sql/1.0/warehouses/abc123def456", - out var importDiags + out var diagnostics ); -sb.AppendLine("IMPORT RESULT"); -sb.AppendLine("-------------"); -sb.AppendLine($"Success: {success}"); -sb.AppendLine($"Diagnostics: {importDiags.Count}"); -sb.AppendLine(""); - -if (importDiags.Count > 0) +var sb = new System.Text.StringBuilder(); +sb.AppendLine(success ? "Import succeeded with issues." : "Import failed."); +foreach (var diag in diagnostics) { - sb.AppendLine("Import issues:"); - foreach (var diag in importDiags) - { - sb.AppendLine($" [{diag.Severity}] {diag.Message}"); - } + sb.AppendLine($" [{diag.Severity}] {diag.Code}: {diag.Message}"); } - +// note that we search for the DisplayName, as that is what is translated to TOM +sb.AppendLine($"TOM measure expression: {Model.AllMeasures.First(m => m.Name == "Total Revenue").Expression}"); Output(sb.ToString()); ``` +**Output:** + +``` +Import succeeded with issues. + [Warning] MEASURE_WINDOW_UNSUPPORTED: Measure 'Total Revenue' uses a window specification that is not currently supported; it has been left inert with the original definition preserved as a comment. +TOM measure expression: // This measure uses a window specification (windowed / cumulative / semiadditive), +// which is not currently supported when importing Databricks Metric Views. +// The measure has been left blank - review the details below and author the DAX +// manually. The translated DAX does NOT account for the window spec; you will most +// likely need to wrap it in CALCULATE (or similar) to apply the windowing. +// +// Original source expression (Databricks SQL): +/* +SUM(revenue) +*/ +// +// Suggested DAX translation (window spec NOT applied): +/* +SUM('Fact'[revenue]) +*/ +// +// Window specification: +/* +- order: order_date + range: trailing 3 month + semiadditive: last + +*/ +``` + +## Next steps + +- [Import a Metric View from a file](xref:semantic-bridge-metric-view-import-from-file) +- [Load and inspect a Metric View](xref:semantic-bridge-load-inspect) +- [Validate a Metric View](xref:semantic-bridge-validate-default) + ## See also - [Semantic Bridge Overview](xref:semantic-bridge) -- [Validate a Metric View](xref:semantic-bridge-validate-default) -- [Load and Inspect a Metric View](xref:semantic-bridge-load-inspect) diff --git a/content/how-tos/semantic-bridge-load-inspect.md b/content/how-tos/semantic-bridge-load-inspect.md index a29eeb573..3be7731ce 100644 --- a/content/how-tos/semantic-bridge-load-inspect.md +++ b/content/how-tos/semantic-bridge-load-inspect.md @@ -2,7 +2,7 @@ uid: semantic-bridge-load-inspect title: Load and Inspect a Metric View author: Greg Baldini -updated: 2025-01-27 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -22,30 +22,18 @@ applies_to: This how-to demonstrates how to load a Databricks Metric View into Tabular Editor and explore its structure using C# scripts. This is the foundational skill for all other Metric View operations. -## Sample Metric View +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. [!INCLUDE [Sample Metric View](includes/sample-metricview.md)] -## Load a Metric View from a file - -Use `SemanticBridge.MetricView.Load` to load a Metric View from a YAML file on disk. - -```csharp -// Load from a file path -SemanticBridge.MetricView.Load("C:/MetricViews/sales-metrics.yaml"); - -// Confirm it loaded -Output($"Loaded Metric View version: {SemanticBridge.MetricView.Model.Version}"); -``` - -[!INCLUDE [deserialize](includes/sample-metricview-deserialize.md)] - ## Access the loaded Metric View After loading, the Metric View is available in any script as `SemanticBridge.MetricView.Model`. This returns a Metric View [`View`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View) object, the root of the [Metric View object graph](xref:semantic-bridge-metric-view-object-model). -```csharp +```csharp {run id=basic setup=mv-sample after=none output=true} var sb = new System.Text.StringBuilder(); var view = SemanticBridge.MetricView.Model; @@ -54,22 +42,30 @@ sb.AppendLine($"Source (fact table): {view.Source}"); Output(sb.ToString()); ``` +**Output** + +``` +Version: 1.1 +Source (fact table): sales.fact.orders +``` + ## Inspect Metric View joins (dimension tables) The Metric View `Joins` property contains the dimension tables joined to the fact. -```csharp +```csharp {run id=joins setup=mv-sample after=none output=true} var sb = new System.Text.StringBuilder(); var view = SemanticBridge.MetricView.Model; -sb.AppendLine($"Number of joins: {view.Joins?.Count ?? 0}"); +sb.AppendLine($"Number of joins: {view.Joins.Count}"); sb.AppendLine(""); -foreach (var join in view.Joins ?? []) +foreach (var join in view.Joins) { sb.AppendLine($"Join: {join.Name}"); sb.AppendLine($" Source: {join.Source}"); sb.AppendLine($" On: {join.On}"); + sb.AppendLine($" Cardinality: {join.Cardinality?.ToString() ?? "ManyToOne (default)"}"); sb.AppendLine(""); } @@ -83,31 +79,34 @@ Number of joins: 3 Join: product Source: sales.dim.product - On: product_id = product.product_id + On: source.product_id = product.product_id + Cardinality: ManyToOne Join: customer Source: sales.dim.customer - On: customer_id = customer.customer_id + On: source.customer_id = customer.customer_id + Cardinality: ManyToOne Join: date Source: sales.dim.date - On: order_date = date.date_key + On: source.order_date = date.date_key + Cardinality: ManyToOne ``` -## Inspect Metric View dimensions (fields) +## Inspect Metric View fields -The Metric View `Dimensions` property contains all field definitions. +The Metric View `Fields` property contains all field definitions. -```csharp +```csharp {run id=fields setup=mv-sample after=none output=true} var sb = new System.Text.StringBuilder(); var view = SemanticBridge.MetricView.Model; -sb.AppendLine($"Number of dimensions: {view.Dimensions?.Count ?? 0}"); +sb.AppendLine($"Number of fields: {view.Fields.Count}"); sb.AppendLine(""); -foreach (var dim in view.Dimensions ?? []) +foreach (var field in view.Fields) { - sb.AppendLine($"{dim.Name,-20} <- {dim.Expr}"); + sb.AppendLine($"{field.Name,-20} <- {field.Expr}"); } Output(sb.ToString()); @@ -116,7 +115,7 @@ Output(sb.ToString()); **Output:** ``` -Number of dimensions: 6 +Number of fields: 6 product_name <- product.product_name product_category <- product.category @@ -130,14 +129,14 @@ order_month <- date.month_name The Metric View `Measures` property contains all Metric View measure definitions with their aggregation expressions. -```csharp +```csharp {run id=measures setup=mv-sample after=none output=true} var sb = new System.Text.StringBuilder(); var view = SemanticBridge.MetricView.Model; -sb.AppendLine($"Number of measures: {view.Measures?.Count ?? 0}"); +sb.AppendLine($"Number of measures: {view.Measures.Count}"); sb.AppendLine(""); -foreach (var measure in view.Measures ?? []) +foreach (var measure in view.Measures) { sb.AppendLine($"{measure.Name,-20} = {measure.Expr}"); } @@ -148,11 +147,13 @@ Output(sb.ToString()); **Output:** ``` -Number of measures: 4 +Number of measures: 6 total_revenue = SUM(revenue) -order_count = COUNT(order_id) +gross_margin = SUM(revenue) - SUM(cost) +order_count = COUNT(*) avg_order_value = AVG(revenue) +revenue_to_budget = (SUM(revenue) - SUM(budget)) / SUM(budget) unique_customers = COUNT(DISTINCT customer_id) ``` @@ -160,7 +161,7 @@ unique_customers = COUNT(DISTINCT customer_id) Here is a complete script that outputs a formatted summary of the entire Metric View. -```csharp +```csharp {run id=summary setup=mv-sample after=none output=true} var sb = new System.Text.StringBuilder(); var view = SemanticBridge.MetricView.Model; @@ -172,27 +173,27 @@ sb.AppendLine($"Fact Source: {view.Source}"); sb.AppendLine(""); // Joins -sb.AppendLine($"JOINS ({view.Joins?.Count ?? 0})"); +sb.AppendLine($"JOINS ({view.Joins.Count})"); sb.AppendLine("---------"); -foreach (var join in view.Joins ?? []) +foreach (var join in view.Joins) { sb.AppendLine($" {join.Name,-15} -> {join.Source}"); } sb.AppendLine(""); -// Dimensions -sb.AppendLine($"DIMENSIONS ({view.Dimensions?.Count ?? 0})"); +// Fields +sb.AppendLine($"FIELDS ({view.Fields.Count})"); sb.AppendLine("--------------"); -foreach (var dim in view.Dimensions ?? []) +foreach (var field in view.Fields) { - sb.AppendLine($" {dim.Name,-20} <- {dim.Expr}"); + sb.AppendLine($" {field.Name,-20} <- {field.Expr}"); } sb.AppendLine(""); // Measures -sb.AppendLine($"MEASURES ({view.Measures?.Count ?? 0})"); +sb.AppendLine($"MEASURES ({view.Measures.Count})"); sb.AppendLine("------------"); -foreach (var measure in view.Measures ?? []) +foreach (var measure in view.Measures) { sb.AppendLine($" {measure.Name,-20} = {measure.Expr}"); } @@ -200,12 +201,49 @@ foreach (var measure in view.Measures ?? []) Output(sb.ToString()); ``` +**Output** + +``` +METRIC VIEW SUMMARY +=================== + +Version: 1.1 +Fact Source: sales.fact.orders + +JOINS (3) +--------- + product -> sales.dim.product + customer -> sales.dim.customer + date -> sales.dim.date + +FIELDS (6) +-------------- + product_name <- product.product_name + product_category <- product.category + customer_segment <- customer.segment + order_date <- date.full_date + order_year <- date.year + order_month <- date.month_name + +MEASURES (6) +------------ + total_revenue = SUM(revenue) + gross_margin = SUM(revenue) - SUM(cost) + order_count = COUNT(*) + avg_order_value = AVG(revenue) + revenue_to_budget = (SUM(revenue) - SUM(budget)) / SUM(budget) + unique_customers = COUNT(DISTINCT customer_id) +``` + ## Next steps Now that you can load and inspect a Metric View, you can: -- [Validate the Metric View](xref:semantic-bridge-metric-view-validation) to check for issues -- [Import the Metric View to Tabular](xref:semantic-bridge) to create tables, columns, and measures +- [Add objects to a Metric View](xref:semantic-bridge-add-object) +- [Remove objects from a Metric View](xref:semantic-bridge-remove-object) +- [Rename a field](xref:semantic-bridge-rename-objects) +- [Validate the Metric View](xref:semantic-bridge-validate-default) +- [Import the Metric View to Tabular](xref:semantic-bridge-import) ## See also diff --git a/content/how-tos/semantic-bridge-metric-view-handle-failures.md b/content/how-tos/semantic-bridge-metric-view-handle-failures.md new file mode 100644 index 000000000..b2b09c891 --- /dev/null +++ b/content/how-tos/semantic-bridge-metric-view-handle-failures.md @@ -0,0 +1,167 @@ +--- +uid: semantic-bridge-metric-view-handle-failures +title: Handle Common Failures +author: Greg Baldini +updated: 2026-07-02 +applies_to: + products: + - product: Tabular Editor 2 + none: true + - product: Tabular Editor 3 + since: 3.25.0 + editions: + - edition: Desktop + none: true + - edition: Business + none: true + - edition: Enterprise + full: true +--- +# Handle common failures + +This how-to shows how to handle several common failure modes when working with Metric Views in C# scripts: +invalid YAML, missing files, operations with no loaded metric view, and an import that does not complete. + +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. + +## A failed load or deserialize + +`Load` and `Deserialize` throw `System.IO.InvalidDataException` when the input does not represent valid Metric View YAML. +The exception itself only signals that loading failed; +the specific reasons are captured in `ImportDiagnostics`. +On failure, the current Metric View (`SemanticBridge.MetricView.Model`) is set to `null`. + +```csharp {run id=failed-deserialize setup=none after=none output=true} +try +{ + // This Metric View is missing the required `source`, so it fails to deserialize. + SemanticBridge.MetricView.Deserialize(""" + version: 1.1 + fields: + - name: revenue + expr: source.revenue + """); +} +catch (System.IO.InvalidDataException) +{ + var sb = new System.Text.StringBuilder(); + sb.AppendLine("Could not load the Metric View:"); + foreach (var diag in SemanticBridge.MetricView.ImportDiagnostics) + { + sb.AppendLine($" [{diag.Severity}] {diag.Code}: {diag.Message}"); + } + Output(sb.ToString()); +} +``` + +**Output** + +``` +Could not load the Metric View: + [Error] VIEW_SOURCE_REQUIRED: View source cannot be empty +``` + +> [!NOTE] +> `Load` reads from a file path, so a path that does not exist throws `System.IO.FileNotFoundException` instead of `InvalidDataException`. +> Catch that (or a broader `System.Exception`) when loading by path. + +## Guard against no loaded Metric View + +`Validate`, `Serialize`, `Save`, and `ImportToTabular` throw `System.InvalidOperationException` if no Metric View is loaded. +`Model` is `null` when nothing is loaded, so guard against it. + +Run this script in a fresh Tabular Editor 3 instance to ensure you have no loaded Metric View: + +```csharp {run id=guard-no-model setup=none after=none output=true} +if (SemanticBridge.MetricView.Model == null) +{ + Output("No Metric View is loaded. Load or deserialize one first."); +} +else +{ + var diagnostics = SemanticBridge.MetricView.Validate(); + Output($"Found {diagnostics.Count()} issue(s)."); +} +``` + +**Output** + +``` +No Metric View is loaded. Load or deserialize one first. +``` + +Without the guard, calling `SemanticBridge.MetricView.Validate()` with nothing loaded throws `InvalidOperationException`. + +## An import that does not complete + +`ImportToTabular` and `ImportToTabularFromFile` return `false` when the import cannot complete, rather than throwing an exception. +Check the return value and read the `out` diagnostics to see why. + +The example below deserializes a valid Metric View, +then edits a field's expression to be blank, which yields a validation error. +Because `failOnValidationErrors` defaults to `true`, +the import stops before translating and returns `false`, +with the reasons in the `out` diagnostics. +A Tabular model must be open. + +```csharp {run id=import-incomplete setup=none after=none output=true} +// Load a valid Metric View, then make it invalid +SemanticBridge.MetricView.Deserialize(""" + version: 1.1 + source: sales.fact.orders + fields: + - name: order_year + expr: source.order_year + measures: + - name: total_revenue + expr: SUM(source.revenue) + """); + +var view = SemanticBridge.MetricView.Model; +view.Fields["order_year"].Expr = ""; // an empty expression is invalid + +var success = SemanticBridge.MetricView.ImportToTabular( + Model, + "your-workspace.azuredatabricks.net", + "/sql/1.0/warehouses/abc123def456", + out var diagnostics +); + +var sb = new System.Text.StringBuilder(); +if (success) +{ + sb.AppendLine("Import complete."); +} +else +{ + sb.AppendLine("Import did not complete:"); + foreach (var diag in diagnostics) + { + sb.AppendLine($" [{diag.Severity}] {diag.Code}: {diag.Message}"); + } +} +Output(sb.ToString()); +``` + +**Output** + +``` +Import did not complete: + [Error] FIELD_EXPR_REQUIRED: Field 'order_year' expr cannot be empty +``` + +Pass `failOnValidationErrors: false` at your own risk if you'd like to import despite validation issues. +If no Metric View is loaded when you call this, +it throws `InvalidOperationException` as described above. + +## Next steps + +- [Load and inspect a Metric View](xref:semantic-bridge-load-inspect) +- [Validate a Metric View](xref:semantic-bridge-validate-default) +- [Import a Metric View and view diagnostics](xref:semantic-bridge-import) + +## See also + +- [Semantic Bridge Overview](xref:semantic-bridge) diff --git a/content/how-tos/semantic-bridge-metric-view-import-from-file.md b/content/how-tos/semantic-bridge-metric-view-import-from-file.md new file mode 100644 index 000000000..959f065d2 --- /dev/null +++ b/content/how-tos/semantic-bridge-metric-view-import-from-file.md @@ -0,0 +1,64 @@ +--- +uid: semantic-bridge-metric-view-import-from-file +title: Import a Metric View from a File +author: Greg Baldini +updated: 2026-07-02 +applies_to: + products: + - product: Tabular Editor 2 + none: true + - product: Tabular Editor 3 + since: 3.25.0 + editions: + - edition: Desktop + none: true + - edition: Business + none: true + - edition: Enterprise + full: true +--- +# Import a Metric View from a file + +This how-to demonstrates importing a Metric View into a Tabular model directly from a YAML file. + +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. + +## Get the sample Metric View + +Save the sample Metric View (below) to a local file. +You will need to replace the placeholder in the example below with this path. +For this how-to, specifically, you only need to save the file; you do not need to run `Load` or `Deserialize`. + +[!INCLUDE [sample](includes/sample-metricview.md)] + +## Import from file + +`ImportToTabularFromFile` loads the YAML from disk and imports it into the open model in one step. +Update the placeholder in the script below (``) with the path where you saved the YAML. +The Databricks hostname and HTTP path are used to build the M partition expressions; for a quick test you can pass placeholder values and fix them before refreshing data. + +```csharp {compile} +var success = SemanticBridge.MetricView.ImportToTabularFromFile( + "", + Model, + "your-workspace.azuredatabricks.net", + "/sql/1.0/warehouses/abc123def456", + out var diagnostics +); + +var sb = new System.Text.StringBuilder(); +sb.AppendLine(success ? "Import successful!" : "Import failed."); +sb.AppendLine($"Diagnostics: {diagnostics.Count}"); +Output(sb.ToString()); +``` + +## Next steps + +- [Load and inspect a Metric View](xref:semantic-bridge-load-inspect) +- [Import a Metric View and view diagnostics](xref:semantic-bridge-import) + +## See also + +- [Semantic Bridge Overview](xref:semantic-bridge) diff --git a/content/how-tos/semantic-bridge-remove-object.md b/content/how-tos/semantic-bridge-remove-object.md index e61177d53..1b936b008 100644 --- a/content/how-tos/semantic-bridge-remove-object.md +++ b/content/how-tos/semantic-bridge-remove-object.md @@ -2,7 +2,7 @@ uid: semantic-bridge-remove-object title: Remove an Object from a Metric View author: Greg Baldini -updated: 2025-01-27 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -19,10 +19,14 @@ applies_to: --- # Remove an object from a Metric View -This how-to demonstrates how to remove Metric View dimensions from a loaded Metric View. +This how-to demonstrates removing Metric View fields and measures. Similar approaches apply to all collections in a Metric View. -[!INCLUDE [deserialize](includes/sample-metricview-deserialize.md)] +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. + +[!INCLUDE [sample](includes/sample-metricview.md)] > [!NOTE] > Each removal script here affects the currently loaded Metric View. @@ -30,68 +34,88 @@ Similar approaches apply to all collections in a Metric View. ## Remove by name -Find the Metric View dimension and remove it from the collection: +Get the Metric View field and delete it. +After you delete an object, you should not attempt to modify it. +You can still read properties off of the deleted object. +It is safe to call `Delete()` on an object multiple times; after the first, these are no-ops. -```csharp +```csharp {run id=removefield setup=mv-sample after=none output=true} var view = SemanticBridge.MetricView.Model; var sb = new System.Text.StringBuilder(); -sb.AppendLine($"Dimensions before: {view.Dimensions.Count}"); +sb.AppendLine($"Fields before: {view.Fields.Count}"); -var dimToRemove = view.Dimensions.FirstOrDefault(d => d.Name == "order_month"); -if (dimToRemove != null) -{ - view.Dimensions.Remove(dimToRemove); - sb.AppendLine($"Removed: {dimToRemove.Name}"); -} +var fieldToRemove = view.Fields["order_month"]; +fieldToRemove.Delete(); +fieldToRemove.Delete(); // note we can call Delete twice safely +sb.AppendLine($"Removed: {fieldToRemove.Name}"); -sb.AppendLine($"Dimensions after: {view.Dimensions.Count}"); +sb.AppendLine($"Fields after: {view.Fields.Count}"); Output(sb.ToString()); ``` **Output:** ``` -Dimensions before: 6 +Fields before: 6 Removed: order_month -Dimensions after: 5 +Fields after: 5 +``` + +Observe that there are multiple calls to `Delete()` but only one removal. + +## Remove a measure + +Measures are removed the same way: get a reference to the measure and delete it. + +```csharp {run id=removemeasure setup=mv-sample after=none output=true} +var view = SemanticBridge.MetricView.Model; + +var sb = new System.Text.StringBuilder(); +sb.AppendLine($"Measures before: {view.Measures.Count}"); + +var measureToRemove = view.Measures["gross_margin"]; +measureToRemove.Delete(); +sb.AppendLine($"Removed: {measureToRemove.Name}"); + +sb.AppendLine($"Measures after: {view.Measures.Count}"); +Output(sb.ToString()); ``` -Observe that if you run the script above twice in a row, there is no additional removal; the before and after counts are both 5. +**Output:** -## Remove multiple Metric View dimensions +``` +Measures before: 6 +Removed: gross_margin +Measures after: 5 +``` -Use LINQ to filter and rebuild the collection: +## Remove multiple Metric View fields -```csharp -using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; +Filter to the fields you want to remove, snapshot them with `ToList`, then delete each one. +Snapshotting first avoids modifying the collection while iterating it. +```csharp {run id=removemultiple setup=mv-sample after=none output=true} var view = SemanticBridge.MetricView.Model; var sb = new System.Text.StringBuilder(); -sb.AppendLine($"Dimensions before: {view.Dimensions.Count}"); +sb.AppendLine($"Fields before: {view.Fields.Count}"); -// Remove all date-related dimensions +// Remove all date-related fields string[] toRemove = ["order_date", "order_year", "order_month"]; -var toKeep = view.Dimensions - .Where(d => !toRemove.Contains(d.Name)) - .ToList(); - -// Clear and repopulate -view.Dimensions.Clear(); -foreach (var dim in toKeep) +foreach (var field in view.Fields.Where(f => toRemove.Contains(f.Name)).ToList()) { - view.Dimensions.Add(dim); + field.Delete(); } -sb.AppendLine($"Dimensions after: {view.Dimensions.Count}"); +sb.AppendLine($"Fields after: {view.Fields.Count}"); sb.AppendLine(); -sb.AppendLine("Remaining dimensions:"); -sb.AppendLine("---------------------"); -foreach (var dim in view.Dimensions) +sb.AppendLine("Remaining fields:"); +sb.AppendLine("-----------------"); +foreach (var field in view.Fields) { - sb.AppendLine($" {dim.Name}"); + sb.AppendLine($" {field.Name}"); } Output(sb.ToString()); @@ -100,57 +124,57 @@ Output(sb.ToString()); **Output:** ``` -Dimensions before: 6 -Dimensions after: 3 +Fields before: 6 +Fields after: 3 -Remaining dimensions: ---------------------- +Remaining fields: +----------------- product_name product_category customer_segment ``` -## Remove Metric View dimensions from a specific table +## Remove Metric View fields from a specific table -Remove all Metric View dimensions that reference the date table. +Remove all Metric View fields that reference the date table. > [!WARNING] -> This example is not guaranteed to remove all and exclusively Metric View dimensions which reference a given Metric View Join. -> Metric View Dimensions may include near-arbitrary SQL expressions, and may also reference previously defined Metric View Dimensions. +> This example is not guaranteed to remove all and exclusively Metric View fields which reference a given Metric View Join. +> Metric View fields may include near-arbitrary SQL expressions, and may also reference previously defined Metric View fields. > This example is for illustrative purposes only. -```csharp +```csharp {run id=remove-by-table setup=mv-sample after=none output=true} var view = SemanticBridge.MetricView.Model; var sb = new System.Text.StringBuilder(); -sb.AppendLine($"Dimensions before: {view.Dimensions.Count}"); - -var toRemove = view.Dimensions - .Where(d => d.Expr.StartsWith("date.")) - .ToList(); +sb.AppendLine($"Fields before: {view.Fields.Count}"); -foreach (var dim in toRemove) +foreach (var field in view.Fields.Where(f => f.Expr.StartsWith("date.")).ToList()) { - view.Dimensions.Remove(dim); - sb.AppendLine($"Removed: {dim.Name} ({dim.Expr})"); + field.Delete(); + sb.AppendLine($"Removed: {field.Name} ({field.Expr})"); } -sb.AppendLine($"Dimensions after: {view.Dimensions.Count}"); +sb.AppendLine($"Fields after: {view.Fields.Count}"); Output(sb.ToString()); ``` **Output:** ``` -Dimensions before: 6 +Fields before: 6 Removed: order_date (date.full_date) Removed: order_year (date.year) Removed: order_month (date.month_name) -Dimensions after: 3 +Fields after: 3 ``` +## Next steps + +- [Add objects to a Metric View](xref:semantic-bridge-add-object) +- [Rename a field](xref:semantic-bridge-rename-objects) +- [Serialize a Metric View to YAML](xref:semantic-bridge-serialize) + ## See also -- @semantic-bridge-add-object -- @semantic-bridge-rename-objects -- @semantic-bridge-serialize +- [Metric View Object Model](xref:semantic-bridge-metric-view-object-model) diff --git a/content/how-tos/semantic-bridge-rename-objects.md b/content/how-tos/semantic-bridge-rename-objects.md index c7ef337ee..8d0a17833 100644 --- a/content/how-tos/semantic-bridge-rename-objects.md +++ b/content/how-tos/semantic-bridge-rename-objects.md @@ -2,7 +2,7 @@ uid: semantic-bridge-rename-objects title: Rename Objects in a Metric View author: Greg Baldini -updated: 2025-01-27 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -19,151 +19,62 @@ applies_to: --- # Rename objects in a Metric View -This how-to demonstrates how to rename Metric View dimensions using a copy-modify pattern for bulk transformations. +This how-to demonstrates renaming a Metric View field. The same patterns apply to all collections in a Metric View. -[!INCLUDE [deserialize](includes/sample-metricview-deserialize.md)] +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. -## The copy-modify pattern +[!INCLUDE [sample](includes/sample-metricview.md)] -Since Metric View dimension names are properties on objects in a collection, the cleanest approach is to: +## Rename a field -1. Create new Metric View `Dimension` objects with the modified names -2. Clear the original collection -3. Add the new objects - -This avoids issues with modifying objects while iterating. - -## Convert snake_case to Title Case - -Transform Metric View dimension names from `product_name` to `Product Name`: - -```csharp -using System.Globalization; -using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; +Rename a field by adding a new field under the new name, copying its other properties across, then removing the original. +[`AddField`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.MetricView.View.AddField%2A) sets only the name and expression, so copy the remaining properties (`Comment`, `DisplayName`, `Synonyms`, `Format`) yourself. +```csharp {run id=rename setup=mv-sample after=none output=true} var view = SemanticBridge.MetricView.Model; -var textInfo = CultureInfo.CurrentCulture.TextInfo; -var sb = new System.Text.StringBuilder(); -sb.AppendLine("BEFORE"); -sb.AppendLine("------"); -foreach (var dim in view.Dimensions) -{ - sb.AppendLine($" {dim.Name}"); -} +var old = view.Fields["order_month"]; -// Create renamed dimensions -var renamed = view.Dimensions.Select(dim => new MetricView.Dimension -{ - Name = textInfo.ToTitleCase(dim.Name.Replace('_', ' ')), - Expr = dim.Expr -}).ToList(); +// add the replacement, copy the remaining properties, then remove the original +var renamed = view.AddField("Order Month", old.Expr); +renamed.Comment = old.Comment; +renamed.DisplayName = old.DisplayName; +renamed.Synonyms = old.Synonyms; +renamed.Format = old.Format; +old.Delete(); -// Replace the collection -view.Dimensions.Clear(); -foreach (var dim in renamed) -{ - view.Dimensions.Add(dim); -} - -sb.AppendLine(); -sb.AppendLine("AFTER"); -sb.AppendLine("-----"); -foreach (var dim in view.Dimensions) +var sb = new System.Text.StringBuilder(); +sb.AppendLine("Fields:"); +foreach (var field in view.Fields) { - sb.AppendLine($" {dim.Name}"); + sb.AppendLine($" {field.Name}"); } - Output(sb.ToString()); ``` **Output:** ``` -BEFORE ------- +Fields: product_name product_category customer_segment order_date order_year - order_month - -AFTER ------ - Product Name - Product Category - Customer Segment - Order Date - Order Year Order Month ``` -## Rename using a mapping dictionary +The re-added field moves to the end of the collection. -Apply specific renames using a lookup: +## Next steps -```csharp -using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; - -var view = SemanticBridge.MetricView.Model; - -// Define rename mappings -var renames = new Dictionary -{ - { "product_name", "Product" }, - { "product_category", "Category" }, - { "customer_segment", "Segment" }, - { "order_date", "Date" }, - { "order_year", "Year" }, - { "order_month", "Month" } -}; - -var sb = new System.Text.StringBuilder(); - -// Create renamed dimensions -var renamed = view.Dimensions - .Select( - dim => new MetricView.Dimension - { - Name = renames.TryGetValue(dim.Name, out var newName) ? newName : dim.Name, - Expr = dim.Expr - }) - .ToList(); - -// Replace the collection -view.Dimensions.Clear(); -foreach (var dim in renamed) -{ - view.Dimensions.Add(dim); -} - -sb.AppendLine("Renamed dimensions:"); -sb.AppendLine("-------------------"); -foreach (var dim in view.Dimensions) -{ - sb.AppendLine($" {dim.Name,-20} <- {dim.Expr}"); -} - -Output(sb.ToString()); -``` - -**Output:** - -``` -Renamed dimensions: -------------------- - Product <- product.product_name - Category <- product.category - Segment <- customer.segment - Date <- date.full_date - Year <- date.year - Month <- date.month_name -``` +- [Add objects to a Metric View](xref:semantic-bridge-add-object) +- [Remove objects from a Metric View](xref:semantic-bridge-remove-object) +- [Serialize a Metric View to YAML](xref:semantic-bridge-serialize) ## See also -- @semantic-bridge-add-object -- @semantic-bridge-remove-object -- @semantic-bridge-serialize +- [Metric View Object Model](xref:semantic-bridge-metric-view-object-model) diff --git a/content/how-tos/semantic-bridge-serialize.md b/content/how-tos/semantic-bridge-serialize.md index fad851dc7..ac7f75806 100644 --- a/content/how-tos/semantic-bridge-serialize.md +++ b/content/how-tos/semantic-bridge-serialize.md @@ -2,7 +2,7 @@ uid: semantic-bridge-serialize title: Serialize a Metric View to YAML author: Greg Baldini -updated: 2026-04-17 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -21,18 +21,18 @@ applies_to: This how-to demonstrates how to serialize a Metric View back to YAML format, either as a string or saved to a file. -> [!WARNING] -> The public preview only supports v0.1 Metric View properties. Any v1.1 metadata present in a loaded Metric View is silently ignored and will be lost when you serialize. -> Do not overwrite a source YAML file that contains v1.1 metadata. +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. - -[!INCLUDE [deserialize](includes/sample-metricview-deserialize.md)] +[!INCLUDE [sample](includes/sample-metricview.md)] ## Serialize to a string -Use `Serialize()` to get the YAML representation: +Use `Serialize()` to get the YAML representation. +This simply re-serializes the YAML you loaded above. -```csharp +```csharp {run id=serialize setup=mv-sample after=none output=true} var yaml = SemanticBridge.MetricView.Serialize(); var sb = new System.Text.StringBuilder(); @@ -42,11 +42,74 @@ sb.AppendLine(yaml); Output(sb.ToString()); ``` +**Output** + +``` +YAML output: +------------ +version: 1.1 +source: sales.fact.orders +joins: +- name: product + source: sales.dim.product + on: source.product_id = product.product_id + cardinality: many_to_one +- name: customer + source: sales.dim.customer + on: source.customer_id = customer.customer_id + cardinality: many_to_one +- name: date + source: sales.dim.date + on: source.order_date = date.date_key + cardinality: many_to_one +fields: +- name: product_name + expr: product.product_name +- name: product_category + expr: product.category + display_name: Product Category +- name: customer_segment + expr: customer.segment +- name: order_date + expr: date.full_date +- name: order_year + expr: date.year +- name: order_month + expr: date.month_name +measures: +- name: total_revenue + expr: SUM(revenue) + display_name: Total Revenue + format: + type: currency + decimal_places: + type: exact + places: 2 + currency_code: USD +- name: gross_margin + expr: SUM(revenue) - SUM(cost) +- name: order_count + expr: COUNT(*) +- name: avg_order_value + expr: AVG(revenue) +- name: revenue_to_budget + expr: (SUM(revenue) - SUM(budget)) / SUM(budget) + display_name: Revenue vs Budget + format: + type: percentage + decimal_places: + type: max + places: 1 +- name: unique_customers + expr: COUNT(DISTINCT customer_id) +``` + ## Save to a file -Use `Save(path)` to write the YAML directly to disk: +Use `Save(path)` to write the YAML directly to disk. +This will write the Metric View you loaded above to disk. -```csharp +```csharp {compile} var path = "C:/MetricViews/updated-sales-metrics.yaml"; SemanticBridge.MetricView.Save(path); @@ -58,29 +121,12 @@ Output($"Metric View saved to: {path}"); A common workflow is to load, modify, and save a Metric View: -```csharp -using System.Globalization; -using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; +```csharp {run id=roundtrip setup=mv-sample after=none output=true} +var view = SemanticBridge.MetricView.Model; -// The Metric View is already loaded from the include above +// set a display name on a field, then serialize to confirm it round-trips +view.Fields["order_month"].DisplayName = "Order Month"; -var view = SemanticBridge.MetricView.Model; -var textInfo = CultureInfo.CurrentCulture.TextInfo; - -// Modify: rename dimensions from snake_case to Title Case -var renamed = view.Dimensions.Select(dim => new MetricView.Dimension -{ - Name = textInfo.ToTitleCase(dim.Name.Replace('_', ' ')), - Expr = dim.Expr -}).ToList(); - -view.Dimensions.Clear(); -foreach (var dim in renamed) -{ - view.Dimensions.Add(dim); -} - -// Serialize to see the result var yaml = SemanticBridge.MetricView.Serialize(); var sb = new System.Text.StringBuilder(); @@ -95,44 +141,69 @@ Output(sb.ToString()); ``` Modified YAML: -------------- -version: 0.1 +version: 1.1 source: sales.fact.orders joins: - name: product source: sales.dim.product on: source.product_id = product.product_id + cardinality: many_to_one - name: customer source: sales.dim.customer on: source.customer_id = customer.customer_id + cardinality: many_to_one - name: date source: sales.dim.date on: source.order_date = date.date_key -dimensions: -- name: Product Name + cardinality: many_to_one +fields: +- name: product_name expr: product.product_name -- name: Product Category +- name: product_category expr: product.category -- name: Customer Segment + display_name: Product Category +- name: customer_segment expr: customer.segment -- name: Order Date +- name: order_date expr: date.full_date -- name: Order Year +- name: order_year expr: date.year -- name: Order Month +- name: order_month expr: date.month_name + display_name: Order Month measures: - name: total_revenue expr: SUM(revenue) + display_name: Total Revenue + format: + type: currency + decimal_places: + type: exact + places: 2 + currency_code: USD +- name: gross_margin + expr: SUM(revenue) - SUM(cost) - name: order_count - expr: COUNT(order_id) + expr: COUNT(*) - name: avg_order_value expr: AVG(revenue) +- name: revenue_to_budget + expr: (SUM(revenue) - SUM(budget)) / SUM(budget) + display_name: Revenue vs Budget + format: + type: percentage + decimal_places: + type: max + places: 1 - name: unique_customers expr: COUNT(DISTINCT customer_id) ``` +## Next steps + +- [Load and inspect a Metric View](xref:semantic-bridge-load-inspect) +- [Import a Metric View to Tabular](xref:semantic-bridge-import) + ## See also -- @semantic-bridge-load-inspect -- @semantic-bridge-import -- @semantic-bridge +- [Semantic Bridge Overview](xref:semantic-bridge) diff --git a/content/how-tos/semantic-bridge-validate-contextual-rules.md b/content/how-tos/semantic-bridge-validate-contextual-rules.md index 5a6725d1d..300d149c8 100644 --- a/content/how-tos/semantic-bridge-validate-contextual-rules.md +++ b/content/how-tos/semantic-bridge-validate-contextual-rules.md @@ -2,7 +2,7 @@ uid: semantic-bridge-validate-contextual-rules title: Create Contextual Validation Rules author: Greg Baldini -updated: 2025-01-27 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -22,39 +22,44 @@ applies_to: This how-to demonstrates how to create validation rules that check conditions across multiple objects using the validation context. These rules are for illustrative purposes only and do not necessarily reflect hard technical requirements of either Metric Views or the Semantic Bridge. +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. + ## When to use contextual rules Use contextual rules when you need to: -- Detect duplicate names across objects -- Check that names don't conflict between different object types +- Check that a name is not reused across different object types - Access information about previously validated objects > [!NOTE] -> The validation process validates each Metric View object in order, so the context consists only of those items already visited in the validation. +> The validation process validates each Metric View object in order (joins, then fields, then measures), so the context consists only of those items already visited in the validation. ## The MakeValidationRule method The generic `MakeValidationRule` method provides access to the validation context: -```csharp -SemanticBridge.MetricView.MakeValidationRule( +```csharp {compile} +using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; + +SemanticBridge.MetricView.MakeValidationRule( // or Field, Join, View "rule_name", "category", - (obj, context) => { - // Return IEnumerable - // Empty collection means validation passed - } + + // return an IEnumerable; + // an empty collection means the object passed + (obj, context) => [] ); ``` The `context` parameter provides: -- `context.DimensionNames` - names of dimensions already validated +- `context.FieldNames` - names of fields already validated - `context.MeasureNames` - names of measures already validated - `context.JoinNames` - names of joins already validated -- `context.MakeError(message)` - create an error diagnostic -- `context.MakeError(message, property)` - create an error diagnostic, calling out the specific property with an error +- `context.MakeError(code, message, object)` - create an error diagnostic for the given object +- `context.MakeWarning(code, message, object)` - create a warning diagnostic for the given object Because you create the diagnostic message in the body of the validation function, you can put details about the current object being validated into the message. @@ -62,36 +67,46 @@ Because you create the diagnostic message in the body of the validation function Add this using directive to reference Metric View types: -```csharp +```csharp {compile} using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; ``` -## Rule: Metric View Measure name must not duplicate a Metric View dimension name +## Rule: a Metric View Measure name must not duplicate a Metric View Field name + +Fields are validated before measures, so when a measure is checked, `context.FieldNames` already holds every field name. -```csharp +```csharp {compile} using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; -var measureNotDimensionRule = SemanticBridge.MetricView.MakeValidationRule( - "measure_not_dimension_name", +var measureNameRule = SemanticBridge.MetricView.MakeValidationRule( + "measure_not_field_name", "naming", (measure, context) => - context.DimensionNames.Contains(measure.Name) - ? [context.MakeError($"Measure '{measure.Name}' has the same name as a dimension")] + context.FieldNames.Contains(measure.Name) + ? [context.MakeError( + "measure_field_name_collision", + $"Measure '{measure.Name}' has the same name as a field", + measure)] : [] ); ``` -## Rule: Metric View Measure name must not duplicate another Metric View measure +## Rule: a Metric View Measure name must not duplicate a Metric View Join name -```csharp +Joins are validated first, so `context.JoinNames` holds every join name by the time measures are checked. + +```csharp {compile} using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; -var noDuplicateMeasureRule = SemanticBridge.MetricView.MakeValidationRule( - "no_duplicate_measures", +var measureNotJoinRule = SemanticBridge.MetricView.MakeValidationRule( + "measure_not_join_name", "naming", (measure, context) => - context.MeasureNames.Contains(measure.Name) - ? [context.MakeError($"Measure '{measure.Name}' is defined more than once")] + context.JoinNames.Contains(measure.Name) + ? [context.MakeError( + "measure_join_name_collision", + $"Measure '{measure.Name}' has the same name as a join", + measure)] : [] ); ``` @@ -109,58 +124,64 @@ Notice that we created two separate rules instead of one combined rule. This is This Metric View has naming conflicts that will trigger both contextual rules: -```csharp +```csharp {run id=complete setup=mv-sample after=none output=true} using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; -// Create a Metric View with naming conflicts +// Create a Metric View with names reused across object types SemanticBridge.MetricView.Deserialize(""" - version: 0.1 + version: 1.1 source: sales.fact.orders - dimensions: - # 'revenue' is used as both a dimension and measure name + joins: + - name: customer + source: sales.dim.customer + on: source.customer_id = customer.customer_id + cardinality: many_to_one + fields: + # 'revenue' is also used as a measure name below - name: revenue expr: source.revenue - name: quantity expr: source.quantity - - name: order_date - expr: source.order_date measures: - # measureNotDimensionRule violation - same name as dimension + # measureNameRule violation - same name as the 'revenue' field - name: revenue expr: SUM(source.revenue) - # noDuplicateMeasureRule violation - 'total_quantity' appears twice - - name: total_quantity - expr: SUM(source.quantity) - - name: total_quantity - expr: COUNT(source.order_id) - # This one is fine + # measureNotJoinRule violation - same name as the 'customer' join + - name: customer + expr: COUNT(DISTINCT source.customer_id) + # this measure is fine - name: order_count expr: COUNT(source.order_id) """); -// Define contextual rules -var measureNotDimensionRule = SemanticBridge.MetricView.MakeValidationRule( - "measure_not_dimension_name", +var measureNameRule = SemanticBridge.MetricView.MakeValidationRule( + "measure_not_field_name", "naming", (measure, context) => - context.DimensionNames.Contains(measure.Name) - ? [context.MakeError($"Measure '{measure.Name}' has the same name as a dimension")] + context.FieldNames.Contains(measure.Name) + ? [context.MakeError( + "measure_field_name_collision", + $"Measure '{measure.Name}' has the same name as a field", + measure)] : [] ); -var noDuplicateMeasureRule = SemanticBridge.MetricView.MakeValidationRule( - "no_duplicate_measures", +var measureNotJoinRule = SemanticBridge.MetricView.MakeValidationRule( + "measure_not_join_name", "naming", (measure, context) => - context.MeasureNames.Contains(measure.Name) - ? [context.MakeError($"Measure '{measure.Name}' is defined more than once")] + context.JoinNames.Contains(measure.Name) + ? [context.MakeError( + "measure_join_name_collision", + $"Measure '{measure.Name}' has the same name as a join", + measure)] : [] ); -// Run validation +// Run validation with both rules var diagnostics = SemanticBridge.MetricView.Validate([ - measureNotDimensionRule, - noDuplicateMeasureRule + measureNameRule, + measureNotJoinRule ]).ToList(); // Output results @@ -187,26 +208,38 @@ CONTEXTUAL VALIDATION RESULTS Found 2 issue(s): -[Error] Measure 'revenue' has the same name as a dimension -[Error] Measure 'total_quantity' is defined more than once +[Error] Measure 'revenue' has the same name as a field +[Error] Measure 'customer' has the same name as a join ``` ## Combining with default rules -You can run contextual rules alongside the default validation rules: +You can run contextual rules alongside the default validation rules by calling `Validate` twice: -```csharp +```csharp {run id=combined setup=mv-sample after=complete output=true} using MetricView = TabularEditor.SemanticBridge.Platforms.Databricks.MetricView; var customRules = new[] { SemanticBridge.MetricView.MakeValidationRule( - "measure_not_dimension_name", + "measure_not_field_name", "naming", (measure, context) => - context.DimensionNames.Contains(measure.Name) - ? [context.MakeError($"Measure '{measure.Name}' has the same name as a dimension")] - : [] - ) + context.FieldNames.Contains(measure.Name) + ? [context.MakeError( + "measure_field_name_collision", + $"Measure '{measure.Name}' has the same name as a field", + measure)] + : []), + SemanticBridge.MetricView.MakeValidationRule( + "measure_not_join_name", + "naming", + (measure, context) => + context.JoinNames.Contains(measure.Name) + ? [context.MakeError( + "measure_join_name_collision", + $"Measure '{measure.Name}' has the same name as a join", + measure)] + : []) }; // Run default rules first @@ -221,8 +254,13 @@ sb.AppendLine($"Custom rule issues: {customDiagnostics.Count}"); Output(sb.ToString()); ``` +**Output** + +``` +Default rule issues: 0 +Custom rule issues: 2 +``` + ## See also - [Semantic Bridge Validation](xref:semantic-bridge-metric-view-validation) -- [Create Simple Validation Rules](xref:semantic-bridge-validate-simple-rules) -- [Validate with Default Rules](xref:semantic-bridge-validate-default) diff --git a/content/how-tos/semantic-bridge-validate-default.md b/content/how-tos/semantic-bridge-validate-default.md index 6d73e7dbf..ee19d2221 100644 --- a/content/how-tos/semantic-bridge-validate-default.md +++ b/content/how-tos/semantic-bridge-validate-default.md @@ -2,7 +2,7 @@ uid: semantic-bridge-validate-default title: Validate a Metric View with Default Rules author: Greg Baldini -updated: 2026-04-17 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -19,38 +19,39 @@ applies_to: --- # Validate a Metric View with Default Rules -This how-to demonstrates how to validate a loaded Metric View using the built-in validation rules and interpret the diagnostic messages. +This how-to demonstrates validating a loaded Metric View using the built-in validation rules and interpreting the diagnostic messages. + +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. + +[!INCLUDE [sample](includes/sample-metricview.md)] ## Default validation rules -The Semantic Bridge includes these built-in validation rules: - -| Rule | Description | -|--------------------------|-------------------------------------------------------------------------------| -| JoinNameRequired | Metric View Join must have a name | -| UniqueJoinName | Metric View Join names must be unique | -| JoinSourceRequired | Metric View Join must have a source | -| JoinOnOrUsingRequired | Metric View Join must specify either `on` or `using` | -| JoinOnOrUsingExclusivity | Metric View Join cannot specify both `on` and `using` | -| JoinOnFormat | Metric View Join `on` clause must be a valid equijoin expression | -| JoinUsingColumnCount | Metric View Join `using` clause must have exactly one column (public preview limitation) | -| DimensionNameRequired | Metric View Dimension must have a name | -| UniqueDimensionName | Metric View Dimension names must be unique | -| DimensionExprRequired | Metric View Dimension must have an expression | -| MeasureNameRequired | Metric View Measure must have a name | -| UniqueMeasureName | Metric View Measure names must be unique | -| MeasureExprRequired | Metric View Measure must have an expression | +The Semantic Bridge includes built-in rules that validate a Metric View definition against rules defined in [the Databricks documentation](https://learn.microsoft.com/en-us/azure/databricks/business-semantics/). +These rules are automatically run upon deserialization, whether via `Deserialize` directly or any method that reads a Metric View, such as `Load` or `ImportToTabularFromFile`. +Diagnostics from those automatic runs remain available afterward through `SemanticBridge.MetricView.ImportDiagnostics`. +You can also run these rules on demand against the loaded Metric View, which this document covers. ## Run validation with default rules -Call `Validate()` with no arguments to use the built-in validation rules. +Run [`SemanticBridge.MetricView.Validate();`](xref:TabularEditor.SemanticBridge.Platforms.Databricks.DatabricksMetricViewService.Validate) with no arguments to run the built-in rules against the loaded Metric View. -```csharp +```csharp {run id=validate-count setup=mv-sample after=none output=true} var diagnostics = SemanticBridge.MetricView.Validate().ToList(); Output($"Validation complete: {diagnostics.Count} issue(s) found"); ``` +**Output** + +``` +Validation complete: 0 issue(s) found +``` + +The sample Metric View is valid, so this reports no issues. + ## Interpret diagnostic messages Each diagnostic message contains: @@ -59,7 +60,7 @@ Each diagnostic message contains: - **Message**: Description of the issue - **Path**: Location of the object in the Metric View hierarchy -```csharp +```csharp {run id=interpret setup=mv-sample after=none output=true} var diagnostics = SemanticBridge.MetricView.Validate().ToList(); var sb = new System.Text.StringBuilder(); @@ -84,55 +85,43 @@ else Output(sb.ToString()); ``` -## Example with validation errors - -Some rules (required fields) are enforced during deserialization. -The remaining rules check for duplicates and structural issues after deserialization. - -This Metric View demonstrates violations that are caught by `Validate()`: - -```csharp -SemanticBridge.MetricView.Deserialize(""" - version: 0.1 - source: sales.fact.orders - joins: - # UniqueJoinName - duplicate name 'customer' - - name: customer - source: sales.dim.customer - on: customer_id = customer.customer_id - - name: customer - source: sales.dim.customer_backup - on: customer_id = customer_backup.customer_id - # JoinOnOrUsingRequired - neither on nor using - - name: date - source: sales.dim.date - dimensions: - # UniqueDimensionName - duplicate name 'category' - - name: category - expr: product.category - - name: category - expr: product.subcategory - - name: product_name - expr: product.product_name - measures: - # UniqueMeasureName - duplicate name 'total' - - name: total - expr: SUM(revenue) - - name: total - expr: SUM(quantity) - - name: order_count - expr: COUNT(order_id) - """); +**Output** + +``` +VALIDATION RESULTS +------------------ + +No issues found. +``` + +## Example with a validation error + +Validation always runs against the currently loaded Metric View, so you can introduce a violation in a script and see it caught. +Here we clear a field's expression to trigger `FieldExprRequired`: + +```csharp {run id=error-example setup=mv-sample after=none output=true} +var view = SemanticBridge.MetricView.Model; +view.Fields["order_year"].Expr = ""; var diagnostics = SemanticBridge.MetricView.Validate().ToList(); var sb = new System.Text.StringBuilder(); -sb.AppendLine($"Found {diagnostics.Count} issue(s):"); +sb.AppendLine("VALIDATION RESULTS"); +sb.AppendLine("------------------"); sb.AppendLine(""); -foreach (var diag in diagnostics) +if (diagnostics.Count == 0) +{ + sb.AppendLine("No issues found."); +} +else { - sb.AppendLine($"[{diag.Severity}] {diag.Message}"); + foreach (var diag in diagnostics) + { + sb.AppendLine($"[{diag.Severity}] {diag.Message}"); + sb.AppendLine($" Path: {diag.Path}"); + sb.AppendLine(""); + } } Output(sb.ToString()); @@ -141,21 +130,18 @@ Output(sb.ToString()); **Output:** ``` -Found 6 issue(s): - -[Error] Join 'customer' must use a simple equality condition with table prefixes (e.g. 'source.column = dimension.column') -[Error] Duplicate join name: 'customer' -[Error] Join 'customer' must use a simple equality condition with table prefixes (e.g. 'source.column = dimension.column') -[Error] Join 'date' must specify either 'on' or 'using' clause -[Error] Duplicate dimension name: 'category' -[Error] Duplicate measure name: 'total' +VALIDATION RESULTS +------------------ + +[Error] Field 'order_year' expr cannot be empty + Path: Model.Fields["order_year"].Expr ``` ## Filter diagnostics by severity You can filter diagnostics to focus on errors only: -```csharp +```csharp {run id=filter-severity setup=mv-sample after=none output=true} using System.Linq; using TabularEditor.SemanticBridge.Orchestration; @@ -168,10 +154,17 @@ sb.AppendLine($"Total issues: {diagnostics.Count}"); Output(sb.ToString()); ``` +**Output** + +``` +Errors: 0 +Total issues: 0 +``` + ## Next steps -- [Create simple validation rules](xref:semantic-bridge-validate-simple-rules) to enforce your own conventions -- [Create contextual validation rules](xref:semantic-bridge-validate-contextual-rules) for cross-object checks +- [Create simple validation rules](xref:semantic-bridge-validate-simple-rules) +- [Create contextual validation rules](xref:semantic-bridge-validate-contextual-rules) ## See also diff --git a/content/how-tos/semantic-bridge-validate-simple-rules.md b/content/how-tos/semantic-bridge-validate-simple-rules.md index 14e89b29d..18d275d46 100644 --- a/content/how-tos/semantic-bridge-validate-simple-rules.md +++ b/content/how-tos/semantic-bridge-validate-simple-rules.md @@ -2,7 +2,7 @@ uid: semantic-bridge-validate-simple-rules title: Create Simple Validation Rules author: Greg Baldini -updated: 2025-01-27 +updated: 2026-07-02 applies_to: products: - product: Tabular Editor 2 @@ -22,13 +22,17 @@ applies_to: This how-to demonstrates how to create simple predicate-based validation rules to enforce naming conventions and structural requirements. These rules are for illustrative purposes only and do not necessarily reflect hard technical requirements of either Metric Views or the Semantic Bridge. +> [!NOTE] +> These how-tos target Tabular Editor 3.26.2 and later. +> Earlier versions do not support the v1.1 Metric View features shown here. + ## The four rule helpers There is a helper method for each type of Metric View object: - `MakeValidationRuleForView` - rules for the root View object - `MakeValidationRuleForJoin` - rules for Join objects -- `MakeValidationRuleForDimension` - rules for Dimension objects +- `MakeValidationRuleForField` - rules for Field objects - `MakeValidationRuleForMeasure` - rules for Measure objects Each helper takes four parameters: @@ -42,7 +46,7 @@ Each helper takes four parameters: Check that the Metric View version is the expected value: -```csharp +```csharp {compile} var versionRule = SemanticBridge.MetricView.MakeValidationRuleForView( "version_check", "structure", @@ -55,7 +59,7 @@ var versionRule = SemanticBridge.MetricView.MakeValidationRuleForView( Check that Metric View join sources use fully qualified table names (contain a dot): -```csharp +```csharp {compile} var joinSourceRule = SemanticBridge.MetricView.MakeValidationRuleForJoin( "qualified_source", "structure", @@ -64,16 +68,16 @@ var joinSourceRule = SemanticBridge.MetricView.MakeValidationRuleForJoin( ); ``` -## Rule for Metric View Dimension +## Rule for Metric View Field -Check that Metric View dimension names do not contain underscores: +Check that Metric View field names do not contain underscores: -```csharp -var dimensionNameRule = SemanticBridge.MetricView.MakeValidationRuleForDimension( +```csharp {compile} +var fieldNameRule = SemanticBridge.MetricView.MakeValidationRuleForField( "no_underscores", "naming", - "Dimension names should use spaces, not underscores", - (dim) => dim.Name.Contains('_') + "Field names should use spaces, not underscores", + (field) => field.Name.Contains('_') ); ``` @@ -81,7 +85,7 @@ var dimensionNameRule = SemanticBridge.MetricView.MakeValidationRuleForDimension Check that Metric View measure expressions do not contain SELECT (to avoid accidental subqueries): -```csharp +```csharp {compile} var measureExprRule = SemanticBridge.MetricView.MakeValidationRuleForMeasure( "no_select_subquery", "structure", @@ -90,11 +94,28 @@ var measureExprRule = SemanticBridge.MetricView.MakeValidationRuleForMeasure( ); ``` +## Rules for specific Metric View versions + +Each helper has an overload that takes a final `minVersion` argument, a string such as "0.1" or "1.1". +Rules defined with a `minVersion` only run against Metric Views at or above that version. +This is useful for a rule that checks a property introduced in a later version, +such as `display_name` (added in v1.1): + +```csharp {compile} +var displayNameRule = SemanticBridge.MetricView.MakeValidationRuleForField( + "field_display_name_required", + "naming", + "Fields should have a display name set", + (field) => string.IsNullOrEmpty(field.DisplayName), + "1.1" +); +``` + ## Complete example This Metric View has violations for each of the rules defined above: -```csharp +```csharp {run id=complete setup=mv-sample after=none output=true} // Create a Metric View with violations for each rule SemanticBridge.MetricView.Deserialize(""" version: 0.2 @@ -104,8 +125,8 @@ SemanticBridge.MetricView.Deserialize(""" - name: customer source: customer_table on: source.customer_id = customer.customer_id - dimensions: - # dimensionNameRule violations - contains underscores + fields: + # fieldNameRule violations - contains underscores - name: product_name expr: source.product_name - name: order_date @@ -136,11 +157,11 @@ var joinSourceRule = SemanticBridge.MetricView.MakeValidationRuleForJoin( (join) => !join.Source.Contains('.') ); -var dimensionNameRule = SemanticBridge.MetricView.MakeValidationRuleForDimension( +var fieldNameRule = SemanticBridge.MetricView.MakeValidationRuleForField( "no_underscores", "naming", - "Dimension names should use spaces, not underscores", - (dim) => dim.Name.Contains('_') + "Field names should use spaces, not underscores", + (field) => field.Name.Contains('_') ); var measureExprRule = SemanticBridge.MetricView.MakeValidationRuleForMeasure( @@ -154,7 +175,7 @@ var measureExprRule = SemanticBridge.MetricView.MakeValidationRuleForMeasure( var diagnostics = SemanticBridge.MetricView.Validate([ versionRule, joinSourceRule, - dimensionNameRule, + fieldNameRule, measureExprRule ]).ToList(); @@ -182,18 +203,17 @@ CUSTOM VALIDATION RESULTS Found 5 issue(s): -[Error] MetricView: Metric View version must be 0.1 or 1.1 -[Error] MetricView.Joins['customer']: Join source must be a fully qualified table name (e.g., `catalog.schema.table`) -[Error] MetricView.Dimensions['product_name']: Dimension names should use spaces, not underscores -[Error] MetricView.Dimensions['order_date']: Dimension names should use spaces, not underscores -[Error] MetricView.Measures['complex_calc']: Measure expressions should not contain SELECT subqueries +[Error] Model: Metric View version must be 0.1 or 1.1 +[Error] Model.Joins["customer"]: Join source must be a fully qualified table name (e.g., `catalog.schema.table`) +[Error] Model.Fields["product_name"]: Field names should use spaces, not underscores +[Error] Model.Fields["order_date"]: Field names should use spaces, not underscores +[Error] Model.Measures["complex_calc"]: Measure expressions should not contain SELECT subqueries ``` ## Next steps -- [Create contextual validation rules](xref:semantic-bridge-validate-contextual-rules) for cross-object checks like duplicate detection +- [Create contextual validation rules](xref:semantic-bridge-validate-contextual-rules) ## See also - [Semantic Bridge Validation](xref:semantic-bridge-metric-view-validation) -- [Validate with Default Rules](xref:semantic-bridge-validate-default) diff --git a/content/how-tos/toc.md b/content/how-tos/toc.md index 0c1f275ac..4b1c1ecd3 100644 --- a/content/how-tos/toc.md +++ b/content/how-tos/toc.md @@ -55,6 +55,7 @@ # [Semantic Bridge](semantic-bridge-how-tos.md) ## @semantic-bridge-load-inspect +## @semantic-bridge-metric-view-import-from-file ## @semantic-bridge-import ## @semantic-bridge-validate-default ## @semantic-bridge-validate-simple-rules @@ -63,3 +64,4 @@ ## @semantic-bridge-remove-object ## @semantic-bridge-rename-objects ## @semantic-bridge-serialize +## @semantic-bridge-metric-view-handle-failures diff --git a/content/references/roadmap.md b/content/references/roadmap.md index 6cb9e6576..30feed902 100644 --- a/content/references/roadmap.md +++ b/content/references/roadmap.md @@ -18,7 +18,7 @@ Below is an overview of major new features to be shipped with Tabular Editor 3 u ## In Development -- **Semantic Bridge enhancements**: Support for v1.1 properties and enhanced import UI +- **Semantic Bridge enhancements**: Enhanced import UI, support for multi-fact pattern - **Localization improvements**: Expanding language support and refining existing translations - **Power Query (M) Auto-Formatting**: Advanced formatting capabilities for M expressions - **Graphical Model Comparison**: See the changes that are applied to @@ -51,7 +51,7 @@ For detailed information about each release, see the [full release history](xref ✅ [**Built-in Best Practice Analyzer rules**](xref:built-in-bpa-rules) - Comprehensive set of BPA rules covering formatting, metadata, model layout, DAX expressions, and translations (v3.25.0) -✅ **Semantic Bridge** - Create semantic models from Databricks Metric Views (Enterprise Edition, v3.25.0) +✅ **Semantic Bridge** - Create semantic models from Databricks Metric Views (Enterprise Edition, v3.25.0); support for Metric View v1.1 spec ✅ [**Save with supporting files for Fabric**](xref:save-with-supporting-files) - Support for .platform and definition.pbism files to match Fabric repository structure (v3.25.0)