lightdb支持oracle pl/sql以及開源postgresql Plpgsql兩種過程性(增強)語言。本文講解pgpgsql函數出參的典型用法及限制。
注:匿名塊實際上走的是plorasql,而非plpgsql,即使調用的是plpgsql過程。
本文我們假設對於函數、存儲過程的調用是進行邏輯處理,而不是返回結果集或遊標,這通常是兩種上下文場景。
1、如果有多個出參,則不能帶return預定義類型或必須return record
zjh@postgres=# CREATE OR REPLACE FUNCTION public.f_square(inout v_a int, inout v_str varchar) returns int LANGUAGE 'plpgsql' AS $BODY$ declare lv_result int; begin v_a := v_a * v_a; v_str := v_str || '..ret'; end $BODY$; CREATE FUNCTION zjh@postgres=# select f_square(10,'a'); ERROR: invalid input syntax for type integer: "(100,a..ret)" CONTEXT: PL/pgSQL function f_square(integer,character varying) while casting return value to function's return type
除了事務外,該限制和oracle存在明顯的不兼容性。
zjh@postgres=# CREATE OR REPLACE FUNCTION public.f_square(inout v_a int) returns int LANGUAGE 'plpgsql' AS $BODY$ declare lv_result int; begin v_a := v_a * v_a; -- v_str := v_str || '..ret'; end $BODY$; CREATE FUNCTION zjh@postgres=# zjh@postgres=# zjh@postgres=# select f_square(10); f_square ---------- 100 (1 row)
zjh@postgres=# CREATE OR REPLACE FUNCTION public.f_square(inout v_a int, inout v_str varchar) returns record LANGUAGE 'plpgsql' AS $BODY$ declare lv_result int; begin v_a := v_a * v_a; v_str := v_str || '..ret'; end $BODY$; CREATE FUNCTION zjh@postgres=# select f_square(10,'a'); f_square -------------- (100,a..ret) (1 row)
2、plpgsql匿名塊或過程中使用perform調用函數未修改出參值
zjh@postgres=# CREATE OR REPLACE FUNCTION public.f_square(inout v_a int, inout v_str varchar) returns record LANGUAGE 'plpgsql' AS $BODY$ declare lv_result int; begin v_a := v_a * v_a; v_str := v_str || '..ret';
raise notice 'v_str=%',v_str; end $BODY$; CREATE FUNCTION zjh@postgres=# declare v_a int :=10; v_str varchar(100) := 'b'; begin perform f_square(v_a,v_str);
raise notice 'v_a=%,v_str=%',v_a,v_str; end; / NOTICE: v_str=b..ret NOTICE: v_a=10,v_str=b DO
zjh@postgres=# declare v_a int :=10; v_str varchar(100) := 'b'; begin select * into v_a,v_str from f_square(v_a,v_str);
raise notice 'v_a=%,v_str=%',v_a,v_str; end; / NOTICE: v_str=b..ret NOTICE: v_a=100,v_str=b..ret DO
爲什麼通過perform調用不生效,直接select 函數(v1,v2),select into o1,o2 from 函數(v1,v2)能返回呢?因爲本質上in/out/inout都是傳值處理,所有的出參事實上都是通過return scalar或return record實現。exec_stmt_execsql處理完了into才丟棄SPI_tuptable,exec_stmt_perform則立刻丟棄。
postgresql函數OUT和INOUT使用方法 https://blog.csdn.net/llj318/article/details/122387617
實現oracle的v_ret := fnc(in a, out b,inout c);調用兼容支持
declare v_a int :=10; v_str varchar(100) := 'b'; v_ret int; begin v_ret := f_square(v_a,v_str); -- 因爲pg不支持帶出參返回非record的函數定義,所以自然也就不支持oracle的這種調用。 raise notice 'v_a=%,v_str=%',v_a,v_str; end; 其實只要實現PLpgSQL_execstate.retval即可(因爲record存儲在SPITupleTable *eval_tuptable成員中,所以不存在破壞)。 typedef struct PLpgSQL_execstate { PLpgSQL_function *func; /* function being executed */ TriggerData *trigdata; /* if regular trigger, data about firing */ EventTriggerData *evtrigdata; /* if event trigger, data about firing */ Datum retval; bool retisnull; Oid rettype; /* type of current retval */ /* temporary state for results from evaluation of query or expr */ SPITupleTable *eval_tuptable; uint64 eval_processed;
lightdb將在23.3版本支持該兼容性。
調用存儲過程獲取出參
zjh@postgres=# create or replace procedure proce_inouttest(in nD1 bigint, inout szD varchar, out nD2 integer) zjh@postgres-# as zjh@postgres$# $$ zjh@postgres$# begin zjh@postgres$# nD1:=99; zjh@postgres$# szD:='qaz'; zjh@postgres$# nD2:=88; zjh@postgres$# end; zjh@postgres$# $$ zjh@postgres-# language plpgsql; zjh@postgres=# call proce_inouttest(1,'3',1); szd | nd2 -----+----- qaz | 88 (1 row)
CALL executes a procedure.
If the procedure has any output parameters, then a result row will be returned, containing the values of those parameters.
zjh@postgres=# DO $$ zjh@postgres$# DECLARE myvar int := 5; zjh@postgres$# BEGIN zjh@postgres$# CALL triple(myvar); zjh@postgres$# RAISE NOTICE 'myvar = %', myvar; -- prints 15 zjh@postgres$# END; zjh@postgres$# $$; NOTICE: myvar = 15
在plpgsql中,存儲過程是支持出參賦值的。參見http://www.light-pg.com/docs/lightdb/13.8-22.3/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-CALLING-PROCEDURE。在請求返回的時候,OUT重新賦值回去的。這一點存儲過程和函數的行爲不一樣。
libpq調用存儲過程
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <sys/types.h> #include "libpq-fe.h" /* for ntohl/htonl */ #include <netinet/in.h> #include <arpa/inet.h> static void exit_nicely(PGconn *conn) { PQfinish(conn); exit(1); } void test_call_sp() { const char *conninfo; PGconn *conn; PGresult *res; const char *paramValues[3]; int paramLengths[3]; int paramFormats[3]; uint32_t binaryIntVal; uint64_t bigbinaryIntVal; int nFields; int i, j; conninfo = "postgresql:///postgres?host=localhost&port=18888"; /* Make a connection to the database */ conn = PQconnectdb(conninfo); /* Check to see that the backend connection was successfully made */ if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); exit_nicely(conn); } /* Convert integer value "2" to network byte order */ binaryIntVal = htonl((uint32_t) 2); bigbinaryIntVal = htonl((uint64_t) 2); /* Set up parameter arrays for PQexecParams */ paramValues[0] = (char *) &bigbinaryIntVal; paramLengths[0] = sizeof(bigbinaryIntVal); paramFormats[0] = 1; /* binary */ paramValues[1] = "2"; paramLengths[1] = 1; paramFormats[1] = 0; /* test */ paramValues[2] = (char *) &binaryIntVal; paramLengths[2] = sizeof(binaryIntVal); paramFormats[2] = 1; /* binary */ res = PQexecParams(conn, "call proce_inouttest($1,$2,$3)", 3, /* one param */ NULL, /* let the backend deduce param type */ paramValues, paramLengths, paramFormats, 0); /* ask for text results */ if (PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, "SELECT failed: %s", PQerrorMessage(conn)); PQclear(res); exit_nicely(conn); } nFields = PQnfields(res); for (i = 0; i < nFields; i++) printf("%-15s", PQfname(res, i)); printf("\n\n"); /* next, print out the instances */ for (i = 0; i < PQntuples(res); i++) { for (j = 0; j < nFields; j++) printf("%-15s", PQgetvalue(res, i, j)); printf("\n"); } PQclear(res); /* close the connection to the database and cleanup */ PQfinish(conn); return 0; }
create or replace procedure proce_inouttest(in nD1 bigint, inout szD varchar, out nD2 integer) as $$ begin nD1:=99; szD:='qaz'; nD2:=88; end; $$ language plpgsql;
SQL(注意不是psql)對於存儲過程out/inout參數返回值的處理,與一般的select查詢返回值處理一樣,使用PQnfields、PQntuples、PQgetvalue等函數對結果PGresult結構體進行處理。
輸出如下:
szd nd2
qaz 88 --名字爲啥是qaz?
其它
注:postgresql不支持oracle的select bulk collect和fetch bulk collect,但是如果返回多行,沒有指定strict的情況下,只返回第1行,但是不會報錯。
通過數組也可以支持該特性。