lorebase-store 0.2.3__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.
- lorebase_store/__init__.py +1 -0
- lorebase_store/_migrations/env.py +106 -0
- lorebase_store/_migrations/script.py.mako +27 -0
- lorebase_store/_migrations/versions/0001_initial_schema.py +1071 -0
- lorebase_store/_migrations/versions/0002_merge_attempt_details.py +74 -0
- lorebase_store/_migrations/versions/0003_webhooks.py +127 -0
- lorebase_store/_migrations/versions/0004_defer_revision_references.py +38 -0
- lorebase_store/_migrations/versions/0005_security_definer_reads.py +396 -0
- lorebase_store/_migrations/versions/0006_api_key_usage.py +48 -0
- lorebase_store/_migrations/versions/0007_document_extraction.py +154 -0
- lorebase_store/_migrations/versions/0008_definer_argument_checks.py +178 -0
- lorebase_store/_migrations/versions/0009_api_key_networks.py +33 -0
- lorebase_store/_migrations/versions/0010_overlay_branches.py +582 -0
- lorebase_store/_migrations/versions/0011_branch_scoped_search_index.py +229 -0
- lorebase_store/_migrations/versions/0012_overlay_neighborhood_lookups.py +185 -0
- lorebase_store/_migrations/versions/0013_web_sessions.py +72 -0
- lorebase_store/_migrations/versions/0014_embedding_jobs.py +70 -0
- lorebase_store/_migrations/versions/0015_append_only_audit_log.py +83 -0
- lorebase_store/_migrations/versions/0016_sealed_webhook_keys.py +55 -0
- lorebase_store/_migrations/versions/0017_background_branch_copies.py +77 -0
- lorebase_store/_migrations/versions/0018_search_lexicon.py +477 -0
- lorebase_store/_migrations/versions/0019_vault_bindings.py +56 -0
- lorebase_store/_migrations/versions/0020_outbox_consumers.py +150 -0
- lorebase_store/_migrations/versions/0021_schema_suggestions.py +114 -0
- lorebase_store/_migrations/versions/0022_read_audit_and_shared_rate_limits.py +133 -0
- lorebase_store/_migrations/versions/0023_agent_trace.py +96 -0
- lorebase_store/_migrations/versions/0024_embedding_template_keys.py +205 -0
- lorebase_store/_migrations/versions/0025_snapshot_refs.py +35 -0
- lorebase_store/_migrations/versions/0026_plain_reverse_link_index.py +112 -0
- lorebase_store/_migrations/versions/0027_vault_rotation_and_tombstones.py +145 -0
- lorebase_store/_migrations/versions/0028_operation_deferred_events.py +71 -0
- lorebase_store/_migrations/versions/0029_agent_trace_sessions.py +108 -0
- lorebase_store/_migrations/versions/0030_path_fanout.py +31 -0
- lorebase_store/_migrations/versions/0031_branch_builds.py +91 -0
- lorebase_store/_migrations/versions/0032_lineage_revisions.py +35 -0
- lorebase_store/_migrations/versions/0033_vault_moved_tokens.py +30 -0
- lorebase_store/_migrations/versions/0034_inference_endpoint_modes.py +73 -0
- lorebase_store/_migrations/versions/0035_search_truncation.py +104 -0
- lorebase_store/_migrations/versions/0036_lineage_maps.py +325 -0
- lorebase_store/_migrations/versions/0037_bm25_search.py +346 -0
- lorebase_store/auth/__init__.py +52 -0
- lorebase_store/auth/api_keys.py +161 -0
- lorebase_store/auth/audit.py +382 -0
- lorebase_store/auth/checkpoints.py +185 -0
- lorebase_store/auth/grants.py +161 -0
- lorebase_store/auth/principals.py +158 -0
- lorebase_store/auth/scope.py +70 -0
- lorebase_store/auth/sessions.py +153 -0
- lorebase_store/auth/unit.py +53 -0
- lorebase_store/auth/usage.py +70 -0
- lorebase_store/blobs.py +73 -0
- lorebase_store/bm25.py +346 -0
- lorebase_store/bulk.py +147 -0
- lorebase_store/bundle.py +1830 -0
- lorebase_store/changefeed.py +261 -0
- lorebase_store/commit/__init__.py +42 -0
- lorebase_store/commit/changes.py +67 -0
- lorebase_store/commit/errors.py +60 -0
- lorebase_store/commit/migrations.py +335 -0
- lorebase_store/commit/pipeline.py +754 -0
- lorebase_store/commit/schema.py +123 -0
- lorebase_store/db.py +58 -0
- lorebase_store/definer.py +50 -0
- lorebase_store/digests.py +99 -0
- lorebase_store/embeddings.py +444 -0
- lorebase_store/endpoint_modes.py +59 -0
- lorebase_store/extraction/__init__.py +61 -0
- lorebase_store/extraction/items.py +187 -0
- lorebase_store/extraction/materialize.py +364 -0
- lorebase_store/extraction/proposal.py +65 -0
- lorebase_store/extraction/review.py +168 -0
- lorebase_store/gc.py +970 -0
- lorebase_store/indexes.py +1033 -0
- lorebase_store/jobs.py +651 -0
- lorebase_store/keyring.py +72 -0
- lorebase_store/layout.py +335 -0
- lorebase_store/materialize/__init__.py +103 -0
- lorebase_store/materialize/apply.py +436 -0
- lorebase_store/materialize/background.py +1184 -0
- lorebase_store/materialize/branch.py +268 -0
- lorebase_store/materialize/build.py +536 -0
- lorebase_store/materialize/derived.py +540 -0
- lorebase_store/materialize/lineage_backfill.py +502 -0
- lorebase_store/materialize/links.py +266 -0
- lorebase_store/materialize/overlay.py +292 -0
- lorebase_store/materialize/paths.py +315 -0
- lorebase_store/materialize/rebuild.py +326 -0
- lorebase_store/materialize/rows.py +153 -0
- lorebase_store/materialize/sensitivity.py +66 -0
- lorebase_store/materialize/snapshot.py +205 -0
- lorebase_store/materialize/stale.py +444 -0
- lorebase_store/materialize/verify.py +321 -0
- lorebase_store/merging/__init__.py +39 -0
- lorebase_store/merging/attempts.py +334 -0
- lorebase_store/merging/lineage.py +123 -0
- lorebase_store/merging/service.py +652 -0
- lorebase_store/migrations.py +133 -0
- lorebase_store/models/__init__.py +136 -0
- lorebase_store/models/base.py +64 -0
- lorebase_store/models/enums.py +132 -0
- lorebase_store/models/governance.py +268 -0
- lorebase_store/models/ingestion.py +288 -0
- lorebase_store/models/materialized.py +277 -0
- lorebase_store/models/proposals.py +195 -0
- lorebase_store/models/queues.py +87 -0
- lorebase_store/models/tracing.py +116 -0
- lorebase_store/models/versioning.py +222 -0
- lorebase_store/models/webhooks.py +96 -0
- lorebase_store/nodestore.py +384 -0
- lorebase_store/objectstore.py +162 -0
- lorebase_store/outbox_events.py +166 -0
- lorebase_store/profiling/__init__.py +58 -0
- lorebase_store/profiling/delimited.py +109 -0
- lorebase_store/profiling/postgres.py +211 -0
- lorebase_store/profiling/record_files.py +127 -0
- lorebase_store/profiling/records.py +92 -0
- lorebase_store/profiling/sql_databases.py +298 -0
- lorebase_store/ratelimit.py +129 -0
- lorebase_store/reads.py +1491 -0
- lorebase_store/replicas.py +434 -0
- lorebase_store/repositories/__init__.py +152 -0
- lorebase_store/repositories/changesets.py +59 -0
- lorebase_store/repositories/deferred_events.py +142 -0
- lorebase_store/repositories/documents.py +305 -0
- lorebase_store/repositories/domains.py +97 -0
- lorebase_store/repositories/events.py +205 -0
- lorebase_store/repositories/index_view.py +489 -0
- lorebase_store/repositories/invocations.py +68 -0
- lorebase_store/repositories/materializations.py +276 -0
- lorebase_store/repositories/materialized.py +1129 -0
- lorebase_store/repositories/merges.py +417 -0
- lorebase_store/repositories/proposal_items.py +199 -0
- lorebase_store/repositories/proposals.py +184 -0
- lorebase_store/repositories/provenance.py +77 -0
- lorebase_store/repositories/refs.py +452 -0
- lorebase_store/repositories/revisions.py +273 -0
- lorebase_store/repositories/scope.py +33 -0
- lorebase_store/repositories/source_runs.py +123 -0
- lorebase_store/repositories/suggestions.py +638 -0
- lorebase_store/runtime.py +87 -0
- lorebase_store/schema_cache.py +50 -0
- lorebase_store/settings.py +879 -0
- lorebase_store/storage.py +1478 -0
- lorebase_store/telemetry.py +142 -0
- lorebase_store/testing.py +108 -0
- lorebase_store/tracing.py +505 -0
- lorebase_store/traverse.py +578 -0
- lorebase_store/vault/__init__.py +50 -0
- lorebase_store/vault/keys.py +216 -0
- lorebase_store/vault/quotes.py +133 -0
- lorebase_store/vault/rotation.py +209 -0
- lorebase_store/vault/settings.py +152 -0
- lorebase_store/vault/store.py +987 -0
- lorebase_store/vault/transfer.py +558 -0
- lorebase_store/webhooks/__init__.py +24 -0
- lorebase_store/webhooks/delivery.py +637 -0
- lorebase_store/webhooks/keys.py +126 -0
- lorebase_store/webhooks/repository.py +142 -0
- lorebase_store/webhooks/settings.py +138 -0
- lorebase_store/webhooks/signing.py +50 -0
- lorebase_store-0.2.3.dist-info/METADATA +13 -0
- lorebase_store-0.2.3.dist-info/RECORD +163 -0
- lorebase_store-0.2.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""The fan-out of materialized path Derivations.
|
|
2
|
+
|
|
3
|
+
`mat_schema.path_fanout` records the `store.traverse.path_fanout` a branch's materialized
|
|
4
|
+
path Derivations were computed with. A commit, a rebuild or the job worker that finds another
|
|
5
|
+
setting rewrites them (`lorebase_store.materialize.paths`). Existing rows start without one, so
|
|
6
|
+
their materialized paths are rewritten once.
|
|
7
|
+
|
|
8
|
+
Revision ID: 0030
|
|
9
|
+
Revises: 0029
|
|
10
|
+
Create Date: 2026-10-27 09:00:00.000000
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
|
|
17
|
+
import sqlalchemy as sa
|
|
18
|
+
from alembic import op
|
|
19
|
+
|
|
20
|
+
revision: str = "0030"
|
|
21
|
+
down_revision: str | Sequence[str] | None = "0029"
|
|
22
|
+
branch_labels: str | Sequence[str] | None = None
|
|
23
|
+
depends_on: str | Sequence[str] | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def upgrade() -> None:
|
|
27
|
+
op.add_column("mat_schema", sa.Column("path_fanout", sa.Integer(), nullable=True))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def downgrade() -> None:
|
|
31
|
+
op.drop_column("mat_schema", "path_fanout")
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Branch builds, copy retries and schema reconciliation.
|
|
2
|
+
|
|
3
|
+
- `materialization` gains `building`: a branch whose rows a `store.materialize` job writes
|
|
4
|
+
from the tree of its head in batches (a copy of an overlay or of an older Revision, an
|
|
5
|
+
overlay detached from its base). Reads of its head and commits answer
|
|
6
|
+
`branch-materializing` until the job is done.
|
|
7
|
+
- `branch_materializations.strategy`: `base` (copy the base rows an overlay reads, as before)
|
|
8
|
+
or `tree` (write the rows from the tree of the head).
|
|
9
|
+
- `branch_materializations.failures`: failed jobs of the copy, for the retry backoff and the
|
|
10
|
+
fallback to a tree build.
|
|
11
|
+
- `branch_materializations.reconcile`: the schema changes a branch got while it was copied
|
|
12
|
+
(`{"refresh_kinds": [...]}`); the job recomputes derived values, path Links, titles and
|
|
13
|
+
search vectors after the switch.
|
|
14
|
+
- The `phase` check admits the phases of a tree build and of the reconciliation.
|
|
15
|
+
|
|
16
|
+
Revision ID: 0031
|
|
17
|
+
Revises: 0030
|
|
18
|
+
Create Date: 2026-10-27 09:00:00.000000
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from collections.abc import Sequence
|
|
24
|
+
|
|
25
|
+
import sqlalchemy as sa
|
|
26
|
+
from alembic import op
|
|
27
|
+
from sqlalchemy.dialects import postgresql
|
|
28
|
+
|
|
29
|
+
revision: str = "0031"
|
|
30
|
+
down_revision: str | Sequence[str] | None = "0030"
|
|
31
|
+
branch_labels: str | Sequence[str] | None = None
|
|
32
|
+
depends_on: str | Sequence[str] | None = None
|
|
33
|
+
|
|
34
|
+
TABLE = "branch_materializations"
|
|
35
|
+
PHASES = (
|
|
36
|
+
"records",
|
|
37
|
+
"links",
|
|
38
|
+
"history",
|
|
39
|
+
"clear",
|
|
40
|
+
"tree_history",
|
|
41
|
+
"tree_rows",
|
|
42
|
+
"switch",
|
|
43
|
+
"prune",
|
|
44
|
+
"derived",
|
|
45
|
+
"paths",
|
|
46
|
+
"search",
|
|
47
|
+
"done",
|
|
48
|
+
)
|
|
49
|
+
OLD_PHASES = ("records", "links", "history", "switch", "done")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _phase_check(phases: Sequence[str]) -> str:
|
|
53
|
+
return "phase IN (" + ", ".join(f"'{p}'" for p in phases) + ")"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def upgrade() -> None:
|
|
57
|
+
op.execute("ALTER TYPE materialization ADD VALUE IF NOT EXISTS 'building'")
|
|
58
|
+
op.add_column(
|
|
59
|
+
TABLE, sa.Column("strategy", sa.Text(), server_default=sa.text("'base'"), nullable=False)
|
|
60
|
+
)
|
|
61
|
+
op.add_column(
|
|
62
|
+
TABLE, sa.Column("failures", sa.Integer(), server_default=sa.text("0"), nullable=False)
|
|
63
|
+
)
|
|
64
|
+
op.add_column(
|
|
65
|
+
TABLE, sa.Column("reconcile", postgresql.JSONB(astext_type=sa.Text()), nullable=True)
|
|
66
|
+
)
|
|
67
|
+
op.create_check_constraint(
|
|
68
|
+
op.f("ck_branch_materializations_strategy"), TABLE, "strategy IN ('base', 'tree')"
|
|
69
|
+
)
|
|
70
|
+
op.drop_constraint(op.f("ck_branch_materializations_phase"), TABLE, type_="check")
|
|
71
|
+
op.create_check_constraint(
|
|
72
|
+
op.f("ck_branch_materializations_phase"), TABLE, _phase_check(PHASES)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def downgrade() -> None:
|
|
77
|
+
# A branch still building has no materialized head to fall back to: rebuild it first.
|
|
78
|
+
op.execute(
|
|
79
|
+
"DELETE FROM branch_materializations WHERE phase NOT IN ("
|
|
80
|
+
+ ", ".join(f"'{p}'" for p in OLD_PHASES)
|
|
81
|
+
+ ")"
|
|
82
|
+
)
|
|
83
|
+
op.drop_constraint(op.f("ck_branch_materializations_phase"), TABLE, type_="check")
|
|
84
|
+
op.create_check_constraint(
|
|
85
|
+
op.f("ck_branch_materializations_phase"), TABLE, _phase_check(OLD_PHASES)
|
|
86
|
+
)
|
|
87
|
+
op.drop_constraint(op.f("ck_branch_materializations_strategy"), TABLE, type_="check")
|
|
88
|
+
op.drop_column(TABLE, "reconcile")
|
|
89
|
+
op.drop_column(TABLE, "failures")
|
|
90
|
+
op.drop_column(TABLE, "strategy")
|
|
91
|
+
# Postgres cannot drop an enum value; `building` stays unused.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Revisions behind the lineage of materialized Records.
|
|
2
|
+
|
|
3
|
+
`mat_records.lineage_revisions` maps each Attribute of `lineage` to the Revision of its last
|
|
4
|
+
change and `$created` to the Revision that last created the Record, kept by the materializer
|
|
5
|
+
in the commit transaction. Rows written before this revision hold null and their readers
|
|
6
|
+
fall back to the branch history (`mat_history`); a rebuild fills them.
|
|
7
|
+
|
|
8
|
+
Revision ID: 0032
|
|
9
|
+
Revises: 0031
|
|
10
|
+
Create Date: 2026-10-27 12:00:00.000000
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
|
|
17
|
+
import sqlalchemy as sa
|
|
18
|
+
from alembic import op
|
|
19
|
+
from sqlalchemy.dialects import postgresql
|
|
20
|
+
|
|
21
|
+
revision: str = "0032"
|
|
22
|
+
down_revision: str | Sequence[str] | None = "0031"
|
|
23
|
+
branch_labels: str | Sequence[str] | None = None
|
|
24
|
+
depends_on: str | Sequence[str] | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def upgrade() -> None:
|
|
28
|
+
op.add_column(
|
|
29
|
+
"mat_records",
|
|
30
|
+
sa.Column("lineage_revisions", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def downgrade() -> None:
|
|
35
|
+
op.drop_column("mat_records", "lineage_revisions")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Vault rows remember the key change that took their value over (design 13.5).
|
|
2
|
+
|
|
3
|
+
`vault.moved_to` is the Record key a key change stored the row's value again under. A moved
|
|
4
|
+
token can be taken over by no other key change and claimed again by no Record, so one value is
|
|
5
|
+
never copied to more than one new key, whichever branch the later attempt is made on.
|
|
6
|
+
|
|
7
|
+
Revision ID: 0033
|
|
8
|
+
Revises: 0032
|
|
9
|
+
Create Date: 2026-10-28 09:00:00.000000
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
|
|
16
|
+
import sqlalchemy as sa
|
|
17
|
+
from alembic import op
|
|
18
|
+
|
|
19
|
+
revision: str = "0033"
|
|
20
|
+
down_revision: str | Sequence[str] | None = "0032"
|
|
21
|
+
branch_labels: str | Sequence[str] | None = None
|
|
22
|
+
depends_on: str | Sequence[str] | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def upgrade() -> None:
|
|
26
|
+
op.add_column("vault", sa.Column("moved_to", sa.Text(), nullable=True))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def downgrade() -> None:
|
|
30
|
+
op.drop_column("vault", "moved_to")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Structured output modes learned per chat endpoint.
|
|
2
|
+
|
|
3
|
+
`inference_endpoint_modes` remembers the mode (`json_schema`, `json_mode` or `prompt`) that a
|
|
4
|
+
chat endpoint was found to need after it rejected a stronger one, so a restarted process does
|
|
5
|
+
not retry the stronger ones; `updated_at` lets readers expire an entry. Keyed by a keyed digest
|
|
6
|
+
of the endpoint, model and output settings; no Domain data. The application role may read,
|
|
7
|
+
insert, update and delete rows, nothing else. The downgrade drops the table: the modes are
|
|
8
|
+
learned again from the next rejections.
|
|
9
|
+
|
|
10
|
+
Revision ID: 0034
|
|
11
|
+
Revises: 0033
|
|
12
|
+
Create Date: 2026-10-28 09:00:00.000000
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
|
|
19
|
+
import sqlalchemy as sa
|
|
20
|
+
from alembic import op
|
|
21
|
+
|
|
22
|
+
revision: str = "0034"
|
|
23
|
+
down_revision: str | Sequence[str] | None = "0033"
|
|
24
|
+
branch_labels: str | Sequence[str] | None = None
|
|
25
|
+
depends_on: str | Sequence[str] | None = None
|
|
26
|
+
|
|
27
|
+
MODES = "inference_endpoint_modes"
|
|
28
|
+
REVOKED = "TRUNCATE, REFERENCES, TRIGGER"
|
|
29
|
+
GRANTED = "SELECT, INSERT, UPDATE, DELETE"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _application_role() -> str | None:
|
|
33
|
+
config = op.get_context().config
|
|
34
|
+
if config is None or "database_settings" not in config.attributes:
|
|
35
|
+
raise RuntimeError("run this migration through the Lorebase Alembic environment")
|
|
36
|
+
role: str | None = config.attributes["database_settings"].application_role
|
|
37
|
+
return role
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _quoted(role: str) -> str:
|
|
41
|
+
quoted: str = op.get_bind().execute(sa.text("SELECT quote_ident(:r)"), {"r": role}).scalar_one()
|
|
42
|
+
return quoted
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def upgrade() -> None:
|
|
46
|
+
op.create_table(
|
|
47
|
+
MODES,
|
|
48
|
+
sa.Column("key", sa.LargeBinary(), nullable=False),
|
|
49
|
+
sa.Column("mode_index", sa.Integer(), nullable=False),
|
|
50
|
+
sa.Column(
|
|
51
|
+
"updated_at",
|
|
52
|
+
sa.DateTime(timezone=True),
|
|
53
|
+
server_default=sa.text("now()"),
|
|
54
|
+
nullable=False,
|
|
55
|
+
),
|
|
56
|
+
sa.CheckConstraint(
|
|
57
|
+
"octet_length(key) = 32", name=op.f("ck_inference_endpoint_modes_key_h32")
|
|
58
|
+
),
|
|
59
|
+
sa.CheckConstraint(
|
|
60
|
+
"mode_index BETWEEN 0 AND 2", name=op.f("ck_inference_endpoint_modes_mode_index")
|
|
61
|
+
),
|
|
62
|
+
sa.PrimaryKeyConstraint("key", name=op.f("pk_inference_endpoint_modes")),
|
|
63
|
+
)
|
|
64
|
+
op.execute(f"REVOKE ALL ON TABLE {MODES} FROM PUBLIC")
|
|
65
|
+
role = _application_role()
|
|
66
|
+
if role is not None:
|
|
67
|
+
quoted = _quoted(role)
|
|
68
|
+
op.execute(f"REVOKE {REVOKED} ON TABLE {MODES} FROM {quoted}")
|
|
69
|
+
op.execute(f"GRANT {GRANTED} ON TABLE {MODES} TO {quoted}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def downgrade() -> None:
|
|
73
|
+
op.drop_table(MODES)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Full-text search that says when its candidate cap cut the ranking (design 8.4; store schema
|
|
2
|
+
notes).
|
|
3
|
+
|
|
4
|
+
Search ranks at most `p_candidates` matches, taken from the GIN index in physical order. The
|
|
5
|
+
functions of revision 0018 do not say whether the cap was reached, so a caller cannot tell an
|
|
6
|
+
exact ranking from one over a sample of the matches. This revision adds
|
|
7
|
+
`lb_search_bounded(...)` and `lb_search_bounded_overlay(...)`: the arguments, statements and
|
|
8
|
+
checks of `lb_search_corrected` and `lb_search_corrected_overlay`, with one more column,
|
|
9
|
+
`truncated`, true on every row when the candidates of the query as written, or those found
|
|
10
|
+
only through a correction, filled `p_candidates` (a window count over the candidates, which
|
|
11
|
+
are read anyway to be ranked). The store chooses `p_candidates` from the word lexicon's
|
|
12
|
+
counts (`lorebase_store.reads.CommonTerms`): enough for an exact ranking when a term of the
|
|
13
|
+
query is rare enough, a bounded sample when every term is very common.
|
|
14
|
+
|
|
15
|
+
The functions of revision 0018 stay for processes that still call them. Options, checks and
|
|
16
|
+
grants as in revision 0018.
|
|
17
|
+
|
|
18
|
+
Revision ID: 0035
|
|
19
|
+
Revises: 0034
|
|
20
|
+
Create Date: 2026-10-29 09:00:00.000000
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import importlib.util
|
|
26
|
+
from collections.abc import Sequence
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from types import ModuleType
|
|
29
|
+
|
|
30
|
+
from alembic import op
|
|
31
|
+
|
|
32
|
+
revision: str = "0035"
|
|
33
|
+
down_revision: str | Sequence[str] | None = "0034"
|
|
34
|
+
branch_labels: str | Sequence[str] | None = None
|
|
35
|
+
depends_on: str | Sequence[str] | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _revision_module(filename: str) -> ModuleType:
|
|
39
|
+
"""An earlier revision file of this directory, loaded for its SQL templates and checks."""
|
|
40
|
+
path = Path(__file__).with_name(filename)
|
|
41
|
+
spec = importlib.util.spec_from_file_location(f"_lorebase_revision_{path.stem}", path)
|
|
42
|
+
if spec is None or spec.loader is None:
|
|
43
|
+
raise RuntimeError(f"cannot load migration {path}")
|
|
44
|
+
module = importlib.util.module_from_spec(spec)
|
|
45
|
+
spec.loader.exec_module(module)
|
|
46
|
+
return module
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
_lexicon = _revision_module("0018_search_lexicon.py")
|
|
50
|
+
_checks = _lexicon._checks
|
|
51
|
+
_reads = _lexicon._reads
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _replaced(text: str, old: str, new: str) -> str:
|
|
55
|
+
if text.count(old) != 1:
|
|
56
|
+
raise RuntimeError(f"revision 0018 changed: {old!r} not found once")
|
|
57
|
+
return text.replace(old, new)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
LB_SEARCH_BOUNDED = _lexicon.LB_SEARCH_CORRECTED
|
|
61
|
+
for _old, _new in (
|
|
62
|
+
(
|
|
63
|
+
"RETURNS TABLE (kind_id text, key text, title text, score real, exact boolean)",
|
|
64
|
+
"RETURNS TABLE (kind_id text, key text, title text, score real, exact boolean,\n"
|
|
65
|
+
" truncated boolean)",
|
|
66
|
+
),
|
|
67
|
+
(
|
|
68
|
+
"'c.exact FROM (' || v_rows || ') AS c '",
|
|
69
|
+
"'c.exact, count(*) OVER () >= $6 AS truncated FROM (' || v_rows || ') AS c '",
|
|
70
|
+
),
|
|
71
|
+
(
|
|
72
|
+
"'AS score, c.exact FROM ((' || v_rows",
|
|
73
|
+
"'AS score, c.exact, (count(*) FILTER (WHERE c.exact) OVER () >= $6 '\n"
|
|
74
|
+
" 'OR count(*) FILTER (WHERE NOT c.exact) OVER () >= $6) AS truncated '\n"
|
|
75
|
+
" 'FROM ((' || v_rows",
|
|
76
|
+
),
|
|
77
|
+
):
|
|
78
|
+
LB_SEARCH_BOUNDED = _replaced(LB_SEARCH_BOUNDED, _old, _new)
|
|
79
|
+
"""Revision 0018's search with the `truncated` column."""
|
|
80
|
+
|
|
81
|
+
SEARCH = tuple(
|
|
82
|
+
(name.replace("lb_search_corrected", "lb_search_bounded"), sources, resolve)
|
|
83
|
+
for name, sources, resolve in _lexicon.SEARCH
|
|
84
|
+
)
|
|
85
|
+
SIGNATURES = tuple(f"{name}{_lexicon._CORRECTED_ARGUMENTS}" for name, _, _ in SEARCH)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def upgrade() -> None:
|
|
89
|
+
role = _reads._application_role()
|
|
90
|
+
if role is not None:
|
|
91
|
+
_checks._refuse_writable_schemas(role)
|
|
92
|
+
search_path = _reads._search_path()
|
|
93
|
+
options = _reads._OPTIONS.format(search_path=search_path).strip()
|
|
94
|
+
for name, sources, resolve in SEARCH:
|
|
95
|
+
guards = _checks._guards(search_path, ("p_candidates", "p_limit")) + resolve
|
|
96
|
+
template = LB_SEARCH_BOUNDED.format(name=name)
|
|
97
|
+
statement = _checks._create_or_replace(template, guards)
|
|
98
|
+
op.execute(statement.format(options=options, sources=sources))
|
|
99
|
+
_lexicon._grant(SIGNATURES)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def downgrade() -> None:
|
|
103
|
+
for signature in reversed(SIGNATURES):
|
|
104
|
+
op.execute(f"DROP FUNCTION IF EXISTS {signature}")
|