dbt-firebolt 1.9.3__py3-none-any.whl → 1.10.0__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 (54) hide show
  1. dbt/adapters/firebolt/__init__.py +1 -1
  2. dbt/adapters/firebolt/connections.py +56 -28
  3. dbt/adapters/firebolt/impl.py +2 -1
  4. dbt/include/firebolt/macros/adapters/apply_grants.sql +37 -0
  5. dbt/include/firebolt/macros/adapters/relation.sql +49 -0
  6. dbt/include/firebolt/macros/dbt_external_tables/create_external_table.sql +141 -0
  7. dbt/include/firebolt/macros/dbt_external_tables/dropif.sql +6 -0
  8. dbt/include/firebolt/macros/dbt_external_tables/get_external_build_plan.sql +18 -0
  9. dbt/include/firebolt/macros/materializations/clone.sql +3 -0
  10. dbt/include/firebolt/macros/materializations/materialized_view.sql +3 -0
  11. dbt/include/firebolt/macros/materializations/models/incremental/column_helpers.sql +21 -0
  12. dbt/include/firebolt/macros/materializations/models/incremental/incremental.sql +131 -0
  13. dbt/include/firebolt/macros/materializations/models/incremental/is_incremental.sql +14 -0
  14. dbt/include/firebolt/macros/materializations/models/incremental/merge.sql +38 -0
  15. dbt/include/firebolt/macros/materializations/models/incremental/on_schema_change.sql +90 -0
  16. dbt/include/firebolt/macros/materializations/models/incremental/strategies.sql +132 -0
  17. dbt/include/firebolt/macros/materializations/seed.sql +48 -0
  18. dbt/include/firebolt/macros/materializations/snapshot_merge.sql +19 -0
  19. dbt/include/firebolt/macros/materializations/table.sql +40 -0
  20. dbt/include/firebolt/macros/materializations/test.sql +15 -0
  21. dbt/include/firebolt/macros/materializations/view.sql +39 -0
  22. dbt/include/firebolt/macros/relations/materialized_view/alter.sql +11 -0
  23. dbt/include/firebolt/macros/relations/materialized_view/create.sql +3 -0
  24. dbt/include/firebolt/macros/relations/materialized_view/describe.sql +3 -0
  25. dbt/include/firebolt/macros/relations/materialized_view/drop.sql +3 -0
  26. dbt/include/firebolt/macros/relations/materialized_view/refresh.sql +3 -0
  27. dbt/include/firebolt/macros/relations/table/create.sql +80 -0
  28. dbt/include/firebolt/macros/relations/table/drop.sql +3 -0
  29. dbt/include/firebolt/macros/relations/table/rename.sql +6 -0
  30. dbt/include/firebolt/macros/relations/table/replace.sql +3 -0
  31. dbt/include/firebolt/macros/relations/view/create.sql +16 -0
  32. dbt/include/firebolt/macros/relations/view/drop.sql +3 -0
  33. dbt/include/firebolt/macros/relations/view/rename.sql +6 -0
  34. dbt/include/firebolt/macros/relations/view/replace.sql +3 -0
  35. dbt/include/firebolt/macros/utils/array_append.sql +3 -0
  36. dbt/include/firebolt/macros/utils/array_concat.sql +3 -0
  37. dbt/include/firebolt/macros/utils/array_construct.sql +3 -0
  38. dbt/include/firebolt/macros/utils/bool_or.sql +5 -0
  39. dbt/include/firebolt/macros/utils/cast_bool_to_text.sql +7 -0
  40. dbt/include/firebolt/macros/utils/dateadd.sql +9 -0
  41. dbt/include/firebolt/macros/utils/datediff.sql +9 -0
  42. dbt/include/firebolt/macros/utils/except.sql +6 -0
  43. dbt/include/firebolt/macros/utils/intersect.sql +6 -0
  44. dbt/include/firebolt/macros/utils/listagg.sql +27 -0
  45. dbt/include/firebolt/macros/utils/position.sql +3 -0
  46. dbt/include/firebolt/macros/utils/right.sql +12 -0
  47. dbt/include/firebolt/macros/utils/split_part.sql +14 -0
  48. dbt/include/firebolt/macros/utils/timestamps.sql +14 -0
  49. {dbt_firebolt-1.9.3.dist-info → dbt_firebolt-1.10.0.dist-info}/METADATA +2 -2
  50. dbt_firebolt-1.10.0.dist-info/RECORD +61 -0
  51. dbt_firebolt-1.9.3.dist-info/RECORD +0 -16
  52. {dbt_firebolt-1.9.3.dist-info → dbt_firebolt-1.10.0.dist-info}/LICENSE +0 -0
  53. {dbt_firebolt-1.9.3.dist-info → dbt_firebolt-1.10.0.dist-info}/WHEEL +0 -0
  54. {dbt_firebolt-1.9.3.dist-info → dbt_firebolt-1.10.0.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ from dbt.adapters.firebolt.connections import (
7
7
  from dbt.adapters.firebolt.impl import FireboltAdapter
8
8
  from dbt.include import firebolt
9
9
 
10
- __version__ = "1.9.3"
10
+ __version__ = "1.10.0"
11
11
 
12
12
  Plugin = AdapterPlugin(
13
13
  adapter=FireboltAdapter, # type: ignore
@@ -19,7 +19,12 @@ from dbt_common.exceptions import (
19
19
  NotImplementedError,
20
20
  )
21
21
  from firebolt.client import DEFAULT_API_URL
22
- from firebolt.client.auth import Auth, ClientCredentials, UsernamePassword
22
+ from firebolt.client.auth import (
23
+ Auth,
24
+ ClientCredentials,
25
+ FireboltCore,
26
+ UsernamePassword,
27
+ )
23
28
  from firebolt.db import ARRAY, DECIMAL, ExtendedType
24
29
  from firebolt.db import connect as sdk_connect
25
30
  from firebolt.db.connection import Connection as SDKConnection
@@ -41,6 +46,7 @@ class FireboltCredentials(Credentials):
41
46
  api_endpoint: Optional[str] = DEFAULT_API_URL
42
47
  engine_name: Optional[str] = None
43
48
  account_name: Optional[str] = None
49
+ url: Optional[str] = None
44
50
  retries: int = 1
45
51
 
46
52
  _ALIASES = {
@@ -48,26 +54,14 @@ class FireboltCredentials(Credentials):
48
54
  }
49
55
 
50
56
  def __post_init__(self) -> None:
51
- # If user and password are not provided, assume client_id and client_secret
52
- # are provided instead
53
- if not self.user and not self.password:
54
- if not self.client_id or not self.client_secret:
55
- raise DbtConfigError(
56
- 'Either user and password or client_id and client_secret'
57
- ' must be provided'
58
- )
59
- else:
60
- if self.client_id or self.client_secret:
61
- raise DbtConfigError(
62
- 'Either user and password or client_id and client_secret'
63
- ' must be provided'
64
- )
57
+ # Check the credentials are valid and we can determine the auth method
58
+ _determine_auth(self)
65
59
 
66
60
  @property
67
61
  def type(self) -> str:
68
62
  return 'firebolt'
69
63
 
70
- def _connection_keys(self) -> Tuple[str, str, str, str, str, str, str]:
64
+ def _connection_keys(self) -> Tuple[str, str, str, str, str, str, str, str]:
71
65
  """
72
66
  Return tuple of keys (i.e. not values) to display
73
67
  in the `dbt debug` output.
@@ -80,6 +74,7 @@ class FireboltCredentials(Credentials):
80
74
  'database',
81
75
  'engine_name',
82
76
  'params',
77
+ 'url',
83
78
  )
84
79
 
85
80
  @property
@@ -222,17 +217,50 @@ class FireboltConnectionManager(SQLConnectionManager):
222
217
 
223
218
 
224
219
  def _determine_auth(credentials: FireboltCredentials) -> Auth:
225
- if credentials.client_id and credentials.client_secret:
226
- return ClientCredentials(credentials.client_id, credentials.client_secret)
227
- elif '@' in credentials.user: # type: ignore # checked in the dataclass
228
- # email auth can only be used with UsernamePassword
229
- return UsernamePassword(
230
- credentials.user, # type: ignore[arg-type]
231
- credentials.password, # type: ignore[arg-type]
232
- )
233
- else:
234
- # assume user provided id and secret in the user/password fields
220
+ """Determine the appropriate authentication method based on provided credentials."""
221
+
222
+ def _has_client_credentials() -> bool:
223
+ return bool(credentials.client_id and credentials.client_secret)
224
+
225
+ def _has_user_password() -> bool:
226
+ return bool(credentials.user and credentials.password)
227
+
228
+ def _is_email_auth() -> bool:
229
+ return bool(credentials.user and '@' in credentials.user)
230
+
231
+ # Handle Firebolt Core authentication (no other credentials allowed)
232
+ if credentials.url:
233
+ if _has_client_credentials() or _has_user_password():
234
+ raise DbtConfigError(
235
+ 'If url is provided, do not provide user, password, '
236
+ 'client_id, or client_secret.'
237
+ )
238
+ return FireboltCore()
239
+
240
+ # Handle client credentials authentication
241
+ if _has_client_credentials():
235
242
  return ClientCredentials(
236
- credentials.user, # type: ignore[arg-type]
237
- credentials.password, # type: ignore[arg-type]
243
+ credentials.client_id, # type: ignore[arg-type]
244
+ credentials.client_secret, # type: ignore[arg-type]
238
245
  )
246
+
247
+ # Handle username/password authentication
248
+ if _has_user_password():
249
+ if _is_email_auth():
250
+ return UsernamePassword(
251
+ credentials.user, # type: ignore[arg-type]
252
+ credentials.password, # type: ignore[arg-type]
253
+ )
254
+ else:
255
+ return ClientCredentials(
256
+ credentials.user, # type: ignore[arg-type]
257
+ credentials.password, # type: ignore[arg-type]
258
+ )
259
+
260
+ # If we reach here, no valid authentication method was found
261
+ raise DbtConfigError(
262
+ 'No valid authentication method found. Provide either:\n'
263
+ '- client_id and client_secret\n'
264
+ '- user and password\n'
265
+ '- or use url for Firebolt Core connection'
266
+ )
@@ -313,7 +313,8 @@ class FireboltAdapter(SQLAdapter):
313
313
  names = sorted((self.quote(n) for n in column_names))
314
314
 
315
315
  where_expressions = [
316
- f'{relation_a}.{name} = {relation_b}.{name}' for name in names
316
+ f'{relation_a}.{name} IS NOT DISTINCT FROM {relation_b}.{name}'
317
+ for name in names
317
318
  ]
318
319
  where_clause = ' AND '.join(where_expressions)
319
320
  columns_csv = ', '.join(names)
@@ -0,0 +1,37 @@
1
+ {% macro firebolt__get_show_grant_sql(relation) %}
2
+ {# Usually called from apply_grants. Should not be called directly. #}
3
+ {{ adapter.raise_grant_error() }}
4
+ {% endmacro %}
5
+
6
+
7
+ {%- macro firebolt__get_grant_sql(relation, privilege, grantee) -%}
8
+ {# Usually called from apply_grants. Should not be called directly. #}
9
+ {{ adapter.raise_grant_error() }}
10
+ {%- endmacro -%}
11
+
12
+
13
+ {%- macro firebolt__get_revoke_sql(relation, privilege, grantee) -%}
14
+ {# Usually called from apply_grants. Should not be called directly. #}
15
+ {{ adapter.raise_grant_error() }}
16
+ {%- endmacro -%}
17
+
18
+
19
+ {% macro firebolt__copy_grants() %}
20
+ {{ return(True) }}
21
+ {% endmacro %}
22
+
23
+
24
+ {% macro firebolt__apply_grants(relation, grant_config, should_revoke) %}
25
+ {% if grant_config %}
26
+ {{ adapter.raise_grant_error() }}
27
+ {% endif %}
28
+ {% endmacro %}
29
+
30
+
31
+ {%- macro firebolt__get_dcl_statement_list(relation, grant_config, get_dcl_macro) -%}
32
+ {# Firebolt does not support DCL statements yet #}
33
+ {% if grant_config %}
34
+ {{ adapter.raise_grant_error() }}
35
+ {% endif %}
36
+ {{ return([]) }}
37
+ {%- endmacro %}
@@ -0,0 +1,49 @@
1
+ {% macro firebolt__rename_relation(source, target) -%}
2
+ {# Create new relation, do CTAS to swap it, remove original relation.
3
+ Must check whether relation is view or table, because process is
4
+ different for the two. #}
5
+ {% set all_relations = (list_relations_without_caching(source)) %}
6
+ {% set tables = adapter.filter_table(all_relations, 'type', 'table') %}
7
+ {% set views = adapter.filter_table(all_relations, 'type', 'view') %}
8
+ {% if (views.rows | length) > 0 %}
9
+ {% call statement('view_definition', fetch_result=True) %}
10
+
11
+ SELECT view_definition FROM information_schema.views
12
+ WHERE table_name = '{{ source.identifier }}'
13
+ {%- endcall %}
14
+ {% if load_result('view_definition')['data'] %}
15
+ {# For some reason this ocassionally returns an empty set.
16
+ In that case do nothing. Don't even ask why I have to jump through
17
+ the stupid hoops to get the value out of the result in the next line. #}
18
+ {% set view_ddl = load_result('view_definition')['data'][0][0] %}
19
+ {% set view_ddl = view_ddl.replace(source.identifier,
20
+ target.identifier) %}
21
+ {% call statement('new_view') %}
22
+
23
+ DROP VIEW {{ source.identifier }};
24
+ {{ view_ddl }};
25
+ {% endcall %}
26
+ {% endif %}
27
+ {% elif (tables.rows | length) > 0 %}
28
+ {# There were no views, so retrieve table. (There must be a table.) #}
29
+ {% set table_type = adapter.filter_table(all_relations, 'table_type', '') %}
30
+ {% call statement('tables') %}
31
+
32
+ CREATE {{ table_type }} TABLE {{ target.identifier }} AS
33
+ SELECT * FROM {{ source.identifier }};
34
+ DROP TABLE {{ source.identifier }};
35
+ {%- endcall %}
36
+ {% endif %}
37
+ {% endmacro %}
38
+
39
+
40
+ {% macro firebolt__drop_relation(relation) -%}
41
+ {#-
42
+ Drop relation. Drop both table and view because relation doesn't always
43
+ have a table_type specified.
44
+ #}
45
+ {% call statement('drop') %}
46
+
47
+ DROP {{ relation.type | upper }} IF EXISTS {{ relation }} CASCADE;
48
+ {% endcall %}
49
+ {% endmacro %}
@@ -0,0 +1,141 @@
1
+ {% macro firebolt__create_external_table(source_node) %}
2
+ {% if source_node.external.strategy == 'copy' %}
3
+ {{ firebolt__create_with_copy_from(source_node) }}
4
+ {% else %}
5
+ {{ firebolt__create_with_external_table(source_node) }}
6
+ {% endif %}
7
+ {% endmacro %}
8
+
9
+ {% macro firebolt__create_with_external_table(source_node) %}
10
+ {%- set external = source_node.external -%}
11
+ {%- if 'partitions' in external -%}
12
+ {%- set columns = adapter.make_field_partition_pairs(source_node.columns.values(),
13
+ external.partitions) -%}
14
+ {%- else -%}
15
+ {%- set columns = adapter.make_field_partition_pairs(source_node.columns.values(),
16
+ []) -%}
17
+ {%- endif -%}
18
+ {%- set credentials = external.credentials -%}
19
+ {# Leaving out "IF NOT EXISTS" because this should only be called by
20
+ if no DROP IF is necessary. #}
21
+ CREATE EXTERNAL TABLE {{source(source_node.source_name, source_node.name)}} (
22
+ {%- for column in columns -%}
23
+ {{ column }}
24
+ {{- ',' if not loop.last }}
25
+ {% endfor -%}
26
+ )
27
+ {% if external.url %} URL = '{{external.url}}' {%- endif %}
28
+ {%- if credentials and credentials.internal_role_arn %}
29
+ CREDENTIALS = (AWS_ROLE_ARN = '{{credentials.internal_role_arn}}'
30
+ {%- if credentials.external_role_id %}
31
+ AWS_ROLE_EXTERNAL_ID = '{{credentials.external_role_id}}'
32
+ {%- endif -%}
33
+ )
34
+ {% elif credentials and credentials.aws_key_id %}
35
+ CREDENTIALS = (AWS_KEY_ID = '{{credentials.aws_key_id}}'
36
+ AWS_SECRET_KEY = '{{credentials.aws_secret_key}}')
37
+ {%- endif %}
38
+ {%- if external.object_pattern -%} OBJECT_PATTERN = '{{external.object_pattern}}' {%- endif %}
39
+ {% if external.object_patterns -%}
40
+ OBJECT_PATTERN =
41
+ {%- for obj in external.object_patterns -%}
42
+ {{ obj }}
43
+ {{- ',' if not loop.last }}
44
+ {%- endfor %}
45
+ {%- endif %}
46
+ {%- if external.compression -%} COMPRESSION = {{external.compression}} {%- endif %}
47
+ TYPE = {{ external.type }}
48
+ {% endmacro %}
49
+
50
+ {% macro firebolt__create_with_copy_from(source_node) %}
51
+ {# COPY FROM is only available in Firebolt 2.0. #}
52
+ {%- set external = source_node.external -%}
53
+ {%- set credentials = external.credentials -%}
54
+ {%- set where_clause = external.where -%}
55
+ {%- set options = external.options -%}
56
+ {%- set csv_options = options.csv_options -%}
57
+ {%- set error_file_credentials = options.error_file_credentials -%}
58
+
59
+ {# There are no partitions, but this formats the columns correctly. #}
60
+ {%- if 'partitions' in external -%}
61
+ {%- set columns = adapter.make_field_partition_pairs(source_node.columns.values(),
62
+ external.partitions) -%}
63
+ {%- else -%}
64
+ {%- set columns = adapter.make_field_partition_pairs(source_node.columns.values(),
65
+ []) -%}
66
+ {%- endif -%}
67
+ COPY INTO {{source(source_node.source_name, source_node.name)}}
68
+ {%- if columns and columns | length > 0 %}
69
+ (
70
+ {%- for column in columns -%}
71
+ {{ column.name }}
72
+ {%- if column.default %} DEFAULT {{ column.default }}{% endif %}
73
+ {%- if column.source_column_name %} {{ '$' ~ loop.index0 }}{% endif %}
74
+ {{- ',' if not loop.last }}
75
+ {%- endfor -%}
76
+ )
77
+ {%- endif %}
78
+ FROM '{{external.url}}'
79
+ {%- if options or credentials %}
80
+ WITH
81
+ {%- if options.object_pattern %}
82
+ PATTERN = '{{options.object_pattern}}'
83
+ {%- endif %}
84
+ {%- if options.type %}
85
+ TYPE = {{ options.type }}
86
+ {%- endif %}
87
+ {%- if options.auto_create %}
88
+ AUTO_CREATE = {{ options.auto_create | upper }}
89
+ {%- endif %}
90
+ {%- if options.allow_column_mismatch %}
91
+ ALLOW_COLUMN_MISMATCH = {{ options.allow_column_mismatch | upper }}
92
+ {%- endif %}
93
+ {%- if options.error_file %}
94
+ ERROR_FILE = '{{ options.error_file }}'
95
+ {%- endif %}
96
+ {%- if error_file_credentials %}
97
+ ERROR_FILE_CREDENTIALS = (AWS_KEY_ID = '{{ error_file_credentials.aws_key_id }}' AWS_SECRET_KEY = '{{ error_file_credentials.aws_secret_key }}')
98
+ {%- endif %}
99
+ {%- if options.max_errors_per_file %}
100
+ MAX_ERRORS_PER_FILE = {{ options.max_errors_per_file }}
101
+ {%- endif %}
102
+ {%- if csv_options %}
103
+ {%- if csv_options.header %}
104
+ HEADER = {{ csv_options.header | upper }}
105
+ {%- endif %}
106
+ {%- if csv_options.delimiter %}
107
+ DELIMITER = '{{ csv_options.delimiter }}'
108
+ {%- endif %}
109
+ {%- if csv_options.newline %}
110
+ NEWLINE = '{{ csv_options.newline }}'
111
+ {%- endif %}
112
+ {%- if csv_options.quote %}
113
+ QUOTE = {{ csv_options.quote }}
114
+ {%- endif %}
115
+ {%- if csv_options.escape %}
116
+ ESCAPE = '{{ csv_options.escape }}'
117
+ {%- endif %}
118
+ {%- if csv_options.null_string %}
119
+ NULL_STRING = '{{ csv_options.null_string }}'
120
+ {%- endif %}
121
+ {%- if csv_options.empty_field_as_null %}
122
+ EMPTY_FIELD_AS_NULL = {{ csv_options.empty_field_as_null | upper }}
123
+ {%- endif %}
124
+ {%- if csv_options.skip_blank_lines %}
125
+ SKIP_BLANK_LINES = {{ csv_options.skip_blank_lines | upper }}
126
+ {%- endif %}
127
+ {%- if csv_options.date_format %}
128
+ DATE_FORMAT = '{{ csv_options.date_format }}'
129
+ {%- endif %}
130
+ {%- if csv_options.timestamp_format %}
131
+ TIMESTAMP_FORMAT = '{{ csv_options.timestamp_format }}'
132
+ {%- endif %}
133
+ {%- endif %}
134
+ {%- if credentials %}
135
+ CREDENTIALS = (AWS_KEY_ID = '{{credentials.aws_key_id}}' AWS_SECRET_KEY = '{{credentials.aws_secret_key}}')
136
+ {%- endif %}
137
+ {%- endif %}
138
+ {%- if where_clause %}
139
+ WHERE {{ where_clause }}
140
+ {%- endif %}
141
+ {% endmacro %}
@@ -0,0 +1,6 @@
1
+ {% macro firebolt__dropif(source_node) %}
2
+ {% set ddl %}
3
+ DROP TABLE IF EXISTS {{ source_node['name'] }}
4
+ {% endset %}
5
+ {{return(ddl)}}
6
+ {% endmacro %}
@@ -0,0 +1,18 @@
1
+ {% macro firebolt__get_external_build_plan(source_node) %}
2
+ {% set build_plan = [] %}
3
+ {% set old_relation = adapter.get_relation(
4
+ database = source_node.database,
5
+ schema = source_node.schema,
6
+ identifier = source_node.identifier
7
+ ) %}
8
+ {# var(variable, Boolean) defaults to Boolean value if variable isnt set.
9
+ Firebolt doesn't do refresh because we don't need to—external tables will always
10
+ pull most recent data from S3, so the default action below is to skip. #}
11
+ {% if old_relation is none or var('ext_full_refresh', false) %}
12
+ {% set build_plan = build_plan + [dropif(source_node),
13
+ create_external_table(source_node)] %}
14
+ {% else %}
15
+ {% set build_plan = dbt_external_tables.refresh_external_table(source_node) %}
16
+ {% endif %}
17
+ {% do return(build_plan) %}
18
+ {% endmacro %}
@@ -0,0 +1,3 @@
1
+ {% macro firebolt__can_clone_table() %}
2
+ {{ return(False) }}
3
+ {% endmacro %}
@@ -0,0 +1,3 @@
1
+ {% macro firebolt__get_materialized_view_configuration_changes(existing_relation, new_config) %}
2
+ {{ exceptions.raise_compiler_error("Firebolt does not support materialized views") }}
3
+ {% endmacro %}
@@ -0,0 +1,21 @@
1
+ {% macro intersect_columns(source_columns, target_columns) %}
2
+ {# Return a List[FireboltColumn] of columns that appear in both source and target.
3
+ Args:
4
+ source_columns: List[FireboltColumn]
5
+ target_columns: List[FireboltColumn]
6
+ #}
7
+ {% set result = [] %}
8
+ {# Note I'm using `column` and not `name` below, as name is a getter
9
+ and column is the actual field. #}
10
+ {% set source_names = source_columns | map(attribute = 'column') | list %}
11
+ {% set target_names = target_columns | map(attribute = 'column') | list %}
12
+ {# Check whether the name attribute exists in the target - this does
13
+ not perform a data type check. This is O(m•n), but cardinality
14
+ of columns is probably low enough that it doesn't matter. #}
15
+ {% for sc in source_columns %}
16
+ {% if sc.name in target_names %}
17
+ {{ result.append(sc) }}
18
+ {% endif %}
19
+ {% endfor %}
20
+ {{ return(result) }}
21
+ {% endmacro %}
@@ -0,0 +1,131 @@
1
+ {% macro dbt_firebolt_get_tmp_relation_type(strategy) %}
2
+ {#
3
+ Determine whether to use a temporary view or a table based on
4
+ specified strategy and config. Users can override the behaviour
5
+ by setting tmp_replation_type. Default is to use a view (more efficient).
6
+
7
+ Arguments:
8
+ strategy: incremental strategy from config. e.g. "append"
9
+ #}
10
+ {%- set tmp_relation_type = config.get('tmp_relation_type') -%}
11
+
12
+ {% if tmp_relation_type == "table" %}
13
+ {{ return("table") }}
14
+ {% elif tmp_relation_type == "view" %}
15
+ {{ return("view") }}
16
+ {% elif strategy in ("default", "append", "insert_overwrite") %}
17
+ {{ return("view") }}
18
+ {% else %}
19
+ {{ return("table") }}
20
+ {% endif %}
21
+ {% endmacro %}
22
+
23
+ {% materialization incremental, adapter='firebolt' -%}
24
+ {#
25
+ Incremental implementation. Actual strategy SQL is determined
26
+ outside this materialization, in strategies.sql.
27
+
28
+ This function works by first creating a view, `new_records`, of type `BaseRelation`,
29
+ then using a CTE defined in the project model to populate `new_records` with
30
+ any records that ought to be inserted into the original table (views aren't currently
31
+ supported), `existing`, using an INSERT. This process is agnostic to the incremental
32
+ strategy actually used: the specific INSERT is defined in `strategies.sql`.
33
+
34
+ Note that all new relations must first be instantiated as BaseRelation objects,
35
+ and that those objects are used to create and query actual relations in the DB.
36
+ Care must be taken to correctly define each BaseRelation as either a view or
37
+ a table, lest subsequent operations on the relations (be the they objects or
38
+ the DB tables the objects abstract) fail.
39
+ #}
40
+
41
+ {% set unique_key = config.get('unique_key') %}
42
+
43
+ {% set grant_config = config.get('grants') %}
44
+
45
+ {%- set strategy = config.get('incremental_strategy') -%}
46
+ {%- if is_incremental() and strategy is none -%}
47
+ {{ log('Model %s is set to incremental, but no incremental strategy is set. '
48
+ 'Defaulting to append' % this, True) }}
49
+ {%- set strategy = 'append' -%}
50
+ {%- endif -%}
51
+ {# In following lines:
52
+
53
+ `target` is just a dbt `BaseRelation` object, not a DB table.
54
+
55
+ We're only retrieving `existing` to check for existence; `load_relation()`
56
+ returns a `BaseRelation` with the dictionary values of any relations that exist
57
+ in dbt's cache that share the identifier of the passed relation. If none
58
+ exist, returns None. Note that this does *not* create a table in the DB.
59
+
60
+ `target` is a relation with all the same fields as `existing`, but guaranteed
61
+ to be an actual `BaseRelation` object. #}
62
+ {%- set target = this.incorporate(type='table') -%}
63
+ {%- set existing = load_relation(this) -%}
64
+ {%- set tmp_relation_type = dbt_firebolt_get_tmp_relation_type(strategy) -%}
65
+ {%- set new_records = make_temp_relation(target) -%}
66
+ {{ drop_relation_if_exists(new_records) }}
67
+ {%- set new_records = new_records.incorporate(type=tmp_relation_type) -%}
68
+ {%- set on_schema_change = incremental_validate_on_schema_change(
69
+ config.get('on_schema_change'),
70
+ default='ignore') -%}
71
+
72
+ -- `BEGIN` happens here:
73
+ {{ run_hooks(pre_hooks, inside_transaction=True) }}
74
+ {%- set partition_by = config.get('partition_by') -%}
75
+ {# First check whether we want to full refresh for existing view or config reasons. #}
76
+ {%- if strategy == 'insert_overwrite' and not partition_by -%}
77
+ {% do exceptions.raise_compiler_error('`insert_overwrite` is specified for model %s, '
78
+ 'but `partition_by` is not. `insert_overwrite` '
79
+ 'requires a partition.' % (target)) %}
80
+ {%- endif -%}
81
+ {%- set do_full_refresh = (should_full_refresh()
82
+ or existing.is_view) %}
83
+ {% if existing is none %}
84
+ {%- set build_sql = create_table_as(False, target, sql) -%}
85
+ {%- elif do_full_refresh -%}
86
+ {{ drop_relation_if_exists(existing) }}
87
+ {%- set build_sql = create_table_as(False, target, sql) -%}
88
+ {%- else -%}
89
+ {# Actually do the incremental query here. #}
90
+ {# Instantiate new objects in dbt's internal list. Have to
91
+ run this query so dbt can query the DB to get the columns in
92
+ new_records. #}
93
+ {%- if tmp_relation_type=='view' -%}
94
+ {%- do run_query(create_view_as(new_records, sql)) -%}
95
+ {%- else -%}
96
+ {%- do run_query(create_table_as(True, new_records, sql)) -%}
97
+ {%- endif -%}
98
+ {# All errors involving schema changes are dealt with in `process_schema_changes`. #}
99
+ {%- set dest_columns = process_schema_changes(on_schema_change,
100
+ new_records,
101
+ existing) -%}
102
+ {%- set incremental_predicates = config.get('predicates', none) or config.get('incremental_predicates', none) -%}
103
+ {%- set build_sql = get_incremental_sql(strategy,
104
+ new_records,
105
+ target,
106
+ unique_key,
107
+ dest_columns,
108
+ incremental_predicates) -%}
109
+ {%- endif -%}
110
+ {%- call statement("main") -%}
111
+ {{ build_sql }}
112
+ {%- endcall -%}
113
+
114
+ {% set should_revoke = should_revoke(existing_relation, full_refresh_mode) %}
115
+ {% do apply_grants(target_relation, grant_config, should_revoke) %}
116
+
117
+ {# Todo: figure out what persist_docs and create_indexes do. #}
118
+ {%- do persist_docs(target, model) -%}
119
+ {%- if existing is none
120
+ or existing.is_view
121
+ or should_full_refresh() -%}
122
+ {%- do create_indexes(target) -%}
123
+ {%- endif %}
124
+
125
+ {{ drop_relation_if_exists(new_records) }}
126
+
127
+ {{ run_hooks(post_hooks, inside_transaction=True) }}
128
+
129
+ {{ return({'relations': [target]}) }}
130
+
131
+ {%- endmaterialization %}
@@ -0,0 +1,14 @@
1
+ {% macro is_incremental() %}
2
+ {#- Return True if model is incremental, otherwise False. -#}
3
+ {% if not execute %}
4
+ {{ return(False) }}
5
+ {% else %}
6
+ {% set relation = adapter.get_relation(this.database,
7
+ this.schema,
8
+ this.table) %}
9
+ {{ return(relation is not none
10
+ and relation.type == 'table'
11
+ and model.config.materialized == 'incremental'
12
+ and not should_full_refresh()) }}
13
+ {% endif %}
14
+ {% endmacro %}
@@ -0,0 +1,38 @@
1
+ {% macro firebolt__get_delete_insert_merge_sql(target, source, unique_key, dest_columns, incremental_predicates) -%}
2
+
3
+ {%- set dest_cols_csv = get_quoted_csv(dest_columns | map(attribute="name")) -%}
4
+
5
+ {% if unique_key %}
6
+ {% if unique_key is sequence and unique_key is not string %}
7
+ delete from {{ target }}
8
+ where
9
+ ({{ get_quoted_csv(unique_key) }}) in (
10
+ select {{ get_quoted_csv(unique_key) }}
11
+ from {{ source }}
12
+ )
13
+ {% if incremental_predicates %}
14
+ {% for predicate in incremental_predicates %}
15
+ and {{ predicate }}
16
+ {% endfor %}
17
+ {% endif %};
18
+ {% else %}
19
+ delete from {{ target }}
20
+ where (
21
+ {{ unique_key }}) in (
22
+ select ({{ unique_key }})
23
+ from {{ source }}
24
+ )
25
+ {%- if incremental_predicates %}
26
+ {% for predicate in incremental_predicates %}
27
+ and {{ predicate }}
28
+ {% endfor %}
29
+ {%- endif -%};
30
+
31
+ {% endif %}
32
+ {% endif %}
33
+
34
+ insert into {{ target }} ({{ dest_cols_csv }})
35
+ select {{ dest_cols_csv }}
36
+ from {{ source }}
37
+
38
+ {%- endmacro %}