feat: start impliment postgres hooks
This commit is contained in:
@@ -100,14 +100,37 @@ in {
|
|||||||
# -- Cargo.toml --
|
# -- Cargo.toml --
|
||||||
cargoToml = src: (builtins.fromTOML (builtins.readFile "${src}/Cargo.toml"));
|
cargoToml = src: (builtins.fromTOML (builtins.readFile "${src}/Cargo.toml"));
|
||||||
|
|
||||||
# SQL bundle bootstrapping `hectic.created_at` / `hectic.updated_at` inheritance enforcement.
|
# Consolidated SQL bundles for the `hectic` schema. Single source of truth
|
||||||
# Consumers can either:
|
# for everything that creates objects in the `hectic` namespace, used by
|
||||||
# * read the SQL string for inline pipelines: `self.lib.hecticInheritance.sql`
|
# migrator (init-time), db-tool (postgres-init), and pkgs.hectic.postgres-secrets.
|
||||||
# * reference the source path: `self.lib.hecticInheritance.path`
|
#
|
||||||
# * use the per-system package: `pkgs.hectic.hectic-inheritance` (provides
|
# The whole hectic system shares one `versionString`; `hectic-version.sql`
|
||||||
# `$out/share/hectic/hectic-inheritance.sql`)
|
# registers (`'hectic'`, versionString) into `hectic.version` and raises an
|
||||||
|
# exception on mismatch. Per-hook version rows are intentionally absent.
|
||||||
|
#
|
||||||
|
# Each entry exposes:
|
||||||
|
# * .sql — file contents as a string, with @HECTIC_VERSION@ substituted
|
||||||
|
# * .path — Nix store path (only on entries that need no substitution)
|
||||||
|
hectic = let
|
||||||
|
versionString = lib.fileContents ./hook/sql/HECTIC_VERSION;
|
||||||
|
static = path: { inherit path; sql = builtins.readFile path; };
|
||||||
|
templated = path: {
|
||||||
|
sql = builtins.replaceStrings
|
||||||
|
[ "@HECTIC_VERSION@" ]
|
||||||
|
[ versionString ]
|
||||||
|
(builtins.readFile path);
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
inherit versionString;
|
||||||
|
version = templated ./hook/sql/hectic-version.sql;
|
||||||
|
secret = static ./hook/sql/hectic-secret.sql;
|
||||||
|
migration = static ./hook/sql/hectic-migration.sql;
|
||||||
|
inheritance = static ./hook/sql/hectic-inheritance.sql;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Back-compat alias. Prefer `self.lib.hectic.inheritance`.
|
||||||
hecticInheritance = let
|
hecticInheritance = let
|
||||||
path = ../package/db-tool/sql/hectic-inheritance.sql;
|
path = ./hook/sql/hectic-inheritance.sql;
|
||||||
in {
|
in {
|
||||||
inherit path;
|
inherit path;
|
||||||
sql = builtins.readFile path;
|
sql = builtins.readFile path;
|
||||||
|
|||||||
1
lib/hook/sql/HECTIC_VERSION
Normal file
1
lib/hook/sql/HECTIC_VERSION
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0.1.0
|
||||||
51
lib/hook/sql/hectic-migration.sql
Normal file
51
lib/hook/sql/hectic-migration.sql
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
DO $bootstrap$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||||
|
WHERE n.nspname = 'hectic' AND t.typname = 'migration_name'
|
||||||
|
) THEN
|
||||||
|
CREATE DOMAIN "hectic"."migration_name" AS TEXT
|
||||||
|
CHECK (VALUE ~ '^[0-9]{14}-.*');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||||
|
WHERE n.nspname = 'hectic' AND t.typname = 'sha256'
|
||||||
|
) THEN
|
||||||
|
CREATE DOMAIN "hectic"."sha256" AS CHAR(64)
|
||||||
|
CHECK (VALUE ~ '^[0-9a-f]{64}$');
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$bootstrap$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."sha256_lower"() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
BEGIN
|
||||||
|
NEW."hash" := lower(NEW."hash");
|
||||||
|
RETURN NEW;
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "hectic"."migration" (
|
||||||
|
"id" SERIAL PRIMARY KEY,
|
||||||
|
"name" "hectic"."migration_name" UNIQUE NOT NULL,
|
||||||
|
"hash" "hectic"."sha256" UNIQUE NOT NULL,
|
||||||
|
"applied_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
DO $trg$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_trigger
|
||||||
|
WHERE tgname = 'hectic_t_sha256_lower'
|
||||||
|
AND tgrelid = '"hectic"."migration"'::regclass
|
||||||
|
AND NOT tgisinternal
|
||||||
|
) THEN
|
||||||
|
CREATE TRIGGER "hectic_t_sha256_lower"
|
||||||
|
BEFORE INSERT OR UPDATE ON "hectic"."migration"
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION "hectic"."sha256_lower"();
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$trg$;
|
||||||
51
lib/hook/sql/hectic-secret.sql
Normal file
51
lib/hook/sql/hectic-secret.sql
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "hectic"."secret" (
|
||||||
|
"id" SERIAL PRIMARY KEY,
|
||||||
|
"key" TEXT UNIQUE NOT NULL,
|
||||||
|
"value" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."load_secrets_from_env"(env_content TEXT)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
DECLARE
|
||||||
|
line TEXT;
|
||||||
|
k TEXT;
|
||||||
|
v TEXT;
|
||||||
|
BEGIN
|
||||||
|
TRUNCATE TABLE "hectic"."secret";
|
||||||
|
|
||||||
|
FOR line IN
|
||||||
|
SELECT regexp_split_to_table(env_content, E'\n')
|
||||||
|
LOOP
|
||||||
|
line := btrim(line);
|
||||||
|
|
||||||
|
IF line = '' OR line LIKE '#%' THEN
|
||||||
|
CONTINUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
k := split_part(line, '=', 1);
|
||||||
|
v := substring(line FROM position('=' IN line) + 1);
|
||||||
|
|
||||||
|
k := btrim(k);
|
||||||
|
v := btrim(v);
|
||||||
|
|
||||||
|
IF v ~ '^".*"$' OR v ~ '^''.*''$' THEN
|
||||||
|
v := substring(v FROM 2 FOR char_length(v) - 2);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
INSERT INTO "hectic"."secret" ("key", "value") VALUES (k, v);
|
||||||
|
END LOOP;
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."get_secret"(k TEXT)
|
||||||
|
RETURNS TEXT
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
BEGIN
|
||||||
|
RETURN (
|
||||||
|
SELECT "value"
|
||||||
|
FROM "hectic"."secret"
|
||||||
|
WHERE "key" = k
|
||||||
|
);
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
26
lib/hook/sql/hectic-version.sql
Normal file
26
lib/hook/sql/hectic-version.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
CREATE SCHEMA IF NOT EXISTS "hectic";
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "hectic"."version" (
|
||||||
|
"name" TEXT PRIMARY KEY,
|
||||||
|
"version" TEXT NOT NULL,
|
||||||
|
"installed_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
DO $check$
|
||||||
|
DECLARE
|
||||||
|
existing TEXT;
|
||||||
|
BEGIN
|
||||||
|
SELECT "version" INTO existing
|
||||||
|
FROM "hectic"."version"
|
||||||
|
WHERE "name" = 'hectic';
|
||||||
|
|
||||||
|
IF existing IS NULL THEN
|
||||||
|
INSERT INTO "hectic"."version" ("name", "version")
|
||||||
|
VALUES ('hectic', '@HECTIC_VERSION@');
|
||||||
|
ELSIF existing <> '@HECTIC_VERSION@' THEN
|
||||||
|
RAISE EXCEPTION
|
||||||
|
'hectic schema version mismatch: database has %, code expects %',
|
||||||
|
existing, '@HECTIC_VERSION@';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$check$;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
let
|
let
|
||||||
shell = "${dash}/bin/dash";
|
shell = "${dash}/bin/dash";
|
||||||
|
|
||||||
hecticInheritanceSqlPath = ./sql/hectic-inheritance.sql;
|
hecticInheritanceSqlPath = ../../lib/hook/sql/hectic-inheritance.sql;
|
||||||
|
|
||||||
hecticInheritance = runCommand "hectic-inheritance" { } ''
|
hecticInheritance = runCommand "hectic-inheritance" { } ''
|
||||||
mkdir -p "$out/share/hectic"
|
mkdir -p "$out/share/hectic"
|
||||||
|
|||||||
Reference in New Issue
Block a user