Skip to content

Commit 1c6b2f9

Browse files
authored
Fix!: show proper pip install command when sqlmesh schema version is behind (#1638)
* Fix!: show proper pip install command when sqlmesh schema version is behind * Formatting * PR feedback
1 parent ad352e5 commit 1c6b2f9

4 files changed

Lines changed: 91 additions & 14 deletions

File tree

sqlmesh/core/state_sync/base.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Versions(PydanticModel):
3737

3838
schema_version: int
3939
sqlglot_version: str
40+
sqlmesh_version: str
4041

4142
@property
4243
def minor_sqlglot_version(self) -> t.Tuple[int, int]:
@@ -158,20 +159,43 @@ def get_versions(self, validate: bool = True) -> Versions:
158159
"""
159160
versions = self._get_versions()
160161

161-
def raise_error(lib: str, local: str | int, remote: str | int, ahead: bool = False) -> None:
162+
def raise_error(
163+
lib: str,
164+
local: str | int,
165+
remote: str | int,
166+
remote_package_version: t.Optional[str] = None,
167+
ahead: bool = False,
168+
) -> None:
162169
if ahead:
163170
raise SQLMeshError(
164-
f"{lib} (local) is using version '{local}' which is ahead of '{remote}' (remote). Please run a migration ('sqlmesh migrate' command)."
171+
f"{lib} (local) is using version '{local}' which is ahead of '{remote}' (remote). "
172+
"Please run a migration ('sqlmesh migrate' command)."
165173
)
174+
175+
if remote_package_version:
176+
upgrade_suggestion = f" Please upgrade {lib} ('pip install --upgrade \"{lib.lower()}=={remote_package_version}\"' command)."
177+
else:
178+
upgrade_suggestion = ""
179+
166180
raise SQLMeshError(
167-
f"{lib} (local) is using version '{local}' which is behind '{remote}' (remote). Please upgrade {lib} ('pip install --upgrade \"{lib.lower()}=={remote}\"' command)."
181+
f"{lib} (local) is using version '{local}' which is behind '{remote}' (remote).{upgrade_suggestion}"
168182
)
169183

170184
if SCHEMA_VERSION < versions.schema_version:
171-
raise_error("SQLMesh", SCHEMA_VERSION, versions.schema_version)
185+
raise_error(
186+
"SQLMesh",
187+
SCHEMA_VERSION,
188+
versions.schema_version,
189+
remote_package_version=versions.sqlmesh_version,
190+
)
172191

173192
if major_minor(SQLGLOT_VERSION) < major_minor(versions.sqlglot_version):
174-
raise_error("SQLGlot", SQLGLOT_VERSION, versions.sqlglot_version)
193+
raise_error(
194+
"SQLGlot",
195+
SQLGLOT_VERSION,
196+
versions.sqlglot_version,
197+
remote_package_version=versions.sqlglot_version,
198+
)
175199

176200
if validate:
177201
if SCHEMA_VERSION > versions.schema_version:

sqlmesh/core/state_sync/engine_adapter.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@
5959

6060
logger = logging.getLogger(__name__)
6161

62+
try:
63+
# We can't import directly from the root package due to circular dependency
64+
from sqlmesh._version import __version__ as SQLMESH_VERSION # type: ignore
65+
except ImportError:
66+
logger.error(
67+
'Unable to set __version__, run "pip install -e ." or "python setup.py develop" first.'
68+
)
69+
6270

6371
class EngineAdapterStateSync(CommonStateSyncMixin, StateSync):
6472
"""Manages state of nodes and snapshot with an existing engine adapter.
@@ -133,6 +141,7 @@ def __init__(
133141
self._version_columns_to_types = {
134142
"schema_version": exp.DataType.build("int"),
135143
"sqlglot_version": exp.DataType.build("text"),
144+
"sqlmesh_version": exp.DataType.build("text"),
136145
}
137146

138147
@transactional()
@@ -204,12 +213,21 @@ def _update_versions(
204213
self,
205214
schema_version: int = SCHEMA_VERSION,
206215
sqlglot_version: str = SQLGLOT_VERSION,
216+
sqlmesh_version: str = SQLMESH_VERSION,
207217
) -> None:
208218
self.engine_adapter.delete_from(self.versions_table, "TRUE")
209219

210220
self.engine_adapter.insert_append(
211221
self.versions_table,
212-
pd.DataFrame([{"schema_version": schema_version, "sqlglot_version": sqlglot_version}]),
222+
pd.DataFrame(
223+
[
224+
{
225+
"schema_version": schema_version,
226+
"sqlglot_version": sqlglot_version,
227+
"sqlmesh_version": sqlmesh_version,
228+
}
229+
]
230+
),
213231
columns_to_types=self._version_columns_to_types,
214232
)
215233

@@ -452,7 +470,7 @@ def _get_snapshots_with_same_version(
452470
return [Snapshot(**json.loads(row[0])) for row in snapshot_rows]
453471

454472
def _get_versions(self, lock_for_update: bool = False) -> Versions:
455-
no_version = Versions(schema_version=0, sqlglot_version="0.0.0")
473+
no_version = Versions(schema_version=0, sqlglot_version="0.0.0", sqlmesh_version="0.0.0")
456474

457475
if not self.engine_adapter.table_exists(self.versions_table):
458476
return no_version
@@ -463,7 +481,7 @@ def _get_versions(self, lock_for_update: bool = False) -> Versions:
463481
row = self.engine_adapter.fetchone(query, quote_identifiers=True)
464482
if not row:
465483
return no_version
466-
return Versions(schema_version=row[0], sqlglot_version=row[1])
484+
return Versions(schema_version=row[0], sqlglot_version=row[1], sqlmesh_version=row[2])
467485

468486
def _get_environment(
469487
self, environment: str, lock_for_update: bool = False
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Add new 'sqlmesh_version' column to the version state table."""
2+
from sqlglot import exp
3+
4+
5+
def migrate(state_sync): # type: ignore
6+
engine_adapter = state_sync.engine_adapter
7+
versions_table = "_versions"
8+
if state_sync.schema:
9+
versions_table = f"{state_sync.schema}.{versions_table}"
10+
11+
alter_table_exp = exp.AlterTable(
12+
this=exp.to_table(versions_table),
13+
actions=[
14+
exp.ColumnDef(
15+
this=exp.to_column("sqlmesh_version"),
16+
kind=exp.DataType.build("text"),
17+
)
18+
],
19+
)
20+
21+
engine_adapter.execute(alter_table_exp)

tests/core/test_state_sync.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -994,9 +994,13 @@ def test_unpause_snapshots_remove_intervals(
994994

995995

996996
def test_get_version(state_sync: EngineAdapterStateSync) -> None:
997+
from sqlmesh import __version__ as SQLMESH_VERSION
998+
997999
# fresh install should not raise
9981000
assert state_sync.get_versions() == Versions(
999-
schema_version=SCHEMA_VERSION, sqlglot_version=SQLGLOT_VERSION
1001+
schema_version=SCHEMA_VERSION,
1002+
sqlglot_version=SQLGLOT_VERSION,
1003+
sqlmesh_version=SQLMESH_VERSION,
10001004
)
10011005

10021006
# Start with a clean slate.
@@ -1014,7 +1018,10 @@ def test_get_version(state_sync: EngineAdapterStateSync) -> None:
10141018

10151019
# migration version is behind, always raise
10161020
state_sync._update_versions(schema_version=SCHEMA_VERSION + 1)
1017-
error = rf"SQLMesh \(local\) is using version '{SCHEMA_VERSION}' which is behind '{SCHEMA_VERSION + 1}'"
1021+
error = (
1022+
rf"SQLMesh \(local\) is using version '{SCHEMA_VERSION}' which is behind '{SCHEMA_VERSION + 1}' \(remote\). "
1023+
rf"""Please upgrade SQLMesh \('pip install --upgrade "sqlmesh=={SQLMESH_VERSION}"' command\)."""
1024+
)
10181025

10191026
with pytest.raises(SQLMeshError, match=error):
10201027
state_sync.get_versions()
@@ -1037,14 +1044,17 @@ def test_get_version(state_sync: EngineAdapterStateSync) -> None:
10371044
state_sync._update_versions(sqlglot_version=sqlglot_version)
10381045
state_sync.get_versions(validate=False)
10391046

1040-
# sqlmesh version is behind, always raise
1047+
# sqlglot version is behind, always raise
10411048
sqlglot_version = f"{major}.{int(minor) + 1}.{patch}"
1042-
error = rf"SQLGlot \(local\) is using version '{SQLGLOT_VERSION}' which is behind '{sqlglot_version}'"
1049+
error = (
1050+
rf"SQLGlot \(local\) is using version '{SQLGLOT_VERSION}' which is behind '{sqlglot_version}' \(remote\). "
1051+
rf"""Please upgrade SQLGlot \('pip install --upgrade "sqlglot=={sqlglot_version}"' command\)."""
1052+
)
10431053
state_sync._update_versions(sqlglot_version=sqlglot_version)
10441054
with pytest.raises(SQLMeshError, match=error):
10451055
state_sync.get_versions(validate=False)
10461056

1047-
# sqlmesh version is ahead, only raise with validate is true
1057+
# sqlglot version is ahead, only raise with validate is true
10481058
sqlglot_version = f"{major}.{int(minor) - 1}.{patch}"
10491059
error = rf"SQLGlot \(local\) is using version '{SQLGLOT_VERSION}' which is ahead of '{sqlglot_version}'"
10501060
state_sync._update_versions(sqlglot_version=sqlglot_version)
@@ -1054,6 +1064,8 @@ def test_get_version(state_sync: EngineAdapterStateSync) -> None:
10541064

10551065

10561066
def test_migrate(state_sync: EngineAdapterStateSync, mocker: MockerFixture) -> None:
1067+
from sqlmesh import __version__ as SQLMESH_VERSION
1068+
10571069
migrate_rows_mock = mocker.patch("sqlmesh.core.state_sync.EngineAdapterStateSync._migrate_rows")
10581070
backup_state_mock = mocker.patch("sqlmesh.core.state_sync.EngineAdapterStateSync._backup_state")
10591071
state_sync.migrate()
@@ -1069,7 +1081,9 @@ def test_migrate(state_sync: EngineAdapterStateSync, mocker: MockerFixture) -> N
10691081
migrate_rows_mock.assert_called_once()
10701082
backup_state_mock.assert_called_once()
10711083
assert state_sync.get_versions() == Versions(
1072-
schema_version=SCHEMA_VERSION, sqlglot_version=SQLGLOT_VERSION
1084+
schema_version=SCHEMA_VERSION,
1085+
sqlglot_version=SQLGLOT_VERSION,
1086+
sqlmesh_version=SQLMESH_VERSION,
10731087
)
10741088

10751089

0 commit comments

Comments
 (0)