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.
Files changed (163) hide show
  1. lorebase_store/__init__.py +1 -0
  2. lorebase_store/_migrations/env.py +106 -0
  3. lorebase_store/_migrations/script.py.mako +27 -0
  4. lorebase_store/_migrations/versions/0001_initial_schema.py +1071 -0
  5. lorebase_store/_migrations/versions/0002_merge_attempt_details.py +74 -0
  6. lorebase_store/_migrations/versions/0003_webhooks.py +127 -0
  7. lorebase_store/_migrations/versions/0004_defer_revision_references.py +38 -0
  8. lorebase_store/_migrations/versions/0005_security_definer_reads.py +396 -0
  9. lorebase_store/_migrations/versions/0006_api_key_usage.py +48 -0
  10. lorebase_store/_migrations/versions/0007_document_extraction.py +154 -0
  11. lorebase_store/_migrations/versions/0008_definer_argument_checks.py +178 -0
  12. lorebase_store/_migrations/versions/0009_api_key_networks.py +33 -0
  13. lorebase_store/_migrations/versions/0010_overlay_branches.py +582 -0
  14. lorebase_store/_migrations/versions/0011_branch_scoped_search_index.py +229 -0
  15. lorebase_store/_migrations/versions/0012_overlay_neighborhood_lookups.py +185 -0
  16. lorebase_store/_migrations/versions/0013_web_sessions.py +72 -0
  17. lorebase_store/_migrations/versions/0014_embedding_jobs.py +70 -0
  18. lorebase_store/_migrations/versions/0015_append_only_audit_log.py +83 -0
  19. lorebase_store/_migrations/versions/0016_sealed_webhook_keys.py +55 -0
  20. lorebase_store/_migrations/versions/0017_background_branch_copies.py +77 -0
  21. lorebase_store/_migrations/versions/0018_search_lexicon.py +477 -0
  22. lorebase_store/_migrations/versions/0019_vault_bindings.py +56 -0
  23. lorebase_store/_migrations/versions/0020_outbox_consumers.py +150 -0
  24. lorebase_store/_migrations/versions/0021_schema_suggestions.py +114 -0
  25. lorebase_store/_migrations/versions/0022_read_audit_and_shared_rate_limits.py +133 -0
  26. lorebase_store/_migrations/versions/0023_agent_trace.py +96 -0
  27. lorebase_store/_migrations/versions/0024_embedding_template_keys.py +205 -0
  28. lorebase_store/_migrations/versions/0025_snapshot_refs.py +35 -0
  29. lorebase_store/_migrations/versions/0026_plain_reverse_link_index.py +112 -0
  30. lorebase_store/_migrations/versions/0027_vault_rotation_and_tombstones.py +145 -0
  31. lorebase_store/_migrations/versions/0028_operation_deferred_events.py +71 -0
  32. lorebase_store/_migrations/versions/0029_agent_trace_sessions.py +108 -0
  33. lorebase_store/_migrations/versions/0030_path_fanout.py +31 -0
  34. lorebase_store/_migrations/versions/0031_branch_builds.py +91 -0
  35. lorebase_store/_migrations/versions/0032_lineage_revisions.py +35 -0
  36. lorebase_store/_migrations/versions/0033_vault_moved_tokens.py +30 -0
  37. lorebase_store/_migrations/versions/0034_inference_endpoint_modes.py +73 -0
  38. lorebase_store/_migrations/versions/0035_search_truncation.py +104 -0
  39. lorebase_store/_migrations/versions/0036_lineage_maps.py +325 -0
  40. lorebase_store/_migrations/versions/0037_bm25_search.py +346 -0
  41. lorebase_store/auth/__init__.py +52 -0
  42. lorebase_store/auth/api_keys.py +161 -0
  43. lorebase_store/auth/audit.py +382 -0
  44. lorebase_store/auth/checkpoints.py +185 -0
  45. lorebase_store/auth/grants.py +161 -0
  46. lorebase_store/auth/principals.py +158 -0
  47. lorebase_store/auth/scope.py +70 -0
  48. lorebase_store/auth/sessions.py +153 -0
  49. lorebase_store/auth/unit.py +53 -0
  50. lorebase_store/auth/usage.py +70 -0
  51. lorebase_store/blobs.py +73 -0
  52. lorebase_store/bm25.py +346 -0
  53. lorebase_store/bulk.py +147 -0
  54. lorebase_store/bundle.py +1830 -0
  55. lorebase_store/changefeed.py +261 -0
  56. lorebase_store/commit/__init__.py +42 -0
  57. lorebase_store/commit/changes.py +67 -0
  58. lorebase_store/commit/errors.py +60 -0
  59. lorebase_store/commit/migrations.py +335 -0
  60. lorebase_store/commit/pipeline.py +754 -0
  61. lorebase_store/commit/schema.py +123 -0
  62. lorebase_store/db.py +58 -0
  63. lorebase_store/definer.py +50 -0
  64. lorebase_store/digests.py +99 -0
  65. lorebase_store/embeddings.py +444 -0
  66. lorebase_store/endpoint_modes.py +59 -0
  67. lorebase_store/extraction/__init__.py +61 -0
  68. lorebase_store/extraction/items.py +187 -0
  69. lorebase_store/extraction/materialize.py +364 -0
  70. lorebase_store/extraction/proposal.py +65 -0
  71. lorebase_store/extraction/review.py +168 -0
  72. lorebase_store/gc.py +970 -0
  73. lorebase_store/indexes.py +1033 -0
  74. lorebase_store/jobs.py +651 -0
  75. lorebase_store/keyring.py +72 -0
  76. lorebase_store/layout.py +335 -0
  77. lorebase_store/materialize/__init__.py +103 -0
  78. lorebase_store/materialize/apply.py +436 -0
  79. lorebase_store/materialize/background.py +1184 -0
  80. lorebase_store/materialize/branch.py +268 -0
  81. lorebase_store/materialize/build.py +536 -0
  82. lorebase_store/materialize/derived.py +540 -0
  83. lorebase_store/materialize/lineage_backfill.py +502 -0
  84. lorebase_store/materialize/links.py +266 -0
  85. lorebase_store/materialize/overlay.py +292 -0
  86. lorebase_store/materialize/paths.py +315 -0
  87. lorebase_store/materialize/rebuild.py +326 -0
  88. lorebase_store/materialize/rows.py +153 -0
  89. lorebase_store/materialize/sensitivity.py +66 -0
  90. lorebase_store/materialize/snapshot.py +205 -0
  91. lorebase_store/materialize/stale.py +444 -0
  92. lorebase_store/materialize/verify.py +321 -0
  93. lorebase_store/merging/__init__.py +39 -0
  94. lorebase_store/merging/attempts.py +334 -0
  95. lorebase_store/merging/lineage.py +123 -0
  96. lorebase_store/merging/service.py +652 -0
  97. lorebase_store/migrations.py +133 -0
  98. lorebase_store/models/__init__.py +136 -0
  99. lorebase_store/models/base.py +64 -0
  100. lorebase_store/models/enums.py +132 -0
  101. lorebase_store/models/governance.py +268 -0
  102. lorebase_store/models/ingestion.py +288 -0
  103. lorebase_store/models/materialized.py +277 -0
  104. lorebase_store/models/proposals.py +195 -0
  105. lorebase_store/models/queues.py +87 -0
  106. lorebase_store/models/tracing.py +116 -0
  107. lorebase_store/models/versioning.py +222 -0
  108. lorebase_store/models/webhooks.py +96 -0
  109. lorebase_store/nodestore.py +384 -0
  110. lorebase_store/objectstore.py +162 -0
  111. lorebase_store/outbox_events.py +166 -0
  112. lorebase_store/profiling/__init__.py +58 -0
  113. lorebase_store/profiling/delimited.py +109 -0
  114. lorebase_store/profiling/postgres.py +211 -0
  115. lorebase_store/profiling/record_files.py +127 -0
  116. lorebase_store/profiling/records.py +92 -0
  117. lorebase_store/profiling/sql_databases.py +298 -0
  118. lorebase_store/ratelimit.py +129 -0
  119. lorebase_store/reads.py +1491 -0
  120. lorebase_store/replicas.py +434 -0
  121. lorebase_store/repositories/__init__.py +152 -0
  122. lorebase_store/repositories/changesets.py +59 -0
  123. lorebase_store/repositories/deferred_events.py +142 -0
  124. lorebase_store/repositories/documents.py +305 -0
  125. lorebase_store/repositories/domains.py +97 -0
  126. lorebase_store/repositories/events.py +205 -0
  127. lorebase_store/repositories/index_view.py +489 -0
  128. lorebase_store/repositories/invocations.py +68 -0
  129. lorebase_store/repositories/materializations.py +276 -0
  130. lorebase_store/repositories/materialized.py +1129 -0
  131. lorebase_store/repositories/merges.py +417 -0
  132. lorebase_store/repositories/proposal_items.py +199 -0
  133. lorebase_store/repositories/proposals.py +184 -0
  134. lorebase_store/repositories/provenance.py +77 -0
  135. lorebase_store/repositories/refs.py +452 -0
  136. lorebase_store/repositories/revisions.py +273 -0
  137. lorebase_store/repositories/scope.py +33 -0
  138. lorebase_store/repositories/source_runs.py +123 -0
  139. lorebase_store/repositories/suggestions.py +638 -0
  140. lorebase_store/runtime.py +87 -0
  141. lorebase_store/schema_cache.py +50 -0
  142. lorebase_store/settings.py +879 -0
  143. lorebase_store/storage.py +1478 -0
  144. lorebase_store/telemetry.py +142 -0
  145. lorebase_store/testing.py +108 -0
  146. lorebase_store/tracing.py +505 -0
  147. lorebase_store/traverse.py +578 -0
  148. lorebase_store/vault/__init__.py +50 -0
  149. lorebase_store/vault/keys.py +216 -0
  150. lorebase_store/vault/quotes.py +133 -0
  151. lorebase_store/vault/rotation.py +209 -0
  152. lorebase_store/vault/settings.py +152 -0
  153. lorebase_store/vault/store.py +987 -0
  154. lorebase_store/vault/transfer.py +558 -0
  155. lorebase_store/webhooks/__init__.py +24 -0
  156. lorebase_store/webhooks/delivery.py +637 -0
  157. lorebase_store/webhooks/keys.py +126 -0
  158. lorebase_store/webhooks/repository.py +142 -0
  159. lorebase_store/webhooks/settings.py +138 -0
  160. lorebase_store/webhooks/signing.py +50 -0
  161. lorebase_store-0.2.3.dist-info/METADATA +13 -0
  162. lorebase_store-0.2.3.dist-info/RECORD +163 -0
  163. 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}")