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 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.1.dev2'
21
- __version_tuple__ = version_tuple = (0, 189, 1, 'dev2')
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)
@@ -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(self, violation_msg: t.Optional[str] = None) -> RuleViolation:
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(rule=self, violation_msg=violation_msg or self.summary)
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__(self, rule: Rule, violation_msg: str) -> None:
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.rule import Rule, RuleViolation
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
- return self.violation() if model.query.is_star else None
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):
@@ -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
- isinstance(query_label, exp.Array)
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 (isinstance(authorization, exp.Literal) and authorization.is_string):
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
  )
@@ -195,7 +195,7 @@ class RichExplainerConsole(ExplainerConsole):
195
195
 
196
196
  tree.add(model_tree)
197
197
  else:
198
- tree.add(f"{display_name} \[standalone audit]")
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:
@@ -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=list(get_keywords(context, file_uri)),
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 RenderModelEntry, ModelForRendering
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(self, uri: t.Optional[URI]) -> AllModelsResponse:
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
- with open(diagnostic.model._path, "r", encoding="utf-8") as file:
622
- lines = file.readlines()
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=types.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
- target_range = _range_from_token_position_details(
169
- TokenPositionDetails.from_meta(identifier.meta), read_file
170
- )
171
- table_range = _range_from_token_position_details(
172
- TokenPositionDetails.from_meta(table.this.meta), read_file
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
- table_range = _range_from_token_position_details(table_meta, read_file)
216
- start_pos = table_range.start
217
- end_pos = table_range.end
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
- catalog_or_db_range = _range_from_token_position_details(
224
- catalog_or_db_meta, read_file
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(start=start_pos, end=end_pos),
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
- token_details = TokenPositionDetails.from_meta(node.meta)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlmesh
3
- Version: 0.189.1.dev2
3
+ Version: 0.189.2
4
4
  Summary: Next-generation data transformation framework
5
5
  Author-email: "TobikoData Inc." <engineering@tobikodata.com>
6
6
  License: Apache License
@@ -1,5 +1,5 @@
1
1
  sqlmesh/__init__.py,sha256=QjwaamJIdW7voXoS880yAx7tHm-Im4_mqGA-XJgafos,7631
2
- sqlmesh/_version.py,sha256=WRGFpactk3aY9TjStKBfERG2oCJQD2qAYC0_jGZurpQ,528
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=lU2zUFuD11qVxrGXX82oUSLMkUZAs-1lWk1XdJFofXo,50234
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=AefxwlgE-lw9OgzWa0lHsyL7WTqRMBZwymtNakEZSOo,15567
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=UL05OjFBfoLSYGWXNf4zjZYzIKr-OyFIMIcIh7RhR8s,5237
80
- sqlmesh/core/linter/rule.py,sha256=p8rbMbf2Z18ZJ6ZcZyoCk-3Lvo7d_Gvc35tiwWXvuQs,2579
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=RXns3e042q8aM-3wDUNDc7dcX5hDiOO7qel1YnjeqUk,1950
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=TRsCztBea2gSknPQxoY1ct13hdWgD0mM9XTHqu5KDgs,21167
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=-12__YFHEeKfvcggquZVMFq-Q3DBIy-kMvdyU5j8K5U,11237
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=Ufe5XyqmT8ttXqQDbhH2RtuGC0cVuVfSnGqDMl6KmB4,3601
162
- sqlmesh/lsp/context.py,sha256=ozkrw-897_Scbbn4AnKdeTQhrUb0CI0e34vcV-5U2DA,6158
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=Y1ISd75Ax09cNkSHw-Z0w_56IQXmmGxoGDWFT6KLs6g,30564
166
- sqlmesh/lsp/reference.py,sha256=-HkMMzRzq4ike5mSkoWajanYNMAwhQhlPaqw62451ac,16887
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.1.dev2.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
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.1.dev2.dist-info/METADATA,sha256=PZbnj7sOVFzEyv2j9NphOliFN1hh6NWDby5QO-XoM8A,26540
394
- sqlmesh-0.189.1.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
395
- sqlmesh-0.189.1.dev2.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
396
- sqlmesh-0.189.1.dev2.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
397
- sqlmesh-0.189.1.dev2.dist-info/RECORD,,
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,,