sqlalchemy-cubrid 0.7.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.
- sqlalchemy_cubrid/__init__.py +85 -0
- sqlalchemy_cubrid/alembic_impl.py +145 -0
- sqlalchemy_cubrid/base.py +428 -0
- sqlalchemy_cubrid/compiler.py +515 -0
- sqlalchemy_cubrid/dialect.py +700 -0
- sqlalchemy_cubrid/dml.py +318 -0
- sqlalchemy_cubrid/py.typed +0 -0
- sqlalchemy_cubrid/pycubrid_dialect.py +124 -0
- sqlalchemy_cubrid/requirements.py +270 -0
- sqlalchemy_cubrid/trace.py +92 -0
- sqlalchemy_cubrid/types.py +349 -0
- sqlalchemy_cubrid-0.7.1.dist-info/METADATA +281 -0
- sqlalchemy_cubrid-0.7.1.dist-info/RECORD +18 -0
- sqlalchemy_cubrid-0.7.1.dist-info/WHEEL +5 -0
- sqlalchemy_cubrid-0.7.1.dist-info/entry_points.txt +7 -0
- sqlalchemy_cubrid-0.7.1.dist-info/licenses/AUTHORS +2 -0
- sqlalchemy_cubrid-0.7.1.dist-info/licenses/LICENSE +21 -0
- sqlalchemy_cubrid-0.7.1.dist-info/top_level.txt +1 -0
sqlalchemy_cubrid/dml.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# sqlalchemy_cubrid/dml.py
|
|
2
|
+
# Copyright (C) 2021-2026 by sqlalchemy-cubrid authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of sqlalchemy-cubrid and is released under
|
|
6
|
+
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
"""CUBRID DML constructs."""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, Dict, List, Mapping, Optional, Tuple, Union
|
|
13
|
+
|
|
14
|
+
from sqlalchemy import exc, util
|
|
15
|
+
from sqlalchemy.sql._typing import _DMLTableArgument
|
|
16
|
+
from sqlalchemy.sql.base import (
|
|
17
|
+
_exclusive_against,
|
|
18
|
+
_generative,
|
|
19
|
+
ColumnCollection,
|
|
20
|
+
Generative,
|
|
21
|
+
ReadOnlyColumnCollection,
|
|
22
|
+
)
|
|
23
|
+
from sqlalchemy.sql.dml import Insert as StandardInsert
|
|
24
|
+
from sqlalchemy.sql.elements import ClauseElement, KeyedColumnElement
|
|
25
|
+
from sqlalchemy.sql.expression import Executable, alias
|
|
26
|
+
from sqlalchemy.sql.selectable import NamedFromClause
|
|
27
|
+
from sqlalchemy.util.typing import Self
|
|
28
|
+
|
|
29
|
+
__all__ = ("Insert", "Merge", "Replace", "insert", "merge", "replace")
|
|
30
|
+
|
|
31
|
+
_UpdateArg = Union[
|
|
32
|
+
Mapping[str, Any],
|
|
33
|
+
List[Tuple[str, Any]],
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def insert(table: _DMLTableArgument) -> Insert:
|
|
38
|
+
"""Construct a CUBRID-specific variant :class:`Insert` construct.
|
|
39
|
+
|
|
40
|
+
Includes :meth:`Insert.on_duplicate_key_update`.
|
|
41
|
+
"""
|
|
42
|
+
return Insert(table)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Insert(StandardInsert):
|
|
46
|
+
"""CUBRID-specific implementation of INSERT.
|
|
47
|
+
|
|
48
|
+
Adds methods for CUBRID-specific syntaxes such as ON DUPLICATE KEY UPDATE.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
stringify_dialect = "cubrid"
|
|
52
|
+
inherit_cache = True
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def inserted(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
56
|
+
"""Provide the "inserted" namespace for an ON DUPLICATE KEY UPDATE statement.
|
|
57
|
+
|
|
58
|
+
CUBRID uses VALUES() to reference the row being inserted,
|
|
59
|
+
identical to MySQL's pre-8.0 syntax.
|
|
60
|
+
"""
|
|
61
|
+
return self.inserted_alias.columns
|
|
62
|
+
|
|
63
|
+
@util.memoized_property
|
|
64
|
+
def inserted_alias(self) -> NamedFromClause:
|
|
65
|
+
return alias(self.table, name="inserted")
|
|
66
|
+
|
|
67
|
+
@_generative
|
|
68
|
+
@_exclusive_against(
|
|
69
|
+
"_post_values_clause",
|
|
70
|
+
msgs={
|
|
71
|
+
"_post_values_clause": "This Insert construct already "
|
|
72
|
+
"has an ON DUPLICATE KEY clause present"
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
def on_duplicate_key_update(self, *args: _UpdateArg, **kw: Any) -> Self:
|
|
76
|
+
"""Specifies the ON DUPLICATE KEY UPDATE clause.
|
|
77
|
+
|
|
78
|
+
:param **kw: Column keys linked to UPDATE values.
|
|
79
|
+
:param *args: A dictionary or list of 2-tuples as a single positional argument.
|
|
80
|
+
"""
|
|
81
|
+
arg_values = list(args)
|
|
82
|
+
values = kw
|
|
83
|
+
|
|
84
|
+
if arg_values and kw:
|
|
85
|
+
raise exc.ArgumentError("Can't pass kwargs and positional arguments simultaneously")
|
|
86
|
+
if arg_values:
|
|
87
|
+
if len(arg_values) > 1:
|
|
88
|
+
raise exc.ArgumentError(
|
|
89
|
+
"Only a single dictionary or list of tuples is accepted positionally."
|
|
90
|
+
)
|
|
91
|
+
values = next(iter(arg_values), kw)
|
|
92
|
+
|
|
93
|
+
self._post_values_clause = OnDuplicateClause(self.inserted_alias, values)
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def replace(table: _DMLTableArgument) -> Replace:
|
|
98
|
+
"""Construct a CUBRID REPLACE INTO statement.
|
|
99
|
+
|
|
100
|
+
CUBRID's REPLACE works like MySQL's REPLACE - it inserts a new row,
|
|
101
|
+
or if a duplicate key violation occurs, deletes the old row and inserts
|
|
102
|
+
the new one.
|
|
103
|
+
|
|
104
|
+
Usage::
|
|
105
|
+
|
|
106
|
+
from sqlalchemy_cubrid import replace
|
|
107
|
+
|
|
108
|
+
stmt = replace(users).values(id=1, name="updated")
|
|
109
|
+
"""
|
|
110
|
+
return Replace(table)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Replace(StandardInsert):
|
|
114
|
+
"""CUBRID-specific REPLACE INTO statement.
|
|
115
|
+
|
|
116
|
+
Extends StandardInsert to generate REPLACE INTO instead of INSERT INTO.
|
|
117
|
+
Supports all standard INSERT syntax (values, from_select, etc.)
|
|
118
|
+
but does NOT support ON DUPLICATE KEY UPDATE (use Insert for that).
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
stringify_dialect = "cubrid"
|
|
122
|
+
inherit_cache = True
|
|
123
|
+
__visit_name__ = "replace"
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def _effective_plugin_target(self) -> str:
|
|
127
|
+
return "insert"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class OnDuplicateClause(ClauseElement):
|
|
131
|
+
__visit_name__ = "on_duplicate_key_update"
|
|
132
|
+
|
|
133
|
+
_parameter_ordering: Optional[List[str]] = None
|
|
134
|
+
update: Dict[str, Any]
|
|
135
|
+
stringify_dialect = "cubrid"
|
|
136
|
+
|
|
137
|
+
def __init__(self, inserted_alias: NamedFromClause, update: _UpdateArg) -> None:
|
|
138
|
+
self.inserted_alias = inserted_alias
|
|
139
|
+
if isinstance(update, list) and (update and isinstance(update[0], tuple)):
|
|
140
|
+
self._parameter_ordering = [key for key, _ in update]
|
|
141
|
+
update = dict(update)
|
|
142
|
+
if isinstance(update, dict):
|
|
143
|
+
if not update:
|
|
144
|
+
raise ValueError("update parameter dictionary must not be empty")
|
|
145
|
+
elif isinstance(update, ColumnCollection):
|
|
146
|
+
update = dict(update)
|
|
147
|
+
else:
|
|
148
|
+
raise ValueError(
|
|
149
|
+
"update parameter must be a non-empty dictionary "
|
|
150
|
+
"or a ColumnCollection such as the `.c.` collection "
|
|
151
|
+
"of a Table object"
|
|
152
|
+
)
|
|
153
|
+
self.update = update
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def merge(target: _DMLTableArgument) -> Merge:
|
|
157
|
+
"""Construct a CUBRID-specific MERGE statement.
|
|
158
|
+
|
|
159
|
+
CUBRID's MERGE matches rows from a source against a target table
|
|
160
|
+
using an ON condition, then executes UPDATE for matched rows and/or
|
|
161
|
+
INSERT for unmatched rows.
|
|
162
|
+
|
|
163
|
+
Usage::
|
|
164
|
+
|
|
165
|
+
from sqlalchemy_cubrid import merge
|
|
166
|
+
|
|
167
|
+
stmt = (
|
|
168
|
+
merge(target_table)
|
|
169
|
+
.using(source_table)
|
|
170
|
+
.on(target_table.c.id == source_table.c.id)
|
|
171
|
+
.when_matched_then_update({"name": source_table.c.name})
|
|
172
|
+
.when_not_matched_then_insert({"id": source_table.c.id, "name": source_table.c.name})
|
|
173
|
+
)
|
|
174
|
+
"""
|
|
175
|
+
return Merge(target)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Merge(Executable, ClauseElement, Generative):
|
|
179
|
+
__visit_name__ = "merge"
|
|
180
|
+
stringify_dialect = "cubrid"
|
|
181
|
+
|
|
182
|
+
_target: _DMLTableArgument
|
|
183
|
+
_using_source: Optional[Any]
|
|
184
|
+
_on_condition: Optional[ClauseElement]
|
|
185
|
+
_when_matched: Optional[Dict[str, Any]]
|
|
186
|
+
_when_not_matched: Optional[Dict[str, Any]]
|
|
187
|
+
|
|
188
|
+
def __init__(self, target: _DMLTableArgument) -> None:
|
|
189
|
+
self._target = target
|
|
190
|
+
self._using_source = None
|
|
191
|
+
self._on_condition = None
|
|
192
|
+
self._when_matched = None
|
|
193
|
+
self._when_not_matched = None
|
|
194
|
+
|
|
195
|
+
@_generative
|
|
196
|
+
def into(self, target_table: _DMLTableArgument) -> Self:
|
|
197
|
+
self._target = target_table
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
@_generative
|
|
201
|
+
def using(self, source: Any) -> Self:
|
|
202
|
+
self._using_source = source
|
|
203
|
+
return self
|
|
204
|
+
|
|
205
|
+
@_generative
|
|
206
|
+
def on(self, condition: ClauseElement) -> Self:
|
|
207
|
+
self._on_condition = condition
|
|
208
|
+
return self
|
|
209
|
+
|
|
210
|
+
@_generative
|
|
211
|
+
def when_matched_then_update(
|
|
212
|
+
self,
|
|
213
|
+
values_dict: Union[Mapping[Any, Any], List[Tuple[Any, Any]], Tuple[Tuple[Any, Any], ...]],
|
|
214
|
+
where: Optional[ClauseElement] = None,
|
|
215
|
+
delete_where: Optional[ClauseElement] = None,
|
|
216
|
+
) -> Self:
|
|
217
|
+
values = self._normalize_key_value_pairs(values_dict, argument_name="values_dict")
|
|
218
|
+
existing_delete_where = (
|
|
219
|
+
self._when_matched.get("delete_where") if self._when_matched is not None else None
|
|
220
|
+
)
|
|
221
|
+
self._when_matched = {
|
|
222
|
+
"values": dict(values),
|
|
223
|
+
"where": where,
|
|
224
|
+
"delete_where": (delete_where if delete_where is not None else existing_delete_where),
|
|
225
|
+
}
|
|
226
|
+
return self
|
|
227
|
+
|
|
228
|
+
@_generative
|
|
229
|
+
def when_matched_then_delete(self, where: Optional[ClauseElement] = None) -> Self:
|
|
230
|
+
if self._when_matched is None:
|
|
231
|
+
raise ValueError("WHEN MATCHED UPDATE clause must be specified before DELETE WHERE")
|
|
232
|
+
|
|
233
|
+
self._when_matched = {
|
|
234
|
+
"values": dict(self._when_matched["values"]),
|
|
235
|
+
"where": self._when_matched.get("where"),
|
|
236
|
+
"delete_where": where,
|
|
237
|
+
}
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
@_generative
|
|
241
|
+
def when_not_matched_then_insert(
|
|
242
|
+
self,
|
|
243
|
+
values_dict_or_column_list: Union[
|
|
244
|
+
Mapping[Any, Any],
|
|
245
|
+
List[Tuple[Any, Any]],
|
|
246
|
+
Tuple[Tuple[Any, Any], ...],
|
|
247
|
+
List[Any],
|
|
248
|
+
Tuple[Any, ...],
|
|
249
|
+
],
|
|
250
|
+
where: Optional[ClauseElement] = None,
|
|
251
|
+
) -> Self:
|
|
252
|
+
columns: List[Any]
|
|
253
|
+
values: List[Any]
|
|
254
|
+
|
|
255
|
+
if isinstance(values_dict_or_column_list, Mapping):
|
|
256
|
+
pairs = self._normalize_key_value_pairs(
|
|
257
|
+
values_dict_or_column_list,
|
|
258
|
+
argument_name="values_dict_or_column_list",
|
|
259
|
+
)
|
|
260
|
+
columns = [column for column, _ in pairs]
|
|
261
|
+
values = [value for _, value in pairs]
|
|
262
|
+
elif isinstance(values_dict_or_column_list, ColumnCollection):
|
|
263
|
+
pairs = self._normalize_key_value_pairs(
|
|
264
|
+
dict(values_dict_or_column_list),
|
|
265
|
+
argument_name="values_dict_or_column_list",
|
|
266
|
+
)
|
|
267
|
+
columns = [column for column, _ in pairs]
|
|
268
|
+
values = [value for _, value in pairs]
|
|
269
|
+
elif isinstance(values_dict_or_column_list, (list, tuple)):
|
|
270
|
+
if values_dict_or_column_list and isinstance(values_dict_or_column_list[0], tuple):
|
|
271
|
+
pairs = self._normalize_key_value_pairs(
|
|
272
|
+
list(values_dict_or_column_list),
|
|
273
|
+
argument_name="values_dict_or_column_list",
|
|
274
|
+
)
|
|
275
|
+
columns = [column for column, _ in pairs]
|
|
276
|
+
values = [value for _, value in pairs]
|
|
277
|
+
else:
|
|
278
|
+
columns = list(values_dict_or_column_list)
|
|
279
|
+
if not columns:
|
|
280
|
+
raise ValueError(
|
|
281
|
+
"values_dict_or_column_list must be a non-empty dictionary, "
|
|
282
|
+
"a list of tuples, or a non-empty column list"
|
|
283
|
+
)
|
|
284
|
+
values = list(columns)
|
|
285
|
+
else:
|
|
286
|
+
raise ValueError(
|
|
287
|
+
"values_dict_or_column_list must be a non-empty dictionary, "
|
|
288
|
+
"a list of tuples, or a non-empty column list"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
self._when_not_matched = {
|
|
292
|
+
"columns": columns,
|
|
293
|
+
"values": values,
|
|
294
|
+
"where": where,
|
|
295
|
+
}
|
|
296
|
+
return self
|
|
297
|
+
|
|
298
|
+
def _normalize_key_value_pairs(
|
|
299
|
+
self,
|
|
300
|
+
values: Union[Mapping[Any, Any], List[Tuple[Any, Any]], Tuple[Tuple[Any, Any], ...]],
|
|
301
|
+
argument_name: str,
|
|
302
|
+
) -> List[Tuple[Any, Any]]:
|
|
303
|
+
if isinstance(values, Mapping):
|
|
304
|
+
pairs = list(values.items())
|
|
305
|
+
elif isinstance(values, list):
|
|
306
|
+
pairs = list(values)
|
|
307
|
+
elif isinstance(values, tuple):
|
|
308
|
+
pairs = list(values)
|
|
309
|
+
else:
|
|
310
|
+
raise ValueError(f"{argument_name} must be a non-empty dictionary or a list of tuples")
|
|
311
|
+
|
|
312
|
+
if not pairs:
|
|
313
|
+
raise ValueError(f"{argument_name} must be a non-empty dictionary or a list of tuples")
|
|
314
|
+
|
|
315
|
+
if not all(isinstance(pair, tuple) and len(pair) == 2 for pair in pairs):
|
|
316
|
+
raise ValueError(f"{argument_name} must be a non-empty dictionary or a list of tuples")
|
|
317
|
+
|
|
318
|
+
return pairs
|
|
File without changes
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# sqlalchemy_cubrid/pycubrid_dialect.py
|
|
2
|
+
# Copyright (C) 2021-2026 by sqlalchemy-cubrid authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of sqlalchemy-cubrid and is released under
|
|
6
|
+
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
"""CUBRID dialect variant using the pycubrid pure-Python DB-API 2.0 driver."""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
from sqlalchemy_cubrid.base import CubridExecutionContext
|
|
14
|
+
from sqlalchemy_cubrid.dialect import CubridDialect
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PyCubridExecutionContext(CubridExecutionContext):
|
|
18
|
+
"""Execution context for pycubrid connections.
|
|
19
|
+
|
|
20
|
+
pycubrid exposes ``cursor.lastrowid`` as a proper ``int | None``,
|
|
21
|
+
so we use it directly instead of the CUBRIDdb workaround.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def get_lastrowid(self):
|
|
25
|
+
"""Return the last inserted row ID from pycubrid's cursor."""
|
|
26
|
+
try:
|
|
27
|
+
return self.cursor.lastrowid
|
|
28
|
+
except AttributeError:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# Fallback: use SQL function
|
|
32
|
+
cursor = self.create_server_side_cursor()
|
|
33
|
+
try:
|
|
34
|
+
cursor.execute("SELECT LAST_INSERT_ID()")
|
|
35
|
+
row = cursor.fetchone()
|
|
36
|
+
if row:
|
|
37
|
+
return int(row[0])
|
|
38
|
+
finally:
|
|
39
|
+
cursor.close()
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PyCubridDialect(CubridDialect):
|
|
44
|
+
"""SQLAlchemy dialect for CUBRID using the pycubrid pure-Python driver.
|
|
45
|
+
|
|
46
|
+
Connection URL: ``cubrid+pycubrid://user:password@host:port/dbname``
|
|
47
|
+
|
|
48
|
+
This dialect subclasses :class:`CubridDialect` and overrides only
|
|
49
|
+
the driver-specific methods: ``import_dbapi``, ``create_connect_args``,
|
|
50
|
+
``on_connect``, and ``do_ping``. All SQL compilation, type mapping,
|
|
51
|
+
and schema reflection is inherited unchanged.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
driver = "pycubrid"
|
|
55
|
+
supports_statement_cache = True
|
|
56
|
+
execution_ctx_cls = PyCubridExecutionContext
|
|
57
|
+
|
|
58
|
+
# pycubrid uses qmark paramstyle natively
|
|
59
|
+
default_paramstyle = "qmark"
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def import_dbapi(cls):
|
|
63
|
+
"""Import and return the pycubrid DBAPI module."""
|
|
64
|
+
try:
|
|
65
|
+
import pycubrid as dbapi_module
|
|
66
|
+
except ImportError as e:
|
|
67
|
+
raise e
|
|
68
|
+
return dbapi_module
|
|
69
|
+
|
|
70
|
+
# Keep legacy dbapi() for SA 1.x compat
|
|
71
|
+
@classmethod
|
|
72
|
+
def dbapi(cls):
|
|
73
|
+
return cls.import_dbapi()
|
|
74
|
+
|
|
75
|
+
def create_connect_args(self, url):
|
|
76
|
+
"""Build DB-API connection arguments for pycubrid.
|
|
77
|
+
|
|
78
|
+
pycubrid accepts keyword arguments directly::
|
|
79
|
+
|
|
80
|
+
pycubrid.connect(host=..., port=..., database=..., user=..., password=...)
|
|
81
|
+
"""
|
|
82
|
+
if url is None:
|
|
83
|
+
raise ValueError("Unexpected database URL format")
|
|
84
|
+
|
|
85
|
+
opts = url.translate_connect_args(username="user", database="database")
|
|
86
|
+
return (), {
|
|
87
|
+
"host": opts.get("host", "localhost"),
|
|
88
|
+
"port": opts.get("port", 33000),
|
|
89
|
+
"database": opts.get("database", ""),
|
|
90
|
+
"user": opts.get("user", "dba"),
|
|
91
|
+
"password": opts.get("password", ""),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
def on_connect(self):
|
|
95
|
+
"""Return a callable to set up a new pycubrid connection.
|
|
96
|
+
|
|
97
|
+
Disables autocommit so that SQLAlchemy manages transactions.
|
|
98
|
+
"""
|
|
99
|
+
isolation_level = self.isolation_level
|
|
100
|
+
|
|
101
|
+
def connect(conn):
|
|
102
|
+
# pycubrid uses a property setter for autocommit
|
|
103
|
+
conn.autocommit = False
|
|
104
|
+
if isolation_level is not None:
|
|
105
|
+
self.set_isolation_level(conn, isolation_level)
|
|
106
|
+
|
|
107
|
+
return connect
|
|
108
|
+
|
|
109
|
+
def do_ping(self, dbapi_connection):
|
|
110
|
+
"""Ping the server to check connection liveness.
|
|
111
|
+
|
|
112
|
+
pycubrid does not expose a native ``ping()`` method, so we
|
|
113
|
+
execute a lightweight query instead.
|
|
114
|
+
"""
|
|
115
|
+
cursor = dbapi_connection.cursor()
|
|
116
|
+
try:
|
|
117
|
+
cursor.execute("SELECT 1")
|
|
118
|
+
cursor.fetchone()
|
|
119
|
+
finally:
|
|
120
|
+
cursor.close()
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
dialect = PyCubridDialect
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# sqlalchemy_cubrid/requirements.py
|
|
2
|
+
# Copyright (C) 2021-2026 by sqlalchemy-cubrid authors and contributors
|
|
3
|
+
# <see AUTHORS file>
|
|
4
|
+
#
|
|
5
|
+
# This module is part of sqlalchemy-cubrid and is released under
|
|
6
|
+
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
"""CUBRID test-suite requirement flags.
|
|
9
|
+
|
|
10
|
+
These tell SQLAlchemy's built-in test suite which features this dialect
|
|
11
|
+
supports so tests are automatically skipped when the feature is absent.
|
|
12
|
+
|
|
13
|
+
Reference: https://github.com/sqlalchemy/sqlalchemy/blob/main/README.dialects.rst
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from sqlalchemy.testing import exclusions
|
|
19
|
+
from sqlalchemy.testing.requirements import SuiteRequirements
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Requirements(SuiteRequirements):
|
|
23
|
+
"""CUBRID-specific requirement flags for the SA test suite."""
|
|
24
|
+
|
|
25
|
+
# ----- RETURNING -----
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def returning(self):
|
|
29
|
+
"""CUBRID does not support INSERT/UPDATE/DELETE … RETURNING."""
|
|
30
|
+
return exclusions.closed()
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def insert_returning(self):
|
|
34
|
+
return exclusions.closed()
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def update_returning(self):
|
|
38
|
+
return exclusions.closed()
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def delete_returning(self):
|
|
42
|
+
return exclusions.closed()
|
|
43
|
+
|
|
44
|
+
# ----- Booleans -----
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def nullable_booleans(self):
|
|
48
|
+
"""CUBRID maps BOOLEAN to SMALLINT which is nullable."""
|
|
49
|
+
return exclusions.open()
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def non_native_boolean_unconstrained(self):
|
|
53
|
+
"""The SMALLINT emulation has no CHECK constraint."""
|
|
54
|
+
return exclusions.open()
|
|
55
|
+
|
|
56
|
+
# ----- Sequences -----
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def sequences(self):
|
|
60
|
+
"""CUBRID does not support sequences."""
|
|
61
|
+
return exclusions.closed()
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def sequences_optional(self):
|
|
65
|
+
return exclusions.closed()
|
|
66
|
+
|
|
67
|
+
# ----- Schema / DDL -----
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def schemas(self):
|
|
71
|
+
"""CUBRID does not support multiple schemas."""
|
|
72
|
+
return exclusions.closed()
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def temp_table_names(self):
|
|
76
|
+
return exclusions.closed()
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def temporary_tables(self):
|
|
80
|
+
return exclusions.closed()
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def temporary_views(self):
|
|
84
|
+
return exclusions.closed()
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def table_ddl_if_exists(self):
|
|
88
|
+
"""CUBRID supports IF NOT EXISTS / IF EXISTS in DDL."""
|
|
89
|
+
return exclusions.open()
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def comment_reflection(self):
|
|
93
|
+
"""CUBRID supports table and column comments."""
|
|
94
|
+
return exclusions.open()
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def check_constraint_reflection(self):
|
|
98
|
+
return exclusions.closed()
|
|
99
|
+
|
|
100
|
+
# ----- DML -----
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def empty_inserts(self):
|
|
104
|
+
"""CUBRID supports INSERT INTO t DEFAULT VALUES."""
|
|
105
|
+
return exclusions.open()
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def insert_from_select(self):
|
|
109
|
+
return exclusions.open()
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def ctes(self):
|
|
113
|
+
"""CUBRID 11 supports CTEs."""
|
|
114
|
+
return exclusions.open()
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def ctes_on_dml(self):
|
|
118
|
+
return exclusions.closed()
|
|
119
|
+
|
|
120
|
+
# ----- SELECT features -----
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def window_functions(self):
|
|
124
|
+
"""CUBRID supports window functions (ROW_NUMBER, RANK, etc.) with OVER()."""
|
|
125
|
+
return exclusions.open()
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def intersect(self):
|
|
129
|
+
return exclusions.open()
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def except_(self):
|
|
133
|
+
return exclusions.open()
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def fetch_no_order(self):
|
|
137
|
+
return exclusions.open()
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def order_by_col_from_union(self):
|
|
141
|
+
return exclusions.open()
|
|
142
|
+
|
|
143
|
+
# ----- Type support -----
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def unicode_ddl(self):
|
|
147
|
+
return exclusions.open()
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def datetime_literals(self):
|
|
151
|
+
return exclusions.closed()
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def date(self):
|
|
155
|
+
return exclusions.open()
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def time(self):
|
|
159
|
+
return exclusions.open()
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def datetime(self):
|
|
163
|
+
return exclusions.open()
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def timestamp(self):
|
|
167
|
+
return exclusions.open()
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def text_type(self):
|
|
171
|
+
return exclusions.open()
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def json_type(self):
|
|
175
|
+
"""CUBRID does not support JSON type."""
|
|
176
|
+
return exclusions.closed()
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def array_type(self):
|
|
180
|
+
return exclusions.closed()
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def uuid_data_type(self):
|
|
184
|
+
return exclusions.closed()
|
|
185
|
+
|
|
186
|
+
# ----- Misc -----
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def views(self):
|
|
190
|
+
return exclusions.open()
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def savepoints(self):
|
|
194
|
+
return exclusions.open()
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def foreign_keys(self):
|
|
198
|
+
return exclusions.open()
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def self_referential_foreign_keys(self):
|
|
202
|
+
return exclusions.open()
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def unique_constraint_reflection(self):
|
|
206
|
+
return exclusions.open()
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def foreign_key_constraint_reflection(self):
|
|
210
|
+
return exclusions.open()
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def index_reflection(self):
|
|
214
|
+
return exclusions.open()
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def primary_key_constraint_reflection(self):
|
|
218
|
+
return exclusions.open()
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def on_update_cascade(self):
|
|
222
|
+
return exclusions.open()
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def on_delete_cascade(self):
|
|
226
|
+
return exclusions.open()
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def server_side_cursors(self):
|
|
230
|
+
return exclusions.closed()
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def independent_connections(self):
|
|
234
|
+
return exclusions.open()
|
|
235
|
+
|
|
236
|
+
# ----- Binary / LOB -----
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def binary_comparisons(self):
|
|
240
|
+
"""CUBRID BLOB roundtrip has driver-level issues."""
|
|
241
|
+
return exclusions.closed()
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def binary_literals(self):
|
|
245
|
+
"""CUBRID does not support binary literal syntax."""
|
|
246
|
+
return exclusions.closed()
|
|
247
|
+
|
|
248
|
+
# ----- Identifier quoting -----
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def unusual_column_name_characters(self):
|
|
252
|
+
"""CUBRID has limited support for special characters in identifiers."""
|
|
253
|
+
return exclusions.closed()
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def implicitly_named_constraints(self):
|
|
257
|
+
"""CUBRID FK reflection has issues with special-character table names."""
|
|
258
|
+
return exclusions.closed()
|
|
259
|
+
|
|
260
|
+
# ----- SELECT FOR UPDATE -----
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def update_nowait(self):
|
|
264
|
+
"""CUBRID does not support SELECT ... FOR UPDATE NOWAIT."""
|
|
265
|
+
return exclusions.closed()
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def for_update(self):
|
|
269
|
+
"""CUBRID supports SELECT ... FOR UPDATE [OF col1, col2]."""
|
|
270
|
+
return exclusions.open()
|