cjm-capability-graph-sqlite 0.0.30__tar.gz → 0.0.31__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.
- {cjm_capability_graph_sqlite-0.0.30/cjm_capability_graph_sqlite.egg-info → cjm_capability_graph_sqlite-0.0.31}/PKG-INFO +1 -1
- cjm_capability_graph_sqlite-0.0.31/cjm_capability_graph_sqlite/__init__.py +1 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/cjm_capability_graph_sqlite/capability.py +34 -8
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31/cjm_capability_graph_sqlite.egg-info}/PKG-INFO +1 -1
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/tests/test_capability.py +24 -2
- cjm_capability_graph_sqlite-0.0.30/cjm_capability_graph_sqlite/__init__.py +0 -1
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/LICENSE +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/MANIFEST.in +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/README.md +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/cjm_capability_graph_sqlite/query_translation.py +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/cjm_capability_graph_sqlite.egg-info/SOURCES.txt +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/cjm_capability_graph_sqlite.egg-info/dependency_links.txt +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/cjm_capability_graph_sqlite.egg-info/requires.txt +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/cjm_capability_graph_sqlite.egg-info/top_level.txt +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/pyproject.toml +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/setup.cfg +0 -0
- {cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/tests/test_query_translation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cjm-capability-graph-sqlite
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.31
|
|
4
4
|
Summary: A local, file-backed context-graph storage capability for the cjm-substrate runtime that implements graph storage, traversal, and querying using SQLite.
|
|
5
5
|
Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.31"
|
|
@@ -235,7 +235,7 @@ class SQLiteGraphCapability(ToolCapability):
|
|
|
235
235
|
# correlated-probe plans pick the WRONG composite index (the
|
|
236
236
|
# target-side probe scans a whole document's PART_OF rows per
|
|
237
237
|
# outer row: 5s; source-side probe: 8ms at corpus scale). They are
|
|
238
|
-
# kept by `_writing`'s
|
|
238
|
+
# kept by `_writing`'s statistics rule at the close of every write,
|
|
239
239
|
# this one included (a new index is analyzed there) -- no longer a
|
|
240
240
|
# full ANALYZE per open, O(db) on every read (finding ff3ede3d).
|
|
241
241
|
|
|
@@ -261,22 +261,48 @@ class SQLiteGraphCapability(ToolCapability):
|
|
|
261
261
|
error), then keep the planner's statistics current, then close.
|
|
262
262
|
|
|
263
263
|
Statistics are written on the paths that change data, never on an open (finding
|
|
264
|
-
ff3ede3d: a read paid a full ANALYZE and took the write lock)
|
|
265
|
-
analyzes a table only when
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
264
|
+
ff3ede3d: a read paid a full ANALYZE and took the write lock): `_refresh_statistics`
|
|
265
|
+
analyzes a table only when its statistics are missing or stale, so a steady-state write
|
|
266
|
+
pays a ~0.01 ms check and a fresh rebuild db gains statistics as it fills. Guarded:
|
|
267
|
+
statistics are an OPTIMIZATION — a corrupted DB must still answer integrity_check (the
|
|
268
|
+
designed G3 gate)."""
|
|
269
269
|
con = self._connect()
|
|
270
270
|
try:
|
|
271
271
|
with con:
|
|
272
272
|
yield con
|
|
273
273
|
try:
|
|
274
|
-
|
|
274
|
+
self._refresh_statistics(con)
|
|
275
275
|
except sqlite3.DatabaseError as e:
|
|
276
|
-
logging.warning(f"
|
|
276
|
+
logging.warning(f"statistics skipped (run integrity_check): {e}")
|
|
277
277
|
finally:
|
|
278
278
|
con.close()
|
|
279
279
|
|
|
280
|
+
def _refresh_statistics(
|
|
281
|
+
self,
|
|
282
|
+
con: sqlite3.Connection, # A write connection, after its unit of work committed
|
|
283
|
+
) -> None:
|
|
284
|
+
"""Analyze each graph table whose planner statistics are missing or stale: an index
|
|
285
|
+
with no sqlite_stat1 row (never analyzed, or created since), or a size ~10x the
|
|
286
|
+
analyzed one (max(rowid), an O(log n) estimate). One rule on every SQLite: PRAGMA
|
|
287
|
+
optimize analyzes a never-analyzed table only from SQLite 3.46 on, so on 3.45 (CI's
|
|
288
|
+
Ubuntu 24.04) the write path kept no statistics at all -- the guard of the 162 s
|
|
289
|
+
planner regression gone."""
|
|
290
|
+
for table in ("nodes", "edges"):
|
|
291
|
+
rows = con.execute(f"SELECT max(rowid) FROM {table}").fetchone()[0] or 0
|
|
292
|
+
if not rows:
|
|
293
|
+
continue
|
|
294
|
+
indexes = {r[0] for r in con.execute(
|
|
295
|
+
"SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = ?", (table,))}
|
|
296
|
+
try:
|
|
297
|
+
stats = dict(con.execute(
|
|
298
|
+
"SELECT idx, stat FROM sqlite_stat1 WHERE tbl = ?", (table,)).fetchall())
|
|
299
|
+
except sqlite3.OperationalError: # no sqlite_stat1 yet: never analyzed
|
|
300
|
+
stats = {}
|
|
301
|
+
analyzed = max((int(str(s).split()[0]) for s in stats.values() if s), default=0)
|
|
302
|
+
if indexes - set(stats) or rows >= 10 * max(analyzed, 1):
|
|
303
|
+
con.execute(f"ANALYZE {table};")
|
|
304
|
+
con.commit()
|
|
305
|
+
|
|
280
306
|
# -------------------------------------------------------------------------
|
|
281
307
|
# Helpers
|
|
282
308
|
# -------------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cjm-capability-graph-sqlite
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.31
|
|
4
4
|
Summary: A local, file-backed context-graph storage capability for the cjm-substrate runtime that implements graph storage, traversal, and querying using SQLite.
|
|
5
5
|
Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
|
|
6
6
|
License: Apache-2.0
|
{cjm_capability_graph_sqlite-0.0.30 → cjm_capability_graph_sqlite-0.0.31}/tests/test_capability.py
RENAMED
|
@@ -140,8 +140,8 @@ def test_sg41_wal_query_guard_merge_readonly(tmp_path):
|
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
def test_reads_and_opens_never_write_writes_keep_statistics(tmp_path):
|
|
143
|
-
# Finding ff3ede3d: statistics are written by the paths that change data (
|
|
144
|
-
#
|
|
143
|
+
# Finding ff3ede3d: statistics are written by the paths that change data (the
|
|
144
|
+
# statistics rule at the close of each write), so an open or a read commits nothing -- an
|
|
145
145
|
# observer's data_version moves only when ANOTHER connection commits.
|
|
146
146
|
db = str(tmp_path / "stats.db")
|
|
147
147
|
w = SQLiteGraphCapability()
|
|
@@ -167,6 +167,28 @@ def test_reads_and_opens_never_write_writes_keep_statistics(tmp_path):
|
|
|
167
167
|
observer.close()
|
|
168
168
|
|
|
169
169
|
|
|
170
|
+
def test_statistics_follow_growth_on_every_sqlite(tmp_path):
|
|
171
|
+
# The write path's own rule, never PRAGMA optimize's version-specific one (SQLite < 3.46
|
|
172
|
+
# never analyzed a fresh table, so CI kept no statistics): the first write analyzes, a
|
|
173
|
+
# write that leaves the table ~10x its analyzed size re-analyzes, a small one does not.
|
|
174
|
+
db = str(tmp_path / "grow.db")
|
|
175
|
+
cap = SQLiteGraphCapability()
|
|
176
|
+
cap.initialize({"db_path": db})
|
|
177
|
+
|
|
178
|
+
def analyzed_rows():
|
|
179
|
+
stats = sqlite3.connect(db).execute(
|
|
180
|
+
"SELECT stat FROM sqlite_stat1 WHERE tbl = 'nodes' AND idx = 'idx_nodes_label_id'").fetchone()
|
|
181
|
+
return int(stats[0].split()[0])
|
|
182
|
+
|
|
183
|
+
cap.add_nodes([GraphNode(id=f"a{i:03d}", label="Doc", properties={}) for i in range(5)])
|
|
184
|
+
assert analyzed_rows() == 5
|
|
185
|
+
cap.add_nodes([GraphNode(id=f"b{i:03d}", label="Doc", properties={}) for i in range(10)])
|
|
186
|
+
assert analyzed_rows() == 5 # 15 rows: under 10x, the statistics stand
|
|
187
|
+
cap.add_nodes([GraphNode(id=f"c{i:03d}", label="Doc", properties={}) for i in range(40)])
|
|
188
|
+
assert analyzed_rows() == 55 # 55 rows: 10x the analyzed 5, re-analyzed
|
|
189
|
+
cap.cleanup()
|
|
190
|
+
|
|
191
|
+
|
|
170
192
|
def test_typed_surface_smoke(cap):
|
|
171
193
|
doc = str(uuid.uuid4())
|
|
172
194
|
segs = [str(uuid.uuid4()) for _ in range(3)]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.30"
|
|
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
|