sqlmesh 0.213.1.dev1__py3-none-any.whl → 0.227.2.dev4__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/__init__.py +12 -2
- sqlmesh/_version.py +2 -2
- sqlmesh/cli/main.py +0 -44
- sqlmesh/cli/project_init.py +11 -2
- sqlmesh/core/_typing.py +1 -0
- sqlmesh/core/audit/definition.py +8 -2
- sqlmesh/core/config/__init__.py +1 -1
- sqlmesh/core/config/connection.py +17 -5
- sqlmesh/core/config/dbt.py +13 -0
- sqlmesh/core/config/janitor.py +12 -0
- sqlmesh/core/config/loader.py +7 -0
- sqlmesh/core/config/model.py +2 -0
- sqlmesh/core/config/root.py +3 -0
- sqlmesh/core/console.py +81 -3
- sqlmesh/core/constants.py +1 -1
- sqlmesh/core/context.py +69 -26
- sqlmesh/core/dialect.py +3 -0
- sqlmesh/core/engine_adapter/_typing.py +2 -0
- sqlmesh/core/engine_adapter/base.py +322 -22
- sqlmesh/core/engine_adapter/base_postgres.py +17 -1
- sqlmesh/core/engine_adapter/bigquery.py +146 -7
- sqlmesh/core/engine_adapter/clickhouse.py +17 -13
- sqlmesh/core/engine_adapter/databricks.py +33 -2
- sqlmesh/core/engine_adapter/fabric.py +10 -29
- sqlmesh/core/engine_adapter/mixins.py +142 -48
- sqlmesh/core/engine_adapter/mssql.py +15 -4
- sqlmesh/core/engine_adapter/mysql.py +2 -2
- sqlmesh/core/engine_adapter/postgres.py +9 -3
- sqlmesh/core/engine_adapter/redshift.py +4 -0
- sqlmesh/core/engine_adapter/risingwave.py +1 -0
- sqlmesh/core/engine_adapter/shared.py +6 -0
- sqlmesh/core/engine_adapter/snowflake.py +82 -11
- sqlmesh/core/engine_adapter/spark.py +14 -10
- sqlmesh/core/engine_adapter/trino.py +4 -2
- sqlmesh/core/environment.py +2 -0
- sqlmesh/core/janitor.py +181 -0
- sqlmesh/core/lineage.py +1 -0
- sqlmesh/core/linter/definition.py +13 -13
- sqlmesh/core/linter/rules/builtin.py +29 -0
- sqlmesh/core/macros.py +35 -13
- sqlmesh/core/model/common.py +2 -0
- sqlmesh/core/model/definition.py +82 -28
- sqlmesh/core/model/kind.py +66 -2
- sqlmesh/core/model/meta.py +108 -4
- sqlmesh/core/node.py +101 -1
- sqlmesh/core/plan/builder.py +18 -10
- sqlmesh/core/plan/common.py +199 -2
- sqlmesh/core/plan/definition.py +25 -6
- sqlmesh/core/plan/evaluator.py +75 -113
- sqlmesh/core/plan/explainer.py +90 -8
- sqlmesh/core/plan/stages.py +42 -21
- sqlmesh/core/renderer.py +78 -32
- sqlmesh/core/scheduler.py +102 -22
- sqlmesh/core/selector.py +137 -9
- sqlmesh/core/signal.py +64 -1
- sqlmesh/core/snapshot/__init__.py +2 -0
- sqlmesh/core/snapshot/definition.py +146 -34
- sqlmesh/core/snapshot/evaluator.py +689 -124
- sqlmesh/core/state_sync/__init__.py +0 -1
- sqlmesh/core/state_sync/base.py +55 -33
- sqlmesh/core/state_sync/cache.py +12 -7
- sqlmesh/core/state_sync/common.py +216 -111
- sqlmesh/core/state_sync/db/environment.py +6 -4
- sqlmesh/core/state_sync/db/facade.py +42 -24
- sqlmesh/core/state_sync/db/interval.py +27 -7
- sqlmesh/core/state_sync/db/migrator.py +34 -16
- sqlmesh/core/state_sync/db/snapshot.py +177 -169
- sqlmesh/core/table_diff.py +2 -2
- sqlmesh/core/test/context.py +2 -0
- sqlmesh/core/test/definition.py +14 -9
- sqlmesh/dbt/adapter.py +22 -16
- sqlmesh/dbt/basemodel.py +75 -56
- sqlmesh/dbt/builtin.py +116 -12
- sqlmesh/dbt/column.py +17 -5
- sqlmesh/dbt/common.py +19 -5
- sqlmesh/dbt/context.py +14 -1
- sqlmesh/dbt/loader.py +61 -9
- sqlmesh/dbt/manifest.py +174 -16
- sqlmesh/dbt/model.py +183 -85
- sqlmesh/dbt/package.py +16 -1
- sqlmesh/dbt/profile.py +3 -3
- sqlmesh/dbt/project.py +12 -7
- sqlmesh/dbt/seed.py +6 -1
- sqlmesh/dbt/source.py +13 -1
- sqlmesh/dbt/target.py +25 -6
- sqlmesh/dbt/test.py +36 -5
- sqlmesh/migrations/v0000_baseline.py +95 -0
- sqlmesh/migrations/v0061_mysql_fix_blob_text_type.py +5 -7
- sqlmesh/migrations/v0062_add_model_gateway.py +5 -1
- sqlmesh/migrations/v0063_change_signals.py +5 -3
- sqlmesh/migrations/v0064_join_when_matched_strings.py +5 -3
- sqlmesh/migrations/v0065_add_model_optimize.py +5 -1
- sqlmesh/migrations/v0066_add_auto_restatements.py +8 -3
- sqlmesh/migrations/v0067_add_tsql_date_full_precision.py +5 -1
- sqlmesh/migrations/v0068_include_unrendered_query_in_metadata_hash.py +5 -1
- sqlmesh/migrations/v0069_update_dev_table_suffix.py +5 -3
- sqlmesh/migrations/v0070_include_grains_in_metadata_hash.py +5 -1
- sqlmesh/migrations/v0071_add_dev_version_to_intervals.py +9 -5
- sqlmesh/migrations/v0072_add_environment_statements.py +5 -3
- sqlmesh/migrations/v0073_remove_symbolic_disable_restatement.py +5 -3
- sqlmesh/migrations/v0074_add_partition_by_time_column_property.py +5 -1
- sqlmesh/migrations/v0075_remove_validate_query.py +5 -3
- sqlmesh/migrations/v0076_add_cron_tz.py +5 -1
- sqlmesh/migrations/v0077_fix_column_type_hash_calculation.py +5 -1
- sqlmesh/migrations/v0078_warn_if_non_migratable_python_env.py +5 -3
- sqlmesh/migrations/v0079_add_gateway_managed_property.py +10 -5
- sqlmesh/migrations/v0080_add_batch_size_to_scd_type_2_models.py +5 -1
- sqlmesh/migrations/v0081_update_partitioned_by.py +5 -3
- sqlmesh/migrations/v0082_warn_if_incorrectly_duplicated_statements.py +5 -3
- sqlmesh/migrations/v0083_use_sql_for_scd_time_data_type_data_hash.py +5 -1
- sqlmesh/migrations/v0084_normalize_quote_when_matched_and_merge_filter.py +5 -1
- sqlmesh/migrations/v0085_deterministic_repr.py +5 -3
- sqlmesh/migrations/v0086_check_deterministic_bug.py +5 -3
- sqlmesh/migrations/v0087_normalize_blueprint_variables.py +5 -3
- sqlmesh/migrations/v0088_warn_about_variable_python_env_diffs.py +5 -3
- sqlmesh/migrations/v0089_add_virtual_environment_mode.py +5 -1
- sqlmesh/migrations/v0090_add_forward_only_column.py +9 -5
- sqlmesh/migrations/v0091_on_additive_change.py +5 -1
- sqlmesh/migrations/v0092_warn_about_dbt_data_type_diff.py +5 -3
- sqlmesh/migrations/v0093_use_raw_sql_in_fingerprint.py +5 -1
- sqlmesh/migrations/v0094_add_dev_version_and_fingerprint_columns.py +123 -0
- sqlmesh/migrations/v0095_warn_about_dbt_raw_sql_diff.py +49 -0
- sqlmesh/migrations/v0096_remove_plan_dags_table.py +13 -0
- sqlmesh/migrations/v0097_add_dbt_name_in_node.py +9 -0
- sqlmesh/migrations/{v0060_move_audits_to_model.py → v0098_add_dbt_node_info_in_node.py} +33 -16
- sqlmesh/migrations/v0099_add_last_altered_to_intervals.py +25 -0
- sqlmesh/migrations/v0100_add_grants_and_grants_target_layer.py +9 -0
- sqlmesh/utils/__init__.py +8 -1
- sqlmesh/utils/cache.py +5 -1
- sqlmesh/utils/connection_pool.py +2 -1
- sqlmesh/utils/dag.py +65 -10
- sqlmesh/utils/date.py +8 -1
- sqlmesh/utils/errors.py +8 -0
- sqlmesh/utils/jinja.py +54 -4
- sqlmesh/utils/pydantic.py +6 -6
- sqlmesh/utils/windows.py +13 -3
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/METADATA +7 -10
- sqlmesh-0.227.2.dev4.dist-info/RECORD +370 -0
- sqlmesh_dbt/cli.py +70 -7
- sqlmesh_dbt/console.py +14 -6
- sqlmesh_dbt/operations.py +103 -24
- sqlmesh_dbt/selectors.py +39 -1
- web/client/dist/assets/{Audits-Ucsx1GzF.js → Audits-CBiYyyx-.js} +1 -1
- web/client/dist/assets/{Banner-BWDzvavM.js → Banner-DSRbUlO5.js} +1 -1
- web/client/dist/assets/{ChevronDownIcon-D2VL13Ah.js → ChevronDownIcon-MK_nrjD_.js} +1 -1
- web/client/dist/assets/{ChevronRightIcon-DWGYbf1l.js → ChevronRightIcon-CLWtT22Q.js} +1 -1
- web/client/dist/assets/{Content-DdHDZM3I.js → Content-BNuGZN5l.js} +1 -1
- web/client/dist/assets/{Content-Bikfy8fh.js → Content-CSHJyW0n.js} +1 -1
- web/client/dist/assets/{Data-CzAJH7rW.js → Data-C1oRDbLx.js} +1 -1
- web/client/dist/assets/{DataCatalog-BJF11g8f.js → DataCatalog-HXyX2-_j.js} +1 -1
- web/client/dist/assets/{Editor-s0SBpV2y.js → Editor-BDyfpUuw.js} +1 -1
- web/client/dist/assets/{Editor-DgLhgKnm.js → Editor-D0jNItwC.js} +1 -1
- web/client/dist/assets/{Errors-D0m0O1d3.js → Errors-BfuFLcPi.js} +1 -1
- web/client/dist/assets/{FileExplorer-CEv0vXkt.js → FileExplorer-BR9IE3he.js} +1 -1
- web/client/dist/assets/{Footer-BwzXn8Ew.js → Footer-CgBEtiAh.js} +1 -1
- web/client/dist/assets/{Header-6heDkEqG.js → Header-DSqR6nSO.js} +1 -1
- web/client/dist/assets/{Input-obuJsD6k.js → Input-B-oZ6fGO.js} +1 -1
- web/client/dist/assets/Lineage-DYQVwDbD.js +1 -0
- web/client/dist/assets/{ListboxShow-HM9_qyrt.js → ListboxShow-BE5-xevs.js} +1 -1
- web/client/dist/assets/{ModelLineage-zWdKo0U2.js → ModelLineage-DkIFAYo4.js} +1 -1
- web/client/dist/assets/{Models-Bcu66SRz.js → Models-D5dWr8RB.js} +1 -1
- web/client/dist/assets/{Page-BWEEQfIt.js → Page-C-XfU5BR.js} +1 -1
- web/client/dist/assets/{Plan-C4gXCqlf.js → Plan-ZEuTINBq.js} +1 -1
- web/client/dist/assets/{PlusCircleIcon-CVDO651q.js → PlusCircleIcon-DVXAHG8_.js} +1 -1
- web/client/dist/assets/{ReportErrors-BT6xFwAr.js → ReportErrors-B7FEPzMB.js} +1 -1
- web/client/dist/assets/{Root-ryJoBK4h.js → Root-8aZyhPxF.js} +1 -1
- web/client/dist/assets/{SearchList-DB04sPb9.js → SearchList-W_iT2G82.js} +1 -1
- web/client/dist/assets/{SelectEnvironment-CUYcXUu6.js → SelectEnvironment-C65jALmO.js} +1 -1
- web/client/dist/assets/{SourceList-Doo_9ZGp.js → SourceList-DSLO6nVJ.js} +1 -1
- web/client/dist/assets/{SourceListItem-D5Mj7Dly.js → SourceListItem-BHt8d9-I.js} +1 -1
- web/client/dist/assets/{SplitPane-qHmkD1qy.js → SplitPane-CViaZmw6.js} +1 -1
- web/client/dist/assets/{Tests-DH1Z74ML.js → Tests-DhaVt5t1.js} +1 -1
- web/client/dist/assets/{Welcome-DqUJUNMF.js → Welcome-DvpjH-_4.js} +1 -1
- web/client/dist/assets/context-BctCsyGb.js +71 -0
- web/client/dist/assets/{context-Dr54UHLi.js → context-DFNeGsFF.js} +1 -1
- web/client/dist/assets/{editor-DYIP1yQ4.js → editor-CcO28cqd.js} +1 -1
- web/client/dist/assets/{file-DarlIDVi.js → file-CvJN3aZO.js} +1 -1
- web/client/dist/assets/{floating-ui.react-dom-BH3TFvkM.js → floating-ui.react-dom-CjE-JNW1.js} +1 -1
- web/client/dist/assets/{help-Bl8wqaQc.js → help-DuPhjipa.js} +1 -1
- web/client/dist/assets/{index-D1sR7wpN.js → index-C-dJH7yZ.js} +1 -1
- web/client/dist/assets/{index-O3mjYpnE.js → index-Dj0i1-CA.js} +2 -2
- web/client/dist/assets/{plan-CehRrJUG.js → plan-BTRSbjKn.js} +1 -1
- web/client/dist/assets/{popover-CqgMRE0G.js → popover-_Sf0yvOI.js} +1 -1
- web/client/dist/assets/{project-6gxepOhm.js → project-BvSOI8MY.js} +1 -1
- web/client/dist/index.html +1 -1
- sqlmesh/integrations/llm.py +0 -56
- sqlmesh/migrations/v0001_init.py +0 -60
- sqlmesh/migrations/v0002_remove_identify.py +0 -5
- sqlmesh/migrations/v0003_move_batch_size.py +0 -34
- sqlmesh/migrations/v0004_environmnent_add_finalized_at.py +0 -23
- sqlmesh/migrations/v0005_create_seed_table.py +0 -24
- sqlmesh/migrations/v0006_change_seed_hash.py +0 -5
- sqlmesh/migrations/v0007_env_table_info_to_kind.py +0 -99
- sqlmesh/migrations/v0008_create_intervals_table.py +0 -38
- sqlmesh/migrations/v0009_remove_pre_post_hooks.py +0 -62
- sqlmesh/migrations/v0010_seed_hash_batch_size.py +0 -5
- sqlmesh/migrations/v0011_add_model_kind_name.py +0 -63
- sqlmesh/migrations/v0012_update_jinja_expressions.py +0 -86
- sqlmesh/migrations/v0013_serde_using_model_dialects.py +0 -87
- sqlmesh/migrations/v0014_fix_dev_intervals.py +0 -14
- sqlmesh/migrations/v0015_environment_add_promoted_snapshot_ids.py +0 -26
- sqlmesh/migrations/v0016_fix_windows_path.py +0 -59
- sqlmesh/migrations/v0017_fix_windows_seed_path.py +0 -55
- sqlmesh/migrations/v0018_rename_snapshot_model_to_node.py +0 -53
- sqlmesh/migrations/v0019_add_env_suffix_target.py +0 -28
- sqlmesh/migrations/v0020_remove_redundant_attributes_from_dbt_models.py +0 -80
- sqlmesh/migrations/v0021_fix_table_properties.py +0 -62
- sqlmesh/migrations/v0022_move_project_to_model.py +0 -54
- sqlmesh/migrations/v0023_fix_added_models_with_forward_only_parents.py +0 -65
- sqlmesh/migrations/v0024_replace_model_kind_name_enum_with_value.py +0 -55
- sqlmesh/migrations/v0025_fix_intervals_and_missing_change_category.py +0 -117
- sqlmesh/migrations/v0026_remove_dialect_from_seed.py +0 -55
- sqlmesh/migrations/v0027_minute_interval_to_five.py +0 -57
- sqlmesh/migrations/v0028_add_plan_dags_table.py +0 -29
- sqlmesh/migrations/v0029_generate_schema_types_using_dialect.py +0 -69
- sqlmesh/migrations/v0030_update_unrestorable_snapshots.py +0 -65
- sqlmesh/migrations/v0031_remove_dbt_target_fields.py +0 -65
- sqlmesh/migrations/v0032_add_sqlmesh_version.py +0 -25
- sqlmesh/migrations/v0033_mysql_fix_blob_text_type.py +0 -45
- sqlmesh/migrations/v0034_add_default_catalog.py +0 -367
- sqlmesh/migrations/v0035_add_catalog_name_override.py +0 -22
- sqlmesh/migrations/v0036_delete_plan_dags_bug_fix.py +0 -14
- sqlmesh/migrations/v0037_remove_dbt_is_incremental_macro.py +0 -61
- sqlmesh/migrations/v0038_add_expiration_ts_to_snapshot.py +0 -73
- sqlmesh/migrations/v0039_include_environment_in_plan_dag_spec.py +0 -68
- sqlmesh/migrations/v0040_add_previous_finalized_snapshots.py +0 -26
- sqlmesh/migrations/v0041_remove_hash_raw_query_attribute.py +0 -59
- sqlmesh/migrations/v0042_trim_indirect_versions.py +0 -66
- sqlmesh/migrations/v0043_fix_remove_obsolete_attributes_in_plan_dags.py +0 -61
- sqlmesh/migrations/v0044_quote_identifiers_in_model_attributes.py +0 -5
- sqlmesh/migrations/v0045_move_gateway_variable.py +0 -70
- sqlmesh/migrations/v0046_add_batch_concurrency.py +0 -8
- sqlmesh/migrations/v0047_change_scd_string_to_column.py +0 -5
- sqlmesh/migrations/v0048_drop_indirect_versions.py +0 -59
- sqlmesh/migrations/v0049_replace_identifier_with_version_in_seeds_table.py +0 -57
- sqlmesh/migrations/v0050_drop_seeds_table.py +0 -11
- sqlmesh/migrations/v0051_rename_column_descriptions.py +0 -65
- sqlmesh/migrations/v0052_add_normalize_name_in_environment_naming_info.py +0 -28
- sqlmesh/migrations/v0053_custom_model_kind_extra_attributes.py +0 -5
- sqlmesh/migrations/v0054_fix_trailing_comments.py +0 -5
- sqlmesh/migrations/v0055_add_updated_ts_unpaused_ts_ttl_ms_unrestorable_to_snapshot.py +0 -132
- sqlmesh/migrations/v0056_restore_table_indexes.py +0 -118
- sqlmesh/migrations/v0057_add_table_format.py +0 -5
- sqlmesh/migrations/v0058_add_requirements.py +0 -26
- sqlmesh/migrations/v0059_add_physical_version.py +0 -5
- sqlmesh-0.213.1.dev1.dist-info/RECORD +0 -421
- web/client/dist/assets/Lineage-D0Hgdz2v.js +0 -1
- web/client/dist/assets/context-DgX0fp2E.js +0 -68
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/WHEEL +0 -0
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/top_level.txt +0 -0
|
@@ -20,5 +20,4 @@ from sqlmesh.core.state_sync.base import (
|
|
|
20
20
|
Versions as Versions,
|
|
21
21
|
)
|
|
22
22
|
from sqlmesh.core.state_sync.cache import CachingStateSync as CachingStateSync
|
|
23
|
-
from sqlmesh.core.state_sync.common import cleanup_expired_views as cleanup_expired_views
|
|
24
23
|
from sqlmesh.core.state_sync.db import EngineAdapterStateSync as EngineAdapterStateSync
|
sqlmesh/core/state_sync/base.py
CHANGED
|
@@ -11,7 +11,6 @@ from sqlglot import __version__ as SQLGLOT_VERSION
|
|
|
11
11
|
from sqlmesh import migrations
|
|
12
12
|
from sqlmesh.core.environment import (
|
|
13
13
|
Environment,
|
|
14
|
-
EnvironmentNamingInfo,
|
|
15
14
|
EnvironmentStatements,
|
|
16
15
|
EnvironmentSummary,
|
|
17
16
|
)
|
|
@@ -19,17 +18,22 @@ from sqlmesh.core.snapshot import (
|
|
|
19
18
|
Snapshot,
|
|
20
19
|
SnapshotId,
|
|
21
20
|
SnapshotIdLike,
|
|
21
|
+
SnapshotIdAndVersionLike,
|
|
22
22
|
SnapshotInfoLike,
|
|
23
|
-
SnapshotTableCleanupTask,
|
|
24
|
-
SnapshotTableInfo,
|
|
25
23
|
SnapshotNameVersion,
|
|
24
|
+
SnapshotIdAndVersion,
|
|
26
25
|
)
|
|
27
26
|
from sqlmesh.core.snapshot.definition import Interval, SnapshotIntervals
|
|
28
27
|
from sqlmesh.utils import major_minor
|
|
29
28
|
from sqlmesh.utils.date import TimeLike
|
|
30
29
|
from sqlmesh.utils.errors import SQLMeshError
|
|
31
|
-
from sqlmesh.utils.pydantic import PydanticModel,
|
|
32
|
-
from sqlmesh.core.state_sync.common import
|
|
30
|
+
from sqlmesh.utils.pydantic import PydanticModel, field_validator
|
|
31
|
+
from sqlmesh.core.state_sync.common import (
|
|
32
|
+
StateStream,
|
|
33
|
+
ExpiredSnapshotBatch,
|
|
34
|
+
PromotionResult,
|
|
35
|
+
ExpiredBatchRange,
|
|
36
|
+
)
|
|
33
37
|
|
|
34
38
|
logger = logging.getLogger(__name__)
|
|
35
39
|
|
|
@@ -60,25 +64,14 @@ class Versions(PydanticModel):
|
|
|
60
64
|
return 0 if v is None else int(v)
|
|
61
65
|
|
|
62
66
|
|
|
67
|
+
MIN_SCHEMA_VERSION = 60
|
|
68
|
+
MIN_SQLMESH_VERSION = "0.134.0"
|
|
63
69
|
MIGRATIONS = [
|
|
64
70
|
importlib.import_module(f"sqlmesh.migrations.{migration}")
|
|
65
71
|
for migration in sorted(info.name for info in pkgutil.iter_modules(migrations.__path__))
|
|
66
72
|
]
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class PromotionResult(PydanticModel):
|
|
71
|
-
added: t.List[SnapshotTableInfo]
|
|
72
|
-
removed: t.List[SnapshotTableInfo]
|
|
73
|
-
removed_environment_naming_info: t.Optional[EnvironmentNamingInfo]
|
|
74
|
-
|
|
75
|
-
@field_validator("removed_environment_naming_info")
|
|
76
|
-
def _validate_removed_environment_naming_info(
|
|
77
|
-
cls, v: t.Optional[EnvironmentNamingInfo], info: ValidationInfo
|
|
78
|
-
) -> t.Optional[EnvironmentNamingInfo]:
|
|
79
|
-
if v and not info.data.get("removed"):
|
|
80
|
-
raise ValueError("removed_environment_naming_info must be None if removed is empty")
|
|
81
|
-
return v
|
|
73
|
+
# -1 to account for the baseline script
|
|
74
|
+
SCHEMA_VERSION: int = MIN_SCHEMA_VERSION + len(MIGRATIONS) - 1
|
|
82
75
|
|
|
83
76
|
|
|
84
77
|
class StateReader(abc.ABC):
|
|
@@ -97,6 +90,24 @@ class StateReader(abc.ABC):
|
|
|
97
90
|
A dictionary of snapshot ids to snapshots for ones that could be found.
|
|
98
91
|
"""
|
|
99
92
|
|
|
93
|
+
@abc.abstractmethod
|
|
94
|
+
def get_snapshots_by_names(
|
|
95
|
+
self,
|
|
96
|
+
snapshot_names: t.Iterable[str],
|
|
97
|
+
current_ts: t.Optional[int] = None,
|
|
98
|
+
exclude_expired: bool = True,
|
|
99
|
+
) -> t.Set[SnapshotIdAndVersion]:
|
|
100
|
+
"""Return the snapshot records for all versions of the specified snapshot names.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
snapshot_names: Iterable of snapshot names to fetch all snapshot records for
|
|
104
|
+
current_ts: Sets the current time for identifying which snapshots have expired so they can be excluded (only relevant if :exclude_expired=True)
|
|
105
|
+
exclude_expired: Whether or not to return the snapshot id's of expired snapshots in the result
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
A set containing all the matched snapshot records. To fetch full snapshots, pass it into StateSync.get_snapshots()
|
|
109
|
+
"""
|
|
110
|
+
|
|
100
111
|
@abc.abstractmethod
|
|
101
112
|
def snapshots_exist(self, snapshot_ids: t.Iterable[SnapshotIdLike]) -> t.Set[SnapshotId]:
|
|
102
113
|
"""Checks if multiple snapshots exist in the state sync.
|
|
@@ -292,15 +303,21 @@ class StateReader(abc.ABC):
|
|
|
292
303
|
|
|
293
304
|
@abc.abstractmethod
|
|
294
305
|
def get_expired_snapshots(
|
|
295
|
-
self,
|
|
296
|
-
|
|
297
|
-
|
|
306
|
+
self,
|
|
307
|
+
*,
|
|
308
|
+
batch_range: ExpiredBatchRange,
|
|
309
|
+
current_ts: t.Optional[int] = None,
|
|
310
|
+
ignore_ttl: bool = False,
|
|
311
|
+
) -> t.Optional[ExpiredSnapshotBatch]:
|
|
312
|
+
"""Returns a single batch of expired snapshots ordered by (updated_ts, name, identifier).
|
|
298
313
|
|
|
299
|
-
|
|
300
|
-
|
|
314
|
+
Args:
|
|
315
|
+
current_ts: Timestamp used to evaluate expiration.
|
|
316
|
+
ignore_ttl: If True, include snapshots regardless of TTL (only checks if unreferenced).
|
|
317
|
+
batch_range: The range of the batch to fetch.
|
|
301
318
|
|
|
302
319
|
Returns:
|
|
303
|
-
|
|
320
|
+
A batch describing expired snapshots or None if no snapshots are pending cleanup.
|
|
304
321
|
"""
|
|
305
322
|
|
|
306
323
|
@abc.abstractmethod
|
|
@@ -340,19 +357,21 @@ class StateSync(StateReader, abc.ABC):
|
|
|
340
357
|
|
|
341
358
|
@abc.abstractmethod
|
|
342
359
|
def delete_expired_snapshots(
|
|
343
|
-
self,
|
|
344
|
-
|
|
360
|
+
self,
|
|
361
|
+
batch_range: ExpiredBatchRange,
|
|
362
|
+
ignore_ttl: bool = False,
|
|
363
|
+
current_ts: t.Optional[int] = None,
|
|
364
|
+
) -> None:
|
|
345
365
|
"""Removes expired snapshots.
|
|
346
366
|
|
|
347
367
|
Expired snapshots are snapshots that have exceeded their time-to-live
|
|
348
368
|
and are no longer in use within an environment.
|
|
349
369
|
|
|
350
370
|
Args:
|
|
371
|
+
batch_range: The range of snapshots to delete in this batch.
|
|
351
372
|
ignore_ttl: Ignore the TTL on the snapshot when considering it expired. This has the effect of deleting
|
|
352
373
|
all snapshots that are not referenced in any environment
|
|
353
|
-
|
|
354
|
-
Returns:
|
|
355
|
-
The list of snapshot table cleanup tasks.
|
|
374
|
+
current_ts: Timestamp used to evaluate expiration.
|
|
356
375
|
"""
|
|
357
376
|
|
|
358
377
|
@abc.abstractmethod
|
|
@@ -371,7 +390,7 @@ class StateSync(StateReader, abc.ABC):
|
|
|
371
390
|
@abc.abstractmethod
|
|
372
391
|
def remove_intervals(
|
|
373
392
|
self,
|
|
374
|
-
snapshot_intervals: t.Sequence[t.Tuple[
|
|
393
|
+
snapshot_intervals: t.Sequence[t.Tuple[SnapshotIdAndVersionLike, Interval]],
|
|
375
394
|
remove_shared_versions: bool = False,
|
|
376
395
|
) -> None:
|
|
377
396
|
"""Remove an interval from a list of snapshots and sync it to the store.
|
|
@@ -453,7 +472,6 @@ class StateSync(StateReader, abc.ABC):
|
|
|
453
472
|
@abc.abstractmethod
|
|
454
473
|
def migrate(
|
|
455
474
|
self,
|
|
456
|
-
default_catalog: t.Optional[str],
|
|
457
475
|
skip_backup: bool = False,
|
|
458
476
|
promoted_snapshots_only: bool = True,
|
|
459
477
|
) -> None:
|
|
@@ -477,6 +495,7 @@ class StateSync(StateReader, abc.ABC):
|
|
|
477
495
|
start: TimeLike,
|
|
478
496
|
end: TimeLike,
|
|
479
497
|
is_dev: bool = False,
|
|
498
|
+
last_altered_ts: t.Optional[int] = None,
|
|
480
499
|
) -> None:
|
|
481
500
|
"""Add an interval to a snapshot and sync it to the store.
|
|
482
501
|
|
|
@@ -485,6 +504,7 @@ class StateSync(StateReader, abc.ABC):
|
|
|
485
504
|
start: The start of the interval to add.
|
|
486
505
|
end: The end of the interval to add.
|
|
487
506
|
is_dev: Indicates whether the given interval is being added while in development mode
|
|
507
|
+
last_altered_ts: The timestamp of the last modification of the physical table
|
|
488
508
|
"""
|
|
489
509
|
start_ts, end_ts = snapshot.inclusive_exclusive(start, end, strict=False, expand=False)
|
|
490
510
|
if not snapshot.version:
|
|
@@ -497,6 +517,8 @@ class StateSync(StateReader, abc.ABC):
|
|
|
497
517
|
dev_version=snapshot.dev_version,
|
|
498
518
|
intervals=intervals if not is_dev else [],
|
|
499
519
|
dev_intervals=intervals if is_dev else [],
|
|
520
|
+
last_altered_ts=last_altered_ts if not is_dev else None,
|
|
521
|
+
dev_last_altered_ts=last_altered_ts if is_dev else None,
|
|
500
522
|
)
|
|
501
523
|
self.add_snapshots_intervals([snapshot_intervals])
|
|
502
524
|
|
sqlmesh/core/state_sync/cache.py
CHANGED
|
@@ -7,11 +7,12 @@ from sqlmesh.core.snapshot import (
|
|
|
7
7
|
Snapshot,
|
|
8
8
|
SnapshotId,
|
|
9
9
|
SnapshotIdLike,
|
|
10
|
+
SnapshotIdAndVersionLike,
|
|
10
11
|
SnapshotInfoLike,
|
|
11
|
-
SnapshotTableCleanupTask,
|
|
12
12
|
)
|
|
13
13
|
from sqlmesh.core.snapshot.definition import Interval, SnapshotIntervals
|
|
14
14
|
from sqlmesh.core.state_sync.base import DelegatingStateSync, StateSync
|
|
15
|
+
from sqlmesh.core.state_sync.common import ExpiredBatchRange
|
|
15
16
|
from sqlmesh.utils.date import TimeLike, now_timestamp
|
|
16
17
|
|
|
17
18
|
|
|
@@ -108,12 +109,16 @@ class CachingStateSync(DelegatingStateSync):
|
|
|
108
109
|
self.state_sync.delete_snapshots(snapshot_ids)
|
|
109
110
|
|
|
110
111
|
def delete_expired_snapshots(
|
|
111
|
-
self,
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
self,
|
|
113
|
+
batch_range: ExpiredBatchRange,
|
|
114
|
+
ignore_ttl: bool = False,
|
|
115
|
+
current_ts: t.Optional[int] = None,
|
|
116
|
+
) -> None:
|
|
114
117
|
self.snapshot_cache.clear()
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
self.state_sync.delete_expired_snapshots(
|
|
119
|
+
batch_range=batch_range,
|
|
120
|
+
ignore_ttl=ignore_ttl,
|
|
121
|
+
current_ts=current_ts,
|
|
117
122
|
)
|
|
118
123
|
|
|
119
124
|
def add_snapshots_intervals(self, snapshots_intervals: t.Sequence[SnapshotIntervals]) -> None:
|
|
@@ -131,7 +136,7 @@ class CachingStateSync(DelegatingStateSync):
|
|
|
131
136
|
|
|
132
137
|
def remove_intervals(
|
|
133
138
|
self,
|
|
134
|
-
snapshot_intervals: t.Sequence[t.Tuple[
|
|
139
|
+
snapshot_intervals: t.Sequence[t.Tuple[SnapshotIdAndVersionLike, Interval]],
|
|
135
140
|
remove_shared_versions: bool = False,
|
|
136
141
|
) -> None:
|
|
137
142
|
for s, _ in snapshot_intervals:
|
|
@@ -7,124 +7,25 @@ import itertools
|
|
|
7
7
|
import abc
|
|
8
8
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from pydantic_core.core_schema import ValidationInfo
|
|
10
12
|
from sqlglot import exp
|
|
11
13
|
|
|
12
|
-
from sqlmesh.
|
|
13
|
-
from sqlmesh.core.
|
|
14
|
-
from sqlmesh.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
from sqlmesh.utils.pydantic import PydanticModel, field_validator
|
|
15
|
+
from sqlmesh.core.environment import Environment, EnvironmentStatements, EnvironmentNamingInfo
|
|
16
|
+
from sqlmesh.core.snapshot import (
|
|
17
|
+
Snapshot,
|
|
18
|
+
SnapshotId,
|
|
19
|
+
SnapshotTableCleanupTask,
|
|
20
|
+
SnapshotTableInfo,
|
|
21
|
+
)
|
|
18
22
|
|
|
19
23
|
if t.TYPE_CHECKING:
|
|
20
|
-
from sqlmesh.core.
|
|
21
|
-
from sqlmesh.core.state_sync.base import Versions
|
|
24
|
+
from sqlmesh.core.state_sync.base import Versions, StateReader
|
|
22
25
|
|
|
23
26
|
logger = logging.getLogger(__name__)
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
def cleanup_expired_views(
|
|
27
|
-
default_adapter: EngineAdapter,
|
|
28
|
-
engine_adapters: t.Dict[str, EngineAdapter],
|
|
29
|
-
environments: t.List[Environment],
|
|
30
|
-
warn_on_delete_failure: bool = False,
|
|
31
|
-
console: t.Optional[Console] = None,
|
|
32
|
-
) -> None:
|
|
33
|
-
expired_schema_or_catalog_environments = [
|
|
34
|
-
environment
|
|
35
|
-
for environment in environments
|
|
36
|
-
if environment.suffix_target.is_schema or environment.suffix_target.is_catalog
|
|
37
|
-
]
|
|
38
|
-
expired_table_environments = [
|
|
39
|
-
environment for environment in environments if environment.suffix_target.is_table
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
# We have to use the corresponding adapter if the virtual layer is gateway managed
|
|
43
|
-
def get_adapter(gateway_managed: bool, gateway: t.Optional[str] = None) -> EngineAdapter:
|
|
44
|
-
if gateway_managed and gateway:
|
|
45
|
-
return engine_adapters.get(gateway, default_adapter)
|
|
46
|
-
return default_adapter
|
|
47
|
-
|
|
48
|
-
catalogs_to_drop: t.Set[t.Tuple[EngineAdapter, str]] = set()
|
|
49
|
-
schemas_to_drop: t.Set[t.Tuple[EngineAdapter, exp.Table]] = set()
|
|
50
|
-
|
|
51
|
-
# Collect schemas and catalogs to drop
|
|
52
|
-
for engine_adapter, expired_catalog, expired_schema, suffix_target in {
|
|
53
|
-
(
|
|
54
|
-
(engine_adapter := get_adapter(environment.gateway_managed, snapshot.model_gateway)),
|
|
55
|
-
snapshot.qualified_view_name.catalog_for_environment(
|
|
56
|
-
environment.naming_info, dialect=engine_adapter.dialect
|
|
57
|
-
),
|
|
58
|
-
snapshot.qualified_view_name.schema_for_environment(
|
|
59
|
-
environment.naming_info, dialect=engine_adapter.dialect
|
|
60
|
-
),
|
|
61
|
-
environment.suffix_target,
|
|
62
|
-
)
|
|
63
|
-
for environment in expired_schema_or_catalog_environments
|
|
64
|
-
for snapshot in environment.snapshots
|
|
65
|
-
if snapshot.is_model and not snapshot.is_symbolic
|
|
66
|
-
}:
|
|
67
|
-
if suffix_target.is_catalog:
|
|
68
|
-
if expired_catalog:
|
|
69
|
-
catalogs_to_drop.add((engine_adapter, expired_catalog))
|
|
70
|
-
else:
|
|
71
|
-
schema = schema_(expired_schema, expired_catalog)
|
|
72
|
-
schemas_to_drop.add((engine_adapter, schema))
|
|
73
|
-
|
|
74
|
-
# Drop the views for the expired environments
|
|
75
|
-
for engine_adapter, expired_view in {
|
|
76
|
-
(
|
|
77
|
-
(engine_adapter := get_adapter(environment.gateway_managed, snapshot.model_gateway)),
|
|
78
|
-
snapshot.qualified_view_name.for_environment(
|
|
79
|
-
environment.naming_info, dialect=engine_adapter.dialect
|
|
80
|
-
),
|
|
81
|
-
)
|
|
82
|
-
for environment in expired_table_environments
|
|
83
|
-
for snapshot in environment.snapshots
|
|
84
|
-
if snapshot.is_model and not snapshot.is_symbolic
|
|
85
|
-
}:
|
|
86
|
-
try:
|
|
87
|
-
engine_adapter.drop_view(expired_view, ignore_if_not_exists=True)
|
|
88
|
-
if console:
|
|
89
|
-
console.update_cleanup_progress(expired_view)
|
|
90
|
-
except Exception as e:
|
|
91
|
-
message = f"Failed to drop the expired environment view '{expired_view}': {e}"
|
|
92
|
-
if warn_on_delete_failure:
|
|
93
|
-
logger.warning(message)
|
|
94
|
-
else:
|
|
95
|
-
raise SQLMeshError(message) from e
|
|
96
|
-
|
|
97
|
-
# Drop the schemas for the expired environments
|
|
98
|
-
for engine_adapter, schema in schemas_to_drop:
|
|
99
|
-
try:
|
|
100
|
-
engine_adapter.drop_schema(
|
|
101
|
-
schema,
|
|
102
|
-
ignore_if_not_exists=True,
|
|
103
|
-
cascade=True,
|
|
104
|
-
)
|
|
105
|
-
if console:
|
|
106
|
-
console.update_cleanup_progress(schema.sql(dialect=engine_adapter.dialect))
|
|
107
|
-
except Exception as e:
|
|
108
|
-
message = f"Failed to drop the expired environment schema '{schema}': {e}"
|
|
109
|
-
if warn_on_delete_failure:
|
|
110
|
-
logger.warning(message)
|
|
111
|
-
else:
|
|
112
|
-
raise SQLMeshError(message) from e
|
|
113
|
-
|
|
114
|
-
# Drop any catalogs that were associated with a snapshot where the engine adapter supports dropping catalogs
|
|
115
|
-
# catalogs_to_drop is only populated when environment_suffix_target is set to 'catalog'
|
|
116
|
-
for engine_adapter, catalog in catalogs_to_drop:
|
|
117
|
-
if engine_adapter.SUPPORTS_CREATE_DROP_CATALOG:
|
|
118
|
-
try:
|
|
119
|
-
engine_adapter.drop_catalog(catalog)
|
|
120
|
-
if console:
|
|
121
|
-
console.update_cleanup_progress(catalog)
|
|
122
|
-
except Exception as e:
|
|
123
|
-
message = f"Failed to drop the expired environment catalog '{catalog}': {e}"
|
|
124
|
-
if warn_on_delete_failure:
|
|
125
|
-
logger.warning(message)
|
|
126
|
-
else:
|
|
127
|
-
raise SQLMeshError(message) from e
|
|
28
|
+
EXPIRED_SNAPSHOT_DEFAULT_BATCH_SIZE = 200
|
|
128
29
|
|
|
129
30
|
|
|
130
31
|
def transactional() -> t.Callable[[t.Callable], t.Callable]:
|
|
@@ -215,3 +116,207 @@ class StateStream(abc.ABC):
|
|
|
215
116
|
yield EnvironmentsChunk(environments)
|
|
216
117
|
|
|
217
118
|
return _StateStream()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ExpiredBatchRange(PydanticModel):
|
|
122
|
+
start: RowBoundary
|
|
123
|
+
end: t.Union[RowBoundary, LimitBoundary]
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def init_batch_range(cls, batch_size: int) -> ExpiredBatchRange:
|
|
127
|
+
return ExpiredBatchRange(
|
|
128
|
+
start=RowBoundary.lowest_boundary(),
|
|
129
|
+
end=LimitBoundary(batch_size=batch_size),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def all_batch_range(cls) -> ExpiredBatchRange:
|
|
134
|
+
return ExpiredBatchRange(
|
|
135
|
+
start=RowBoundary.lowest_boundary(),
|
|
136
|
+
end=RowBoundary.highest_boundary(),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def _expanded_tuple_comparison(
|
|
141
|
+
cls,
|
|
142
|
+
columns: t.List[exp.Column],
|
|
143
|
+
values: t.List[exp.Literal],
|
|
144
|
+
operator: t.Type[exp.Expression],
|
|
145
|
+
) -> exp.Expression:
|
|
146
|
+
"""Generate expanded tuple comparison that works across all SQL engines.
|
|
147
|
+
|
|
148
|
+
Converts tuple comparisons like (a, b, c) OP (x, y, z) into an expanded form
|
|
149
|
+
that's compatible with all SQL engines, since native tuple comparisons have
|
|
150
|
+
inconsistent support across engines (especially DuckDB, MySQL, SQLite).
|
|
151
|
+
|
|
152
|
+
Repro of problem with DuckDB:
|
|
153
|
+
"SELECT * FROM VALUES(1,'2') as test(a,b) WHERE ((a, b) > (1, 'foo')) AND ((a, b) <= (10, 'baz'))"
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
columns: List of column expressions to compare
|
|
157
|
+
values: List of value expressions to compare against
|
|
158
|
+
operator: The comparison operator class (exp.GT, exp.GTE, exp.LT, exp.LTE)
|
|
159
|
+
|
|
160
|
+
Examples:
|
|
161
|
+
(a, b, c) > (x, y, z) expands to:
|
|
162
|
+
a > x OR (a = x AND b > y) OR (a = x AND b = y AND c > z)
|
|
163
|
+
|
|
164
|
+
(a, b, c) <= (x, y, z) expands to:
|
|
165
|
+
a < x OR (a = x AND b < y) OR (a = x AND b = y AND c <= z)
|
|
166
|
+
|
|
167
|
+
(a, b, c) >= (x, y, z) expands to:
|
|
168
|
+
a > x OR (a = x AND b > y) OR (a = x AND b = y AND c >= z)
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
An expanded OR expression representing the tuple comparison
|
|
172
|
+
"""
|
|
173
|
+
if operator not in (exp.GT, exp.GTE, exp.LT, exp.LTE):
|
|
174
|
+
raise ValueError(f"Unsupported operator: {operator}. Use GT, GTE, LT, or LTE.")
|
|
175
|
+
|
|
176
|
+
# For <= and >=, we use the strict operator for all but the last column
|
|
177
|
+
# e.g., (a, b) <= (x, y) becomes: a < x OR (a = x AND b <= y)
|
|
178
|
+
# For < and >, we use the strict operator throughout
|
|
179
|
+
# e.g., (a, b) > (x, y) becomes: a > x OR (a = x AND b > x)
|
|
180
|
+
strict_operator: t.Type[exp.Expression]
|
|
181
|
+
final_operator: t.Type[exp.Expression]
|
|
182
|
+
|
|
183
|
+
if operator in (exp.LTE, exp.GTE):
|
|
184
|
+
# For inclusive operators (<=, >=), use strict form for intermediate columns
|
|
185
|
+
# but keep inclusive form for the last column
|
|
186
|
+
strict_operator = exp.LT if operator == exp.LTE else exp.GT
|
|
187
|
+
final_operator = operator # Keep LTE/GTE for last column
|
|
188
|
+
else:
|
|
189
|
+
# For strict operators (<, >), use them throughout
|
|
190
|
+
strict_operator = operator
|
|
191
|
+
final_operator = operator
|
|
192
|
+
|
|
193
|
+
conditions: t.List[exp.Expression] = []
|
|
194
|
+
for i in range(len(columns)):
|
|
195
|
+
# Build equality conditions for all columns before current
|
|
196
|
+
equality_conditions = [exp.EQ(this=columns[j], expression=values[j]) for j in range(i)]
|
|
197
|
+
|
|
198
|
+
# Use the final operator for the last column, strict for others
|
|
199
|
+
comparison_op = final_operator if i == len(columns) - 1 else strict_operator
|
|
200
|
+
comparison_condition = comparison_op(this=columns[i], expression=values[i])
|
|
201
|
+
|
|
202
|
+
if equality_conditions:
|
|
203
|
+
conditions.append(exp.and_(*equality_conditions, comparison_condition))
|
|
204
|
+
else:
|
|
205
|
+
conditions.append(comparison_condition)
|
|
206
|
+
|
|
207
|
+
return exp.or_(*conditions) if len(conditions) > 1 else conditions[0]
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def where_filter(self) -> exp.Expression:
|
|
211
|
+
# Use expanded tuple comparisons for cross-engine compatibility
|
|
212
|
+
# Native tuple comparisons like (a, b) > (x, y) don't work reliably across all SQL engines
|
|
213
|
+
columns = [
|
|
214
|
+
exp.column("updated_ts"),
|
|
215
|
+
exp.column("name"),
|
|
216
|
+
exp.column("identifier"),
|
|
217
|
+
]
|
|
218
|
+
start_values = [
|
|
219
|
+
exp.Literal.number(self.start.updated_ts),
|
|
220
|
+
exp.Literal.string(self.start.name),
|
|
221
|
+
exp.Literal.string(self.start.identifier),
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
start_condition = self._expanded_tuple_comparison(columns, start_values, exp.GT)
|
|
225
|
+
|
|
226
|
+
range_filter: exp.Expression
|
|
227
|
+
if isinstance(self.end, RowBoundary):
|
|
228
|
+
end_values = [
|
|
229
|
+
exp.Literal.number(self.end.updated_ts),
|
|
230
|
+
exp.Literal.string(self.end.name),
|
|
231
|
+
exp.Literal.string(self.end.identifier),
|
|
232
|
+
]
|
|
233
|
+
end_condition = self._expanded_tuple_comparison(columns, end_values, exp.LTE)
|
|
234
|
+
range_filter = exp.and_(start_condition, end_condition)
|
|
235
|
+
else:
|
|
236
|
+
range_filter = start_condition
|
|
237
|
+
return range_filter
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class RowBoundary(PydanticModel):
|
|
241
|
+
updated_ts: int
|
|
242
|
+
name: str
|
|
243
|
+
identifier: str
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def lowest_boundary(cls) -> RowBoundary:
|
|
247
|
+
return RowBoundary(updated_ts=0, name="", identifier="")
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def highest_boundary(cls) -> RowBoundary:
|
|
251
|
+
# 9999-12-31T23:59:59.999Z in epoch milliseconds
|
|
252
|
+
return RowBoundary(updated_ts=253_402_300_799_999, name="", identifier="")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class LimitBoundary(PydanticModel):
|
|
256
|
+
batch_size: int
|
|
257
|
+
|
|
258
|
+
@classmethod
|
|
259
|
+
def init_batch_boundary(cls, batch_size: int) -> LimitBoundary:
|
|
260
|
+
return LimitBoundary(batch_size=batch_size)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class PromotionResult(PydanticModel):
|
|
264
|
+
added: t.List[SnapshotTableInfo]
|
|
265
|
+
removed: t.List[SnapshotTableInfo]
|
|
266
|
+
removed_environment_naming_info: t.Optional[EnvironmentNamingInfo]
|
|
267
|
+
|
|
268
|
+
@field_validator("removed_environment_naming_info")
|
|
269
|
+
def _validate_removed_environment_naming_info(
|
|
270
|
+
cls, v: t.Optional[EnvironmentNamingInfo], info: ValidationInfo
|
|
271
|
+
) -> t.Optional[EnvironmentNamingInfo]:
|
|
272
|
+
if v and not info.data.get("removed"):
|
|
273
|
+
raise ValueError("removed_environment_naming_info must be None if removed is empty")
|
|
274
|
+
return v
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class ExpiredSnapshotBatch(PydanticModel):
|
|
278
|
+
"""A batch of expired snapshots to be cleaned up."""
|
|
279
|
+
|
|
280
|
+
expired_snapshot_ids: t.Set[SnapshotId]
|
|
281
|
+
cleanup_tasks: t.List[SnapshotTableCleanupTask]
|
|
282
|
+
batch_range: ExpiredBatchRange
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def iter_expired_snapshot_batches(
|
|
286
|
+
state_reader: StateReader,
|
|
287
|
+
*,
|
|
288
|
+
current_ts: int,
|
|
289
|
+
ignore_ttl: bool = False,
|
|
290
|
+
batch_size: t.Optional[int] = None,
|
|
291
|
+
) -> t.Iterator[ExpiredSnapshotBatch]:
|
|
292
|
+
"""Yields expired snapshot batches.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
state_reader: StateReader instance to query expired snapshots from.
|
|
296
|
+
current_ts: Timestamp used to evaluate expiration.
|
|
297
|
+
ignore_ttl: If True, include snapshots regardless of TTL (only checks if unreferenced).
|
|
298
|
+
batch_size: Maximum number of snapshots to fetch per batch.
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
batch_size = batch_size if batch_size is not None else EXPIRED_SNAPSHOT_DEFAULT_BATCH_SIZE
|
|
302
|
+
batch_range = ExpiredBatchRange.init_batch_range(batch_size=batch_size)
|
|
303
|
+
|
|
304
|
+
while True:
|
|
305
|
+
batch = state_reader.get_expired_snapshots(
|
|
306
|
+
current_ts=current_ts,
|
|
307
|
+
ignore_ttl=ignore_ttl,
|
|
308
|
+
batch_range=batch_range,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if batch is None:
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
yield batch
|
|
315
|
+
|
|
316
|
+
assert isinstance(batch.batch_range.end, RowBoundary), (
|
|
317
|
+
"Only RowBoundary is supported for pagination currently"
|
|
318
|
+
)
|
|
319
|
+
batch_range = ExpiredBatchRange(
|
|
320
|
+
start=batch.batch_range.end,
|
|
321
|
+
end=LimitBoundary(batch_size=batch_size),
|
|
322
|
+
)
|
|
@@ -285,11 +285,13 @@ class EnvironmentState:
|
|
|
285
285
|
return []
|
|
286
286
|
|
|
287
287
|
def _environment_from_row(self, row: t.Tuple[str, ...]) -> Environment:
|
|
288
|
-
return Environment(
|
|
288
|
+
return Environment(
|
|
289
|
+
**{field: row[i] for i, field in enumerate(sorted(Environment.all_fields()))}
|
|
290
|
+
)
|
|
289
291
|
|
|
290
292
|
def _environment_summmary_from_row(self, row: t.Tuple[str, ...]) -> EnvironmentSummary:
|
|
291
293
|
return EnvironmentSummary(
|
|
292
|
-
**{field: row[i] for i, field in enumerate(EnvironmentSummary.all_fields())}
|
|
294
|
+
**{field: row[i] for i, field in enumerate(sorted(EnvironmentSummary.all_fields()))}
|
|
293
295
|
)
|
|
294
296
|
|
|
295
297
|
def _environments_query(
|
|
@@ -298,7 +300,7 @@ class EnvironmentState:
|
|
|
298
300
|
lock_for_update: bool = False,
|
|
299
301
|
required_fields: t.Optional[t.List[str]] = None,
|
|
300
302
|
) -> exp.Select:
|
|
301
|
-
query_fields = required_fields if required_fields else Environment.all_fields()
|
|
303
|
+
query_fields = required_fields if required_fields else sorted(Environment.all_fields())
|
|
302
304
|
query = (
|
|
303
305
|
exp.select(*(exp.to_identifier(field) for field in query_fields))
|
|
304
306
|
.from_(self.environments_table)
|
|
@@ -328,7 +330,7 @@ class EnvironmentState:
|
|
|
328
330
|
self.engine_adapter,
|
|
329
331
|
self._environments_query(
|
|
330
332
|
where=where,
|
|
331
|
-
required_fields=
|
|
333
|
+
required_fields=sorted(EnvironmentSummary.all_fields()),
|
|
332
334
|
),
|
|
333
335
|
)
|
|
334
336
|
]
|