Repository: TobikoData/sqlmesh
Pull request #4603 (2025-06-01 17:00:54)

refactor(lsp): move autocomplete functions about


Patch:
diff --git a/docs/integrations/engines/duckdb.md b/docs/integrations/engines/duckdb.md
index 33751cda..7aef7def 100644
--- a/docs/integrations/engines/duckdb.md
+++ b/docs/integrations/engines/duckdb.md
@@ -79,6 +79,7 @@ SQLMesh will place models with the explicit catalog "ephemeral", such as `epheme
               path: 'catalog.ducklake'
               data_path: data/ducklake
               encrypted: True
+              data_inlining_row_limit: 10
     ```
     
 === "Python"
@@ -102,7 +103,8 @@ SQLMesh will place models with the explicit catalog "ephemeral", such as `epheme
                             type="ducklake",
                             path="catalog.ducklake",
                             data_path="data/ducklake",
-                            encrypted=True
+                            encrypted=True,
+                            data_inlining_row_limit=10,
                         ),
                     }
                 )
diff --git a/sqlmesh/core/config/connection.py b/sqlmesh/core/config/connection.py
index b932e5d3..1ba66a0a 100644
--- a/sqlmesh/core/config/connection.py
+++ b/sqlmesh/core/config/connection.py
@@ -224,6 +224,7 @@ class DuckDBAttachOptions(BaseConfig):
     # DuckLake specific options
     data_path: t.Optional[str] = None
     encrypted: bool = False
+    data_inlining_row_limit: t.Optional[int] = None
 
     def to_sql(self, alias: str) -> str:
         options = []
@@ -236,10 +237,12 @@ class DuckDBAttachOptions(BaseConfig):
 
         # DuckLake specific options
         if self.type == "ducklake":
-            if self.data_path:
+            if self.data_path is not None:
                 options.append(f"DATA_PATH '{self.data_path}'")
             if self.encrypted:
                 options.append("ENCRYPTED")
+            if self.data_inlining_row_limit is not None:
+                options.append(f"DATA_INLINING_ROW_LIMIT {self.data_inlining_row_limit}")
 
         options_sql = f" ({', '.join(options)})" if options else ""
         alias_sql = ""
diff --git a/sqlmesh/lsp/completions.py b/sqlmesh/lsp/completions.py
index e3bd9ab9..8d5d68d9 100644
--- a/sqlmesh/lsp/completions.py
+++ b/sqlmesh/lsp/completions.py
@@ -6,7 +6,9 @@ from sqlmesh.lsp.context import AuditTarget, LSPContext, ModelTarget
 from sqlmesh.lsp.uri import URI
 
 
-def get_sql_completions(context: t.Optional[LSPContext], file_uri: URI) -> AllModelsResponse:
+def get_sql_completions(
+    context: t.Optional[LSPContext], file_uri: t.Optional[URI]
+) -> AllModelsResponse:
     """
     Return a list of completions for a given file.
     """
diff --git a/sqlmesh/lsp/context.py b/sqlmesh/lsp/context.py
index 945f2a83..251bac8b 100644
--- a/sqlmesh/lsp/context.py
+++ b/sqlmesh/lsp/context.py
@@ -6,6 +6,7 @@ import typing as t
 from sqlmesh.core.model.definition import SqlModel
 from sqlmesh.core.linter.definition import AnnotatedRuleViolation
 from sqlmesh.lsp.custom import RenderModelEntry, ModelForRendering
+from sqlmesh.lsp.custom import AllModelsResponse, RenderModelEntry
 from sqlmesh.lsp.uri import URI
 
 
@@ -174,3 +175,16 @@ class LSPContext:
             for audit in self.context.standalone_audits.values()
             if audit._path is not None
         ]
+
+    def get_autocomplete(self, uri: t.Optional[URI]) -> AllModelsResponse:
+        """Get autocomplete suggestions for a file.
+
+        Args:
+            uri: The URI of the file to get autocomplete suggestions for.
+
+        Returns:
+            AllModelsResponse containing models and keywords.
+        """
+        from sqlmesh.lsp.completions import get_sql_completions
+
+        return get_sql_completions(self, uri)
diff --git a/sqlmesh/lsp/main.py b/sqlmesh/lsp/main.py
index a43c4d25..d2b9fcad 100644
--- a/sqlmesh/lsp/main.py
+++ b/sqlmesh/lsp/main.py
@@ -20,7 +20,6 @@ from sqlmesh.lsp.api import (
     ApiResponseGetLineage,
     ApiResponseGetModels,
 )
-from sqlmesh.lsp.completions import get_sql_completions
 from sqlmesh.lsp.context import (
     LSPContext,
     ModelTarget,
@@ -115,9 +114,11 @@ class SQLMeshLanguageServer:
             uri = URI(params.textDocument.uri)
             try:
                 context = self._context_get_or_load(uri)
-                return get_sql_completions(context, uri)
+                return context.get_autocomplete(uri)
             except Exception as e:
-                return get_sql_completions(None, uri)
+                from sqlmesh.lsp.completions import get_sql_completions
+
+                return get_sql_completions(None, URI(params.textDocument.uri))
 
         @self.server.feature(RENDER_MODEL_FEATURE)
         def render_model(ls: LanguageServer, params: RenderModelRequest) -> RenderModelResponse:


Test patch:
diff --git a/tests/core/test_connection_config.py b/tests/core/test_connection_config.py
index 9f91ddd5..63a32e8a 100644
--- a/tests/core/test_connection_config.py
+++ b/tests/core/test_connection_config.py
@@ -611,6 +611,7 @@ def test_duckdb_attach_ducklake_catalog(make_config):
                 path="catalog.ducklake",
                 data_path="/tmp/ducklake_data",
                 encrypted=True,
+                data_inlining_row_limit=10,
             ),
         },
     )
@@ -621,9 +622,11 @@ def test_duckdb_attach_ducklake_catalog(make_config):
     assert ducklake_catalog.path == "catalog.ducklake"
     assert ducklake_catalog.data_path == "/tmp/ducklake_data"
     assert ducklake_catalog.encrypted is True
+    assert ducklake_catalog.data_inlining_row_limit == 10
     # Check that the generated SQL includes DATA_PATH
     assert "DATA_PATH '/tmp/ducklake_data'" in ducklake_catalog.to_sql("ducklake")
     assert "ENCRYPTED" in ducklake_catalog.to_sql("ducklake")
+    assert "DATA_INLINING_ROW_LIMIT 10" in ducklake_catalog.to_sql("ducklake")
 
 
 def test_duckdb_attach_options():
diff --git a/tests/lsp/test_completions.py b/tests/lsp/test_completions.py
index 2110c5f3..4af2d4df 100644
--- a/tests/lsp/test_completions.py
+++ b/tests/lsp/test_completions.py
@@ -26,7 +26,7 @@ def test_get_sql_completions_with_context_no_file_uri():
     context = Context(paths=["examples/sushi"])
     lsp_context = LSPContext(context)
 
-    completions = get_sql_completions(lsp_context, None)
+    completions = lsp_context.get_autocomplete(None)
     assert len(completions.keywords) > len(TOKENIZER_KEYWORDS)
     assert "sushi.active_customers" in completions.models
     assert "sushi.customers" in completions.models
@@ -38,6 +38,6 @@ def test_get_sql_completions_with_context_and_file_uri():
     lsp_context = LSPContext(context)
 
     file_uri = next(key for key in lsp_context.map.keys() if key.name == "active_customers.sql")
-    completions = get_sql_completions(lsp_context, URI.from_path(file_uri))
+    completions = lsp_context.get_autocomplete(URI.from_path(file_uri))
     assert len(completions.keywords) > len(TOKENIZER_KEYWORDS)
     assert "sushi.active_customers" not in completions.models