sqlmesh 0.189.2.dev4__py3-none-any.whl → 0.189.3.dev1__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/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/lsp/context.py +1 -1
- sqlmesh/lsp/hints.py +136 -0
- sqlmesh/lsp/main.py +62 -9
- sqlmesh/lsp/reference.py +125 -93
- {sqlmesh-0.189.2.dev4.dist-info → sqlmesh-0.189.3.dev1.dist-info}/METADATA +1 -1
- {sqlmesh-0.189.2.dev4.dist-info → sqlmesh-0.189.3.dev1.dist-info}/RECORD +15 -13
- {sqlmesh-0.189.2.dev4.dist-info → sqlmesh-0.189.3.dev1.dist-info}/WHEEL +0 -0
- {sqlmesh-0.189.2.dev4.dist-info → sqlmesh-0.189.3.dev1.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.189.2.dev4.dist-info → sqlmesh-0.189.3.dev1.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.189.2.dev4.dist-info → sqlmesh-0.189.3.dev1.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.3.dev1'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 189, 3, 'dev1')
|
|
@@ -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/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
|
|
sqlmesh/lsp/hints.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Type hinting on SQLMesh models"""
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from lsprotocol import types
|
|
6
|
+
|
|
7
|
+
from sqlglot import exp
|
|
8
|
+
from sqlglot.expressions import Expression
|
|
9
|
+
from sqlglot.optimizer.normalize_identifiers import normalize_identifiers
|
|
10
|
+
from sqlmesh.core.model.definition import SqlModel
|
|
11
|
+
from sqlmesh.lsp.context import LSPContext, ModelTarget
|
|
12
|
+
from sqlmesh.lsp.uri import URI
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_hints(
|
|
16
|
+
lsp_context: LSPContext,
|
|
17
|
+
document_uri: URI,
|
|
18
|
+
start_line: int,
|
|
19
|
+
end_line: int,
|
|
20
|
+
) -> t.List[types.InlayHint]:
|
|
21
|
+
"""
|
|
22
|
+
Get type hints for certain lines in a document
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
lint_context: The LSP context
|
|
26
|
+
document_uri: The URI of the document
|
|
27
|
+
start_line: the starting line to get hints for
|
|
28
|
+
end_line: the ending line to get hints for
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
A list of hints to apply to the document
|
|
32
|
+
"""
|
|
33
|
+
path = document_uri.to_path()
|
|
34
|
+
if path.suffix != ".sql":
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
if path not in lsp_context.map:
|
|
38
|
+
return []
|
|
39
|
+
|
|
40
|
+
file_info = lsp_context.map[path]
|
|
41
|
+
|
|
42
|
+
# Process based on whether it's a model or standalone audit
|
|
43
|
+
if not isinstance(file_info, ModelTarget):
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
# It's a model
|
|
47
|
+
model = lsp_context.context.get_model(
|
|
48
|
+
model_or_snapshot=file_info.names[0], raise_if_missing=False
|
|
49
|
+
)
|
|
50
|
+
if not isinstance(model, SqlModel):
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
query = model.query
|
|
54
|
+
dialect = model.dialect
|
|
55
|
+
columns_to_types = model.columns_to_types or {}
|
|
56
|
+
|
|
57
|
+
return _get_type_hints_for_model_from_query(
|
|
58
|
+
query, dialect, columns_to_types, start_line, end_line
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get_type_hints_for_select(
|
|
63
|
+
expression: exp.Expression,
|
|
64
|
+
dialect: str,
|
|
65
|
+
columns_to_types: t.Dict[str, exp.DataType],
|
|
66
|
+
start_line: int,
|
|
67
|
+
end_line: int,
|
|
68
|
+
) -> t.List[types.InlayHint]:
|
|
69
|
+
hints: t.List[types.InlayHint] = []
|
|
70
|
+
|
|
71
|
+
for select_exp in expression.expressions:
|
|
72
|
+
if isinstance(select_exp, exp.Alias):
|
|
73
|
+
if isinstance(select_exp.this, exp.Cast):
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
meta = select_exp.args["alias"].meta
|
|
77
|
+
|
|
78
|
+
elif isinstance(select_exp, exp.Column):
|
|
79
|
+
meta = select_exp.parts[-1].meta
|
|
80
|
+
else:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
if "line" not in meta or "col" not in meta:
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
line = meta["line"]
|
|
87
|
+
col = meta["col"]
|
|
88
|
+
|
|
89
|
+
# Lines from sqlglot are 1 based
|
|
90
|
+
line -= 1
|
|
91
|
+
|
|
92
|
+
if line < start_line or line > end_line:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
name = select_exp.alias_or_name
|
|
96
|
+
data_type = columns_to_types.get(name)
|
|
97
|
+
|
|
98
|
+
if not data_type or data_type.is_type(exp.DataType.Type.UNKNOWN):
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
type_label = data_type.sql(dialect)
|
|
102
|
+
hints.append(
|
|
103
|
+
types.InlayHint(
|
|
104
|
+
label=f"::{type_label}",
|
|
105
|
+
kind=types.InlayHintKind.Type,
|
|
106
|
+
padding_left=False,
|
|
107
|
+
padding_right=True,
|
|
108
|
+
position=types.Position(line=line, character=col),
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return hints
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _get_type_hints_for_model_from_query(
|
|
116
|
+
query: Expression,
|
|
117
|
+
dialect: str,
|
|
118
|
+
columns_to_types: t.Dict[str, exp.DataType],
|
|
119
|
+
start_line: int,
|
|
120
|
+
end_line: int,
|
|
121
|
+
) -> t.List[types.InlayHint]:
|
|
122
|
+
hints: t.List[types.InlayHint] = []
|
|
123
|
+
try:
|
|
124
|
+
query = normalize_identifiers(query.copy(), dialect=dialect)
|
|
125
|
+
|
|
126
|
+
# Return the hints for top level selects (model definition columns only)
|
|
127
|
+
return [
|
|
128
|
+
hint
|
|
129
|
+
for q in query.walk(prune=lambda n: not isinstance(n, exp.SetOperation))
|
|
130
|
+
if isinstance(select := q.unnest(), exp.Select)
|
|
131
|
+
for hint in _get_type_hints_for_select(
|
|
132
|
+
q, dialect, columns_to_types, start_line, end_line
|
|
133
|
+
)
|
|
134
|
+
]
|
|
135
|
+
except Exception:
|
|
136
|
+
return []
|
sqlmesh/lsp/main.py
CHANGED
|
@@ -36,9 +36,8 @@ from sqlmesh.lsp.custom import (
|
|
|
36
36
|
RenderModelRequest,
|
|
37
37
|
RenderModelResponse,
|
|
38
38
|
)
|
|
39
|
-
from sqlmesh.lsp.
|
|
40
|
-
|
|
41
|
-
)
|
|
39
|
+
from sqlmesh.lsp.hints import get_hints
|
|
40
|
+
from sqlmesh.lsp.reference import get_references, get_cte_references
|
|
42
41
|
from sqlmesh.lsp.uri import URI
|
|
43
42
|
from web.server.api.endpoints.lineage import column_lineage, model_lineage
|
|
44
43
|
from web.server.api.endpoints.models import get_models
|
|
@@ -336,6 +335,25 @@ class SQLMeshLanguageServer:
|
|
|
336
335
|
)
|
|
337
336
|
return None
|
|
338
337
|
|
|
338
|
+
@self.server.feature(types.TEXT_DOCUMENT_INLAY_HINT)
|
|
339
|
+
def inlay_hint(
|
|
340
|
+
ls: LanguageServer, params: types.InlayHintParams
|
|
341
|
+
) -> t.List[types.InlayHint]:
|
|
342
|
+
"""Implement type hints for sql columns as inlay hints"""
|
|
343
|
+
try:
|
|
344
|
+
uri = URI(params.text_document.uri)
|
|
345
|
+
self._ensure_context_for_document(uri)
|
|
346
|
+
if self.lsp_context is None:
|
|
347
|
+
raise RuntimeError(f"No context found for document: {uri}")
|
|
348
|
+
|
|
349
|
+
start_line = params.range.start.line
|
|
350
|
+
end_line = params.range.end.line
|
|
351
|
+
hints = get_hints(self.lsp_context, uri, start_line, end_line)
|
|
352
|
+
return hints
|
|
353
|
+
|
|
354
|
+
except Exception as e:
|
|
355
|
+
return []
|
|
356
|
+
|
|
339
357
|
@self.server.feature(types.TEXT_DOCUMENT_DEFINITION)
|
|
340
358
|
def goto_definition(
|
|
341
359
|
ls: LanguageServer, params: types.DefinitionParams
|
|
@@ -378,6 +396,28 @@ class SQLMeshLanguageServer:
|
|
|
378
396
|
ls.show_message(f"Error getting references: {e}", types.MessageType.Error)
|
|
379
397
|
return []
|
|
380
398
|
|
|
399
|
+
@self.server.feature(types.TEXT_DOCUMENT_REFERENCES)
|
|
400
|
+
def find_references(
|
|
401
|
+
ls: LanguageServer, params: types.ReferenceParams
|
|
402
|
+
) -> t.Optional[t.List[types.Location]]:
|
|
403
|
+
"""Find all references of a symbol (currently supporting CTEs)"""
|
|
404
|
+
try:
|
|
405
|
+
uri = URI(params.text_document.uri)
|
|
406
|
+
self._ensure_context_for_document(uri)
|
|
407
|
+
document = ls.workspace.get_text_document(params.text_document.uri)
|
|
408
|
+
if self.lsp_context is None:
|
|
409
|
+
raise RuntimeError(f"No context found for document: {document.path}")
|
|
410
|
+
|
|
411
|
+
cte_references = get_cte_references(self.lsp_context, uri, params.position)
|
|
412
|
+
|
|
413
|
+
# Convert references to Location objects
|
|
414
|
+
locations = [types.Location(uri=ref.uri, range=ref.range) for ref in cte_references]
|
|
415
|
+
|
|
416
|
+
return locations if locations else None
|
|
417
|
+
except Exception as e:
|
|
418
|
+
ls.show_message(f"Error getting locations: {e}", types.MessageType.Error)
|
|
419
|
+
return None
|
|
420
|
+
|
|
381
421
|
@self.server.feature(types.TEXT_DOCUMENT_DIAGNOSTIC)
|
|
382
422
|
def diagnostic(
|
|
383
423
|
ls: LanguageServer, params: types.DocumentDiagnosticParams
|
|
@@ -635,8 +675,24 @@ class SQLMeshLanguageServer:
|
|
|
635
675
|
) -> t.Optional[types.Diagnostic]:
|
|
636
676
|
if diagnostic.model._path is None:
|
|
637
677
|
return None
|
|
638
|
-
|
|
639
|
-
|
|
678
|
+
if not diagnostic.violation_range:
|
|
679
|
+
with open(diagnostic.model._path, "r", encoding="utf-8") as file:
|
|
680
|
+
lines = file.readlines()
|
|
681
|
+
range = types.Range(
|
|
682
|
+
start=types.Position(line=0, character=0),
|
|
683
|
+
end=types.Position(line=len(lines) - 1, character=len(lines[-1])),
|
|
684
|
+
)
|
|
685
|
+
else:
|
|
686
|
+
range = types.Range(
|
|
687
|
+
start=types.Position(
|
|
688
|
+
line=diagnostic.violation_range.start.line,
|
|
689
|
+
character=diagnostic.violation_range.start.character,
|
|
690
|
+
),
|
|
691
|
+
end=types.Position(
|
|
692
|
+
line=diagnostic.violation_range.end.line,
|
|
693
|
+
character=diagnostic.violation_range.end.character,
|
|
694
|
+
),
|
|
695
|
+
)
|
|
640
696
|
|
|
641
697
|
# Get rule definition location for diagnostics link
|
|
642
698
|
rule_location = diagnostic.rule.get_definition_location()
|
|
@@ -645,10 +701,7 @@ class SQLMeshLanguageServer:
|
|
|
645
701
|
|
|
646
702
|
# Use URI format to create a link for "related information"
|
|
647
703
|
return types.Diagnostic(
|
|
648
|
-
range=
|
|
649
|
-
start=types.Position(line=0, character=0),
|
|
650
|
-
end=types.Position(line=len(lines), character=len(lines[-1])),
|
|
651
|
-
),
|
|
704
|
+
range=range,
|
|
652
705
|
message=diagnostic.violation_msg,
|
|
653
706
|
severity=types.DiagnosticSeverity.Error
|
|
654
707
|
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=46P0A4hbz6tPxXHU78sPmL24cECOw9bp_3B8jB9kTr8,528
|
|
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
|
|
@@ -76,10 +76,11 @@ sqlmesh/core/engine_adapter/snowflake.py,sha256=oaZxKPmPXxmr46NQfw6OI5vPwvaYJ3Ym
|
|
|
76
76
|
sqlmesh/core/engine_adapter/spark.py,sha256=sYkVOPS0X43UPbI0z7Hh_EpKlI36qkOcr4NNFOTQlLs,21045
|
|
77
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
|
|
@@ -159,11 +160,12 @@ sqlmesh/integrations/github/cicd/config.py,sha256=tRhMZaLf1p6AWXvi67n85Q-lptJNGt
|
|
|
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
162
|
sqlmesh/lsp/completions.py,sha256=Y6zNKNne91tzRm5vRk82JmhIpo7dIBUoK8GGQesGlT8,5746
|
|
162
|
-
sqlmesh/lsp/context.py,sha256=
|
|
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/
|
|
166
|
-
sqlmesh/lsp/
|
|
166
|
+
sqlmesh/lsp/hints.py,sha256=j-blccBUil94yRoDjWfkSM0HqMmsKD6hP2TaWdWWVk4,3734
|
|
167
|
+
sqlmesh/lsp/main.py,sha256=c1h1hWluBixEFZEX2jiBjA_EOgguruaG9cSLjCrEPr8,33581
|
|
168
|
+
sqlmesh/lsp/reference.py,sha256=dyeacD_MHATGRIrl8Ax86nDK9EC_dh3TaaVNAepPce0,17902
|
|
167
169
|
sqlmesh/lsp/uri.py,sha256=mvSLbZGmMTHPpliThOtVWiFcHnfJzp1w39q9l-xD2SI,802
|
|
168
170
|
sqlmesh/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
169
171
|
sqlmesh/migrations/v0001_init.py,sha256=h8FwMYJj9_O8yGpk12guaSaGwX5A3we2GiKeHrXi15o,2048
|
|
@@ -272,7 +274,7 @@ sqlmesh/utils/pydantic.py,sha256=I898jAFqa3qU6lG2uPpRebVFncOUo8ZrLWM_R3K3DHU,116
|
|
|
272
274
|
sqlmesh/utils/rich.py,sha256=xNZyPCqEc4TAlJlsEthg-5eYUWpaizz0QjBzO7a1Yv4,1509
|
|
273
275
|
sqlmesh/utils/windows.py,sha256=r7Yin4WEwbvH7sKA3hOJtRsclmscNuBeCklCfVPvtQs,611
|
|
274
276
|
sqlmesh/utils/yaml.py,sha256=np-WVcwmwsKghCgAj1OBBMsK1SXSRn4wR-H37cGiFr8,2282
|
|
275
|
-
sqlmesh-0.189.
|
|
277
|
+
sqlmesh-0.189.3.dev1.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
|
|
276
278
|
web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
277
279
|
web/client/dist/index.html,sha256=LbR9XrOXE77otBPoDnaomssYz6h2TBwLANs6hUJ0p2M,5472
|
|
278
280
|
web/client/dist/assets/Audits-DB1HcP9M.js,sha256=eGI_Soj69Jntsixw1NqQpdwG94N5jipQZv7T1Y7WUSs,1041
|
|
@@ -390,8 +392,8 @@ web/server/api/endpoints/models.py,sha256=ud-lRwriyI_OYg9tZchrBgeiXELiRTLNEodraf
|
|
|
390
392
|
web/server/api/endpoints/modules.py,sha256=8hqqgonGay_mJmpCw0IdbjsPhWlQH2VLdKAqha-myac,468
|
|
391
393
|
web/server/api/endpoints/plan.py,sha256=OYuQ_4dgbhG9ibTcX1a8mvYgH3ANxw9Fhbb4wg6QWqc,9222
|
|
392
394
|
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.
|
|
395
|
+
sqlmesh-0.189.3.dev1.dist-info/METADATA,sha256=S9cWZvOMCb4TkPpsuwJOjHs_Bcbiddm0657q1eWV8jE,26540
|
|
396
|
+
sqlmesh-0.189.3.dev1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
397
|
+
sqlmesh-0.189.3.dev1.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
|
|
398
|
+
sqlmesh-0.189.3.dev1.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
|
|
399
|
+
sqlmesh-0.189.3.dev1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|