apexdevkit 1.17.8__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.8 → apexdevkit-1.17.10}/PKG-INFO +1 -1
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/mssql.py +43 -68
- apexdevkit-1.17.10/apexdevkit/repository/sql.py +228 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/sqlite.py +50 -68
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/pyproject.toml +1 -1
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/LICENSE +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/README.md +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/error.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/request.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fluent.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/connector.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/decorator.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/mongo.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/server.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/synchronization.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/testing/rest.py +0 -0
- {apexdevkit-1.17.8 → 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,13 +235,17 @@ 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
|
-
columns = ", ".join(
|
|
232
|
-
|
|
243
|
+
columns = ", ".join(
|
|
244
|
+
["[" + field.name + "]" for field in self.fields if field.include_in_insert]
|
|
245
|
+
)
|
|
246
|
+
placeholders = ", ".join(
|
|
247
|
+
[f"%({key.name})s" for key in self.fields if key.include_in_insert]
|
|
248
|
+
)
|
|
233
249
|
output = ", ".join(["INSERTED." + field.name for field in self.fields])
|
|
234
250
|
|
|
235
251
|
return DatabaseCommand(f"""
|
|
@@ -242,7 +258,7 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
242
258
|
{placeholders}
|
|
243
259
|
)
|
|
244
260
|
REVERT
|
|
245
|
-
""").with_data(self.
|
|
261
|
+
""").with_data(self.fields.with_fixed(self.formatter.dump(item)))
|
|
246
262
|
|
|
247
263
|
def select(self, item_id: str) -> DatabaseCommand:
|
|
248
264
|
columns = ", ".join(["[" + field.name + "]" for field in self.fields])
|
|
@@ -252,9 +268,9 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
252
268
|
SELECT
|
|
253
269
|
{columns}
|
|
254
270
|
FROM [{self.schema}].[{self.table}]
|
|
255
|
-
{self.
|
|
271
|
+
{self.fields.where_statement(include_id=True)}
|
|
256
272
|
REVERT
|
|
257
|
-
""").with_data(self.
|
|
273
|
+
""").with_data(self.fields.with_fixed({self.fields.id: item_id}))
|
|
258
274
|
|
|
259
275
|
def select_all(self) -> DatabaseCommand:
|
|
260
276
|
columns = ", ".join(["[" + field.name + "]" for field in self.fields])
|
|
@@ -264,10 +280,10 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
264
280
|
SELECT
|
|
265
281
|
{columns}
|
|
266
282
|
FROM [{self.schema}].[{self.table}]
|
|
267
|
-
{self.
|
|
268
|
-
{self.
|
|
283
|
+
{self.fields.where_statement(include_id=False)}
|
|
284
|
+
{self.fields.order}
|
|
269
285
|
REVERT
|
|
270
|
-
""").with_data(self.
|
|
286
|
+
""").with_data(self.fields.with_fixed({}))
|
|
271
287
|
|
|
272
288
|
def update(self, item: ItemT) -> DatabaseCommand:
|
|
273
289
|
updates = ", ".join(
|
|
@@ -283,27 +299,27 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
283
299
|
UPDATE [{self.schema}].[{self.table}]
|
|
284
300
|
SET
|
|
285
301
|
{updates}
|
|
286
|
-
{self.
|
|
302
|
+
{self.fields.where_statement(include_id=True)}
|
|
287
303
|
REVERT
|
|
288
|
-
""").with_data(self.
|
|
304
|
+
""").with_data(self.fields.with_fixed(self.formatter.dump(item)))
|
|
289
305
|
|
|
290
306
|
def delete(self, item_id: str) -> DatabaseCommand:
|
|
291
307
|
return DatabaseCommand(f"""
|
|
292
308
|
{self._user_check}
|
|
293
309
|
DELETE
|
|
294
310
|
FROM [{self.schema}].[{self.table}]
|
|
295
|
-
{self.
|
|
311
|
+
{self.fields.where_statement(include_id=True)}
|
|
296
312
|
REVERT
|
|
297
|
-
""").with_data(self.
|
|
313
|
+
""").with_data(self.fields.with_fixed({self.fields.id: item_id}))
|
|
298
314
|
|
|
299
315
|
def delete_all(self) -> DatabaseCommand:
|
|
300
316
|
return DatabaseCommand(f"""
|
|
301
317
|
{self._user_check}
|
|
302
318
|
DELETE
|
|
303
319
|
FROM [{self.schema}].[{self.table}]
|
|
304
|
-
{self.
|
|
320
|
+
{self.fields.where_statement(include_id=False)}
|
|
305
321
|
REVERT
|
|
306
|
-
""").with_data(self.
|
|
322
|
+
""").with_data(self.fields.with_fixed({}))
|
|
307
323
|
|
|
308
324
|
def load(self, data: dict[str, Any]) -> ItemT:
|
|
309
325
|
return self.formatter.load(data)
|
|
@@ -311,53 +327,12 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
311
327
|
def exists(self, duplicate: ItemT) -> ExistsError:
|
|
312
328
|
raw = self.formatter.dump(duplicate)
|
|
313
329
|
return ExistsError(duplicate).with_duplicate(
|
|
314
|
-
lambda i: f"{self.
|
|
330
|
+
lambda i: f"{self.fields.id}<{raw[self.fields.id]}>"
|
|
315
331
|
)
|
|
316
332
|
|
|
317
|
-
def _where_statement(self, include_id: bool = False) -> str:
|
|
318
|
-
result = next((field for field in self.fields if field.is_parent), None)
|
|
319
|
-
if result is None:
|
|
320
|
-
return f"WHERE [{self._id}] = %({self._id})s" if include_id else ""
|
|
321
|
-
else:
|
|
322
|
-
statement = "WHERE [" + result.name + "] = %(" + result.name + ")s"
|
|
323
|
-
if include_id:
|
|
324
|
-
statement += f" AND [{self._id}] = %({self._id})s"
|
|
325
|
-
return statement
|
|
326
|
-
|
|
327
|
-
def _data_with_replaced_parent(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
328
|
-
result = next((field for field in self.fields if field.is_parent), None)
|
|
329
|
-
if result is not None:
|
|
330
|
-
data[result.name] = result.fixed_value
|
|
331
|
-
return data
|
|
332
|
-
|
|
333
|
-
@property
|
|
334
|
-
def _id(self) -> str:
|
|
335
|
-
result = next((field for field in self.fields if field.is_id), None)
|
|
336
|
-
if result is None:
|
|
337
|
-
raise ValueError("Id field is required.")
|
|
338
|
-
return result.name
|
|
339
|
-
|
|
340
333
|
@property
|
|
341
334
|
def _user_check(self) -> str:
|
|
342
335
|
if self.username is not None:
|
|
343
336
|
return f"EXECUTE AS USER = '{self.username}'"
|
|
344
337
|
else:
|
|
345
338
|
return ""
|
|
346
|
-
|
|
347
|
-
@property
|
|
348
|
-
def _order(self) -> str:
|
|
349
|
-
ordering = [field.name for field in self.fields if field.is_ordered]
|
|
350
|
-
if len(ordering) > 0:
|
|
351
|
-
return "ORDER BY " + ", ".join(ordering)
|
|
352
|
-
else:
|
|
353
|
-
return ""
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
@dataclass(frozen=True)
|
|
357
|
-
class MsSqlField:
|
|
358
|
-
name: str
|
|
359
|
-
is_id: bool = False
|
|
360
|
-
is_ordered: bool = False
|
|
361
|
-
|
|
362
|
-
is_parent: bool = False
|
|
363
|
-
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,27 +151,32 @@ 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
|
-
|
|
153
|
-
|
|
164
|
+
insert_columns = ", ".join(
|
|
165
|
+
[field.name for field in self.fields if field.include_in_insert]
|
|
166
|
+
)
|
|
167
|
+
return_columns = ", ".join([field.name for field in self.fields])
|
|
168
|
+
placeholders = ", ".join(
|
|
169
|
+
[f":{key.name}" for key in self.fields if key.include_in_insert]
|
|
170
|
+
)
|
|
154
171
|
|
|
155
172
|
return DatabaseCommand(f"""
|
|
156
173
|
INSERT INTO {self.table_name.upper()} (
|
|
157
|
-
{
|
|
174
|
+
{insert_columns}
|
|
158
175
|
) VALUES (
|
|
159
176
|
{placeholders}
|
|
160
177
|
)
|
|
161
|
-
RETURNING {
|
|
162
|
-
""").with_data(self.
|
|
178
|
+
RETURNING {return_columns};
|
|
179
|
+
""").with_data(self.fields.with_fixed(self.formatter.dump(item)))
|
|
163
180
|
|
|
164
181
|
def select(self, item_id: str) -> DatabaseCommand:
|
|
165
182
|
columns = ", ".join([field.name for field in self.fields])
|
|
@@ -168,21 +185,23 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
168
185
|
SELECT
|
|
169
186
|
{columns}
|
|
170
187
|
FROM {self.table_name.upper()}
|
|
171
|
-
{self.
|
|
172
|
-
""").with_data(self.
|
|
188
|
+
{self.fields.where_statement(include_id=True)};
|
|
189
|
+
""").with_data(self.fields.with_fixed({self.fields.id: item_id}))
|
|
173
190
|
|
|
174
191
|
def select_duplicate(self, item: ItemT) -> DatabaseCommand:
|
|
175
192
|
raw = self.formatter.dump(item)
|
|
176
193
|
columns = ", ".join([field.name for field in self.fields])
|
|
177
194
|
|
|
178
|
-
duplicates = " AND ".join(
|
|
195
|
+
duplicates = " AND ".join(
|
|
196
|
+
[f"{field} = :{field}" for field in self.fields.composite]
|
|
197
|
+
)
|
|
179
198
|
|
|
180
199
|
return DatabaseCommand(f"""
|
|
181
200
|
SELECT
|
|
182
201
|
{columns}
|
|
183
202
|
FROM {self.table_name.upper()}
|
|
184
203
|
WHERE {duplicates};
|
|
185
|
-
""").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})
|
|
186
205
|
|
|
187
206
|
def select_all(self) -> DatabaseCommand:
|
|
188
207
|
columns = ", ".join([field.name for field in self.fields])
|
|
@@ -191,8 +210,9 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
191
210
|
SELECT
|
|
192
211
|
{columns}
|
|
193
212
|
FROM {self.table_name.capitalize()}
|
|
194
|
-
{self.
|
|
195
|
-
|
|
213
|
+
{self.fields.where_statement(include_id=False)}
|
|
214
|
+
{self.fields.order}
|
|
215
|
+
""").with_data(self.fields.with_fixed({}))
|
|
196
216
|
|
|
197
217
|
def update(self, item: ItemT) -> DatabaseCommand:
|
|
198
218
|
updates = ", ".join(
|
|
@@ -207,22 +227,22 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
207
227
|
UPDATE {self.table_name.upper()}
|
|
208
228
|
SET
|
|
209
229
|
{updates}
|
|
210
|
-
{self.
|
|
211
|
-
""").with_data(self.
|
|
230
|
+
{self.fields.where_statement(include_id=True)};
|
|
231
|
+
""").with_data(self.fields.with_fixed(self.formatter.dump(item)))
|
|
212
232
|
|
|
213
233
|
def delete(self, item_id: str) -> DatabaseCommand:
|
|
214
234
|
return DatabaseCommand(f"""
|
|
215
235
|
DELETE
|
|
216
236
|
FROM {self.table_name.upper()}
|
|
217
|
-
{self.
|
|
218
|
-
""").with_data(self.
|
|
237
|
+
{self.fields.where_statement(include_id=True)};
|
|
238
|
+
""").with_data(self.fields.with_fixed({self.fields.id: item_id}))
|
|
219
239
|
|
|
220
240
|
def delete_all(self) -> DatabaseCommand:
|
|
221
241
|
return DatabaseCommand(f"""
|
|
222
242
|
DELETE
|
|
223
243
|
FROM {self.table_name.upper()}
|
|
224
|
-
{self.
|
|
225
|
-
""").with_data(self.
|
|
244
|
+
{self.fields.where_statement(include_id=False)};
|
|
245
|
+
""").with_data(self.fields.with_fixed({}))
|
|
226
246
|
|
|
227
247
|
def load(self, data: dict[str, Any]) -> ItemT:
|
|
228
248
|
return self.formatter.load(data)
|
|
@@ -231,44 +251,6 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
231
251
|
raw = self.formatter.dump(item)
|
|
232
252
|
return ExistsError(item).with_duplicate(
|
|
233
253
|
lambda i: ",".join(
|
|
234
|
-
[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]
|
|
235
255
|
)
|
|
236
256
|
)
|
|
237
|
-
|
|
238
|
-
def _where_statement(self, include_id: bool = False) -> str:
|
|
239
|
-
result = next((field for field in self.fields if field.is_parent), None)
|
|
240
|
-
if result is None:
|
|
241
|
-
return f"WHERE {self._id} = :{self._id}" if include_id else ""
|
|
242
|
-
else:
|
|
243
|
-
statement = "WHERE " + result.name + " = :" + result.name
|
|
244
|
-
if include_id:
|
|
245
|
-
statement += f" AND {self._id} = :{self._id}"
|
|
246
|
-
return statement
|
|
247
|
-
|
|
248
|
-
def _data_with_replaced_parent(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
249
|
-
result = next((field for field in self.fields if field.is_parent), None)
|
|
250
|
-
if result is not None:
|
|
251
|
-
data[result.name] = result.fixed_value
|
|
252
|
-
return data
|
|
253
|
-
|
|
254
|
-
@property
|
|
255
|
-
def _id(self) -> str:
|
|
256
|
-
result = next((field for field in self.fields if field.is_id), None)
|
|
257
|
-
if result is None:
|
|
258
|
-
raise ValueError("Id field is required.")
|
|
259
|
-
return result.name
|
|
260
|
-
|
|
261
|
-
@property
|
|
262
|
-
def _composite(self) -> list[str]:
|
|
263
|
-
names = [field.name for field in self.fields if field.is_composite]
|
|
264
|
-
return [self._id] if len(names) == 0 else names
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
@dataclass(frozen=True)
|
|
268
|
-
class SqliteField:
|
|
269
|
-
name: str
|
|
270
|
-
is_id: bool = False
|
|
271
|
-
is_composite: bool = False
|
|
272
|
-
|
|
273
|
-
is_parent: bool = False
|
|
274
|
-
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
|