Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3ad8bbc
feat(thoughtspot): add first-class TML Model object support
nicosuave Jun 13, 2026
ede3e18
fix(thoughtspot): keep table qualifier for model column_id name refs
nicosuave Jun 13, 2026
924af09
fix(thoughtspot): make joined models queryable and preserve role-play…
nicosuave Jun 14, 2026
8f9777c
fix(thoughtspot): resolve unqualified formula refs and aliased base t…
nicosuave Jun 14, 2026
14a9a58
fix(thoughtspot): preserve string literals and make single aliased ta…
nicosuave Jun 14, 2026
f1679a7
fix(thoughtspot): map bare formula refs by column name and project re…
nicosuave Jun 14, 2026
ae95a8f
fix(thoughtspot): resolve single-table formula refs and one-to-many j…
nicosuave Jun 14, 2026
8829f2f
fix(thoughtspot): correct one-to-one join key direction and project r…
nicosuave Jun 14, 2026
c270e69
fix(thoughtspot): preserve composite join keys and infer non-id prima…
nicosuave Jun 14, 2026
9f33276
fix(thoughtspot): inline nested formula references in model formulas
nicosuave Jun 14, 2026
5a590f7
fix(thoughtspot): prefer TML field names over colliding columns and r…
nicosuave Jun 14, 2026
e4aaade
fix(thoughtspot): skip joined-table id columns when inferring primary…
nicosuave Jun 14, 2026
608e374
fix(thoughtspot): only treat equality conjuncts as relationship join …
nicosuave Jun 14, 2026
f4e34cd
fix(thoughtspot): resolve aliased table id and name refs to the alias
nicosuave Jun 14, 2026
280e05b
fix(thoughtspot): resolve aliased join targets and infer measure-back…
nicosuave Jun 14, 2026
8a23d0d
fix(thoughtspot): skip key-less and function-wrapped joins as relatio…
nicosuave Jun 14, 2026
176fe8d
fix(thoughtspot): strip surrounding parentheses when matching join eq…
nicosuave Jun 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
694 changes: 683 additions & 11 deletions sidemantic/adapters/thoughtspot.py

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions sidemantic/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ def load_from_directory(layer: "SemanticLayer", directory: str | Path, *, strict
adapter = ThoughtSpotAdapter()
elif _contains_yaml_key(yaml_data, "worksheet") and _contains_yaml_key(yaml_data, "worksheet_columns"):
adapter = ThoughtSpotAdapter()
elif (
_contains_yaml_key(yaml_data, "model")
and _contains_yaml_key(yaml_data, "model_tables")
and _contains_yaml_key(yaml_data, "columns")
):
# ThoughtSpot TML Model object (export_schema_version v2)
adapter = ThoughtSpotAdapter()
elif _yaml_has_top_level_key(yaml_data, "tables") and _contains_yaml_key(yaml_data, "base_table"):
# Snowflake Cortex Semantic Model format
adapter = SnowflakeAdapter()
Expand Down
822 changes: 822 additions & 0 deletions tests/adapters/thoughtspot/test_parsing.py

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions tests/fixtures/thoughtspot/model_alias.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
guid: "model-alias"
export_schema_version: "2"
model:
name: orders_model
description: Model with id-based tables and an aliased role-playing join
model_tables:
- name: orders
id: orders_tbl
fqn: ANALYTICS.PUBLIC.orders
joins:
- with: ship_country
on: "[orders::ship_country_id] = [ship_country::id]"
type: LEFT_OUTER
cardinality: MANY_TO_ONE
- name: countries
id: countries_tbl
fqn: ANALYTICS.PUBLIC.countries
alias: ship_country
columns:
- name: order_id
column_id: orders::id
properties:
column_type: ATTRIBUTE
- name: ship_country_name
column_id: ship_country::name
properties:
column_type: ATTRIBUTE
- name: amount
column_id: orders::amount
properties:
column_type: MEASURE
aggregation: SUM
30 changes: 30 additions & 0 deletions tests/fixtures/thoughtspot/model_base_alias.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
guid: "model-base-alias"
export_schema_version: "2"
model:
name: orders_model
description: Joined model whose base/source table carries an alias (orders AS o)
model_tables:
- name: orders
alias: o
fqn: ANALYTICS.PUBLIC.orders
joins:
- with: customers
on: "[o::customer_id] = [customers::id]"
type: LEFT_OUTER
cardinality: MANY_TO_ONE
- name: customers
fqn: ANALYTICS.PUBLIC.customers
columns:
- name: order_id
column_id: o::id
properties:
column_type: ATTRIBUTE
- name: customer_name
column_id: customers::name
properties:
column_type: ATTRIBUTE
- name: amount
column_id: o::amount
properties:
column_type: MEASURE
aggregation: SUM
29 changes: 29 additions & 0 deletions tests/fixtures/thoughtspot/model_composite_key.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
guid: "model-composite-key"
export_schema_version: "2"
model:
name: sales_model
description: Joined model with a composite-key join predicate
model_tables:
- name: sales
fqn: ANALYTICS.PUBLIC.sales
joins:
- with: regions
on: "[sales::region_id] = [regions::id] AND [sales::country_code] = [regions::country_code]"
type: INNER
cardinality: MANY_TO_ONE
- name: regions
fqn: ANALYTICS.PUBLIC.regions
columns:
- name: order_id
column_id: sales::id
properties:
column_type: ATTRIBUTE
- name: region_name
column_id: regions::name
properties:
column_type: ATTRIBUTE
- name: amount
column_id: sales::amount
properties:
column_type: MEASURE
aggregation: SUM
29 changes: 29 additions & 0 deletions tests/fixtures/thoughtspot/model_joined_id_key.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
guid: "model-joined-id-key"
export_schema_version: "2"
model:
name: orders_model
description: A joined-table id dimension precedes the base table key
model_tables:
- name: orders
fqn: ANALYTICS.PUBLIC.orders
joins:
- with: customers
on: "[orders::customer_id] = [customers::id]"
type: LEFT_OUTER
cardinality: MANY_TO_ONE
- name: customers
fqn: ANALYTICS.PUBLIC.customers
columns:
- name: id
column_id: customers::id
properties:
column_type: ATTRIBUTE
- name: order_key
column_id: orders::order_key
properties:
column_type: ATTRIBUTE
- name: amount
column_id: orders::amount
properties:
column_type: MEASURE
aggregation: SUM
39 changes: 39 additions & 0 deletions tests/fixtures/thoughtspot/model_name_collision.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
guid: "model-name-collision"
export_schema_version: "2"
model:
name: sales_model
description: A TML field name collides with a different physical column name
model_tables:
- name: sales
fqn: ANALYTICS.PUBLIC.sales
joins:
- with: customers
on: "[sales::customer_id] = [customers::id]"
type: LEFT_OUTER
cardinality: MANY_TO_ONE
- name: customers
fqn: ANALYTICS.PUBLIC.customers
formulas:
- name: total
expr: "[amount] + [gross_revenue]"
id: total_f
columns:
- name: customer_name
column_id: customers::name
properties:
column_type: ATTRIBUTE
- name: gross_revenue
column_id: sales::amount
properties:
column_type: MEASURE
aggregation: SUM
- name: amount
column_id: sales::cost
properties:
column_type: MEASURE
aggregation: SUM
- name: total
formula_id: total_f
properties:
column_type: MEASURE
aggregation: SUM
47 changes: 47 additions & 0 deletions tests/fixtures/thoughtspot/model_nested_formula.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
guid: "model-nested-formula"
export_schema_version: "2"
model:
name: sales_model
description: Joined model whose formula references another formula
model_tables:
- name: sales
fqn: ANALYTICS.PUBLIC.sales
joins:
- with: customers
on: "[sales::customer_id] = [customers::id]"
type: LEFT_OUTER
cardinality: MANY_TO_ONE
- name: customers
fqn: ANALYTICS.PUBLIC.customers
formulas:
- name: net_revenue
expr: "[gross_revenue] - [discount]"
id: net_rev
- name: margin
expr: "[net_revenue] / [gross_revenue]"
id: margin_f
columns:
- name: customer_name
column_id: customers::name
properties:
column_type: ATTRIBUTE
- name: gross_revenue
column_id: sales::gross_revenue
properties:
column_type: MEASURE
aggregation: SUM
- name: discount
column_id: sales::discount
properties:
column_type: MEASURE
aggregation: SUM
- name: net_revenue
formula_id: net_rev
properties:
column_type: MEASURE
aggregation: SUM
- name: margin
formula_id: margin_f
properties:
column_type: MEASURE
aggregation: SUM
29 changes: 29 additions & 0 deletions tests/fixtures/thoughtspot/model_non_id_key.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
guid: "model-non-id-key"
export_schema_version: "2"
model:
name: orders_model
description: Joined model whose base table key is not literally `id`
model_tables:
- name: orders
fqn: ANALYTICS.PUBLIC.orders
joins:
- with: customers
on: "[orders::customer_id] = [customers::id]"
type: LEFT_OUTER
cardinality: MANY_TO_ONE
- name: customers
fqn: ANALYTICS.PUBLIC.customers
columns:
- name: order_key
column_id: orders::order_key
properties:
column_type: ATTRIBUTE
- name: customer_name
column_id: customers::name
properties:
column_type: ATTRIBUTE
- name: amount
column_id: orders::amount
properties:
column_type: MEASURE
aggregation: SUM
29 changes: 29 additions & 0 deletions tests/fixtures/thoughtspot/model_one_to_many.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
guid: "model-one-to-many"
export_schema_version: "2"
model:
name: customers_model
description: One-to-many parent-to-child join (FK lives on the related model)
model_tables:
- name: customers
fqn: ANALYTICS.PUBLIC.customers
joins:
- with: orders
on: "[customers::id] = [orders::customer_id]"
type: LEFT_OUTER
cardinality: ONE_TO_MANY
- name: orders
fqn: ANALYTICS.PUBLIC.orders
columns:
- name: customer_id
column_id: customers::id
properties:
column_type: ATTRIBUTE
- name: customer_name
column_id: customers::name
properties:
column_type: ATTRIBUTE
- name: order_amount
column_id: orders::amount
properties:
column_type: MEASURE
aggregation: SUM
29 changes: 29 additions & 0 deletions tests/fixtures/thoughtspot/model_one_to_one.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
guid: "model-one-to-one"
export_schema_version: "2"
model:
name: sales_model
description: One-to-one join where the source side owns the foreign key
model_tables:
- name: sales
fqn: ANALYTICS.PUBLIC.sales
joins:
- with: regions
on: "[sales::region_id] = [regions::id]"
type: INNER
cardinality: ONE_TO_ONE
- name: regions
fqn: ANALYTICS.PUBLIC.regions
columns:
- name: order_id
column_id: sales::id
properties:
column_type: ATTRIBUTE
- name: region_name
column_id: regions::name
properties:
column_type: ATTRIBUTE
- name: amount
column_id: sales::amount
properties:
column_type: MEASURE
aggregation: SUM
29 changes: 29 additions & 0 deletions tests/fixtures/thoughtspot/model_relationship_fk.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
guid: "model-relationship-fk"
export_schema_version: "2"
model:
name: sales_model
description: Joined model whose relationship foreign key is not also a column
model_tables:
- name: sales
fqn: ANALYTICS.PUBLIC.sales
joins:
- with: regions
on: "[sales::region_id] = [regions::id]"
type: LEFT_OUTER
cardinality: MANY_TO_ONE
- name: regions
fqn: ANALYTICS.PUBLIC.regions
columns:
- name: order_id
column_id: sales::id
properties:
column_type: ATTRIBUTE
- name: region_name
column_id: regions::name
properties:
column_type: ATTRIBUTE
- name: amount
column_id: sales::amount
properties:
column_type: MEASURE
aggregation: SUM
39 changes: 39 additions & 0 deletions tests/fixtures/thoughtspot/model_renamed_formula.model.tml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
guid: "model-renamed-formula"
export_schema_version: "2"
model:
name: sales_model
description: Formula references a TML column whose backing DB column name differs
model_tables:
- name: sales
fqn: ANALYTICS.PUBLIC.sales
joins:
- with: customers
on: "[sales::customer_id] = [customers::id]"
type: LEFT_OUTER
cardinality: MANY_TO_ONE
- name: customers
fqn: ANALYTICS.PUBLIC.customers
formulas:
- name: net_revenue
expr: "[gross_revenue] - [discount]"
id: net_rev
columns:
- name: customer_name
column_id: customers::name
properties:
column_type: ATTRIBUTE
- name: gross_revenue
column_id: sales::gross_amt
properties:
column_type: MEASURE
aggregation: SUM
- name: discount
column_id: sales::disc_amt
properties:
column_type: MEASURE
aggregation: SUM
- name: net_revenue
formula_id: net_rev
properties:
column_type: MEASURE
aggregation: SUM
Loading
Loading