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.
Files changed (252) hide show
  1. sqlmesh/__init__.py +12 -2
  2. sqlmesh/_version.py +2 -2
  3. sqlmesh/cli/main.py +0 -44
  4. sqlmesh/cli/project_init.py +11 -2
  5. sqlmesh/core/_typing.py +1 -0
  6. sqlmesh/core/audit/definition.py +8 -2
  7. sqlmesh/core/config/__init__.py +1 -1
  8. sqlmesh/core/config/connection.py +17 -5
  9. sqlmesh/core/config/dbt.py +13 -0
  10. sqlmesh/core/config/janitor.py +12 -0
  11. sqlmesh/core/config/loader.py +7 -0
  12. sqlmesh/core/config/model.py +2 -0
  13. sqlmesh/core/config/root.py +3 -0
  14. sqlmesh/core/console.py +81 -3
  15. sqlmesh/core/constants.py +1 -1
  16. sqlmesh/core/context.py +69 -26
  17. sqlmesh/core/dialect.py +3 -0
  18. sqlmesh/core/engine_adapter/_typing.py +2 -0
  19. sqlmesh/core/engine_adapter/base.py +322 -22
  20. sqlmesh/core/engine_adapter/base_postgres.py +17 -1
  21. sqlmesh/core/engine_adapter/bigquery.py +146 -7
  22. sqlmesh/core/engine_adapter/clickhouse.py +17 -13
  23. sqlmesh/core/engine_adapter/databricks.py +33 -2
  24. sqlmesh/core/engine_adapter/fabric.py +10 -29
  25. sqlmesh/core/engine_adapter/mixins.py +142 -48
  26. sqlmesh/core/engine_adapter/mssql.py +15 -4
  27. sqlmesh/core/engine_adapter/mysql.py +2 -2
  28. sqlmesh/core/engine_adapter/postgres.py +9 -3
  29. sqlmesh/core/engine_adapter/redshift.py +4 -0
  30. sqlmesh/core/engine_adapter/risingwave.py +1 -0
  31. sqlmesh/core/engine_adapter/shared.py +6 -0
  32. sqlmesh/core/engine_adapter/snowflake.py +82 -11
  33. sqlmesh/core/engine_adapter/spark.py +14 -10
  34. sqlmesh/core/engine_adapter/trino.py +4 -2
  35. sqlmesh/core/environment.py +2 -0
  36. sqlmesh/core/janitor.py +181 -0
  37. sqlmesh/core/lineage.py +1 -0
  38. sqlmesh/core/linter/definition.py +13 -13
  39. sqlmesh/core/linter/rules/builtin.py +29 -0
  40. sqlmesh/core/macros.py +35 -13
  41. sqlmesh/core/model/common.py +2 -0
  42. sqlmesh/core/model/definition.py +82 -28
  43. sqlmesh/core/model/kind.py +66 -2
  44. sqlmesh/core/model/meta.py +108 -4
  45. sqlmesh/core/node.py +101 -1
  46. sqlmesh/core/plan/builder.py +18 -10
  47. sqlmesh/core/plan/common.py +199 -2
  48. sqlmesh/core/plan/definition.py +25 -6
  49. sqlmesh/core/plan/evaluator.py +75 -113
  50. sqlmesh/core/plan/explainer.py +90 -8
  51. sqlmesh/core/plan/stages.py +42 -21
  52. sqlmesh/core/renderer.py +78 -32
  53. sqlmesh/core/scheduler.py +102 -22
  54. sqlmesh/core/selector.py +137 -9
  55. sqlmesh/core/signal.py +64 -1
  56. sqlmesh/core/snapshot/__init__.py +2 -0
  57. sqlmesh/core/snapshot/definition.py +146 -34
  58. sqlmesh/core/snapshot/evaluator.py +689 -124
  59. sqlmesh/core/state_sync/__init__.py +0 -1
  60. sqlmesh/core/state_sync/base.py +55 -33
  61. sqlmesh/core/state_sync/cache.py +12 -7
  62. sqlmesh/core/state_sync/common.py +216 -111
  63. sqlmesh/core/state_sync/db/environment.py +6 -4
  64. sqlmesh/core/state_sync/db/facade.py +42 -24
  65. sqlmesh/core/state_sync/db/interval.py +27 -7
  66. sqlmesh/core/state_sync/db/migrator.py +34 -16
  67. sqlmesh/core/state_sync/db/snapshot.py +177 -169
  68. sqlmesh/core/table_diff.py +2 -2
  69. sqlmesh/core/test/context.py +2 -0
  70. sqlmesh/core/test/definition.py +14 -9
  71. sqlmesh/dbt/adapter.py +22 -16
  72. sqlmesh/dbt/basemodel.py +75 -56
  73. sqlmesh/dbt/builtin.py +116 -12
  74. sqlmesh/dbt/column.py +17 -5
  75. sqlmesh/dbt/common.py +19 -5
  76. sqlmesh/dbt/context.py +14 -1
  77. sqlmesh/dbt/loader.py +61 -9
  78. sqlmesh/dbt/manifest.py +174 -16
  79. sqlmesh/dbt/model.py +183 -85
  80. sqlmesh/dbt/package.py +16 -1
  81. sqlmesh/dbt/profile.py +3 -3
  82. sqlmesh/dbt/project.py +12 -7
  83. sqlmesh/dbt/seed.py +6 -1
  84. sqlmesh/dbt/source.py +13 -1
  85. sqlmesh/dbt/target.py +25 -6
  86. sqlmesh/dbt/test.py +36 -5
  87. sqlmesh/migrations/v0000_baseline.py +95 -0
  88. sqlmesh/migrations/v0061_mysql_fix_blob_text_type.py +5 -7
  89. sqlmesh/migrations/v0062_add_model_gateway.py +5 -1
  90. sqlmesh/migrations/v0063_change_signals.py +5 -3
  91. sqlmesh/migrations/v0064_join_when_matched_strings.py +5 -3
  92. sqlmesh/migrations/v0065_add_model_optimize.py +5 -1
  93. sqlmesh/migrations/v0066_add_auto_restatements.py +8 -3
  94. sqlmesh/migrations/v0067_add_tsql_date_full_precision.py +5 -1
  95. sqlmesh/migrations/v0068_include_unrendered_query_in_metadata_hash.py +5 -1
  96. sqlmesh/migrations/v0069_update_dev_table_suffix.py +5 -3
  97. sqlmesh/migrations/v0070_include_grains_in_metadata_hash.py +5 -1
  98. sqlmesh/migrations/v0071_add_dev_version_to_intervals.py +9 -5
  99. sqlmesh/migrations/v0072_add_environment_statements.py +5 -3
  100. sqlmesh/migrations/v0073_remove_symbolic_disable_restatement.py +5 -3
  101. sqlmesh/migrations/v0074_add_partition_by_time_column_property.py +5 -1
  102. sqlmesh/migrations/v0075_remove_validate_query.py +5 -3
  103. sqlmesh/migrations/v0076_add_cron_tz.py +5 -1
  104. sqlmesh/migrations/v0077_fix_column_type_hash_calculation.py +5 -1
  105. sqlmesh/migrations/v0078_warn_if_non_migratable_python_env.py +5 -3
  106. sqlmesh/migrations/v0079_add_gateway_managed_property.py +10 -5
  107. sqlmesh/migrations/v0080_add_batch_size_to_scd_type_2_models.py +5 -1
  108. sqlmesh/migrations/v0081_update_partitioned_by.py +5 -3
  109. sqlmesh/migrations/v0082_warn_if_incorrectly_duplicated_statements.py +5 -3
  110. sqlmesh/migrations/v0083_use_sql_for_scd_time_data_type_data_hash.py +5 -1
  111. sqlmesh/migrations/v0084_normalize_quote_when_matched_and_merge_filter.py +5 -1
  112. sqlmesh/migrations/v0085_deterministic_repr.py +5 -3
  113. sqlmesh/migrations/v0086_check_deterministic_bug.py +5 -3
  114. sqlmesh/migrations/v0087_normalize_blueprint_variables.py +5 -3
  115. sqlmesh/migrations/v0088_warn_about_variable_python_env_diffs.py +5 -3
  116. sqlmesh/migrations/v0089_add_virtual_environment_mode.py +5 -1
  117. sqlmesh/migrations/v0090_add_forward_only_column.py +9 -5
  118. sqlmesh/migrations/v0091_on_additive_change.py +5 -1
  119. sqlmesh/migrations/v0092_warn_about_dbt_data_type_diff.py +5 -3
  120. sqlmesh/migrations/v0093_use_raw_sql_in_fingerprint.py +5 -1
  121. sqlmesh/migrations/v0094_add_dev_version_and_fingerprint_columns.py +123 -0
  122. sqlmesh/migrations/v0095_warn_about_dbt_raw_sql_diff.py +49 -0
  123. sqlmesh/migrations/v0096_remove_plan_dags_table.py +13 -0
  124. sqlmesh/migrations/v0097_add_dbt_name_in_node.py +9 -0
  125. sqlmesh/migrations/{v0060_move_audits_to_model.py → v0098_add_dbt_node_info_in_node.py} +33 -16
  126. sqlmesh/migrations/v0099_add_last_altered_to_intervals.py +25 -0
  127. sqlmesh/migrations/v0100_add_grants_and_grants_target_layer.py +9 -0
  128. sqlmesh/utils/__init__.py +8 -1
  129. sqlmesh/utils/cache.py +5 -1
  130. sqlmesh/utils/connection_pool.py +2 -1
  131. sqlmesh/utils/dag.py +65 -10
  132. sqlmesh/utils/date.py +8 -1
  133. sqlmesh/utils/errors.py +8 -0
  134. sqlmesh/utils/jinja.py +54 -4
  135. sqlmesh/utils/pydantic.py +6 -6
  136. sqlmesh/utils/windows.py +13 -3
  137. {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/METADATA +7 -10
  138. sqlmesh-0.227.2.dev4.dist-info/RECORD +370 -0
  139. sqlmesh_dbt/cli.py +70 -7
  140. sqlmesh_dbt/console.py +14 -6
  141. sqlmesh_dbt/operations.py +103 -24
  142. sqlmesh_dbt/selectors.py +39 -1
  143. web/client/dist/assets/{Audits-Ucsx1GzF.js → Audits-CBiYyyx-.js} +1 -1
  144. web/client/dist/assets/{Banner-BWDzvavM.js → Banner-DSRbUlO5.js} +1 -1
  145. web/client/dist/assets/{ChevronDownIcon-D2VL13Ah.js → ChevronDownIcon-MK_nrjD_.js} +1 -1
  146. web/client/dist/assets/{ChevronRightIcon-DWGYbf1l.js → ChevronRightIcon-CLWtT22Q.js} +1 -1
  147. web/client/dist/assets/{Content-DdHDZM3I.js → Content-BNuGZN5l.js} +1 -1
  148. web/client/dist/assets/{Content-Bikfy8fh.js → Content-CSHJyW0n.js} +1 -1
  149. web/client/dist/assets/{Data-CzAJH7rW.js → Data-C1oRDbLx.js} +1 -1
  150. web/client/dist/assets/{DataCatalog-BJF11g8f.js → DataCatalog-HXyX2-_j.js} +1 -1
  151. web/client/dist/assets/{Editor-s0SBpV2y.js → Editor-BDyfpUuw.js} +1 -1
  152. web/client/dist/assets/{Editor-DgLhgKnm.js → Editor-D0jNItwC.js} +1 -1
  153. web/client/dist/assets/{Errors-D0m0O1d3.js → Errors-BfuFLcPi.js} +1 -1
  154. web/client/dist/assets/{FileExplorer-CEv0vXkt.js → FileExplorer-BR9IE3he.js} +1 -1
  155. web/client/dist/assets/{Footer-BwzXn8Ew.js → Footer-CgBEtiAh.js} +1 -1
  156. web/client/dist/assets/{Header-6heDkEqG.js → Header-DSqR6nSO.js} +1 -1
  157. web/client/dist/assets/{Input-obuJsD6k.js → Input-B-oZ6fGO.js} +1 -1
  158. web/client/dist/assets/Lineage-DYQVwDbD.js +1 -0
  159. web/client/dist/assets/{ListboxShow-HM9_qyrt.js → ListboxShow-BE5-xevs.js} +1 -1
  160. web/client/dist/assets/{ModelLineage-zWdKo0U2.js → ModelLineage-DkIFAYo4.js} +1 -1
  161. web/client/dist/assets/{Models-Bcu66SRz.js → Models-D5dWr8RB.js} +1 -1
  162. web/client/dist/assets/{Page-BWEEQfIt.js → Page-C-XfU5BR.js} +1 -1
  163. web/client/dist/assets/{Plan-C4gXCqlf.js → Plan-ZEuTINBq.js} +1 -1
  164. web/client/dist/assets/{PlusCircleIcon-CVDO651q.js → PlusCircleIcon-DVXAHG8_.js} +1 -1
  165. web/client/dist/assets/{ReportErrors-BT6xFwAr.js → ReportErrors-B7FEPzMB.js} +1 -1
  166. web/client/dist/assets/{Root-ryJoBK4h.js → Root-8aZyhPxF.js} +1 -1
  167. web/client/dist/assets/{SearchList-DB04sPb9.js → SearchList-W_iT2G82.js} +1 -1
  168. web/client/dist/assets/{SelectEnvironment-CUYcXUu6.js → SelectEnvironment-C65jALmO.js} +1 -1
  169. web/client/dist/assets/{SourceList-Doo_9ZGp.js → SourceList-DSLO6nVJ.js} +1 -1
  170. web/client/dist/assets/{SourceListItem-D5Mj7Dly.js → SourceListItem-BHt8d9-I.js} +1 -1
  171. web/client/dist/assets/{SplitPane-qHmkD1qy.js → SplitPane-CViaZmw6.js} +1 -1
  172. web/client/dist/assets/{Tests-DH1Z74ML.js → Tests-DhaVt5t1.js} +1 -1
  173. web/client/dist/assets/{Welcome-DqUJUNMF.js → Welcome-DvpjH-_4.js} +1 -1
  174. web/client/dist/assets/context-BctCsyGb.js +71 -0
  175. web/client/dist/assets/{context-Dr54UHLi.js → context-DFNeGsFF.js} +1 -1
  176. web/client/dist/assets/{editor-DYIP1yQ4.js → editor-CcO28cqd.js} +1 -1
  177. web/client/dist/assets/{file-DarlIDVi.js → file-CvJN3aZO.js} +1 -1
  178. web/client/dist/assets/{floating-ui.react-dom-BH3TFvkM.js → floating-ui.react-dom-CjE-JNW1.js} +1 -1
  179. web/client/dist/assets/{help-Bl8wqaQc.js → help-DuPhjipa.js} +1 -1
  180. web/client/dist/assets/{index-D1sR7wpN.js → index-C-dJH7yZ.js} +1 -1
  181. web/client/dist/assets/{index-O3mjYpnE.js → index-Dj0i1-CA.js} +2 -2
  182. web/client/dist/assets/{plan-CehRrJUG.js → plan-BTRSbjKn.js} +1 -1
  183. web/client/dist/assets/{popover-CqgMRE0G.js → popover-_Sf0yvOI.js} +1 -1
  184. web/client/dist/assets/{project-6gxepOhm.js → project-BvSOI8MY.js} +1 -1
  185. web/client/dist/index.html +1 -1
  186. sqlmesh/integrations/llm.py +0 -56
  187. sqlmesh/migrations/v0001_init.py +0 -60
  188. sqlmesh/migrations/v0002_remove_identify.py +0 -5
  189. sqlmesh/migrations/v0003_move_batch_size.py +0 -34
  190. sqlmesh/migrations/v0004_environmnent_add_finalized_at.py +0 -23
  191. sqlmesh/migrations/v0005_create_seed_table.py +0 -24
  192. sqlmesh/migrations/v0006_change_seed_hash.py +0 -5
  193. sqlmesh/migrations/v0007_env_table_info_to_kind.py +0 -99
  194. sqlmesh/migrations/v0008_create_intervals_table.py +0 -38
  195. sqlmesh/migrations/v0009_remove_pre_post_hooks.py +0 -62
  196. sqlmesh/migrations/v0010_seed_hash_batch_size.py +0 -5
  197. sqlmesh/migrations/v0011_add_model_kind_name.py +0 -63
  198. sqlmesh/migrations/v0012_update_jinja_expressions.py +0 -86
  199. sqlmesh/migrations/v0013_serde_using_model_dialects.py +0 -87
  200. sqlmesh/migrations/v0014_fix_dev_intervals.py +0 -14
  201. sqlmesh/migrations/v0015_environment_add_promoted_snapshot_ids.py +0 -26
  202. sqlmesh/migrations/v0016_fix_windows_path.py +0 -59
  203. sqlmesh/migrations/v0017_fix_windows_seed_path.py +0 -55
  204. sqlmesh/migrations/v0018_rename_snapshot_model_to_node.py +0 -53
  205. sqlmesh/migrations/v0019_add_env_suffix_target.py +0 -28
  206. sqlmesh/migrations/v0020_remove_redundant_attributes_from_dbt_models.py +0 -80
  207. sqlmesh/migrations/v0021_fix_table_properties.py +0 -62
  208. sqlmesh/migrations/v0022_move_project_to_model.py +0 -54
  209. sqlmesh/migrations/v0023_fix_added_models_with_forward_only_parents.py +0 -65
  210. sqlmesh/migrations/v0024_replace_model_kind_name_enum_with_value.py +0 -55
  211. sqlmesh/migrations/v0025_fix_intervals_and_missing_change_category.py +0 -117
  212. sqlmesh/migrations/v0026_remove_dialect_from_seed.py +0 -55
  213. sqlmesh/migrations/v0027_minute_interval_to_five.py +0 -57
  214. sqlmesh/migrations/v0028_add_plan_dags_table.py +0 -29
  215. sqlmesh/migrations/v0029_generate_schema_types_using_dialect.py +0 -69
  216. sqlmesh/migrations/v0030_update_unrestorable_snapshots.py +0 -65
  217. sqlmesh/migrations/v0031_remove_dbt_target_fields.py +0 -65
  218. sqlmesh/migrations/v0032_add_sqlmesh_version.py +0 -25
  219. sqlmesh/migrations/v0033_mysql_fix_blob_text_type.py +0 -45
  220. sqlmesh/migrations/v0034_add_default_catalog.py +0 -367
  221. sqlmesh/migrations/v0035_add_catalog_name_override.py +0 -22
  222. sqlmesh/migrations/v0036_delete_plan_dags_bug_fix.py +0 -14
  223. sqlmesh/migrations/v0037_remove_dbt_is_incremental_macro.py +0 -61
  224. sqlmesh/migrations/v0038_add_expiration_ts_to_snapshot.py +0 -73
  225. sqlmesh/migrations/v0039_include_environment_in_plan_dag_spec.py +0 -68
  226. sqlmesh/migrations/v0040_add_previous_finalized_snapshots.py +0 -26
  227. sqlmesh/migrations/v0041_remove_hash_raw_query_attribute.py +0 -59
  228. sqlmesh/migrations/v0042_trim_indirect_versions.py +0 -66
  229. sqlmesh/migrations/v0043_fix_remove_obsolete_attributes_in_plan_dags.py +0 -61
  230. sqlmesh/migrations/v0044_quote_identifiers_in_model_attributes.py +0 -5
  231. sqlmesh/migrations/v0045_move_gateway_variable.py +0 -70
  232. sqlmesh/migrations/v0046_add_batch_concurrency.py +0 -8
  233. sqlmesh/migrations/v0047_change_scd_string_to_column.py +0 -5
  234. sqlmesh/migrations/v0048_drop_indirect_versions.py +0 -59
  235. sqlmesh/migrations/v0049_replace_identifier_with_version_in_seeds_table.py +0 -57
  236. sqlmesh/migrations/v0050_drop_seeds_table.py +0 -11
  237. sqlmesh/migrations/v0051_rename_column_descriptions.py +0 -65
  238. sqlmesh/migrations/v0052_add_normalize_name_in_environment_naming_info.py +0 -28
  239. sqlmesh/migrations/v0053_custom_model_kind_extra_attributes.py +0 -5
  240. sqlmesh/migrations/v0054_fix_trailing_comments.py +0 -5
  241. sqlmesh/migrations/v0055_add_updated_ts_unpaused_ts_ttl_ms_unrestorable_to_snapshot.py +0 -132
  242. sqlmesh/migrations/v0056_restore_table_indexes.py +0 -118
  243. sqlmesh/migrations/v0057_add_table_format.py +0 -5
  244. sqlmesh/migrations/v0058_add_requirements.py +0 -26
  245. sqlmesh/migrations/v0059_add_physical_version.py +0 -5
  246. sqlmesh-0.213.1.dev1.dist-info/RECORD +0 -421
  247. web/client/dist/assets/Lineage-D0Hgdz2v.js +0 -1
  248. web/client/dist/assets/context-DgX0fp2E.js +0 -68
  249. {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/WHEEL +0 -0
  250. {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/entry_points.txt +0 -0
  251. {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/licenses/LICENSE +0 -0
  252. {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/top_level.txt +0 -0
sqlmesh/dbt/model.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import datetime
4
4
  import typing as t
5
+ import logging
5
6
 
6
7
  from sqlglot import exp
7
8
  from sqlglot.errors import SqlglotError
@@ -24,18 +25,33 @@ from sqlmesh.core.model import (
24
25
  ManagedKind,
25
26
  create_sql_model,
26
27
  )
27
- from sqlmesh.core.model.kind import SCDType2ByTimeKind, OnDestructiveChange, OnAdditiveChange
28
+ from sqlmesh.core.model.kind import (
29
+ SCDType2ByTimeKind,
30
+ OnDestructiveChange,
31
+ OnAdditiveChange,
32
+ on_destructive_change_validator,
33
+ on_additive_change_validator,
34
+ DbtCustomKind,
35
+ )
28
36
  from sqlmesh.dbt.basemodel import BaseModelConfig, Materialization, SnapshotStrategy
29
- from sqlmesh.dbt.common import SqlStr, extract_jinja_config, sql_str_validator
37
+ from sqlmesh.dbt.common import SqlStr, sql_str_validator
30
38
  from sqlmesh.utils.errors import ConfigError
31
39
  from sqlmesh.utils.pydantic import field_validator
32
40
 
33
41
  if t.TYPE_CHECKING:
34
42
  from sqlmesh.core.audit.definition import ModelAudit
35
43
  from sqlmesh.dbt.context import DbtContext
44
+ from sqlmesh.dbt.package import MaterializationConfig
45
+
46
+ logger = logging.getLogger(__name__)
47
+
48
+
49
+ logger = logging.getLogger(__name__)
36
50
 
37
51
 
38
- INCREMENTAL_BY_TIME_STRATEGIES = set(["delete+insert", "insert_overwrite", "microbatch"])
52
+ INCREMENTAL_BY_TIME_RANGE_STRATEGIES = set(
53
+ ["delete+insert", "insert_overwrite", "microbatch", "incremental_by_time_range"]
54
+ )
39
55
  INCREMENTAL_BY_UNIQUE_KEY_STRATEGIES = set(["merge"])
40
56
 
41
57
 
@@ -71,16 +87,19 @@ class ModelConfig(BaseModelConfig):
71
87
 
72
88
  # sqlmesh fields
73
89
  sql: SqlStr = SqlStr("")
74
- time_column: t.Optional[str] = None
90
+ time_column: t.Optional[t.Union[str, t.Dict[str, str]]] = None
75
91
  cron: t.Optional[str] = None
76
92
  interval_unit: t.Optional[str] = None
77
93
  batch_concurrency: t.Optional[int] = None
78
94
  forward_only: bool = True
79
95
  disable_restatement: t.Optional[bool] = None
80
- allow_partials: t.Optional[bool] = None
96
+ allow_partials: bool = True
81
97
  physical_version: t.Optional[str] = None
82
98
  auto_restatement_cron: t.Optional[str] = None
83
99
  auto_restatement_intervals: t.Optional[int] = None
100
+ partition_by_time_column: t.Optional[bool] = None
101
+ on_destructive_change: t.Optional[OnDestructiveChange] = None
102
+ on_additive_change: t.Optional[OnAdditiveChange] = None
84
103
 
85
104
  # DBT configuration fields
86
105
  cluster_by: t.Optional[t.List[str]] = None
@@ -132,11 +151,9 @@ class ModelConfig(BaseModelConfig):
132
151
  inserts_only: t.Optional[bool] = None
133
152
  incremental_predicates: t.Optional[t.List[str]] = None
134
153
 
135
- # Private fields
136
- _sql_embedded_config: t.Optional[SqlStr] = None
137
- _sql_no_config: t.Optional[SqlStr] = None
138
-
139
154
  _sql_validator = sql_str_validator
155
+ _on_destructive_change_validator = on_destructive_change_validator
156
+ _on_additive_change_validator = on_additive_change_validator
140
157
 
141
158
  @field_validator(
142
159
  "unique_key",
@@ -155,6 +172,22 @@ class ModelConfig(BaseModelConfig):
155
172
  return "*"
156
173
  return ensure_list(v)
157
174
 
175
+ @field_validator("updated_at", mode="before")
176
+ @classmethod
177
+ def _validate_updated_at(cls, v: t.Optional[str]) -> t.Optional[str]:
178
+ """
179
+ Extract column name if updated_at contains a cast.
180
+
181
+ SCDType2ByTimeKind and SCDType2ByColumnKind expect a column, and the casting is done later.
182
+ """
183
+ if v is None:
184
+ return None
185
+ parsed = d.parse_one(v)
186
+ if isinstance(parsed, exp.Cast) and isinstance(parsed.this, exp.Column):
187
+ return parsed.this.name
188
+
189
+ return v
190
+
158
191
  @field_validator("sql", mode="before")
159
192
  @classmethod
160
193
  def _validate_sql(cls, v: t.Union[str, SqlStr]) -> SqlStr:
@@ -182,6 +215,14 @@ class ModelConfig(BaseModelConfig):
182
215
  ):
183
216
  granularity = v["granularity"]
184
217
  raise ConfigError(f"Unexpected granularity '{granularity}' in partition_by '{v}'.")
218
+ if "data_type" in v and v["data_type"].lower() not in (
219
+ "timestamp",
220
+ "date",
221
+ "datetime",
222
+ "int64",
223
+ ):
224
+ data_type = v["data_type"]
225
+ raise ConfigError(f"Unexpected data_type '{data_type}' in partition_by '{v}'.")
185
226
  return {"data_type": "date", "granularity": "day", **v}
186
227
  raise ConfigError(f"Invalid format for partition_by '{v}'")
187
228
 
@@ -228,17 +269,6 @@ class ModelConfig(BaseModelConfig):
228
269
  def table_schema(self) -> str:
229
270
  return self.target_schema or super().table_schema
230
271
 
231
- def _get_overlapping_field_value(
232
- self, context: DbtContext, dbt_field_name: str, sqlmesh_field_name: str
233
- ) -> t.Optional[t.Any]:
234
- dbt_field = self._get_field_value(dbt_field_name)
235
- sqlmesh_field = getattr(self, sqlmesh_field_name, None)
236
- if dbt_field is not None and sqlmesh_field is not None:
237
- get_console().log_warning(
238
- f"Both '{dbt_field_name}' and '{sqlmesh_field_name}' are set for model '{self.canonical_name(context)}'. '{sqlmesh_field_name}' will be used."
239
- )
240
- return sqlmesh_field if sqlmesh_field is not None else dbt_field
241
-
242
272
  def model_kind(self, context: DbtContext) -> ModelKind:
243
273
  """
244
274
  Get the sqlmesh ModelKind
@@ -273,8 +303,12 @@ class ModelConfig(BaseModelConfig):
273
303
  "Valid values are 'ignore', 'fail', 'append_new_columns', 'sync_all_columns'."
274
304
  )
275
305
 
276
- incremental_kind_kwargs["on_destructive_change"] = on_destructive_change
277
- incremental_kind_kwargs["on_additive_change"] = on_additive_change
306
+ incremental_kind_kwargs["on_destructive_change"] = (
307
+ self._get_field_value("on_destructive_change") or on_destructive_change
308
+ )
309
+ incremental_kind_kwargs["on_additive_change"] = (
310
+ self._get_field_value("on_additive_change") or on_additive_change
311
+ )
278
312
  auto_restatement_cron_value = self._get_field_value("auto_restatement_cron")
279
313
  if auto_restatement_cron_value is not None:
280
314
  incremental_kind_kwargs["auto_restatement_cron"] = auto_restatement_cron_value
@@ -290,7 +324,8 @@ class ModelConfig(BaseModelConfig):
290
324
  incremental_kind_kwargs["forward_only"] = forward_only_value
291
325
 
292
326
  is_incremental_by_time_range = self.time_column or (
293
- self.incremental_strategy and self.incremental_strategy == "microbatch"
327
+ self.incremental_strategy
328
+ and self.incremental_strategy in {"microbatch", "incremental_by_time_range"}
294
329
  )
295
330
  # Get shared incremental by kwargs
296
331
  for field in ("batch_size", "batch_concurrency", "lookback"):
@@ -311,35 +346,35 @@ class ModelConfig(BaseModelConfig):
311
346
  )
312
347
  incremental_by_kind_kwargs["disable_restatement"] = disable_restatement
313
348
 
314
- # Incremental by time range which includes microbatch
315
349
  if is_incremental_by_time_range:
316
350
  strategy = self.incremental_strategy or target.default_incremental_strategy(
317
351
  IncrementalByTimeRangeKind
318
352
  )
319
353
 
320
- if strategy not in INCREMENTAL_BY_TIME_STRATEGIES:
354
+ if strategy not in INCREMENTAL_BY_TIME_RANGE_STRATEGIES:
321
355
  get_console().log_warning(
322
356
  f"SQLMesh incremental by time strategy is not compatible with '{strategy}' incremental strategy in model '{self.canonical_name(context)}'. "
323
- f"Supported strategies include {collection_to_str(INCREMENTAL_BY_TIME_STRATEGIES)}."
357
+ f"Supported strategies include {collection_to_str(INCREMENTAL_BY_TIME_RANGE_STRATEGIES)}."
324
358
  )
325
359
 
326
- if strategy == "microbatch":
327
- time_column = self._get_overlapping_field_value(
328
- context, "event_time", "time_column"
360
+ if self.time_column and strategy != "incremental_by_time_range":
361
+ get_console().log_warning(
362
+ f"Using `time_column` on a model with incremental_strategy '{strategy}' has been deprecated. "
363
+ f"Please use `incremental_by_time_range` instead in model '{self.canonical_name(context)}'."
329
364
  )
365
+
366
+ if strategy == "microbatch":
367
+ if self.time_column:
368
+ raise ConfigError(
369
+ f"{self.canonical_name(context)}: 'time_column' cannot be used with 'microbatch' incremental strategy. Use 'event_time' instead."
370
+ )
371
+ time_column = self._get_field_value("event_time")
330
372
  if not time_column:
331
373
  raise ConfigError(
332
374
  f"{self.canonical_name(context)}: 'event_time' is required for microbatch incremental strategy."
333
375
  )
334
- concurrent_batches = self._get_field_value("concurrent_batches")
335
- if concurrent_batches is True:
336
- if incremental_by_kind_kwargs.get("batch_size"):
337
- get_console().log_warning(
338
- f"'concurrent_batches' is set to True and 'batch_size' are defined in '{self.canonical_name(context)}'. The batch size will be set to the value of `batch_size`."
339
- )
340
- incremental_by_kind_kwargs["batch_size"] = incremental_by_kind_kwargs.get(
341
- "batch_size", 1
342
- )
376
+ # dbt microbatch always processes batches in a size of 1
377
+ incremental_by_kind_kwargs["batch_size"] = 1
343
378
  else:
344
379
  if not self.time_column:
345
380
  raise ConfigError(
@@ -347,11 +382,22 @@ class ModelConfig(BaseModelConfig):
347
382
  )
348
383
  time_column = self.time_column
349
384
 
385
+ incremental_by_time_range_kwargs = {
386
+ "time_column": time_column,
387
+ }
388
+ if self.auto_restatement_intervals:
389
+ incremental_by_time_range_kwargs["auto_restatement_intervals"] = (
390
+ self.auto_restatement_intervals
391
+ )
392
+ if self.partition_by_time_column is not None:
393
+ incremental_by_time_range_kwargs["partition_by_time_column"] = (
394
+ self.partition_by_time_column
395
+ )
396
+
350
397
  return IncrementalByTimeRangeKind(
351
- time_column=time_column,
352
- auto_restatement_intervals=self.auto_restatement_intervals,
353
398
  **incremental_kind_kwargs,
354
399
  **incremental_by_kind_kwargs,
400
+ **incremental_by_time_range_kwargs,
355
401
  )
356
402
 
357
403
  if self.unique_key:
@@ -389,7 +435,7 @@ class ModelConfig(BaseModelConfig):
389
435
  IncrementalUnmanagedKind
390
436
  )
391
437
  return IncrementalUnmanagedKind(
392
- insert_overwrite=strategy in INCREMENTAL_BY_TIME_STRATEGIES,
438
+ insert_overwrite=strategy in INCREMENTAL_BY_TIME_RANGE_STRATEGIES,
393
439
  disable_restatement=incremental_by_kind_kwargs["disable_restatement"],
394
440
  **incremental_kind_kwargs,
395
441
  )
@@ -424,26 +470,20 @@ class ModelConfig(BaseModelConfig):
424
470
  if materialization == Materialization.DYNAMIC_TABLE:
425
471
  return ManagedKind()
426
472
 
427
- raise ConfigError(f"{materialization.value} materialization not supported.")
428
-
429
- @property
430
- def sql_no_config(self) -> SqlStr:
431
- if self._sql_no_config is None:
432
- self._sql_no_config = SqlStr("")
433
- self._extract_sql_config()
434
- return self._sql_no_config
473
+ if materialization == Materialization.CUSTOM:
474
+ if custom_materialization := self._get_custom_materialization(context):
475
+ return DbtCustomKind(
476
+ materialization=self.materialized,
477
+ adapter=custom_materialization.adapter,
478
+ dialect=self.dialect(context),
479
+ definition=custom_materialization.definition,
480
+ )
435
481
 
436
- @property
437
- def sql_embedded_config(self) -> SqlStr:
438
- if self._sql_embedded_config is None:
439
- self._sql_embedded_config = SqlStr("")
440
- self._extract_sql_config()
441
- return self._sql_embedded_config
482
+ raise ConfigError(
483
+ f"Unknown materialization '{self.materialized}'. Custom materializations must be defined in your dbt project."
484
+ )
442
485
 
443
- def _extract_sql_config(self) -> None:
444
- no_config, embedded_config = extract_jinja_config(self.sql)
445
- self._sql_no_config = SqlStr(no_config)
446
- self._sql_embedded_config = SqlStr(embedded_config)
486
+ raise ConfigError(f"{materialization.value} materialization not supported.")
447
487
 
448
488
  def _big_query_partition_by_expr(self, context: DbtContext) -> exp.Expression:
449
489
  assert isinstance(self.partition_by, dict)
@@ -482,6 +522,18 @@ class ModelConfig(BaseModelConfig):
482
522
  dialect="bigquery",
483
523
  )
484
524
 
525
+ def _get_custom_materialization(self, context: DbtContext) -> t.Optional[MaterializationConfig]:
526
+ materializations = context.manifest.materializations()
527
+ name, target_adapter = self.materialized, context.target.dialect
528
+
529
+ adapter_specific_key = f"{name}_{target_adapter}"
530
+ default_key = f"{name}_default"
531
+ if adapter_specific_key in materializations:
532
+ return materializations[adapter_specific_key]
533
+ if default_key in materializations:
534
+ return materializations[default_key]
535
+ return None
536
+
485
537
  @property
486
538
  def sqlmesh_config_fields(self) -> t.Set[str]:
487
539
  return super().sqlmesh_config_fields | {
@@ -502,35 +554,71 @@ class ModelConfig(BaseModelConfig):
502
554
  ) -> Model:
503
555
  """Converts the dbt model into a SQLMesh model."""
504
556
  model_dialect = self.dialect(context)
505
- query = d.jinja_query(self.sql_no_config)
557
+ query = d.jinja_query(self.sql)
558
+ kind = self.model_kind(context)
506
559
 
507
560
  optional_kwargs: t.Dict[str, t.Any] = {}
508
561
  physical_properties: t.Dict[str, t.Any] = {}
509
562
 
510
563
  if self.partition_by:
511
- partitioned_by = []
512
- if isinstance(self.partition_by, list):
513
- for p in self.partition_by:
564
+ if isinstance(kind, (ViewKind, EmbeddedKind)):
565
+ logger.warning(
566
+ "Ignoring partition_by config for model '%s'; partition_by is not supported for %s.",
567
+ self.name,
568
+ "views" if isinstance(kind, ViewKind) else "ephemeral models",
569
+ )
570
+ elif context.target.dialect == "snowflake":
571
+ logger.warning(
572
+ "Ignoring partition_by config for model '%s' targeting %s. The partition_by config is not supported for Snowflake.",
573
+ self.name,
574
+ context.target.dialect,
575
+ )
576
+ else:
577
+ partitioned_by = []
578
+ if isinstance(self.partition_by, list):
579
+ for p in self.partition_by:
580
+ try:
581
+ partitioned_by.append(d.parse_one(p, dialect=model_dialect))
582
+ except SqlglotError as e:
583
+ raise ConfigError(
584
+ f"Failed to parse model '{self.canonical_name(context)}' partition_by field '{p}' in '{self.path}': {e}"
585
+ ) from e
586
+ elif isinstance(self.partition_by, dict):
587
+ if context.target.dialect == "bigquery":
588
+ partitioned_by.append(self._big_query_partition_by_expr(context))
589
+ else:
590
+ logger.warning(
591
+ "Ignoring partition_by config for model '%s' targeting %s. The format of the config field is only supported for BigQuery.",
592
+ self.name,
593
+ context.target.dialect,
594
+ )
595
+
596
+ if partitioned_by:
597
+ optional_kwargs["partitioned_by"] = partitioned_by
598
+
599
+ if self.cluster_by:
600
+ if isinstance(kind, (ViewKind, EmbeddedKind)):
601
+ logger.warning(
602
+ "Ignoring cluster_by config for model '%s'; cluster_by is not supported for %s.",
603
+ self.name,
604
+ "views" if isinstance(kind, ViewKind) else "ephemeral models",
605
+ )
606
+ else:
607
+ clustered_by = []
608
+ for c in self.cluster_by:
514
609
  try:
515
- partitioned_by.append(d.parse_one(p, dialect=model_dialect))
610
+ cluster_expr = exp.maybe_parse(
611
+ c, into=exp.Cluster, prefix="CLUSTER BY", dialect=model_dialect
612
+ )
613
+ for expr in cluster_expr.expressions:
614
+ clustered_by.append(
615
+ expr.this if isinstance(expr, exp.Ordered) else expr
616
+ )
516
617
  except SqlglotError as e:
517
618
  raise ConfigError(
518
- f"Failed to parse model '{self.canonical_name(context)}' partition_by field '{p}' in '{self.path}': {e}"
619
+ f"Failed to parse model '{self.canonical_name(context)}' cluster_by field '{c}' in '{self.path}': {e}"
519
620
  ) from e
520
- else:
521
- partitioned_by.append(self._big_query_partition_by_expr(context))
522
- optional_kwargs["partitioned_by"] = partitioned_by
523
-
524
- if self.cluster_by:
525
- clustered_by = []
526
- for c in self.cluster_by:
527
- try:
528
- clustered_by.append(d.parse_one(c, dialect=model_dialect))
529
- except SqlglotError as e:
530
- raise ConfigError(
531
- f"Failed to parse model '{self.canonical_name(context)}' cluster_by field '{c}' in '{self.path}': {e}"
532
- ) from e
533
- optional_kwargs["clustered_by"] = clustered_by
621
+ optional_kwargs["clustered_by"] = clustered_by
534
622
 
535
623
  model_kwargs = self.sqlmesh_model_kwargs(context)
536
624
  if self.sql_header:
@@ -628,15 +716,19 @@ class ModelConfig(BaseModelConfig):
628
716
  model_kwargs["physical_properties"] = physical_properties
629
717
 
630
718
  kind = self.model_kind(context)
719
+
720
+ # A falsy grants config (None or {}) is considered as unmanaged per dbt semantics
721
+ if self.grants and kind.supports_grants:
722
+ model_kwargs["grants"] = self.grants
723
+
631
724
  allow_partials = model_kwargs.pop("allow_partials", None)
632
- if (
633
- allow_partials is None
634
- and kind.is_materialized
635
- and not kind.is_incremental_by_time_range
636
- ):
725
+ if allow_partials is None:
637
726
  # Set allow_partials to True for dbt models to preserve the original semantics.
638
727
  allow_partials = True
639
728
 
729
+ # pop begin for all models so we don't pass it through for non-incremental materializations
730
+ # (happens if model config is microbatch but project config overrides)
731
+ begin = model_kwargs.pop("begin", None)
640
732
  if kind.is_incremental:
641
733
  if self.batch_size and isinstance(self.batch_size, str):
642
734
  if "interval_unit" in model_kwargs:
@@ -646,13 +738,18 @@ class ModelConfig(BaseModelConfig):
646
738
  else:
647
739
  model_kwargs["interval_unit"] = self.batch_size
648
740
  self.batch_size = None
649
- if begin := model_kwargs.pop("begin", None):
741
+ if begin:
650
742
  if "start" in model_kwargs:
651
743
  get_console().log_warning(
652
744
  f"Both 'begin' and 'start' are set for model '{self.canonical_name(context)}'. 'start' will be used."
653
745
  )
654
746
  else:
655
747
  model_kwargs["start"] = begin
748
+ # If user explicitly disables concurrent batches then we want to set depends on past to true which we
749
+ # will do by including the model in the depends_on
750
+ if self.concurrent_batches is not None and self.concurrent_batches is False:
751
+ depends_on = model_kwargs.get("depends_on", set())
752
+ depends_on.add(self.canonical_name(context))
656
753
 
657
754
  model_kwargs["start"] = model_kwargs.get(
658
755
  "start", context.sqlmesh_config.model_defaults.start
@@ -670,6 +767,7 @@ class ModelConfig(BaseModelConfig):
670
767
  extract_dependencies_from_query=False,
671
768
  allow_partials=allow_partials,
672
769
  virtual_environment_mode=virtual_environment_mode,
770
+ dbt_node_info=self.node_info,
673
771
  **optional_kwargs,
674
772
  **model_kwargs,
675
773
  )
sqlmesh/dbt/package.py CHANGED
@@ -37,6 +37,16 @@ class HookConfig(PydanticModel):
37
37
  dependencies: Dependencies
38
38
 
39
39
 
40
+ class MaterializationConfig(PydanticModel):
41
+ """Class to contain custom materialization configuration."""
42
+
43
+ name: str
44
+ adapter: str
45
+ definition: str
46
+ dependencies: Dependencies
47
+ path: Path
48
+
49
+
40
50
  class Package(PydanticModel):
41
51
  """Class to contain package configuration"""
42
52
 
@@ -47,6 +57,7 @@ class Package(PydanticModel):
47
57
  models: t.Dict[str, ModelConfig]
48
58
  variables: t.Dict[str, t.Any]
49
59
  macros: t.Dict[str, MacroConfig]
60
+ materializations: t.Dict[str, MaterializationConfig]
50
61
  on_run_start: t.Dict[str, HookConfig]
51
62
  on_run_end: t.Dict[str, HookConfig]
52
63
  files: t.Set[Path]
@@ -94,6 +105,7 @@ class PackageLoader:
94
105
  models = _fix_paths(self._context.manifest.models(package_name), package_root)
95
106
  seeds = _fix_paths(self._context.manifest.seeds(package_name), package_root)
96
107
  macros = _fix_paths(self._context.manifest.macros(package_name), package_root)
108
+ materializations = _fix_paths(self._context.manifest.materializations(), package_root)
97
109
  on_run_start = _fix_paths(self._context.manifest.on_run_start(package_name), package_root)
98
110
  on_run_end = _fix_paths(self._context.manifest.on_run_end(package_name), package_root)
99
111
  sources = self._context.manifest.sources(package_name)
@@ -114,13 +126,16 @@ class PackageLoader:
114
126
  seeds=seeds,
115
127
  variables=package_variables,
116
128
  macros=macros,
129
+ materializations=materializations,
117
130
  files=config_paths,
118
131
  on_run_start=on_run_start,
119
132
  on_run_end=on_run_end,
120
133
  )
121
134
 
122
135
 
123
- T = t.TypeVar("T", TestConfig, ModelConfig, MacroConfig, SeedConfig, HookConfig)
136
+ T = t.TypeVar(
137
+ "T", TestConfig, ModelConfig, MacroConfig, MaterializationConfig, SeedConfig, HookConfig
138
+ )
124
139
 
125
140
 
126
141
  def _fix_paths(configs: t.Dict[str, T], package_root: Path) -> t.Dict[str, T]:
sqlmesh/dbt/profile.py CHANGED
@@ -60,7 +60,7 @@ class Profile:
60
60
  if not context.profile_name:
61
61
  raise ConfigError(f"{project_file.stem} must include project name.")
62
62
 
63
- profile_filepath = cls._find_profile(context.project_root)
63
+ profile_filepath = cls._find_profile(context.project_root, context.profiles_dir)
64
64
  if not profile_filepath:
65
65
  raise ConfigError(f"{cls.PROFILE_FILE} not found.")
66
66
 
@@ -68,8 +68,8 @@ class Profile:
68
68
  return Profile(profile_filepath, target_name, target)
69
69
 
70
70
  @classmethod
71
- def _find_profile(cls, project_root: Path) -> t.Optional[Path]:
72
- dir = os.environ.get("DBT_PROFILES_DIR", "")
71
+ def _find_profile(cls, project_root: Path, profiles_dir: t.Optional[Path]) -> t.Optional[Path]:
72
+ dir = os.environ.get("DBT_PROFILES_DIR", profiles_dir or "")
73
73
  path = Path(project_root, dir, cls.PROFILE_FILE)
74
74
  if path.exists():
75
75
  return path
sqlmesh/dbt/project.py CHANGED
@@ -99,16 +99,21 @@ class Project:
99
99
  package = package_loader.load(path.parent)
100
100
  packages[package.name] = package
101
101
 
102
+ # Variable resolution precedence:
103
+ # 1. Variable overrides
104
+ # 2. Package-scoped variables in the root project's dbt_project.yml
105
+ # 3. Global project variables in the root project's dbt_project.yml
106
+ # 4. Variables in the package's dbt_project.yml
102
107
  all_project_variables = {**(project_yaml.get("vars") or {}), **(variable_overrides or {})}
103
108
  for name, package in packages.items():
104
- package_vars = all_project_variables.get(name)
105
-
106
- if isinstance(package_vars, dict):
107
- package.variables.update(package_vars)
108
-
109
- if name == context.project_name:
110
- package.variables.update(all_project_variables)
109
+ if isinstance(all_project_variables.get(name), dict):
110
+ project_vars_copy = all_project_variables.copy()
111
+ package_scoped_vars = project_vars_copy.pop(name)
112
+ package.variables.update(project_vars_copy)
113
+ package.variables.update(package_scoped_vars)
111
114
  else:
115
+ package.variables.update(all_project_variables)
116
+ if variable_overrides:
112
117
  package.variables.update(variable_overrides)
113
118
 
114
119
  return Project(context, profile, packages)
sqlmesh/dbt/seed.py CHANGED
@@ -79,7 +79,11 @@ class SeedConfig(BaseModelConfig):
79
79
  kwargs["columns"] = new_columns
80
80
 
81
81
  # dbt treats single whitespace as a null value
82
- csv_settings = CsvSettings(na_values=[" "], keep_default_na=True)
82
+ csv_settings = CsvSettings(
83
+ delimiter=self.delimiter,
84
+ na_values=[" "],
85
+ keep_default_na=True,
86
+ )
83
87
 
84
88
  return create_seed_model(
85
89
  self.canonical_name(context),
@@ -88,6 +92,7 @@ class SeedConfig(BaseModelConfig):
88
92
  audit_definitions=audit_definitions,
89
93
  virtual_environment_mode=virtual_environment_mode,
90
94
  start=self.start or context.sqlmesh_config.model_defaults.start,
95
+ dbt_node_info=self.node_info,
91
96
  **kwargs,
92
97
  )
93
98
 
sqlmesh/dbt/source.py CHANGED
@@ -8,6 +8,7 @@ from sqlmesh.core.config.base import UpdateStrategy
8
8
  from sqlmesh.dbt.column import ColumnConfig
9
9
  from sqlmesh.dbt.common import GeneralConfig
10
10
  from sqlmesh.dbt.relation import RelationType
11
+ from sqlmesh.dbt.util import DBT_VERSION
11
12
  from sqlmesh.utils import AttributeDict
12
13
  from sqlmesh.utils.errors import ConfigError
13
14
 
@@ -35,6 +36,7 @@ class SourceConfig(GeneralConfig):
35
36
  # DBT configuration fields
36
37
  name: str = ""
37
38
  source_name_: str = Field("", alias="source_name")
39
+ fqn_: t.List[str] = Field(default_factory=list, alias="fqn")
38
40
  database: t.Optional[str] = None
39
41
  schema_: t.Optional[str] = Field(None, alias="schema")
40
42
  identifier: t.Optional[str] = None
@@ -46,6 +48,7 @@ class SourceConfig(GeneralConfig):
46
48
  external: t.Optional[t.Dict[str, t.Any]] = {}
47
49
  source_meta: t.Optional[t.Dict[str, t.Any]] = {}
48
50
  columns: t.Dict[str, ColumnConfig] = {}
51
+ event_time: t.Optional[str] = None
49
52
 
50
53
  _canonical_name: t.Optional[str] = None
51
54
 
@@ -62,6 +65,10 @@ class SourceConfig(GeneralConfig):
62
65
  def config_name(self) -> str:
63
66
  return f"{self.source_name_}.{self.name}"
64
67
 
68
+ @property
69
+ def fqn(self) -> str:
70
+ return ".".join(self.fqn_)
71
+
65
72
  def canonical_name(self, context: DbtContext) -> str:
66
73
  if self._canonical_name is None:
67
74
  source = context.get_callable_macro("source")
@@ -72,7 +79,7 @@ class SourceConfig(GeneralConfig):
72
79
  relation = source(self.source_name_, self.name)
73
80
  except Exception as e:
74
81
  raise ConfigError(
75
- f"'source' macro failed for '{self.config_name}' with exeception '{e}'."
82
+ f"'source' macro failed for '{self.config_name}' with exception '{e}'."
76
83
  )
77
84
 
78
85
  relation = relation.quote(
@@ -94,6 +101,11 @@ class SourceConfig(GeneralConfig):
94
101
  if external_location:
95
102
  extras["external"] = external_location.replace("{name}", self.table_name)
96
103
 
104
+ if DBT_VERSION >= (1, 9, 0) and self.event_time:
105
+ extras["event_time_filter"] = {
106
+ "field_name": self.event_time,
107
+ }
108
+
97
109
  return AttributeDict(
98
110
  {
99
111
  "database": self.database,
sqlmesh/dbt/target.py CHANGED
@@ -45,10 +45,24 @@ IncrementalKind = t.Union[
45
45
 
46
46
  # We only serialize a subset of fields in order to avoid persisting sensitive information
47
47
  SERIALIZABLE_FIELDS = {
48
- "type",
48
+ # core
49
49
  "name",
50
- "database",
51
50
  "schema_",
51
+ "type",
52
+ "threads",
53
+ # snowflake
54
+ "database",
55
+ "warehouse",
56
+ "user",
57
+ "role",
58
+ "account",
59
+ # postgres/redshift
60
+ "dbname",
61
+ "host",
62
+ "port",
63
+ # bigquery
64
+ "project",
65
+ "dataset",
52
66
  }
53
67
 
54
68
  SCHEMA_DIFFER_OVERRIDES = {
@@ -587,12 +601,17 @@ class BigQueryConfig(TargetConfig):
587
601
  if not isinstance(data, dict):
588
602
  return data
589
603
 
590
- data["schema"] = data.get("schema") or data.get("dataset")
591
- if not data["schema"]:
604
+ # dbt treats schema and dataset interchangeably
605
+ schema = data.get("schema") or data.get("dataset")
606
+ if not schema:
592
607
  raise ConfigError("Either schema or dataset must be set")
593
- data["database"] = data.get("database") or data.get("project")
594
- if not data["database"]:
608
+ data["dataset"] = data["schema"] = schema
609
+
610
+ # dbt treats database and project interchangeably
611
+ database = data.get("database") or data.get("project")
612
+ if not database:
595
613
  raise ConfigError("Either database or project must be set")
614
+ data["database"] = data["project"] = database
596
615
 
597
616
  return data
598
617