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>
87 lines
3.4 KiB
PL/PgSQL
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;
|