First pass implementation of an SQLite backing store for fetched opportunities

This commit is contained in:
bwilliams committed 2026-10-13 12:52:17 +01:00
1 parent 9b0e6dccfe
commit 045492a148
29 files changed
+3132 -9

No files matched your search

@@ -0,0 +1,40 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS "opportunities" (
-- Surrogate key. "uuid" is the real identifier; "id" exists only to give
-- the title search index (opportunities_fts) a stable rowid to point at.
"id" INTEGER PRIMARY KEY,
"uuid" TEXT NOT NULL UNIQUE,
"uuid_type" TEXT NOT NULL,
"title" TEXT NOT NULL,
"status" TEXT NOT NULL,
"description" TEXT NOT NULL DEFAULT '',
"buyer_name" TEXT NOT NULL DEFAULT '',
-- classifications: see tender_classifications
"buyer_id" TEXT NOT NULL DEFAULT '',
"main_category" TEXT NOT NULL DEFAULT '',
"procurement_method" TEXT NOT NULL DEFAULT '',
"multi_lot" BOOLEAN NOT NULL DEFAULT FALSE,
"sme_suitable" BOOLEAN NOT NULL DEFAULT FALSE,
"vcse_suitable" BOOLEAN NOT NULL DEFAULT FALSE,
"start_date" DATETIME,
"end_date" DATETIME,
"gross_value" REAL NOT NULL DEFAULT 0,
"has_documents" BOOLEAN NOT NULL DEFAULT FALSE,
"source" TEXT NOT NULL DEFAULT '',
-- Release date of the newest release applied to this row. A release
-- older than this never overwrites the tender.
"latest_release_date" DATETIME NOT NULL
-- releases: see tender_ocds_releases
);
CREATE INDEX IF NOT EXISTS "opportunities_status"
ON "opportunities" ("status");
CREATE INDEX IF NOT EXISTS "opportunities_latest_release_date"
ON "opportunities" ("latest_release_date");
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS "opportunities";
-- +goose StatementEnd
@@ -0,0 +1,19 @@
-- +goose Up
-- +goose StatementBegin
-- Write-once: a release is never modified after it is stored.
-- "release_id" is the OCDS release id, unique only within its OCID ("uuid").
CREATE TABLE IF NOT EXISTS "opportunity_releases" (
"uuid" TEXT NOT NULL
REFERENCES "opportunities" ("uuid") ON DELETE CASCADE,
"release_id" TEXT NOT NULL,
"release_date" DATETIME NOT NULL,
-- tags: see release_tags
"data" TEXT NOT NULL,
PRIMARY KEY ("uuid", "release_id")
) WITHOUT ROWID;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS "opportunity_releases";
-- +goose StatementEnd
@@ -0,0 +1,18 @@
-- +goose Up
-- +goose StatementBegin
-- Write-once, inserted together with the release it belongs to.
CREATE TABLE IF NOT EXISTS "opportunity_release_tags" (
"uuid" TEXT NOT NULL,
"release_id" TEXT NOT NULL,
"tag" TEXT NOT NULL,
PRIMARY KEY ("uuid", "release_id", "tag"),
FOREIGN KEY ("uuid", "release_id")
REFERENCES "opportunity_releases" ("uuid", "release_id")
ON DELETE CASCADE
) WITHOUT ROWID;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS "opportunity_release_tags";
-- +goose StatementEnd
@@ -0,0 +1,26 @@
-- +goose Up
-- +goose StatementBegin
-- Shared, write-once lookup of classifications. A row is reused only when
-- every content column matches; any difference (e.g. a changed description)
-- creates a new row.
--
-- When adding a new scheme flag column (e.g. "is_unspsc"), the same migration
-- must drop and recreate "classification_codes_content" with the new column
-- included, otherwise rows differing only by that flag would be merged.
CREATE TABLE IF NOT EXISTS "classification_codes" (
"id" INTEGER PRIMARY KEY,
"is_cpv" BOOLEAN NOT NULL,
"code" TEXT NOT NULL,
"classification" TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS "classification_codes_content"
ON "classification_codes" ("is_cpv", "code", "classification");
CREATE INDEX IF NOT EXISTS "classification_codes_code"
ON "classification_codes" ("code");
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS "classification_codes";
-- +goose StatementEnd
@@ -0,0 +1,20 @@
-- +goose Up
-- +goose StatementBegin
-- Maps opportunities to classification codes. Replaced as a set whenever the
-- opportunity row is updated.
CREATE TABLE IF NOT EXISTS "opportunity_classifications" (
"opportunity_uuid" TEXT NOT NULL
REFERENCES "opportunities" ("uuid") ON DELETE CASCADE,
"classification_id" INTEGER NOT NULL
REFERENCES "classification_codes" ("id"),
PRIMARY KEY ("opportunity_uuid", "classification_id")
) WITHOUT ROWID;
CREATE INDEX IF NOT EXISTS "opportunity_classifications_classification_id"
ON "opportunity_classifications" ("classification_id");
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS "opportunity_classifications";
-- +goose StatementEnd
@@ -0,0 +1,40 @@
-- +goose Up
-- +goose StatementBegin
-- Full-text index over opportunities.title. Requires mattn/go-sqlite3 to be
-- built with -tags sqlite_fts5. Kept in sync by the triggers below.
CREATE VIRTUAL TABLE IF NOT EXISTS "opportunities_fts" USING fts5(
"title",
content = 'opportunities',
content_rowid = 'id',
tokenize = 'porter unicode61 remove_diacritics 2'
);
CREATE TRIGGER IF NOT EXISTS "opportunities_fts_insert" AFTER INSERT ON "opportunities"
BEGIN
INSERT INTO "opportunities_fts" ("rowid", "title") VALUES (new."id", new."title");
END;
CREATE TRIGGER IF NOT EXISTS "opportunities_fts_delete" AFTER DELETE ON "opportunities"
BEGIN
INSERT INTO "opportunities_fts" ("opportunities_fts", "rowid", "title")
VALUES ('delete', old."id", old."title");
END;
CREATE TRIGGER IF NOT EXISTS "opportunities_fts_update" AFTER UPDATE OF "title" ON "opportunities"
BEGIN
INSERT INTO "opportunities_fts" ("opportunities_fts", "rowid", "title")
VALUES ('delete', old."id", old."title");
INSERT INTO "opportunities_fts" ("rowid", "title") VALUES (new."id", new."title");
END;
-- Index any opportunities that existed before this migration.
INSERT INTO "opportunities_fts" ("opportunities_fts") VALUES ('rebuild');
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TRIGGER IF EXISTS "opportunities_fts_update";
DROP TRIGGER IF EXISTS "opportunities_fts_delete";
DROP TRIGGER IF EXISTS "opportunities_fts_insert";
DROP TABLE IF EXISTS "opportunities_fts";
-- +goose StatementEnd
@@ -0,0 +1,22 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS "opportunity_marks" (
"uuid" TEXT NOT NULL UNIQUE,
"hidden" BOOLEAN NOT NULL DEFAULT 0,
"skipped" BOOLEAN NOT NULL DEFAULT 0,
"interested" BOOLEAN NOT NULL DEFAULT 0,
"bid_submitted" BOOLEAN NOT NULL DEFAULT 0,
"lost" BOOLEAN NOT NULL DEFAULT 0,
"won" BOOLEAN NOT NULL DEFAULT 0,
CHECK (NOT ("won" AND "lost")),
PRIMARY KEY ("uuid"),
FOREIGN KEY ("uuid")
REFERENCES "opportunities" ("uuid")
ON DELETE CASCADE
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS "opportunity_marks";
-- +goose StatementEnd