Skip to content

feat: Add support for Microsoft Fabric Warehouse #4751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

fresioAS
Copy link

@fresioAS fresioAS commented Jun 16, 2025

Add Microsoft Fabric Engine Support

Overview

This PR adds support for Microsoft Fabric as a new execution engine in SQLMesh. Users can now connect to and execute queries on Microsoft Fabric Data Warehouses.

Changes

  • Documentation:

    • Added docs/integrations/engines/fabric.md with Fabric connection options, installation, and configuration instructions.
    • Listed Fabric in docs/guides/configuration.md and docs/integrations/overview.md.
    • Updated mkdocs.yml to include the new Fabric documentation page.
  • Core Implementation:

    • Added FabricConnectionConfig, inheriting from MSSQLConnectionConfig, with Fabric-specific defaults and validation.
    • Registered the new Fabric engine adapter (FabricAdapter) in the registry.
    • Added sqlmesh/core/engine_adapter/fabric.py with Fabric-specific logic, including the use of DELETE/INSERT for overwrite operations.
  • Testing:

    • Added tests/core/engine_adapter/test_fabric.py for adapter logic, table checks, insert/overwrite, and replace query tests.
    • Updated tests/core/test_connection_config.py for config validation and ODBC connection string generation.
  • Configuration:

    • Updated pyproject.toml to add a fabric test marker.
    • Registered Fabric in all relevant config and adapter modules.

@CLAassistant
Copy link

CLAassistant commented Jun 16, 2025

CLA assistant check
All committers have signed the CLA.

@fresioAS fresioAS force-pushed the add_fabric_warehouse branch from 4e7d6c7 to b679716 Compare June 16, 2025 22:45
@mattiasthalen
Copy link
Contributor

mattiasthalen commented Jun 17, 2025

Thanks for creating this PR draft, so I can try it out 😃

I tried the models creating by sqlmesh init, and that works great!
But... as soon as I try to create a model from some table that exists in the warehouse, I'm getting this error:

    ProgrammingError:
      ('42000', '[42000] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]An object or column name is missing or empty. For SELECT INTO statements, verify each column
has a name. For other statements, look for empty alias names. Aliases defined as "" or [] are not allowed. Change the alias to a valid name. (1038) (SQLExecDirectW)')

The log show some interesting stuff:

2025-06-17 06:35:52,905 - MainThread - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: USE [data_according_to_business] (base.py:2184)
2025-06-17 06:35:52,922 - MainThread - sqlmesh.core.plan.evaluator - INFO - Evaluating plan stage PhysicalLayerUpdateStage (evaluator.py:115)
2025-06-17 06:35:52,923 - ThreadPoolExecutor-1_0 - sqlmesh.core.snapshot.evaluator - INFO - Listing data objects in schema data_according_to_business.sqlmesh__hook (evaluator.py:348)
2025-06-17 06:35:52,923 - ThreadPoolExecutor-1_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-17 06:35:53,392 - ThreadPoolExecutor-1_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-17 06:35:53,404 - MainThread - sqlmesh.core.snapshot.evaluator - INFO - Creating schema 'data_according_to_business.sqlmesh__hook' (evaluator.py:1128)
2025-06-17 06:35:53,405 - MainThread - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT 1 FROM data_according_to_business.INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'data_according_to_business.sqlmesh__hook'; (base.py:2184)
2025-06-17 06:35:53,412 - ThreadPoolExecutor-2_0 - sqlmesh.core.snapshot.evaluator - INFO - Creating table 'data_according_to_business.sqlmesh__hook.hook__frame__northwind__customers__906262037' (evaluator.py:1480)
2025-06-17 06:35:53,412 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-17 06:35:53,750 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT 1 FROM data_according_to_business.INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ''; (base.py:2184)
2025-06-17 06:35:53,754 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: USE [data_according_to_business] (base.py:2184)
2025-06-17 06:35:53,758 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: CREATE SCHEMA [] (base.py:2184)
2025-06-17 06:35:53,762 - MainThread - sqlmesh.core.plan.evaluator - INFO - Execution failed for node SnapshotId<"data_according_to_business"."hook"."frame__northwind__customers": 1341083144> (evaluator.py:186)

This in particular looks suspect:

2025-06-17 06:35:53,758 - ... - Executing SQL: CREATE SCHEMA [] (base.py:2184)

Here's my model:

MODEL (
  name data_according_to_business.hook.frame__northwind__customers,
  kind FULL
);

SELECT
  *
FROM data_according_to_business.dbo.raw__northwind__customers

And the rendered SQL works just fine when evaluating:

$ uv run sqlmesh evaluate hook.frame__northwind__customers
   customer_id                        company_name             contact_name              contact_title  ...             fax        _dlt_load_id         _dlt_id region
0        ALFKI                 Alfreds Futterkiste             Maria Anders       Sales Representative  ...     030-0076545  1750078533.6436024  xpfDb7mcWB5ijQ   None
1        ANATR  Ana Trujillo Emparedados y helados             Ana Trujillo                      Owner  ...    (5) 555-3745  1750078533.6436024  Pr3sRmDpwu66mA   None
2        ANTON             Antonio Moreno Taquería           Antonio Moreno                      Owner  ...            None  1750078533.6436024  X206DXOYfUMhMA   None
3        AROUT                     Around the Horn             Thomas Hardy       Sales Representative  ...  (171) 555-6750  1750078533.6436024  UvMQUiuIwfPMVw   None
4        BERGS                  Berglunds snabbköp       Christina Berglund        Order Administrator  ...   0921-12 34 67  1750078533.6436024  sPupxoT/AS8XXA   None
..         ...                                 ...                      ...                        ...  ...             ...                 ...             ...    ...
86       WARTH                      Wartian Herkku         Pirkko Koskitalo         Accounting Manager  ...      981-443655  1750078533.6436024  sbnEuPm0vmJbTw   None
87       WELLI              Wellington Importadora            Paula Parente              Sales Manager  ...            None  1750078533.6436024  JUEwhfkd5hbtYQ     SP
88       WHITC                White Clover Markets           Karl Jablonski                      Owner  ...  (206) 555-4115  1750078533.6436024  iwjZC43nTrqgKg     WA
89       WILMK                         Wilman Kala          Matti Karttunen  Owner/Marketing Assistant  ...     90-224 8858  1750078533.6436024  LTCR7N1bsPyuhw   None
90       WOLZA                      Wolski  Zajazd  Zbyszek Piestrzeniewicz                      Owner  ...   (26) 642-7012  1750078533.6436024  fDzC3tFHAgLfPQ   None

[91 rows x 13 columns]

@fresioAS
Copy link
Author

Thanks. I will investigate later - perhaps we need schema = self._get_schema_name(table_name) in create_schema() function?
Feel free to contribute if you find a fix. I have not battle tested this outside of SQLMesh init, just wanted to get the code out there for draft first!

@mattiasthalen
Copy link
Contributor

I've made some progress... it fails later now:

2025-06-17 13:54:49,369 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: USE [data_according_to_business] (base.py:2184)
2025-06-17 13:54:54,242 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,131 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT 1 FROM [information_schema].[tables] WHERE [table_name] = '_versions' AND [table_schema] = 'sqlmesh'; (base.py:2184)
2025-06-17 13:54:55,167 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,169 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT * FROM [sqlmesh].[_versions]; (base.py:2184)
2025-06-17 13:54:55,207 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,210 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT 1 FROM [information_schema].[tables] WHERE [table_name] = '_versions' AND [table_schema] = 'sqlmesh'; (base.py:2184)
2025-06-17 13:54:55,214 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,216 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT * FROM [sqlmesh].[_versions]; (base.py:2184)
2025-06-17 13:54:55,241 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,244 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [name], [identifier], [updated_ts], [unpaused_ts], [unrestorable], [next_auto_restatement_ts] FROM [sqlmesh].[_snapshots] AS [snapshots] LEFT JOIN [sqlmesh].[_auto_restatements] AS [auto_restatements] ON [snapshots].[name] = [auto_restatements].[snapshot_name] AND [snapshots].[version] = [auto_restatements].[snapshot_version] WHERE [name] = '"data_according_to_business"."hook"."frame__northwind__customers"' AND [identifier] = '4150067777'; (base.py:2184)
2025-06-17 13:54:55,265 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,269 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [id], [intervals].[name], [intervals].[identifier], [intervals].[version], [intervals].[dev_version], [start_ts], [end_ts], [is_dev], [is_removed], [is_pending_restatement] FROM [sqlmesh].[_intervals] AS [intervals] WHERE [intervals].[name] = '"data_according_to_business"."hook"."frame__northwind__customers"' AND [intervals].[version] = '2009851109' ORDER BY [intervals].[name], [intervals].[version], [created_ts], [is_removed], [is_pending_restatement]; (base.py:2184)
2025-06-17 13:54:55,281 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,285 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [end_at], [plan_id], [promoted_snapshot_ids], [finalized_ts], [snapshots], [catalog_name_override], [previous_finalized_snapshots], [requirements], [suffix_target], [start_at], [name], [expiration_ts], [previous_plan_id], [normalize_name], [gateway_managed] FROM [sqlmesh].[_environments] WHERE [name] = 'dev'; (base.py:2184)
2025-06-17 13:54:55,314 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,317 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [name], [identifier], [updated_ts], [unpaused_ts], [unrestorable], [next_auto_restatement_ts] FROM [sqlmesh].[_snapshots] AS [snapshots] LEFT JOIN [sqlmesh].[_auto_restatements] AS [auto_restatements] ON [snapshots].[name] = [auto_restatements].[snapshot_name] AND [snapshots].[version] = [auto_restatements].[snapshot_version] WHERE [name] = '"data_according_to_business"."hook"."frame__northwind__customers"' AND [identifier] = '3229175387'; (base.py:2184)
2025-06-17 13:54:55,323 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,326 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [id], [intervals].[name], [intervals].[identifier], [intervals].[version], [intervals].[dev_version], [start_ts], [end_ts], [is_dev], [is_removed], [is_pending_restatement] FROM [sqlmesh].[_intervals] AS [intervals] WHERE [intervals].[name] = '"data_according_to_business"."hook"."frame__northwind__customers"' AND [intervals].[version] = '1077561134' ORDER BY [intervals].[name], [intervals].[version], [created_ts], [is_removed], [is_pending_restatement]; (base.py:2184)
2025-06-17 13:54:55,527 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,531 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [environment_statements] FROM [sqlmesh].[_environment_statements] WHERE [environment_name] = 'dev'; (base.py:2184)
2025-06-17 13:54:55,537 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,540 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [end_at], [plan_id], [promoted_snapshot_ids], [finalized_ts], [snapshots], [catalog_name_override], [previous_finalized_snapshots], [requirements], [suffix_target], [start_at], [name], [expiration_ts], [previous_plan_id], [normalize_name], [gateway_managed] FROM [sqlmesh].[_environments] WHERE [name] = 'prod'; (base.py:2184)
2025-06-17 13:54:55,545 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:55,547 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [id], [intervals].[name], [intervals].[identifier], [intervals].[version], [intervals].[dev_version], [start_ts], [end_ts], [is_dev], [is_removed], [is_pending_restatement] FROM [sqlmesh].[_intervals] AS [intervals] WHERE [intervals].[name] = '"data_according_to_business"."hook"."frame__northwind__customers"' AND [intervals].[version] = '2009851109' ORDER BY [intervals].[name], [intervals].[version], [created_ts], [is_removed], [is_pending_restatement]; (base.py:2184)
2025-06-17 13:54:57,306 - MainThread - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: USE [data_according_to_business] (base.py:2184)
2025-06-17 13:54:57,317 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:57,320 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [id], [intervals].[name], [intervals].[identifier], [intervals].[version], [intervals].[dev_version], [start_ts], [end_ts], [is_dev], [is_removed], [is_pending_restatement] FROM [sqlmesh].[_intervals] AS [intervals] WHERE [intervals].[name] = '"data_according_to_business"."hook"."frame__northwind__customers"' AND [intervals].[version] = '2009851109' ORDER BY [intervals].[name], [intervals].[version], [created_ts], [is_removed], [is_pending_restatement]; (base.py:2184)
2025-06-17 13:54:57,326 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Pinging the database to check the connection (base.py:2104)
2025-06-17 13:54:57,328 - MainThread - sqlmesh.core.engine_adapter.base - DEBUG - Executing SQL: SELECT [end_at], [plan_id], [promoted_snapshot_ids], [finalized_ts], [snapshots], [catalog_name_override], [previous_finalized_snapshots], [requirements], [suffix_target], [start_at], [name], [expiration_ts], [previous_plan_id], [normalize_name], [gateway_managed] FROM [sqlmesh].[_environments] WHERE [name] = 'dev'; (base.py:2184)
2025-06-17 13:54:57,332 - MainThread - sqlmesh.core.plan.evaluator - INFO - Evaluating plan stage PhysicalLayerUpdateStage (evaluator.py:115)
2025-06-17 13:54:57,333 - ThreadPoolExecutor-1_0 - sqlmesh.core.snapshot.evaluator - INFO - Listing data objects in schema data_according_to_business.sqlmesh__hook (evaluator.py:348)
2025-06-17 13:54:57,333 - ThreadPoolExecutor-1_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-17 13:54:57,806 - ThreadPoolExecutor-1_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-17 13:54:57,810 - ThreadPoolExecutor-1_0 - sqlmesh.core.engine_adapter.mixins - DEBUG - Executing SQL:
SELECT TABLE_NAME AS name, TABLE_SCHEMA AS schema_name, CASE WHEN TABLE_TYPE = 'BASE TABLE' THEN 'TABLE' ELSE TABLE_TYPE END AS type FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'sqlmesh__hook' AND TABLE_NAME IN ('hook__frame__northwind__customers__2009851109'); (mixins.py:61)
2025-06-17 13:54:57,838 - MainThread - sqlmesh.core.snapshot.evaluator - INFO - Creating schema 'data_according_to_business.sqlmesh__hook' (evaluator.py:1128)
2025-06-17 13:54:57,839 - MainThread - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT 1 FROM data_according_to_business.INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'data_according_to_business.sqlmesh__hook'; (base.py:2184)
2025-06-17 13:54:57,854 - ThreadPoolExecutor-2_0 - sqlmesh.core.snapshot.evaluator - INFO - Creating table 'data_according_to_business.sqlmesh__hook.hook__frame__northwind__customers__2009851109' (evaluator.py:1480)
2025-06-17 13:54:57,854 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-17 13:54:58,406 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT 1 FROM data_according_to_business.INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'dbo'; (base.py:2184)
2025-06-17 13:54:58,410 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-17 13:54:58,412 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: CREATE VIEW .[__temp_ctas_9frxanyy] AS SELECT * FROM [data_according_to_system].[northwind].[raw__northwind__customers] AS [raw__northwind__customers]; (base.py:2184)
2025-06-17 13:54:58,467 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.mixins - DEBUG - Executing SQL:
SELECT COLUMN_NAME, DATA_TYPE FROM data_according_to_business.INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '__temp_ctas_9frxanyy' AND TABLE_SCHEMA = 'dbo' ORDER BY ORDINAL_POSITION; (mixins.py:61)
2025-06-17 13:54:58,519 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: DROP VIEW IF EXISTS [__temp_ctas_9frxanyy]; (base.py:2184)
2025-06-17 13:54:58,530 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: IF NOT EXISTS (SELECT * FROM information_schema.tables WHERE table_name = 'hook__frame__northwind__customers__2009851109' AND table_schema = 'sqlmesh__hook') EXEC('CREATE TABLE [sqlmesh__hook].[hook__frame__northwind__customers__2009851109] ([customer_id] VARCHAR, [company_name] VARCHAR, [contact_name] VARCHAR, [contact_title] VARCHAR, [address] VARCHAR, [city] VARCHAR, [postal_code] VARCHAR, [country] VARCHAR, [phone] VARCHAR, [fax] VARCHAR, [_dlt_load_id] VARCHAR, [_dlt_id] VARCHAR, [region] VARCHAR)'); (base.py:2184)
2025-06-17 13:54:58,533 - MainThread - sqlmesh.core.plan.evaluator - INFO - Execution failed for node SnapshotId<"data_according_to_business"."hook"."frame__northwind__customers": 4150067777> (evaluator.py:186)
Traceback (most recent call last):
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/utils/concurrency.py", line 69, in _process_node
    self.fn(node)
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/utils/concurrency.py", line 172, in <lambda>
    lambda s_id: fn(snapshots_by_id[s_id]),
                 ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/snapshot/evaluator.py", line 412, in <lambda>
    lambda s: self._create_snapshot(
              ^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/snapshot/evaluator.py", line 884, in _create_snapshot
    self._execute_create(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/snapshot/evaluator.py", line 1162, in _execute_create
    evaluation_strategy.create(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/snapshot/evaluator.py", line 1505, in create
    self.adapter.ctas(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/engine_adapter/shared.py", line 342, in internal_wrapper
    resp = func(*list_args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/engine_adapter/base.py", line 561, in ctas
    return self._create_table_from_source_queries(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/engine_adapter/base.py", line 781, in _create_table_from_source_queries
    self._create_table(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/engine_adapter/base.py", line 819, in _create_table
    self.execute(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/engine_adapter/base.py", line 2166, in execute
    self._execute(sql, **kwargs)
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/engine_adapter/base.py", line 2187, in _execute
    self.cursor.execute(sql, **kwargs)
pyodbc.ProgrammingError: ('42S02', "[42S02] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Invalid object name 'information_schema.tables'. (208) (SQLExecDirectW)")

The above exception was the direct cause of the following exception:

sqlmesh.utils.concurrency.NodeExecutionFailedError: Execution failed for node SnapshotId<"data_according_to_business"."hook"."frame__northwind__customers": 4150067777>
2025-06-17 13:54:58,537 - MainThread - sqlmesh.core.context - INFO - Plan application failed. (context.py:1615)
Traceback (most recent call last):
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/plan/evaluator.py", line 168, in visit_physical_layer_update_stage
    completion_status = self.snapshot_evaluator.create(
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/snapshot/evaluator.py", line 389, in create
    self._create_snapshots(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/snapshot/evaluator.py", line 424, in _create_snapshots
    raise SnapshotCreationFailedError(errors, skipped)
sqlmesh.core.snapshot.evaluator.SnapshotCreationFailedError: Physical table creation failed:

Execution failed for node SnapshotId<"data_according_to_business"."hook"."frame__northwind__customers": 4150067777>
  ('42S02', "[42S02] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Invalid object name 'information_schema.tables'. (208) (SQLExecDirectW)")

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/context.py", line 1607, in apply
    self._apply(plan, circuit_breaker)
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/context.py", line 2391, in _apply
    self._scheduler.create_plan_evaluator(self).evaluate(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/plan/evaluator.py", line 98, in evaluate
    self._evaluate_stages(plan_stages, plan)
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/plan/evaluator.py", line 117, in _evaluate_stages
    handler(stage, plan)
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/plan/evaluator.py", line 191, in visit_physical_layer_update_stage
    raise PlanError("Plan application failed.")
sqlmesh.utils.errors.PlanError: Plan application failed.
2025-06-17 13:54:58,538 - MainThread - sqlmesh.cli - ERROR - Unhandled exception (__init__.py:53)
Traceback (most recent call last):
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/plan/evaluator.py", line 168, in visit_physical_layer_update_stage
    completion_status = self.snapshot_evaluator.create(
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/snapshot/evaluator.py", line 389, in create
    self._create_snapshots(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/snapshot/evaluator.py", line 424, in _create_snapshots
    raise SnapshotCreationFailedError(errors, skipped)
sqlmesh.core.snapshot.evaluator.SnapshotCreationFailedError: Physical table creation failed:

Execution failed for node SnapshotId<"data_according_to_business"."hook"."frame__northwind__customers": 4150067777>
  ('42S02', "[42S02] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]Invalid object name 'information_schema.tables'. (208) (SQLExecDirectW)")

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/cli/__init__.py", line 51, in _debug_exception_handler
    return func()
           ^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/cli/__init__.py", line 29, in <lambda>
    return handler(sqlmesh_context, lambda: func(*args, **kwargs))
                                            ^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/analytics/__init__.py", line 82, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/cli/main.py", line 490, in plan
    context.plan(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/analytics/__init__.py", line 110, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/context.py", line 1308, in plan
    self.console.plan(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/console.py", line 1636, in plan
    self._show_options_after_categorization(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/console.py", line 1765, in _show_options_after_categorization
    plan_builder.apply()
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/plan/builder.py", line 260, in apply
    self._apply(self.build())
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/context.py", line 1616, in apply
    raise e
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/context.py", line 1607, in apply
    self._apply(plan, circuit_breaker)
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/context.py", line 2391, in _apply
    self._scheduler.create_plan_evaluator(self).evaluate(
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/plan/evaluator.py", line 98, in evaluate
    self._evaluate_stages(plan_stages, plan)
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/plan/evaluator.py", line 117, in _evaluate_stages
    handler(stage, plan)
  File "/workspaces/fabric/.venv/lib/python3.12/site-packages/sqlmesh/core/plan/evaluator.py", line 191, in visit_physical_layer_update_stage
    raise PlanError("Plan application failed.")
sqlmesh.utils.errors.PlanError: Plan application failed.
2025-06-17 13:54:58,552 - MainThread - root - INFO - Shutting down the event dispatcher (dispatcher.py:159)
2025-06-17 13:54:58,553 - MainThread - sqlmesh.core.analytics.dispatcher - DEBUG - Emitting 4 events (dispatcher.py:134)
2025-06-17 13:54:58,561 - MainThread - urllib3.connectionpool - DEBUG - Starting new HTTPS connection (1): analytics.tobikodata.com:443 (connectionpool.py:1049)
2025-06-17 13:54:58,760 - MainThread - urllib3.connectionpool - DEBUG - https://analytics.tobikodata.com:443 "POST /v1/sqlmesh/ HTTP/1.1" 200 0 (connectionpool.py:544)

But I can't find where the failing part is actually generated.

@georgesittas
Copy link
Contributor

@mattiasthalen the information schema query is generated in SQLGlot (source code) for "create if not exists" expressions, which are constructed in SQLMesh when trying to materialize the model (create physical table, etc).

@mattiasthalen
Copy link
Contributor

@georgesittas, would you say that most of these changes would be more suitable in sqlglot, a fabric-tsql dialect, if you will.

Seeing as there are more differences between tsql and the version in fabric.

@georgesittas
Copy link
Contributor

@georgesittas, would you say that most of these changes would be more suitable in sqlglot, a fabric-tsql dialect, if you will.

Seeing as there are more differences between tsql and the version in fabric.

Could you summarize what the differences are? I thought fabric used t-sql under the hood, but if the two diverge then what you say is reasonable. I'd start with this information schema example and then see if there are more examples besides that.

@erindru
Copy link
Collaborator

erindru commented Jun 17, 2025

@mattiasthalen yeah that's the conclusion I came to when I first started investigating this. Like all abstractions, the Fabric TSQL abstraction is leaky enough to be subtly different from the TSQL supported by SQL Server and not a drop-in replacement.

@fresioAS thanks for giving this a go! The general process for adding new engines to SQLMesh is:

  • Ensure there is a working SQLGlot dialect for it
  • Implement the engine adapter
  • Validate it works to a basic level by adding it to the integration test suite and ensuring the tests pass
  • here is an example PR where I added Athena support

I know this is an early draft, but rather than implementing two separate adapters for fabric_warehouse and fabric_lakehouse, can we just add a single adapter for fabric?

The connection config could take a type parameter to denote if warehouse or lakehouse is in use and the engine adapter itself could delegate to warehouse or lakehouse implementations of certain operations as necessary (or in ConnectionConfig.create_engine_adapter(), return a FabricWarehouseAdapter or FabricLakehouseAdapter depending on the config)

Note that the lakehouse side can just throw NotImplementedError to begin with, it shouldnt be a blocker for adding Fabric Warehouse support to SQLMesh. But we would want to ensure that the fabric entrypoint in SQLMesh is treated as a single entity, even though Fabric under the hood is a collection of services

@mattiasthalen
Copy link
Contributor

@erindru, I don't think there should be any separation between warehouse and Lakehouse. Both use the same type of sql endpoint, the "fabric flavored t-sql".

The only difference I can think of is wether or not the Lakehouse supports schemas. As of now, you get the option to activate schemas when creating a Lakehouse. And that comes with its own issues, e.g., a weaker API.

This might merit a parameter to tell if the catalog/database is a Lakehouse with/without schema, or a warehouse. But I agree that a different engine is overkill.

With that said, the current code in this PR can actually query a Lakehouse. The host/endpoint used is the same for LH & WH, and you specify which one by the catalog/database.

Same thing happens with the sql database object, they share host/endpoint and you select the object by setting the database.

@erindru
Copy link
Collaborator

erindru commented Jun 17, 2025

In that case, a coherent fabric implementation that works transparently across both would be even better.

MS has probably improved this since I last looked, but isn't Lakehouse based on Spark SQL and Warehouse based on the "Polaris" flavour of TSQL?

@mattiasthalen
Copy link
Contributor

Well, yeah. Spark SQL is used in a Lakehouse to create tables. But you can use the SQL endpoint to query it, and I think you can create views with it. The warehouse can use both tsql and spark.

@fresioAS
Copy link
Author

The latest commit including the dialect found with this sqlglot fork allows me to reference lakehouse external data. Now there is most likely some overlaps between the engine and the dialect now, and also there is a good amount of generated code that is probably irrelevant.

Try it out @mattiasthalen and check if we get a bit closer towards the goal

@mattiasthalen
Copy link
Contributor

You're fast! I haven't even fired up a codespace for sqlglot yet.

Did a quick test, but all I got was that there is no fabric dialect. Not sure if the error comes from sqlmesh or sqlglot. ☺️

@mattiasthalen
Copy link
Contributor

Did my own attempt at creating a fabric dialect (https://github.com/mattiasthalen/sqlglot/tree/add-fabric-tsql-dialect), so far only ensures INFORMATION_SCHEMA, but more important: it works! 😀

2025-06-18 20:44:43,757 - MainThread - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: USE [data_according_to_business] (base.py:2184)
2025-06-18 20:44:43,779 - MainThread - sqlmesh.core.plan.evaluator - INFO - Evaluating plan stage CreateSnapshotRecordsStage (evaluator.py:115)
2025-06-18 20:44:43,804 - MainThread - sqlmesh.core.plan.evaluator - INFO - Evaluating plan stage PhysicalLayerUpdateStage (evaluator.py:115)
2025-06-18 20:44:43,805 - ThreadPoolExecutor-1_0 - sqlmesh.core.snapshot.evaluator - INFO - Listing data objects in schema data_according_to_business.sqlmesh__hook (evaluator.py:348)
2025-06-18 20:44:43,806 - ThreadPoolExecutor-1_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-18 20:44:44,262 - ThreadPoolExecutor-1_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-18 20:44:44,273 - MainThread - sqlmesh.core.snapshot.evaluator - INFO - Creating schema 'data_according_to_business.sqlmesh__hook' (evaluator.py:1128)
2025-06-18 20:44:44,274 - MainThread - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: CREATE SCHEMA [sqlmesh__hook] (base.py:2184)
2025-06-18 20:44:44,291 - ThreadPoolExecutor-2_0 - sqlmesh.core.snapshot.evaluator - INFO - Creating table 'data_according_to_business.sqlmesh__hook.hook__frame__northwind__customers__2009851109' (evaluator.py:1480)
2025-06-18 20:44:44,291 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-18 20:44:44,752 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-18 20:44:44,754 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: CREATE VIEW [__temp_ctas_vi2z9jxv] AS SELECT * FROM [data_according_to_system].[northwind].[raw__northwind__customers] AS [raw__northwind__customers]; (base.py:2184)
2025-06-18 20:44:44,819 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: DROP VIEW IF EXISTS [__temp_ctas_vi2z9jxv]; (base.py:2184)
2025-06-18 20:44:44,831 - ThreadPoolExecutor-2_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'hook__frame__northwind__customers__2009851109' AND TABLE_SCHEMA = 'sqlmesh__hook') EXEC('CREATE TABLE [sqlmesh__hook].[hook__frame__northwind__customers__2009851109] ([customer_id] VARCHAR(8000), [company_name] VARCHAR(8000), [contact_name] VARCHAR(8000), [contact_title] VARCHAR(8000), [address] VARCHAR(8000), [city] VARCHAR(8000), [postal_code] VARCHAR(8000), [country] VARCHAR(8000), [phone] VARCHAR(8000), [fax] VARCHAR(8000), [_dlt_load_id] VARCHAR(8000), [_dlt_id] VARCHAR(8000), [region] VARCHAR(8000))'); (base.py:2184)
2025-06-18 20:44:45,046 - MainThread - sqlmesh.core.plan.evaluator - INFO - Evaluating plan stage BackfillStage (evaluator.py:115)
2025-06-18 20:44:45,048 - ThreadPoolExecutor-3_0 - sqlmesh.core.snapshot.evaluator - INFO - Evaluating snapshot SnapshotId<"data_according_to_business"."hook"."frame__northwind__customers": 4150067777> (evaluator.py:634)
2025-06-18 20:44:45,048 - ThreadPoolExecutor-3_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-18 20:44:45,499 - ThreadPoolExecutor-3_0 - sqlmesh.core.snapshot.evaluator - INFO - Inserting batch (2025-05-09 00:00:00, 2025-06-18 00:00:00) into data_according_to_business.sqlmesh__hook.hook__frame__northwind__customers__2009851109' (evaluator.py:688)
2025-06-18 20:44:45,522 - ThreadPoolExecutor-3_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-18 20:44:45,524 - ThreadPoolExecutor-3_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT 1 FROM [INFORMATION_SCHEMA].[TABLES] WHERE [TABLE_NAME] = 'hook__frame__northwind__customers__2009851109' AND [TABLE_SCHEMA] = 'sqlmesh__hook'; (base.py:2184)
2025-06-18 20:44:45,527 - ThreadPoolExecutor-3_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: TRUNCATE TABLE [sqlmesh__hook].[hook__frame__northwind__customers__2009851109]; (base.py:2184)
2025-06-18 20:44:45,720 - ThreadPoolExecutor-3_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: INSERT INTO [sqlmesh__hook].[hook__frame__northwind__customers__2009851109] ([customer_id], [company_name], [contact_name], [contact_title], [address], [city], [postal_code], [country], [phone], [fax], [_dlt_load_id], [_dlt_id], [region]) SELECT [customer_id], [company_name], [contact_name], [contact_title], [address], [city], [postal_code], [country], [phone], [fax], [_dlt_load_id], [_dlt_id], [region] FROM (SELECT * FROM [data_according_to_system].[northwind].[raw__northwind__customers] AS [raw__northwind__customers]) AS [_subquery]; (base.py:2184)
2025-06-18 20:44:46,919 - ThreadPoolExecutor-3_0 - sqlmesh.core.state_sync.db.facade - INFO - Adding interval (2025-05-09 00:00:00, 2025-06-18 00:00:00) for snapshot SnapshotId<"data_according_to_business"."hook"."frame__northwind__customers": 4150067777> (facade.py:625)
2025-06-18 20:44:46,919 - ThreadPoolExecutor-3_0 - sqlmesh.core.state_sync.db.interval - INFO - Pushing intervals for snapshot SnapshotId<"data_according_to_business"."hook"."frame__northwind__customers": 4150067777> (interval.py:214)
2025-06-18 20:44:46,942 - MainThread - sqlmesh.core.plan.evaluator - INFO - Evaluating plan stage EnvironmentRecordUpdateStage (evaluator.py:115)
2025-06-18 20:44:46,943 - MainThread - sqlmesh.core.state_sync.db.facade - INFO - Promoting environment 'dev' (facade.py:173)
2025-06-18 20:44:46,964 - MainThread - sqlmesh.core.plan.evaluator - INFO - Evaluating plan stage VirtualLayerUpdateStage (evaluator.py:115)
2025-06-18 20:44:46,967 - MainThread - sqlmesh.core.snapshot.evaluator - INFO - Creating schema 'data_according_to_business.hook__dev' (evaluator.py:1128)
2025-06-18 20:44:46,967 - MainThread - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: CREATE SCHEMA [hook__dev] (base.py:2184)
2025-06-18 20:44:46,985 - ThreadPoolExecutor-4_0 - sqlmesh.core.snapshot.evaluator - INFO - Updating view 'data_according_to_business.hook__dev.frame__northwind__customers' to point at table 'data_according_to_business.sqlmesh__hook.hook__frame__northwind__customers__2009851109' (evaluator.py:1435)
2025-06-18 20:44:46,986 - ThreadPoolExecutor-4_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: CREATE SCHEMA [hook__dev] (base.py:2184)
2025-06-18 20:44:47,427 - ThreadPoolExecutor-4_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: SELECT DB_NAME(); (base.py:2184)
2025-06-18 20:44:47,429 - ThreadPoolExecutor-4_0 - sqlmesh.core.engine_adapter.base - INFO - Executing SQL: CREATE OR ALTER VIEW [hook__dev].[frame__northwind__customers] AS SELECT * FROM [data_according_to_business].[sqlmesh__hook].[hook__frame__northwind__customers__2009851109]; (base.py:2184)
2025-06-18 20:44:47,452 - MainThread - sqlmesh.core.plan.evaluator - INFO - Evaluating plan stage FinalizeEnvironmentStage (evaluator.py:115)
2025-06-18 20:44:47,454 - MainThread - sqlmesh.core.state_sync.db.environment - INFO - Finalizing environment 'dev' (environment.py:139)
2025-06-18 20:44:47,477 - MainThread - root - INFO - Shutting down the event dispatcher (dispatcher.py:159)

@fresioAS
Copy link
Author

test it on datetime2 - i had to do some changes there to get it to work

@fresioAS fresioAS force-pushed the add_fabric_warehouse branch from 605badf to 332ea32 Compare June 19, 2025 11:06
@mattiasthalen
Copy link
Contributor

@georgesittas / @erindru, what's needed for the config to be available for Python configs?

@erindru
Copy link
Collaborator

erindru commented Jun 22, 2025

Do you mean FabricConnectionConfig?

Nothing - if you're using Python config, you're just manually instantiating the same classes that Yaml config would automatically instantiate

@mattiasthalen
Copy link
Contributor

You should also add FabricConnectionConfig here: https://github.com/fresioAS/sqlmesh/blob/ff0219c70e50160ac96673c9ca2850be0c58f40b/sqlmesh/core/config/__init__.py#L6-L23 :)

@mattiasthalen
Copy link
Contributor

I got it all working here: https://github.com/mattiasthalen/sqlmesh/tree/fabric
Basically the same as yours, but I moved the information_schema in upper case to MSSQL.

It requires the SQLGlot from main, hopefully that will be released soon.

@fresioAS
Copy link
Author

I got it all working here: https://github.com/mattiasthalen/sqlmesh/tree/fabric Basically the same as yours, but I moved the information_schema in upper case to MSSQL.

It requires the SQLGlot from main, hopefully that will be released soon.

Makes the codebase simpler - probably a standalone PR to change the mssql adapter

@fresioAS fresioAS force-pushed the add_fabric_warehouse branch from ff0219c to 6895570 Compare June 23, 2025 21:30
@mattiasthalen
Copy link
Contributor

I got it all working here: https://github.com/mattiasthalen/sqlmesh/tree/fabric Basically the same as yours, but I moved the information_schema in upper case to MSSQL.
It requires the SQLGlot from main, hopefully that will be released soon.

Makes the codebase simpler - probably a standalone PR to change the mssql adapter

Submitted #4795

@fresioAS fresioAS force-pushed the add_fabric_warehouse branch from 158059b to 9c0a2dd Compare June 24, 2025 14:19
@fresioAS fresioAS force-pushed the add_fabric_warehouse branch from 11e2952 to f40fc4d Compare June 25, 2025 06:52
@fresioAS fresioAS force-pushed the add_fabric_warehouse branch from 2ee2ad6 to 5cc30ab Compare June 25, 2025 08:55
@fresioAS fresioAS force-pushed the add_fabric_warehouse branch from 51a87cd to d5f7aa7 Compare June 25, 2025 09:10
@fresioAS fresioAS marked this pull request as ready for review June 25, 2025 09:25
@mattiasthalen
Copy link
Contributor

@fresioAS & @georgesittas I'm curious, how is the fabric dialect from sqlglot included here? I thought sqlglot needed a new tag and the version bumped in sqlmesh.

Copy link
Contributor

@mattiasthalen mattiasthalen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!
I have a pending PR in SQLGlot that will sort the datetime2 errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants