From cb87b6f646888bf62af31e8b23bb642de9a57344 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 27 Mar 2023 07:23:27 -0400 Subject: [PATCH] New spi files --- .../exchange_do_batch_coin_known.sql | 477 ++++++++++ src/exchangedb/exchange_get_ready_deposit.sql | 60 ++ src/exchangedb/spi/README.md | 41 + src/exchangedb/spi/own_test.bc | Bin 0 -> 22876 bytes src/exchangedb/spi/own_test.c | 818 ++++++++++++++++++ src/exchangedb/spi/own_test.control | 4 + src/exchangedb/spi/own_test.so | Bin 0 -> 76824 bytes src/exchangedb/spi/own_test.sql | 216 +++++ src/exchangedb/spi/perf_own_test.c | 25 + src/exchangedb/spi/pg_aggregate.c | 389 +++++++++ 10 files changed, 2030 insertions(+) create mode 100644 src/exchangedb/exchange_do_batch_coin_known.sql create mode 100644 src/exchangedb/exchange_get_ready_deposit.sql create mode 100644 src/exchangedb/spi/README.md create mode 100644 src/exchangedb/spi/own_test.bc create mode 100644 src/exchangedb/spi/own_test.c create mode 100644 src/exchangedb/spi/own_test.control create mode 100755 src/exchangedb/spi/own_test.so create mode 100644 src/exchangedb/spi/own_test.sql create mode 100644 src/exchangedb/spi/perf_own_test.c create mode 100644 src/exchangedb/spi/pg_aggregate.c diff --git a/src/exchangedb/exchange_do_batch_coin_known.sql b/src/exchangedb/exchange_do_batch_coin_known.sql new file mode 100644 index 000000000..38d795959 --- /dev/null +++ b/src/exchangedb/exchange_do_batch_coin_known.sql @@ -0,0 +1,477 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2014--2022 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see +-- + +CREATE OR REPLACE FUNCTION exchange_do_batch4_known_coin( + IN in_coin_pub1 BYTEA, + IN in_denom_pub_hash1 BYTEA, + IN in_h_age_commitment1 BYTEA, + IN in_denom_sig1 BYTEA, + IN in_coin_pub2 BYTEA, + IN in_denom_pub_hash2 BYTEA, + IN in_h_age_commitment2 BYTEA, + IN in_denom_sig2 BYTEA, + IN in_coin_pub3 BYTEA, + IN in_denom_pub_hash3 BYTEA, + IN in_h_age_commitment3 BYTEA, + IN in_denom_sig3 BYTEA, + IN in_coin_pub4 BYTEA, + IN in_denom_pub_hash4 BYTEA, + IN in_h_age_commitment4 BYTEA, + IN in_denom_sig4 BYTEA, + OUT existed1 BOOLEAN, + OUT existed2 BOOLEAN, + OUT existed3 BOOLEAN, + OUT existed4 BOOLEAN, + OUT known_coin_id1 INT8, + OUT known_coin_id2 INT8, + OUT known_coin_id3 INT8, + OUT known_coin_id4 INT8, + OUT denom_pub_hash1 BYTEA, + OUT denom_pub_hash2 BYTEA, + OUT denom_pub_hash3 BYTEA, + OUT denom_pub_hash4 BYTEA, + OUT age_commitment_hash1 BYTEA, + OUT age_commitment_hash2 BYTEA, + OUT age_commitment_hash3 BYTEA, + OUT age_commitment_hash4 BYTEA) +LANGUAGE plpgsql +AS $$ +BEGIN +WITH dd AS ( +SELECT + denominations_serial, + coin_val, coin_frac + FROM denominations + WHERE denom_pub_hash + IN + (in_denom_pub_hash1, + in_denom_pub_hash2, + in_denom_pub_hash3, + in_denom_pub_hash4) + ),--dd + input_rows AS ( + VALUES + (in_coin_pub1, + in_denom_pub_hash1, + in_h_age_commitment1, + in_denom_sig1), + (in_coin_pub2, + in_denom_pub_hash2, + in_h_age_commitment2, + in_denom_sig2), + (in_coin_pub3, + in_denom_pub_hash3, + in_h_age_commitment3, + in_denom_sig3), + (in_coin_pub4, + in_denom_pub_hash4, + in_h_age_commitment4, + in_denom_sig4) + ),--ir + ins AS ( + INSERT INTO known_coins ( + coin_pub, + denominations_serial, + age_commitment_hash, + denom_sig, + remaining_val, + remaining_frac + ) + SELECT + ir.coin_pub, + dd.denominations_serial, + ir.age_commitment_hash, + ir.denom_sig, + dd.coin_val, + dd.coin_frac + FROM input_rows ir + JOIN dd + ON dd.denom_pub_hash = ir.denom_pub_hash + ON CONFLICT DO NOTHING + RETURNING known_coin_id + ),--kc + exists AS ( + SELECT + CASE + WHEN + ins.known_coin_id IS NOT NULL + THEN + FALSE + ELSE + TRUE + END AS existed, + ins.known_coin_id, + dd.denom_pub_hash, + kc.age_commitment_hash + FROM input_rows ir + LEFT JOIN ins + ON ins.coin_pub = ir.coin_pub + LEFT JOIN known_coins kc + ON kc.coin_pub = ir.coin_pub + LEFT JOIN dd + ON dd.denom_pub_hash = ir.denom_pub_hash + )--exists +SELECT + exists.existed AS existed1, + exists.known_coin_id AS known_coin_id1, + exists.denom_pub_hash AS denom_pub_hash1, + exists.age_commitment_hash AS age_commitment_hash1, + ( + SELECT exists.existed + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash2 + ) AS existed2, + ( + SELECT exists.known_coin_id + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash2 + ) AS known_coin_id2, + ( + SELECT exists.denom_pub_hash + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash2 + ) AS denom_pub_hash2, + ( + SELECT exists.age_commitment_hash + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash2 + )AS age_commitment_hash2, + ( + SELECT exists.existed + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash3 + ) AS existed3, + ( + SELECT exists.known_coin_id + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash3 + ) AS known_coin_id3, + ( + SELECT exists.denom_pub_hash + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash3 + ) AS denom_pub_hash3, + ( + SELECT exists.age_commitment_hash + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash3 + )AS age_commitment_hash3, + ( + SELECT exists.existed + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash4 + ) AS existed4, + ( + SELECT exists.known_coin_id + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash4 + ) AS known_coin_id4, + ( + SELECT exists.denom_pub_hash + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash4 + ) AS denom_pub_hash4, + ( + SELECT exists.age_commitment_hash + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash4 + )AS age_commitment_hash4 +FROM exists; + +RETURN; +END $$; + + +CREATE OR REPLACE FUNCTION exchange_do_batch2_known_coin( + IN in_coin_pub1 BYTEA, + IN in_denom_pub_hash1 BYTEA, + IN in_h_age_commitment1 BYTEA, + IN in_denom_sig1 BYTEA, + IN in_coin_pub2 BYTEA, + IN in_denom_pub_hash2 BYTEA, + IN in_h_age_commitment2 BYTEA, + IN in_denom_sig2 BYTEA, + OUT existed1 BOOLEAN, + OUT existed2 BOOLEAN, + OUT known_coin_id1 INT8, + OUT known_coin_id2 INT8, + OUT denom_pub_hash1 BYTEA, + OUT denom_pub_hash2 BYTEA, + OUT age_commitment_hash1 BYTEA, + OUT age_commitment_hash2 BYTEA) +LANGUAGE plpgsql +AS $$ +BEGIN +WITH dd AS ( +SELECT + denominations_serial, + coin_val, coin_frac + FROM denominations + WHERE denom_pub_hash + IN + (in_denom_pub_hash1, + in_denom_pub_hash2) + ),--dd + input_rows AS ( + VALUES + (in_coin_pub1, + in_denom_pub_hash1, + in_h_age_commitment1, + in_denom_sig1), + (in_coin_pub2, + in_denom_pub_hash2, + in_h_age_commitment2, + in_denom_sig2) + ),--ir + ins AS ( + INSERT INTO known_coins ( + coin_pub, + denominations_serial, + age_commitment_hash, + denom_sig, + remaining_val, + remaining_frac + ) + SELECT + ir.coin_pub, + dd.denominations_serial, + ir.age_commitment_hash, + ir.denom_sig, + dd.coin_val, + dd.coin_frac + FROM input_rows ir + JOIN dd + ON dd.denom_pub_hash = ir.denom_pub_hash + ON CONFLICT DO NOTHING + RETURNING known_coin_id + ),--kc + exists AS ( + SELECT + CASE + WHEN ins.known_coin_id IS NOT NULL + THEN + FALSE + ELSE + TRUE + END AS existed, + ins.known_coin_id, + dd.denom_pub_hash, + kc.age_commitment_hash + FROM input_rows ir + LEFT JOIN ins + ON ins.coin_pub = ir.coin_pub + LEFT JOIN known_coins kc + ON kc.coin_pub = ir.coin_pub + LEFT JOIN dd + ON dd.denom_pub_hash = ir.denom_pub_hash + )--exists +SELECT + exists.existed AS existed1, + exists.known_coin_id AS known_coin_id1, + exists.denom_pub_hash AS denom_pub_hash1, + exists.age_commitment_hash AS age_commitment_hash1, + ( + SELECT exists.existed + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash2 + ) AS existed2, + ( + SELECT exists.known_coin_id + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash2 + ) AS known_coin_id2, + ( + SELECT exists.denom_pub_hash + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash2 + ) AS denom_pub_hash2, + ( + SELECT exists.age_commitment_hash + FROM exists + WHERE exists.denom_pub_hash = in_denom_pub_hash2 + )AS age_commitment_hash2 +FROM exists; + +RETURN; +END $$; + + +CREATE OR REPLACE FUNCTION exchange_do_batch1_known_coin( + IN in_coin_pub1 BYTEA, + IN in_denom_pub_hash1 BYTEA, + IN in_h_age_commitment1 BYTEA, + IN in_denom_sig1 BYTEA, + OUT existed1 BOOLEAN, + OUT known_coin_id1 INT8, + OUT denom_pub_hash1 BYTEA, + OUT age_commitment_hash1 BYTEA) +LANGUAGE plpgsql +AS $$ +BEGIN +WITH dd AS ( +SELECT + denominations_serial, + coin_val, coin_frac + FROM denominations + WHERE denom_pub_hash + IN + (in_denom_pub_hash1, + in_denom_pub_hash2) + ),--dd + input_rows AS ( + VALUES + (in_coin_pub1, + in_denom_pub_hash1, + in_h_age_commitment1, + in_denom_sig1) + ),--ir + ins AS ( + INSERT INTO known_coins ( + coin_pub, + denominations_serial, + age_commitment_hash, + denom_sig, + remaining_val, + remaining_frac + ) + SELECT + ir.coin_pub, + dd.denominations_serial, + ir.age_commitment_hash, + ir.denom_sig, + dd.coin_val, + dd.coin_frac + FROM input_rows ir + JOIN dd + ON dd.denom_pub_hash = ir.denom_pub_hash + ON CONFLICT DO NOTHING + RETURNING known_coin_id + ),--kc + exists AS ( + SELECT + CASE + WHEN ins.known_coin_id IS NOT NULL + THEN + FALSE + ELSE + TRUE + END AS existed, + ins.known_coin_id, + dd.denom_pub_hash, + kc.age_commitment_hash + FROM input_rows ir + LEFT JOIN ins + ON ins.coin_pub = ir.coin_pub + LEFT JOIN known_coins kc + ON kc.coin_pub = ir.coin_pub + LEFT JOIN dd + ON dd.denom_pub_hash = ir.denom_pub_hash + )--exists +SELECT + exists.existed AS existed1, + exists.known_coin_id AS known_coin_id1, + exists.denom_pub_hash AS denom_pub_hash1, + exists.age_commitment_hash AS age_commitment_hash1 +FROM exists; + +RETURN; +END $$; + +/*** THIS SQL CODE WORKS ***/ +/* +CREATE OR REPLACE FUNCTION exchange_do_batch2_known_coin( + IN in_coin_pub1 BYTEA, + IN in_denom_pub_hash1 TEXT, + IN in_h_age_commitment1 TEXT, + IN in_denom_sig1 TEXT, + IN in_coin_pub2 BYTEA, + IN in_denom_pub_hash2 TEXT, + IN in_h_age_commitment2 TEXT, + IN in_denom_sig2 TEXT, + OUT existed1 BOOLEAN, + OUT existed2 BOOLEAN, + OUT known_coin_id1 INT8, + OUT known_coin_id2 INT8, + OUT denom_pub_hash1 TEXT, + OUT denom_pub_hash2 TEXT, + OUT age_commitment_hash1 TEXT, + OUT age_commitment_hash2 TEXT) +LANGUAGE plpgsql +AS $$ +DECLARE + ins_values RECORD; +BEGIN + FOR i IN 1..2 LOOP + ins_values := ( + SELECT + in_coin_pub1 AS coin_pub, + in_denom_pub_hash1 AS denom_pub_hash, + in_h_age_commitment1 AS age_commitment_hash, + in_denom_sig1 AS denom_sig + WHERE i = 1 + UNION + SELECT + in_coin_pub2 AS coin_pub, + in_denom_pub_hash2 AS denom_pub_hash, + in_h_age_commitment2 AS age_commitment_hash, + in_denom_sig2 AS denom_sig + WHERE i = 2 + ); + WITH dd (denominations_serial, coin_val, coin_frac) AS ( + SELECT denominations_serial, coin_val, coin_frac + FROM denominations + WHERE denom_pub_hash = ins_values.denom_pub_hash + ), + input_rows(coin_pub) AS ( + VALUES (ins_values.coin_pub) + ), + ins AS ( + INSERT INTO known_coins ( + coin_pub, + denominations_serial, + age_commitment_hash, + denom_sig, + remaining_val, + remaining_frac + ) SELECT + input_rows.coin_pub, + dd.denominations_serial, + ins_values.age_commitment_hash, + ins_values.denom_sig, + coin_val, + coin_frac + FROM dd + CROSS JOIN input_rows + ON CONFLICT DO NOTHING + RETURNING known_coin_id, denom_pub_hash + ) + SELECT + CASE i + WHEN 1 THEN + COALESCE(ins.known_coin_id, 0) <> 0 AS existed1, + ins.known_coin_id AS known_coin_id1, + ins.denom_pub_hash AS denom_pub_hash1, + ins.age_commitment_hash AS age_commitment_hash1 + WHEN 2 THEN + COALESCE(ins.known_coin_id, 0) <> 0 AS existed2, + ins.known_coin_id AS known_coin_id2, + ins.denom_pub_hash AS denom_pub_hash2, + ins.age_commitment_hash AS age_commitment_hash2 + END + FROM ins; + END LOOP; +END; +$$;*/ diff --git a/src/exchangedb/exchange_get_ready_deposit.sql b/src/exchangedb/exchange_get_ready_deposit.sql new file mode 100644 index 000000000..4f76463fc --- /dev/null +++ b/src/exchangedb/exchange_get_ready_deposit.sql @@ -0,0 +1,60 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2014--2022 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see +-- +CREATE OR REPLACE FUNCTION exchange_do_get_ready_deposit( + IN in_now INT8, + IN in_start_shard_now INT8, + IN in_end_shard_now INT8, + OUT out_payto_uri VARCHAR, + OUT out_merchant_pub BYTEA +) +LANGUAGE plpgsql +AS $$ +DECLARE + curs CURSOR + FOR + SELECT + coin_pub + ,deposit_serial_id + ,wire_deadline + ,shard + FROM deposits_by_ready dbr + WHERE wire_deadline <= in_now + AND shard >= in_start_shard_now + AND shard <=in_end_shard_now + ORDER BY + wire_deadline ASC + ,shard ASC + LIMIT 1; +DECLARE + i RECORD; +BEGIN +OPEN curs; +FETCH FROM curs INTO i; +SELECT + payto_uri + ,merchant_pub + INTO + out_payto_uri + ,out_merchant_pub + FROM deposits + JOIN wire_targets wt + USING (wire_target_h_payto) + WHERE + i.coin_pub = coin_pub + AND i.deposit_serial_id=deposit_serial_id; +CLOSE curs; +RETURN; +END $$; diff --git a/src/exchangedb/spi/README.md b/src/exchangedb/spi/README.md new file mode 100644 index 000000000..ec6a9016a --- /dev/null +++ b/src/exchangedb/spi/README.md @@ -0,0 +1,41 @@ + Server Programming Interface (SPI) + + +Dependencies: +============= + +These are the direct dependencies for running SPI functions : + + + +Step 1: +"postgresql-server-dev-" +-- sudo apt-get install libpq-dev postgresql-server-dev-13 + +Step 2: +To solve gssapi/gssapi.h, use the following command: +apt-get install libkrb5-dev + +Step 3: +apt-cache search openssl | grep -- -dev +apt-get install libssl-dev + +Compile: +======== +gcc -shared -o .so .c + +CALL FUNCTIONS: +=============== + +psql -c "SELECT ();" db_name + +Structure: +========== + +usr/include/postgres/ + +usr/include/postgres/13/server/ + +make +make install +psql \ No newline at end of file diff --git a/src/exchangedb/spi/own_test.bc b/src/exchangedb/spi/own_test.bc new file mode 100644 index 0000000000000000000000000000000000000000..240c78cf4b882a737e0907d58246419583dac593 GIT binary patch literal 22876 zcmdUX2~<*kAY=f9AR&f9M2i>(kVO()J9Pq`8y>TsRKXfbV|ojzRhcSL3ORATOCpsQ?h(!8zvdiXu1i(6`2{4qX{))? z_2jH}=3Wm`(m0*y1S!0{f=#w={jVO9gcj_xv#diNAH|B;L@piK$wPa{keONp=Z}kBU*CRPST_lnnn{d`+YD>@j za~_*@fqrYk#$jeF^u_u8+^y$Qr&q99u4`92Q>nL8MHO%3(_4tGys2`Mn2s0XomWh5 zD=|_B*4?L^^Y9~$<~5Qr64uxV>yg+#7?!HiZ@2W`JML5uV`{@d7fSYYi8LDFd5xqivD$gyk4R!wo7y7EBY z*ot(zlPiV|+a+_o8adV+?dmLZ!@48$RtGB{B#}#J-JYq_V|4%9Fzs=xC#2BIoj(y+ ztdNRLc`d=^49Q)^#N08@xBNE6kY%Es;puHgeFszGotqibxRdJ{<|!_jPTj&kIh>MK zO}pE{qo&+k<@aHtxh*}NYqlujJ?gb&{?C!_g_E`>jdtfv(5cjFwO{=-a~pLgi+5%L zuC1p`L1Dh9MSfYw=;Oz0f8M35UM-m`O>HjOikmLT6wNyp^G)(>cSgu-7}I(1`jA@o z24ul2f&5`D(YrS5=A+fHFfzGgYFb)ab!DYn%$tG8o4#%qo$VWQT4FHV3dS{XVMabE zX_hFRS)xB|G`2;P%;-k8M@nSV^dSM$XGQG}#{dj_1cuyzxPX|d#!s+*&t zDSk7~0la8DZd`yC`cdw4jMQo#RwEiE zq&maFEQ#T(0g2)3NU7mSJWFCQQl$n{GFxJ35l9WKC5eW%G^ydJ8&fvdDfi}HD%B$o z+j2N%i~q#BfpgwW4@`DF!Q!XYyj7FB4Ha$%GKpmJKej$}9qwgM8aC3Hp4%uvtEFUC zB01~wFr>uC<43ntM7uIVsyaPR(^HedFJr|cN0PWBBJ=h$#aqwMqz~r1JPILUl^g3B z#`19=V{++^O2b~P$uON9T%o2(M(p-98CB_3B)K8`3?=E)$|j{BNz@sRr5pA6QY-|U z_E|`oTo>iUJzaw=dxX5LG8YT%{K&k3rz{-)1sf~&Y<|O;@8yQM+#Xt$XiOg^WpeGZT*}f?ZJCl$6wD~fr4*$y-cE&UZB(7MRL>~Or4*~QQMp>B zfl-2W_z5}{!jmLXyPxk3KS7(?_hh~6O+Ud|weLl_phfKqtynRWvA9lKgfWZtT4k;l zWR{j{OO%YVV8&AHQn&A=dI47Mdr~31QSaI%6`oWG&VtN(*A9~KqMx9hB-SeB+NHq^ zRW9_>M#;5B(FloZ!Y2$f&()wAqWN@TA`o~{9s|547uwu(vV)2 z>-R=MuR_?PcKzPqdyzDxQ|@{L)C;$AP`IE67_cF|U2JTd@RCB0SsuNSn}yMnje z@k7@a<-)5{;msFYN~A5Rvl<9lDwVM~S5pYS1kPX^SJVOOtw&zh)$M!Mphqu@*EXKg?vT> z9$Wl(hCW{}ya@iL61GSMo%Mnn4lW1k`ddG_;pcnW!z8!qfEvWSV-=jPN!xrBF4R&j81ZN~aVXwM+Gk zsC3QZT*flFW^^5jP`Cw!7wHkhGNUFA)0HW;Fx6u4oEHViHspaYpUWnEAhqvhv{)kE z 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 pm^IWID?-=?>Wu}7AS6T0u_A +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils/array.h" +#include +#include "utils/numeric.h" +#include "utils/timestamp.h" +#include + +#ifdef PG_MODULE_MAGIC +PG_MODULE_MAGIC; +#endif + +typedef struct { + Datum col1; + Datum col2; +} valuest; + +void _PG_init(void); +void _PG_fini(void); + +void _PG_init(void) +{ +} + +PG_FUNCTION_INFO_V1(pg_spi_insert_int); +PG_FUNCTION_INFO_V1(pg_spi_select_from_x); +PG_FUNCTION_INFO_V1(pg_spi_select_pair_from_y); +//PG_FUNCTION_INFO_V1(pg_spi_select_with_cond); +PG_FUNCTION_INFO_V1(pg_spi_update_y); +PG_FUNCTION_INFO_V1(pg_spi_prepare_example); +PG_FUNCTION_INFO_V1(pg_spi_prepare_example_without_saveplan); +PG_FUNCTION_INFO_V1(pg_spi_prepare_insert); +PG_FUNCTION_INFO_V1(pg_spi_prepare_insert_without_saveplan); +//PG_FUNCTION_INFO_V1(pg_spi_prepare_select_with_cond); +PG_FUNCTION_INFO_V1(pg_spi_prepare_select_with_cond_without_saveplan); +PG_FUNCTION_INFO_V1(pg_spi_prepare_update); +PG_FUNCTION_INFO_V1(pg_spi_get_dep_ref_fees); +// SIMPLE SELECT +Datum +pg_spi_prepare_example(PG_FUNCTION_ARGS) +{ + static SPIPlanPtr prepared_plan; + int ret; + int64 result; + char * value; + SPIPlanPtr new_plan; + + ret=SPI_connect(); + if (ret != SPI_OK_CONNECT) { + elog(ERROR, "DB connexion failed ! \n"); + } + { + if (prepared_plan == NULL) + { + new_plan = SPI_prepare("SELECT 1 FROM joseph_test.X", 0, NULL); + prepared_plan = SPI_saveplan(new_plan); + + if (prepared_plan == NULL) + { + elog(ERROR, "FAIL TO SAVE !\n"); + } + } + + ret = SPI_execute_plan(prepared_plan, NULL, 0,false, 0); + if (ret != SPI_OK_SELECT) { + elog(ERROR, "SELECT FAILED %d !\n", ret); + } + + if (SPI_tuptable != NULL && SPI_tuptable->vals != NULL && SPI_tuptable->tupdesc != NULL) + { + value = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1); + result = atoi(value); + } + else + { + elog(ERROR, "EMPTY TABLE !\n"); + } + } + SPI_finish(); + PG_RETURN_INT64(result); +} + + + +Datum +pg_spi_prepare_example_without_saveplan(PG_FUNCTION_ARGS) +{ + int ret; + int64 result; + char * value; + SPIPlanPtr new_plan; + + ret=SPI_connect(); + if (ret != SPI_OK_CONNECT) { + elog(ERROR, "DB connexion failed ! \n"); + } + + { + new_plan = SPI_prepare("SELECT 1 FROM joseph_test.X", 0, NULL); + ret = SPI_execute_plan(new_plan, NULL, 0,false, 0); + if (ret != SPI_OK_SELECT) { + elog(ERROR, "SELECT FAILED %d !\n", ret); + } + + if (SPI_tuptable != NULL + && SPI_tuptable->vals != NULL + && SPI_tuptable->tupdesc != NULL) + { + value = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1); + result = atoi(value); + } + else + { + elog(ERROR, "EMPTY TABLE !\n"); + } + } + SPI_finish(); + PG_RETURN_INT64(result);// PG_RETURN_INT64(result); +} + + +//SELECT 1 FROM X +//V1 +Datum +pg_spi_select_from_x(PG_FUNCTION_ARGS) +{ + int ret; + char *query = "SELECT 1 FROM joseph_test.X"; + uint64 proc; + ret = SPI_connect(); + + if (ret != SPI_OK_CONNECT) + { + elog(ERROR, "SPI_connect failed"); + } + + ret = SPI_exec(query, 10); + proc = SPI_processed; + if (ret != SPI_OK_SELECT) + { + elog(ERROR, "SPI_exec failed"); + } + + SPI_finish(); + + PG_RETURN_INT64(proc); +} + +//INSERT INTO X VALUES (1) +Datum +pg_spi_insert_int(PG_FUNCTION_ARGS) +{ + int ret; + int nargs; + Oid argtypes[1]; + Datum values[1]; + char *query = "INSERT INTO joseph_test.X (a) VALUES ($1)"; + + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) + { + elog(ERROR, "SPI_connect failed"); + } + + nargs = 1; + argtypes[0] = INT4OID; + values[0] = Int32GetDatum(3); + + ret = SPI_execute_with_args(query, nargs, argtypes, values, NULL, false, 0); + if (ret != SPI_OK_INSERT) + { + elog(ERROR, "SPI_execute_with_args failed"); + } + + SPI_finish(); + + PG_RETURN_VOID(); +} + + +Datum +pg_spi_prepare_insert(PG_FUNCTION_ARGS) +{ + static SPIPlanPtr prepared_plan = NULL; + int ret; + int nargs; + Oid argtypes[1]; + Datum values[1]; + char *query = "INSERT INTO joseph_test.X (a) VALUES ($1)"; + SPIPlanPtr new_plan; + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) + { + elog(ERROR, "SPI_connect failed ! \n"); + } + if (prepared_plan == NULL) { + + argtypes[0] = INT4OID; + nargs = 1; + values[0] = Int32GetDatum(3); + new_plan = SPI_prepare(query, nargs, argtypes); + if (new_plan== NULL) + { + elog(ERROR, "SPI_prepare failed ! \n"); + } + prepared_plan = SPI_saveplan(new_plan); + if (prepared_plan == NULL) + { + elog(ERROR, "SPI_saveplan failed ! \n"); + } + } + + ret = SPI_execute_plan(prepared_plan, values, NULL, false, 0); + if (ret != SPI_OK_INSERT) + { + elog(ERROR, "SPI_execute_plan failed ! \n"); + } + + SPI_finish(); + + PG_RETURN_VOID(); +} +/* +Datum +pg_spi_prepare_insert_bytea(PG_FUNCTION_ARGS) +{ + static SPIPlanPtr prepared_plan = NULL; + int ret; + int nargs; + Oid argtypes[1]; + Datum values[1]; + Oid argtypes2[1]; + Datum val[1]; + char *query = "INSERT INTO joseph_test.X (a) VALUES ($1)"; + SPIPlanPtr new_plan; + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) + { + elog(ERROR, "SPI_connect failed ! \n"); + } + if (prepared_plan == NULL) { + argtypes2[0] = BOOLOID; + val[0] = BoolGetDatum(); + argtypes[0] = BYTEAOID; + nargs = 1; + values[0] = Int32GetDatum(3); + new_plan = SPI_prepare(query, nargs, argtypes); + if (new_plan== NULL) + { + elog(ERROR, "SPI_prepare failed ! \n"); + } + prepared_plan = SPI_saveplan(new_plan); + if (prepared_plan == NULL) + { + elog(ERROR, "SPI_saveplan failed ! \n"); + } + } + + ret = SPI_execute_plan(prepared_plan, values, NULL, false, 0); + if (ret != SPI_OK_INSERT) + { + elog(ERROR, "SPI_execute_plan failed ! \n"); + } + + SPI_finish(); + + PG_RETURN_VOID(); +} +*/ + +Datum +pg_spi_prepare_insert_without_saveplan(PG_FUNCTION_ARGS) +{ + int ret; + int nargs; + Oid argtypes[1]; + Datum values[1]; + char *query = "INSERT INTO joseph_test.X (a) VALUES ($1)"; + SPIPlanPtr new_plan; + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) + { + elog(ERROR, "SPI_connect failed"); + } + { + argtypes[0] = INT4OID; + nargs = 1; + values[0] = Int32GetDatum(3); + new_plan = SPI_prepare(query, nargs, argtypes); + if (new_plan== NULL) + { + elog(ERROR, "SPI_prepare failed"); + } + } + + ret = SPI_execute_plan(new_plan, values, NULL, false, 0); + if (ret != SPI_OK_INSERT) + { + elog(ERROR, "SPI_execute_plan failed"); + } + + SPI_finish(); + + PG_RETURN_VOID(); +} + + + + + + +/* +Datum +pg_spi_select_pair_from_y(PG_FUNCTION_ARGS) +{ + int ret; + valuest result; + bool isnull; + char *query = "SELECT 1,1 FROM joseph_test.Y"; + result.col1 = 0; + result.col2 = 0; + + if ((ret = SPI_connect()) < 0) { + fprintf(stderr, "SPI_connect returned %d\n", ret); + exit(1); + } + ret = SPI_exec(query, 0); + if (ret == SPI_OK_SELECT && SPI_processed > 0) { + int i; + SPITupleTable *tuptable = SPI_tuptable; + TupleDesc tupdesc = tuptable->tupdesc; + for (i = 0; i < SPI_processed; i++) { + HeapTuple tuple = tuptable->vals[i]; + result.col1 = SPI_getbinval(tuple, tupdesc, 1, &isnull); + result.col2 = SPI_getbinval(tuple, tupdesc, 2, &isnull); + } + } + SPI_finish(); + PG_RETURN_TEXT_P(result); +} +*/ + +//SELECT X FROM Y WHERE Z=$1 +/* +Datum +pg_spi_select_with_cond(PG_FUNCTION_ARGS) +{ + int ret; + char *query; + int nargs; + Oid argtypes[1]; + Datum values[1]; + uint64 proc; + query = "SELECT col1 FROM joseph_test.Y WHERE col2 = $1"; + + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) { + elog(ERROR, "SPI_connect failed: %d", ret); + } + nargs = 1; + argtypes[0] = INT4OID; + values[0] = Int32GetDatum(2); + + ret = SPI_execute_with_args(query, nargs, argtypes, values, NULL, false, 0); + proc = SPI_processed; + if (ret != SPI_OK_SELECT) + { + elog(ERROR, "SPI_execute_with_args failed"); + } + + SPI_finish(); + + + PG_RETURN_INT64(proc); + }*/ + +////////SELECT WITH COND +/* +Datum pg_spi_prepare_select_with_cond(PG_FUNCTION_ARGS) { + static SPIPlanPtr prepared_plan = NULL; + SPIPlanPtr new_plan; + int ret; + Datum values[1]; + uint64 proc; + int nargs; + Oid argtypes[1]; + char *query = "SELECT col1 FROM joseph_test.Y WHERE col1 = $1"; + int result = 0; + + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed ! \n"); + + if (prepared_plan == NULL) { + + argtypes[0] = INT4OID; + nargs = 1; + values[0] = DatumGetByteaP(SPI_getbinval(tuptable->vals[0], tupdesc, 1, &isnull)); //Value col2 + + new_plan = SPI_prepare(query, nargs, argtypes); + if (new_plan == NULL) + elog(ERROR, "SPI_prepare failed ! \n"); + + prepared_plan = SPI_saveplan(new_plan); + if (prepared_plan == NULL) + elog(ERROR, "SPI_saveplan failed ! \n"); + } + + + ret = SPI_execute_plan(prepared_plan, values, NULL, false, 0); + + if (ret != SPI_OK_SELECT) { + elog(ERROR, "SPI_execute_plan failed: %d \n", ret); + } + + proc = SPI_processed; + + if (proc > 0) { + SPITupleTable *tuptable = SPI_tuptable; + TupleDesc tupdesc = tuptable->tupdesc; + HeapTuple tuple; + int i; + + for (i = 0; i < proc; i++) { + tuple = tuptable->vals[i]; + for (int j = 1; j <= tupdesc->natts; j++) { + char * value = SPI_getvalue(tuple, tupdesc, j); + result += atoi(value); + } + } + } + SPI_finish(); + PG_RETURN_INT64(result); +} +*/ + +Datum pg_spi_prepare_select_with_cond_without_saveplan(PG_FUNCTION_ARGS) { + + SPIPlanPtr new_plan; + int ret; + Datum values[1]; + uint64 proc; + int nargs; + Oid argtypes[1]; + char *query = "SELECT col1 FROM joseph_test.Y WHERE col2 = $1"; + int result = 0; + + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed ! \n"); + + { + + argtypes[0] = INT4OID; + nargs = 1; + values[0] = Int32GetDatum(2); //Value col2 + + new_plan = SPI_prepare(query, nargs, argtypes); + if (new_plan == NULL) + elog(ERROR, "SPI_prepare failed ! \n"); + + } + + + ret = SPI_execute_plan(new_plan, values, NULL, false, 0); + + if (ret != SPI_OK_SELECT) { + elog(ERROR, "SPI_execute_plan failed: %d \n", ret); + } + + proc = SPI_processed; + + if (proc > 0) { + SPITupleTable *tuptable = SPI_tuptable; + TupleDesc tupdesc = tuptable->tupdesc; + HeapTuple tuple; + int i; + + for (i = 0; i < proc; i++) { + tuple = tuptable->vals[i]; + for (int j = 1; j <= tupdesc->natts; j++) { + char * value = SPI_getvalue(tuple, tupdesc, j); + result += atoi(value); + } + } + } + SPI_finish(); + PG_RETURN_INT64(result); +} + + + + +Datum +pg_spi_update_y(PG_FUNCTION_ARGS) +{ + int ret; + int nargs; + Oid argtypes[1]; + Datum values[1]; + char *query = "UPDATE joseph_test.Y SET col1 = 4 WHERE (col2 = $1)"; + + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) + { + elog(ERROR, "SPI_connect failed ! \n"); + } + + nargs = 1; + argtypes[0] = INT4OID; + values[0] = Int32GetDatum(0); + + ret = SPI_execute_with_args(query, nargs, argtypes, values, NULL, false, 0); + if (ret != SPI_OK_UPDATE) + { + elog(ERROR, "SPI_execute_with_args failed ! \n"); + } + + SPI_finish(); + + PG_RETURN_VOID(); +} + +Datum +pg_spi_prepare_update(PG_FUNCTION_ARGS) +{ + static SPIPlanPtr prepared_plan = NULL; + SPIPlanPtr new_plan; + int ret; + int nargs; + Oid argtypes[1]; + Datum values[1]; + char *query = "UPDATE joseph_test.Y SET col1 = 4 WHERE (col2 = $1)"; + + ret = SPI_connect(); + if (ret != SPI_OK_CONNECT) + { + elog(ERROR, "SPI_connect failed ! \n"); + } + + if ( prepared_plan == NULL) + { + argtypes[0] = INT4OID; + nargs = 1; + values[0] = Int32GetDatum(3); + //PREPARE + new_plan = SPI_prepare(query, nargs, argtypes); + if (new_plan == NULL) + elog(ERROR, "SPI_prepare failed ! \n"); + //SAVEPLAN + prepared_plan = SPI_saveplan(new_plan); + if(prepared_plan == NULL) + elog(ERROR, "SPI_saveplan failed ! \n"); + } + ret = SPI_execute_plan(prepared_plan, values, NULL, false, 0); + if (ret != SPI_OK_UPDATE) + elog(ERROR, "SPI_execute_plan failed ! \n"); + + SPI_finish(); + PG_RETURN_VOID(); +} +/* +Datum +pg_spi_prepare_update_without_saveplan(PG_FUNCTION_ARGS) +{}*/ +void _PG_fini(void) +{ +} + +/* + +*/ + +Datum +pg_spi_get_dep_ref_fees (PG_FUNCTION_ARGS) { + /* Define plan to save */ + static SPIPlanPtr deposit_plan; + static SPIPlanPtr ref_plan; + static SPIPlanPtr fees_plan; + static SPIPlanPtr dummy_plan; + /* Define variables to update */ + Timestamp refund_deadline = PG_GETARG_TIMESTAMP(0); + bytea *merchant_pub = PG_GETARG_BYTEA_P(1); + bytea *wire_target_h_payto = PG_GETARG_BYTEA_P(2); + bytea *wtid_raw = PG_GETARG_BYTEA_P(3); + bool is_null; + /* Define variables to store the results of each SPI query */ + uint64_t sum_deposit_val = 0; + uint32_t sum_deposit_frac = 0; + uint64_t s_refund_val = 0; + uint32_t s_refund_frac = 0; + uint64_t sum_dep_fee_val = 0; + uint32_t sum_dep_fee_frac = 0; + uint64_t norm_refund_val = 0; + uint32_t norm_refund_frac = 0; + uint64_t sum_refund_val = 0; + uint32_t sum_refund_frac = 0; + /* Define variables to store the Tuptable */ + SPITupleTable *dep_res; + SPITupleTable *ref_res; + SPITupleTable *ref_by_coin_res; + SPITupleTable *norm_ref_by_coin_res; + SPITupleTable *fully_refunded_coins_res; + SPITupleTable *fees_res; + SPITupleTable *dummys_res; + /* Define variable to update */ + Datum values_refund[2]; + Datum values_deposit[3]; + Datum values_fees[2]; + Datum values_dummys[2]; + TupleDesc tupdesc; + /* Define variables to replace some tables */ + bytea *ref_by_coin_coin_pub; + int64 ref_by_coin_deposit_serial_id = 0; + bytea *norm_ref_by_coin_coin_pub; + int64_t norm_ref_by_coin_deposit_serial_id = 0; + bytea *new_dep_coin_pub = NULL; + int res = SPI_connect(); + + /* Connect to SPI */ + if (res < 0) { + elog(ERROR, "Could not connect to SPI manager"); + } + if (deposit_plan == NULL) + { + const char *dep_sql; + SPIPlanPtr new_plan; + + // Execute first query and store results in variables + dep_sql = + "UPDATE deposits SET done=TRUE " + "WHERE NOT (done OR policy_blocked) " + "AND refund_deadline=$1 " + "AND merchant_pub=$2 " + "AND wire_target_h_payto=$3 " + "RETURNING " + "deposit_serial_id," + "coin_pub," + "amount_with_fee_val," + "amount_with_fee_frac;"; + fprintf(stderr, "dep sql %d\n", 1); + new_plan = + SPI_prepare(dep_sql, 4,(Oid[]){INT8OID, BYTEAOID, BYTEAOID}); + fprintf(stderr, "dep sql %d\n", 2); + if (new_plan == NULL) + elog(ERROR, "SPI_prepare failed for dep \n"); + deposit_plan = SPI_saveplan(new_plan); + if (deposit_plan == NULL) + elog(ERROR, "SPI_saveplan failed for dep \n"); + } + fprintf(stdout, "dep sql %d\n", 3); + + values_deposit[0] = Int64GetDatum(refund_deadline); + values_deposit[1] = PointerGetDatum(merchant_pub); + values_deposit[2] = PointerGetDatum(wire_target_h_payto); + + res = SPI_execute_plan (deposit_plan, + values_deposit, + NULL, + true, + 0); + fprintf(stdout, "dep sql %d\n", 4); + if (res != SPI_OK_UPDATE) + { + elog(ERROR, "Failed to execute subquery 1 \n"); + } + // STORE TUPTABLE deposit + dep_res = SPI_tuptable; + + for (unsigned int i = 0; i < SPI_processed; i++) { + int64 dep_deposit_serial_ids = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &is_null)); + bytea *dep_coin_pub = DatumGetByteaP(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &is_null)); + int64 dep_amount_val = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 3, &is_null)); + int32 dep_amount_frac = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 4, &is_null)); + + if (is_null) + elog(ERROR, "Failed to retrive data from deposit \n"); + if (ref_plan == NULL) + { + // Execute second query with parameters from first query and store results in variables + const char * ref_sql = + "SELECT amount_with_fee_val, amount_with_fee_frac, coin_pub, deposit_serial_id " + "FROM refunds " + "WHERE coin_pub=$1 " + "AND deposit_serial_id=$2;"; + SPIPlanPtr new_plan = SPI_prepare(ref_sql, 3, (Oid[]){BYTEAOID, INT8OID}); + if (new_plan == NULL) + elog(ERROR, "SPI_prepare failed for refund\n"); + ref_plan = SPI_saveplan(new_plan); + if (ref_plan == NULL) + elog(ERROR, "SPI_saveplan failed for refund\n"); + } + values_refund[0] = PointerGetDatum(dep_coin_pub); + values_refund[1] = Int64GetDatum(dep_deposit_serial_ids); + res = SPI_execute_plan(ref_plan, + values_refund, + NULL, + false, + 0); + if (res != SPI_OK_SELECT) + elog(ERROR, "Failed to execute subquery 2\n"); + // STORE TUPTABLE refund + ref_res = SPI_tuptable; + for (unsigned int j = 0; j < SPI_processed; j++) { + int64 ref_refund_val = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[j], SPI_tuptable->tupdesc, 1, &is_null)); + int32 ref_refund_frac = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[j], SPI_tuptable->tupdesc, 2, &is_null)); + bytea *ref_coin_pub = DatumGetByteaP(SPI_getbinval(SPI_tuptable->vals[j], SPI_tuptable->tupdesc, 3, &is_null)); + int64 ref_deposit_serial_id = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[j], SPI_tuptable->tupdesc, 4, &is_null)); + // Execute third query with parameters from second query and store results in variables + ref_by_coin_coin_pub = ref_coin_pub; + ref_by_coin_deposit_serial_id = ref_deposit_serial_id; + // LOOP TO GET THE SUM FROM REFUND BY COIN + for (unsigned int i = 0; ivals[i], SPI_tuptable->tupdesc, 1, &is_null))) + && + (ref_by_coin_deposit_serial_id == + DatumGetUInt64(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &is_null))) + ) + { + sum_refund_val += ref_refund_val; + sum_refund_frac += ref_refund_frac; + norm_ref_by_coin_coin_pub = ref_by_coin_coin_pub; + norm_ref_by_coin_deposit_serial_id = ref_by_coin_deposit_serial_id; + } + }// END SUM CALCULATION + //NORMALIZE REFUND VAL FRAC + norm_refund_val = + (sum_refund_val + sum_refund_frac ) / 100000000; + norm_refund_frac = + sum_refund_frac % 100000000; + // Get refund values + s_refund_val += sum_refund_val; + s_refund_frac = sum_refund_frac; + }//END REFUND + if (norm_ref_by_coin_coin_pub == dep_coin_pub + && ref_by_coin_deposit_serial_id == dep_deposit_serial_ids + && norm_refund_val == dep_amount_val + && norm_refund_frac == dep_amount_frac) + { + new_dep_coin_pub = dep_coin_pub; + } + // Ensure we get the fee for each coin and not only once per denomination + if (fees_plan == NULL ) + { + const char * fees_sql = + "SELECT " + " denom.fee_deposit_val AS fee_val, " + " denom.fee_deposit_frac AS fee_frac, " + "FROM known_coins kc" + "JOIN denominations denom USING (denominations_serial) " + "WHERE kc.coin_pub = $1 AND kc.coin_pub != $2;"; + SPIPlanPtr new_plan = SPI_prepare(fees_sql, 3, (Oid[]){BYTEAOID, BYTEAOID}); + if (new_plan == NULL) + { + elog(ERROR, "SPI_prepare for fees failed ! \n"); + } + fees_plan = SPI_saveplan(new_plan); + if (fees_plan == NULL) + { + elog(ERROR, "SPI_saveplan for fees failed ! \n"); + } + } + values_fees[0] = PointerGetDatum(dep_coin_pub); + values_fees[1] = PointerGetDatum(new_dep_coin_pub); + res = SPI_execute_plan(fees_plan, values_fees, NULL, false, 0); + if (res != SPI_OK_SELECT) + elog(ERROR, "SPI_execute_plan failed for fees \n"); + fees_res = SPI_tuptable; + tupdesc = fees_res->tupdesc; + for (unsigned int i = 0; ivals[i]; + bool is_null; + uint64_t fee_val = DatumGetUInt64(SPI_getbinval(tuple, tupdesc, 1, &is_null)); + uint32_t fee_frac = DatumGetUInt32(SPI_getbinval(tuple, tupdesc, 2, &is_null)); + uint64_t fees_deposit_serial_id = DatumGetUInt64(SPI_getbinval(tuple, tupdesc, 3, &is_null)); + if (dummy_plan == NULL) + { + const char *insert_dummy_sql = + "INSERT INTO " + "aggregation_tracking(deposit_serial_id, wtid_raw)" + " VALUES ($1, $2)"; + + SPIPlanPtr new_plan = SPI_prepare(insert_dummy_sql, 2, (Oid[]){INT8OID, BYTEAOID}); + if (new_plan == NULL) + elog(ERROR, "FAILED to prepare aggregation tracking \n"); + dummy_plan = SPI_saveplan(new_plan); + if ( dummy_plan == NULL ) + elog(ERROR, "FAILED to saveplan aggregation tracking\n"); + } + values_dummys[0] = Int64GetDatum(dep_deposit_serial_ids); + values_dummys[1] = PointerGetDatum(wtid_raw); + res = SPI_execute_plan(dummy_plan, values_dummys, NULL, false, 0); + if (res != SPI_OK_INSERT) + elog(ERROR, "Failed to insert dummy\n"); + dummys_res = SPI_tuptable; + // Calculation of deposit fees for not fully refunded deposits + sum_dep_fee_val += fee_val; + sum_dep_fee_frac += fee_frac; + } + // Get deposit values + sum_deposit_val += dep_amount_val; + sum_deposit_frac += dep_amount_frac; + }//END DEPOSIT + SPI_finish(); + PG_RETURN_VOID(); +} diff --git a/src/exchangedb/spi/own_test.control b/src/exchangedb/spi/own_test.control new file mode 100644 index 000000000..4e73e207f --- /dev/null +++ b/src/exchangedb/spi/own_test.control @@ -0,0 +1,4 @@ +comment = 'Example extension for testing purposes' +default_version = '1.0' +module_pathname = '$libdir/own_test' +relocatable = true diff --git a/src/exchangedb/spi/own_test.so b/src/exchangedb/spi/own_test.so new file mode 100755 index 0000000000000000000000000000000000000000..fda70c9d0896756a7915d4b81b1cab58fbc661eb GIT binary patch literal 76824 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 Q4=GfxBtS>+EjLaF$8bghZ_>pdaw8gm^O z2I(n7Maf388AZ#))n#yHp~occ!jEay6M<2dOmTf&O)Kr zk9b$w=#A0GULWu3;dg?+LI1zMr5(p0j$XV1PpRGQCG!|nkNC0Pn7o1yy-MHx5cJ*O zr|*u^tra((cuW5Hm(LBNp7Hs+#I#rNdrpHFU2|||Dw0%>s|#m2PR1k8I*I-b+d#B$ z{|7b(aR2aIot>??K8Ne;*ev?P+nt>%Hc^JX)7iNa*B!V%fa`a-eueAf?{;<$#O}nx z_c}Xw;QA%5&*3`%eQYOS%K0^lZr+S><=iTpN?xe zY)vmu8V9{wyd0@?7cWx6<}|fU}VFUfV~dbOSqRRQk@rArlllI z+h*Ntv$Q9{)-#g1ovFLK57Y$bX4?gpZB}L@;`P-Vot^Y19~~~7kAt@xcDt4U-FaUI z){KsPumL{2hH!9@Prn1*9?;DufI&B7n{@+-sGpAGWMPtUe~~OtgJ(U%%p@FJpV$3F`i3^XC?8Z*jY+_h`s0Nio=DI za=aC})=q#J-G-PfM7M)Z(9!#vbPU5uHGy)s1ony1&ArrmiEX>GEn#z_wU10ZDrju7 z0UgKbvpEr#O4zPkV!IUVn2f~xQH*8yz=if&bZ$dlpU7S$BfagIhni)!^B&Os6LkO0 zCe5Im2|B|j#H#~z&8Qos8XY9lFMu@x#{O-eVW$~f-?>cTR~1s&4TXuvcQ_vcUwUEe z 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 literal 0 HcmV?d00001 diff --git a/src/exchangedb/spi/own_test.sql b/src/exchangedb/spi/own_test.sql new file mode 100644 index 000000000..369c56a60 --- /dev/null +++ b/src/exchangedb/spi/own_test.sql @@ -0,0 +1,216 @@ + +DROP TABLE joseph_test.X; +CREATE TABLE joseph_test.X ( + a integer +); + +INSERT INTO joseph_test.X (a) VALUES (1), (2), (3), (4), (5), (6), (7); + +DROP TABLE joseph_test.Y; +CREATE TABLE joseph_test.Y (col1 INT, col2 INT); +INSERT INTO joseph_test.Y (col1,col2) VALUES (1,2), (2,0), (0,4), (4,0), (0,6), (6,7), (7,8); + +DROP TABLE joseph_test.Z; +CREATE TABLE joseph_test.Z(col1 BYTEA); +DROP TABLE deposits; +/*CREATE TABLE deposits( + deposit_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY + ,shard INT8 NOT NULL + ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32) + ,known_coin_id INT8 NOT NULL + ,amount_with_fee_val INT8 NOT NULL + ,amount_with_fee_frac INT4 NOT NULL + ,wallet_timestamp INT8 NOT NULL + ,exchange_timestamp INT8 NOT NULL + ,refund_deadline INT8 NOT NULL + ,wire_deadline INT8 NOT NULL + ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) + ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64) + ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64) + ,wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16) + ,wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32) + ,done BOOLEAN NOT NULL DEFAULT FALSE + ,policy_blocked BOOLEAN NOT NULL DEFAULT FALSE + ,policy_details_serial_id INT8); +*/ +--INSERT INTO deposits VALUES (); + + + +CREATE TABLE deposits( + deposit_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY + ,shard INT8 NOT NULL + ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32) + ,known_coin_id INT8 NOT NULL + ,amount_with_fee_val INT8 NOT NULL + ,amount_with_fee_frac INT4 NOT NULL + ,wallet_timestamp INT8 NOT NULL + ,exchange_timestamp INT8 NOT NULL + ,refund_deadline INT8 NOT NULL + ,wire_deadline INT8 NOT NULL + ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) + ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64) + ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64) + ,wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16) + ,wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32) + ,done BOOLEAN NOT NULL DEFAULT FALSE + ,policy_blocked BOOLEAN NOT NULL DEFAULT FALSE + ,policy_details_serial_id INT8); + + +CREATE OR REPLACE FUNCTION pg_spi_insert_int() + RETURNS VOID + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_insert_int'; +DROP FUNCTION pg_spi_select_from_x(); +CREATE OR REPLACE FUNCTION pg_spi_select_from_x() + RETURNS INT8 + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_select_from_x'; + +/*DROP FUNCTION pg_spi_select_pair_from_y(); +CREATE OR REPLACE FUNCTION pg_spi_select_pair_from_y() + RETURNS valuest + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_select_pair_from_y'; +*/ +/*CREATE OR REPLACE FUNCTION pg_spi_select_with_cond() + RETURNS INT8 + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_select_with_cond'; +*/ +DROP FUNCTION pg_spi_update_y(); +CREATE OR REPLACE FUNCTION pg_spi_update_y() + RETURNS VOID + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_update_y'; +DROP FUNCTION pg_spi_prepare_example(); + +CREATE OR REPLACE FUNCTION pg_spi_prepare_example() + RETURNS INT8 + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_prepare_example'; + +DROP FUNCTION pg_spi_prepare_example_without_saveplan(); +CREATE OR REPLACE FUNCTION pg_spi_prepare_example_without_saveplan() + RETURNS INT8 + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_prepare_example_without_saveplan'; + +CREATE OR REPLACE FUNCTION pg_spi_prepare_insert() + RETURNS VOID + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_prepare_insert'; + +CREATE OR REPLACE FUNCTION pg_spi_prepare_insert_without_saveplan() + RETURNS VOID + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_prepare_insert_without_saveplan'; + +/*DROP FUNCTION pg_spi_prepare_select_with_cond(); +CREATE OR REPLACE FUNCTION pg_spi_prepare_select_with_cond() + RETURNS INT8 + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_prepare_select_with_cond'; +*/ +DROP FUNCTION pg_spi_prepare_select_with_cond_without_saveplan(); +CREATE OR REPLACE FUNCTION pg_spi_prepare_select_with_cond_without_saveplan() + RETURNS INT8 + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_prepare_select_with_cond_without_saveplan'; + +DROP FUNCTION pg_spi_prepare_update(); +CREATE OR REPLACE FUNCTION pg_spi_prepare_update() + RETURNS VOID + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_prepare_update'; + +DROP FUNCTION pg_spi_get_dep_ref_fees( + IN in_timestamp INT8 + ,IN merchant_pub BYTEA + ,IN wire_target_h_payto BYTEA + ,IN wtid BYTEA); +CREATE OR REPLACE FUNCTION pg_spi_get_dep_ref_fees( + IN in_timestamp INT8 + ,IN merchant_pub BYTEA + ,IN wire_target_h_payto BYTEA + ,IN wtid BYTEA +) + RETURNS VOID + LANGUAGE c COST 100 +AS '$libdir/own_test', 'pg_spi_get_dep_ref_fees'; + +CREATE OR REPLACE FUNCTION update_pg_spi_get_dep_ref_fees( + IN in_refund_deadline INT8, + IN in_merchant_pub BYTEA, + IN in_wire_target_h_payto BYTEA +) +RETURNS SETOF record +LANGUAGE plpgsql VOLATILE +AS $$ +DECLARE + +BEGIN +RETURN QUERY + UPDATE deposits + SET done = TRUE + WHERE NOT (done OR policy_blocked) + AND refund_deadline < in_refund_deadline + AND merchant_pub = in_merchant_pub + AND wire_target_h_payto = in_wire_target_h_payto + RETURNING + deposit_serial_id, + coin_pub, + amount_with_fee_val, + amount_with_fee_frac; +END $$; + +CREATE OR REPLACE FUNCTION stored_procedure_update( +IN in_number INT8 +) +RETURNS VOID +LANGUAGE plpgsql +AS $$ +BEGIN + UPDATE joseph_test.Y + SET col1 = 4 + WHERE col2 = in_number; +END $$; + +CREATE OR REPLACE FUNCTION stored_procedure_select(OUT out_value INT8) +RETURNS INT8 +LANGUAGE plpgsql +AS $$ +BEGIN + SELECT 1 + INTO out_value + FROM joseph_test.X; + RETURN; +END $$; + + +CREATE OR REPLACE FUNCTION stored_procedure_insert( +IN in_number INT8, +OUT out_number INT8) +RETURNS INT8 +LANGUAGE plpgsql +AS $$ +BEGIN + INSERT INTO joseph_test.X (a) + VALUES (in_number) + RETURNING a INTO out_number; +END $$; + +CREATE OR REPLACE FUNCTION stored_procedure_select_with_cond( +IN in_number INT8, +OUT out_number INT8 +) +RETURNS INT8 +LANGUAGE plpgsql +AS $$ +BEGIN + SELECT col1 INTO out_number + FROM joseph_test.Y + WHERE col2 = in_number; + RETURN; +END $$; diff --git a/src/exchangedb/spi/perf_own_test.c b/src/exchangedb/spi/perf_own_test.c new file mode 100644 index 000000000..92be2235e --- /dev/null +++ b/src/exchangedb/spi/perf_own_test.c @@ -0,0 +1,25 @@ +/* + This file is part of TALER + Copyright (C) 2014-2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file exchangedb/spi/perf_own_test.c + * @brief benchmark for 'own_test' + * @author Joseph Xu + */ +#include "exchangedb/platform.h" +#include "exchangedb/taler_exchangedb_lib.h" +#include "exchangedb/taler_json_lib.h" +#include "exchangedb/taler_exchangedb_plugin.h" +#include "own_test.sql" diff --git a/src/exchangedb/spi/pg_aggregate.c b/src/exchangedb/spi/pg_aggregate.c new file mode 100644 index 000000000..262100ce8 --- /dev/null +++ b/src/exchangedb/spi/pg_aggregate.c @@ -0,0 +1,389 @@ +#include "postgres.h" +#include "fmgr.h" +#include "utils/numeric.h" +#include "utils/builtins.h" +#include "executor/spi.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(get_deposit_summary); + +Datum get_deposit_summary(PG_FUNCTION_ARGS) +{ + + static SPIPlanPtr deposit_plan; + static SPIPlanPtr refund_plan; + static SPIPlanPtr refund_by_coin_plan; + static SPIPlanPtr norm_refund_by_coin_plan; + static SPIPlanPtr fully_refunded_by_coins_plan; + static SPIPlanPtr fees_plan; + + int shard = PG_GETARG_INT32(0); + char * sql; + char *merchant_pub = text_to_cstring(PG_GETARG_TEXT_P(1)); + char *wire_target_h_payto = text_to_cstring(PG_GETARG_TEXT_P(2)); + char *wtid_raw = text_to_cstring(PG_GETARG_TEXT_P(3)); + int refund_deadline = PG_GETARG_INT32(4); + int conn = SPI_connect(); + if (conn != SPI_OK_CONNECT) + { + elog(ERROR, "DB connexion failed ! \n"); + } + + if ( deposit_plan == NULL + || refund_plan == NULL + || refund_by_coin_plan == NULL + || norm_refund_by_coin_plan = NULL + || fully_refunded_coins_plan = NULL + || fees_plan == NULL ) + { + if (deposit_plan == NULL) + { + int nargs = 3; + Oid argtypes[3]; + argtypes[0] = INT8OID; + argtypes[1] = BYTEAOID; + argtypes[2] = BYTEAOID; + const char *dep_sql = + " UPDATE deposits" + " SET done=TRUE" + " WHERE NOT (done OR policy_blocked)" + " AND refund_deadline < $1" + " AND merchant_pub = $2" + " AND wire_target_h_payto = $3" + " RETURNING" + " deposit_serial_id" + " ,coin_pub" + " ,amount_with_fee_val AS amount_val" + " ,amount_with_fee_frac AS amount_frac"; + SPIPlanPtr new_plan = + SPI_prepare(dep_sql, 4, argtypes}); + if (new_plan == NULL) + { + elog(ERROR, "SPI_prepare for deposit failed ! \n"); + } + deposit_plan = SPI_saveplan(new_plan); + if (deposit_plan == NULL) + { + elog(ERROR, "SPI_saveplan for deposit failed ! \n"); + } + } + + Datum values[4]; + values[0] = Int64GetDatum(refund_deadline); + values[1] = CStringGetDatum(merchant_pub); + values[2] = CStringGetDatum(wire_target_h_payto); + int ret = SPI_execute_plan (deposit_plan, + values, + NULL, + true, + 0); + if (ret != SPI_OK_UPDATE) + { + elog(ERROR, "Failed to execute subquery 1\n"); + } + uint64_t *dep_deposit_serial_ids = palloc(sizeof(uint64_t) * SPI_processed); + BYTEA **dep_coin_pubs = palloc(sizeof(BYTEA *) * SPI_processed); + uint64_t *dep_amount_vals = palloc(sizeof(uint64_t) * SPI_processed); + uint32_t *dep_amount_fracs = palloc(sizeof(uint32_t) * SPI_processed); + for (unsigned int i = 0; i < SPI_processed; i++) { + HeapTuple tuple = SPI_tuptable->vals[i]; + dep_deposit_serial_ids[i] = + DatumGetInt64(SPI_getbinval(tuple, SPI_tuptable->tupdesc, 1, &ret)); + dep_coin_pubs[i] = + DatumGetByteaP(SPI_getbinval(tuple, SPI_tuptable->tupdesc, 2, &ret)); + dep_amount_vals[i] = + DatumGetInt64(SPI_getbinval(tuple, SPI_tuptable->tupdesc, 3, &ret)); + dep_amount_fracs[i] = + DatumGetInt32(SPI_getbinval(tuple, SPI_tuptable->tupdesc, 4, &ret)); + } + + + if (refund_plan == NULL) + { + const char *ref_sql = + "ref AS (" + " SELECT" + " amount_with_fee_val AS refund_val" + " ,amount_with_fee_frac AS refund_frac" + " ,coin_pub" + " ,deposit_serial_id" + " FROM refunds" + " WHERE coin_pub IN (SELECT coin_pub FROM dep)" + " AND deposit_serial_id IN (SELECT deposit_serial_id FROM dep)) "; + SPIPlanPtr new_plan = SPI_prepare(ref_sql, 0, NULL); + if (new_plan == NULL) + elog (ERROR, "SPI_prepare for refund failed ! \n"); + refund_plan = SPI_saveplan(new_plan); + if (refund_plan == NULL) + { + elog(ERROR, "SPI_saveplan for refund failed ! \n"); + } + } + + int64t_t *ref_deposit_serial_ids = palloc(sizeof(int64_t) * SPI_processed); + + int res = SPI_execute_plan (refund_plan, NULL, NULL, false, 0); + if (res != SPI_OK_SELECT) + { + elog(ERROR, "Failed to execute subquery 2\n"); + } + SPITupleTable *tuptable = SPI_tuptable; + TupleDesc tupdesc = tuptable->tupdesc; + for (unsigned int i = 0; i < SPI_processed; i++) + { + HeapTuple tuple = tuptable->vals[i]; + Datum refund_val = SPI_getbinval(tuple, tupdesc, 1, &refund_val_isnull); + Datum refund_frac = SPI_getbinval(tuple, tupdesc, 2, &refund_frac_isnull); + Datum coin_pub = SPI_getbinval(tuple, tupdesc, 3, &coin_pub_isnull); + Datum deposit_serial_id = SPI_getbinval(tuple, tupdesc, 4, &deposit_serial_id_isnull); + if (refund_val_isnull + || refund_frac_isnull + || coin_pub_isnull + || deposit_serial_id_isnull ) + { + elog(ERROR, "Failed to retrieve data from subquery 2"); + } + uint64_t refund_val_int = DatumGetUInt64(refund_val); + uint32_t refund_frac_int = DatumGetUInt32(refund_frac); + BYTEA coin_pub = DatumGetByteaP(coin_pub); + ref_deposit_serial_ids = DatumGetInt64(deposit_serial_id); + + refund *new_refund = (refund*) palloc(sizeof(refund)); + new_refund->coin_pub = coin_pub_str; + new_refund->deposit_serial_id = deposit_serial_id_int; + new_refund->amount_with_fee_val = refund_val_int; + new_refund->amount_with_fee_frac = refund_frac_int; + } + + + if (refund_by_coin_plan == NULL) + { + const char *ref_by_coin_sql = + "ref_by_coin AS (" + " SELECT" + " SUM(refund_val) AS sum_refund_val" + " ,SUM(refund_frac) AS sum_refund_frac" + " ,coin_pub" + " ,deposit_serial_id" + " FROM ref" + " GROUP BY coin_pub, deposit_serial_id) "; + SPIPlanPtr new_plan = SPI_prepare (ref_by_coin_sql, 0, NULL); + if (new_plan == NULL) + elog(ERROR, "SPI_prepare for refund by coin failed ! \n"); + refund_by_coin_plan = SPI_saveplan (new_plan); + if (refund_by_coin_plan == NULL) + elog(ERROR, "SPI_saveplan for refund failed"); + } + + + int res = SPI_execute_plan (refund_by_coin_plan, NULL, NULL, false, 0); + if (res != SPI_OK_SELECT) + { + elog(ERROR, "Failed to execute subquery 2\n"); + } + + SPITupleTable *tuptable = SPI_tuptable; + TupleDesc tupdesc = tuptable->tupdesc; + for (unsigned int i = 0; i < SPI_processed; i++) + { + HeapTuple tuple = tuptable->vals[i]; + Datum sum_refund_val = SPI_getbinval(tuple, tupdesc, 1, &refund_val_isnull); + Datum sum_refund_frac = SPI_getbinval(tuple, tupdesc, 2, &refund_frac_isnull); + Datum coin_pub = SPI_getbinval(tuple, tupdesc, 3, &coin_pub_isnull); + Datum deposit_serial_id_int = SPI_getbinval(tuple, tupdesc, 4, &deposit_serial_id_isnull); + if (refund_val_isnull + || refund_frac_isnull + || coin_pub_isnull + || deposit_serial_id_isnull ) + { + elog(ERROR, "Failed to retrieve data from subquery 2"); + } + uint64_t s_refund_val_int = DatumGetUInt64(sum_refund_val); + uint32_t s_refund_frac_int = DatumGetUInt32(sum_refund_frac); + BYTEA coin_pub = DatumGetByteaP(coin_pub); + uint64_t deposit_serial_id_int = DatumGetInt64(deposit_serial_id_int); + refund *new_refund_by_coin = (refund*) palloc(sizeof(refund)); + new_refund_by_coin->coin_pub = coin_pub; + new_refund_by_coin->deposit_serial_id = deposit_serial_id_int; + new_refund_by_coin->refund_amount_with_fee_val = s_refund_val_int; + new_refund_by_coin->refund_amount_with_fee_frac = s_refund_frac_int; + } + + + if (norm_refund_by_coin_plan == NULL) + { + const char *norm_ref_by_coin_sql = + "norm_ref_by_coin AS (" + " SELECT" + " coin_pub" + " ,deposit_serial_id" + " FROM ref_by_coin) "; + SPIPlanPtr new_plan = SPI_prepare (norm_ref_by_coin_sql, 0, NULL); + if (new_plan == NULL) + elog(ERROR, "SPI_prepare for norm refund by coin failed ! \n"); + norm_refund_by_coin_plan = SPI_saveplan(new_plan); + if (norm_refund_by_coin_plan == NULL) + elog(ERROR, "SPI_saveplan for norm refund by coin failed ! \n"); + } + + double norm_refund_val = + ((double)new_refund_by_coin->refund_amount_with_fee_val + + (double)new_refund_by_coin->refund_amount_with_fee_frac) / 100000000; + double norm_refund_frac = + (double)new_refund_by_coin->refund_amount_with_fee_frac % 100000000; + + if (fully_refunded_coins_plan == NULL) + { + const char *fully_refunded_coins_sql = + "fully_refunded_coins AS (" + " SELECT" + " dep.coin_pub" + " FROM norm_ref_by_coin norm" + " JOIN dep" + " ON (norm.coin_pub = dep.coin_pub" + " AND norm.deposit_serial_id = dep.deposit_serial_id" + " AND norm.norm_refund_val = dep.amount_val" + " AND norm.norm_refund_frac = dep.amount_frac)) "; + SPIPlanPtr new_plan = + SPI_prepare(fully_refunded_coins_sql, 0, NULL); + if (new_plan == NULL) + elog (ERROR, "SPI_prepare for fully refunded coins failed ! \n"); + fully_refunded_coins_plan = SPI_saveplan(new_plan); + if (fully_refunded_coins_plan == NULL) + elog (ERROR, "SPI_saveplan for fully refunded coins failed ! \n"); + } + + int res = SPI_execute_plan(fully_refunded_coins_sql); + if ( res != SPI_OK_SELECT) + elog(ERROR, "Failed to execute subquery 4\n"); + SPITupleTable * tuptable = SPI_tuptable; + TupleDesc tupdesc = tuptable->tupdesc; + + BYTEA coin_pub = SPI_getbinval(tuple, tupdesc, 1, &coin_pub_isnull); + if (fees_plan == NULL) + { + const char *fees_sql = + "SELECT " + " denom.fee_deposit_val AS fee_val, " + " denom.fee_deposit_frac AS fee_frac, " + " cs.deposit_serial_id " + "FROM dep cs " + "JOIN known_coins kc USING (coin_pub) " + "JOIN denominations denom USING (denominations_serial) " + "WHERE coin_pub NOT IN (SELECT coin_pub FROM fully_refunded_coins)"; + SPIPlanPtr new_plan = + SPI_prepare(fees_sql, 0, NULL); + if (new_plan == NULL) + { + elog(ERROR, "SPI_prepare for fees failed ! \n"); + } + fees_plan = SPI_saveplan(new_plan); + if (fees_plan == NULL) + { + elog(ERROR, "SPI_saveplan for fees failed ! \n"); + } + } + } + int fees_ntuples; + SPI_execute(fees_sql, true, 0); + if (SPI_result_code() != SPI_OK_SELECT) + { + ereport( + ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("deposit fee query failed: error code %d \n", SPI_result_code()))); + } + fees_ntuples = SPI_processed; + + if (fees_ntuples > 0) + { + for (i = 0; i < fees_ntuples; i++) + { + Datum fee_val_datum = + SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &fee_null); + Datum fee_frac_datum = + SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &fee_null); + Datum deposit_id_datum = + SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 3, &deposit_null); + if (!fee_null && !deposit_null) + { + int64 fee_val = DatumGetInt64(fee_val_datum); + int32 fee_frac = DatumGetInt32(fee_frac_datum); + int64 deposit_id = DatumGetInt64(deposit_id_datum); + sum_fee_value += fee_val; + sum_fee_fraction += fee_frac; + char *insert_agg_sql = + psprintf( + "INSERT INTO " + "aggregation_tracking(deposit_serial_id, wtid_raw)" + " VALUES (%lld, '%s')", + deposit_id, wtid_raw); + SPI_execute(insert_agg_sql, false, 0); + } + } + } + + TupleDesc tupdesc; + SPITupleTable *tuptable = SPI_tuptable; + HeapTuple tuple; + Datum result; + + if (tuptable == NULL || SPI_processed != 1) + { + ereport( + ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Unexpected result \n"))); + } + tupdesc = SPI_tuptable->tupdesc; + tuple = SPI_tuptable->vals[0]; + result = HeapTupleGetDatum(tuple); + + TupleDesc result_desc = CreateTemplateTupleDesc(6, false); + TupleDescInitEntry(result_desc, (AttrNumber) 1, "sum_deposit_value", INT8OID, -1, 0); + TupleDescInitEntry(result_desc, (AttrNumber) 2, "sum_deposit_fraction", INT4OID, -1, 0); + TupleDescInitEntry(result_desc, (AttrNumber) 3, "sum_refund_value", INT8OID, -1, 0); + TupleDescInitEntry(result_desc, (AttrNumber) 4, "sum_refund_fraction", INT4OID, -1, 0); + TupleDescInitEntry(result_desc, (AttrNumber) 5, "sum_fee_value", INT8OID, -1, 0); + TupleDescInitEntry(result_desc, (AttrNumber) 6, "sum_fee_fraction", INT4OID, -1, 0); + + int ret = SPI_prepare(sql, 4, argtypes); + if (ret != SPI_OK_PREPARE) + { + elog(ERROR, "Failed to prepare statement: %s \n", sql); + } + + ret = SPI_execute_plan(plan, args, nulls, true, 0); + if (ret != SPI_OK_SELECT) + { + elog(ERROR, "Failed to execute statement: %s \n", sql); + } + + if (SPI_processed > 0) + { + HeapTuple tuple; + Datum values[6]; + bool nulls[6] = {false}; + values[0] = + SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &nulls[0]); + values[1] = + SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &nulls[1]); + values[2] = + SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 3, &nulls[2]); + values[3] = + SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 4, &nulls[3]); + values[4] = + SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 5, &nulls[4]); + values[5] = + SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 6, &nulls[5]); + tuple = heap_form_tuple(result_desc, values, nulls); + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); + } + SPI_finish(); + + PG_RETURN_NULL(); +} + + +