From 2f601a0b1ac9750e19373f267d36129870273b07 Mon Sep 17 00:00:00 2001 From: TMA Date: Fri, 11 Apr 2025 13:39:07 +0200 Subject: [PATCH 1/4] functions for querying sequence values for read only access --- brmbar3/SQL-for-RO-access.sql | 57 +++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 brmbar3/SQL-for-RO-access.sql diff --git a/brmbar3/SQL-for-RO-access.sql b/brmbar3/SQL-for-RO-access.sql new file mode 100644 index 0000000..b8c7291 --- /dev/null +++ b/brmbar3/SQL-for-RO-access.sql @@ -0,0 +1,57 @@ +CREATE OR REPLACE FUNCTION accounts_id_seq_value() +RETURNS bigint +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + result bigint; +BEGIN + SELECT last_value FROM accounts_id_seq + INTO result; + RETURN result; +END; +$$; + +CREATE OR REPLACE FUNCTION transactions_id_seq_value() +RETURNS bigint +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + result bigint; +BEGIN + SELECT last_value FROM transactions_id_seq + INTO result; + RETURN result; +END; +$$; + +CREATE OR REPLACE FUNCTION transaction_splits_id_seq_value() +RETURNS bigint +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + result bigint; +BEGIN + SELECT last_value FROM transaction_splits_id_seq + INTO result; + RETURN result; +END; +$$; + +CREATE OR REPLACE FUNCTION currencies_id_seq_value() +RETURNS bigint +LANGUAGE plpgsql +SECURITY DEFINER +AS $$ +DECLARE + result bigint; +BEGIN + SELECT last_value FROM currencies_id_seq + INTO result; + RETURN result; +END; +$$; + + From 3000731ac7b4bbcba045276a0df93acbb02b9af8 Mon Sep 17 00:00:00 2001 From: TMA Date: Fri, 11 Apr 2025 20:56:17 +0200 Subject: [PATCH 2/4] SQL schema v002 script part 1 --- brmbar3/SQL | 4 +- brmbar3/SQL-schema-v002.sql | 80 +++++++++++++++++++++++++++++++++++++ brmbar3/brmbar/Shop.py | 17 ++++---- 3 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 brmbar3/SQL-schema-v002.sql diff --git a/brmbar3/SQL b/brmbar3/SQL index a61dba8..04667f4 100644 --- a/brmbar3/SQL +++ b/brmbar3/SQL @@ -1,11 +1,11 @@ -CREATE SEQUENCE currencies_id_seq START WITH 1 INCREMENT BY 1; +CREATE SEQUENCE currencies_id_seq START WITH 2 INCREMENT BY 1; CREATE TABLE currencies ( id INTEGER PRIMARY KEY NOT NULL DEFAULT NEXTVAL('currencies_id_seq'::regclass), name VARCHAR(128) NOT NULL, UNIQUE(name) ); -- Some code depends on the primary physical currency to have id 1. -INSERT INTO currencies (name) VALUES ('Kč'); +INSERT INTO currencies (id, name) VALUES (1, 'Kč'); CREATE TYPE exchange_rate_direction AS ENUM ('source_to_target', 'target_to_source'); CREATE TABLE exchange_rates ( diff --git a/brmbar3/SQL-schema-v002.sql b/brmbar3/SQL-schema-v002.sql new file mode 100644 index 0000000..afe6c80 --- /dev/null +++ b/brmbar3/SQL-schema-v002.sql @@ -0,0 +1,80 @@ +-- intoduce implementation schema +CREATE SCHEMA IF NOT EXISTS brmbar_implementation; +-- version table (with initialization) +CREATE TABLE IF NOT EXISTS brmbar_implementation.brmbar_schema ( + ver INTEGER NOT NULL +); +DO $$ +DECLARE v INTEGER; +BEGIN + SELECT ver FROM brmbar_implementation.brmbar_schema INTO v; + IF v IS NULL THEN + INSERT INTO brmbar_implementation.brmbar_schema (ver) VALUES (1); + END IF; +END; +$$; + + +--- upgrade schema +DO $$ +DECLARE +current_ver INTEGER; +BEGIN + +SELECT ver FROM brmbar_implementation.brmbar_schema INTO current_ver; +IF current_ver <> 1 THEN + RAISE EXCEPTION 'BrmBar schema version % cannot be upgraded to version 2.', current_ver; +END IF; + +ALTER TYPE public.account_type ADD VALUE 'trading'; + +--drop function if exists public.create_currency; +CREATE OR REPLACE PROCEDURE public.create_currency( + IN i_name public.currencies.name%TYPE, + OUT o_id public.currencies.id%TYPE) +LANGUAGE plpgsql SECURITY DEFINER AS $fn$ +BEGIN + INSERT INTO currencies ("name") VALUES (i_name) RETURNING o_id; +END +$fn$; + +CREATE OR REPLACE PROCEDURE public.undo_transaction( + IN i_id public.transactions.id%TYPE, + OUT o_id public.transactions.id%TYPE) +LANGUAGE plpgsql SECURITY DEFINER AS $fn$ +DECLARE + v_ntrn_id public.transactions.id%TYPE; + v_old_trn public.transactions%ROWTYPE; + v_old_split public.transaction_splits%ROWTYPE; +BEGIN + SELECT * INTO v_old_trn FROM public.transactions WHERE id = i_id; + INSERT INTO transactions ("description") VALUES ('undo '||o_id||' ('||v_old_trn.description||')') RETURNING id into v_ntrn_id; + FOR v_old_split IN + SELECT * FROM transaction_splits WHERE "transaction" = i_id + LOOP + INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo") + VALUES (v_ntrn_id, v_old_split.side, v_old_split.account, -v_old_split.amount, + 'undo ' || v_old_split.id || ' (' || v_old_split.memo || ')' ); + END LOOP; + o_id := v_ntrn_id; +END +$fn$; + +-- this is for the case that psycopg2 cannot do output arguments +CREATE OR REPLACE FUNCTION public.undo_transaction_fn( + IN i_id public.transactions.id%TYPE) +RETURNS public.transactions.id%TYPE +VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$ +DECLARE + v_ntrn_id public.transactions.id%TYPE; +BEGIN + CALL public.undo_transaction(i_id, v_ntrn_id); + RETURN v_ntrn_id; +END +$fn$; + + +-- end of upgrade do block +end +$$; + diff --git a/brmbar3/brmbar/Shop.py b/brmbar3/brmbar/Shop.py index 6027084..5049fcc 100644 --- a/brmbar3/brmbar/Shop.py +++ b/brmbar3/brmbar/Shop.py @@ -219,14 +219,15 @@ class Shop: self.db.commit() def undo(self, oldtid): - description = self.db.execute_and_fetch("SELECT description FROM transactions WHERE id = %s", [oldtid])[0] - description = 'undo %d (%s)' % (oldtid, description) + #description = self.db.execute_and_fetch("SELECT description FROM transactions WHERE id = %s", [oldtid])[0] + #description = 'undo %d (%s)' % (oldtid, description) - transaction = self._transaction(description=description) - for split in self.db.execute_and_fetchall("SELECT id, side, account, amount, memo FROM transaction_splits WHERE transaction = %s", [oldtid]): - splitid, side, account, amount, memo = split - memo = 'undo %d (%s)' % (splitid, memo) - amount = -amount - self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, account, amount, memo]) + #transaction = self._transaction(description=description) + #for split in self.db.execute_and_fetchall("SELECT id, side, account, amount, memo FROM transaction_splits WHERE transaction = %s", [oldtid]): + # splitid, side, account, amount, memo = split + # memo = 'undo %d (%s)' % (splitid, memo) + # amount = -amount + # self.db.execute("INSERT INTO transaction_splits (transaction, side, account, amount, memo) VALUES (%s, %s, %s, %s, %s)", [transaction, side, account, amount, memo]) + transaction = self.db.execute_and_fetch("CALL undo_transaction(%s)",[oldtid])[0] self.db.commit() return transaction From f6ec2be215bbfbdf99156507b5e53fc0a185c5ac Mon Sep 17 00:00:00 2001 From: TMA Date: Sat, 19 Apr 2025 22:44:01 +0200 Subject: [PATCH 3/4] schema v1 setup script --- brmbar3/SQL-schema-v001.sql | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 brmbar3/SQL-schema-v001.sql diff --git a/brmbar3/SQL-schema-v001.sql b/brmbar3/SQL-schema-v001.sql new file mode 100644 index 0000000..44b9693 --- /dev/null +++ b/brmbar3/SQL-schema-v001.sql @@ -0,0 +1,59 @@ +--RESET search_path; +SELECT pg_catalog.set_config('search_path', '', false); +-- intoduce implementation schema +CREATE SCHEMA IF NOT EXISTS brmbar_implementation; +-- version table (with initialization) +CREATE TABLE IF NOT EXISTS brmbar_implementation.brmbar_schema ( + ver INTEGER NOT NULL +); +DO $$ +DECLARE v INTEGER; +BEGIN + SELECT ver FROM brmbar_implementation.brmbar_schema INTO v; + IF v IS NULL THEN + INSERT INTO brmbar_implementation.brmbar_schema (ver) VALUES (1); + END IF; +END; +$$; + +CREATE OR REPLACE FUNCTION brmbar_implementation.has_exact_schema_version( + IN i_ver INTEGER NOT NULL +) RETURNS INTEGER +VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $$ +DECLARE + v_ver INTEGER; +BEGIN + SELECT ver INTO STRICT v_ver FROM brmbar_implementation.brmbar_schema; + IF v_ver IS NULL or v_ver <> i_ver THEN + RAISE EXCEPTION 'Invalid brmbar schema version'; + END IF; + RETURN v_ver; +/* +EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'PID % not found'; + WHEN TOO_MANY_ROWS THEN + RAISE EXCEPTION 'PID % not unique'; +*/ +END; +$$; + +CREATE OR REPLACE FUNCTION brmbar_implementation.upgrade_schema_version_to( + IN i_ver INTEGER NOT NULL +) RETURNS INTEGER +VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $$ +DECLARE + v_ver INTEGER; +BEGIN + SELECT brmbar_implementation.has_exact_schema_version(i_ver) INTO v_ver; + IF v_ver + 1 = i_ver THEN + UPDATE brmbar_implementation.brmbar_schema SET ver = i_ver; + ELSE + RAISE EXCEPTION 'Invalid brmbar schema version'; + END IF; + RETURN i_ver; +END; +$$; + +-- vim: set ft=plsql : + From 900264469a9f925749246477b7803b052b91ae07 Mon Sep 17 00:00:00 2001 From: TMA Date: Sun, 20 Apr 2025 10:13:31 +0200 Subject: [PATCH 4/4] schema v2 setup script --- brmbar3/SQL-schema-v002.sql | 105 +++++++++++++++--------------------- 1 file changed, 43 insertions(+), 62 deletions(-) diff --git a/brmbar3/SQL-schema-v002.sql b/brmbar3/SQL-schema-v002.sql index afe6c80..bfb63fa 100644 --- a/brmbar3/SQL-schema-v002.sql +++ b/brmbar3/SQL-schema-v002.sql @@ -1,80 +1,61 @@ --- intoduce implementation schema -CREATE SCHEMA IF NOT EXISTS brmbar_implementation; --- version table (with initialization) -CREATE TABLE IF NOT EXISTS brmbar_implementation.brmbar_schema ( - ver INTEGER NOT NULL -); -DO $$ -DECLARE v INTEGER; -BEGIN - SELECT ver FROM brmbar_implementation.brmbar_schema INTO v; - IF v IS NULL THEN - INSERT INTO brmbar_implementation.brmbar_schema (ver) VALUES (1); - END IF; -END; -$$; - +--RESET search_path +SELECT pg_catalog.set_config('search_path', '', false); --- upgrade schema -DO $$ +DO $upgrade_block$ DECLARE current_ver INTEGER; BEGIN -SELECT ver FROM brmbar_implementation.brmbar_schema INTO current_ver; +-- confirm that we are upgrading from version 1 +SELECT brmbar_implementation.has_exact_schema_version(1) INTO current_ver; IF current_ver <> 1 THEN RAISE EXCEPTION 'BrmBar schema version % cannot be upgraded to version 2.', current_ver; END IF; -ALTER TYPE public.account_type ADD VALUE 'trading'; +-- structural changes ---drop function if exists public.create_currency; -CREATE OR REPLACE PROCEDURE public.create_currency( - IN i_name public.currencies.name%TYPE, - OUT o_id public.currencies.id%TYPE) -LANGUAGE plpgsql SECURITY DEFINER AS $fn$ -BEGIN - INSERT INTO currencies ("name") VALUES (i_name) RETURNING o_id; -END -$fn$; +-- TRADING ACCOUNTS +--START TRANSACTION ISOLATION LEVEL SERIALIZABLE; -CREATE OR REPLACE PROCEDURE public.undo_transaction( - IN i_id public.transactions.id%TYPE, - OUT o_id public.transactions.id%TYPE) -LANGUAGE plpgsql SECURITY DEFINER AS $fn$ -DECLARE - v_ntrn_id public.transactions.id%TYPE; - v_old_trn public.transactions%ROWTYPE; - v_old_split public.transaction_splits%ROWTYPE; -BEGIN - SELECT * INTO v_old_trn FROM public.transactions WHERE id = i_id; - INSERT INTO transactions ("description") VALUES ('undo '||o_id||' ('||v_old_trn.description||')') RETURNING id into v_ntrn_id; - FOR v_old_split IN - SELECT * FROM transaction_splits WHERE "transaction" = i_id - LOOP - INSERT INTO transaction_splits ("transaction", "side", "account", "amount", "memo") - VALUES (v_ntrn_id, v_old_split.side, v_old_split.account, -v_old_split.amount, - 'undo ' || v_old_split.id || ' (' || v_old_split.memo || ')' ); - END LOOP; - o_id := v_ntrn_id; -END -$fn$; +-- currency trading accounts - account type +ALTER TYPE public.account_type ADD VALUE IF NOT EXISTS 'trading'; --- this is for the case that psycopg2 cannot do output arguments -CREATE OR REPLACE FUNCTION public.undo_transaction_fn( - IN i_id public.transactions.id%TYPE) -RETURNS public.transactions.id%TYPE -VOLATILE NOT LEAKPROOF LANGUAGE plpgsql AS $fn$ -DECLARE - v_ntrn_id public.transactions.id%TYPE; -BEGIN - CALL public.undo_transaction(i_id, v_ntrn_id); - RETURN v_ntrn_id; -END -$fn$; +-- constraint needed for foreign key in currencies table +ALTER TABLE public.accounts ADD CONSTRAINT accounts_id_acctype_key UNIQUE(id, acctype); + +-- add columns to currencies to record the trading account associated with the currency +ALTER TABLE public.currencies + ADD COLUMN IF NOT EXISTS trading_account integer, + ADD COLUMN IF NOT EXISTS trading_account_type account_type GENERATED ALWAYS AS ('trading'::public.account_type) STORED; + +-- make trading accounts (without making duplicates) +INSERT INTO public.accounts ("name", "currency", acctype) +SELECT + 'Currency Trading Account: ' || c."name", + c.id, + 'trading'::public.account_type +FROM public.currencies AS c +WHERE NOT EXISTS ( + SELECT 1 + FROM public.accounts a + WHERE a.currency = c.id AND a.acctype = 'trading'::public.account_type +); +-- record the trading account IDs in currencies table +UPDATE public.currencies AS c SET (trading_account) = (SELECT a.id FROM public.accounts AS a WHERE a.currency = c.id AND c.acctype = 'trading'::public.account_type); + +-- foreign key to check the validity of currency trading account reference +ALTER TABLE public.currencies + ADD CONSTRAINT currencies_trading_fkey FOREIGN KEY (trading_account, trading_account_type) + REFERENCES xaccounts(id,acctype) DEFERRABLE INITIALLY DEFERRED; + +--COMMIT AND CHAIN; + +SELECT brmbar_implementation.upgrade_schema_version_to(2) INTO current_ver; -- end of upgrade do block end -$$; +$upgrade_block$; +-- vim: set ft=plsql :