sqlmesh 0.189.1.dev2__py3-none-any.whl → 0.189.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlmesh/_version.py +2 -2
- sqlmesh/core/engine_adapter/bigquery.py +4 -0
- sqlmesh/core/engine_adapter/trino.py +6 -0
- sqlmesh/core/linter/definition.py +5 -2
- sqlmesh/core/linter/helpers.py +115 -0
- sqlmesh/core/linter/rule.py +29 -3
- sqlmesh/core/linter/rules/builtin.py +20 -3
- sqlmesh/core/model/meta.py +5 -5
- sqlmesh/core/plan/explainer.py +1 -1
- sqlmesh/lsp/completions.py +64 -2
- sqlmesh/lsp/context.py +6 -3
- sqlmesh/lsp/main.py +62 -12
- sqlmesh/lsp/reference.py +125 -93
- {sqlmesh-0.189.1.dev2.dist-info → sqlmesh-0.189.2.dist-info}/METADATA +1 -1
- {sqlmesh-0.189.1.dev2.dist-info → sqlmesh-0.189.2.dist-info}/RECORD +19 -18
- {sqlmesh-0.189.1.dev2.dist-info → sqlmesh-0.189.2.dist-info}/WHEEL +0 -0
- {sqlmesh-0.189.1.dev2.dist-info → sqlmesh-0.189.2.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.189.1.dev2.dist-info → sqlmesh-0.189.2.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.189.1.dev2.dist-info → sqlmesh-0.189.2.dist-info}/top_level.txt +0 -0
sqlmesh/_version.py
CHANGED
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '0.189.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 189,
|
|
20
|
+
__version__ = version = '0.189.2'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 189, 2)
|
|
@@ -199,6 +199,10 @@ class BigQueryEngineAdapter(InsertOverwriteWithMergeMixin, ClusteredByMixin, Row
|
|
|
199
199
|
(label_tuple.expressions[0].name, label_tuple.expressions[1].name)
|
|
200
200
|
for label_tuple in label_tuples
|
|
201
201
|
)
|
|
202
|
+
elif query_label_property is not None:
|
|
203
|
+
raise SQLMeshError(
|
|
204
|
+
"Invalid value for `session_properties.query_label`. Must be an array or tuple."
|
|
205
|
+
)
|
|
202
206
|
|
|
203
207
|
if parsed_query_label:
|
|
204
208
|
query_label_str = ",".join([":".join(label) for label in parsed_query_label])
|
|
@@ -27,6 +27,7 @@ from sqlmesh.core.engine_adapter.shared import (
|
|
|
27
27
|
set_catalog,
|
|
28
28
|
)
|
|
29
29
|
from sqlmesh.core.schema_diff import SchemaDiffer
|
|
30
|
+
from sqlmesh.utils.errors import SQLMeshError
|
|
30
31
|
from sqlmesh.utils.date import TimeLike
|
|
31
32
|
|
|
32
33
|
if t.TYPE_CHECKING:
|
|
@@ -99,6 +100,11 @@ class TrinoEngineAdapter(
|
|
|
99
100
|
if not isinstance(authorization, exp.Expression):
|
|
100
101
|
authorization = exp.Literal.string(authorization)
|
|
101
102
|
|
|
103
|
+
if not authorization.is_string:
|
|
104
|
+
raise SQLMeshError(
|
|
105
|
+
"Invalid value for `session_properties.authorization`. Must be a string literal."
|
|
106
|
+
)
|
|
107
|
+
|
|
102
108
|
authorization_sql = authorization.sql(dialect=self.dialect)
|
|
103
109
|
|
|
104
110
|
self.execute(f"SET SESSION AUTHORIZATION {authorization_sql}")
|
|
@@ -8,7 +8,7 @@ import operator as op
|
|
|
8
8
|
from collections.abc import Iterator, Iterable, Set, Mapping, Callable
|
|
9
9
|
from functools import reduce
|
|
10
10
|
from sqlmesh.core.model import Model
|
|
11
|
-
from sqlmesh.core.linter.rule import Rule, RuleViolation
|
|
11
|
+
from sqlmesh.core.linter.rule import Rule, RuleViolation, Range
|
|
12
12
|
from sqlmesh.core.console import LinterConsole, get_console
|
|
13
13
|
|
|
14
14
|
if t.TYPE_CHECKING:
|
|
@@ -74,6 +74,7 @@ class Linter:
|
|
|
74
74
|
violation_msg=violation.violation_msg,
|
|
75
75
|
model=model,
|
|
76
76
|
violation_type="error",
|
|
77
|
+
violation_range=violation.violation_range,
|
|
77
78
|
)
|
|
78
79
|
for violation in error_violations
|
|
79
80
|
] + [
|
|
@@ -82,6 +83,7 @@ class Linter:
|
|
|
82
83
|
violation_msg=violation.violation_msg,
|
|
83
84
|
model=model,
|
|
84
85
|
violation_type="warning",
|
|
86
|
+
violation_range=violation.violation_range,
|
|
85
87
|
)
|
|
86
88
|
for violation in warn_violations
|
|
87
89
|
]
|
|
@@ -149,7 +151,8 @@ class AnnotatedRuleViolation(RuleViolation):
|
|
|
149
151
|
violation_msg: str,
|
|
150
152
|
model: Model,
|
|
151
153
|
violation_type: t.Literal["error", "warning"],
|
|
154
|
+
violation_range: t.Optional[Range] = None,
|
|
152
155
|
) -> None:
|
|
153
|
-
super().__init__(rule, violation_msg)
|
|
156
|
+
super().__init__(rule, violation_msg, violation_range)
|
|
154
157
|
self.model = model
|
|
155
158
|
self.violation_type = violation_type
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from sqlmesh.core.linter.rule import Position, Range
|
|
4
|
+
from sqlmesh.utils.pydantic import PydanticModel
|
|
5
|
+
import typing as t
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TokenPositionDetails(PydanticModel):
|
|
9
|
+
"""
|
|
10
|
+
Details about a token's position in the source code in the structure provided by SQLGlot.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
line (int): The line that the token ends on.
|
|
14
|
+
col (int): The column that the token ends on.
|
|
15
|
+
start (int): The start index of the token.
|
|
16
|
+
end (int): The ending index of the token.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
line: int
|
|
20
|
+
col: int
|
|
21
|
+
start: int
|
|
22
|
+
end: int
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def from_meta(meta: t.Dict[str, int]) -> "TokenPositionDetails":
|
|
26
|
+
return TokenPositionDetails(
|
|
27
|
+
line=meta["line"],
|
|
28
|
+
col=meta["col"],
|
|
29
|
+
start=meta["start"],
|
|
30
|
+
end=meta["end"],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def to_range(self, read_file: t.Optional[t.List[str]]) -> Range:
|
|
34
|
+
"""
|
|
35
|
+
Convert a TokenPositionDetails object to a Range object.
|
|
36
|
+
|
|
37
|
+
In the circumstances where the token's start and end positions are the same,
|
|
38
|
+
there is no need for a read_file parameter, as the range can be derived from the token's
|
|
39
|
+
line and column. This is an optimization to avoid unnecessary file reads and should
|
|
40
|
+
only be used when the token represents a single character or position in the file.
|
|
41
|
+
|
|
42
|
+
If the token's start and end positions are different, the read_file parameter is required.
|
|
43
|
+
|
|
44
|
+
:param read_file: List of lines from the file. Optional
|
|
45
|
+
:return: A Range object representing the token's position
|
|
46
|
+
"""
|
|
47
|
+
if self.start == self.end:
|
|
48
|
+
# If the start and end positions are the same, we can create a range directly
|
|
49
|
+
return Range(
|
|
50
|
+
start=Position(line=self.line - 1, character=self.col - 1),
|
|
51
|
+
end=Position(line=self.line - 1, character=self.col),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if read_file is None:
|
|
55
|
+
raise ValueError("read_file must be provided when start and end positions differ.")
|
|
56
|
+
|
|
57
|
+
# Convert from 1-indexed to 0-indexed for line only
|
|
58
|
+
end_line_0 = self.line - 1
|
|
59
|
+
end_col_0 = self.col
|
|
60
|
+
|
|
61
|
+
# Find the start line and column by counting backwards from the end position
|
|
62
|
+
start_pos = self.start
|
|
63
|
+
end_pos = self.end
|
|
64
|
+
|
|
65
|
+
# Initialize with the end position
|
|
66
|
+
start_line_0 = end_line_0
|
|
67
|
+
start_col_0 = end_col_0 - (end_pos - start_pos + 1)
|
|
68
|
+
|
|
69
|
+
# If start_col_0 is negative, we need to go back to previous lines
|
|
70
|
+
while start_col_0 < 0 and start_line_0 > 0:
|
|
71
|
+
start_line_0 -= 1
|
|
72
|
+
start_col_0 += len(read_file[start_line_0])
|
|
73
|
+
# Account for newline character
|
|
74
|
+
if start_col_0 >= 0:
|
|
75
|
+
break
|
|
76
|
+
start_col_0 += 1 # For the newline character
|
|
77
|
+
|
|
78
|
+
# Ensure we don't have negative values
|
|
79
|
+
start_col_0 = max(0, start_col_0)
|
|
80
|
+
return Range(
|
|
81
|
+
start=Position(line=start_line_0, character=start_col_0),
|
|
82
|
+
end=Position(line=end_line_0, character=end_col_0),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def read_range_from_file(file: Path, text_range: Range) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Read the file and return the content within the specified range.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
file: Path to the file to read
|
|
92
|
+
text_range: The range of text to extract
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
The content within the specified range
|
|
96
|
+
"""
|
|
97
|
+
with file.open("r", encoding="utf-8") as f:
|
|
98
|
+
lines = f.readlines()
|
|
99
|
+
|
|
100
|
+
# Ensure the range is within bounds
|
|
101
|
+
start_line = max(0, text_range.start.line)
|
|
102
|
+
end_line = min(len(lines), text_range.end.line + 1)
|
|
103
|
+
|
|
104
|
+
if start_line >= end_line:
|
|
105
|
+
return ""
|
|
106
|
+
|
|
107
|
+
# Extract the relevant portions of each line
|
|
108
|
+
result = []
|
|
109
|
+
for i in range(start_line, end_line):
|
|
110
|
+
line = lines[i]
|
|
111
|
+
start_char = text_range.start.character if i == text_range.start.line else 0
|
|
112
|
+
end_char = text_range.end.character if i == text_range.end.line else len(line)
|
|
113
|
+
result.append(line[start_char:end_char])
|
|
114
|
+
|
|
115
|
+
return "".join(result)
|
sqlmesh/core/linter/rule.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
+
from dataclasses import dataclass
|
|
4
5
|
|
|
5
6
|
from sqlmesh.core.model import Model
|
|
6
7
|
|
|
@@ -22,6 +23,22 @@ class RuleLocation(PydanticModel):
|
|
|
22
23
|
start_line: t.Optional[int] = None
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class Position:
|
|
28
|
+
"""The position of a rule violation in a file, the position follows the LSP standard."""
|
|
29
|
+
|
|
30
|
+
line: int
|
|
31
|
+
character: int
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class Range:
|
|
36
|
+
"""The range of a rule violation in a file. The range follows the LSP standard."""
|
|
37
|
+
|
|
38
|
+
start: Position
|
|
39
|
+
end: Position
|
|
40
|
+
|
|
41
|
+
|
|
25
42
|
class _Rule(abc.ABCMeta):
|
|
26
43
|
def __new__(cls: Type[_Rule], clsname: str, bases: t.Tuple, attrs: t.Dict) -> _Rule:
|
|
27
44
|
attrs["name"] = clsname.lower()
|
|
@@ -45,9 +62,15 @@ class Rule(abc.ABC, metaclass=_Rule):
|
|
|
45
62
|
"""A summary of what this rule checks for."""
|
|
46
63
|
return self.__doc__ or ""
|
|
47
64
|
|
|
48
|
-
def violation(
|
|
65
|
+
def violation(
|
|
66
|
+
self,
|
|
67
|
+
violation_msg: t.Optional[str] = None,
|
|
68
|
+
violation_range: t.Optional[Range] = None,
|
|
69
|
+
) -> RuleViolation:
|
|
49
70
|
"""Create a RuleViolation instance for this rule"""
|
|
50
|
-
return RuleViolation(
|
|
71
|
+
return RuleViolation(
|
|
72
|
+
rule=self, violation_msg=violation_msg or self.summary, violation_range=violation_range
|
|
73
|
+
)
|
|
51
74
|
|
|
52
75
|
def get_definition_location(self) -> RuleLocation:
|
|
53
76
|
"""Return the file path and position information for this rule.
|
|
@@ -79,9 +102,12 @@ class Rule(abc.ABC, metaclass=_Rule):
|
|
|
79
102
|
|
|
80
103
|
|
|
81
104
|
class RuleViolation:
|
|
82
|
-
def __init__(
|
|
105
|
+
def __init__(
|
|
106
|
+
self, rule: Rule, violation_msg: str, violation_range: t.Optional[Range] = None
|
|
107
|
+
) -> None:
|
|
83
108
|
self.rule = rule
|
|
84
109
|
self.violation_msg = violation_msg
|
|
110
|
+
self.violation_range = violation_range
|
|
85
111
|
|
|
86
112
|
def __repr__(self) -> str:
|
|
87
113
|
return f"{self.rule.name}: {self.violation_msg}"
|
|
@@ -4,9 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import typing as t
|
|
6
6
|
|
|
7
|
+
from sqlglot.expressions import Star
|
|
7
8
|
from sqlglot.helper import subclasses
|
|
8
9
|
|
|
9
|
-
from sqlmesh.core.linter.
|
|
10
|
+
from sqlmesh.core.linter.helpers import TokenPositionDetails
|
|
11
|
+
from sqlmesh.core.linter.rule import Rule, RuleViolation, Range
|
|
10
12
|
from sqlmesh.core.linter.definition import RuleSet
|
|
11
13
|
from sqlmesh.core.model import Model, SqlModel
|
|
12
14
|
|
|
@@ -15,10 +17,25 @@ class NoSelectStar(Rule):
|
|
|
15
17
|
"""Query should not contain SELECT * on its outer most projections, even if it can be expanded."""
|
|
16
18
|
|
|
17
19
|
def check_model(self, model: Model) -> t.Optional[RuleViolation]:
|
|
20
|
+
# Only applies to SQL models, as other model types do not have a query.
|
|
18
21
|
if not isinstance(model, SqlModel):
|
|
19
22
|
return None
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
if model.query.is_star:
|
|
24
|
+
violation_range = self._get_range(model)
|
|
25
|
+
return self.violation(violation_range=violation_range)
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
def _get_range(self, model: SqlModel) -> t.Optional[Range]:
|
|
29
|
+
"""Get the range of the violation if available."""
|
|
30
|
+
try:
|
|
31
|
+
if len(model.query.expressions) == 1 and isinstance(model.query.expressions[0], Star):
|
|
32
|
+
return TokenPositionDetails.from_meta(model.query.expressions[0].meta).to_range(
|
|
33
|
+
None
|
|
34
|
+
)
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
return None
|
|
22
39
|
|
|
23
40
|
|
|
24
41
|
class InvalidSelectStarExpansion(Rule):
|
sqlmesh/core/model/meta.py
CHANGED
|
@@ -323,10 +323,8 @@ class ModelMeta(_Node):
|
|
|
323
323
|
|
|
324
324
|
if prop_name == "query_label":
|
|
325
325
|
query_label = eq.right
|
|
326
|
-
if not (
|
|
327
|
-
|
|
328
|
-
or isinstance(query_label, exp.Tuple)
|
|
329
|
-
or isinstance(query_label, exp.Paren)
|
|
326
|
+
if not isinstance(
|
|
327
|
+
query_label, (exp.Array, exp.Tuple, exp.Paren, d.MacroFunc, d.MacroVar)
|
|
330
328
|
):
|
|
331
329
|
raise ConfigError(
|
|
332
330
|
"Invalid value for `session_properties.query_label`. Must be an array or tuple."
|
|
@@ -349,7 +347,9 @@ class ModelMeta(_Node):
|
|
|
349
347
|
)
|
|
350
348
|
elif prop_name == "authorization":
|
|
351
349
|
authorization = eq.right
|
|
352
|
-
if not (
|
|
350
|
+
if not (
|
|
351
|
+
isinstance(authorization, exp.Literal) and authorization.is_string
|
|
352
|
+
) and not isinstance(authorization, (d.MacroFunc, d.MacroVar)):
|
|
353
353
|
raise ConfigError(
|
|
354
354
|
"Invalid value for `session_properties.authorization`. Must be a string literal."
|
|
355
355
|
)
|
sqlmesh/core/plan/explainer.py
CHANGED
|
@@ -195,7 +195,7 @@ class RichExplainerConsole(ExplainerConsole):
|
|
|
195
195
|
|
|
196
196
|
tree.add(model_tree)
|
|
197
197
|
else:
|
|
198
|
-
tree.add(f"{display_name}
|
|
198
|
+
tree.add(f"{display_name} \\[standalone audit]")
|
|
199
199
|
return tree
|
|
200
200
|
|
|
201
201
|
def visit_migrate_schemas_stage(self, stage: stages.MigrateSchemasStage) -> Tree:
|
sqlmesh/lsp/completions.py
CHANGED
|
@@ -7,14 +7,25 @@ from sqlmesh.lsp.uri import URI
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def get_sql_completions(
|
|
10
|
-
context: t.Optional[LSPContext], file_uri: t.Optional[URI]
|
|
10
|
+
context: t.Optional[LSPContext], file_uri: t.Optional[URI], content: t.Optional[str] = None
|
|
11
11
|
) -> AllModelsResponse:
|
|
12
12
|
"""
|
|
13
13
|
Return a list of completions for a given file.
|
|
14
14
|
"""
|
|
15
|
+
# Get SQL keywords for the dialect
|
|
16
|
+
sql_keywords = get_keywords(context, file_uri)
|
|
17
|
+
|
|
18
|
+
# Get keywords from file content if provided
|
|
19
|
+
file_keywords = set()
|
|
20
|
+
if content:
|
|
21
|
+
file_keywords = extract_keywords_from_content(content, get_dialect(context, file_uri))
|
|
22
|
+
|
|
23
|
+
# Combine keywords - SQL keywords first, then file keywords
|
|
24
|
+
all_keywords = list(sql_keywords) + list(file_keywords - sql_keywords)
|
|
25
|
+
|
|
15
26
|
return AllModelsResponse(
|
|
16
27
|
models=list(get_models(context, file_uri)),
|
|
17
|
-
keywords=
|
|
28
|
+
keywords=all_keywords,
|
|
18
29
|
)
|
|
19
30
|
|
|
20
31
|
|
|
@@ -97,3 +108,54 @@ def get_keywords_from_tokenizer(dialect: t.Optional[str] = None) -> t.Set[str]:
|
|
|
97
108
|
parts = keyword.split(" ")
|
|
98
109
|
expanded_keywords.update(parts)
|
|
99
110
|
return expanded_keywords
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_dialect(context: t.Optional[LSPContext], file_uri: t.Optional[URI]) -> t.Optional[str]:
|
|
114
|
+
"""
|
|
115
|
+
Get the dialect for a given file.
|
|
116
|
+
"""
|
|
117
|
+
if file_uri is not None and context is not None and file_uri.to_path() in context.map:
|
|
118
|
+
file_info = context.map[file_uri.to_path()]
|
|
119
|
+
|
|
120
|
+
# Handle ModelInfo objects
|
|
121
|
+
if isinstance(file_info, ModelTarget) and file_info.names:
|
|
122
|
+
model_name = file_info.names[0]
|
|
123
|
+
model_from_context = context.context.get_model(model_name)
|
|
124
|
+
return model_from_context.dialect
|
|
125
|
+
|
|
126
|
+
# Handle AuditInfo objects
|
|
127
|
+
if isinstance(file_info, AuditTarget) and file_info.name:
|
|
128
|
+
audit = context.context.standalone_audits.get(file_info.name)
|
|
129
|
+
if audit is not None and audit.dialect:
|
|
130
|
+
return audit.dialect
|
|
131
|
+
|
|
132
|
+
if context is not None:
|
|
133
|
+
return context.context.default_dialect
|
|
134
|
+
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def extract_keywords_from_content(content: str, dialect: t.Optional[str] = None) -> t.Set[str]:
|
|
139
|
+
"""
|
|
140
|
+
Extract identifiers from SQL content using the tokenizer.
|
|
141
|
+
Only extracts identifiers (variable names, table names, column names, etc.)
|
|
142
|
+
that are not SQL keywords.
|
|
143
|
+
"""
|
|
144
|
+
if not content:
|
|
145
|
+
return set()
|
|
146
|
+
|
|
147
|
+
tokenizer_class = Dialect.get_or_raise(dialect).tokenizer_class
|
|
148
|
+
keywords = set()
|
|
149
|
+
try:
|
|
150
|
+
tokenizer = tokenizer_class()
|
|
151
|
+
tokens = tokenizer.tokenize(content)
|
|
152
|
+
for token in tokens:
|
|
153
|
+
# Don't include keywords in the set
|
|
154
|
+
if token.text.upper() not in tokenizer_class.KEYWORDS:
|
|
155
|
+
keywords.add(token.text)
|
|
156
|
+
|
|
157
|
+
except Exception:
|
|
158
|
+
# If tokenization fails, return empty set
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
return keywords
|
sqlmesh/lsp/context.py
CHANGED
|
@@ -5,7 +5,7 @@ import typing as t
|
|
|
5
5
|
|
|
6
6
|
from sqlmesh.core.model.definition import SqlModel
|
|
7
7
|
from sqlmesh.core.linter.definition import AnnotatedRuleViolation
|
|
8
|
-
from sqlmesh.lsp.custom import
|
|
8
|
+
from sqlmesh.lsp.custom import ModelForRendering
|
|
9
9
|
from sqlmesh.lsp.custom import AllModelsResponse, RenderModelEntry
|
|
10
10
|
from sqlmesh.lsp.uri import URI
|
|
11
11
|
|
|
@@ -176,15 +176,18 @@ class LSPContext:
|
|
|
176
176
|
if audit._path is not None
|
|
177
177
|
]
|
|
178
178
|
|
|
179
|
-
def get_autocomplete(
|
|
179
|
+
def get_autocomplete(
|
|
180
|
+
self, uri: t.Optional[URI], content: t.Optional[str] = None
|
|
181
|
+
) -> AllModelsResponse:
|
|
180
182
|
"""Get autocomplete suggestions for a file.
|
|
181
183
|
|
|
182
184
|
Args:
|
|
183
185
|
uri: The URI of the file to get autocomplete suggestions for.
|
|
186
|
+
content: The content of the file (optional).
|
|
184
187
|
|
|
185
188
|
Returns:
|
|
186
189
|
AllModelsResponse containing models and keywords.
|
|
187
190
|
"""
|
|
188
191
|
from sqlmesh.lsp.completions import get_sql_completions
|
|
189
192
|
|
|
190
|
-
return get_sql_completions(self, uri)
|
|
193
|
+
return get_sql_completions(self, uri, content)
|
sqlmesh/lsp/main.py
CHANGED
|
@@ -36,9 +36,7 @@ from sqlmesh.lsp.custom import (
|
|
|
36
36
|
RenderModelRequest,
|
|
37
37
|
RenderModelResponse,
|
|
38
38
|
)
|
|
39
|
-
from sqlmesh.lsp.reference import
|
|
40
|
-
get_references,
|
|
41
|
-
)
|
|
39
|
+
from sqlmesh.lsp.reference import get_references, get_cte_references
|
|
42
40
|
from sqlmesh.lsp.uri import URI
|
|
43
41
|
from web.server.api.endpoints.lineage import column_lineage, model_lineage
|
|
44
42
|
from web.server.api.endpoints.models import get_models
|
|
@@ -114,13 +112,22 @@ class SQLMeshLanguageServer:
|
|
|
114
112
|
@self.server.feature(ALL_MODELS_FEATURE)
|
|
115
113
|
def all_models(ls: LanguageServer, params: AllModelsRequest) -> AllModelsResponse:
|
|
116
114
|
uri = URI(params.textDocument.uri)
|
|
115
|
+
|
|
116
|
+
# Get the document content
|
|
117
|
+
content = None
|
|
118
|
+
try:
|
|
119
|
+
document = ls.workspace.get_text_document(params.textDocument.uri)
|
|
120
|
+
content = document.source
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
|
|
117
124
|
try:
|
|
118
125
|
context = self._context_get_or_load(uri)
|
|
119
|
-
return context.get_autocomplete(uri)
|
|
126
|
+
return context.get_autocomplete(uri, content)
|
|
120
127
|
except Exception as e:
|
|
121
128
|
from sqlmesh.lsp.completions import get_sql_completions
|
|
122
129
|
|
|
123
|
-
return get_sql_completions(None, URI(params.textDocument.uri))
|
|
130
|
+
return get_sql_completions(None, URI(params.textDocument.uri), content)
|
|
124
131
|
|
|
125
132
|
@self.server.feature(RENDER_MODEL_FEATURE)
|
|
126
133
|
def render_model(ls: LanguageServer, params: RenderModelRequest) -> RenderModelResponse:
|
|
@@ -369,6 +376,28 @@ class SQLMeshLanguageServer:
|
|
|
369
376
|
ls.show_message(f"Error getting references: {e}", types.MessageType.Error)
|
|
370
377
|
return []
|
|
371
378
|
|
|
379
|
+
@self.server.feature(types.TEXT_DOCUMENT_REFERENCES)
|
|
380
|
+
def find_references(
|
|
381
|
+
ls: LanguageServer, params: types.ReferenceParams
|
|
382
|
+
) -> t.Optional[t.List[types.Location]]:
|
|
383
|
+
"""Find all references of a symbol (currently supporting CTEs)"""
|
|
384
|
+
try:
|
|
385
|
+
uri = URI(params.text_document.uri)
|
|
386
|
+
self._ensure_context_for_document(uri)
|
|
387
|
+
document = ls.workspace.get_text_document(params.text_document.uri)
|
|
388
|
+
if self.lsp_context is None:
|
|
389
|
+
raise RuntimeError(f"No context found for document: {document.path}")
|
|
390
|
+
|
|
391
|
+
cte_references = get_cte_references(self.lsp_context, uri, params.position)
|
|
392
|
+
|
|
393
|
+
# Convert references to Location objects
|
|
394
|
+
locations = [types.Location(uri=ref.uri, range=ref.range) for ref in cte_references]
|
|
395
|
+
|
|
396
|
+
return locations if locations else None
|
|
397
|
+
except Exception as e:
|
|
398
|
+
ls.show_message(f"Error getting locations: {e}", types.MessageType.Error)
|
|
399
|
+
return None
|
|
400
|
+
|
|
372
401
|
@self.server.feature(types.TEXT_DOCUMENT_DIAGNOSTIC)
|
|
373
402
|
def diagnostic(
|
|
374
403
|
ls: LanguageServer, params: types.DocumentDiagnosticParams
|
|
@@ -471,8 +500,16 @@ class SQLMeshLanguageServer:
|
|
|
471
500
|
uri = URI(params.text_document.uri)
|
|
472
501
|
context = self._context_get_or_load(uri)
|
|
473
502
|
|
|
503
|
+
# Get the document content
|
|
504
|
+
content = None
|
|
505
|
+
try:
|
|
506
|
+
document = ls.workspace.get_text_document(params.text_document.uri)
|
|
507
|
+
content = document.source
|
|
508
|
+
except Exception:
|
|
509
|
+
pass
|
|
510
|
+
|
|
474
511
|
# Get completions using the existing completions module
|
|
475
|
-
completion_response = context.get_autocomplete(uri)
|
|
512
|
+
completion_response = context.get_autocomplete(uri, content)
|
|
476
513
|
|
|
477
514
|
completion_items = []
|
|
478
515
|
# Add model completions
|
|
@@ -618,8 +655,24 @@ class SQLMeshLanguageServer:
|
|
|
618
655
|
) -> t.Optional[types.Diagnostic]:
|
|
619
656
|
if diagnostic.model._path is None:
|
|
620
657
|
return None
|
|
621
|
-
|
|
622
|
-
|
|
658
|
+
if not diagnostic.violation_range:
|
|
659
|
+
with open(diagnostic.model._path, "r", encoding="utf-8") as file:
|
|
660
|
+
lines = file.readlines()
|
|
661
|
+
range = types.Range(
|
|
662
|
+
start=types.Position(line=0, character=0),
|
|
663
|
+
end=types.Position(line=len(lines) - 1, character=len(lines[-1])),
|
|
664
|
+
)
|
|
665
|
+
else:
|
|
666
|
+
range = types.Range(
|
|
667
|
+
start=types.Position(
|
|
668
|
+
line=diagnostic.violation_range.start.line,
|
|
669
|
+
character=diagnostic.violation_range.start.character,
|
|
670
|
+
),
|
|
671
|
+
end=types.Position(
|
|
672
|
+
line=diagnostic.violation_range.end.line,
|
|
673
|
+
character=diagnostic.violation_range.end.character,
|
|
674
|
+
),
|
|
675
|
+
)
|
|
623
676
|
|
|
624
677
|
# Get rule definition location for diagnostics link
|
|
625
678
|
rule_location = diagnostic.rule.get_definition_location()
|
|
@@ -628,10 +681,7 @@ class SQLMeshLanguageServer:
|
|
|
628
681
|
|
|
629
682
|
# Use URI format to create a link for "related information"
|
|
630
683
|
return types.Diagnostic(
|
|
631
|
-
range=
|
|
632
|
-
start=types.Position(line=0, character=0),
|
|
633
|
-
end=types.Position(line=len(lines), character=len(lines[-1])),
|
|
634
|
-
),
|
|
684
|
+
range=range,
|
|
635
685
|
message=diagnostic.violation_msg,
|
|
636
686
|
severity=types.DiagnosticSeverity.Error
|
|
637
687
|
if diagnostic.violation_type == "error"
|
sqlmesh/lsp/reference.py
CHANGED
|
@@ -4,6 +4,11 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
from sqlmesh.core.audit import StandaloneAudit
|
|
6
6
|
from sqlmesh.core.dialect import normalize_model_name
|
|
7
|
+
from sqlmesh.core.linter.helpers import (
|
|
8
|
+
TokenPositionDetails,
|
|
9
|
+
Range as SQLMeshRange,
|
|
10
|
+
Position as SQLMeshPosition,
|
|
11
|
+
)
|
|
7
12
|
from sqlmesh.core.model.definition import SqlModel
|
|
8
13
|
from sqlmesh.lsp.context import LSPContext, ModelTarget, AuditTarget
|
|
9
14
|
from sqlglot import exp
|
|
@@ -47,16 +52,7 @@ def by_position(position: Position) -> t.Callable[[Reference], bool]:
|
|
|
47
52
|
"""
|
|
48
53
|
|
|
49
54
|
def contains_position(r: Reference) -> bool:
|
|
50
|
-
return (
|
|
51
|
-
r.range.start.line < position.line
|
|
52
|
-
or (
|
|
53
|
-
r.range.start.line == position.line
|
|
54
|
-
and r.range.start.character <= position.character
|
|
55
|
-
)
|
|
56
|
-
) and (
|
|
57
|
-
r.range.end.line > position.line
|
|
58
|
-
or (r.range.end.line == position.line and r.range.end.character >= position.character)
|
|
59
|
-
)
|
|
55
|
+
return _position_within_range(position, r.range)
|
|
60
56
|
|
|
61
57
|
return contains_position
|
|
62
58
|
|
|
@@ -165,12 +161,17 @@ def get_model_definitions_for_a_path(
|
|
|
165
161
|
if isinstance(alias, exp.TableAlias):
|
|
166
162
|
identifier = alias.this
|
|
167
163
|
if isinstance(identifier, exp.Identifier):
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)
|
|
164
|
+
target_range_sqlmesh = TokenPositionDetails.from_meta(
|
|
165
|
+
identifier.meta
|
|
166
|
+
).to_range(read_file)
|
|
167
|
+
table_range_sqlmesh = TokenPositionDetails.from_meta(
|
|
168
|
+
table.this.meta
|
|
169
|
+
).to_range(read_file)
|
|
170
|
+
|
|
171
|
+
# Convert SQLMesh Range to LSP Range
|
|
172
|
+
target_range = to_lsp_range(target_range_sqlmesh)
|
|
173
|
+
table_range = to_lsp_range(table_range_sqlmesh)
|
|
174
|
+
|
|
174
175
|
references.append(
|
|
175
176
|
Reference(
|
|
176
177
|
uri=document_uri.value, # Same file
|
|
@@ -212,25 +213,26 @@ def get_model_definitions_for_a_path(
|
|
|
212
213
|
|
|
213
214
|
# Extract metadata for positioning
|
|
214
215
|
table_meta = TokenPositionDetails.from_meta(table.this.meta)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
table_range_sqlmesh = table_meta.to_range(read_file)
|
|
217
|
+
start_pos_sqlmesh = table_range_sqlmesh.start
|
|
218
|
+
end_pos_sqlmesh = table_range_sqlmesh.end
|
|
218
219
|
|
|
219
220
|
# If there's a catalog or database qualifier, adjust the start position
|
|
220
221
|
catalog_or_db = table.args.get("catalog") or table.args.get("db")
|
|
221
222
|
if catalog_or_db is not None:
|
|
222
223
|
catalog_or_db_meta = TokenPositionDetails.from_meta(catalog_or_db.meta)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
226
|
-
start_pos = catalog_or_db_range.start
|
|
224
|
+
catalog_or_db_range_sqlmesh = catalog_or_db_meta.to_range(read_file)
|
|
225
|
+
start_pos_sqlmesh = catalog_or_db_range_sqlmesh.start
|
|
227
226
|
|
|
228
227
|
description = generate_markdown_description(referenced_model)
|
|
229
228
|
|
|
230
229
|
references.append(
|
|
231
230
|
Reference(
|
|
232
231
|
uri=referenced_model_uri.value,
|
|
233
|
-
range=Range(
|
|
232
|
+
range=Range(
|
|
233
|
+
start=to_lsp_position(start_pos_sqlmesh),
|
|
234
|
+
end=to_lsp_position(end_pos_sqlmesh),
|
|
235
|
+
),
|
|
234
236
|
markdown_description=description,
|
|
235
237
|
)
|
|
236
238
|
)
|
|
@@ -238,71 +240,6 @@ def get_model_definitions_for_a_path(
|
|
|
238
240
|
return references
|
|
239
241
|
|
|
240
242
|
|
|
241
|
-
class TokenPositionDetails(PydanticModel):
|
|
242
|
-
"""
|
|
243
|
-
Details about a token's position in the source code.
|
|
244
|
-
|
|
245
|
-
Attributes:
|
|
246
|
-
line (int): The line that the token ends on.
|
|
247
|
-
col (int): The column that the token ends on.
|
|
248
|
-
start (int): The start index of the token.
|
|
249
|
-
end (int): The ending index of the token.
|
|
250
|
-
"""
|
|
251
|
-
|
|
252
|
-
line: int
|
|
253
|
-
col: int
|
|
254
|
-
start: int
|
|
255
|
-
end: int
|
|
256
|
-
|
|
257
|
-
@staticmethod
|
|
258
|
-
def from_meta(meta: t.Dict[str, int]) -> "TokenPositionDetails":
|
|
259
|
-
return TokenPositionDetails(
|
|
260
|
-
line=meta["line"],
|
|
261
|
-
col=meta["col"],
|
|
262
|
-
start=meta["start"],
|
|
263
|
-
end=meta["end"],
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def _range_from_token_position_details(
|
|
268
|
-
token_position_details: TokenPositionDetails, read_file: t.List[str]
|
|
269
|
-
) -> Range:
|
|
270
|
-
"""
|
|
271
|
-
Convert a TokenPositionDetails object to a Range object.
|
|
272
|
-
|
|
273
|
-
:param token_position_details: Details about a token's position
|
|
274
|
-
:param read_file: List of lines from the file
|
|
275
|
-
:return: A Range object representing the token's position
|
|
276
|
-
"""
|
|
277
|
-
# Convert from 1-indexed to 0-indexed for line only
|
|
278
|
-
end_line_0 = token_position_details.line - 1
|
|
279
|
-
end_col_0 = token_position_details.col
|
|
280
|
-
|
|
281
|
-
# Find the start line and column by counting backwards from the end position
|
|
282
|
-
start_pos = token_position_details.start
|
|
283
|
-
end_pos = token_position_details.end
|
|
284
|
-
|
|
285
|
-
# Initialize with the end position
|
|
286
|
-
start_line_0 = end_line_0
|
|
287
|
-
start_col_0 = end_col_0 - (end_pos - start_pos + 1)
|
|
288
|
-
|
|
289
|
-
# If start_col_0 is negative, we need to go back to previous lines
|
|
290
|
-
while start_col_0 < 0 and start_line_0 > 0:
|
|
291
|
-
start_line_0 -= 1
|
|
292
|
-
start_col_0 += len(read_file[start_line_0])
|
|
293
|
-
# Account for newline character
|
|
294
|
-
if start_col_0 >= 0:
|
|
295
|
-
break
|
|
296
|
-
start_col_0 += 1 # For the newline character
|
|
297
|
-
|
|
298
|
-
# Ensure we don't have negative values
|
|
299
|
-
start_col_0 = max(0, start_col_0)
|
|
300
|
-
return Range(
|
|
301
|
-
start=Position(line=start_line_0, character=start_col_0),
|
|
302
|
-
end=Position(line=end_line_0, character=end_col_0),
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
|
|
306
243
|
def get_macro_definitions_for_a_path(
|
|
307
244
|
lsp_context: LSPContext, document_uri: URI
|
|
308
245
|
) -> t.List[Reference]:
|
|
@@ -382,11 +319,10 @@ def get_macro_reference(
|
|
|
382
319
|
try:
|
|
383
320
|
# Get the position of the macro invocation in the source file first
|
|
384
321
|
if hasattr(node, "meta") and node.meta:
|
|
385
|
-
|
|
386
|
-
macro_range = _range_from_token_position_details(token_details, read_file)
|
|
322
|
+
macro_range = TokenPositionDetails.from_meta(node.meta).to_range(read_file)
|
|
387
323
|
|
|
388
324
|
# Check if it's a built-in method
|
|
389
|
-
if builtin := get_built_in_macro_reference(macro_name, macro_range):
|
|
325
|
+
if builtin := get_built_in_macro_reference(macro_name, to_lsp_range(macro_range)):
|
|
390
326
|
return builtin
|
|
391
327
|
else:
|
|
392
328
|
# Skip if we can't get the position
|
|
@@ -438,7 +374,7 @@ def get_macro_reference(
|
|
|
438
374
|
|
|
439
375
|
return Reference(
|
|
440
376
|
uri=macro_uri.value,
|
|
441
|
-
range=macro_range,
|
|
377
|
+
range=to_lsp_range(macro_range),
|
|
442
378
|
target_range=Range(
|
|
443
379
|
start=Position(line=start_line - 1, character=0),
|
|
444
380
|
end=Position(line=end_line - 1, character=get_length_of_end_line),
|
|
@@ -478,3 +414,99 @@ def get_built_in_macro_reference(macro_name: str, macro_range: Range) -> t.Optio
|
|
|
478
414
|
),
|
|
479
415
|
markdown_description=func.__doc__ if func.__doc__ else None,
|
|
480
416
|
)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def get_cte_references(
|
|
420
|
+
lint_context: LSPContext, document_uri: URI, position: Position
|
|
421
|
+
) -> t.List[Reference]:
|
|
422
|
+
"""
|
|
423
|
+
Get all references to a CTE at a specific position in a document.
|
|
424
|
+
|
|
425
|
+
This function finds both the definition and all usages of a CTE within the same file.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
lint_context: The LSP context
|
|
429
|
+
document_uri: The URI of the document
|
|
430
|
+
position: The position to check for CTE references
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
A list of references to the CTE (including its definition and all usages)
|
|
434
|
+
"""
|
|
435
|
+
references = get_model_definitions_for_a_path(lint_context, document_uri)
|
|
436
|
+
|
|
437
|
+
# Filter for CTE references (those with target_range set and same URI)
|
|
438
|
+
# TODO: Consider extending Reference class to explicitly indicate reference type instead
|
|
439
|
+
cte_references = [
|
|
440
|
+
ref for ref in references if ref.target_range is not None and ref.uri == document_uri.value
|
|
441
|
+
]
|
|
442
|
+
|
|
443
|
+
if not cte_references:
|
|
444
|
+
return []
|
|
445
|
+
|
|
446
|
+
target_cte_definition_range = None
|
|
447
|
+
for ref in cte_references:
|
|
448
|
+
# Check if cursor is on a CTE usage
|
|
449
|
+
if _position_within_range(position, ref.range):
|
|
450
|
+
target_cte_definition_range = ref.target_range
|
|
451
|
+
break
|
|
452
|
+
# Check if cursor is on the CTE definition
|
|
453
|
+
elif ref.target_range and _position_within_range(position, ref.target_range):
|
|
454
|
+
target_cte_definition_range = ref.target_range
|
|
455
|
+
break
|
|
456
|
+
|
|
457
|
+
if target_cte_definition_range is None:
|
|
458
|
+
return []
|
|
459
|
+
|
|
460
|
+
# Add the CTE definition
|
|
461
|
+
matching_references = [
|
|
462
|
+
Reference(
|
|
463
|
+
uri=document_uri.value,
|
|
464
|
+
range=target_cte_definition_range,
|
|
465
|
+
markdown_description="CTE definition",
|
|
466
|
+
)
|
|
467
|
+
]
|
|
468
|
+
|
|
469
|
+
# Add all usages
|
|
470
|
+
for ref in cte_references:
|
|
471
|
+
if ref.target_range == target_cte_definition_range:
|
|
472
|
+
matching_references.append(
|
|
473
|
+
Reference(
|
|
474
|
+
uri=document_uri.value,
|
|
475
|
+
range=ref.range,
|
|
476
|
+
markdown_description="CTE usage",
|
|
477
|
+
)
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
return matching_references
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _position_within_range(position: Position, range: Range) -> bool:
|
|
484
|
+
"""Check if a position is within a given range."""
|
|
485
|
+
return (
|
|
486
|
+
range.start.line < position.line
|
|
487
|
+
or (range.start.line == position.line and range.start.character <= position.character)
|
|
488
|
+
) and (
|
|
489
|
+
range.end.line > position.line
|
|
490
|
+
or (range.end.line == position.line and range.end.character >= position.character)
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def to_lsp_range(
|
|
495
|
+
range: SQLMeshRange,
|
|
496
|
+
) -> Range:
|
|
497
|
+
"""
|
|
498
|
+
Converts a SQLMesh Range to an LSP Range.
|
|
499
|
+
"""
|
|
500
|
+
return Range(
|
|
501
|
+
start=Position(line=range.start.line, character=range.start.character),
|
|
502
|
+
end=Position(line=range.end.line, character=range.end.character),
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def to_lsp_position(
|
|
507
|
+
position: SQLMeshPosition,
|
|
508
|
+
) -> Position:
|
|
509
|
+
"""
|
|
510
|
+
Converts a SQLMesh Position to an LSP Position.
|
|
511
|
+
"""
|
|
512
|
+
return Position(line=position.line, character=position.character)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
sqlmesh/__init__.py,sha256=QjwaamJIdW7voXoS880yAx7tHm-Im4_mqGA-XJgafos,7631
|
|
2
|
-
sqlmesh/_version.py,sha256=
|
|
2
|
+
sqlmesh/_version.py,sha256=kra5ta__MYnZcxN-ELkhfg1WzR3g3TjVwihb3Azv-3w,515
|
|
3
3
|
sqlmesh/magics.py,sha256=fu7-OeT_0_TAkWZOLoiioE3-3iFpvTRxFCbQUlRnfuw,39944
|
|
4
4
|
sqlmesh/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
sqlmesh/cicd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -61,7 +61,7 @@ sqlmesh/core/engine_adapter/_typing.py,sha256=_63pYCThfC39UtPVHY3N0H6qJ1YBT32Oqw
|
|
|
61
61
|
sqlmesh/core/engine_adapter/athena.py,sha256=WBVAB_YzBJXdiqq51LTd2jmYoMtGrLmtsA8IrIFVnQ0,26199
|
|
62
62
|
sqlmesh/core/engine_adapter/base.py,sha256=2W3dPeO7iqaA3byEDziB3lBh7pjgaq4_J8IKkcX4urg,101348
|
|
63
63
|
sqlmesh/core/engine_adapter/base_postgres.py,sha256=DOsOdCJyQAVfA9S0GznuH0J1bonMqYVP_yJLDYcxFYM,6772
|
|
64
|
-
sqlmesh/core/engine_adapter/bigquery.py,sha256=
|
|
64
|
+
sqlmesh/core/engine_adapter/bigquery.py,sha256=qJE8vHLKKAeWMvltmYaUvWS95jM_UEZ6UzYLlr58sGo,50424
|
|
65
65
|
sqlmesh/core/engine_adapter/clickhouse.py,sha256=LoPdm_s_s-ZxS1XJPA3Et0iOHeJ1LgqTAFbwBdiqE7A,34912
|
|
66
66
|
sqlmesh/core/engine_adapter/databricks.py,sha256=8ZExPIeT3dDh9MTJW8udvmh9LiY-f9HkRAN1Q5Qebhs,14541
|
|
67
67
|
sqlmesh/core/engine_adapter/duckdb.py,sha256=rFv27sF1KXhJcrIuM9Zdfl2cyZNRXEF9qQczgODdK0M,4614
|
|
@@ -74,12 +74,13 @@ sqlmesh/core/engine_adapter/risingwave.py,sha256=YYCQ0EJaSrR9_P1_2_LmElQ6zSxqkZz
|
|
|
74
74
|
sqlmesh/core/engine_adapter/shared.py,sha256=NyF2osSJDWPp7KlJUBR4GZcS_cJFr4daUPvhGyp4XcQ,12909
|
|
75
75
|
sqlmesh/core/engine_adapter/snowflake.py,sha256=oaZxKPmPXxmr46NQfw6OI5vPwvaYJ3Ym8OnnhDiUXNw,27234
|
|
76
76
|
sqlmesh/core/engine_adapter/spark.py,sha256=sYkVOPS0X43UPbI0z7Hh_EpKlI36qkOcr4NNFOTQlLs,21045
|
|
77
|
-
sqlmesh/core/engine_adapter/trino.py,sha256=
|
|
77
|
+
sqlmesh/core/engine_adapter/trino.py,sha256=7WdLFlT0AyD5NF0EDP7GqX0SHF8sTm7cafBZxk84cmY,15798
|
|
78
78
|
sqlmesh/core/linter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
|
-
sqlmesh/core/linter/definition.py,sha256=
|
|
80
|
-
sqlmesh/core/linter/
|
|
79
|
+
sqlmesh/core/linter/definition.py,sha256=Rjqvto7VvVEnMXXOH4q3Jol1HnPe6I6BCHK1sEJm50I,5430
|
|
80
|
+
sqlmesh/core/linter/helpers.py,sha256=VF4hsdzGZ8srpbtCjdRN_Hd8S_er5KTAaLXLde27Yxo,4032
|
|
81
|
+
sqlmesh/core/linter/rule.py,sha256=iCmFUw6Qx7GuUwLKIpHrHdq3kgtgm5JC2t9VFtMY3BQ,3180
|
|
81
82
|
sqlmesh/core/linter/rules/__init__.py,sha256=gevzfb67vFqckTCoVAe_TBGf6hQ-YtE1_YuGuXyh1L0,77
|
|
82
|
-
sqlmesh/core/linter/rules/builtin.py,sha256=
|
|
83
|
+
sqlmesh/core/linter/rules/builtin.py,sha256=EHTd4sgHT6oT93onK7t5pLmv2KgzZ81twkTnOrMi9tI,2681
|
|
83
84
|
sqlmesh/core/metric/__init__.py,sha256=H1HmoD5IwN4YWe9iJXyueLYNmTQFZwok5nSWNJcZIBQ,237
|
|
84
85
|
sqlmesh/core/metric/definition.py,sha256=Yd5aVgsZCDPJ43aGP7WqtzZOuuSUtB8uJGVA6Jw9x9M,7201
|
|
85
86
|
sqlmesh/core/metric/rewriter.py,sha256=GiSTHfn2kinqCfNPYgZPRk93JFLzVaaejHtHDQ0yXZI,7326
|
|
@@ -89,14 +90,14 @@ sqlmesh/core/model/common.py,sha256=vbcXFhWE22-FeKYAVIlKWvTb-wkeC8XISjU_TIlak0c,
|
|
|
89
90
|
sqlmesh/core/model/decorator.py,sha256=v_sVudNROGrtj5cZ3vgcOFnwPdzwoUGY6REAkQpIMQc,8927
|
|
90
91
|
sqlmesh/core/model/definition.py,sha256=G2FHqCsJaDOzRcJCdTlfe6Ygl55rh9RAEXC1Zaka8uA,108470
|
|
91
92
|
sqlmesh/core/model/kind.py,sha256=i6_AMBarXPGPoMdF9kzBkyqS9tu__a2339-BoX7TgCM,36010
|
|
92
|
-
sqlmesh/core/model/meta.py,sha256=
|
|
93
|
+
sqlmesh/core/model/meta.py,sha256=D9InI9M2-IXeb34vkXNQSLALL8xVXjZ_LXwI-Tqxm7I,21197
|
|
93
94
|
sqlmesh/core/model/schema.py,sha256=OClbAeA8LpW-IiSiLT_TlDWkEqmIIvSk0GXq8SWI-GQ,3443
|
|
94
95
|
sqlmesh/core/model/seed.py,sha256=a0M-1zY1gOkN5ph2GQyataEdBSCtq50YjeFk2LyvInI,5058
|
|
95
96
|
sqlmesh/core/plan/__init__.py,sha256=aieod5xxcJJl0m7Xp9BXCwwrYnhMbeltGoNZGjwoVTA,521
|
|
96
97
|
sqlmesh/core/plan/builder.py,sha256=mZ64P0m4LDtkyy4NoPsz8gqhtezF8Piaw5aj0_YuTjA,38496
|
|
97
98
|
sqlmesh/core/plan/definition.py,sha256=pBjQu1P5U-XheWOe9WkSNAZeLzYE7LFgYtq6-4Fx67g,13236
|
|
98
99
|
sqlmesh/core/plan/evaluator.py,sha256=7dPtjxc4SuKf9fHsMDwaGsprlXmhcoKi0N6Q1N8eFY4,27009
|
|
99
|
-
sqlmesh/core/plan/explainer.py,sha256
|
|
100
|
+
sqlmesh/core/plan/explainer.py,sha256=LAeboOuHc-gug-9C34rHn9yrsyCyNTeCJHU95WftAxc,11238
|
|
100
101
|
sqlmesh/core/plan/stages.py,sha256=AIBzEji7OorFx9B0Hm50E6h8zWIGSuLkoVWqIhq9AVw,10911
|
|
101
102
|
sqlmesh/core/snapshot/__init__.py,sha256=Z5HTetNCZGoKF1WAKXk4LWL9dc8EC8lwS2O4TtoBmt4,1317
|
|
102
103
|
sqlmesh/core/snapshot/cache.py,sha256=bgqCR2hyf6r2A_8QP1EnXFK25gDX37-Zg0YeMuETWxg,3934
|
|
@@ -158,12 +159,12 @@ sqlmesh/integrations/github/cicd/command.py,sha256=rNWb-aMOA9R23YumD5vPwR0PgPGGj
|
|
|
158
159
|
sqlmesh/integrations/github/cicd/config.py,sha256=tRhMZaLf1p6AWXvi67n85Q-lptJNGt1Wk_Jr61ytpiM,2104
|
|
159
160
|
sqlmesh/integrations/github/cicd/controller.py,sha256=IgUWK9orYSMiElO3GaL6Ja-osFgA7je3RDcLChWUi5s,43769
|
|
160
161
|
sqlmesh/lsp/api.py,sha256=LJdECSaEZoPbu_Xl4KscVG-WLt7_g4dY-zKGO68dH14,2162
|
|
161
|
-
sqlmesh/lsp/completions.py,sha256=
|
|
162
|
-
sqlmesh/lsp/context.py,sha256=
|
|
162
|
+
sqlmesh/lsp/completions.py,sha256=Y6zNKNne91tzRm5vRk82JmhIpo7dIBUoK8GGQesGlT8,5746
|
|
163
|
+
sqlmesh/lsp/context.py,sha256=36bUTiMSyfX91bri_F1aivDvSllq0rKidIM_RGAtirs,6253
|
|
163
164
|
sqlmesh/lsp/custom.py,sha256=Mml6V1nYAHdP4Zt8gxly1ZLKYALtxGeAVzPrrYpc7m8,1426
|
|
164
165
|
sqlmesh/lsp/description.py,sha256=4QIGC9ix8IzmWF6DVUNhY4m1Crk3sNQA6hZ1RbyqU78,827
|
|
165
|
-
sqlmesh/lsp/main.py,sha256=
|
|
166
|
-
sqlmesh/lsp/reference.py,sha256
|
|
166
|
+
sqlmesh/lsp/main.py,sha256=fu274od-9qfStr1BEiC4lnxN9mPxSdEm69CIm5Nmxeo,32763
|
|
167
|
+
sqlmesh/lsp/reference.py,sha256=dyeacD_MHATGRIrl8Ax86nDK9EC_dh3TaaVNAepPce0,17902
|
|
167
168
|
sqlmesh/lsp/uri.py,sha256=mvSLbZGmMTHPpliThOtVWiFcHnfJzp1w39q9l-xD2SI,802
|
|
168
169
|
sqlmesh/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
169
170
|
sqlmesh/migrations/v0001_init.py,sha256=h8FwMYJj9_O8yGpk12guaSaGwX5A3we2GiKeHrXi15o,2048
|
|
@@ -272,7 +273,7 @@ sqlmesh/utils/pydantic.py,sha256=I898jAFqa3qU6lG2uPpRebVFncOUo8ZrLWM_R3K3DHU,116
|
|
|
272
273
|
sqlmesh/utils/rich.py,sha256=xNZyPCqEc4TAlJlsEthg-5eYUWpaizz0QjBzO7a1Yv4,1509
|
|
273
274
|
sqlmesh/utils/windows.py,sha256=r7Yin4WEwbvH7sKA3hOJtRsclmscNuBeCklCfVPvtQs,611
|
|
274
275
|
sqlmesh/utils/yaml.py,sha256=np-WVcwmwsKghCgAj1OBBMsK1SXSRn4wR-H37cGiFr8,2282
|
|
275
|
-
sqlmesh-0.189.
|
|
276
|
+
sqlmesh-0.189.2.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
|
|
276
277
|
web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
277
278
|
web/client/dist/index.html,sha256=LbR9XrOXE77otBPoDnaomssYz6h2TBwLANs6hUJ0p2M,5472
|
|
278
279
|
web/client/dist/assets/Audits-DB1HcP9M.js,sha256=eGI_Soj69Jntsixw1NqQpdwG94N5jipQZv7T1Y7WUSs,1041
|
|
@@ -390,8 +391,8 @@ web/server/api/endpoints/models.py,sha256=ud-lRwriyI_OYg9tZchrBgeiXELiRTLNEodraf
|
|
|
390
391
|
web/server/api/endpoints/modules.py,sha256=8hqqgonGay_mJmpCw0IdbjsPhWlQH2VLdKAqha-myac,468
|
|
391
392
|
web/server/api/endpoints/plan.py,sha256=OYuQ_4dgbhG9ibTcX1a8mvYgH3ANxw9Fhbb4wg6QWqc,9222
|
|
392
393
|
web/server/api/endpoints/table_diff.py,sha256=SCy3-pr5x6bEEiOaZGCDbiHQhpgxwmnz3Acqa2Ie6jI,2090
|
|
393
|
-
sqlmesh-0.189.
|
|
394
|
-
sqlmesh-0.189.
|
|
395
|
-
sqlmesh-0.189.
|
|
396
|
-
sqlmesh-0.189.
|
|
397
|
-
sqlmesh-0.189.
|
|
394
|
+
sqlmesh-0.189.2.dist-info/METADATA,sha256=ToEJw-gXy6awCbUCrU2v5583s5hO6zLynCm4DiLNFuk,26535
|
|
395
|
+
sqlmesh-0.189.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
396
|
+
sqlmesh-0.189.2.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
|
|
397
|
+
sqlmesh-0.189.2.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
|
|
398
|
+
sqlmesh-0.189.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|