Reworked balancing and rebalancing

This commit is contained in:
Florian "flowdy" Heß 2017-01-22 21:20:17 +01:00
parent 336185aee1
commit 84d913836e
11 changed files with 115 additions and 117 deletions

View File

@ -59,7 +59,8 @@ CREATE TABLE Transfer (
); );
-- For internal purposes: Memory of rebalance triggers -- For internal purposes: Memory of rebalance triggers
CREATE TABLE __DO_NOT_MANIPULATE__trigger_memory (d, c, m); -- Do not fiddle with it! I.e. if you do, don't expect any support.
CREATE TABLE __INTERNAL_TRIGGER_STACK (id, d, c, m);
-- Only for use of HTTP interface -- Only for use of HTTP interface
CREATE TABLE web_auth ( user_id primary key, password, grade not null, username, email ); CREATE TABLE web_auth ( user_id primary key, password, grade not null, username, email );

View File

@ -1,5 +1,5 @@
CREATE TRIGGER balanceTransfer CREATE TRIGGER linkTransferTightly
AFTER INSERT ON Transfer AFTER INSERT ON Transfer
BEGIN BEGIN
SELECT RAISE(FAIL, "It is not the debtor who is set to pay") SELECT RAISE(FAIL, "It is not the debtor who is set to pay")
@ -16,51 +16,58 @@ BEGIN
HAVING count(d.billId) == 0 HAVING count(d.billId) == 0
; ;
INSERT INTO __DO_NOT_MANIPULATE__trigger_memory INSERT INTO __INTERNAL_TRIGGER_STACK
SELECT remainingDebt, remainingCredit, min(remainingDebt,remainingCredit) SELECT NEW.ROWID,
FROM (SELECT CASE remainingDebt WHEN 0 THEN RAISE(FAIL, "Debt settled") ELSE NEW.billId END,
(SELECT value - paid FROM Debit WHERE billId=NEW.billId) AS remainingDebt, CASE remainingCredit WHEN 0 THEN RAISE(FAIL, "Credit spent") ELSE NEW.credId END,
(SELECT value - spent FROM Credit WHERE credId=NEW.credId) AS remainingCredit min(remainingDebt, remainingCredit)
) FROM (SELECT
; (SELECT value - paid FROM Debit WHERE billId=NEW.billId) AS remainingDebt,
(SELECT value - spent FROM Credit WHERE credId=NEW.credId) AS remainingCredit
UPDATE Debit
SET paid = paid + CASE
WHEN (SELECT d FROM __DO_NOT_MANIPULATE__trigger_memory) <= 0
THEN RAISE(FAIL, "Debt settled")
ELSE
(SELECT m FROM __DO_NOT_MANIPULATE__trigger_memory)
END
WHERE billId=NEW.billId;
UPDATE Credit
SET spent = spent + CASE
WHEN (SELECT c FROM __DO_NOT_MANIPULATE__trigger_memory) <= 0
THEN RAISE(FAIL, "Credit spent")
ELSE IFNULL(
(SELECT m FROM __DO_NOT_MANIPULATE__trigger_memory),
RAISE(FAIL,"Oops, lost __DO_NOT_MANIPULATE__trigger_memory record before increasing spent")
)
END
WHERE credId=NEW.credId;
UPDATE Transfer
SET amount = (SELECT m FROM __DO_NOT_MANIPULATE__trigger_memory)
WHERE billId=NEW.billId AND credId=NEW.credId
;
UPDATE Credit
SET value = value + IFNULL(
(SELECT m FROM __DO_NOT_MANIPULATE__trigger_memory),
RAISE(FAIL, "Oops, lost __DO_NOT_MANIPULATE__trigger_memory record before increasing value")
) )
WHERE credId = ( ;
SELECT targetCredit
FROM Debit END;
WHERE billId=NEW.billId
); CREATE TRIGGER reflectTransfer
AFTER INSERT ON __INTERNAL_TRIGGER_STACK
DELETE FROM __DO_NOT_MANIPULATE__trigger_memory; WHEN NEW.id > 0
BEGIN
UPDATE Debit
SET paid = paid + NEW.m
WHERE billId = NEW.d;
UPDATE Credit
SET spent = spent + NEW.m
WHERE credId = NEW.c;
UPDATE Transfer
SET amount = ifnull(amount,0) + NEW.m
WHERE ROWID = NEW.id;
UPDATE Credit
SET value = value + NEW.m
WHERE credId = (
SELECT targetCredit
FROM Debit
WHERE billId = NEW.d
);
DELETE FROM __INTERNAL_TRIGGER_STACK WHERE id=NEW.id;
END;
CREATE TRIGGER refreshTransfer
AFTER UPDATE OF amount ON Transfer
BEGIN
DELETE FROM Transfer
WHERE ROWID = NEW.ROWID AND amount = 0;
UPDATE Transfer
SET timestamp=CURRENT_TIMESTAMP
WHERE ROWID=NEW.ROWID;
END; END;

View File

@ -2,11 +2,8 @@
-- when new transfer records are inserted -- when new transfer records are inserted
CREATE TRIGGER enforceDebtImmutableOutsideTrigger CREATE TRIGGER enforceDebtImmutableOutsideTrigger
BEFORE UPDATE OF paid ON Debit BEFORE UPDATE OF paid ON Debit
WHEN NOT EXISTS (SELECT * FROM Transfer t WHERE NEW.billId=t.billId AND amount IS NULL) WHEN NOT EXISTS (SELECT * FROM __INTERNAL_TRIGGER_STACK LIMIT 1)
BEGIN BEGIN
SELECT RAISE(FAIL, "paid is set and adjusted automatically according to added Transfer records") SELECT RAISE(FAIL, "paid is set and adjusted automatically according to added Transfer records");
WHERE (NEW.paid + IFNULL(
(SELECT m FROM __DO_NOT_MANIPULATE__trigger_memory WHERE c IS NULL AND d IS NULL), 0
) ) <> OLD.paid;
END; END;

View File

@ -1,7 +1,7 @@
CREATE TRIGGER enforceFixedCredit CREATE TRIGGER enforceFixedCredit
BEFORE UPDATE OF account, value ON Credit BEFORE UPDATE OF account, value ON Credit
WHEN EXISTS (SELECT * FROM Transfer WHERE credId=NEW.credId) WHEN EXISTS (SELECT * FROM Transfer WHERE credId=NEW.credId)
AND NOT EXISTS (SELECT * FROM __DO_NOT_MANIPULATE__trigger_memory) AND NOT EXISTS (SELECT * FROM __INTERNAL_TRIGGER_STACK)
BEGIN BEGIN
SELECT RAISE(FAIL, "Credit involved in transactions to revoke at first"); SELECT RAISE(FAIL, "Credit involved in transactions to revoke at first");
END; END;

View File

@ -1,6 +1,7 @@
CREATE TRIGGER enforceFixedDebit CREATE TRIGGER enforceFixedDebit
BEFORE UPDATE OF debtor, transferCredit, value ON Debit BEFORE UPDATE OF debtor, transferCredit, value ON Debit
WHEN EXISTS (SELECT * FROM Transfer WHERE billId=NEW.billId) WHEN EXISTS (SELECT * FROM Transfer WHERE billId=NEW.billId)
AND NOT EXISTS (SELECT * FROM __INTERNAL_TRIGGER_STACK LIMIT 1)
BEGIN BEGIN
SELECT RAISE(FAIL, "Debt is involved in transfers to revoke at first"); SELECT RAISE(FAIL, "Debt is involved in transfers to revoke at first");
END; END;

View File

@ -1,6 +1,7 @@
CREATE TRIGGER enforceImmutableTransfer CREATE TRIGGER enforceImmutableTransfer
BEFORE UPDATE ON Transfer BEFORE UPDATE ON Transfer
WHEN OLD.amount IS NOT NULL WHEN OLD.amount IS NOT NULL
AND NOT EXISTS (SELECT * FROM __INTERNAL_TRIGGER_STACK)
BEGIN BEGIN
SELECT RAISE(FAIL, "Transfer cannot be updated, but needs to be replaced to make triggers run"); SELECT RAISE(FAIL, "Transfer cannot be updated, but needs to be replaced to make triggers run");
END; END;

View File

@ -2,11 +2,8 @@
-- when new transfer records are inserted -- when new transfer records are inserted
CREATE TRIGGER enforceSpentImmutableOutsideTrigger CREATE TRIGGER enforceSpentImmutableOutsideTrigger
BEFORE UPDATE OF spent ON Credit BEFORE UPDATE OF spent ON Credit
WHEN NOT EXISTS (SELECT * FROM Transfer t WHERE NEW.credId=t.credId AND amount IS NULL) WHEN NOT EXISTS (SELECT * FROM __INTERNAL_TRIGGER_STACK)
BEGIN BEGIN
SELECT RAISE(FAIL, "spent is set and adjusted automatically according to added Transfer records") SELECT RAISE(FAIL, "spent is set and adjusted automatically according to added Transfer records");
WHERE (NEW.spent + IFNULL(
(SELECT m FROM __DO_NOT_MANIPULATE__trigger_memory WHERE c IS NULL AND d IS NULL), 0
) ) <> OLD.spent;
END; END;

View File

@ -3,11 +3,12 @@
-- That way, a transfer may issue recursively chained transfers. -- That way, a transfer may issue recursively chained transfers.
CREATE TRIGGER rebalanceIncreasedCredit CREATE TRIGGER rebalanceIncreasedCredit
AFTER UPDATE OF value ON Credit AFTER UPDATE OF value ON Credit
WHEN NEW.value > OLD.spent WHEN NEW.value > OLD.value
BEGIN BEGIN
REPLACE INTO Transfer (credId, billId) INSERT INTO __INTERNAL_TRIGGER_STACK
SELECT OLD.credId, t.billId SELECT t.ROWID, NEW.value, ca.difference,
min(ca.difference, NEW.value - OLD.value)
FROM Transfer t FROM Transfer t
JOIN CurrentArrears ca ON t.billId = ca.billId JOIN CurrentArrears ca ON t.billId = ca.billId
WHERE OLD.credId = t.credId WHERE OLD.credId = t.credId

View File

@ -2,42 +2,50 @@
-- check if we can still have paid the debts linked to this transfer in an "is paid from" relation, -- check if we can still have paid the debts linked to this transfer in an "is paid from" relation,
-- otherwise we have to revoke these transfers as well. -- otherwise we have to revoke these transfers as well.
CREATE TRIGGER rebalanceReducedCredit CREATE TRIGGER rebalanceReducedCredit
AFTER UPDATE OF value ON Credit BEFORE UPDATE OF value ON Credit
WHEN NEW.value < OLD.spent WHEN NEW.value < OLD.spent
BEGIN BEGIN
INSERT INTO __INTERNAL_TRIGGER_STACK (id) VALUES (-NEW.credId);
REPLACE INTO __DO_NOT_MANIPULATE__trigger_memory (d, m) UPDATE __INTERNAL_TRIGGER_STACK SET c=OLD.spent-NEW.value;
SELECT DELETE FROM __INTERNAL_TRIGGER_STACK WHERE id=-NEW.credId;
'from_' || NEW.credId,
billId
FROM Transfer
WHERE credId = NEW.credId
ORDER BY timestamp DESC
LIMIT 1
;
DELETE
FROM Transfer
WHERE credId = NEW.credId
AND billId IN (
SELECT billId
FROM __DO_NOT_MANIPULATE__trigger_memory
WHERE c = 'from_' || NEW.credId
)
;
INSERT INTO Transfer (credId, billId)
SELECT NEW.credId, m
FROM __DO_NOT_MANIPULATE__trigger_memory
WHERE d = 'from_' || NEW.credId
AND NEW.value > (
SELECT spent
FROM Credit
WHERE credId = NEW.credId
)
;
DELETE FROM __DO_NOT_MANIPULATE__trigger_memory WHERE d = 'from_' || NEW.credId;
END; END;
CREATE TRIGGER _inner_rebalanceReducedCredit
AFTER UPDATE OF c ON __INTERNAL_TRIGGER_STACK
BEGIN
UPDATE __INTERNAL_TRIGGER_STACK
SET d = (
SELECT ROWID
FROM Transfer
WHERE credId = abs(OLD.id)
ORDER BY timestamp DESC
LIMIT 1
)
WHERE id = OLD.id
;
UPDATE __INTERNAL_TRIGGER_STACK
SET m = min((
SELECT amount
FROM Transfer
WHERE credId = abs(OLD.id)
ORDER BY timestamp DESC
LIMIT 1
), NEW.c)
WHERE id = OLD.id
;
INSERT INTO __INTERNAL_TRIGGER_STACK
SELECT d, billId, abs(OLD.id), -m
FROM __INTERNAL_TRIGGER_STACK s
JOIN Transfer t ON t.ROWID=d
WHERE id = OLD.id AND m > 0
;
UPDATE __INTERNAL_TRIGGER_STACK
SET c = NEW.c-m
WHERE id = OLD.id AND m > 0
; -- recurse
END;

View File

@ -1,27 +1,11 @@
CREATE TRIGGER revokeTransfer CREATE TRIGGER revokeTransfer
BEFORE DELETE ON Transfer BEFORE DELETE ON Transfer
WHEN OLD.amount > 0
BEGIN BEGIN
INSERT INTO __DO_NOT_MANIPULATE__trigger_memory VALUES (null,null,OLD.amount); INSERT INTO __INTERNAL_TRIGGER_STACK VALUES (
OLD.ROWID, OLD.billId, OLD.credId, -OLD.amount
UPDATE Debit );
SET paid = paid - OLD.amount
WHERE billId=OLD.billId
;
UPDATE Credit
SET value = value - OLD.amount
WHERE credId = (
SELECT targetCredit
FROM Debit
WHERE billId=OLD.billId
);
UPDATE Credit
SET spent = spent - OLD.amount
WHERE credId = OLD.credId;
DELETE FROM __DO_NOT_MANIPULATE__trigger_memory;
END; END;

View File

@ -125,5 +125,6 @@ $db->make_transfers( q{*} => q{*} );
is $rose->available_credits->get_column("difference")->sum, 3600, "partial use of credit"; is $rose->available_credits->get_column("difference")->sum, 3600, "partial use of credit";
is $db->resultset("Balance")->find("alex")->earned, '10800', 'indirect transfers'; is $db->resultset("Balance")->find("alex")->earned, '10800', 'indirect transfers';
is $db->resultset("Balance")->find("Club")->available, '0', ' ... spent increased';
done_testing(); done_testing();