|
10 | 10 |
|
11 | 11 | from click import ClickException |
12 | 12 | from click.testing import CliRunner |
| 13 | +from sqlglot import __version__ as SQLGLOT_VERSION |
13 | 14 | from sqlmesh import RuntimeEnv |
| 15 | +from sqlmesh._version import __version__ as SQLMESH_VERSION |
14 | 16 | from sqlmesh.cli.project_init import ProjectTemplate, init_example_project |
15 | 17 | from sqlmesh.cli.main import cli |
16 | 18 | from sqlmesh.core.context import Context |
| 19 | +from sqlmesh.core.state_sync.base import SCHEMA_VERSION |
17 | 20 | from sqlmesh.integrations.dlt import generate_dlt_models |
| 21 | +from sqlmesh.utils import major_minor |
18 | 22 | from sqlmesh.utils.date import now_ds, time_like_to_str, timedelta, to_datetime, yesterday_ds |
19 | 23 | from sqlmesh.core.config.connection import DIALECT_TO_TYPE |
20 | 24 |
|
@@ -1024,6 +1028,107 @@ def test_info_on_new_project_does_not_create_state_sync(runner, tmp_path): |
1024 | 1028 | assert not context.engine_adapter.table_exists("sqlmesh._versions") |
1025 | 1029 |
|
1026 | 1030 |
|
| 1031 | +def test_info_state_versions(runner, tmp_path): |
| 1032 | + create_example_project(tmp_path) |
| 1033 | + init_prod_and_backfill(runner, tmp_path) |
| 1034 | + |
| 1035 | + result = runner.invoke(cli, ["--log-file-dir", tmp_path, "--paths", tmp_path, "info"]) |
| 1036 | + assert result.exit_code == 0 |
| 1037 | + assert "State backend versions" not in result.output |
| 1038 | + |
| 1039 | + result = runner.invoke(cli, ["--log-file-dir", tmp_path, "--paths", tmp_path, "info", "-v"]) |
| 1040 | + assert result.exit_code == 0 |
| 1041 | + assert "State backend versions" in result.output |
| 1042 | + assert f"Schema version: {SCHEMA_VERSION}" in result.output |
| 1043 | + assert f"SQLGlot version: {SQLGLOT_VERSION}" in result.output |
| 1044 | + assert f"SQLMesh version: {SQLMESH_VERSION}" in result.output |
| 1045 | + |
| 1046 | + |
| 1047 | +def test_rollback_state_versions(runner, tmp_path): |
| 1048 | + create_example_project(tmp_path) |
| 1049 | + init_prod_and_backfill(runner, tmp_path) |
| 1050 | + |
| 1051 | + context = Context(paths=tmp_path) |
| 1052 | + state_sync = context._new_state_sync() |
| 1053 | + # Back up the current state, then pretend the state was migrated by a newer SQLMesh. |
| 1054 | + state_sync.migrator._backup_state() |
| 1055 | + state_sync.version_state.update_versions( |
| 1056 | + schema_version=SCHEMA_VERSION + 1, |
| 1057 | + sqlglot_version="9999.0.0", |
| 1058 | + sqlmesh_version="9999.0.0", |
| 1059 | + ) |
| 1060 | + context.close() |
| 1061 | + |
| 1062 | + result = runner.invoke(cli, ["--log-file-dir", tmp_path, "--paths", tmp_path, "rollback"]) |
| 1063 | + assert result.exit_code == 0 |
| 1064 | + assert "State backend versions" in result.output |
| 1065 | + assert f"Schema version: {SCHEMA_VERSION + 1} -> {SCHEMA_VERSION}" in result.output |
| 1066 | + assert f"SQLGlot version: 9999.0.0 -> {SQLGLOT_VERSION}" in result.output |
| 1067 | + assert f"SQLMesh version: 9999.0.0 -> {SQLMESH_VERSION}" in result.output |
| 1068 | + |
| 1069 | + |
| 1070 | +def test_migrate_state_versions(runner, tmp_path): |
| 1071 | + create_example_project(tmp_path) |
| 1072 | + init_prod_and_backfill(runner, tmp_path) |
| 1073 | + |
| 1074 | + context = Context(paths=tmp_path) |
| 1075 | + # Pretend the state was written by an older patch release of the same minor version, which |
| 1076 | + # is the case `migrate` used to leave untouched. |
| 1077 | + major, minor = major_minor(SQLMESH_VERSION) |
| 1078 | + older_sqlmesh = f"{major}.{minor}.dev0" |
| 1079 | + context._new_state_sync().version_state.update_versions( |
| 1080 | + sqlglot_version="0.0.1", |
| 1081 | + sqlmesh_version=older_sqlmesh, |
| 1082 | + ) |
| 1083 | + context.close() |
| 1084 | + |
| 1085 | + result = runner.invoke(cli, ["--log-file-dir", tmp_path, "--paths", tmp_path, "migrate"]) |
| 1086 | + assert result.exit_code == 0 |
| 1087 | + assert "State backend versions" in result.output |
| 1088 | + assert f"SQLGlot version: 0.0.1 -> {SQLGLOT_VERSION}" in result.output |
| 1089 | + assert f"SQLMesh version: {older_sqlmesh} -> {SQLMESH_VERSION}" in result.output |
| 1090 | + |
| 1091 | + |
| 1092 | +def test_migrate_updates_versions_after_a_patch_bump(runner, tmp_path): |
| 1093 | + """A patch bump leaves the minor version equal, but the recorded versions must still move. |
| 1094 | +
|
| 1095 | + Both minor versions have to match the installed ones, otherwise `_apply_migrations` reports |
| 1096 | + rows to migrate and the early return this covers is never reached. |
| 1097 | + """ |
| 1098 | + create_example_project(tmp_path) |
| 1099 | + init_prod_and_backfill(runner, tmp_path) |
| 1100 | + |
| 1101 | + sqlmesh_major, sqlmesh_minor = major_minor(SQLMESH_VERSION) |
| 1102 | + sqlglot_major, sqlglot_minor = major_minor(SQLGLOT_VERSION) |
| 1103 | + context = Context(paths=tmp_path) |
| 1104 | + context._new_state_sync().version_state.update_versions( |
| 1105 | + sqlglot_version=f"{sqlglot_major}.{sqlglot_minor}.dev0", |
| 1106 | + sqlmesh_version=f"{sqlmesh_major}.{sqlmesh_minor}.dev0", |
| 1107 | + ) |
| 1108 | + context.close() |
| 1109 | + |
| 1110 | + assert ( |
| 1111 | + runner.invoke(cli, ["--log-file-dir", tmp_path, "--paths", tmp_path, "migrate"]).exit_code |
| 1112 | + == 0 |
| 1113 | + ) |
| 1114 | + |
| 1115 | + context = Context(paths=tmp_path) |
| 1116 | + versions = context._new_state_sync().get_versions(validate=False) |
| 1117 | + context.close() |
| 1118 | + assert versions.sqlmesh_version == SQLMESH_VERSION |
| 1119 | + assert versions.sqlglot_version == SQLGLOT_VERSION |
| 1120 | + |
| 1121 | + |
| 1122 | +def test_rollback_without_backup_does_not_print_state_versions(runner, tmp_path): |
| 1123 | + create_example_project(tmp_path) |
| 1124 | + init_prod_and_backfill(runner, tmp_path) |
| 1125 | + |
| 1126 | + result = runner.invoke(cli, ["--log-file-dir", tmp_path, "--paths", tmp_path, "rollback"]) |
| 1127 | + assert result.exit_code == 1 |
| 1128 | + assert "There are no prior migrations to roll back to." in result.output |
| 1129 | + assert "State backend versions" not in result.output |
| 1130 | + |
| 1131 | + |
1027 | 1132 | def test_dlt_pipeline_errors(runner, tmp_path): |
1028 | 1133 | # Error if no pipeline is provided |
1029 | 1134 | result = runner.invoke(cli, ["--paths", tmp_path, "init", "-t", "dlt", "duckdb"]) |
|
0 commit comments