Skip to content

Commit 195bcde

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 8d361ce + 6724d96 commit 195bcde

File tree

5 files changed

+161
-69
lines changed

5 files changed

+161
-69
lines changed

sqlmesh/core/linter/helpers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,55 @@ def get_range_of_model_block(
158158
return Range(
159159
start=start_position.to_range(splitlines).start, end=end_position.to_range(splitlines).end
160160
)
161+
162+
163+
def get_range_of_a_key_in_model_block(
164+
sql: str,
165+
dialect: str,
166+
key: str,
167+
) -> t.Optional[Range]:
168+
"""
169+
Get the range of a specific key in the model block of an SQL file.
170+
"""
171+
tokens = tokenize(sql, dialect=dialect)
172+
if tokens is None:
173+
return None
174+
175+
# Find the start of the model block
176+
start_index = next(
177+
(
178+
i
179+
for i, t in enumerate(tokens)
180+
if t.token_type is TokenType.VAR and t.text.upper() == "MODEL"
181+
),
182+
None,
183+
)
184+
end_index = next(
185+
(i for i, t in enumerate(tokens) if t.token_type is TokenType.SEMICOLON),
186+
None,
187+
)
188+
if start_index is None or end_index is None:
189+
return None
190+
if start_index >= end_index:
191+
return None
192+
193+
tokens_of_interest = tokens[start_index + 1 : end_index]
194+
# Find the key token
195+
key_token = next(
196+
(
197+
t
198+
for t in tokens_of_interest
199+
if t.token_type is TokenType.VAR and t.text.upper() == key.upper()
200+
),
201+
None,
202+
)
203+
if key_token is None:
204+
return None
205+
206+
position = TokenPositionDetails(
207+
line=key_token.line,
208+
col=key_token.col,
209+
start=key_token.start,
210+
end=key_token.end,
211+
)
212+
return position.to_range(sql.splitlines())

sqlmesh/core/renderer.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -523,8 +523,6 @@ def render(
523523
runtime_stage, start, end, execution_time, *kwargs.values()
524524
)
525525

526-
needs_optimization = needs_optimization and self._optimize_query_flag
527-
528526
if should_cache and self._optimized_cache:
529527
query = self._optimized_cache
530528
else:
@@ -560,7 +558,7 @@ def render(
560558
)
561559
raise
562560

563-
if needs_optimization:
561+
if needs_optimization and self._optimize_query_flag:
564562
deps = d.find_tables(
565563
query, default_catalog=self._default_catalog, dialect=self._dialect
566564
)

tests/core/linter/test_helpers.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from sqlmesh import Context
2-
from sqlmesh.core.linter.helpers import read_range_from_file, get_range_of_model_block
2+
from sqlmesh.core.linter.helpers import (
3+
read_range_from_file,
4+
get_range_of_model_block,
5+
get_range_of_a_key_in_model_block,
6+
)
37
from sqlmesh.core.model import SqlModel
48

59

@@ -34,3 +38,41 @@ def test_get_position_of_model_block():
3438
read_range = read_range_from_file(path, range)
3539
assert read_range.startswith("MODEL")
3640
assert read_range.endswith(";")
41+
42+
43+
def test_get_range_of_a_key_in_model_block_testing_on_sushi():
44+
context = Context(paths=["examples/sushi"])
45+
46+
sql_models = [
47+
model
48+
for model in context.models.values()
49+
if isinstance(model, SqlModel)
50+
and model._path is not None
51+
and str(model._path).endswith(".sql")
52+
]
53+
assert len(sql_models) > 0
54+
55+
for model in sql_models:
56+
possible_keys = ["name", "tags", "description", "columns", "owner", "cron", "dialect"]
57+
58+
dialect = model.dialect
59+
assert dialect is not None
60+
61+
path = model._path
62+
assert path is not None
63+
64+
with open(path, "r", encoding="utf-8") as file:
65+
content = file.read()
66+
67+
count_properties_checked = 0
68+
69+
for key in possible_keys:
70+
range = get_range_of_a_key_in_model_block(content, dialect, key)
71+
72+
# Check that the range starts with the key and ends with ;
73+
if range:
74+
read_range = read_range_from_file(path, range)
75+
assert read_range.lower() == key.lower()
76+
count_properties_checked += 1
77+
78+
assert count_properties_checked > 0

tests/core/test_model.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11061,3 +11061,38 @@ def entrypoint(context, **kwargs):
1106111061

1106211062
assert model_daily is not None
1106311063
assert model_daily.cron == "@daily"
11064+
11065+
11066+
def test_render_query_optimize_query_false(assert_exp_eq, sushi_context):
11067+
snapshots = sushi_context.snapshots
11068+
11069+
model = sushi_context.get_model("sushi.top_waiters")
11070+
model = model.copy(update={"optimize_query": False})
11071+
11072+
upstream_model_version = sushi_context.get_snapshot("sushi.waiter_revenue_by_day").version
11073+
11074+
assert_exp_eq(
11075+
model.render_query(snapshots=snapshots).sql(),
11076+
f"""
11077+
WITH "test_macros" AS (
11078+
SELECT
11079+
2 AS "lit_two",
11080+
"revenue" * 2.0 AS "sql_exp",
11081+
CAST("revenue" AS TEXT) AS "sql_lit"
11082+
FROM "memory"."sqlmesh__sushi"."sushi__waiter_revenue_by_day__{upstream_model_version}" AS "waiter_revenue_by_day" /* memory.sushi.waiter_revenue_by_day */
11083+
)
11084+
SELECT
11085+
CAST("waiter_id" AS INT) AS "waiter_id",
11086+
CAST("revenue" AS DOUBLE) AS "revenue"
11087+
FROM "memory"."sqlmesh__sushi"."sushi__waiter_revenue_by_day__{upstream_model_version}" AS "waiter_revenue_by_day" /* memory.sushi.waiter_revenue_by_day */
11088+
WHERE
11089+
"event_date" = (
11090+
SELECT
11091+
MAX("event_date")
11092+
FROM "memory"."sqlmesh__sushi"."sushi__waiter_revenue_by_day__{upstream_model_version}" AS "waiter_revenue_by_day" /* memory.sushi.waiter_revenue_by_day */
11093+
)
11094+
ORDER BY
11095+
"revenue" DESC
11096+
LIMIT 10
11097+
""",
11098+
)

vscode/extension/tests/lineage.spec.ts

Lines changed: 30 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ import {
1919
* Helper function to launch VS Code and test lineage with given project path config
2020
*/
2121
async function testLineageWithProjectPath(page: Page): Promise<void> {
22-
await page.waitForLoadState('networkidle')
23-
await page.waitForLoadState('domcontentloaded')
2422
await openLineageView(page)
2523
await waitForLoadedSQLMesh(page)
2624
}
@@ -37,120 +35,87 @@ test('Lineage panel renders correctly - no project path config (default)', async
3735
await testLineageWithProjectPath(page)
3836
})
3937

40-
test.skip('Lineage panel renders correctly - relative project path', async ({
38+
test('Lineage panel renders correctly - relative project path', async ({
4139
page,
4240
sharedCodeServer,
41+
tempDir,
4342
}) => {
44-
const workspaceDir = await fs.mkdtemp(
45-
path.join(os.tmpdir(), 'vscode-test-workspace-'),
46-
)
47-
const projectDir = path.join(workspaceDir, 'projects', 'sushi')
43+
const projectDir = path.join(tempDir, 'projects', 'sushi')
4844
await fs.copy(SUSHI_SOURCE_PATH, projectDir)
4945

50-
const context = await startCodeServer({
51-
tempDir: workspaceDir,
52-
})
53-
5446
const settings = {
5547
'sqlmesh.projectPath': './projects/sushi',
56-
'python.defaultInterpreterPath': context.defaultPythonInterpreter,
48+
'python.defaultInterpreterPath': sharedCodeServer.defaultPythonInterpreter,
5749
}
58-
await fs.ensureDir(path.join(workspaceDir, '.vscode'))
59-
await fs.writeJson(
60-
path.join(workspaceDir, '.vscode', 'settings.json'),
61-
settings,
62-
{ spaces: 2 },
63-
)
50+
await fs.ensureDir(path.join(tempDir, '.vscode'))
51+
await fs.writeJson(path.join(tempDir, '.vscode', 'settings.json'), settings, {
52+
spaces: 2,
53+
})
6454

6555
try {
66-
await openServerPage(page, workspaceDir, sharedCodeServer)
56+
await openServerPage(page, tempDir, sharedCodeServer)
6757
await testLineageWithProjectPath(page)
6858
} finally {
69-
await fs.remove(workspaceDir)
59+
await fs.remove(tempDir)
7060
}
7161
})
7262

73-
test.skip('Lineage panel renders correctly - absolute project path', async ({
63+
test('Lineage panel renders correctly - absolute project path', async ({
7464
page,
7565
tempDir,
7666
sharedCodeServer,
7767
}) => {
78-
const workspaceDir = await fs.mkdtemp(
79-
path.join(os.tmpdir(), 'vscode-test-workspace-'),
80-
)
81-
const projectDir = path.join(workspaceDir, 'projects', 'sushi')
82-
await fs.ensureDir(path.join(workspaceDir, '.vscode'))
68+
// Copy the sushi project to temporary directory
69+
const projectDir = path.join(tempDir, 'projects', 'sushi')
70+
await fs.ensureDir(path.join(tempDir, '.vscode'))
8371
await fs.copy(SUSHI_SOURCE_PATH, projectDir)
84-
await fs.ensureDir(path.join(workspaceDir, '.vscode'))
85-
const context = await startCodeServer({
86-
tempDir: workspaceDir,
87-
})
8872

8973
const settings = {
9074
'sqlmesh.projectPath': projectDir,
91-
'python.defaultInterpreterPath': context.defaultPythonInterpreter,
75+
'python.defaultInterpreterPath': sharedCodeServer.defaultPythonInterpreter,
9276
}
93-
await fs.writeJson(
94-
path.join(workspaceDir, '.vscode', 'settings.json'),
95-
settings,
96-
{ spaces: 2 },
97-
)
77+
await fs.writeJson(path.join(tempDir, '.vscode', 'settings.json'), settings, {
78+
spaces: 2,
79+
})
9880

99-
try {
100-
await openServerPage(page, tempDir, sharedCodeServer)
101-
await testLineageWithProjectPath(page)
102-
} finally {
103-
await stopCodeServer(context)
104-
}
81+
await openServerPage(page, tempDir, sharedCodeServer)
82+
await testLineageWithProjectPath(page)
10583
})
10684

107-
test.skip('Lineage panel renders correctly - relative project outside of workspace', async ({
85+
test('Lineage panel renders correctly - relative project outside of workspace', async ({
10886
page,
10987
sharedCodeServer,
11088
tempDir,
11189
}) => {
112-
const tempFolder = await fs.mkdtemp(
113-
path.join(os.tmpdir(), 'vscode-test-workspace-'),
114-
)
115-
const projectDir = path.join(tempFolder, 'projects', 'sushi')
90+
const projectDir = path.join(tempDir, 'projects', 'sushi')
11691
await fs.copy(SUSHI_SOURCE_PATH, projectDir)
11792

118-
const workspaceDir = path.join(tempFolder, 'workspace')
93+
const workspaceDir = path.join(tempDir, 'workspace')
11994
await fs.ensureDir(workspaceDir)
120-
const context = await startCodeServer({
121-
tempDir: workspaceDir,
122-
})
12395

12496
const settings = {
12597
'sqlmesh.projectPath': './../projects/sushi',
126-
'python.defaultInterpreterPath': context.defaultPythonInterpreter,
98+
'python.defaultInterpreterPath': sharedCodeServer.defaultPythonInterpreter,
12799
}
128100
await fs.ensureDir(path.join(workspaceDir, '.vscode'))
129101
await fs.writeJson(
130102
path.join(workspaceDir, '.vscode', 'settings.json'),
131103
settings,
132104
{ spaces: 2 },
133105
)
134-
135-
try {
136-
await openServerPage(page, tempDir, sharedCodeServer)
137-
await testLineageWithProjectPath(page)
138-
} finally {
139-
await stopCodeServer(context)
140-
}
106+
await openServerPage(page, workspaceDir, sharedCodeServer)
107+
await testLineageWithProjectPath(page)
141108
})
142109

143-
test.skip('Lineage panel renders correctly - absolute path project outside of workspace', async ({
110+
test('Lineage panel renders correctly - absolute path project outside of workspace', async ({
144111
page,
145112
sharedCodeServer,
113+
tempDir,
146114
}) => {
147-
const tempFolder = await fs.mkdtemp(
148-
path.join(os.tmpdir(), 'vscode-test-workspace-'),
149-
)
150-
const projectDir = path.join(tempFolder, 'projects', 'sushi')
115+
const projectDir = path.join(tempDir, 'projects', 'sushi')
151116
await fs.copy(SUSHI_SOURCE_PATH, projectDir)
152117

153-
const workspaceDir = path.join(tempFolder, 'workspace')
118+
const workspaceDir = path.join(tempDir, 'workspace')
154119
await fs.ensureDir(workspaceDir)
155120

156121
const settings = {

0 commit comments

Comments
 (0)