mtsql 1.11.14__py3-none-any.whl → 1.11.16__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.
mt/sql/redshift/ddl.py ADDED
@@ -0,0 +1,264 @@
1
+ import sqlalchemy as sa
2
+ from sqlalchemy.ext import compiler as sa_compiler
3
+ from sqlalchemy.schema import DDLElement
4
+
5
+
6
+ def _check_if_key_exists(key):
7
+ return isinstance(key, sa.Column) or key
8
+
9
+
10
+ def get_table_attributes(preparer,
11
+ diststyle=None,
12
+ distkey=None,
13
+ sortkey=None,
14
+ interleaved_sortkey=None):
15
+ """
16
+ Parse the table attributes into an acceptable string for Redshift,
17
+ checking for valid combinations of distribution options.
18
+
19
+ Parameters
20
+ ----------
21
+ preparer: RedshiftIdentifierPreparer, required
22
+ The preparer associated with the compiler, usually accessed through
23
+ compiler.preparer. You can use a RedshiftDDLCompiler instance to
24
+ access it.
25
+ diststyle: str, optional
26
+ The diststle to use for the table attributes. This must be one of:
27
+ ("ALL", "EVEN", "KEY"). If unset, Redshift passes AUTO. If KEY is used
28
+ a distkey argument must be provided. Inversely, if a diststyle other
29
+ than KEY is provided, a distkey argument cannot be provided.
30
+ distkey: str or sqlalchemy.Column, optional
31
+ The distribution key to use the for the table attributes. This can be
32
+ provided without any distsyle specified or with KEY diststyle
33
+ specified.
34
+ sortkey: str or sqlalchemy.Column (or iterable thereof), optional
35
+ The (compound) sort key(s) to use for the table attributes. Mutually
36
+ exclusive option from interleaved_sortkey.
37
+ interleaved_sortkey: str or sqlalchemy.Column (or iterable), optional
38
+ The (interleaved) sort key(s) to use for the table attributes. Mutually
39
+ exclusive option from sortkey.
40
+
41
+ Returns
42
+ -------
43
+ string
44
+ the table_attributes to append to a DDLElement, normally when creating
45
+ a table or materialized view.
46
+
47
+ Raises
48
+ ------
49
+ sqlalchemy.exc.ArgumentError
50
+ when an invalid diststyle is set,
51
+ when incompatable (diststyle, distkey) pairs are used,
52
+ when both sortkey and interleaved_sortkey is specified.
53
+ """
54
+ text = ""
55
+
56
+ has_distkey = _check_if_key_exists(distkey)
57
+ if diststyle:
58
+ diststyle = diststyle.upper()
59
+ if diststyle not in ('EVEN', 'KEY', 'ALL'):
60
+ raise sa.exc.ArgumentError(
61
+ u"diststyle {0} is invalid".format(diststyle)
62
+ )
63
+ if diststyle != 'KEY' and has_distkey:
64
+ raise sa.exc.ArgumentError(
65
+ u"DISTSTYLE EVEN/ALL is not compatible with a DISTKEY."
66
+ )
67
+ if diststyle == 'KEY' and not has_distkey:
68
+ raise sa.exc.ArgumentError(
69
+ u"DISTKEY specification is required for DISTSTYLE KEY"
70
+ )
71
+ text += " DISTSTYLE " + diststyle
72
+
73
+ if has_distkey:
74
+ if isinstance(distkey, sa.Column):
75
+ distkey = distkey.name
76
+ text += " DISTKEY ({0})".format(preparer.quote(distkey))
77
+
78
+ has_sortkey = _check_if_key_exists(sortkey)
79
+ has_interleaved = _check_if_key_exists(interleaved_sortkey)
80
+ if has_sortkey and has_interleaved:
81
+ raise sa.exc.ArgumentError(
82
+ "Parameters sortkey and interleaved_sortkey are "
83
+ "mutually exclusive; you may not specify both."
84
+ )
85
+
86
+ if has_sortkey or has_interleaved:
87
+ keys = sortkey if has_sortkey else interleaved_sortkey
88
+ if isinstance(keys, (str, sa.Column)):
89
+ keys = [keys]
90
+ keys = [key.name if isinstance(key, sa.Column) else key
91
+ for key in keys]
92
+ if has_interleaved:
93
+ text += " INTERLEAVED"
94
+ sortkey_string = ", ".join(preparer.quote(key)
95
+ for key in keys)
96
+ text += " SORTKEY ({0})".format(sortkey_string)
97
+ return text
98
+
99
+
100
+ class CreateMaterializedView(DDLElement):
101
+ """
102
+ DDLElement to create a materialized view in Redshift where the distribution
103
+ options can be set.
104
+ SEE:
105
+ docs.aws.amazon.com/redshift/latest/dg/materialized-view-create-sql-command
106
+
107
+ This works for any selectable. Consider the trivial example of this table:
108
+
109
+ >>> import sqlalchemy as sa
110
+ >>> from sqlalchemy_redshift.dialect import CreateMaterializedView
111
+ >>> engine = sa.create_engine('redshift+psycopg2://example')
112
+ >>> metadata = sa.MetaData()
113
+ >>> user = sa.Table(
114
+ ... 'user',
115
+ ... metadata,
116
+ ... sa.Column('id', sa.Integer, primary_key=True),
117
+ ... sa.Column('name', sa.String)
118
+ ... )
119
+ >>> selectable = sa.select([user.c.id, user.c.name], from_obj=user)
120
+ >>> view = CreateMaterializedView(
121
+ ... 'materialized_view_of_users',
122
+ ... selectable,
123
+ ... distkey='id',
124
+ ... sortkey='name'
125
+ ... )
126
+ >>> print(view.compile(engine))
127
+ <BLANKLINE>
128
+ CREATE MATERIALIZED VIEW materialized_view_of_users
129
+ DISTKEY (id) SORTKEY (name)
130
+ AS SELECT "user".id, "user".name
131
+ FROM "user"
132
+ <BLANKLINE>
133
+ <BLANKLINE>
134
+
135
+ The materialized view can take full advantage of Redshift's distributed
136
+ architecture via distribution styles and sort keys.
137
+
138
+ The CreateMaterializedView is a DDLElement, so it can be executed via any
139
+ execute() command, be it from an Engine, Session, or Connection.
140
+ """
141
+ def __init__(self, name, selectable, backup=True, diststyle=None,
142
+ distkey=None, sortkey=None, interleaved_sortkey=None):
143
+ """
144
+ Parameters
145
+ ----------
146
+ name: str, required
147
+ the name of the materialized view to be created.
148
+ selectable: str, required
149
+ the sqlalchemy selectable to be the base query for the view.
150
+ diststyle: str, optional
151
+ The diststle to use for the table attributes. This must be one of:
152
+ ("ALL", "EVEN", "KEY"). If unset, Redshift passes AUTO. If KEY is
153
+ used, a distkey argument must be provided. Inversely, if
154
+ a diststyle other than KEY is provided, a distkey argument cannot
155
+ be provided.
156
+ distkey: str or sqlalchemy.Column, optional
157
+ The distribution key to use the for the table attributes. This can
158
+ be provided without any distsyle specified or with KEY diststyle
159
+ specified.
160
+ sortkey: str or sqlalchemy.Column (or iterable thereof), optional
161
+ The (compound) sort key(s) to use for the table attributes.
162
+ Mutually exclusive option from interleaved_sortkey.
163
+ interleaved_sortkey: str or sqlalchemy.Column (or iterable), optional
164
+ The (interleaved) sort key(s) to use for the table attributes.
165
+ Mutually exclusive option from sortkey.
166
+ """
167
+ self.name = name
168
+ self.selectable = selectable
169
+ self.backup = backup
170
+ self.diststyle = diststyle
171
+ self.distkey = distkey
172
+ self.sortkey = sortkey
173
+ self.interleaved_sortkey = interleaved_sortkey
174
+
175
+
176
+ @sa_compiler.compiles(CreateMaterializedView)
177
+ def compile_create_materialized_view(element, compiler, **kw):
178
+ """
179
+ Returns the sql query that creates the materialized view
180
+ """
181
+
182
+ text = """\
183
+ CREATE MATERIALIZED VIEW {name}
184
+ {backup}
185
+ {table_attributes}
186
+ AS {selectable}\
187
+ """
188
+ table_attributes = get_table_attributes(
189
+ compiler.preparer,
190
+ diststyle=element.diststyle,
191
+ distkey=element.distkey,
192
+ sortkey=element.sortkey,
193
+ interleaved_sortkey=element.interleaved_sortkey
194
+ )
195
+ # Defaults to yes, so omit default cas3
196
+ backup = "" if element.backup else "BACKUP NO "
197
+ selectable = compiler.sql_compiler.process(element.selectable,
198
+ literal_binds=True)
199
+ text = text.format(
200
+ name=element.name,
201
+ backup=backup,
202
+ table_attributes=table_attributes,
203
+ selectable=selectable
204
+ )
205
+ # Clean it up to have no leading spaces
206
+ text = "\n".join([line.strip() for line in text.split("\n")
207
+ if line.strip()])
208
+ return text
209
+
210
+
211
+ class DropMaterializedView(DDLElement):
212
+ """
213
+ Drop the materialized view from the database.
214
+ SEE:
215
+ docs.aws.amazon.com/redshift/latest/dg/materialized-view-drop-sql-command
216
+
217
+ This undoes the create command, as expected:
218
+
219
+ >>> import sqlalchemy as sa
220
+ >>> from sqlalchemy_redshift.dialect import DropMaterializedView
221
+ >>> engine = sa.create_engine('redshift+psycopg2://example')
222
+ >>> drop = DropMaterializedView(
223
+ ... 'materialized_view_of_users',
224
+ ... if_exists=True
225
+ ... )
226
+ >>> print(drop.compile(engine))
227
+ <BLANKLINE>
228
+ DROP MATERIALIZED VIEW IF EXISTS materialized_view_of_users
229
+ <BLANKLINE>
230
+ <BLANKLINE>
231
+
232
+ This can be included in any execute() statement.
233
+ """
234
+ def __init__(self, name, if_exists=False, cascade=False):
235
+ """
236
+ Build the DropMaterializedView DDLElement.
237
+
238
+ Parameters
239
+ ----------
240
+ name: str
241
+ name of the materialized view to drop
242
+ if_exists: bool, optional
243
+ if True, the IF EXISTS clause is added. This will make the query
244
+ successful even if the view does not exist, i.e. it lets you drop
245
+ a non-existant view. Defaults to False.
246
+ cascade: bool, optional
247
+ if True, the CASCADE clause is added. This will drop all
248
+ views/objects in the DB that depend on this materialized view.
249
+ Defaults to False.
250
+ """
251
+ self.name = name
252
+ self.if_exists = if_exists
253
+ self.cascade = cascade
254
+
255
+
256
+ @sa_compiler.compiles(DropMaterializedView)
257
+ def compile_drop_materialized_view(element, compiler, **kw):
258
+ """
259
+ Formats and returns the drop statement for materialized views.
260
+ """
261
+ text = "DROP MATERIALIZED VIEW {if_exists}{name}{cascade}"
262
+ if_exists = "IF EXISTS " if element.if_exists else ""
263
+ cascade = " CASCADE" if element.cascade else ""
264
+ return text.format(if_exists=if_exists, name=element.name, cascade=cascade)