dataflow/database/status.sql
Paul Trowbridge 2ea2548715 Flatten database/queries into database/ and fix five stale functions
database/functions.sql held a full pg_dump appended onto the original
hand-written file, duplicating ~50 functions that had since been split into
database/queries/. Nothing deployed it, but CLAUDE.md and the tutorial still
told you to psql it, which would have reverted the split versions.

The reverse had also happened: five functions in queries/ were behind the live
database, all of them undoing the May 2026 split of the transformed column.
preview_rule lost its data -> transformed fallback for chained rules;
set_/clear_/bulk_set_record_overrides wrote overrides back into transformed and
returned the wrong type (which would have made the redeploy error outright);
generate_source_view read only transformed instead of merging all three layers.
Those are corrected here from the live definitions.

generate_source_view additionally regains the _overridden column that queries/
had and live lacked — Records.jsx reads row._overridden to highlight manually
edited rows, so that indicator had been dead.

The seven functions that existed only in functions.sql move to two new files,
import.sql (import + audit trail) and transform.sql (the rule/mapping engine),
leaving database/ flat: schema.sql plus one file per route. The four already
applied migrate_*.sql scripts are removed.

manage.py picks up the new files in QUERY_FILES, and its DB_ACTIONS set now
keys off the action functions rather than duplicated label strings that no
longer matched any menu entry, so the "into database X" hint renders again.

uninstall.sh is folded into manage.py as menu option 10. Beyond what the script
did, it stops/disables/removes the systemd unit, removes the nginx site with an
nginx -t check before reloading, and deletes public/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 21:55:37 -04:00

87 lines
3.4 KiB
PL/PgSQL

--
-- Status tracking: view_generated_at on sources and stacks
-- Cleared by triggers when definitions change; set by API when views are generated.
--
SET search_path TO dataflow, public;
-- Add view_generated_at columns
ALTER TABLE dataflow.sources ADD COLUMN IF NOT EXISTS view_generated_at TIMESTAMPTZ;
ALTER TABLE dataflow.stacks ADD COLUMN IF NOT EXISTS view_generated_at TIMESTAMPTZ;
------------------------------------------------------
-- Trigger: clear source view_generated_at when config (field definitions) changes
-- Rules and mappings affect transformed data, not view structure — no trigger needed there
------------------------------------------------------
DROP TRIGGER IF EXISTS trg_rules_changed ON dataflow.rules;
DROP TRIGGER IF EXISTS trg_mappings_changed ON dataflow.mappings;
DROP FUNCTION IF EXISTS dataflow.rules_changed();
DROP FUNCTION IF EXISTS dataflow.mappings_changed();
CREATE OR REPLACE FUNCTION dataflow.source_config_changed()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.config IS DISTINCT FROM OLD.config THEN
NEW.view_generated_at := NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_source_config_changed ON dataflow.sources;
CREATE TRIGGER trg_source_config_changed
BEFORE UPDATE ON dataflow.sources
FOR EACH ROW EXECUTE FUNCTION dataflow.source_config_changed();
------------------------------------------------------
-- Trigger: clear stack view_generated_at when sources change
-- On UPDATE, skip if all view-relevant columns are unchanged (upsert no-ops should not mark stale)
------------------------------------------------------
CREATE OR REPLACE FUNCTION dataflow.stack_sources_changed()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
IF NEW.field_map IS NOT DISTINCT FROM OLD.field_map AND
NEW.amount_sign IS NOT DISTINCT FROM OLD.amount_sign AND
NEW.balance_offset IS NOT DISTINCT FROM OLD.balance_offset AND
NEW.amount_field IS NOT DISTINCT FROM OLD.amount_field AND
NEW.date_field IS NOT DISTINCT FROM OLD.date_field AND
NEW.seq IS NOT DISTINCT FROM OLD.seq THEN
RETURN NULL;
END IF;
END IF;
UPDATE dataflow.stacks SET view_generated_at = NULL
WHERE name = COALESCE(NEW.stack_name, OLD.stack_name);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_stack_sources_changed ON dataflow.stack_sources;
CREATE TRIGGER trg_stack_sources_changed
AFTER INSERT OR UPDATE OR DELETE ON dataflow.stack_sources
FOR EACH ROW EXECUTE FUNCTION dataflow.stack_sources_changed();
------------------------------------------------------
-- Function: get_status
-- Returns sources and stacks whose view is stale (null or never generated)
------------------------------------------------------
CREATE OR REPLACE FUNCTION get_status()
RETURNS JSON AS $$
DECLARE
v_sources JSON;
v_stacks JSON;
BEGIN
SELECT COALESCE(json_agg(json_build_object('name', name, 'view_generated_at', view_generated_at) ORDER BY name), '[]'::json)
INTO v_sources
FROM dataflow.sources
WHERE view_generated_at IS NULL;
SELECT COALESCE(json_agg(json_build_object('name', name, 'view_generated_at', view_generated_at) ORDER BY name), '[]'::json)
INTO v_stacks
FROM dataflow.stacks
WHERE view_generated_at IS NULL;
RETURN json_build_object('stale_sources', v_sources, 'stale_stacks', v_stacks);
END;
$$ LANGUAGE plpgsql STABLE;