apexdevkit 1.17.9__tar.gz → 1.17.10__tar.gz
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.
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/PKG-INFO +1 -1
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/mssql.py +37 -69
- apexdevkit-1.17.10/apexdevkit/repository/sql.py +228 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/sqlite.py +41 -67
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/pyproject.toml +1 -1
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/LICENSE +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/README.md +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/error.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/request.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/fluent.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/connector.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/decorator.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/repository/mongo.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/server.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/synchronization.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/testing/rest.py +0 -0
- {apexdevkit-1.17.9 → apexdevkit-1.17.10}/apexdevkit/value.py +0 -0
|
@@ -8,6 +8,7 @@ from pymssql.exceptions import DatabaseError
|
|
|
8
8
|
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
9
9
|
from apexdevkit.formatter import Formatter
|
|
10
10
|
from apexdevkit.repository import Database, DatabaseCommand, RepositoryBase
|
|
11
|
+
from apexdevkit.repository.sql import NotNone, SqlFieldManager, _SqlField
|
|
11
12
|
|
|
12
13
|
ItemT = TypeVar("ItemT")
|
|
13
14
|
|
|
@@ -143,7 +144,7 @@ class MsSqlTableBuilder(Generic[ItemT]):
|
|
|
143
144
|
schema: str | None = None
|
|
144
145
|
table: str | None = None
|
|
145
146
|
formatter: Formatter[dict[str, Any], ItemT] | None = None
|
|
146
|
-
fields: list[
|
|
147
|
+
fields: list[_SqlField] | None = None
|
|
147
148
|
|
|
148
149
|
def with_username(self, value: str) -> MsSqlTableBuilder[ItemT]:
|
|
149
150
|
return MsSqlTableBuilder[ItemT](
|
|
@@ -183,18 +184,29 @@ class MsSqlTableBuilder(Generic[ItemT]):
|
|
|
183
184
|
self.fields,
|
|
184
185
|
)
|
|
185
186
|
|
|
186
|
-
def with_fields(self, value: Iterable[
|
|
187
|
-
|
|
188
|
-
if len([
|
|
189
|
-
raise ValueError("Pass only one identifier
|
|
190
|
-
if len([
|
|
191
|
-
raise ValueError("Pass only one parent
|
|
187
|
+
def with_fields(self, value: Iterable[_SqlField]) -> MsSqlTableBuilder[ItemT]:
|
|
188
|
+
key_list = list(value)
|
|
189
|
+
if len([key for key in key_list if key.is_id]) != 1:
|
|
190
|
+
raise ValueError("Pass only one identifier key.")
|
|
191
|
+
if len([key for key in key_list if key.is_parent]) > 1:
|
|
192
|
+
raise ValueError("Pass only one parent key.")
|
|
193
|
+
if (
|
|
194
|
+
len(
|
|
195
|
+
[
|
|
196
|
+
key
|
|
197
|
+
for key in key_list
|
|
198
|
+
if not key.is_filter and isinstance(key.fixed_value, NotNone)
|
|
199
|
+
]
|
|
200
|
+
)
|
|
201
|
+
> 0
|
|
202
|
+
):
|
|
203
|
+
raise ValueError("Only filter fields can be 'not null'.")
|
|
192
204
|
return MsSqlTableBuilder[ItemT](
|
|
193
205
|
self.username,
|
|
194
206
|
self.schema,
|
|
195
207
|
self.table,
|
|
196
208
|
self.formatter,
|
|
197
|
-
|
|
209
|
+
key_list,
|
|
198
210
|
)
|
|
199
211
|
|
|
200
212
|
def build(self) -> SqlTable[ItemT]:
|
|
@@ -205,7 +217,7 @@ class MsSqlTableBuilder(Generic[ItemT]):
|
|
|
205
217
|
self.schema,
|
|
206
218
|
self.table,
|
|
207
219
|
self.formatter,
|
|
208
|
-
self.fields,
|
|
220
|
+
SqlFieldManager.Builder().with_fields(self.fields).for_mssql().build(),
|
|
209
221
|
self.username,
|
|
210
222
|
)
|
|
211
223
|
|
|
@@ -215,7 +227,7 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
215
227
|
schema: str
|
|
216
228
|
table: str
|
|
217
229
|
formatter: Formatter[dict[str, Any], ItemT]
|
|
218
|
-
fields:
|
|
230
|
+
fields: SqlFieldManager
|
|
219
231
|
username: str | None = None
|
|
220
232
|
|
|
221
233
|
def count_all(self) -> DatabaseCommand:
|
|
@@ -223,9 +235,9 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
223
235
|
{self._user_check}
|
|
224
236
|
SELECT count(*) AS n_items
|
|
225
237
|
FROM [{self.schema}].[{self.table}]
|
|
226
|
-
{self.
|
|
238
|
+
{self.fields.where_statement(include_id=False)}
|
|
227
239
|
REVERT
|
|
228
|
-
""").with_data(self.
|
|
240
|
+
""").with_data(self.fields.with_fixed({}))
|
|
229
241
|
|
|
230
242
|
def insert(self, item: ItemT) -> DatabaseCommand:
|
|
231
243
|
columns = ", ".join(
|
|
@@ -246,7 +258,7 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
246
258
|
{placeholders}
|
|
247
259
|
)
|
|
248
260
|
REVERT
|
|
249
|
-
""").with_data(self.
|
|
261
|
+
""").with_data(self.fields.with_fixed(self.formatter.dump(item)))
|
|
250
262
|
|
|
251
263
|
def select(self, item_id: str) -> DatabaseCommand:
|
|
252
264
|
columns = ", ".join(["[" + field.name + "]" for field in self.fields])
|
|
@@ -256,9 +268,9 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
256
268
|
SELECT
|
|
257
269
|
{columns}
|
|
258
270
|
FROM [{self.schema}].[{self.table}]
|
|
259
|
-
{self.
|
|
271
|
+
{self.fields.where_statement(include_id=True)}
|
|
260
272
|
REVERT
|
|
261
|
-
""").with_data(self.
|
|
273
|
+
""").with_data(self.fields.with_fixed({self.fields.id: item_id}))
|
|
262
274
|
|
|
263
275
|
def select_all(self) -> DatabaseCommand:
|
|
264
276
|
columns = ", ".join(["[" + field.name + "]" for field in self.fields])
|
|
@@ -268,10 +280,10 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
268
280
|
SELECT
|
|
269
281
|
{columns}
|
|
270
282
|
FROM [{self.schema}].[{self.table}]
|
|
271
|
-
{self.
|
|
272
|
-
{self.
|
|
283
|
+
{self.fields.where_statement(include_id=False)}
|
|
284
|
+
{self.fields.order}
|
|
273
285
|
REVERT
|
|
274
|
-
""").with_data(self.
|
|
286
|
+
""").with_data(self.fields.with_fixed({}))
|
|
275
287
|
|
|
276
288
|
def update(self, item: ItemT) -> DatabaseCommand:
|
|
277
289
|
updates = ", ".join(
|
|
@@ -287,27 +299,27 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
287
299
|
UPDATE [{self.schema}].[{self.table}]
|
|
288
300
|
SET
|
|
289
301
|
{updates}
|
|
290
|
-
{self.
|
|
302
|
+
{self.fields.where_statement(include_id=True)}
|
|
291
303
|
REVERT
|
|
292
|
-
""").with_data(self.
|
|
304
|
+
""").with_data(self.fields.with_fixed(self.formatter.dump(item)))
|
|
293
305
|
|
|
294
306
|
def delete(self, item_id: str) -> DatabaseCommand:
|
|
295
307
|
return DatabaseCommand(f"""
|
|
296
308
|
{self._user_check}
|
|
297
309
|
DELETE
|
|
298
310
|
FROM [{self.schema}].[{self.table}]
|
|
299
|
-
{self.
|
|
311
|
+
{self.fields.where_statement(include_id=True)}
|
|
300
312
|
REVERT
|
|
301
|
-
""").with_data(self.
|
|
313
|
+
""").with_data(self.fields.with_fixed({self.fields.id: item_id}))
|
|
302
314
|
|
|
303
315
|
def delete_all(self) -> DatabaseCommand:
|
|
304
316
|
return DatabaseCommand(f"""
|
|
305
317
|
{self._user_check}
|
|
306
318
|
DELETE
|
|
307
319
|
FROM [{self.schema}].[{self.table}]
|
|
308
|
-
{self.
|
|
320
|
+
{self.fields.where_statement(include_id=False)}
|
|
309
321
|
REVERT
|
|
310
|
-
""").with_data(self.
|
|
322
|
+
""").with_data(self.fields.with_fixed({}))
|
|
311
323
|
|
|
312
324
|
def load(self, data: dict[str, Any]) -> ItemT:
|
|
313
325
|
return self.formatter.load(data)
|
|
@@ -315,56 +327,12 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
315
327
|
def exists(self, duplicate: ItemT) -> ExistsError:
|
|
316
328
|
raw = self.formatter.dump(duplicate)
|
|
317
329
|
return ExistsError(duplicate).with_duplicate(
|
|
318
|
-
lambda i: f"{self.
|
|
330
|
+
lambda i: f"{self.fields.id}<{raw[self.fields.id]}>"
|
|
319
331
|
)
|
|
320
332
|
|
|
321
|
-
def _where_statement(self, include_id: bool = False) -> str:
|
|
322
|
-
result = next((field for field in self.fields if field.is_parent), None)
|
|
323
|
-
if result is None:
|
|
324
|
-
return f"WHERE [{self._id}] = %({self._id})s" if include_id else ""
|
|
325
|
-
else:
|
|
326
|
-
statement = "WHERE [" + result.name + "] = %(" + result.name + ")s"
|
|
327
|
-
if include_id:
|
|
328
|
-
statement += f" AND [{self._id}] = %({self._id})s"
|
|
329
|
-
return statement
|
|
330
|
-
|
|
331
|
-
def _data_with_fixed(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
332
|
-
for field in self.fields:
|
|
333
|
-
if field.is_parent or field.is_fixed:
|
|
334
|
-
data[field.name] = field.fixed_value
|
|
335
|
-
return data
|
|
336
|
-
|
|
337
|
-
@property
|
|
338
|
-
def _id(self) -> str:
|
|
339
|
-
result = next((field for field in self.fields if field.is_id), None)
|
|
340
|
-
if result is None:
|
|
341
|
-
raise ValueError("Id field is required.")
|
|
342
|
-
return result.name
|
|
343
|
-
|
|
344
333
|
@property
|
|
345
334
|
def _user_check(self) -> str:
|
|
346
335
|
if self.username is not None:
|
|
347
336
|
return f"EXECUTE AS USER = '{self.username}'"
|
|
348
337
|
else:
|
|
349
338
|
return ""
|
|
350
|
-
|
|
351
|
-
@property
|
|
352
|
-
def _order(self) -> str:
|
|
353
|
-
ordering = [field.name for field in self.fields if field.is_ordered]
|
|
354
|
-
if len(ordering) > 0:
|
|
355
|
-
return "ORDER BY " + ", ".join(ordering)
|
|
356
|
-
else:
|
|
357
|
-
return ""
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
@dataclass(frozen=True)
|
|
361
|
-
class MsSqlField:
|
|
362
|
-
name: str
|
|
363
|
-
is_id: bool = False
|
|
364
|
-
is_ordered: bool = False
|
|
365
|
-
include_in_insert: bool = True
|
|
366
|
-
|
|
367
|
-
is_parent: bool = False
|
|
368
|
-
|
|
369
|
-
is_fixed: bool = False
|
|
370
|
-
fixed_value: Any | None = None
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Iterator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class NotNone:
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class _SqlField:
|
|
14
|
+
name: str
|
|
15
|
+
is_id: bool = False
|
|
16
|
+
is_ordered: bool = False
|
|
17
|
+
is_composite: bool = False
|
|
18
|
+
include_in_insert: bool = True
|
|
19
|
+
|
|
20
|
+
is_parent: bool = False
|
|
21
|
+
parent_value: Any | None = None
|
|
22
|
+
|
|
23
|
+
is_filter: bool = False
|
|
24
|
+
filter_value: Any | None | NotNone = None
|
|
25
|
+
|
|
26
|
+
is_fixed: bool = False
|
|
27
|
+
fixed_value: Any | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class SqlFieldBuilder:
|
|
32
|
+
_name: str = field(init=False)
|
|
33
|
+
_is_id: bool = False
|
|
34
|
+
_is_ordered: bool = False
|
|
35
|
+
_is_composite: bool = False
|
|
36
|
+
_include_in_insert: bool = True
|
|
37
|
+
|
|
38
|
+
_is_parent: bool = False
|
|
39
|
+
_parent_value: Any | None = None
|
|
40
|
+
|
|
41
|
+
_is_filter: bool = False
|
|
42
|
+
_filter_value: Any | None | NotNone = None
|
|
43
|
+
|
|
44
|
+
_is_fixed: bool = False
|
|
45
|
+
_fixed_value: Any | None = None
|
|
46
|
+
|
|
47
|
+
def with_name(self, value: str) -> SqlFieldBuilder:
|
|
48
|
+
self._name = value
|
|
49
|
+
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def as_id(self) -> SqlFieldBuilder:
|
|
53
|
+
self._is_id = True
|
|
54
|
+
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def as_composite(self) -> SqlFieldBuilder:
|
|
58
|
+
self._is_composite = True
|
|
59
|
+
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def in_ordering(self) -> SqlFieldBuilder:
|
|
63
|
+
self._is_ordered = True
|
|
64
|
+
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def derive_on_insert(self) -> SqlFieldBuilder:
|
|
68
|
+
self._include_in_insert = False
|
|
69
|
+
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def as_parent(self, value: Any | None) -> SqlFieldBuilder:
|
|
73
|
+
self._is_parent = True
|
|
74
|
+
self._parent_value = value
|
|
75
|
+
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
def as_filter(self, value: Any | None | NotNone) -> SqlFieldBuilder:
|
|
79
|
+
self._is_filter = True
|
|
80
|
+
self._filter_value = value
|
|
81
|
+
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def as_fixed(self, value: Any | None) -> SqlFieldBuilder:
|
|
85
|
+
self._is_fixed = True
|
|
86
|
+
self._fixed_value = value
|
|
87
|
+
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
def build(self) -> _SqlField:
|
|
91
|
+
return _SqlField(
|
|
92
|
+
self._name,
|
|
93
|
+
self._is_id,
|
|
94
|
+
self._is_ordered,
|
|
95
|
+
self._is_composite,
|
|
96
|
+
self._include_in_insert,
|
|
97
|
+
self._is_parent,
|
|
98
|
+
self._parent_value,
|
|
99
|
+
self._is_filter,
|
|
100
|
+
self._filter_value,
|
|
101
|
+
self._is_fixed,
|
|
102
|
+
self._fixed_value,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class SqlFieldManager:
|
|
108
|
+
fields: list[_SqlField]
|
|
109
|
+
key_formatter: str
|
|
110
|
+
value_formatter: str
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def id(self) -> str:
|
|
114
|
+
result = next((key for key in self.fields if key.is_id), None)
|
|
115
|
+
if result is None:
|
|
116
|
+
raise ValueError("Id field is required.")
|
|
117
|
+
return result.name
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def composite(self) -> list[str]:
|
|
121
|
+
names = [key.name for key in self.fields if key.is_composite]
|
|
122
|
+
return [self.id] if len(names) == 0 else names
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def order(self) -> str:
|
|
126
|
+
ordering = [key.name for key in self.fields if key.is_ordered]
|
|
127
|
+
if len(ordering) > 0:
|
|
128
|
+
return "ORDER BY " + ", ".join(ordering)
|
|
129
|
+
else:
|
|
130
|
+
return ""
|
|
131
|
+
|
|
132
|
+
def __iter__(self) -> Iterator[_SqlField]:
|
|
133
|
+
yield from self.fields
|
|
134
|
+
|
|
135
|
+
def where_statement(self, include_id: bool = False) -> str:
|
|
136
|
+
statements = [
|
|
137
|
+
statement
|
|
138
|
+
for statement in [
|
|
139
|
+
self._parent_filter(),
|
|
140
|
+
self._id_filter(include_id),
|
|
141
|
+
self._general_filters(),
|
|
142
|
+
]
|
|
143
|
+
if statement != ""
|
|
144
|
+
]
|
|
145
|
+
if len(statements) > 0:
|
|
146
|
+
return "WHERE " + " AND ".join(statements)
|
|
147
|
+
else:
|
|
148
|
+
return ""
|
|
149
|
+
|
|
150
|
+
def with_fixed(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
151
|
+
for key in self.fields:
|
|
152
|
+
if key.is_parent:
|
|
153
|
+
data[key.name] = key.parent_value
|
|
154
|
+
if key.is_fixed:
|
|
155
|
+
data[key.name] = key.fixed_value
|
|
156
|
+
return data
|
|
157
|
+
|
|
158
|
+
def _parent_filter(self) -> str:
|
|
159
|
+
result = next((key for key in self.fields if key.is_parent), None)
|
|
160
|
+
if result is not None:
|
|
161
|
+
if result.parent_value is None:
|
|
162
|
+
return self.key_formatter.replace("x", result.name) + " IS NULL"
|
|
163
|
+
else:
|
|
164
|
+
return (
|
|
165
|
+
self.key_formatter.replace("x", result.name)
|
|
166
|
+
+ " = "
|
|
167
|
+
+ self.value_formatter.replace("x", result.name)
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
return ""
|
|
171
|
+
|
|
172
|
+
def _id_filter(self, include_id: bool = False) -> str:
|
|
173
|
+
return (
|
|
174
|
+
self.key_formatter.replace("x", self.id)
|
|
175
|
+
+ " = "
|
|
176
|
+
+ self.value_formatter.replace("x", self.id)
|
|
177
|
+
if include_id
|
|
178
|
+
else ""
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def _general_filters(self) -> str:
|
|
182
|
+
statements: list[str] = []
|
|
183
|
+
for key in self.fields:
|
|
184
|
+
if key.is_filter:
|
|
185
|
+
if key.filter_value is None:
|
|
186
|
+
statements.append(
|
|
187
|
+
self.key_formatter.replace("x", key.name) + " IS NULL"
|
|
188
|
+
)
|
|
189
|
+
elif isinstance(key.filter_value, NotNone):
|
|
190
|
+
statements.append(
|
|
191
|
+
self.key_formatter.replace("x", key.name) + " IS NOT NULL"
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
statements.append(
|
|
195
|
+
self.key_formatter.replace("x", key.name)
|
|
196
|
+
+ " = "
|
|
197
|
+
+ self.value_formatter.replace("x", key.name)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return " AND ".join(statements)
|
|
201
|
+
|
|
202
|
+
@dataclass
|
|
203
|
+
class Builder:
|
|
204
|
+
fields: list[_SqlField] = field(init=False)
|
|
205
|
+
key_formatter: str = field(init=False)
|
|
206
|
+
value_formatter: str = field(init=False)
|
|
207
|
+
|
|
208
|
+
def with_fields(self, fields: list[_SqlField]) -> SqlFieldManager.Builder:
|
|
209
|
+
self.fields = fields
|
|
210
|
+
|
|
211
|
+
return self
|
|
212
|
+
|
|
213
|
+
def for_sqlite(self) -> SqlFieldManager.Builder:
|
|
214
|
+
self.key_formatter = "x"
|
|
215
|
+
self.value_formatter = ":x"
|
|
216
|
+
|
|
217
|
+
return self
|
|
218
|
+
|
|
219
|
+
def for_mssql(self) -> SqlFieldManager.Builder:
|
|
220
|
+
self.key_formatter = "[x]"
|
|
221
|
+
self.value_formatter = "%(x)s"
|
|
222
|
+
|
|
223
|
+
return self
|
|
224
|
+
|
|
225
|
+
def build(self) -> SqlFieldManager:
|
|
226
|
+
return SqlFieldManager(
|
|
227
|
+
self.fields, self.key_formatter, self.value_formatter
|
|
228
|
+
)
|
|
@@ -8,6 +8,7 @@ from apexdevkit.error import DoesNotExistError, ExistsError
|
|
|
8
8
|
from apexdevkit.formatter import Formatter
|
|
9
9
|
from apexdevkit.repository import Database, DatabaseCommand, RepositoryBase
|
|
10
10
|
from apexdevkit.repository.interface import ItemT
|
|
11
|
+
from apexdevkit.repository.sql import NotNone, SqlFieldManager, _SqlField
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclass(frozen=True)
|
|
@@ -102,7 +103,7 @@ class UnknownError(Exception):
|
|
|
102
103
|
class SqliteTableBuilder(Generic[ItemT]):
|
|
103
104
|
table_name: str | None = None
|
|
104
105
|
formatter: Formatter[dict[str, Any], ItemT] | None = None
|
|
105
|
-
fields: list[
|
|
106
|
+
fields: list[_SqlField] | None = None
|
|
106
107
|
|
|
107
108
|
def with_name(self, value: str) -> SqliteTableBuilder[ItemT]:
|
|
108
109
|
return SqliteTableBuilder[ItemT](value, self.formatter, self.fields)
|
|
@@ -112,16 +113,27 @@ class SqliteTableBuilder(Generic[ItemT]):
|
|
|
112
113
|
) -> SqliteTableBuilder[ItemT]:
|
|
113
114
|
return SqliteTableBuilder[ItemT](self.table_name, value, self.fields)
|
|
114
115
|
|
|
115
|
-
def with_fields(self, value: Iterable[
|
|
116
|
-
|
|
117
|
-
if len([
|
|
118
|
-
raise ValueError("Pass only one identifier
|
|
119
|
-
if len([
|
|
120
|
-
raise ValueError("Pass only one parent
|
|
116
|
+
def with_fields(self, value: Iterable[_SqlField]) -> SqliteTableBuilder[ItemT]:
|
|
117
|
+
key_list = list(value)
|
|
118
|
+
if len([key for key in key_list if key.is_id]) != 1:
|
|
119
|
+
raise ValueError("Pass only one identifier key.")
|
|
120
|
+
if len([key for key in key_list if key.is_parent]) > 1:
|
|
121
|
+
raise ValueError("Pass only one parent key.")
|
|
122
|
+
if (
|
|
123
|
+
len(
|
|
124
|
+
[
|
|
125
|
+
key
|
|
126
|
+
for key in key_list
|
|
127
|
+
if not key.is_filter and isinstance(key.fixed_value, NotNone)
|
|
128
|
+
]
|
|
129
|
+
)
|
|
130
|
+
> 0
|
|
131
|
+
):
|
|
132
|
+
raise ValueError("Only filter fields can be 'not null'.")
|
|
121
133
|
return SqliteTableBuilder[ItemT](
|
|
122
134
|
self.table_name,
|
|
123
135
|
self.formatter,
|
|
124
|
-
|
|
136
|
+
key_list,
|
|
125
137
|
)
|
|
126
138
|
|
|
127
139
|
def build(self) -> SqlTable[ItemT]:
|
|
@@ -131,7 +143,7 @@ class SqliteTableBuilder(Generic[ItemT]):
|
|
|
131
143
|
return _DefaultSqlTable(
|
|
132
144
|
self.table_name,
|
|
133
145
|
self.formatter,
|
|
134
|
-
self.fields,
|
|
146
|
+
SqlFieldManager.Builder().with_fields(self.fields).for_sqlite().build(),
|
|
135
147
|
)
|
|
136
148
|
|
|
137
149
|
|
|
@@ -139,14 +151,14 @@ class SqliteTableBuilder(Generic[ItemT]):
|
|
|
139
151
|
class _DefaultSqlTable(SqlTable[ItemT]):
|
|
140
152
|
table_name: str
|
|
141
153
|
formatter: Formatter[dict[str, Any], ItemT]
|
|
142
|
-
fields:
|
|
154
|
+
fields: SqlFieldManager
|
|
143
155
|
|
|
144
156
|
def count_all(self) -> DatabaseCommand:
|
|
145
157
|
return DatabaseCommand(f"""
|
|
146
158
|
SELECT count(*) as n_items
|
|
147
159
|
FROM {self.table_name.upper()}
|
|
148
|
-
{self.
|
|
149
|
-
""").with_data(self.
|
|
160
|
+
{self.fields.where_statement(include_id=False)};
|
|
161
|
+
""").with_data(self.fields.with_fixed({}))
|
|
150
162
|
|
|
151
163
|
def insert(self, item: ItemT) -> DatabaseCommand:
|
|
152
164
|
insert_columns = ", ".join(
|
|
@@ -164,7 +176,7 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
164
176
|
{placeholders}
|
|
165
177
|
)
|
|
166
178
|
RETURNING {return_columns};
|
|
167
|
-
""").with_data(self.
|
|
179
|
+
""").with_data(self.fields.with_fixed(self.formatter.dump(item)))
|
|
168
180
|
|
|
169
181
|
def select(self, item_id: str) -> DatabaseCommand:
|
|
170
182
|
columns = ", ".join([field.name for field in self.fields])
|
|
@@ -173,21 +185,23 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
173
185
|
SELECT
|
|
174
186
|
{columns}
|
|
175
187
|
FROM {self.table_name.upper()}
|
|
176
|
-
{self.
|
|
177
|
-
""").with_data(self.
|
|
188
|
+
{self.fields.where_statement(include_id=True)};
|
|
189
|
+
""").with_data(self.fields.with_fixed({self.fields.id: item_id}))
|
|
178
190
|
|
|
179
191
|
def select_duplicate(self, item: ItemT) -> DatabaseCommand:
|
|
180
192
|
raw = self.formatter.dump(item)
|
|
181
193
|
columns = ", ".join([field.name for field in self.fields])
|
|
182
194
|
|
|
183
|
-
duplicates = " AND ".join(
|
|
195
|
+
duplicates = " AND ".join(
|
|
196
|
+
[f"{field} = :{field}" for field in self.fields.composite]
|
|
197
|
+
)
|
|
184
198
|
|
|
185
199
|
return DatabaseCommand(f"""
|
|
186
200
|
SELECT
|
|
187
201
|
{columns}
|
|
188
202
|
FROM {self.table_name.upper()}
|
|
189
203
|
WHERE {duplicates};
|
|
190
|
-
""").with_data({key: raw[key] for key in raw if key in self.
|
|
204
|
+
""").with_data({key: raw[key] for key in raw if key in self.fields.composite})
|
|
191
205
|
|
|
192
206
|
def select_all(self) -> DatabaseCommand:
|
|
193
207
|
columns = ", ".join([field.name for field in self.fields])
|
|
@@ -196,8 +210,9 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
196
210
|
SELECT
|
|
197
211
|
{columns}
|
|
198
212
|
FROM {self.table_name.capitalize()}
|
|
199
|
-
{self.
|
|
200
|
-
|
|
213
|
+
{self.fields.where_statement(include_id=False)}
|
|
214
|
+
{self.fields.order}
|
|
215
|
+
""").with_data(self.fields.with_fixed({}))
|
|
201
216
|
|
|
202
217
|
def update(self, item: ItemT) -> DatabaseCommand:
|
|
203
218
|
updates = ", ".join(
|
|
@@ -212,22 +227,22 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
212
227
|
UPDATE {self.table_name.upper()}
|
|
213
228
|
SET
|
|
214
229
|
{updates}
|
|
215
|
-
{self.
|
|
216
|
-
""").with_data(self.
|
|
230
|
+
{self.fields.where_statement(include_id=True)};
|
|
231
|
+
""").with_data(self.fields.with_fixed(self.formatter.dump(item)))
|
|
217
232
|
|
|
218
233
|
def delete(self, item_id: str) -> DatabaseCommand:
|
|
219
234
|
return DatabaseCommand(f"""
|
|
220
235
|
DELETE
|
|
221
236
|
FROM {self.table_name.upper()}
|
|
222
|
-
{self.
|
|
223
|
-
""").with_data(self.
|
|
237
|
+
{self.fields.where_statement(include_id=True)};
|
|
238
|
+
""").with_data(self.fields.with_fixed({self.fields.id: item_id}))
|
|
224
239
|
|
|
225
240
|
def delete_all(self) -> DatabaseCommand:
|
|
226
241
|
return DatabaseCommand(f"""
|
|
227
242
|
DELETE
|
|
228
243
|
FROM {self.table_name.upper()}
|
|
229
|
-
{self.
|
|
230
|
-
""").with_data(self.
|
|
244
|
+
{self.fields.where_statement(include_id=False)};
|
|
245
|
+
""").with_data(self.fields.with_fixed({}))
|
|
231
246
|
|
|
232
247
|
def load(self, data: dict[str, Any]) -> ItemT:
|
|
233
248
|
return self.formatter.load(data)
|
|
@@ -236,47 +251,6 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
236
251
|
raw = self.formatter.dump(item)
|
|
237
252
|
return ExistsError(item).with_duplicate(
|
|
238
253
|
lambda i: ",".join(
|
|
239
|
-
[f"{key}<{raw[key]}>" for key in raw if key in self.
|
|
254
|
+
[f"{key}<{raw[key]}>" for key in raw if key in self.fields.composite]
|
|
240
255
|
)
|
|
241
256
|
)
|
|
242
|
-
|
|
243
|
-
def _where_statement(self, include_id: bool = False) -> str:
|
|
244
|
-
result = next((field for field in self.fields if field.is_parent), None)
|
|
245
|
-
if result is None:
|
|
246
|
-
return f"WHERE {self._id} = :{self._id}" if include_id else ""
|
|
247
|
-
else:
|
|
248
|
-
statement = "WHERE " + result.name + " = :" + result.name
|
|
249
|
-
if include_id:
|
|
250
|
-
statement += f" AND {self._id} = :{self._id}"
|
|
251
|
-
return statement
|
|
252
|
-
|
|
253
|
-
def _data_with_fixed(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
254
|
-
for field in self.fields:
|
|
255
|
-
if field.is_parent or field.is_fixed:
|
|
256
|
-
data[field.name] = field.fixed_value
|
|
257
|
-
return data
|
|
258
|
-
|
|
259
|
-
@property
|
|
260
|
-
def _id(self) -> str:
|
|
261
|
-
result = next((field for field in self.fields if field.is_id), None)
|
|
262
|
-
if result is None:
|
|
263
|
-
raise ValueError("Id field is required.")
|
|
264
|
-
return result.name
|
|
265
|
-
|
|
266
|
-
@property
|
|
267
|
-
def _composite(self) -> list[str]:
|
|
268
|
-
names = [field.name for field in self.fields if field.is_composite]
|
|
269
|
-
return [self._id] if len(names) == 0 else names
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
@dataclass(frozen=True)
|
|
273
|
-
class SqliteField:
|
|
274
|
-
name: str
|
|
275
|
-
is_id: bool = False
|
|
276
|
-
is_composite: bool = False
|
|
277
|
-
include_in_insert: bool = True
|
|
278
|
-
|
|
279
|
-
is_parent: bool = False
|
|
280
|
-
|
|
281
|
-
is_fixed: bool = False
|
|
282
|
-
fixed_value: Any | None = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|