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.
Files changed (86) hide show
  1. nadata/__init__.py +15 -0
  2. nadata/api.py +894 -0
  3. nadata/cli.py +445 -0
  4. nadata/console.py +486 -0
  5. nadata/core/__init__.py +0 -0
  6. nadata/core/database/__init__.py +38 -0
  7. nadata/core/database/builtins.py +87 -0
  8. nadata/core/database/database.py +958 -0
  9. nadata/core/database/dumper.py +285 -0
  10. nadata/core/database/entity_id.py +142 -0
  11. nadata/core/database/instance.py +706 -0
  12. nadata/core/database/network_index.py +1142 -0
  13. nadata/core/database/page_manager.py +175 -0
  14. nadata/core/database/persistence/__init__.py +28 -0
  15. nadata/core/database/persistence/binary_cells.py +1141 -0
  16. nadata/core/database/persistence/compaction.py +97 -0
  17. nadata/core/database/persistence/dumper.py +798 -0
  18. nadata/core/database/persistence/lazy_instances.py +1537 -0
  19. nadata/core/database/persistence/loader.py +424 -0
  20. nadata/core/database/persistence/record_store.py +228 -0
  21. nadata/core/database/persistence/serialization.py +605 -0
  22. nadata/core/database/query_engine.py +222 -0
  23. nadata/core/database/queryset.py +508 -0
  24. nadata/core/database/reconciliation.py +287 -0
  25. nadata/core/database/slotted_page.py +190 -0
  26. nadata/core/database/spatial_index.py +1080 -0
  27. nadata/core/expressions/__init__.py +44 -0
  28. nadata/core/expressions/autograd.py +2909 -0
  29. nadata/core/expressions/builtins.py +4673 -0
  30. nadata/core/expressions/expression.py +2658 -0
  31. nadata/core/expressions/io_builtins.py +972 -0
  32. nadata/core/expressions/scope.py +336 -0
  33. nadata/core/expressions/statement.py +953 -0
  34. nadata/core/struct/__init__.py +14 -0
  35. nadata/core/struct/struct.py +218 -0
  36. nadata/core/struct/struct_attribute.py +100 -0
  37. nadata/core/struct/struct_method.py +324 -0
  38. nadata/core/types/__init__.py +33 -0
  39. nadata/core/types/bits/__init__.py +0 -0
  40. nadata/core/types/bits/bits.py +686 -0
  41. nadata/core/types/home_types/__init__.py +19 -0
  42. nadata/core/types/home_types/color.py +102 -0
  43. nadata/core/types/home_types/coordinate.py +1008 -0
  44. nadata/core/types/home_types/datetime.py +362 -0
  45. nadata/core/types/home_types/geogrid.py +92 -0
  46. nadata/core/types/home_types/geometry.py +1223 -0
  47. nadata/core/types/home_types/graph.py +0 -0
  48. nadata/core/types/home_types/planet.py +359 -0
  49. nadata/core/types/home_types/sea_level.py +98 -0
  50. nadata/core/types/home_types/vec.py +0 -0
  51. nadata/core/types/types.py +1345 -0
  52. nadata/core/values/__init__.py +14 -0
  53. nadata/core/values/values.py +1568 -0
  54. nadata/parser/__init__.py +0 -0
  55. nadata/parser/expressions/__init__.py +6 -0
  56. nadata/parser/expressions/dependencies.py +318 -0
  57. nadata/parser/expressions/expression_parser.py +993 -0
  58. nadata/parser/expressions/splitter.py +98 -0
  59. nadata/parser/expressions/statement_parser.py +1425 -0
  60. nadata/parser/expressions/tokenizer.py +402 -0
  61. nadata/parser/expressions/type_parser.py +187 -0
  62. nadata/parser/nadata_loader/__init__.py +8 -0
  63. nadata/parser/nadata_loader/loader.py +951 -0
  64. nadata/parser/nastruct_parser/__init__.py +0 -0
  65. nadata/parser/nastruct_parser/declaration.py +105 -0
  66. nadata/parser/nastruct_parser/imports.py +164 -0
  67. nadata/parser/nastruct_parser/nastruct_parser.py +209 -0
  68. nadata/parser/nastruct_parser/parse_attributes.py +149 -0
  69. nadata/parser/nastruct_parser/parse_callables.py +436 -0
  70. nadata/parser/nastruct_parser/parse_expression.py +59 -0
  71. nadata/parser/nastruct_parser/parse_structs.py +206 -0
  72. nadata/parser/nastruct_parser/parser_state.py +84 -0
  73. nadata/parser/nastruct_parser/resolver.py +1412 -0
  74. nadata/parser/nastruct_parser/struct_body_state.py +50 -0
  75. nadata/parser/nastruct_parser/tmp_structs.py +156 -0
  76. nadata/parser/utils/__init__.py +20 -0
  77. nadata/parser/utils/brackets.py +252 -0
  78. nadata/parser/utils/comments_remove.py +149 -0
  79. nadata/parser/utils/identifiers.py +121 -0
  80. nadata/session.py +868 -0
  81. nadata-0.1.0.dist-info/METADATA +191 -0
  82. nadata-0.1.0.dist-info/RECORD +86 -0
  83. nadata-0.1.0.dist-info/WHEEL +5 -0
  84. nadata-0.1.0.dist-info/entry_points.txt +2 -0
  85. nadata-0.1.0.dist-info/licenses/LICENSE +21 -0
  86. 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