sqlleaf 0.0.1__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 (111) hide show
  1. sqlleaf/__init__.py +4 -0
  2. sqlleaf/dialects/__init__.py +0 -0
  3. sqlleaf/dialects/plpgsql/__init__.py +0 -0
  4. sqlleaf/dialects/plpgsql/p_classes.py +275 -0
  5. sqlleaf/dialects/plpgsql/p_dialect.py +148 -0
  6. sqlleaf/dialects/plpgsql/p_generator.py +538 -0
  7. sqlleaf/dialects/plpgsql/p_parser.py +1244 -0
  8. sqlleaf/dialects/plpgsql/p_tokenizer.py +41 -0
  9. sqlleaf/exception.py +67 -0
  10. sqlleaf/lineage.py +326 -0
  11. sqlleaf/mappings.py +376 -0
  12. sqlleaf/models/__init__.py +0 -0
  13. sqlleaf/models/context.py +153 -0
  14. sqlleaf/models/hook.py +9 -0
  15. sqlleaf/models/node/__init__.py +27 -0
  16. sqlleaf/models/node/base.py +148 -0
  17. sqlleaf/models/node/column.py +345 -0
  18. sqlleaf/models/node/dynamodb.py +39 -0
  19. sqlleaf/models/node/function.py +33 -0
  20. sqlleaf/models/node/interval.py +21 -0
  21. sqlleaf/models/node/json.py +57 -0
  22. sqlleaf/models/node/literal.py +17 -0
  23. sqlleaf/models/node/null.py +14 -0
  24. sqlleaf/models/node/pivot.py +34 -0
  25. sqlleaf/models/node/program.py +27 -0
  26. sqlleaf/models/node/sequence.py +18 -0
  27. sqlleaf/models/node/star.py +11 -0
  28. sqlleaf/models/node/stream.py +14 -0
  29. sqlleaf/models/node/user_defined_function.py +33 -0
  30. sqlleaf/models/node/var.py +7 -0
  31. sqlleaf/models/node/variable.py +8 -0
  32. sqlleaf/models/node/window.py +22 -0
  33. sqlleaf/models/query/__init__.py +33 -0
  34. sqlleaf/models/query/base.py +248 -0
  35. sqlleaf/models/query/call.py +111 -0
  36. sqlleaf/models/query/copy.py +144 -0
  37. sqlleaf/models/query/ctas.py +61 -0
  38. sqlleaf/models/query/database.py +48 -0
  39. sqlleaf/models/query/delete.py +26 -0
  40. sqlleaf/models/query/execute.py +66 -0
  41. sqlleaf/models/query/holder.py +49 -0
  42. sqlleaf/models/query/insert.py +69 -0
  43. sqlleaf/models/query/merge.py +29 -0
  44. sqlleaf/models/query/multitable_insert.py +42 -0
  45. sqlleaf/models/query/prepare.py +61 -0
  46. sqlleaf/models/query/procedure.py +72 -0
  47. sqlleaf/models/query/put.py +38 -0
  48. sqlleaf/models/query/replace.py +32 -0
  49. sqlleaf/models/query/schema.py +47 -0
  50. sqlleaf/models/query/select.py +27 -0
  51. sqlleaf/models/query/sequence.py +33 -0
  52. sqlleaf/models/query/set.py +74 -0
  53. sqlleaf/models/query/stage.py +78 -0
  54. sqlleaf/models/query/table.py +220 -0
  55. sqlleaf/models/query/trigger.py +42 -0
  56. sqlleaf/models/query/type.py +42 -0
  57. sqlleaf/models/query/unload.py +64 -0
  58. sqlleaf/models/query/update.py +45 -0
  59. sqlleaf/models/query/user_defined_function.py +297 -0
  60. sqlleaf/models/query/values.py +20 -0
  61. sqlleaf/models/query/view.py +41 -0
  62. sqlleaf/path.py +194 -0
  63. sqlleaf/processors/__init__.py +0 -0
  64. sqlleaf/processors/collector/__init__.py +0 -0
  65. sqlleaf/processors/collector/collector.py +827 -0
  66. sqlleaf/processors/collector/substitute.py +396 -0
  67. sqlleaf/processors/generator/__init__.py +0 -0
  68. sqlleaf/processors/generator/dialects/__init__.py +5 -0
  69. sqlleaf/processors/generator/dialects/athena.py +45 -0
  70. sqlleaf/processors/generator/dialects/base.py +572 -0
  71. sqlleaf/processors/generator/dialects/mysql.py +19 -0
  72. sqlleaf/processors/generator/dialects/postgres.py +182 -0
  73. sqlleaf/processors/generator/dialects/redshift.py +192 -0
  74. sqlleaf/processors/generator/dialects/snowflake.py +90 -0
  75. sqlleaf/processors/generator/generator.py +495 -0
  76. sqlleaf/processors/transformer/__init__.py +31 -0
  77. sqlleaf/processors/transformer/base.py +899 -0
  78. sqlleaf/processors/transformer/call.py +9 -0
  79. sqlleaf/processors/transformer/copy.py +63 -0
  80. sqlleaf/processors/transformer/ctas.py +39 -0
  81. sqlleaf/processors/transformer/delete.py +14 -0
  82. sqlleaf/processors/transformer/execute.py +9 -0
  83. sqlleaf/processors/transformer/expressions/__init__.py +11 -0
  84. sqlleaf/processors/transformer/expressions/functional_notation.py +22 -0
  85. sqlleaf/processors/transformer/expressions/row.py +451 -0
  86. sqlleaf/processors/transformer/expressions/values.py +318 -0
  87. sqlleaf/processors/transformer/insert.py +125 -0
  88. sqlleaf/processors/transformer/merge.py +14 -0
  89. sqlleaf/processors/transformer/multitable_insert.py +14 -0
  90. sqlleaf/processors/transformer/replace.py +23 -0
  91. sqlleaf/processors/transformer/transformer.py +141 -0
  92. sqlleaf/processors/transformer/udf.py +235 -0
  93. sqlleaf/processors/transformer/unload.py +30 -0
  94. sqlleaf/processors/transformer/update.py +174 -0
  95. sqlleaf/processors/transformer/values.py +10 -0
  96. sqlleaf/settings/__init__.py +19 -0
  97. sqlleaf/settings/athena.py +23 -0
  98. sqlleaf/settings/base.py +37 -0
  99. sqlleaf/settings/postgres.py +52 -0
  100. sqlleaf/system_tables/load.py +0 -0
  101. sqlleaf/typing.py +93 -0
  102. sqlleaf/util/__init__.py +34 -0
  103. sqlleaf/util/expression.py +282 -0
  104. sqlleaf/util/graph.py +116 -0
  105. sqlleaf/util/helpers.py +42 -0
  106. sqlleaf/util/iterators.py +37 -0
  107. sqlleaf-0.0.1.dist-info/METADATA +46 -0
  108. sqlleaf-0.0.1.dist-info/RECORD +111 -0
  109. sqlleaf-0.0.1.dist-info/WHEEL +5 -0
  110. sqlleaf-0.0.1.dist-info/licenses/LICENSE +21 -0
  111. sqlleaf-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from sqlglot import exp
6
+
7
+ from sqlleaf import mappings, util
8
+ from sqlleaf.models.query.base import Query
9
+ from sqlleaf.typing import SourceInfo, TargetInfo
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class PutQueryProperties:
14
+ file_format: str
15
+
16
+
17
+ class PutQuery(Query):
18
+ KIND = "put"
19
+
20
+ def __init__(self, expr: exp.Put, dialect: str, object_mapping: mappings.ObjectMapping, statement_index: int):
21
+ source = expr.this
22
+ target = expr.args["target"]
23
+
24
+ util.rename_if_stage(source, target)
25
+
26
+ source_type = self._determine_expression_type(source, dialect)
27
+ target_type = self._determine_expression_type(target, dialect)
28
+
29
+ super().__init__(
30
+ dialect=dialect,
31
+ statement=expr,
32
+ statement_index=statement_index,
33
+ object_mapping=object_mapping,
34
+ source_info=SourceInfo(expression=source, type=source_type),
35
+ target_info=TargetInfo(expression=target, type=target_type),
36
+ skip_type_annotation=True,
37
+ )
38
+ self.parameters = PutQueryProperties(file_format="TEXT")
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ import sqlglot
4
+ from sqlglot import exp
5
+
6
+ from sqlleaf import mappings
7
+ from sqlleaf.models.query.insert import InsertQuery
8
+
9
+
10
+ class ReplaceQuery(InsertQuery):
11
+ KIND = "replace"
12
+
13
+ def __init__(
14
+ self,
15
+ expr: exp.Command,
16
+ dialect: str,
17
+ object_mapping: mappings.ObjectMapping,
18
+ statement_index: int,
19
+ ):
20
+ # Transform to Insert temporarily to initialize source/target info
21
+ expression = expr.args.get("expression")
22
+ new_sql = f"INSERT {expression.this}" if expression else "INSERT"
23
+ insert_expr = sqlglot.parse_one(new_sql, dialect=dialect)
24
+
25
+ super().__init__(
26
+ expr=insert_expr,
27
+ dialect=dialect,
28
+ object_mapping=object_mapping,
29
+ statement_index=statement_index,
30
+ )
31
+ # Restore the original statement
32
+ self.statement = expr
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from sqlglot import exp
6
+
7
+ from sqlleaf import mappings, util
8
+ from sqlleaf.models.query.base import Query
9
+ from sqlleaf.typing import SqlObjectType, TargetInfo
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class SchemaQueryProperties:
14
+ location: str | None
15
+
16
+ @classmethod
17
+ def from_expression(cls, expr: exp.Create, dialect: str):
18
+ location = util.get_location_property(expr, dialect)
19
+ return cls(location=location)
20
+
21
+
22
+ class SchemaQuery(Query):
23
+ KIND = "schema"
24
+
25
+ def __init__(
26
+ self,
27
+ expr: exp.Create,
28
+ dialect: str,
29
+ object_mapping: mappings.ObjectMapping,
30
+ statement_index: int,
31
+ ):
32
+ source = None
33
+ target = expr.this
34
+
35
+ super().__init__(
36
+ dialect=dialect,
37
+ statement=expr,
38
+ statement_index=statement_index,
39
+ object_mapping=object_mapping,
40
+ source_info=source,
41
+ target_info=TargetInfo(expression=target, type=SqlObjectType.SCHEMA),
42
+ )
43
+ self.properties = SchemaQueryProperties.from_expression(expr, dialect)
44
+
45
+ @property
46
+ def location(self) -> str | None:
47
+ return self.properties.location
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from sqlglot import exp
4
+
5
+ from sqlleaf import mappings
6
+ from sqlleaf.models.query.base import Query
7
+ from sqlleaf.typing import SourceInfo, TargetInfo, SqlObjectType
8
+
9
+
10
+ class SelectQuery(Query):
11
+ KIND = "select"
12
+
13
+ def __init__(self, expr: exp.Select, dialect: str, object_mapping: mappings.ObjectMapping, statement_index: int):
14
+ source = expr
15
+ source_type = self._determine_expression_type(source, dialect)
16
+
17
+ super().__init__(
18
+ dialect=dialect,
19
+ statement=expr,
20
+ statement_index=statement_index,
21
+ object_mapping=object_mapping,
22
+ source_info=SourceInfo(expression=source, type=source_type),
23
+ target_info=TargetInfo(expression=None, type=SqlObjectType.NONE)
24
+ )
25
+
26
+ def get_ctes(self):
27
+ return getattr(self.statement, "ctes", [])
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from sqlglot import exp
4
+
5
+ from sqlleaf import mappings, util
6
+ from sqlleaf.models.query.base import Query
7
+ from sqlleaf.typing import TargetInfo
8
+
9
+
10
+ class SequenceQuery(Query):
11
+ KIND = "sequence"
12
+
13
+ def __init__(
14
+ self,
15
+ expr: exp.Create,
16
+ dialect: str,
17
+ object_mapping: mappings.ObjectMapping,
18
+ statement_index: int,
19
+ ):
20
+ source = None
21
+ target = expr.this
22
+
23
+ target_type = self._determine_expression_type(target, dialect)
24
+
25
+ super().__init__(
26
+ dialect=dialect,
27
+ statement=expr,
28
+ statement_index=statement_index,
29
+ object_mapping=object_mapping,
30
+ source_info=source,
31
+ target_info=TargetInfo(expression=target, type=target_type),
32
+ )
33
+ self.property = util.find_property(expr, self.target_info.expression, dialect)
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from sqlglot import exp
4
+
5
+ from sqlleaf import mappings
6
+ from sqlleaf.models.query.base import Query
7
+
8
+
9
+ class SetQuery(Query):
10
+ """
11
+ Represents a SQL SET statement, used to define session variables.
12
+
13
+ In Snowflake, this can take several forms:
14
+ - SET a = 10;
15
+ - SET (a, b) = (10, 20);
16
+ - SET (a, b) = (SELECT x, y FROM table);
17
+
18
+ This class extracts these variables and stores them in the ObjectMapping
19
+ so they can be substituted into subsequent queries in the same session.
20
+ """
21
+
22
+ KIND = "set"
23
+
24
+ def __init__(self, expr: exp.Set, dialect: str, object_mapping: mappings.ObjectMapping, statement_index: int):
25
+ super().__init__(
26
+ dialect=dialect,
27
+ statement=expr,
28
+ statement_index=statement_index,
29
+ object_mapping=object_mapping,
30
+ source_info=None,
31
+ target_info=None,
32
+ skip_type_annotation=True,
33
+ )
34
+ self._collect()
35
+
36
+ def _collect(self):
37
+ """
38
+ Extracts session variables from the SET statement.
39
+ """
40
+ for expression in self.statement.expressions:
41
+ eq_expr = expression.this if isinstance(expression, exp.SetItem) else expression
42
+ if not isinstance(eq_expr, exp.EQ):
43
+ continue
44
+
45
+ left, right = eq_expr.left, eq_expr.right
46
+ if isinstance(left, exp.Tuple):
47
+ # SET (a, b) = (10, 20) or SET (a, b) = (SELECT ...)
48
+ names = [col.name.upper() for col in left.expressions]
49
+ if isinstance(right, exp.Tuple):
50
+ for name, value in zip(names, right.expressions):
51
+ self.object_mapping.session_variables[name] = value
52
+
53
+ else: # Subquery - clone it once per variable, keeping only the i-th column
54
+ # Example: SET (a, b) = (SELECT col1, col2 FROM t)
55
+ # This will store:
56
+ # a -> (SELECT col1 FROM t)
57
+ # b -> (SELECT col2 FROM t)
58
+ if isinstance(right, exp.Subquery) and isinstance(right.this, exp.Select):
59
+ select_cols = right.this.expressions
60
+
61
+ for i, name in enumerate(names):
62
+ cloned = right.copy()
63
+ if i < len(select_cols):
64
+ cloned.this.set("expressions", [select_cols[i].copy()])
65
+
66
+ # Map the variable name to its corresponding single-column subquery
67
+ self.object_mapping.session_variables[name] = cloned
68
+ else:
69
+ for name in names:
70
+ self.object_mapping.session_variables[name] = right
71
+ else:
72
+ # SET a = <expr>
73
+ name = left.name.upper()
74
+ self.object_mapping.session_variables[name] = right
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+ from dataclasses import dataclass
5
+
6
+ from sqlglot import exp
7
+
8
+ from sqlleaf import mappings, util
9
+ from sqlleaf.models.query.base import Query
10
+ from sqlleaf.typing import SourceInfo, TargetInfo
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class StageQueryProperties:
15
+ path: str
16
+ is_temporary: bool
17
+
18
+ @classmethod
19
+ def from_expression(
20
+ cls, expr: exp.Create, source_info: SourceInfo, target_info: TargetInfo, dialect: str
21
+ ) -> StageQueryProperties:
22
+ is_temporary = expr.find(exp.TemporaryProperty) is not None
23
+
24
+ path = ""
25
+ # Get the URL
26
+ if props := expr.args.get("properties"):
27
+ for prop in props.expressions:
28
+ if isinstance(prop, exp.Property) and prop.name.upper() == "URL":
29
+ path = prop.args["value"].this
30
+ break
31
+ return cls(is_temporary=is_temporary, path=path)
32
+
33
+
34
+ class StageQuery(Query):
35
+ KIND = "stage"
36
+
37
+ def __init__(
38
+ self,
39
+ expr: exp.Create,
40
+ dialect: str,
41
+ object_mapping: mappings.ObjectMapping,
42
+ statement_index: int,
43
+ ):
44
+ source = None
45
+ target = util.get_table(expr)
46
+
47
+ target_type = self._determine_expression_type(target, dialect)
48
+
49
+ super().__init__(
50
+ dialect=dialect,
51
+ statement=expr,
52
+ statement_index=statement_index,
53
+ object_mapping=object_mapping,
54
+ source_info=source,
55
+ target_info=TargetInfo(expression=target, type=target_type),
56
+ )
57
+
58
+ self.column_defs: t.List[exp.ColumnDef] = []
59
+ # Needed due to a bug in sqlglot. Never access the table name via print()!
60
+ # as it prints double-double quotes
61
+ target.this.set("this", "@" + str(target.this))
62
+ target.this.set("quoted", False)
63
+
64
+ self.properties = StageQueryProperties.from_expression(
65
+ self.statement, self.source_info, self.target_info, dialect
66
+ )
67
+
68
+ @property
69
+ def path(self) -> str:
70
+ return self.properties.path
71
+
72
+ @property
73
+ def name(self) -> str:
74
+ return self.target_info.expression.name
75
+
76
+ @property
77
+ def is_temporary(self) -> bool:
78
+ return self.properties.is_temporary
@@ -0,0 +1,220 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+ from dataclasses import dataclass
5
+
6
+ from sqlglot import exp
7
+
8
+ from sqlleaf import mappings, util, exception, settings
9
+ from sqlleaf.models.query.base import Query
10
+ from sqlleaf.typing import SourceInfo, TargetInfo
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class TableQueryProperties:
15
+ location: exp.LocationProperty | None
16
+
17
+ @classmethod
18
+ def from_expression(cls, expr: exp.Create, dialect: str):
19
+ location = None
20
+ if properties := expr.args.get("properties"):
21
+ location = properties.find(exp.LocationProperty)
22
+ return cls(location=location)
23
+
24
+
25
+ class TableQuery(Query):
26
+ KIND = "table"
27
+
28
+ def __init__(self, expr: exp.Create, dialect: str, object_mapping: mappings.ObjectMapping, statement_index: int):
29
+ self.properties = TableQueryProperties.from_expression(expr, dialect)
30
+
31
+ if self.properties.location:
32
+ location_literal = self.properties.location.this
33
+ source_type = self._determine_expression_type(location_literal, dialect)
34
+ source = SourceInfo(expression=location_literal, type=source_type)
35
+ else:
36
+ source = None
37
+
38
+ target = util.get_table(expr.this)
39
+ target_type = self._determine_expression_type(target, dialect)
40
+
41
+ super().__init__(
42
+ dialect=dialect,
43
+ statement=expr,
44
+ statement_index=statement_index,
45
+ object_mapping=object_mapping,
46
+ source_info=source,
47
+ target_info=TargetInfo(expression=target, type=target_type),
48
+ skip_type_annotation=True,
49
+ )
50
+ self.column_defs: t.List[exp.ColumnDef] = []
51
+ self.system_column_defs: t.List[exp.ColumnDef] = []
52
+ self.inherits: t.List[TableQuery] = []
53
+ self.inherited_by: t.List[TableQuery] = []
54
+
55
+ self.property: str = util.find_property(expr, self.target_info.expression, dialect)
56
+
57
+ @property
58
+ def location(self) -> exp.LocationProperty | None:
59
+ return self.properties.location
60
+
61
+ def is_external(self) -> bool:
62
+ return
63
+
64
+ def get_column_defs(self, include_system: bool = False) -> t.List[exp.ColumnDef]:
65
+ return self.column_defs + self.system_column_defs if include_system else self.column_defs
66
+
67
+ def get_column_names_with_types(self, include_system: bool = False) -> t.Dict[str, str]:
68
+ """
69
+ Used by sqlglot's MappingSchema
70
+ """
71
+ columns = {col.name: str(col.kind) for col in self.get_column_defs(include_system=include_system)}
72
+ return columns
73
+
74
+ def set_column_defs(self) -> None:
75
+ """
76
+ Collect all the column definitions for this table.
77
+ """
78
+ all_columns = []
79
+
80
+ for expression in self.statement.this.expressions:
81
+ if isinstance(expression, exp.ColumnDef):
82
+ all_columns.append(expression)
83
+ elif isinstance(expression, exp.LikeProperty):
84
+ like_columns = self._collect_like_columns(expression, self.object_mapping, self.get_target_as_table())
85
+ all_columns.extend(like_columns)
86
+ elif isinstance(expression, exp.Identifier):
87
+ # CREATE TABLE (a INT, b);
88
+ raise exception.InvalidQueryError(message=f"Column '{expression.name}' must define a data type.")
89
+ else:
90
+ raise exception.InvalidQueryError(message=f"Unsupported column expression: {type(expression)}")
91
+
92
+ if inherited_props := list[exp.InheritsProperty](self.statement.find_all(exp.InheritsProperty)):
93
+ inherited_columns = self._collect_inherited_columns(inherited_props)
94
+ all_columns = inherited_columns + all_columns
95
+
96
+ # Set the column's 'default' type to the column's own type (it is sometimes missing)
97
+ for col_def in all_columns:
98
+ if default := col_def.find(exp.DefaultColumnConstraint):
99
+ default.this.type = col_def.kind
100
+
101
+ self.column_defs = all_columns
102
+ self.system_column_defs = settings.system_columns(self.dialect)
103
+
104
+ def _collect_inherited_columns(
105
+ self, inherits_properties: t.List[exp.InheritsProperty]
106
+ ) -> t.List[exp.ColumnDef]:
107
+ """
108
+ Search for tables referenced as 'CREATE TABLE b INHERITS (a)' and collect all their columns.
109
+ A table can have multiple tables in an INHERITS clause.
110
+ """
111
+ column_defs = []
112
+
113
+ for inh_prop in inherits_properties:
114
+ for inh_table in inh_prop.expressions:
115
+ parent_table_query = t.cast(TableQuery, self.object_mapping.lookup_table_query(table=inh_table))
116
+ parent_table_query.inherited_by.append(self)
117
+ self.inherits.append(parent_table_query)
118
+
119
+ # Re-assign the columns to a copy of the correct table
120
+ expr = self.target_info.expression
121
+ if expr.parent:
122
+ schema = util.copy_expression(expr.parent)
123
+ for parent_col_def in parent_table_query.column_defs:
124
+ col_def = parent_col_def.copy()
125
+ schema.append("expressions", col_def)
126
+ column_defs.append(col_def)
127
+
128
+ return column_defs
129
+
130
+ def _collect_like_columns(
131
+ self, like_property: exp.LikeProperty, object_mapping: mappings.ObjectMapping, child_object: exp.Table
132
+ ) -> t.List[exp.ColumnDef]:
133
+ """
134
+ Search for tables referenced as 'CREATE TABLE b (LIKE a)'.
135
+ A table can have multiple LIKE clauses.
136
+ """
137
+ columns = []
138
+ property_names = []
139
+
140
+ for like_prop in like_property.expressions:
141
+ # sqlglot concats properties with '='
142
+ property_names.append(str(like_prop).replace("=", " "))
143
+
144
+ properties = self._get_properties_to_include(property_names)
145
+
146
+ # Look up the like-table's columns and determine which properties to transfer
147
+ parent_table_query = t.cast(TableQuery, object_mapping.lookup_table_query(table=like_property.this))
148
+ parent_columns = parent_table_query.get_column_defs()
149
+
150
+ for parent_col_def in parent_columns:
151
+ new_col = parent_col_def.copy()
152
+ for prop_name, prop_attrs in properties.items():
153
+ prop_expr = new_col.find(prop_attrs["expr"])
154
+
155
+ if properties[prop_name]["include"]:
156
+ # Set the expression's parent to be the new table (it's missing)
157
+ if prop_expr:
158
+ for inner_col in prop_expr.find_all(exp.Column):
159
+ # A GENERATED column expression might refer to other columns
160
+ try:
161
+ referenced_parent_col_def = [c for c in parent_columns if c.name == inner_col.name][0]
162
+ except IndexError:
163
+ message = f"Column '{inner_col.name}' does not exist in table '{child_object}'."
164
+ raise exception.MappingError(message=message)
165
+
166
+ inner_col.set("catalog", exp.to_identifier(child_object.catalog))
167
+ inner_col.set("db", exp.to_identifier(child_object.db))
168
+ inner_col.set("table", exp.to_identifier(child_object.this))
169
+ inner_col.type = referenced_parent_col_def.kind
170
+ else:
171
+ # Discard the column's expression
172
+ if prop_expr:
173
+ prop_expr.parent.pop()
174
+
175
+ columns.append(new_col)
176
+
177
+ return columns
178
+
179
+ def _get_properties_to_include(self, options: t.List[str]) -> t.Dict:
180
+ """
181
+ Determine which column properties to keep within a LIKE according to the rules below.
182
+
183
+ From the Postgres docs:
184
+ Specifying INCLUDING copies the property, specifying EXCLUDING omits the property.
185
+ EXCLUDING is the default. If multiple specifications are made for the same kind
186
+ of object, the last one is used. It could be useful to write individual EXCLUDING
187
+ clauses after INCLUDING ALL to select all but some specific options.
188
+ """
189
+ # All supported properties
190
+ properties = {
191
+ "DEFAULTS": {"include": False, "expr": exp.DefaultColumnConstraint},
192
+ "GENERATED": {"include": False, "expr": exp.ComputedColumnConstraint},
193
+ "IDENTITY": {"include": False, "expr": exp.GeneratedAsIdentityColumnConstraint},
194
+ }
195
+
196
+ for opt in options:
197
+ opt = opt.strip().upper()
198
+
199
+ if opt == "INCLUDING ALL":
200
+ for prop in properties:
201
+ properties[prop]["include"] = True
202
+ continue
203
+
204
+ if opt == "EXCLUDING ALL":
205
+ for prop in properties:
206
+ properties[prop]["include"] = False
207
+ continue
208
+
209
+ parts = opt.split()
210
+ action, prop = parts
211
+
212
+ if prop not in properties:
213
+ continue # Ignore unknown properties
214
+
215
+ if action == "INCLUDING":
216
+ properties[prop]["include"] = True
217
+ elif action == "EXCLUDING":
218
+ properties[prop]["include"] = False
219
+
220
+ return properties
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from sqlglot import exp
4
+
5
+ from sqlleaf import mappings
6
+ from sqlleaf.models.query.base import Query
7
+ from sqlleaf.typing import SqlObjectType, TargetInfo
8
+
9
+
10
+ class TriggerQuery(Query):
11
+ KIND = "trigger"
12
+
13
+ def __init__(
14
+ self,
15
+ expr: exp.Create,
16
+ dialect: str,
17
+ object_mapping: mappings.ObjectMapping,
18
+ statement_index: int,
19
+ ):
20
+ """
21
+ Example:
22
+ CREATE TRIGGER before_fruit_insert
23
+ BEFORE INSERT ON fruit.processed
24
+ FOR EACH ROW
25
+ EXECUTE FUNCTION check_fruit('apple');
26
+ """
27
+ properties = expr.args["properties"].expressions[0]
28
+ target = expr.this
29
+ super().__init__(
30
+ dialect=dialect,
31
+ statement=expr,
32
+ statement_index=statement_index,
33
+ object_mapping=object_mapping,
34
+ source_info=None,
35
+ target_info=TargetInfo(expression=target, type=SqlObjectType.TABLE),
36
+ )
37
+ self.name = expr.name # before_fruit_insert
38
+ self.table = properties.args["table"] # Table(fruit.processed)
39
+ self.timing = properties.args["timing"] # BEFORE
40
+ self.events = properties.args["events"] # [TriggerEvent(INSERT)]
41
+ self.execute = properties.args["execute"].this # Anonymous(check_fruit())
42
+ self.execute_args = self.execute.expressions # [Literal(apple)]
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from sqlglot import exp
6
+
7
+ from sqlleaf import mappings, util
8
+ from sqlleaf.models.query.base import Query
9
+ from sqlleaf.typing import TargetInfo
10
+
11
+
12
+ class TypeQuery(Query):
13
+ KIND = "type"
14
+
15
+ def __init__(
16
+ self, statement: exp.Create, dialect: str, object_mapping: mappings.ObjectMapping, statement_index: int
17
+ ):
18
+ source = None
19
+ target = util.get_table(statement)
20
+
21
+ target_type = self._determine_expression_type(target, dialect)
22
+
23
+ super().__init__(
24
+ dialect=dialect,
25
+ statement=statement,
26
+ statement_index=statement_index,
27
+ object_mapping=object_mapping,
28
+ source_info=source,
29
+ target_info=TargetInfo(expression=target, type=target_type),
30
+ )
31
+ self.column_defs: t.List[exp.ColumnDef] = []
32
+ self._collect()
33
+
34
+ def _collect(self) -> None:
35
+ """
36
+ Collect the type's column definitions.
37
+ """
38
+ expression = self.statement.args.get("expression")
39
+ if isinstance(expression, exp.Schema):
40
+ for col_def in expression.expressions:
41
+ if isinstance(col_def, exp.ColumnDef):
42
+ self.column_defs.append(col_def)