sqlalchemy-cratedb 0.38.0__py3-none-any.whl → 0.39.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.
- sqlalchemy_cratedb/__init__.py +7 -4
- sqlalchemy_cratedb/compat/api13.py +5 -9
- sqlalchemy_cratedb/compat/core10.py +26 -37
- sqlalchemy_cratedb/compat/core14.py +30 -52
- sqlalchemy_cratedb/compat/core20.py +35 -58
- sqlalchemy_cratedb/compiler.py +72 -83
- sqlalchemy_cratedb/dialect.py +62 -53
- sqlalchemy_cratedb/predicate.py +14 -17
- sqlalchemy_cratedb/sa_version.py +2 -2
- sqlalchemy_cratedb/support/__init__.py +7 -3
- sqlalchemy_cratedb/support/pandas.py +4 -5
- sqlalchemy_cratedb/support/polyfill.py +9 -4
- sqlalchemy_cratedb/support/util.py +42 -1
- sqlalchemy_cratedb/type/__init__.py +9 -0
- sqlalchemy_cratedb/type/array.py +5 -8
- sqlalchemy_cratedb/type/geo.py +5 -10
- sqlalchemy_cratedb/type/object.py +13 -11
- sqlalchemy_cratedb/type/vector.py +6 -3
- {sqlalchemy_cratedb-0.38.0.dist-info → sqlalchemy_cratedb-0.39.0.dist-info}/METADATA +8 -9
- sqlalchemy_cratedb-0.39.0.dist-info/RECORD +26 -0
- {sqlalchemy_cratedb-0.38.0.dist-info → sqlalchemy_cratedb-0.39.0.dist-info}/WHEEL +1 -1
- sqlalchemy_cratedb-0.38.0.dist-info/RECORD +0 -26
- {sqlalchemy_cratedb-0.38.0.dist-info → sqlalchemy_cratedb-0.39.0.dist-info}/LICENSE +0 -0
- {sqlalchemy_cratedb-0.38.0.dist-info → sqlalchemy_cratedb-0.39.0.dist-info}/NOTICE +0 -0
- {sqlalchemy_cratedb-0.38.0.dist-info → sqlalchemy_cratedb-0.39.0.dist-info}/entry_points.txt +0 -0
- {sqlalchemy_cratedb-0.38.0.dist-info → sqlalchemy_cratedb-0.39.0.dist-info}/top_level.txt +0 -0
sqlalchemy_cratedb/compiler.py
CHANGED
@@ -24,17 +24,18 @@ import warnings
|
|
24
24
|
from collections import defaultdict
|
25
25
|
|
26
26
|
import sqlalchemy as sa
|
27
|
-
from sqlalchemy.dialects.postgresql.base import PGCompiler
|
28
27
|
from sqlalchemy.dialects.postgresql.base import RESERVED_WORDS as POSTGRESQL_RESERVED_WORDS
|
28
|
+
from sqlalchemy.dialects.postgresql.base import PGCompiler
|
29
29
|
from sqlalchemy.sql import compiler
|
30
30
|
from sqlalchemy.types import String
|
31
|
+
|
32
|
+
from .sa_version import SA_1_4, SA_VERSION
|
31
33
|
from .type.geo import Geopoint, Geoshape
|
32
34
|
from .type.object import MutableDict, ObjectTypeImpl
|
33
|
-
from .sa_version import SA_VERSION, SA_1_4
|
34
35
|
|
35
36
|
|
36
37
|
def rewrite_update(clauseelement, multiparams, params):
|
37
|
-
"""
|
38
|
+
"""change the params to enable partial updates
|
38
39
|
|
39
40
|
sqlalchemy by default only supports updates of complex types in the form of
|
40
41
|
|
@@ -55,9 +56,8 @@ def rewrite_update(clauseelement, multiparams, params):
|
|
55
56
|
for _params in _multiparams:
|
56
57
|
newparams = {}
|
57
58
|
for key, val in _params.items():
|
58
|
-
if (
|
59
|
-
not
|
60
|
-
(not any(val._changed_keys) and not any(val._deleted_keys))
|
59
|
+
if not isinstance(val, MutableDict) or (
|
60
|
+
not any(val._changed_keys) and not any(val._deleted_keys)
|
61
61
|
):
|
62
62
|
newparams[key] = val
|
63
63
|
continue
|
@@ -68,7 +68,7 @@ def rewrite_update(clauseelement, multiparams, params):
|
|
68
68
|
for subkey in val._deleted_keys:
|
69
69
|
newparams["{0}['{1}']".format(key, subkey)] = None
|
70
70
|
newmultiparams.append(newparams)
|
71
|
-
_multiparams = (newmultiparams,
|
71
|
+
_multiparams = (newmultiparams,)
|
72
72
|
clause = clauseelement.values(newmultiparams[0])
|
73
73
|
clause._crate_specific = True
|
74
74
|
return clause, _multiparams, params
|
@@ -76,7 +76,7 @@ def rewrite_update(clauseelement, multiparams, params):
|
|
76
76
|
|
77
77
|
@sa.event.listens_for(sa.engine.Engine, "before_execute", retval=True)
|
78
78
|
def crate_before_execute(conn, clauseelement, multiparams, params, *args, **kwargs):
|
79
|
-
is_crate = type(conn.dialect).__name__ ==
|
79
|
+
is_crate = type(conn.dialect).__name__ == "CrateDialect"
|
80
80
|
if is_crate and isinstance(clauseelement, sa.sql.expression.Update):
|
81
81
|
if SA_VERSION >= SA_1_4:
|
82
82
|
if params is None:
|
@@ -98,19 +98,19 @@ def crate_before_execute(conn, clauseelement, multiparams, params, *args, **kwar
|
|
98
98
|
|
99
99
|
|
100
100
|
class CrateDDLCompiler(compiler.DDLCompiler):
|
101
|
-
|
102
|
-
__special_opts_tmpl = {
|
103
|
-
'partitioned_by': ' PARTITIONED BY ({0})'
|
104
|
-
}
|
101
|
+
__special_opts_tmpl = {"partitioned_by": " PARTITIONED BY ({0})"}
|
105
102
|
__clustered_opts_tmpl = {
|
106
|
-
|
107
|
-
|
103
|
+
"number_of_shards": " INTO {0} SHARDS",
|
104
|
+
"clustered_by": " BY ({0})",
|
108
105
|
}
|
109
|
-
__clustered_opt_tmpl =
|
106
|
+
__clustered_opt_tmpl = " CLUSTERED{clustered_by}{number_of_shards}"
|
110
107
|
|
111
108
|
def get_column_specification(self, column, **kwargs):
|
112
|
-
colspec =
|
113
|
-
self.
|
109
|
+
colspec = (
|
110
|
+
self.preparer.format_column(column)
|
111
|
+
+ " "
|
112
|
+
+ self.dialect.type_compiler.process(column.type)
|
113
|
+
)
|
114
114
|
|
115
115
|
default = self.get_column_default_string(column)
|
116
116
|
if default is not None:
|
@@ -122,11 +122,9 @@ class CrateDDLCompiler(compiler.DDLCompiler):
|
|
122
122
|
if column.nullable is False:
|
123
123
|
colspec += " NOT NULL"
|
124
124
|
elif column.nullable and column.primary_key:
|
125
|
-
raise sa.exc.CompileError(
|
126
|
-
"Primary key columns cannot be nullable"
|
127
|
-
)
|
125
|
+
raise sa.exc.CompileError("Primary key columns cannot be nullable")
|
128
126
|
|
129
|
-
if column.dialect_options[
|
127
|
+
if column.dialect_options["crate"].get("index") is False:
|
130
128
|
if isinstance(column.type, (Geopoint, Geoshape, ObjectTypeImpl)):
|
131
129
|
raise sa.exc.CompileError(
|
132
130
|
"Disabling indexing is not supported for column "
|
@@ -135,8 +133,8 @@ class CrateDDLCompiler(compiler.DDLCompiler):
|
|
135
133
|
|
136
134
|
colspec += " INDEX OFF"
|
137
135
|
|
138
|
-
if column.dialect_options[
|
139
|
-
if not isinstance(column.type, (String,
|
136
|
+
if column.dialect_options["crate"].get("columnstore") is False:
|
137
|
+
if not isinstance(column.type, (String,)):
|
140
138
|
raise sa.exc.CompileError(
|
141
139
|
"Controlling the columnstore is only allowed for STRING columns"
|
142
140
|
)
|
@@ -148,8 +146,7 @@ class CrateDDLCompiler(compiler.DDLCompiler):
|
|
148
146
|
def visit_computed_column(self, generated):
|
149
147
|
if generated.persisted is False:
|
150
148
|
raise sa.exc.CompileError(
|
151
|
-
"Virtual computed columns are not supported, set "
|
152
|
-
"'persisted' to None or True"
|
149
|
+
"Virtual computed columns are not supported, set " "'persisted' to None or True"
|
153
150
|
)
|
154
151
|
|
155
152
|
return "GENERATED ALWAYS AS (%s)" % self.sql_compiler.process(
|
@@ -157,14 +154,14 @@ class CrateDDLCompiler(compiler.DDLCompiler):
|
|
157
154
|
)
|
158
155
|
|
159
156
|
def post_create_table(self, table):
|
160
|
-
special_options =
|
157
|
+
special_options = ""
|
161
158
|
clustered_options = defaultdict(str)
|
162
159
|
table_opts = []
|
163
160
|
|
164
161
|
opts = dict(
|
165
|
-
(k[len(self.dialect.name) + 1:], v)
|
166
|
-
for k, v
|
167
|
-
if k.startswith(
|
162
|
+
(k[len(self.dialect.name) + 1 :], v)
|
163
|
+
for k, v in table.kwargs.items()
|
164
|
+
if k.startswith("%s_" % self.dialect.name)
|
168
165
|
)
|
169
166
|
for k, v in opts.items():
|
170
167
|
if k in self.__special_opts_tmpl:
|
@@ -172,69 +169,73 @@ class CrateDDLCompiler(compiler.DDLCompiler):
|
|
172
169
|
elif k in self.__clustered_opts_tmpl:
|
173
170
|
clustered_options[k] = self.__clustered_opts_tmpl[k].format(v)
|
174
171
|
else:
|
175
|
-
table_opts.append(
|
172
|
+
table_opts.append("{0} = {1}".format(k, v))
|
176
173
|
if clustered_options:
|
177
174
|
special_options += string.Formatter().vformat(
|
178
|
-
self.__clustered_opt_tmpl, (), clustered_options
|
175
|
+
self.__clustered_opt_tmpl, (), clustered_options
|
176
|
+
)
|
179
177
|
if table_opts:
|
180
|
-
return special_options +
|
181
|
-
', '.join(sorted(table_opts)))
|
178
|
+
return special_options + " WITH ({0})".format(", ".join(sorted(table_opts)))
|
182
179
|
return special_options
|
183
180
|
|
184
181
|
def visit_foreign_key_constraint(self, constraint, **kw):
|
185
182
|
"""
|
186
183
|
CrateDB does not support foreign key constraints.
|
187
184
|
"""
|
188
|
-
warnings.warn(
|
189
|
-
|
190
|
-
|
185
|
+
warnings.warn(
|
186
|
+
"CrateDB does not support foreign key constraints, "
|
187
|
+
"they will be omitted when generating DDL statements.",
|
188
|
+
stacklevel=2,
|
189
|
+
)
|
190
|
+
return
|
191
191
|
|
192
192
|
def visit_unique_constraint(self, constraint, **kw):
|
193
193
|
"""
|
194
194
|
CrateDB does not support unique key constraints.
|
195
195
|
"""
|
196
|
-
warnings.warn(
|
197
|
-
|
198
|
-
|
196
|
+
warnings.warn(
|
197
|
+
"CrateDB does not support unique constraints, "
|
198
|
+
"they will be omitted when generating DDL statements.",
|
199
|
+
stacklevel=2,
|
200
|
+
)
|
201
|
+
return
|
199
202
|
|
200
203
|
|
201
204
|
class CrateTypeCompiler(compiler.GenericTypeCompiler):
|
202
|
-
|
203
205
|
def visit_string(self, type_, **kw):
|
204
|
-
return
|
206
|
+
return "STRING"
|
205
207
|
|
206
208
|
def visit_unicode(self, type_, **kw):
|
207
|
-
return
|
209
|
+
return "STRING"
|
208
210
|
|
209
211
|
def visit_TEXT(self, type_, **kw):
|
210
|
-
return
|
212
|
+
return "STRING"
|
211
213
|
|
212
214
|
def visit_DECIMAL(self, type_, **kw):
|
213
|
-
return
|
215
|
+
return "DOUBLE"
|
214
216
|
|
215
217
|
def visit_BIGINT(self, type_, **kw):
|
216
|
-
return
|
218
|
+
return "LONG"
|
217
219
|
|
218
220
|
def visit_NUMERIC(self, type_, **kw):
|
219
|
-
return
|
221
|
+
return "LONG"
|
220
222
|
|
221
223
|
def visit_INTEGER(self, type_, **kw):
|
222
|
-
return
|
224
|
+
return "INT"
|
223
225
|
|
224
226
|
def visit_SMALLINT(self, type_, **kw):
|
225
|
-
return
|
227
|
+
return "SHORT"
|
226
228
|
|
227
229
|
def visit_datetime(self, type_, **kw):
|
228
230
|
return self.visit_TIMESTAMP(type_, **kw)
|
229
231
|
|
230
232
|
def visit_date(self, type_, **kw):
|
231
|
-
return
|
233
|
+
return "TIMESTAMP"
|
232
234
|
|
233
235
|
def visit_ARRAY(self, type_, **kw):
|
234
236
|
if type_.dimensions is not None and type_.dimensions > 1:
|
235
|
-
raise NotImplementedError(
|
236
|
-
|
237
|
-
return 'ARRAY({0})'.format(self.process(type_.item_type))
|
237
|
+
raise NotImplementedError("CrateDB doesn't support multidimensional arrays")
|
238
|
+
return "ARRAY({0})".format(self.process(type_.item_type))
|
238
239
|
|
239
240
|
def visit_OBJECT(self, type_, **kw):
|
240
241
|
return "OBJECT"
|
@@ -251,32 +252,21 @@ class CrateTypeCompiler(compiler.GenericTypeCompiler):
|
|
251
252
|
|
252
253
|
From `sqlalchemy.dialects.postgresql.base.PGTypeCompiler`.
|
253
254
|
"""
|
254
|
-
return "TIMESTAMP %s" % (
|
255
|
-
(type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE",
|
256
|
-
)
|
255
|
+
return "TIMESTAMP %s" % ((type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE",)
|
257
256
|
|
258
257
|
|
259
258
|
class CrateCompiler(compiler.SQLCompiler):
|
260
|
-
|
261
259
|
def visit_getitem_binary(self, binary, operator, **kw):
|
262
|
-
return "{0}['{1}']".format(
|
263
|
-
self.process(binary.left, **kw),
|
264
|
-
binary.right.value
|
265
|
-
)
|
260
|
+
return "{0}['{1}']".format(self.process(binary.left, **kw), binary.right.value)
|
266
261
|
|
267
|
-
def visit_json_getitem_op_binary(
|
268
|
-
self
|
269
|
-
):
|
270
|
-
return "{0}['{1}']".format(
|
271
|
-
self.process(binary.left, **kw),
|
272
|
-
binary.right.value
|
273
|
-
)
|
262
|
+
def visit_json_getitem_op_binary(self, binary, operator, _cast_applied=False, **kw):
|
263
|
+
return "{0}['{1}']".format(self.process(binary.left, **kw), binary.right.value)
|
274
264
|
|
275
265
|
def visit_any(self, element, **kw):
|
276
266
|
return "%s%sANY (%s)" % (
|
277
267
|
self.process(element.left, **kw),
|
278
268
|
compiler.OPERATORS[element.operator],
|
279
|
-
self.process(element.right, **kw)
|
269
|
+
self.process(element.right, **kw),
|
280
270
|
)
|
281
271
|
|
282
272
|
def visit_ilike_case_insensitive_operand(self, element, **kw):
|
@@ -331,29 +321,32 @@ class CrateCompiler(compiler.SQLCompiler):
|
|
331
321
|
def for_update_clause(self, select, **kw):
|
332
322
|
# CrateDB does not support the `INSERT ... FOR UPDATE` clause.
|
333
323
|
# See https://github.com/crate/crate-python/issues/577.
|
334
|
-
warnings.warn(
|
335
|
-
|
336
|
-
|
324
|
+
warnings.warn(
|
325
|
+
"CrateDB does not support the 'INSERT ... FOR UPDATE' clause, "
|
326
|
+
"it will be omitted when generating SQL statements.",
|
327
|
+
stacklevel=2,
|
328
|
+
)
|
329
|
+
return ""
|
337
330
|
|
338
331
|
|
339
|
-
CRATEDB_RESERVED_WORDS =
|
340
|
-
"add, alter, between, by, called, costs, delete, deny, directory, drop, escape, exists, "
|
341
|
-
"extract, first, function, if, index, input, insert, last, match, nulls, object, "
|
342
|
-
"persistent, recursive, reset, returns, revoke, set, stratify, transient, try_cast, "
|
332
|
+
CRATEDB_RESERVED_WORDS = (
|
333
|
+
"add, alter, between, by, called, costs, delete, deny, directory, drop, escape, exists, "
|
334
|
+
"extract, first, function, if, index, input, insert, last, match, nulls, object, "
|
335
|
+
"persistent, recursive, reset, returns, revoke, set, stratify, transient, try_cast, "
|
343
336
|
"unbounded, update".split(", ")
|
337
|
+
)
|
344
338
|
|
345
339
|
|
346
340
|
class CrateIdentifierPreparer(sa.sql.compiler.IdentifierPreparer):
|
347
341
|
"""
|
348
342
|
Define CrateDB's reserved words to be quoted properly.
|
349
343
|
"""
|
344
|
+
|
350
345
|
reserved_words = set(list(POSTGRESQL_RESERVED_WORDS) + CRATEDB_RESERVED_WORDS)
|
351
346
|
|
352
347
|
def _unquote_identifier(self, value):
|
353
348
|
if value[0] == self.initial_quote:
|
354
|
-
value = value[1:-1].replace(
|
355
|
-
self.escape_to_quote, self.escape_quote
|
356
|
-
)
|
349
|
+
value = value[1:-1].replace(self.escape_to_quote, self.escape_quote)
|
357
350
|
return value
|
358
351
|
|
359
352
|
def format_type(self, type_, use_schema=True):
|
@@ -363,10 +356,6 @@ class CrateIdentifierPreparer(sa.sql.compiler.IdentifierPreparer):
|
|
363
356
|
name = self.quote(type_.name)
|
364
357
|
effective_schema = self.schema_for_object(type_)
|
365
358
|
|
366
|
-
if
|
367
|
-
not self.omit_schema
|
368
|
-
and use_schema
|
369
|
-
and effective_schema is not None
|
370
|
-
):
|
359
|
+
if not self.omit_schema and use_schema and effective_schema is not None:
|
371
360
|
name = self.quote_schema(effective_schema) + "." + name
|
372
361
|
return name
|
sqlalchemy_cratedb/dialect.py
CHANGED
@@ -20,7 +20,7 @@
|
|
20
20
|
# software solely pursuant to the terms of the relevant commercial agreement.
|
21
21
|
|
22
22
|
import logging
|
23
|
-
from datetime import
|
23
|
+
from datetime import date, datetime
|
24
24
|
|
25
25
|
from sqlalchemy import types as sqltypes
|
26
26
|
from sqlalchemy.engine import default, reflection
|
@@ -28,11 +28,11 @@ from sqlalchemy.sql import functions
|
|
28
28
|
from sqlalchemy.util import asbool, to_list
|
29
29
|
|
30
30
|
from .compiler import (
|
31
|
-
CrateTypeCompiler,
|
32
31
|
CrateDDLCompiler,
|
33
32
|
CrateIdentifierPreparer,
|
33
|
+
CrateTypeCompiler,
|
34
34
|
)
|
35
|
-
from .sa_version import
|
35
|
+
from .sa_version import SA_1_4, SA_2_0, SA_VERSION
|
36
36
|
from .type import FloatVector, ObjectArray, ObjectType
|
37
37
|
|
38
38
|
TYPES_MAP = {
|
@@ -54,9 +54,12 @@ TYPES_MAP = {
|
|
54
54
|
"text": sqltypes.String,
|
55
55
|
"float_vector": FloatVector,
|
56
56
|
}
|
57
|
+
|
58
|
+
# Needed for SQLAlchemy >= 1.1.
|
59
|
+
# TODO: Dissolve.
|
57
60
|
try:
|
58
|
-
# SQLAlchemy >= 1.1
|
59
61
|
from sqlalchemy.types import ARRAY
|
62
|
+
|
60
63
|
TYPES_MAP["integer_array"] = ARRAY(sqltypes.Integer)
|
61
64
|
TYPES_MAP["boolean_array"] = ARRAY(sqltypes.Boolean)
|
62
65
|
TYPES_MAP["short_array"] = ARRAY(sqltypes.SmallInteger)
|
@@ -71,7 +74,7 @@ try:
|
|
71
74
|
TYPES_MAP["real_array"] = ARRAY(sqltypes.Float)
|
72
75
|
TYPES_MAP["string_array"] = ARRAY(sqltypes.String)
|
73
76
|
TYPES_MAP["text_array"] = ARRAY(sqltypes.String)
|
74
|
-
except Exception:
|
77
|
+
except Exception: # noqa: S110
|
75
78
|
pass
|
76
79
|
|
77
80
|
|
@@ -82,14 +85,16 @@ class Date(sqltypes.Date):
|
|
82
85
|
def bind_processor(self, dialect):
|
83
86
|
def process(value):
|
84
87
|
if value is not None:
|
85
|
-
assert isinstance(value, date)
|
86
|
-
return value.strftime(
|
88
|
+
assert isinstance(value, date) # noqa: S101
|
89
|
+
return value.strftime("%Y-%m-%d")
|
90
|
+
return None
|
91
|
+
|
87
92
|
return process
|
88
93
|
|
89
94
|
def result_processor(self, dialect, coltype):
|
90
95
|
def process(value):
|
91
96
|
if not value:
|
92
|
-
return
|
97
|
+
return None
|
93
98
|
try:
|
94
99
|
return datetime.utcfromtimestamp(value / 1e3).date()
|
95
100
|
except TypeError:
|
@@ -103,27 +108,29 @@ class Date(sqltypes.Date):
|
|
103
108
|
# the date will be returned in the format it was inserted.
|
104
109
|
log.warning(
|
105
110
|
"Received timestamp isn't a long value."
|
106
|
-
"Trying to parse as date string and then as datetime string"
|
111
|
+
"Trying to parse as date string and then as datetime string"
|
112
|
+
)
|
107
113
|
try:
|
108
|
-
return datetime.strptime(value,
|
114
|
+
return datetime.strptime(value, "%Y-%m-%d").date()
|
109
115
|
except ValueError:
|
110
|
-
return datetime.strptime(value,
|
116
|
+
return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ").date()
|
117
|
+
|
111
118
|
return process
|
112
119
|
|
113
120
|
|
114
121
|
class DateTime(sqltypes.DateTime):
|
115
|
-
|
116
122
|
def bind_processor(self, dialect):
|
117
123
|
def process(value):
|
118
124
|
if isinstance(value, (datetime, date)):
|
119
|
-
return value.strftime(
|
125
|
+
return value.strftime("%Y-%m-%dT%H:%M:%S.%f%z")
|
120
126
|
return value
|
127
|
+
|
121
128
|
return process
|
122
129
|
|
123
130
|
def result_processor(self, dialect, coltype):
|
124
131
|
def process(value):
|
125
132
|
if not value:
|
126
|
-
return
|
133
|
+
return None
|
127
134
|
try:
|
128
135
|
return datetime.utcfromtimestamp(value / 1e3)
|
129
136
|
except TypeError:
|
@@ -137,11 +144,13 @@ class DateTime(sqltypes.DateTime):
|
|
137
144
|
# the date will be returned in the format it was inserted.
|
138
145
|
log.warning(
|
139
146
|
"Received timestamp isn't a long value."
|
140
|
-
"Trying to parse as datetime string and then as date string"
|
147
|
+
"Trying to parse as datetime string and then as date string"
|
148
|
+
)
|
141
149
|
try:
|
142
|
-
return datetime.strptime(value,
|
150
|
+
return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ")
|
143
151
|
except ValueError:
|
144
|
-
return datetime.strptime(value,
|
152
|
+
return datetime.strptime(value, "%Y-%m-%d")
|
153
|
+
|
145
154
|
return process
|
146
155
|
|
147
156
|
|
@@ -154,19 +163,22 @@ colspecs = {
|
|
154
163
|
|
155
164
|
if SA_VERSION >= SA_2_0:
|
156
165
|
from .compat.core20 import CrateCompilerSA20
|
166
|
+
|
157
167
|
statement_compiler = CrateCompilerSA20
|
158
168
|
elif SA_VERSION >= SA_1_4:
|
159
169
|
from .compat.core14 import CrateCompilerSA14
|
170
|
+
|
160
171
|
statement_compiler = CrateCompilerSA14
|
161
172
|
else:
|
162
173
|
from .compat.core10 import CrateCompilerSA10
|
174
|
+
|
163
175
|
statement_compiler = CrateCompilerSA10
|
164
176
|
|
165
177
|
|
166
178
|
class CrateDialect(default.DefaultDialect):
|
167
|
-
name =
|
168
|
-
driver =
|
169
|
-
default_paramstyle =
|
179
|
+
name = "crate"
|
180
|
+
driver = "crate-python"
|
181
|
+
default_paramstyle = "qmark"
|
170
182
|
statement_compiler = statement_compiler
|
171
183
|
ddl_compiler = CrateDDLCompiler
|
172
184
|
type_compiler = CrateTypeCompiler
|
@@ -192,15 +204,13 @@ class CrateDialect(default.DefaultDialect):
|
|
192
204
|
|
193
205
|
# Currently, our SQL parser doesn't support unquoted column names that
|
194
206
|
# start with _. Adding it here causes sqlalchemy to quote such columns.
|
195
|
-
self.identifier_preparer.illegal_initial_characters.add(
|
207
|
+
self.identifier_preparer.illegal_initial_characters.add("_")
|
196
208
|
|
197
209
|
def initialize(self, connection):
|
198
210
|
# get lowest server version
|
199
|
-
self.server_version_info =
|
200
|
-
self._get_server_version_info(connection)
|
211
|
+
self.server_version_info = self._get_server_version_info(connection)
|
201
212
|
# get default schema name
|
202
|
-
self.default_schema_name =
|
203
|
-
self._get_default_schema_name(connection)
|
213
|
+
self.default_schema_name = self._get_default_schema_name(connection)
|
204
214
|
|
205
215
|
def do_rollback(self, connection):
|
206
216
|
# if any exception is raised by the dbapi, sqlalchemy by default
|
@@ -212,9 +222,9 @@ class CrateDialect(default.DefaultDialect):
|
|
212
222
|
def connect(self, host=None, port=None, *args, **kwargs):
|
213
223
|
server = None
|
214
224
|
if host:
|
215
|
-
server =
|
216
|
-
if
|
217
|
-
server = kwargs.pop(
|
225
|
+
server = "{0}:{1}".format(host, port or "4200")
|
226
|
+
if "servers" in kwargs:
|
227
|
+
server = kwargs.pop("servers")
|
218
228
|
servers = to_list(server)
|
219
229
|
if servers:
|
220
230
|
use_ssl = asbool(kwargs.pop("ssl", False))
|
@@ -224,7 +234,7 @@ class CrateDialect(default.DefaultDialect):
|
|
224
234
|
return self.dbapi.connect(**kwargs)
|
225
235
|
|
226
236
|
def _get_default_schema_name(self, connection):
|
227
|
-
return
|
237
|
+
return "doc"
|
228
238
|
|
229
239
|
def _get_effective_schema_name(self, connection):
|
230
240
|
schema_name_raw = connection.engine.url.query.get("schema")
|
@@ -241,6 +251,7 @@ class CrateDialect(default.DefaultDialect):
|
|
241
251
|
@classmethod
|
242
252
|
def import_dbapi(cls):
|
243
253
|
from crate import client
|
254
|
+
|
244
255
|
return client
|
245
256
|
|
246
257
|
@classmethod
|
@@ -256,9 +267,7 @@ class CrateDialect(default.DefaultDialect):
|
|
256
267
|
@reflection.cache
|
257
268
|
def get_schema_names(self, connection, **kw):
|
258
269
|
cursor = connection.exec_driver_sql(
|
259
|
-
"select schema_name "
|
260
|
-
"from information_schema.schemata "
|
261
|
-
"order by schema_name asc"
|
270
|
+
"select schema_name " "from information_schema.schemata " "order by schema_name asc"
|
262
271
|
)
|
263
272
|
return [row[0] for row in cursor.fetchall()]
|
264
273
|
|
@@ -271,7 +280,7 @@ class CrateDialect(default.DefaultDialect):
|
|
271
280
|
"WHERE {0} = ? "
|
272
281
|
"AND table_type = 'BASE TABLE' "
|
273
282
|
"ORDER BY table_name ASC, {0} ASC".format(self.schema_column),
|
274
|
-
(schema or self.default_schema_name,
|
283
|
+
(schema or self.default_schema_name,),
|
275
284
|
)
|
276
285
|
return [row[0] for row in cursor.fetchall()]
|
277
286
|
|
@@ -280,22 +289,25 @@ class CrateDialect(default.DefaultDialect):
|
|
280
289
|
cursor = connection.exec_driver_sql(
|
281
290
|
"SELECT table_name FROM information_schema.views "
|
282
291
|
"ORDER BY table_name ASC, {0} ASC".format(self.schema_column),
|
283
|
-
(schema or self.default_schema_name,
|
292
|
+
(schema or self.default_schema_name,),
|
284
293
|
)
|
285
294
|
return [row[0] for row in cursor.fetchall()]
|
286
295
|
|
287
296
|
@reflection.cache
|
288
297
|
def get_columns(self, connection, table_name, schema=None, **kw):
|
289
|
-
query =
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
298
|
+
query = (
|
299
|
+
"SELECT column_name, data_type "
|
300
|
+
"FROM information_schema.columns "
|
301
|
+
"WHERE table_name = ? AND {0} = ? "
|
302
|
+
"AND column_name !~ ?".format(self.schema_column)
|
303
|
+
)
|
294
304
|
cursor = connection.exec_driver_sql(
|
295
305
|
query,
|
296
|
-
(
|
297
|
-
|
298
|
-
|
306
|
+
(
|
307
|
+
table_name,
|
308
|
+
schema or self.default_schema_name,
|
309
|
+
r"(.*)\[\'(.*)\'\]",
|
310
|
+
), # regex to filter subscript
|
299
311
|
)
|
300
312
|
return [self._create_column_info(row) for row in cursor.fetchall()]
|
301
313
|
|
@@ -330,17 +342,14 @@ class CrateDialect(default.DefaultDialect):
|
|
330
342
|
rows = result.fetchone()
|
331
343
|
return set(rows[0] if rows else [])
|
332
344
|
|
333
|
-
pk_result = engine.exec_driver_sql(
|
334
|
-
query,
|
335
|
-
(table_name, schema or self.default_schema_name)
|
336
|
-
)
|
345
|
+
pk_result = engine.exec_driver_sql(query, (table_name, schema or self.default_schema_name))
|
337
346
|
pks = result_fun(pk_result)
|
338
|
-
return {
|
339
|
-
'name': 'PRIMARY KEY'}
|
347
|
+
return {"constrained_columns": sorted(pks), "name": "PRIMARY KEY"}
|
340
348
|
|
341
349
|
@reflection.cache
|
342
|
-
def get_foreign_keys(
|
343
|
-
|
350
|
+
def get_foreign_keys(
|
351
|
+
self, connection, table_name, schema=None, postgresql_ignore_search_path=False, **kw
|
352
|
+
):
|
344
353
|
# Crate doesn't support Foreign Keys, so this stays empty
|
345
354
|
return []
|
346
355
|
|
@@ -354,12 +363,12 @@ class CrateDialect(default.DefaultDialect):
|
|
354
363
|
|
355
364
|
def _create_column_info(self, row):
|
356
365
|
return {
|
357
|
-
|
358
|
-
|
366
|
+
"name": row[0],
|
367
|
+
"type": self._resolve_type(row[1]),
|
359
368
|
# In Crate every column is nullable except PK
|
360
369
|
# Primary Key Constraints are not nullable anyway, no matter what
|
361
370
|
# we return here, so it's fine to return always `True`
|
362
|
-
|
371
|
+
"nullable": True,
|
363
372
|
}
|
364
373
|
|
365
374
|
def _resolve_type(self, type_):
|
sqlalchemy_cratedb/predicate.py
CHANGED
@@ -19,8 +19,8 @@
|
|
19
19
|
# with Crate these terms will supersede the license and you may use the
|
20
20
|
# software solely pursuant to the terms of the relevant commercial agreement.
|
21
21
|
|
22
|
-
from sqlalchemy.sql.expression import ColumnElement, literal
|
23
22
|
from sqlalchemy.ext.compiler import compiles
|
23
|
+
from sqlalchemy.sql.expression import ColumnElement, literal
|
24
24
|
|
25
25
|
|
26
26
|
class Match(ColumnElement):
|
@@ -35,9 +35,8 @@ class Match(ColumnElement):
|
|
35
35
|
|
36
36
|
def compile_column(self, compiler):
|
37
37
|
if isinstance(self.column, dict):
|
38
|
-
column =
|
39
|
-
sorted(["{0} {1}".format(compiler.process(k), v)
|
40
|
-
for k, v in self.column.items()])
|
38
|
+
column = ", ".join(
|
39
|
+
sorted(["{0} {1}".format(compiler.process(k), v) for k, v in self.column.items()])
|
41
40
|
)
|
42
41
|
return "({0})".format(column)
|
43
42
|
else:
|
@@ -51,21 +50,22 @@ class Match(ColumnElement):
|
|
51
50
|
using = "using {0}".format(self.match_type)
|
52
51
|
with_clause = self.with_clause()
|
53
52
|
if with_clause:
|
54
|
-
using =
|
53
|
+
using = " ".join([using, with_clause])
|
55
54
|
return using
|
56
55
|
if self.options:
|
57
|
-
raise ValueError(
|
58
|
-
|
59
|
-
|
56
|
+
raise ValueError(
|
57
|
+
"missing match_type. "
|
58
|
+
+ "It's not allowed to specify options "
|
59
|
+
+ "without match_type"
|
60
|
+
)
|
61
|
+
return None
|
60
62
|
|
61
63
|
def with_clause(self):
|
62
64
|
if self.options:
|
63
|
-
options =
|
64
|
-
sorted(["{0}={1}".format(k, v)
|
65
|
-
for k, v in self.options.items()])
|
66
|
-
)
|
65
|
+
options = ", ".join(sorted(["{0}={1}".format(k, v) for k, v in self.options.items()]))
|
67
66
|
|
68
67
|
return "with ({0})".format(options)
|
68
|
+
return None
|
69
69
|
|
70
70
|
|
71
71
|
def match(column, term, match_type=None, options=None):
|
@@ -89,11 +89,8 @@ def match(column, term, match_type=None, options=None):
|
|
89
89
|
|
90
90
|
@compiles(Match)
|
91
91
|
def compile_match(match, compiler, **kwargs):
|
92
|
-
func = "match(%s, %s)" % (
|
93
|
-
match.compile_column(compiler),
|
94
|
-
match.compile_term(compiler)
|
95
|
-
)
|
92
|
+
func = "match(%s, %s)" % (match.compile_column(compiler), match.compile_term(compiler))
|
96
93
|
using = match.compile_using(compiler)
|
97
94
|
if using:
|
98
|
-
func =
|
95
|
+
func = " ".join([func, using])
|
99
96
|
return func
|