nadata 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.
- nadata/__init__.py +15 -0
- nadata/api.py +894 -0
- nadata/cli.py +445 -0
- nadata/console.py +486 -0
- nadata/core/__init__.py +0 -0
- nadata/core/database/__init__.py +38 -0
- nadata/core/database/builtins.py +87 -0
- nadata/core/database/database.py +958 -0
- nadata/core/database/dumper.py +285 -0
- nadata/core/database/entity_id.py +142 -0
- nadata/core/database/instance.py +706 -0
- nadata/core/database/network_index.py +1142 -0
- nadata/core/database/page_manager.py +175 -0
- nadata/core/database/persistence/__init__.py +28 -0
- nadata/core/database/persistence/binary_cells.py +1141 -0
- nadata/core/database/persistence/compaction.py +97 -0
- nadata/core/database/persistence/dumper.py +798 -0
- nadata/core/database/persistence/lazy_instances.py +1537 -0
- nadata/core/database/persistence/loader.py +424 -0
- nadata/core/database/persistence/record_store.py +228 -0
- nadata/core/database/persistence/serialization.py +605 -0
- nadata/core/database/query_engine.py +222 -0
- nadata/core/database/queryset.py +508 -0
- nadata/core/database/reconciliation.py +287 -0
- nadata/core/database/slotted_page.py +190 -0
- nadata/core/database/spatial_index.py +1080 -0
- nadata/core/expressions/__init__.py +44 -0
- nadata/core/expressions/autograd.py +2909 -0
- nadata/core/expressions/builtins.py +4673 -0
- nadata/core/expressions/expression.py +2658 -0
- nadata/core/expressions/io_builtins.py +972 -0
- nadata/core/expressions/scope.py +336 -0
- nadata/core/expressions/statement.py +953 -0
- nadata/core/struct/__init__.py +14 -0
- nadata/core/struct/struct.py +218 -0
- nadata/core/struct/struct_attribute.py +100 -0
- nadata/core/struct/struct_method.py +324 -0
- nadata/core/types/__init__.py +33 -0
- nadata/core/types/bits/__init__.py +0 -0
- nadata/core/types/bits/bits.py +686 -0
- nadata/core/types/home_types/__init__.py +19 -0
- nadata/core/types/home_types/color.py +102 -0
- nadata/core/types/home_types/coordinate.py +1008 -0
- nadata/core/types/home_types/datetime.py +362 -0
- nadata/core/types/home_types/geogrid.py +92 -0
- nadata/core/types/home_types/geometry.py +1223 -0
- nadata/core/types/home_types/graph.py +0 -0
- nadata/core/types/home_types/planet.py +359 -0
- nadata/core/types/home_types/sea_level.py +98 -0
- nadata/core/types/home_types/vec.py +0 -0
- nadata/core/types/types.py +1345 -0
- nadata/core/values/__init__.py +14 -0
- nadata/core/values/values.py +1568 -0
- nadata/parser/__init__.py +0 -0
- nadata/parser/expressions/__init__.py +6 -0
- nadata/parser/expressions/dependencies.py +318 -0
- nadata/parser/expressions/expression_parser.py +993 -0
- nadata/parser/expressions/splitter.py +98 -0
- nadata/parser/expressions/statement_parser.py +1425 -0
- nadata/parser/expressions/tokenizer.py +402 -0
- nadata/parser/expressions/type_parser.py +187 -0
- nadata/parser/nadata_loader/__init__.py +8 -0
- nadata/parser/nadata_loader/loader.py +951 -0
- nadata/parser/nastruct_parser/__init__.py +0 -0
- nadata/parser/nastruct_parser/declaration.py +105 -0
- nadata/parser/nastruct_parser/imports.py +164 -0
- nadata/parser/nastruct_parser/nastruct_parser.py +209 -0
- nadata/parser/nastruct_parser/parse_attributes.py +149 -0
- nadata/parser/nastruct_parser/parse_callables.py +436 -0
- nadata/parser/nastruct_parser/parse_expression.py +59 -0
- nadata/parser/nastruct_parser/parse_structs.py +206 -0
- nadata/parser/nastruct_parser/parser_state.py +84 -0
- nadata/parser/nastruct_parser/resolver.py +1412 -0
- nadata/parser/nastruct_parser/struct_body_state.py +50 -0
- nadata/parser/nastruct_parser/tmp_structs.py +156 -0
- nadata/parser/utils/__init__.py +20 -0
- nadata/parser/utils/brackets.py +252 -0
- nadata/parser/utils/comments_remove.py +149 -0
- nadata/parser/utils/identifiers.py +121 -0
- nadata/session.py +868 -0
- nadata-0.1.0.dist-info/METADATA +191 -0
- nadata-0.1.0.dist-info/RECORD +86 -0
- nadata-0.1.0.dist-info/WHEEL +5 -0
- nadata-0.1.0.dist-info/entry_points.txt +2 -0
- nadata-0.1.0.dist-info/licenses/LICENSE +21 -0
- nadata-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NaDataDatabase engine for Nadata.
|
|
3
|
+
|
|
4
|
+
Provides the high-performance database manager supporting typed tables,
|
|
5
|
+
globally unique 64-bit EntityIds, lazy paging, symbol registries,
|
|
6
|
+
and parallel/optimized queries.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Import Modules
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
from typing import Any, Callable, Optional
|
|
12
|
+
|
|
13
|
+
from nadata.core.values.values import Value
|
|
14
|
+
from nadata.core.struct.struct import Struct
|
|
15
|
+
from nadata.core.database.instance import StructInstance
|
|
16
|
+
from nadata.core.database.entity_id import EntityId
|
|
17
|
+
from nadata.core.database.queryset import QuerySet
|
|
18
|
+
from nadata.core.struct.struct_method import StructMethod
|
|
19
|
+
from nadata.core.database.page_manager import PageManager
|
|
20
|
+
from nadata.core.database.query_engine import QueryExecutor
|
|
21
|
+
from nadata.core.types.types import TypeBool, TypeFloat, TypeInt, TypeStr
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _indexed_raw_of(
|
|
29
|
+
struct: Struct,
|
|
30
|
+
instance: StructInstance,
|
|
31
|
+
attribute: str,
|
|
32
|
+
) -> Optional[Any]:
|
|
33
|
+
"""
|
|
34
|
+
Returns an instance's raw value for an indexed attribute.
|
|
35
|
+
|
|
36
|
+
Absent attributes with a schema default materialize through
|
|
37
|
+
get_attribute; attributes with neither a stored value nor a default
|
|
38
|
+
yield None (the row is skipped by the index — it can never match).
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
struct (Struct): Table schema.
|
|
42
|
+
instance (StructInstance): Row to read.
|
|
43
|
+
attribute (str): Indexed attribute name.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Optional[Any]: The raw value, or None when absent.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
if attribute in instance.attributes:
|
|
50
|
+
return instance.attributes[attribute].get()
|
|
51
|
+
|
|
52
|
+
attr_def: Any = struct.get_attribute(name=attribute)
|
|
53
|
+
|
|
54
|
+
if attr_def is not None and attr_def.has_default:
|
|
55
|
+
return instance.get_attribute(name=attribute).get()
|
|
56
|
+
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class StructTable:
|
|
61
|
+
"""
|
|
62
|
+
Storage table for all instances of a specific Struct schema.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
struct (Struct): Associated struct definition.
|
|
66
|
+
type_id (int): 16-bit type index in the database.
|
|
67
|
+
instances (dict[int, StructInstance]): Local id -> StructInstance mapping.
|
|
68
|
+
next_local_id (int): Next local id index to assign.
|
|
69
|
+
indexes (dict[str, dict[Any, set[int]]]): Optional per-attribute
|
|
70
|
+
hash indexes (raw value -> local ids), session scoped.
|
|
71
|
+
spatial_indexes (dict[str, Any]): Optional per-attribute cell
|
|
72
|
+
indexes over geometry attributes (SpatialIndex), session
|
|
73
|
+
scoped.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, struct: Struct, type_id: int) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Initializes the StructTable instance.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
struct (Struct): Struct schema.
|
|
82
|
+
type_id (int): 16-bit type index.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
self.struct: Struct = struct
|
|
86
|
+
self.type_id: int = type_id
|
|
87
|
+
self.instances: dict[int, StructInstance] = {}
|
|
88
|
+
self.next_local_id: int = 0
|
|
89
|
+
self.indexes: dict[str, dict[Any, set[int]]] = {}
|
|
90
|
+
|
|
91
|
+
# Session-scoped spatial indexes over geometry attributes
|
|
92
|
+
self.spatial_indexes: dict[str, Any] = {}
|
|
93
|
+
|
|
94
|
+
# Session-scoped routing networks involving this table (as the
|
|
95
|
+
# node or the edge side)
|
|
96
|
+
self.networks: list[Any] = []
|
|
97
|
+
|
|
98
|
+
def insert(self, attributes: Optional[dict[str, Value]] = None) -> StructInstance:
|
|
99
|
+
"""
|
|
100
|
+
Creates and stores a new StructInstance with a globally unique EntityId.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
attributes (Optional[dict[str, Value]], optional): Attribute values. Defaults to None.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
StructInstance: Newly created instance.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
local_id: int = self.next_local_id
|
|
110
|
+
self.next_local_id += 1
|
|
111
|
+
|
|
112
|
+
entity_id: EntityId = EntityId.from_parts(type_id=self.type_id, local_id=local_id)
|
|
113
|
+
|
|
114
|
+
instance: StructInstance = StructInstance(
|
|
115
|
+
struct=self.struct,
|
|
116
|
+
entity_id=entity_id,
|
|
117
|
+
attributes=attributes
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
self.instances[local_id] = instance
|
|
121
|
+
|
|
122
|
+
# Write-back hook for hash index maintenance
|
|
123
|
+
instance.table_ref = self
|
|
124
|
+
|
|
125
|
+
# New rows join any existing hash indexes (defaults included)
|
|
126
|
+
self.index_instance(instance=instance)
|
|
127
|
+
|
|
128
|
+
# New rows join any existing spatial indexes
|
|
129
|
+
self.spatial_index_instance(instance=instance)
|
|
130
|
+
|
|
131
|
+
for network in self.networks:
|
|
132
|
+
network.on_insert(instance=instance)
|
|
133
|
+
|
|
134
|
+
return instance
|
|
135
|
+
|
|
136
|
+
def get(self, local_id: int) -> Optional[StructInstance]:
|
|
137
|
+
"""
|
|
138
|
+
Retrieves an instance by local id.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
local_id (int): Local index.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Optional[StructInstance]: Stored instance or None.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
return self.instances.get(local_id)
|
|
148
|
+
|
|
149
|
+
def all(self) -> list[StructInstance]:
|
|
150
|
+
"""
|
|
151
|
+
Returns all instances stored in this table.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
list[StructInstance]: List of instances.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
return list(self.instances.values())
|
|
158
|
+
|
|
159
|
+
# --- Hash indexes (session scoped) ---
|
|
160
|
+
|
|
161
|
+
def create_index(self, attribute: str) -> None:
|
|
162
|
+
"""
|
|
163
|
+
Builds a hash index over a scalar attribute of this table.
|
|
164
|
+
|
|
165
|
+
Materializes every row once (lazy rows decode), then indexes
|
|
166
|
+
each instance's value for the attribute — schema defaults
|
|
167
|
+
included. Incremental maintenance happens through insert,
|
|
168
|
+
set_attribute, and removal.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
attribute (str): Declared scalar attribute name.
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
AttributeError: If the attribute is not declared.
|
|
175
|
+
TypeError: If the attribute is not a scalar
|
|
176
|
+
(Int / Float / Str / Bool).
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
attr_def: Any = self.struct.get_attribute(name=attribute)
|
|
180
|
+
|
|
181
|
+
if attr_def is None:
|
|
182
|
+
raise AttributeError(
|
|
183
|
+
f"Cannot index undeclared attribute '{attribute}' "
|
|
184
|
+
f"on Struct '{self.struct.name}'"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if not isinstance(
|
|
188
|
+
attr_def.attr_type, (TypeInt, TypeFloat, TypeStr, TypeBool)
|
|
189
|
+
):
|
|
190
|
+
raise TypeError(
|
|
191
|
+
f"Only scalar attributes (Int / Float / Str / Bool) are "
|
|
192
|
+
f"indexable, but '{self.struct.name}.{attribute}' is "
|
|
193
|
+
f"'{repr(attr_def.attr_type)}'"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Build from scratch: materialize lazy rows once
|
|
197
|
+
if hasattr(self.instances, "materialize_all"):
|
|
198
|
+
self.instances.materialize_all()
|
|
199
|
+
|
|
200
|
+
index: dict[Any, set[int]] = {}
|
|
201
|
+
|
|
202
|
+
for local_id, instance in dict.items(self.instances):
|
|
203
|
+
|
|
204
|
+
raw: Optional[Any] = _indexed_raw_of(
|
|
205
|
+
struct=self.struct, instance=instance, attribute=attribute
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if raw is None:
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
index.setdefault(raw, set()).add(local_id)
|
|
212
|
+
|
|
213
|
+
self.indexes[attribute] = index
|
|
214
|
+
|
|
215
|
+
def _index_add(self, attribute: str, raw: Any, local_id: int) -> None:
|
|
216
|
+
"""
|
|
217
|
+
Adds one local id under an indexed attribute value.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
attribute (str): Indexed attribute name.
|
|
221
|
+
raw (Any): Raw scalar value.
|
|
222
|
+
local_id (int): Local instance id.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
self.indexes[attribute].setdefault(raw, set()).add(local_id)
|
|
226
|
+
|
|
227
|
+
def _index_remove(self, attribute: str, raw: Any, local_id: int) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Removes one local id from an indexed attribute value.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
attribute (str): Indexed attribute name.
|
|
233
|
+
raw (Any): Previously indexed raw value.
|
|
234
|
+
local_id (int): Local instance id.
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
entries: Optional[set[int]] = self.indexes[attribute].get(raw)
|
|
238
|
+
|
|
239
|
+
if entries is not None:
|
|
240
|
+
entries.discard(local_id)
|
|
241
|
+
|
|
242
|
+
if not entries:
|
|
243
|
+
self.indexes[attribute].pop(raw, None)
|
|
244
|
+
|
|
245
|
+
def index_instance(self, instance: StructInstance) -> None:
|
|
246
|
+
"""
|
|
247
|
+
Indexes one instance under every existing index of this table.
|
|
248
|
+
|
|
249
|
+
Schema defaults materialize through get_attribute, so rows
|
|
250
|
+
created without explicit values are indexed too.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
instance (StructInstance): The instance to index.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
if not self.indexes:
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
for attribute in self.indexes:
|
|
260
|
+
|
|
261
|
+
raw: Optional[Any] = _indexed_raw_of(
|
|
262
|
+
struct=self.struct, instance=instance, attribute=attribute
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if raw is None:
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
self._index_add(
|
|
269
|
+
attribute=attribute,
|
|
270
|
+
raw=raw,
|
|
271
|
+
local_id=instance.entity_id.local_id,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def index_update(
|
|
275
|
+
self,
|
|
276
|
+
attribute: str,
|
|
277
|
+
old_raw: Optional[Any],
|
|
278
|
+
new_raw: Any,
|
|
279
|
+
local_id: int,
|
|
280
|
+
) -> None:
|
|
281
|
+
"""
|
|
282
|
+
Re-indexes one instance after an attribute write.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
attribute (str): The written attribute (must be indexed).
|
|
286
|
+
old_raw (Optional[Any]): Previously indexed raw value, or
|
|
287
|
+
None when the attribute had no indexed value.
|
|
288
|
+
new_raw (Any): The new raw value.
|
|
289
|
+
local_id (int): Local instance id.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
if old_raw is not None:
|
|
293
|
+
self._index_remove(
|
|
294
|
+
attribute=attribute, raw=old_raw, local_id=local_id
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
self._index_add(
|
|
298
|
+
attribute=attribute, raw=new_raw, local_id=local_id
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def index_remove_entity(self, local_id: int) -> None:
|
|
302
|
+
"""
|
|
303
|
+
Drops one local id from every index of this table.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
local_id (int): Local instance id of the removed entity.
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
for index in self.indexes.values():
|
|
310
|
+
|
|
311
|
+
for raw in list(index.keys()):
|
|
312
|
+
index[raw].discard(local_id)
|
|
313
|
+
|
|
314
|
+
empty: list[Any] = [
|
|
315
|
+
raw for raw, entries in index.items() if not entries
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
for raw in empty:
|
|
319
|
+
index.pop(raw, None)
|
|
320
|
+
|
|
321
|
+
entity_value: int = EntityId.from_parts(
|
|
322
|
+
type_id=self.type_id, local_id=local_id
|
|
323
|
+
).value
|
|
324
|
+
|
|
325
|
+
for spatial in self.spatial_indexes.values():
|
|
326
|
+
spatial.remove_instance(key=entity_value)
|
|
327
|
+
|
|
328
|
+
for network in self.networks:
|
|
329
|
+
network.on_remove(local_id=local_id, table=self)
|
|
330
|
+
|
|
331
|
+
# --- Spatial indexes (session scoped) ---
|
|
332
|
+
|
|
333
|
+
def create_spatial_index(
|
|
334
|
+
self,
|
|
335
|
+
attribute: str,
|
|
336
|
+
lod: int = 8,
|
|
337
|
+
parent_attribute: Optional[str] = None,
|
|
338
|
+
database: Optional[Any] = None,
|
|
339
|
+
) -> Any:
|
|
340
|
+
"""
|
|
341
|
+
Builds a cell index over a geometry attribute across this
|
|
342
|
+
struct's hierarchy.
|
|
343
|
+
|
|
344
|
+
Subclass tables (rows of structs inheriting this struct)
|
|
345
|
+
share the index: one hierarchy, one cell grid, global entity
|
|
346
|
+
id keys. The index registers on every involved table so
|
|
347
|
+
insert / write / removal hooks maintain it.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
attribute (str): Declared geometry attribute name.
|
|
351
|
+
lod (int, optional): Level of detail (cell edge =
|
|
352
|
+
360 / 2^lod degrees). Defaults to 8.
|
|
353
|
+
parent_attribute (Optional[str], optional): Self-reference
|
|
354
|
+
attribute enabling hierarchical locate() descent.
|
|
355
|
+
Defaults to None (flat).
|
|
356
|
+
database (Optional[Any], optional): The owning database,
|
|
357
|
+
for subclass table discovery. Defaults to None (the
|
|
358
|
+
base table only).
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Any: The built SpatialIndex.
|
|
362
|
+
|
|
363
|
+
Raises:
|
|
364
|
+
AttributeError: If the attribute is not declared.
|
|
365
|
+
TypeError: If the attribute is not a geometry type.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
from nadata.core.database.spatial_index import SpatialIndex
|
|
369
|
+
|
|
370
|
+
# A previous index over the same attribute anywhere in the
|
|
371
|
+
# hierarchy is replaced
|
|
372
|
+
for involved in self._hierarchy_tables(database=database):
|
|
373
|
+
involved.spatial_indexes.pop(attribute, None)
|
|
374
|
+
|
|
375
|
+
hierarchy_tables: list[Any] = self._hierarchy_tables(
|
|
376
|
+
database=database
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
index: Any = SpatialIndex(
|
|
380
|
+
table=self,
|
|
381
|
+
attribute=attribute,
|
|
382
|
+
lod=lod,
|
|
383
|
+
parent_attribute=parent_attribute,
|
|
384
|
+
tables=hierarchy_tables,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
for involved in hierarchy_tables:
|
|
388
|
+
involved.spatial_indexes[attribute] = index
|
|
389
|
+
|
|
390
|
+
return index
|
|
391
|
+
|
|
392
|
+
def _hierarchy_tables(
|
|
393
|
+
self,
|
|
394
|
+
database: Optional[Any] = None,
|
|
395
|
+
) -> list[Any]:
|
|
396
|
+
"""
|
|
397
|
+
Returns this table plus every subclass table.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
database (Optional[Any], optional): The owning database.
|
|
401
|
+
Defaults to None (base table only).
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
list[Any]: The hierarchy's StructTables, base first.
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
tables: list[Any] = [self]
|
|
408
|
+
|
|
409
|
+
if database is None:
|
|
410
|
+
return tables
|
|
411
|
+
|
|
412
|
+
for candidate in database.tables_by_struct_name.values():
|
|
413
|
+
if candidate is self:
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
walker: Any = candidate.struct.parent
|
|
417
|
+
|
|
418
|
+
while walker is not None:
|
|
419
|
+
if walker.name == self.struct.name:
|
|
420
|
+
tables.append(candidate)
|
|
421
|
+
|
|
422
|
+
break
|
|
423
|
+
|
|
424
|
+
walker = walker.parent
|
|
425
|
+
|
|
426
|
+
return tables
|
|
427
|
+
|
|
428
|
+
def spatial_index_instance(self, instance: Any) -> None:
|
|
429
|
+
"""
|
|
430
|
+
Indexes one instance under every existing spatial index of
|
|
431
|
+
this table (insert hook).
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
instance (Any): The instance to index.
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
if not self.spatial_indexes:
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
for index in self.spatial_indexes.values():
|
|
441
|
+
index.update_instance(instance=instance)
|
|
442
|
+
|
|
443
|
+
def create_network(self, edges_table: Any) -> Any:
|
|
444
|
+
"""
|
|
445
|
+
Builds a routing network over this table (nodes) and an edge
|
|
446
|
+
table, replacing any previous network for the same pair.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
edges_table (Any): The edge StructTable.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
Any: The NetworkIndex.
|
|
453
|
+
"""
|
|
454
|
+
|
|
455
|
+
from nadata.core.database.network_index import NetworkIndex
|
|
456
|
+
|
|
457
|
+
# Drop a previous network for the same pair from both tables
|
|
458
|
+
previous: list[Any] = [
|
|
459
|
+
existing
|
|
460
|
+
for existing in self.networks
|
|
461
|
+
if existing.edges_table is edges_table
|
|
462
|
+
]
|
|
463
|
+
|
|
464
|
+
for stale in previous:
|
|
465
|
+
self.networks.remove(stale)
|
|
466
|
+
edges_table.networks.remove(stale)
|
|
467
|
+
|
|
468
|
+
network: Any = NetworkIndex(nodes_table=self, edges_table=edges_table)
|
|
469
|
+
|
|
470
|
+
network.build()
|
|
471
|
+
|
|
472
|
+
self.networks.append(network)
|
|
473
|
+
edges_table.networks.append(network)
|
|
474
|
+
|
|
475
|
+
return network
|
|
476
|
+
|
|
477
|
+
def spatial_index_update(self, attribute: str, local_id: int) -> None:
|
|
478
|
+
"""
|
|
479
|
+
Re-indexes one instance after a geometry attribute write.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
attribute (str): The written attribute (must be indexed).
|
|
483
|
+
local_id (int): Local instance id.
|
|
484
|
+
"""
|
|
485
|
+
|
|
486
|
+
index: Optional[Any] = self.spatial_indexes.get(attribute)
|
|
487
|
+
|
|
488
|
+
if index is None:
|
|
489
|
+
return
|
|
490
|
+
|
|
491
|
+
instance: Optional[Any] = self.instances.get(local_id) if hasattr(
|
|
492
|
+
self.instances, "get"
|
|
493
|
+
) else None
|
|
494
|
+
|
|
495
|
+
if instance is None:
|
|
496
|
+
instance = self.get(local_id=local_id)
|
|
497
|
+
|
|
498
|
+
if instance is not None:
|
|
499
|
+
index.update_instance(instance=instance)
|
|
500
|
+
|
|
501
|
+
def lookup_by_index(
|
|
502
|
+
self,
|
|
503
|
+
attribute: str,
|
|
504
|
+
raw: Any,
|
|
505
|
+
) -> Optional[list[StructInstance]]:
|
|
506
|
+
"""
|
|
507
|
+
Resolves instances by indexed attribute equality in O(1).
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
attribute (str): Indexed attribute name.
|
|
511
|
+
raw (Any): Raw scalar value to match.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
Optional[list[StructInstance]]: Matching instances, or None
|
|
515
|
+
when the attribute has no index (caller falls back).
|
|
516
|
+
"""
|
|
517
|
+
|
|
518
|
+
index: Optional[dict[Any, set[int]]] = self.indexes.get(attribute)
|
|
519
|
+
|
|
520
|
+
if index is None:
|
|
521
|
+
return None
|
|
522
|
+
|
|
523
|
+
local_ids: Optional[set[int]] = index.get(raw)
|
|
524
|
+
|
|
525
|
+
if not local_ids:
|
|
526
|
+
return []
|
|
527
|
+
|
|
528
|
+
return [
|
|
529
|
+
self.instances[local_id]
|
|
530
|
+
for local_id in local_ids
|
|
531
|
+
if local_id in self.instances
|
|
532
|
+
]
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
class NaDataDatabase:
|
|
536
|
+
"""
|
|
537
|
+
Central database engine for Nadata instances.
|
|
538
|
+
|
|
539
|
+
Attributes:
|
|
540
|
+
page_manager (PageManager): Slotted page and cache manager.
|
|
541
|
+
tables_by_type_id (dict[int, StructTable]): Type ID -> StructTable registry.
|
|
542
|
+
tables_by_struct_name (dict[str, StructTable]): Struct name -> StructTable.
|
|
543
|
+
symbols (dict[str, EntityId]): Named symbol aliases (e.g. "tom" -> EntityId).
|
|
544
|
+
query_executor (QueryExecutor): Parallel query runner.
|
|
545
|
+
"""
|
|
546
|
+
|
|
547
|
+
def __init__(
|
|
548
|
+
self,
|
|
549
|
+
storage_path: Optional[str] = None,
|
|
550
|
+
max_cached_pages: int = 256
|
|
551
|
+
) -> None:
|
|
552
|
+
"""
|
|
553
|
+
Initializes the NaDataDatabase instance.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
storage_path (Optional[str], optional): Path to database file. Defaults to None.
|
|
557
|
+
max_cached_pages (int, optional): In-memory page limit. Defaults to 256.
|
|
558
|
+
"""
|
|
559
|
+
|
|
560
|
+
self.page_manager: PageManager = PageManager(
|
|
561
|
+
storage_path=storage_path,
|
|
562
|
+
max_cached_pages=max_cached_pages
|
|
563
|
+
)
|
|
564
|
+
self.storage_path: Optional[str] = storage_path
|
|
565
|
+
|
|
566
|
+
# Project root for the I/O builtins (the session folder); None
|
|
567
|
+
# resolves script paths against the current working directory
|
|
568
|
+
self.io_base: Optional[str] = None
|
|
569
|
+
|
|
570
|
+
self.tables_by_type_id: dict[int, StructTable] = {}
|
|
571
|
+
self.tables_by_struct_name: dict[str, StructTable] = {}
|
|
572
|
+
self.symbols: dict[str, EntityId] = {}
|
|
573
|
+
self.nids: dict[str, EntityId] = {}
|
|
574
|
+
self._nids_by_name: dict[str, list[tuple[str, EntityId]]] = {}
|
|
575
|
+
self._nid_by_entity: dict[int, tuple[str, str]] = {}
|
|
576
|
+
self.functions: dict[str, StructMethod] = {}
|
|
577
|
+
self.query_executor: QueryExecutor = QueryExecutor()
|
|
578
|
+
self.next_type_id: int = 0
|
|
579
|
+
|
|
580
|
+
# Database-wide settings (persisted in the meta document of .ndb files)
|
|
581
|
+
self.settings: dict[str, Any] = {}
|
|
582
|
+
|
|
583
|
+
def register_function(self, function: StructMethod) -> None:
|
|
584
|
+
"""
|
|
585
|
+
Registers a top-level schema function.
|
|
586
|
+
|
|
587
|
+
Args:
|
|
588
|
+
function (StructMethod): Function to register.
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
self.functions[function.name] = function
|
|
592
|
+
|
|
593
|
+
def register_struct(self, struct: Struct) -> StructTable:
|
|
594
|
+
|
|
595
|
+
"""
|
|
596
|
+
Registers a Struct schema into the database, allocating a 16-bit TypeId.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
struct (Struct): Schema to register.
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
StructTable: Created or existing storage table.
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
if struct.name in self.tables_by_struct_name:
|
|
606
|
+
return self.tables_by_struct_name[struct.name]
|
|
607
|
+
|
|
608
|
+
type_id: int = self.next_type_id
|
|
609
|
+
self.next_type_id += 1
|
|
610
|
+
|
|
611
|
+
table: StructTable = StructTable(struct=struct, type_id=type_id)
|
|
612
|
+
self.tables_by_type_id[type_id] = table
|
|
613
|
+
self.tables_by_struct_name[struct.name] = table
|
|
614
|
+
|
|
615
|
+
return table
|
|
616
|
+
|
|
617
|
+
def create_instance(
|
|
618
|
+
self,
|
|
619
|
+
struct: Struct,
|
|
620
|
+
attributes: Optional[dict[str, Value]] = None,
|
|
621
|
+
symbol: Optional[str] = None
|
|
622
|
+
) -> StructInstance:
|
|
623
|
+
"""
|
|
624
|
+
Creates and stores an instance, optionally binding a symbol name.
|
|
625
|
+
|
|
626
|
+
Args:
|
|
627
|
+
struct (Struct): Schema of the instance.
|
|
628
|
+
attributes (Optional[dict[str, Value]], optional): Field values. Defaults to None.
|
|
629
|
+
symbol (Optional[str], optional): Symbol alias (e.g. "tom"). Defaults to None.
|
|
630
|
+
|
|
631
|
+
Returns:
|
|
632
|
+
StructInstance: Created instance.
|
|
633
|
+
"""
|
|
634
|
+
|
|
635
|
+
table: StructTable = self.register_struct(struct=struct)
|
|
636
|
+
instance: StructInstance = table.insert(attributes=attributes)
|
|
637
|
+
|
|
638
|
+
if symbol is not None:
|
|
639
|
+
self.symbols[symbol] = instance.entity_id
|
|
640
|
+
|
|
641
|
+
return instance
|
|
642
|
+
|
|
643
|
+
def get_by_nid(self, struct_name: str, nid: str) -> Optional[StructInstance]:
|
|
644
|
+
"""
|
|
645
|
+
Retrieves an instance by its named id, or None when unknown.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
struct_name (str): Struct the nid lives in (per-struct
|
|
649
|
+
namespace).
|
|
650
|
+
nid (str): The named id.
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
Optional[StructInstance]: The instance, or None.
|
|
654
|
+
"""
|
|
655
|
+
|
|
656
|
+
entity_id: Optional[EntityId] = self.nids.get(
|
|
657
|
+
f"{struct_name}/{nid}"
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
if entity_id is not None:
|
|
661
|
+
return self.get_by_id(entity_id=entity_id)
|
|
662
|
+
|
|
663
|
+
# Subclass fallback: an instance of Country is also an
|
|
664
|
+
# AdminRegion; walk the registered namespaces by name
|
|
665
|
+
candidates: list[tuple[str, EntityId]] = (
|
|
666
|
+
self._nids_by_name.get(nid, [])
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
if not candidates:
|
|
670
|
+
return None
|
|
671
|
+
|
|
672
|
+
for candidate_name, candidate_id in candidates:
|
|
673
|
+
|
|
674
|
+
candidate_table: Optional[Any] = (
|
|
675
|
+
self.tables_by_struct_name.get(candidate_name)
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
if candidate_table is None:
|
|
679
|
+
continue
|
|
680
|
+
|
|
681
|
+
walker: Any = candidate_table.struct.parent
|
|
682
|
+
|
|
683
|
+
while walker is not None:
|
|
684
|
+
if walker.name == struct_name:
|
|
685
|
+
return self.get_by_id(entity_id=candidate_id)
|
|
686
|
+
|
|
687
|
+
walker = walker.parent
|
|
688
|
+
|
|
689
|
+
return None
|
|
690
|
+
|
|
691
|
+
def register_nid(
|
|
692
|
+
self,
|
|
693
|
+
struct_name: str,
|
|
694
|
+
nid: str,
|
|
695
|
+
entity_id: EntityId,
|
|
696
|
+
) -> None:
|
|
697
|
+
"""
|
|
698
|
+
Binds a named id to an entity in the struct's namespace.
|
|
699
|
+
|
|
700
|
+
Args:
|
|
701
|
+
struct_name (str): Struct the nid lives in.
|
|
702
|
+
nid (str): The named id.
|
|
703
|
+
entity_id (EntityId): The bound entity.
|
|
704
|
+
"""
|
|
705
|
+
|
|
706
|
+
key: str = f"{struct_name}/{nid}"
|
|
707
|
+
|
|
708
|
+
previous: Optional[EntityId] = self.nids.get(key)
|
|
709
|
+
|
|
710
|
+
self.nids[key] = entity_id
|
|
711
|
+
|
|
712
|
+
# Same-name index for the hierarchy fallback (kept
|
|
713
|
+
# incremental so lookups never scan the registry)
|
|
714
|
+
bucket: list[tuple[str, EntityId]] = (
|
|
715
|
+
self._nids_by_name.setdefault(nid, [])
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
if previous is not None:
|
|
719
|
+
for entry_index, (
|
|
720
|
+
entry_name,
|
|
721
|
+
entry_id,
|
|
722
|
+
) in enumerate(bucket):
|
|
723
|
+
if entry_name == struct_name:
|
|
724
|
+
bucket[entry_index] = (struct_name, entity_id)
|
|
725
|
+
|
|
726
|
+
break
|
|
727
|
+
|
|
728
|
+
if not any(
|
|
729
|
+
entry_name == struct_name for entry_name, _ in bucket
|
|
730
|
+
):
|
|
731
|
+
bucket.append((struct_name, entity_id))
|
|
732
|
+
|
|
733
|
+
self._nid_by_entity[entity_id.value] = (
|
|
734
|
+
struct_name,
|
|
735
|
+
nid,
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
def nid_of_entity(self, struct_name: str, entity_id: int) -> Optional[str]:
|
|
739
|
+
"""
|
|
740
|
+
Returns the nid of an entity, or None when anonymous.
|
|
741
|
+
|
|
742
|
+
Args:
|
|
743
|
+
struct_name (str): Struct name of the entity.
|
|
744
|
+
entity_id (int): Global 64-bit entity handle.
|
|
745
|
+
|
|
746
|
+
Returns:
|
|
747
|
+
Optional[str]: The named id, or None.
|
|
748
|
+
"""
|
|
749
|
+
|
|
750
|
+
mapped: Optional[tuple[str, str]] = self._nid_by_entity.get(
|
|
751
|
+
entity_id
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
if mapped is None or mapped[0] != struct_name:
|
|
755
|
+
return None
|
|
756
|
+
|
|
757
|
+
return mapped[1]
|
|
758
|
+
|
|
759
|
+
def drop_nids_of_entity(self, entity_id: int) -> None:
|
|
760
|
+
"""
|
|
761
|
+
Forgets every nid bound to an entity (removal bookkeeping).
|
|
762
|
+
|
|
763
|
+
Args:
|
|
764
|
+
entity_id (int): Global 64-bit entity handle.
|
|
765
|
+
"""
|
|
766
|
+
|
|
767
|
+
mapped: Optional[tuple[str, str]] = self._nid_by_entity.pop(
|
|
768
|
+
entity_id, None
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
if mapped is None:
|
|
772
|
+
return
|
|
773
|
+
|
|
774
|
+
dropped_nid: str = mapped[1]
|
|
775
|
+
dropped_struct: str = mapped[0]
|
|
776
|
+
|
|
777
|
+
self.nids.pop(f"{dropped_struct}/{dropped_nid}", None)
|
|
778
|
+
|
|
779
|
+
bucket: Optional[list[tuple[str, EntityId]]] = (
|
|
780
|
+
self._nids_by_name.get(dropped_nid)
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
if bucket is not None:
|
|
784
|
+
self._nids_by_name[dropped_nid] = [
|
|
785
|
+
entry
|
|
786
|
+
for entry in bucket
|
|
787
|
+
if entry[0] != dropped_struct
|
|
788
|
+
]
|
|
789
|
+
|
|
790
|
+
def nid_names(self, struct_name: str) -> list[str]:
|
|
791
|
+
"""
|
|
792
|
+
Returns the sorted nid strings of a struct's named instances.
|
|
793
|
+
|
|
794
|
+
Args:
|
|
795
|
+
struct_name (str): Struct name.
|
|
796
|
+
|
|
797
|
+
Returns:
|
|
798
|
+
list[str]: Sorted nids.
|
|
799
|
+
"""
|
|
800
|
+
|
|
801
|
+
prefix: str = f"{struct_name}/"
|
|
802
|
+
|
|
803
|
+
return sorted(
|
|
804
|
+
key[len(prefix):]
|
|
805
|
+
for key in self.nids
|
|
806
|
+
if key.startswith(prefix)
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
def remove_instance(self, instance: StructInstance) -> None:
|
|
810
|
+
"""
|
|
811
|
+
Removes an instance from its table and the decoder caches.
|
|
812
|
+
|
|
813
|
+
Purely in-memory: persistence of the removal requires a
|
|
814
|
+
subsequent tombstone append (remove_from_db / flush with
|
|
815
|
+
tombstone support). The entity id is never reused.
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
instance (StructInstance): The instance to remove.
|
|
819
|
+
"""
|
|
820
|
+
|
|
821
|
+
table: Optional[StructTable] = self.tables_by_type_id.get(
|
|
822
|
+
instance.entity_id.type_id
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
if table is None:
|
|
826
|
+
return
|
|
827
|
+
|
|
828
|
+
self.drop_nids_of_entity(entity_id=instance.entity_id.value)
|
|
829
|
+
|
|
830
|
+
table.index_remove_entity(local_id=instance.entity_id.local_id)
|
|
831
|
+
|
|
832
|
+
if (
|
|
833
|
+
table.instances.__contains__(instance.entity_id.local_id)
|
|
834
|
+
and table.instances[instance.entity_id.local_id] is instance
|
|
835
|
+
):
|
|
836
|
+
del table.instances[instance.entity_id.local_id]
|
|
837
|
+
|
|
838
|
+
decoder: Any = getattr(table.instances, "decoder", None)
|
|
839
|
+
|
|
840
|
+
if decoder is not None:
|
|
841
|
+
decoder.remove_entity(entity_id=instance.entity_id.value)
|
|
842
|
+
|
|
843
|
+
def flush(self) -> int:
|
|
844
|
+
"""
|
|
845
|
+
Writes all dirty instances to the database file as delta rows.
|
|
846
|
+
|
|
847
|
+
Returns:
|
|
848
|
+
int: Number of instances written (0 if there is no file).
|
|
849
|
+
"""
|
|
850
|
+
|
|
851
|
+
if self.storage_path is None:
|
|
852
|
+
return 0
|
|
853
|
+
|
|
854
|
+
from nadata.core.database.persistence.dumper import flush_dirty_instances
|
|
855
|
+
|
|
856
|
+
return flush_dirty_instances(database=self, storage_path=self.storage_path)
|
|
857
|
+
|
|
858
|
+
def clear_cache(self) -> int:
|
|
859
|
+
"""
|
|
860
|
+
Evicts all clean, unpinned instances from the decode caches.
|
|
861
|
+
|
|
862
|
+
Returns:
|
|
863
|
+
int: Number of instances evicted (0 if there is no file).
|
|
864
|
+
"""
|
|
865
|
+
|
|
866
|
+
if self.storage_path is None:
|
|
867
|
+
return 0
|
|
868
|
+
|
|
869
|
+
evicted: int = 0
|
|
870
|
+
|
|
871
|
+
for table in self.tables_by_type_id.values():
|
|
872
|
+
|
|
873
|
+
decoder: Any = getattr(table.instances, "decoder", None)
|
|
874
|
+
|
|
875
|
+
if decoder is not None:
|
|
876
|
+
evicted += decoder.evict_clean()
|
|
877
|
+
|
|
878
|
+
return evicted
|
|
879
|
+
|
|
880
|
+
def get_by_id(self, entity_id: EntityId | int) -> Optional[StructInstance]:
|
|
881
|
+
"""
|
|
882
|
+
Retrieves an instance by its globally unique EntityId in O(1).
|
|
883
|
+
|
|
884
|
+
Args:
|
|
885
|
+
entity_id (EntityId | int): EntityId or raw 64-bit int.
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
Optional[StructInstance]: Stored instance or None.
|
|
889
|
+
"""
|
|
890
|
+
|
|
891
|
+
resolved_id: EntityId = (
|
|
892
|
+
entity_id if isinstance(entity_id, EntityId) else EntityId(entity_id)
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
table: Optional[StructTable] = self.tables_by_type_id.get(resolved_id.type_id)
|
|
896
|
+
|
|
897
|
+
if table is None:
|
|
898
|
+
return None
|
|
899
|
+
|
|
900
|
+
return table.get(local_id=resolved_id.local_id)
|
|
901
|
+
|
|
902
|
+
def get_by_symbol(self, name: str) -> Optional[StructInstance]:
|
|
903
|
+
"""
|
|
904
|
+
Retrieves an instance by its named symbol alias.
|
|
905
|
+
|
|
906
|
+
Args:
|
|
907
|
+
name (str): Symbol alias (e.g. "tom").
|
|
908
|
+
|
|
909
|
+
Returns:
|
|
910
|
+
Optional[StructInstance]: Stored instance or None.
|
|
911
|
+
"""
|
|
912
|
+
|
|
913
|
+
entity_id: Optional[EntityId] = self.symbols.get(name)
|
|
914
|
+
|
|
915
|
+
if entity_id is None:
|
|
916
|
+
return None
|
|
917
|
+
|
|
918
|
+
return self.get_by_id(entity_id=entity_id)
|
|
919
|
+
|
|
920
|
+
def query(
|
|
921
|
+
self,
|
|
922
|
+
struct: Struct,
|
|
923
|
+
predicate: Optional[Callable[[StructInstance], bool]] = None,
|
|
924
|
+
include_subtypes: bool = True
|
|
925
|
+
) -> QuerySet:
|
|
926
|
+
"""
|
|
927
|
+
Queries instances of a Struct with optional parallel predicate filtering.
|
|
928
|
+
|
|
929
|
+
Args:
|
|
930
|
+
struct (Struct): Base struct type to query.
|
|
931
|
+
predicate (Optional[Callable[[StructInstance], bool]], optional): Predicate filter.
|
|
932
|
+
Defaults to None.
|
|
933
|
+
include_subtypes (bool, optional): Include derived structs. Defaults to True.
|
|
934
|
+
|
|
935
|
+
Returns:
|
|
936
|
+
QuerySet: Matching instances in a chained QuerySet.
|
|
937
|
+
"""
|
|
938
|
+
|
|
939
|
+
candidates: list[StructInstance] = []
|
|
940
|
+
|
|
941
|
+
# Gather tables matching struct or inheriting from it
|
|
942
|
+
for table in self.tables_by_type_id.values():
|
|
943
|
+
if table.struct == struct:
|
|
944
|
+
candidates.extend(table.all())
|
|
945
|
+
elif include_subtypes and table.struct.is_subclass_of(other=struct):
|
|
946
|
+
candidates.extend(table.all())
|
|
947
|
+
|
|
948
|
+
queryset: QuerySet = QuerySet(
|
|
949
|
+
struct=struct,
|
|
950
|
+
instances=candidates,
|
|
951
|
+
database=self,
|
|
952
|
+
query_executor=self.query_executor
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
if predicate is not None:
|
|
956
|
+
return queryset.where(predicate=predicate)
|
|
957
|
+
|
|
958
|
+
return queryset
|