A schema is an XML file in schema/ that describes one semantic model: SQL sources, cubes, dimensions, hierarchies, measures, calculated members and roles. This page covers the elements used in the eMondrian samples (schema/OnTime.xml, schema/Foodmart.xml).
eMondrian accepts both the attribute-based style shown here (DimensionAttribute, AttributeMapping, sourceAttribute) and the classic Mondrian style (Level column="…" inside a Hierarchy with its own Table). For the classic elements see the original Mondrian schema documentation.
Validate your schema
The MCP tool
validate_schemareports problems the engine ignores silently — misspelled attributes, key mismatches. See AI agents (MCP).
Skeleton
<?xml version="1.0" encoding="UTF-8"?>
<Schema name="MySales">
<!-- Shared SQL views and dimensions -->
<View alias="dim_region"> … </View>
<Cube name="Sales" cache="true" enabled="true">
<View alias="fact"> … </View> <!-- or <Table name="…"/> -->
<Dimension …> … </Dimension>
<Measure …/>
<CalculatedMember …> … </CalculatedMember>
</Cube>
<Role name="…"> … </Role>
</Schema>Sources: Table and View
A cube’s fact source and a dimension’s source are either a table or a SQL view.
<Table name="sales_fact_1997"/><View alias="dim_airline">
<SQL dialect="clickhouse"><![CDATA[
SELECT Reporting_Airline FROM ontime GROUP BY Reporting_Airline
]]></SQL>
<SQL dialect="generic"><![CDATA[
SELECT Reporting_Airline FROM ontime GROUP BY Reporting_Airline
]]></SQL>
</View>| Attribute / child | Meaning |
|---|---|
View alias | Name you refer to from dimensions (table="dim_airline") |
SQL dialect | Which database the SQL is for: clickhouse, postgres, generic, … |
eMondrian picks the <SQL> variant matching the running database and falls back to generic. If neither exists, the schema fails to load. Always add a generic variant unless the SQL is truly database-specific.
<View> elements at schema level can be shared by several cubes; a <View> inside <Cube> is that cube’s fact source.
Cube
<Cube name="OnTime (attributes)" cache="true" enabled="true">| Attribute | Meaning |
|---|---|
name | Cube name shown to clients |
cache | Cache results for this cube (true recommended) |
enabled | false hides the cube without deleting it |
defaultMeasure | Measure used when a query names none |
Children, in order: fact source (Table or View), dimensions (Dimension, DimensionUsage), Measures, CalculatedMembers, NamedSets.
Dimension
<Dimension name="Airline" foreignKey="Reporting_Airline"
table="dim_airline" primaryKey="Reporting_Airline">
<AttributeMapping attribute="Airline" factColumn="Reporting_Airline"/>
<DimensionAttribute name="Airline" usage="Key">
<KeyColumn columnName="Reporting_Airline" dataType="String"/>
</DimensionAttribute>
<Hierarchy hasAll="true" allMemberName="All Airlines">
<Level name="Airline" uniqueMembers="true" sourceAttribute="Airline"/>
</Hierarchy>
</Dimension>| Attribute | Meaning |
|---|---|
name | Dimension name |
table | Source table or view alias for the dimension |
primaryKey | Key column in the dimension source |
foreignKey | Column in the fact source that joins to primaryKey |
Element order is significant
Inside
<Dimension>:AttributeMapping→DimensionAttribute→Hierarchy. Children are parsed by position.
Shared dimensions: DimensionUsage
Define a dimension once at schema level and use it in several cubes:
<DimensionUsage name="Store" source="Store" foreignKey="store_id"/>DimensionAttribute
One usable column of a dimension.
<DimensionAttribute name="Month">
<KeyColumn columnName="month_of_year" dataType="Numeric"/>
<NameColumn columnName="the_month" dataType="String"/>
</DimensionAttribute>| Attribute / child | Meaning |
|---|---|
name | Attribute name, referenced by Level sourceAttribute and Property sourceAttribute |
usage="Key" | Marks the dimension’s key attribute |
attributeHierarchyVisible="false" | Attribute exists but isn’t shown as its own hierarchy |
attributeHierarchyEnabled="false" | No hierarchy is built for this attribute (use it as a member property) |
levelType | Time levels: TimeYears, TimeQuarters, TimeMonths, … |
<KeyColumn> | Column that identifies members. Use dataType (String, Numeric, Integer, Boolean) — not type, which is silently ignored |
<NameColumn> | Column for display names (e.g. month name instead of number) |
<OrderByColumn> | Column for sort order |
AttributeMapping
<AttributeMapping attribute="Year" factColumn="Year"/>Tells eMondrian that the attribute’s value is also available directly in the fact source, so it can filter and group without joining the dimension.
Only for unique keys
Use
AttributeMappingonly when the key is unique in the dimension source. If a key appears twice, the join fans out, and skipping it changes totals. The comment on Origin Airport inschema/OnTime.xmlshows a real case: 2022 Flights come out as 6,767,533 or 6,729,125 depending on the path.
Hierarchy and Level
<Hierarchy hasAll="true" allMemberName="All Time">
<Level name="Year" uniqueMembers="true" sourceAttribute="Year"/>
<Level name="Quarter" uniqueMembers="false" sourceAttribute="Quarter" nameColumn="QuarterName"/>
<Level name="Month" uniqueMembers="false" sourceAttribute="Month" nameColumn="MonthName"/>
</Hierarchy>| Attribute | Meaning |
|---|---|
Hierarchy hasAll | Add a top All member |
Hierarchy allMemberName | Name of the All member |
Hierarchy name | Needed only when a dimension has several hierarchies |
Level name | Level name |
Level sourceAttribute | The DimensionAttribute this level uses |
Level uniqueMembers | true if a value is unique across the whole level (Year), false if only within its parent (Quarter) |
Level nameColumn | Display-name column |
Level levelType | Time level type |
Member properties
<Level name="Store Name" uniqueMembers="true" sourceAttribute="Store Name">
<Property name="Store Type" sourceAttribute="Store Type"/>
</Level>Measure
<Measure name="Flights" column="Flights" aggregator="sum" formatString="#,###"/>
<Measure name="Total Arr Delay" column="ArrDelayMinutes" aggregator="sum" visible="false"/>
<Measure name="Distinct Tail Numbers" column="Tail_Number" aggregator="distinct-count" formatString="#,###"/>| Attribute | Meaning |
|---|---|
name | Measure name |
column | Fact column |
aggregator | sum, count, distinct-count, min, max, avg |
formatString | Display format: #,###, #,##0.00, 0.00%, … |
visible="false" | Hidden helper measure, used in calculations |
CalculatedMember
<CalculatedMember name="On-Time Rate" dimension="Measures" formatString="0.00%">
<Formula>1 - ([Measures].[Late Arrivals] + [Measures].[Cancelled] + [Measures].[Diverted]) / [Measures].[Flights]</Formula>
</CalculatedMember>| Attribute / child | Meaning |
|---|---|
name | Name |
dimension="Measures" | A calculated measure (calculated members on other dimensions are possible too) |
formatString | Display format |
<Formula> | MDX expression |
MDX reference: original Mondrian MDX documentation.
Role
<Role name="SampleRole">
<SchemaGrant access="none">
<CubeGrant cube="Sales" access="all">
<DimensionGrant dimension="[Measures]" access="all"/>
<HierarchyGrant hierarchy="[Time].[Time]" access="custom" rollupPolicy="partial">
<MemberGrant member="[Time].[Time].[1997].[Q4]" access="all"/>
</HierarchyGrant>
<HierarchyGrant hierarchy="[Gender]" access="none"/>
</CubeGrant>
</SchemaGrant>
</Role>Grants go from broad to narrow: schema → cube → dimension / hierarchy → member. access is all, none or custom.
Other elements
Used in Foodmart.xml, documented in the original Mondrian schema documentation: VirtualCube, NamedSet, Annotations, aggregate tables (AggName, AggExclude, …), drill-through actions, parent-child hierarchies (ParentColumn, Closure).
Applying changes
Restart after editing a schema file:
docker compose restart eMondrianErrors appear in mondrian.log — see Logs.
See also
- Modeling on ClickHouse
- Quickstart — section 7, your first cube
- AI agents (MCP) — let an agent draft and validate a schema