dataclassdb 0.1.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.
- dataclassdb/__init__.py +19 -0
- dataclassdb/builders/function_builder.py +407 -0
- dataclassdb/builders/list_utils.py +48 -0
- dataclassdb/builders/query_builder.py +158 -0
- dataclassdb/builders/statement_builder.py +635 -0
- dataclassdb/builders/string_builder.py +98 -0
- dataclassdb/constants.py +372 -0
- dataclassdb/dataclass_db.py +282 -0
- dataclassdb/dataclass_field_codec.py +216 -0
- dataclassdb/dataclass_sqlite_field.py +193 -0
- dataclassdb/dataclass_sqlite_table.py +152 -0
- dataclassdb/dataclass_types.py +22 -0
- dataclassdb/db_engine.py +126 -0
- dataclassdb/utils.py +74 -0
- dataclassdb-0.1.0.dist-info/METADATA +237 -0
- dataclassdb-0.1.0.dist-info/RECORD +19 -0
- dataclassdb-0.1.0.dist-info/WHEEL +5 -0
- dataclassdb-0.1.0.dist-info/licenses/LICENSE +21 -0
- dataclassdb-0.1.0.dist-info/top_level.txt +1 -0
dataclassdb/constants.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"SQL",
|
|
3
|
+
"SQL_FUNC",
|
|
4
|
+
"SQL_TYPES",
|
|
5
|
+
"SQL_TYPES_EXTENDED",
|
|
6
|
+
"SQL_FUNC_PRAGMA",
|
|
7
|
+
"SQL_FUNC_PRAGMA_EXTENDED",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
from enum import StrEnum, auto
|
|
11
|
+
from dataclassdb.utils import UpperStrEnum
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SQL(UpperStrEnum):
|
|
15
|
+
ABORT = auto()
|
|
16
|
+
ACTION = auto()
|
|
17
|
+
ADD = auto()
|
|
18
|
+
AFTER = auto()
|
|
19
|
+
ALL = auto()
|
|
20
|
+
ALTER = auto()
|
|
21
|
+
ALWAYS = auto()
|
|
22
|
+
ANALYZE = auto()
|
|
23
|
+
AND = auto()
|
|
24
|
+
AS = auto()
|
|
25
|
+
ASC = auto()
|
|
26
|
+
ATTACH = auto()
|
|
27
|
+
AUTOINCREMENT = auto()
|
|
28
|
+
BEFORE = auto()
|
|
29
|
+
BEGIN = auto()
|
|
30
|
+
BETWEEN = auto()
|
|
31
|
+
BY = auto()
|
|
32
|
+
CASCADE = auto()
|
|
33
|
+
CASE = auto()
|
|
34
|
+
CAST = auto()
|
|
35
|
+
CHECK = auto()
|
|
36
|
+
COLLATE = auto()
|
|
37
|
+
COLUMN = auto()
|
|
38
|
+
COMMIT = auto()
|
|
39
|
+
CONFLICT = auto()
|
|
40
|
+
CONSTRAINT = auto()
|
|
41
|
+
CREATE = auto()
|
|
42
|
+
CROSS = auto()
|
|
43
|
+
CURRENT = auto()
|
|
44
|
+
CURRENT_DATE = auto()
|
|
45
|
+
CURRENT_TIME = auto()
|
|
46
|
+
CURRENT_TIMESTAMP = auto()
|
|
47
|
+
DATABASE = auto()
|
|
48
|
+
DEFAULT = auto()
|
|
49
|
+
DEFERRABLE = auto()
|
|
50
|
+
DEFERRED = auto()
|
|
51
|
+
DELETE = auto()
|
|
52
|
+
DESC = auto()
|
|
53
|
+
DETACH = auto()
|
|
54
|
+
DISTINCT = auto()
|
|
55
|
+
DO = auto()
|
|
56
|
+
DROP = auto()
|
|
57
|
+
EACH = auto()
|
|
58
|
+
ELSE = auto()
|
|
59
|
+
END = auto()
|
|
60
|
+
ESCAPE = auto()
|
|
61
|
+
EXCEPT = auto()
|
|
62
|
+
EXCLUDE = auto()
|
|
63
|
+
EXCLUSIVE = auto()
|
|
64
|
+
EXISTS = auto()
|
|
65
|
+
EXPLAIN = auto()
|
|
66
|
+
FAIL = auto()
|
|
67
|
+
FILTER = auto()
|
|
68
|
+
FIRST = auto()
|
|
69
|
+
FOLLOWING = auto()
|
|
70
|
+
FOR = auto()
|
|
71
|
+
FOREIGN = auto()
|
|
72
|
+
FROM = auto()
|
|
73
|
+
FULL = auto()
|
|
74
|
+
GENERATED = auto()
|
|
75
|
+
GLOB = auto()
|
|
76
|
+
GROUP = auto()
|
|
77
|
+
GROUPS = auto()
|
|
78
|
+
HAVING = auto()
|
|
79
|
+
IF = auto()
|
|
80
|
+
IGNORE = auto()
|
|
81
|
+
IMMEDIATE = auto()
|
|
82
|
+
IN = auto()
|
|
83
|
+
INDEX = auto()
|
|
84
|
+
INDEXED = auto()
|
|
85
|
+
INITIALLY = auto()
|
|
86
|
+
INNER = auto()
|
|
87
|
+
INSERT = auto()
|
|
88
|
+
INSTEAD = auto()
|
|
89
|
+
INTERSECT = auto()
|
|
90
|
+
INTO = auto()
|
|
91
|
+
IS = auto()
|
|
92
|
+
ISNULL = auto()
|
|
93
|
+
JOIN = auto()
|
|
94
|
+
KEY = auto()
|
|
95
|
+
LAST = auto()
|
|
96
|
+
LEFT = auto()
|
|
97
|
+
LIKE = auto()
|
|
98
|
+
LIMIT = auto()
|
|
99
|
+
MATCH = auto()
|
|
100
|
+
MATERIALIZED = auto()
|
|
101
|
+
NATURAL = auto()
|
|
102
|
+
NO = auto()
|
|
103
|
+
NOT = auto()
|
|
104
|
+
NOTHING = auto()
|
|
105
|
+
NOTNULL = auto()
|
|
106
|
+
NULL = auto()
|
|
107
|
+
NULLS = auto()
|
|
108
|
+
OF = auto()
|
|
109
|
+
OFFSET = auto()
|
|
110
|
+
ON = auto()
|
|
111
|
+
OR = auto()
|
|
112
|
+
ORDER = auto()
|
|
113
|
+
OTHERS = auto()
|
|
114
|
+
OUTER = auto()
|
|
115
|
+
OVER = auto()
|
|
116
|
+
PARTITION = auto()
|
|
117
|
+
PLAN = auto()
|
|
118
|
+
PRAGMA = auto()
|
|
119
|
+
PRECEDING = auto()
|
|
120
|
+
PRIMARY = auto()
|
|
121
|
+
PRIMARY_KEY = auto() # Manually adding PRIMARY KEY
|
|
122
|
+
QUERY = auto()
|
|
123
|
+
RAISE = auto()
|
|
124
|
+
RANGE = auto()
|
|
125
|
+
RECURSIVE = auto()
|
|
126
|
+
REFERENCES = auto()
|
|
127
|
+
REGEXP = auto()
|
|
128
|
+
REINDEX = auto()
|
|
129
|
+
RELEASE = auto()
|
|
130
|
+
RENAME = auto()
|
|
131
|
+
REPLACE = auto()
|
|
132
|
+
RESTRICT = auto()
|
|
133
|
+
RETURNING = auto()
|
|
134
|
+
RIGHT = auto()
|
|
135
|
+
ROLLBACK = auto()
|
|
136
|
+
ROW = auto()
|
|
137
|
+
ROWS = auto()
|
|
138
|
+
SAVEPOINT = auto()
|
|
139
|
+
SELECT = auto()
|
|
140
|
+
SET = auto()
|
|
141
|
+
STORED = auto() # https://sqlite.org/gencol.html
|
|
142
|
+
TABLE = auto()
|
|
143
|
+
TEMP = auto()
|
|
144
|
+
TEMPORARY = auto()
|
|
145
|
+
THEN = auto()
|
|
146
|
+
TIES = auto()
|
|
147
|
+
TO = auto()
|
|
148
|
+
TRANSACTION = auto()
|
|
149
|
+
TRIGGER = auto()
|
|
150
|
+
UNBOUNDED = auto()
|
|
151
|
+
UNION = auto()
|
|
152
|
+
UNIQUE = auto()
|
|
153
|
+
UPDATE = auto()
|
|
154
|
+
USING = auto()
|
|
155
|
+
VACUUM = auto()
|
|
156
|
+
VALUES = auto()
|
|
157
|
+
VIEW = auto()
|
|
158
|
+
VIRTUAL = auto()
|
|
159
|
+
WHEN = auto()
|
|
160
|
+
WHERE = auto()
|
|
161
|
+
WINDOW = auto()
|
|
162
|
+
WITH = auto()
|
|
163
|
+
WITHOUT = auto()
|
|
164
|
+
|
|
165
|
+
INTEGER = auto()
|
|
166
|
+
TEXT = auto()
|
|
167
|
+
BLOB = auto()
|
|
168
|
+
REAL = auto()
|
|
169
|
+
NUMERIC = auto()
|
|
170
|
+
ANY = auto() # FOR STRICT TABLE
|
|
171
|
+
|
|
172
|
+
OLD = auto() # Added to support CreateTrigger Queries https://www.sqlite.org/lang_createtrigger.html
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class SQL_FUNC(StrEnum):
|
|
176
|
+
# https://sqlite.org/lang_corefunc.html
|
|
177
|
+
ABS = auto()
|
|
178
|
+
CHANGES = auto()
|
|
179
|
+
CHAR = auto()
|
|
180
|
+
COALESCE = auto()
|
|
181
|
+
CONCAT = auto()
|
|
182
|
+
CONCAT_WS = auto()
|
|
183
|
+
FORMAT = auto()
|
|
184
|
+
GLOB = auto()
|
|
185
|
+
HEX = auto()
|
|
186
|
+
IF = auto()
|
|
187
|
+
IFNULL = auto()
|
|
188
|
+
IIF = auto()
|
|
189
|
+
INSTR = auto()
|
|
190
|
+
LAST_INSERT_ROWID = auto()
|
|
191
|
+
LENGTH = auto()
|
|
192
|
+
LIKE = auto()
|
|
193
|
+
LIKELIHOOD = auto()
|
|
194
|
+
LIKELY = auto()
|
|
195
|
+
LOAD_EXTENSION = auto()
|
|
196
|
+
LOWER = auto()
|
|
197
|
+
LTRIM = auto()
|
|
198
|
+
MAX = auto()
|
|
199
|
+
MIN = auto()
|
|
200
|
+
NULLIF = auto()
|
|
201
|
+
OCTET_LENGTH = auto()
|
|
202
|
+
PRINTF = auto()
|
|
203
|
+
QUOTE = auto()
|
|
204
|
+
RANDOM = auto()
|
|
205
|
+
RANDOMBLOB = auto()
|
|
206
|
+
REPLACE = auto()
|
|
207
|
+
ROUND = auto()
|
|
208
|
+
RTRIM = auto()
|
|
209
|
+
SIGN = auto()
|
|
210
|
+
SOUNDEX = auto()
|
|
211
|
+
SQLITE_COMPILEOPTION_GET = auto()
|
|
212
|
+
SQLITE_COMPILEOPTION_USED = auto()
|
|
213
|
+
SQLITE_OFFSET = auto()
|
|
214
|
+
SQLITE_SOURCE_ID = auto()
|
|
215
|
+
SQLITE_VERSION = auto()
|
|
216
|
+
SUBSTR = auto()
|
|
217
|
+
SUBSTRING = auto()
|
|
218
|
+
TOTAL_CHANGES = auto()
|
|
219
|
+
TRIM = auto()
|
|
220
|
+
TYPEOF = auto()
|
|
221
|
+
UNHEX = auto()
|
|
222
|
+
UNICODE = auto()
|
|
223
|
+
UNISTR = auto()
|
|
224
|
+
UNISTR_QUOTE = auto()
|
|
225
|
+
UNLIKELY = auto()
|
|
226
|
+
UPPER = auto()
|
|
227
|
+
ZEROBLOB = auto()
|
|
228
|
+
|
|
229
|
+
# https://sqlite.org/lang_aggfunc.html
|
|
230
|
+
AVG = auto()
|
|
231
|
+
GROUP_CONCAT = auto()
|
|
232
|
+
MEDIAN = auto()
|
|
233
|
+
PERCENTILE = auto()
|
|
234
|
+
PERCENTILE_CONT = auto()
|
|
235
|
+
PERCENTILE_DISC = auto()
|
|
236
|
+
STRING_AGG = auto()
|
|
237
|
+
SUM = auto()
|
|
238
|
+
TOTAL = auto()
|
|
239
|
+
COUNT = auto()
|
|
240
|
+
|
|
241
|
+
# https://sqlite.org/lang_datefunc.html
|
|
242
|
+
DATE = auto()
|
|
243
|
+
TIME = auto()
|
|
244
|
+
DATETIME = auto()
|
|
245
|
+
JULIANDAY = auto()
|
|
246
|
+
UNIXEPOCH = auto()
|
|
247
|
+
STRFTIME = auto()
|
|
248
|
+
TIMEDIFF = auto()
|
|
249
|
+
|
|
250
|
+
INTEGER = auto()
|
|
251
|
+
TEXT = auto()
|
|
252
|
+
BLOB = auto()
|
|
253
|
+
REAL = auto()
|
|
254
|
+
NUMERIC = auto()
|
|
255
|
+
ANY = auto() # FOR STRICT TABLE
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class SQL_TYPES(StrEnum):
|
|
259
|
+
# https://sqlite.org/datatype3.html
|
|
260
|
+
# https://sqlite.org/stricttables.html
|
|
261
|
+
INTEGER = auto()
|
|
262
|
+
TEXT = auto()
|
|
263
|
+
BLOB = auto()
|
|
264
|
+
REAL = auto()
|
|
265
|
+
NUMERIC = auto()
|
|
266
|
+
ANY = auto() # FOR STRICT TABLE
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class SQL_TYPES_EXTENDED(UpperStrEnum):
|
|
270
|
+
INTEGER = auto()
|
|
271
|
+
TEXT = auto()
|
|
272
|
+
BLOB = auto()
|
|
273
|
+
REAL = auto()
|
|
274
|
+
NUMERIC = auto()
|
|
275
|
+
ANY = auto() # FOR STRICT TABLE
|
|
276
|
+
INT = auto()
|
|
277
|
+
TINYINT = auto()
|
|
278
|
+
SMALLINT = auto()
|
|
279
|
+
MEDIUMINT = auto()
|
|
280
|
+
BIGINT = auto()
|
|
281
|
+
UNSIGNED_BIG_INT = auto()
|
|
282
|
+
INT2 = auto()
|
|
283
|
+
INT8 = auto()
|
|
284
|
+
CHARACTER = auto()
|
|
285
|
+
VARCHAR = auto()
|
|
286
|
+
VARYING_CHARACTER = auto()
|
|
287
|
+
NCHAR = auto()
|
|
288
|
+
NATIVE_CHARACTER = auto()
|
|
289
|
+
NVARCHAR = auto()
|
|
290
|
+
CLOB = auto()
|
|
291
|
+
DOUBLE = auto()
|
|
292
|
+
DOUBLE_PRECISION = auto()
|
|
293
|
+
FLOAT = auto()
|
|
294
|
+
DECIMAL = auto()
|
|
295
|
+
BOOLEAN = auto()
|
|
296
|
+
DATE = auto()
|
|
297
|
+
DATETIME = auto()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class SQL_FUNC_PRAGMA(StrEnum):
|
|
301
|
+
# https://sqlite.org/pragma.html
|
|
302
|
+
ANALYSIS_LIMIT = auto()
|
|
303
|
+
APPLICATION_ID = auto()
|
|
304
|
+
AUTO_VACUUM = auto()
|
|
305
|
+
AUTOMATIC_INDEX = auto()
|
|
306
|
+
BUSY_TIMEOUT = auto()
|
|
307
|
+
CACHE_SIZE = auto()
|
|
308
|
+
CACHE_SPILL = auto()
|
|
309
|
+
CELL_SIZE_CHECK = auto()
|
|
310
|
+
CHECKPOINT_FULLFSYNC = auto()
|
|
311
|
+
COLLATION_LIST = auto()
|
|
312
|
+
COMPILE_OPTIONS = auto()
|
|
313
|
+
DATA_VERSION = auto()
|
|
314
|
+
DATABASE_LIST = auto()
|
|
315
|
+
DEFER_FOREIGN_KEYS = auto()
|
|
316
|
+
ENCODING = auto()
|
|
317
|
+
FOREIGN_KEY_CHECK = auto()
|
|
318
|
+
FOREIGN_KEY_LIST = auto()
|
|
319
|
+
FOREIGN_KEYS = auto()
|
|
320
|
+
FREELIST_COUNT = auto()
|
|
321
|
+
FULLFSYNC = auto()
|
|
322
|
+
FUNCTION_LIST = auto()
|
|
323
|
+
HARD_HEAP_LIMIT = auto()
|
|
324
|
+
IGNORE_CHECK_CONSTRAINTS = auto()
|
|
325
|
+
INCREMENTAL_VACUUM = auto()
|
|
326
|
+
INDEX_INFO = auto()
|
|
327
|
+
INDEX_LIST = auto()
|
|
328
|
+
INDEX_XINFO = auto()
|
|
329
|
+
INTEGRITY_CHECK = auto()
|
|
330
|
+
JOURNAL_MODE = auto()
|
|
331
|
+
JOURNAL_SIZE_LIMIT = auto()
|
|
332
|
+
LEGACY_ALTER_TABLE = auto()
|
|
333
|
+
LEGACY_FILE_FORMAT = auto()
|
|
334
|
+
LOCKING_MODE = auto()
|
|
335
|
+
MAX_PAGE_COUNT = auto()
|
|
336
|
+
MMAP_SIZE = auto()
|
|
337
|
+
MODULE_LIST = auto()
|
|
338
|
+
OPTIMIZE = auto()
|
|
339
|
+
PAGE_COUNT = auto()
|
|
340
|
+
PAGE_SIZE = auto()
|
|
341
|
+
PRAGMA_LIST = auto()
|
|
342
|
+
QUERY_ONLY = auto()
|
|
343
|
+
QUICK_CHECK = auto()
|
|
344
|
+
READ_UNCOMMITTED = auto()
|
|
345
|
+
RECURSIVE_TRIGGERS = auto()
|
|
346
|
+
REVERSE_UNORDERED_SELECTS = auto()
|
|
347
|
+
SECURE_DELETE = auto()
|
|
348
|
+
SHRINK_MEMORY = auto()
|
|
349
|
+
SOFT_HEAP_LIMIT = auto()
|
|
350
|
+
SYNCHRONOUS = auto()
|
|
351
|
+
TABLE_INFO = auto()
|
|
352
|
+
TABLE_LIST = auto()
|
|
353
|
+
TABLE_XINFO = auto()
|
|
354
|
+
TEMP_STORE = auto()
|
|
355
|
+
THREADS = auto()
|
|
356
|
+
TRUSTED_SCHEMA = auto()
|
|
357
|
+
USER_VERSION = auto()
|
|
358
|
+
WAL_AUTOCHECKPOINT = auto()
|
|
359
|
+
WAL_CHECKPOINT = auto()
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class SQL_FUNC_PRAGMA_EXTENDED(StrEnum):
|
|
363
|
+
# https://sqlite.org/pragma.html
|
|
364
|
+
# Non-Standard and testing commands
|
|
365
|
+
PARSER_TRACE = auto()
|
|
366
|
+
SCHEMA_VERSION = auto()
|
|
367
|
+
STATS = auto()
|
|
368
|
+
VDBE_ADDOPTRACE = auto()
|
|
369
|
+
VDBE_DEBUG = auto()
|
|
370
|
+
VDBE_LISTING = auto()
|
|
371
|
+
VDBE_TRACE = auto()
|
|
372
|
+
WRITABLE_SCHEMA = auto()
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sqlite3
|
|
3
|
+
from dataclasses import is_dataclass
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
from dataclassdb.builders.query_builder import QueryBuilder
|
|
7
|
+
from dataclassdb.dataclass_sqlite_table import get_class_codec
|
|
8
|
+
from dataclassdb.constants import SQL
|
|
9
|
+
|
|
10
|
+
# from dataclassdb.types import T
|
|
11
|
+
from dataclassdb.dataclass_types import IsDataclass
|
|
12
|
+
from dataclassdb.utils import table_exists
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
DataclassT = TypeVar("DataclassT", bound=IsDataclass)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DataclassDb(QueryBuilder):
|
|
20
|
+
primary_key_map: dict[Any, list[str]] = {}
|
|
21
|
+
unique_map: dict[Any, list[str]] = {}
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
data_class: DataclassT,
|
|
26
|
+
connection: str | sqlite3.Connection,
|
|
27
|
+
table_name="",
|
|
28
|
+
):
|
|
29
|
+
super().__init__(connection=connection)
|
|
30
|
+
if not is_dataclass(data_class):
|
|
31
|
+
raise TypeError(f"Class {data_class.__name__} is not a dataclass")
|
|
32
|
+
if not connection:
|
|
33
|
+
raise ConnectionError("Connection must be set.")
|
|
34
|
+
self.data_class = data_class
|
|
35
|
+
self.table_name = table_name if table_name else data_class.__name__
|
|
36
|
+
self.insert_query = None
|
|
37
|
+
self.primary_keys = []
|
|
38
|
+
self.unique = []
|
|
39
|
+
self.codec = get_class_codec(data_class)
|
|
40
|
+
self.connection_name = ""
|
|
41
|
+
for name, _field in self.codec.class_fields.items():
|
|
42
|
+
if _field.primary_key:
|
|
43
|
+
self.primary_keys.append(name)
|
|
44
|
+
if _field.unique:
|
|
45
|
+
self.unique.append(name)
|
|
46
|
+
self.create_or_update_table()
|
|
47
|
+
|
|
48
|
+
def __contains__(self, key) -> bool:
|
|
49
|
+
try:
|
|
50
|
+
query = (
|
|
51
|
+
QueryBuilder()
|
|
52
|
+
.SELECT(1)
|
|
53
|
+
.FROM(self.table_name)
|
|
54
|
+
.WHERE(self.key_match_str())
|
|
55
|
+
.LIMIT(1)
|
|
56
|
+
)
|
|
57
|
+
result = self.execute_one(
|
|
58
|
+
*self.parse_key(key), sql_str=str(query), as_dict=False
|
|
59
|
+
)
|
|
60
|
+
return result is not None
|
|
61
|
+
finally:
|
|
62
|
+
self.clear
|
|
63
|
+
|
|
64
|
+
def __setitem__(self, key, value) -> DataclassT:
|
|
65
|
+
self.insert(value)
|
|
66
|
+
|
|
67
|
+
def key_match_str(self):
|
|
68
|
+
return " AND ".join([f"{key} = ?" for key in self.primary_keys])
|
|
69
|
+
|
|
70
|
+
def get_current_table_query(self) -> str:
|
|
71
|
+
row = (
|
|
72
|
+
QueryBuilder(self.connection)
|
|
73
|
+
.SELECT("sql")
|
|
74
|
+
.FROM("sqlite_master")
|
|
75
|
+
.WHERE("type")
|
|
76
|
+
.eq.quote("table")
|
|
77
|
+
.AND("name")
|
|
78
|
+
.eq.quote(self.table_name)
|
|
79
|
+
.end.execute_one(as_dict=False)
|
|
80
|
+
)
|
|
81
|
+
return row[0] if row else ""
|
|
82
|
+
|
|
83
|
+
def parse_key(self, key):
|
|
84
|
+
if not isinstance(key, tuple):
|
|
85
|
+
if isinstance(key, list):
|
|
86
|
+
key = tuple(key)
|
|
87
|
+
else:
|
|
88
|
+
key = (key,)
|
|
89
|
+
if len(self.primary_keys) != len(key):
|
|
90
|
+
raise KeyError(
|
|
91
|
+
"Input of (%s) does not match length of primary key (%s)",
|
|
92
|
+
key,
|
|
93
|
+
self.primary_keys,
|
|
94
|
+
)
|
|
95
|
+
return key
|
|
96
|
+
|
|
97
|
+
def insert(self, item: DataclassT):
|
|
98
|
+
params = self.codec.encode(item, as_tuple=False, ignore_none=True)
|
|
99
|
+
|
|
100
|
+
field_names = params.keys()
|
|
101
|
+
query = (
|
|
102
|
+
QueryBuilder()
|
|
103
|
+
.INSERT.INTO(self.table_name)
|
|
104
|
+
.par(*field_names)
|
|
105
|
+
.br.VALUES.placeholders(*field_names)
|
|
106
|
+
)
|
|
107
|
+
if self.primary_keys:
|
|
108
|
+
if len(self.primary_keys) < len(self.codec.class_fields):
|
|
109
|
+
(
|
|
110
|
+
query.br.ON.CONFLICT.par(*self.primary_keys).br.DO.UPDATE.SET(
|
|
111
|
+
*[
|
|
112
|
+
f"{col} = excluded.{col}"
|
|
113
|
+
for col in field_names
|
|
114
|
+
if col not in self.primary_keys
|
|
115
|
+
# and col not in self.codec.on_conflict_ignore
|
|
116
|
+
]
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
query.ON.CONFLICT.par(*self.primary_keys).DO.NOTHING
|
|
121
|
+
query.br.RETURNING(*self.primary_keys)
|
|
122
|
+
else:
|
|
123
|
+
query.br.RETURNING("rowid")
|
|
124
|
+
return self.execute_one(*params.values(), sql_str=query)
|
|
125
|
+
|
|
126
|
+
def get(
|
|
127
|
+
self, key, select_fields: list[str] = None, as_dict=False, as_tuple=False
|
|
128
|
+
) -> DataclassT:
|
|
129
|
+
if self.primary_keys == []:
|
|
130
|
+
return self.peek(key, select_fields, as_dict, as_tuple)
|
|
131
|
+
if as_dict and as_tuple:
|
|
132
|
+
raise ValueError("as_dict and as _tuple cannot both be true")
|
|
133
|
+
if select_fields and not as_dict and not as_tuple:
|
|
134
|
+
raise ValueError(
|
|
135
|
+
"If select fields are provided either as_dict or as_tuple must be true."
|
|
136
|
+
)
|
|
137
|
+
if not select_fields:
|
|
138
|
+
field_str = "*"
|
|
139
|
+
else:
|
|
140
|
+
field_str = ", ".join(select_fields)
|
|
141
|
+
query = (
|
|
142
|
+
QueryBuilder()
|
|
143
|
+
.SELECT(field_str)
|
|
144
|
+
.FROM(self.table_name)
|
|
145
|
+
.WHERE(self.key_match_str())
|
|
146
|
+
)
|
|
147
|
+
row_dict: dict = self.execute_one(
|
|
148
|
+
*self.parse_key(key),
|
|
149
|
+
sql_str=str(query),
|
|
150
|
+
as_dict=True,
|
|
151
|
+
)
|
|
152
|
+
as_obj = not (as_tuple or as_dict)
|
|
153
|
+
decoded = self.codec.decode(row_dict, as_obj=as_obj)
|
|
154
|
+
if as_tuple:
|
|
155
|
+
return tuple(decoded.values())
|
|
156
|
+
else:
|
|
157
|
+
return decoded
|
|
158
|
+
|
|
159
|
+
def peek(
|
|
160
|
+
self, rowid=None, select_fields: list[str] = None, as_dict=False, as_tuple=False
|
|
161
|
+
):
|
|
162
|
+
if as_dict and as_tuple:
|
|
163
|
+
raise ValueError("as_dict and as _tuple cannot both be true")
|
|
164
|
+
if select_fields and not as_dict and not as_tuple:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
"If select fields are provided either as_dict or as_tuple must be true."
|
|
167
|
+
)
|
|
168
|
+
if not select_fields:
|
|
169
|
+
field_str = "*"
|
|
170
|
+
else:
|
|
171
|
+
field_str = ", ".join(select_fields)
|
|
172
|
+
query = QueryBuilder().SELECT(field_str).FROM(self.table_name)
|
|
173
|
+
if rowid is not None:
|
|
174
|
+
query.WHERE("rowid").eq("?")
|
|
175
|
+
query.ORDER.BY("rowid").DESC
|
|
176
|
+
if rowid:
|
|
177
|
+
row_dict: dict = self.execute_one(
|
|
178
|
+
rowid,
|
|
179
|
+
sql_str=str(query),
|
|
180
|
+
as_dict=True,
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
row_dict: dict = self.execute_one(
|
|
184
|
+
sql_str=str(query),
|
|
185
|
+
as_dict=True,
|
|
186
|
+
)
|
|
187
|
+
as_obj = not (as_tuple or as_dict)
|
|
188
|
+
decoded = self.codec.decode(row_dict, as_obj=as_obj)
|
|
189
|
+
if as_tuple:
|
|
190
|
+
return tuple(decoded.values())
|
|
191
|
+
else:
|
|
192
|
+
return decoded
|
|
193
|
+
|
|
194
|
+
def delete(self, key) -> DataclassT:
|
|
195
|
+
query = QueryBuilder().DELETE.FROM(self.table_name).WHERE(self.key_match_str())
|
|
196
|
+
self.execute_one(*self.parse_key(key), sql_str=str(query))
|
|
197
|
+
|
|
198
|
+
def create_or_update_table(self):
|
|
199
|
+
logger.info("Creating or updating table %s", self.table_name)
|
|
200
|
+
if not table_exists(self.connection, self.table_name):
|
|
201
|
+
logger.info("Table %s does not exist. Creating table.", self.table_name)
|
|
202
|
+
query = QueryBuilder().add(self.sql_create_table())
|
|
203
|
+
self.execute_one(sql_str=str(query))
|
|
204
|
+
# query.execute_one()
|
|
205
|
+
|
|
206
|
+
elif self.get_curr_cols(self.data_class) != self.dataclass_sql_cols():
|
|
207
|
+
logger.info(
|
|
208
|
+
"Table %s does exist but does not match the dataclass. Attempting to update table..",
|
|
209
|
+
self.table_name,
|
|
210
|
+
)
|
|
211
|
+
fields_list = self.get_current_fields(self.table_name)
|
|
212
|
+
|
|
213
|
+
curr_fields = {f.name: f for f in fields_list}
|
|
214
|
+
dc_fields = self.codec.class_fields
|
|
215
|
+
overlapping_fields = []
|
|
216
|
+
for dc_field, dc_item in dc_fields.items():
|
|
217
|
+
if curr_fields.get(dc_field, None):
|
|
218
|
+
overlapping_fields.append(dc_field)
|
|
219
|
+
temp_table = f"temp_{self.table_name}"
|
|
220
|
+
logger.info(
|
|
221
|
+
"Attempting to update table on these overlapping fields",
|
|
222
|
+
self.table_name,
|
|
223
|
+
overlapping_fields,
|
|
224
|
+
)
|
|
225
|
+
if overlapping_fields:
|
|
226
|
+
query = (
|
|
227
|
+
QueryBuilder()
|
|
228
|
+
.BEGIN.IMMEDIATE.TRANSACTION.end.br(
|
|
229
|
+
self.sql_create_table(temp_table)
|
|
230
|
+
)
|
|
231
|
+
.br.INSERT.INTO(temp_table)
|
|
232
|
+
.par(*overlapping_fields)
|
|
233
|
+
.br.SELECT(*overlapping_fields)
|
|
234
|
+
.FROM(self.table_name)
|
|
235
|
+
.end.br.DROP.TABLE.IF.EXISTS(self.table_name)
|
|
236
|
+
.end.br.ALTER.TABLE(temp_table)
|
|
237
|
+
.RENAME.TO(self.table_name)
|
|
238
|
+
)
|
|
239
|
+
self.execute_script(sql_script=str(query))
|
|
240
|
+
else:
|
|
241
|
+
logger.warning(
|
|
242
|
+
"Table for dataclass %s has changed with no overlapping fields. Deleting table and creating a new one.",
|
|
243
|
+
self.data_class.__name__,
|
|
244
|
+
)
|
|
245
|
+
query = (
|
|
246
|
+
QueryBuilder()
|
|
247
|
+
.BEGIN.IMMEDIATE.TRANSACTION.end.br.DROP.TABLE.IF.EXISTS(
|
|
248
|
+
self.table_name
|
|
249
|
+
)
|
|
250
|
+
.end.br(self.sql_create_table())
|
|
251
|
+
.br
|
|
252
|
+
)
|
|
253
|
+
self.execute_script(sql_script=str(query))
|
|
254
|
+
logger.info(
|
|
255
|
+
"Table creation for %s successful.", self.table_name, overlapping_fields
|
|
256
|
+
)
|
|
257
|
+
else:
|
|
258
|
+
logger.info("Table has not changed.")
|
|
259
|
+
|
|
260
|
+
def sql_create_table(self, table_name=None) -> str:
|
|
261
|
+
table_name = table_name if table_name else self.table_name
|
|
262
|
+
cols_str = ",\n".join(self.dataclass_sql_cols())
|
|
263
|
+
return (
|
|
264
|
+
QueryBuilder()
|
|
265
|
+
.CREATE.TABLE.IF.NOT.EXISTS(table_name)
|
|
266
|
+
.par(f"\n{cols_str}\n")
|
|
267
|
+
.end
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
def dataclass_sql_cols(self):
|
|
271
|
+
"""Returns a list of SQL columns used in CREATE TABLE"""
|
|
272
|
+
#
|
|
273
|
+
params = []
|
|
274
|
+
for name, f in self.codec.class_fields.items():
|
|
275
|
+
params.append(
|
|
276
|
+
f.sql_col_def(len(self.primary_keys) > 1, len(self.unique) > 1)
|
|
277
|
+
)
|
|
278
|
+
if len(self.primary_keys) > 1:
|
|
279
|
+
params.append(f"{SQL.PRIMARY_KEY}({', '.join(self.primary_keys)})")
|
|
280
|
+
if len(self.unique) > 1:
|
|
281
|
+
params.append(f"{SQL.UNIQUE}({', '.join(self.unique)})")
|
|
282
|
+
return params
|