From bd2be3426f2c02a12226c890cafa99f535c570d3 Mon Sep 17 00:00:00 2001 From: humengyao Date: Wed, 23 Oct 2024 23:38:06 -0700 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81gms=5Fsql=E9=AB=98=E7=BA=A7?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/script/aarch64_opengauss_list | 3 + .../opengauss_release_list_ubuntu_single | 3 + build/script/x86_64_opengauss_list | 3 + contrib/CMakeLists.txt | 2 + contrib/gms_sql/CMakeLists.txt | 21 + contrib/gms_sql/Makefile | 25 + contrib/gms_sql/data/dummy.txt | 1 + contrib/gms_sql/expected/gms_sql.out | 753 ++++++ contrib/gms_sql/gms_sql--1.0.sql | 117 + contrib/gms_sql/gms_sql.control | 5 + contrib/gms_sql/gms_sql.cpp | 2295 +++++++++++++++++ contrib/gms_sql/gms_sql.h | 163 ++ contrib/gms_sql/sql/gms_sql.sql | 315 +++ src/bin/gs_guc/cluster_guc.conf | 1 + src/common/backend/parser/gram.y | 28 + src/common/backend/utils/misc/guc.cpp | 15 + src/common/backend/utils/mmgr/mcxt.cpp | 55 + src/gausskernel/runtime/executor/spi.cpp | 23 + src/include/executor/spi.h | 2 + .../knl/knl_guc/knl_session_attr_common.h | 1 + src/include/utils/palloc.h | 23 + 21 files changed, 3854 insertions(+) create mode 100644 contrib/gms_sql/CMakeLists.txt create mode 100644 contrib/gms_sql/Makefile create mode 100644 contrib/gms_sql/data/dummy.txt create mode 100644 contrib/gms_sql/expected/gms_sql.out create mode 100644 contrib/gms_sql/gms_sql--1.0.sql create mode 100644 contrib/gms_sql/gms_sql.control create mode 100644 contrib/gms_sql/gms_sql.cpp create mode 100644 contrib/gms_sql/gms_sql.h create mode 100644 contrib/gms_sql/sql/gms_sql.sql diff --git a/build/script/aarch64_opengauss_list b/build/script/aarch64_opengauss_list index 346ee0554b..96e9c684ea 100644 --- a/build/script/aarch64_opengauss_list +++ b/build/script/aarch64_opengauss_list @@ -129,6 +129,8 @@ ./share/postgresql/extension/gms_stats.control ./share/postgresql/extension/gms_profiler--1.0.sql ./share/postgresql/extension/gms_profiler.control +./share/postgresql/extension/gms_sql--1.0.sql +./share/postgresql/extension/gms_sql.control ./share/postgresql/timezone/GB-Eire ./share/postgresql/timezone/Turkey ./share/postgresql/timezone/Kwajalein @@ -832,6 +834,7 @@ ./lib/postgresql/gms_lob.so ./lib/postgresql/gms_stats.so ./lib/postgresql/gms_profiler.so +./lib/postgresql/gms_sql.so ./lib/libpljava.so ./lib/libpq.a ./lib/libpq.so diff --git a/build/script/opengauss_release_list_ubuntu_single b/build/script/opengauss_release_list_ubuntu_single index b436eef1ad..1ac3feb9c3 100644 --- a/build/script/opengauss_release_list_ubuntu_single +++ b/build/script/opengauss_release_list_ubuntu_single @@ -117,6 +117,8 @@ ./share/postgresql/extension/gms_stats.control ./share/postgresql/extension/gms_profiler--1.0.sql ./share/postgresql/extension/gms_profiler.control +./share/postgresql/extension/gms_sql--1.0.sql +./share/postgresql/extension/gms_sql.control ./share/postgresql/timezone/GB-Eire ./share/postgresql/timezone/Turkey ./share/postgresql/timezone/Kwajalein @@ -802,6 +804,7 @@ ./lib/postgresql/gms_lob.so ./lib/postgresql/gms_stats.so ./lib/postgresql/gms_profiler.so +./lib/postgresql/gms_sql.so ./lib/libpljava.so ./lib/libpq.a ./lib/libpq.so diff --git a/build/script/x86_64_opengauss_list b/build/script/x86_64_opengauss_list index 50f8be714f..b3765a005a 100644 --- a/build/script/x86_64_opengauss_list +++ b/build/script/x86_64_opengauss_list @@ -129,6 +129,8 @@ ./share/postgresql/extension/gms_stats.control ./share/postgresql/extension/gms_profiler--1.0.sql ./share/postgresql/extension/gms_profiler.control +./share/postgresql/extension/gms_sql--1.0.sql +./share/postgresql/extension/gms_sql.control ./share/postgresql/timezone/GB-Eire ./share/postgresql/timezone/Turkey ./share/postgresql/timezone/Kwajalein @@ -832,6 +834,7 @@ ./lib/postgresql/assessment.so ./lib/postgresql/gms_output.so ./lib/postgresql/gms_profiler.so +./lib/postgresql/gms_sql.so ./lib/libpljava.so ./lib/libpq.a ./lib/libpq.so diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 3843da9990..c3acfb6953 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -30,6 +30,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/gms_stats ${CMAKE_CURRENT_SOURCE_DIR}/gms_profiler ${CMAKE_CURRENT_SOURCE_DIR}/gms_lob + ${CMAKE_CURRENT_SOURCE_DIR}/gms_sql ) add_subdirectory(hstore) @@ -49,6 +50,7 @@ add_subdirectory(pg_xlogdump) add_subdirectory(file_fdw) add_subdirectory(log_fdw) add_subdirectory(gms_stats) +add_subdirectory(gms_sql) if("${ENABLE_MULTIPLE_NODES}" STREQUAL "OFF") add_subdirectory(gc_fdw) endif() diff --git a/contrib/gms_sql/CMakeLists.txt b/contrib/gms_sql/CMakeLists.txt new file mode 100644 index 0000000000..48b7c8b1ab --- /dev/null +++ b/contrib/gms_sql/CMakeLists.txt @@ -0,0 +1,21 @@ +#This is the main CMAKE for build all gms_sql. +# gms_sql +AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} TGT_gms_sql_SRC) +set(TGT_gms_sql_INC + ${PROJECT_OPENGS_DIR}/contrib/gms_sql + ${PROJECT_OPENGS_DIR}/contrib +) + +set(gms_sql_DEF_OPTIONS ${MACRO_OPTIONS}) +set(gms_sql_COMPILE_OPTIONS ${OPTIMIZE_OPTIONS} ${OS_OPTIONS} ${PROTECT_OPTIONS} ${WARNING_OPTIONS} ${LIB_SECURE_OPTIONS} ${CHECK_OPTIONS}) +set(gms_sql_LINK_OPTIONS ${LIB_LINK_OPTIONS}) +add_shared_libtarget(gms_sql TGT_gms_sql_SRC TGT_gms_sql_INC "${gms_sql_DEF_OPTIONS}" "${gms_sql_COMPILE_OPTIONS}" "${gms_sql_LINK_OPTIONS}") +set_target_properties(gms_sql PROPERTIES PREFIX "") + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/gms_sql.control + DESTINATION share/postgresql/extension/ +) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/gms_sql--1.0.sql + DESTINATION share/postgresql/extension/ +) +install(TARGETS gms_sql DESTINATION lib/postgresql) diff --git a/contrib/gms_sql/Makefile b/contrib/gms_sql/Makefile new file mode 100644 index 0000000000..d1a1de3ad4 --- /dev/null +++ b/contrib/gms_sql/Makefile @@ -0,0 +1,25 @@ +# contrib/gms_sql/Makefile +MODULE_big = gms_sql +OBJS = gms_sql.o + +EXTENSION = gms_sql +DATA = gms_sql--1.0.sql + +REGRESS = gms_sql + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/gms_sql +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +regress_home = $(top_builddir)/src/test/regress +REGRESS_OPTS = -c 0 -d 1 -r 1 -p 25632 --single_node -w --keep_last_data=false \ + --regconf=$(regress_home)/regress.conf \ + --temp-config=$(regress_home)/make_fastcheck_postgresql.conf +include $(top_srcdir)/contrib/contrib-global.mk +endif + +gms_sql.o: gms_sql.cpp diff --git a/contrib/gms_sql/data/dummy.txt b/contrib/gms_sql/data/dummy.txt new file mode 100644 index 0000000000..8e09a4f6cc --- /dev/null +++ b/contrib/gms_sql/data/dummy.txt @@ -0,0 +1 @@ +The openGauss regression needs this file to run. diff --git a/contrib/gms_sql/expected/gms_sql.out b/contrib/gms_sql/expected/gms_sql.out new file mode 100644 index 0000000000..a46227b3e9 --- /dev/null +++ b/contrib/gms_sql/expected/gms_sql.out @@ -0,0 +1,753 @@ +CREATE EXTENSION gms_sql; +set gms_sql_max_open_cursor_count = 501; +ERROR: 501 is outside the valid range for parameter "gms_sql_max_open_cursor_count" (10 .. 500) +reset gms_sql_max_open_cursor_count; +show gms_sql_max_open_cursor_count; + gms_sql_max_open_cursor_count +------------------------------- + 100 +(1 row) + +do $$ +declare + c int; + strval varchar; + intval int; + nrows int default 30; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'select ''ahoj'' || i, i from generate_series(1, :nrows) g(i)', gms_sql.v6); + gms_sql.bind_variable(c, 'nrows', nrows); + gms_sql.define_column(c, 1, strval); + gms_sql.define_column(c, 2, intval); + perform gms_sql.execute(c); + while gms_sql.fetch_rows(c) > 0 + loop + gms_sql.column_value(c, 1, strval); + gms_sql.column_value(c, 2, intval); + raise notice 'c1: %, c2: %', strval, intval; + end loop; + gms_sql.close_cursor(c); +end; +$$; +NOTICE: c1: ahoj1, c2: 1 +NOTICE: c1: ahoj2, c2: 2 +NOTICE: c1: ahoj3, c2: 3 +NOTICE: c1: ahoj4, c2: 4 +NOTICE: c1: ahoj5, c2: 5 +NOTICE: c1: ahoj6, c2: 6 +NOTICE: c1: ahoj7, c2: 7 +NOTICE: c1: ahoj8, c2: 8 +NOTICE: c1: ahoj9, c2: 9 +NOTICE: c1: ahoj10, c2: 10 +NOTICE: c1: ahoj11, c2: 11 +NOTICE: c1: ahoj12, c2: 12 +NOTICE: c1: ahoj13, c2: 13 +NOTICE: c1: ahoj14, c2: 14 +NOTICE: c1: ahoj15, c2: 15 +NOTICE: c1: ahoj16, c2: 16 +NOTICE: c1: ahoj17, c2: 17 +NOTICE: c1: ahoj18, c2: 18 +NOTICE: c1: ahoj19, c2: 19 +NOTICE: c1: ahoj20, c2: 20 +NOTICE: c1: ahoj21, c2: 21 +NOTICE: c1: ahoj22, c2: 22 +NOTICE: c1: ahoj23, c2: 23 +NOTICE: c1: ahoj24, c2: 24 +NOTICE: c1: ahoj25, c2: 25 +NOTICE: c1: ahoj26, c2: 26 +NOTICE: c1: ahoj27, c2: 27 +NOTICE: c1: ahoj28, c2: 28 +NOTICE: c1: ahoj29, c2: 29 +NOTICE: c1: ahoj30, c2: 30 +do $$ +declare + c int; + strval varchar; + intval int; + nrows int default 30; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'select ''ahoj'' || i, i from generate_series(1, :nrows) g(i)', gms_sql.v7); + gms_sql.bind_variable(c, 'nrows', nrows); + gms_sql.define_column(c, 1, strval); + gms_sql.define_column(c, 2, intval); + perform gms_sql.execute(c); + while gms_sql.fetch_rows(c) > 0 + loop + strval := gms_sql.column_value_f(c, 1, strval); + intval := gms_sql.column_value_f(c, 2, intval); + raise notice 'c1: %, c2: %', strval, intval; + end loop; + gms_sql.close_cursor(c); +end; +$$; +NOTICE: c1: ahoj1, c2: 1 +NOTICE: c1: ahoj2, c2: 2 +NOTICE: c1: ahoj3, c2: 3 +NOTICE: c1: ahoj4, c2: 4 +NOTICE: c1: ahoj5, c2: 5 +NOTICE: c1: ahoj6, c2: 6 +NOTICE: c1: ahoj7, c2: 7 +NOTICE: c1: ahoj8, c2: 8 +NOTICE: c1: ahoj9, c2: 9 +NOTICE: c1: ahoj10, c2: 10 +NOTICE: c1: ahoj11, c2: 11 +NOTICE: c1: ahoj12, c2: 12 +NOTICE: c1: ahoj13, c2: 13 +NOTICE: c1: ahoj14, c2: 14 +NOTICE: c1: ahoj15, c2: 15 +NOTICE: c1: ahoj16, c2: 16 +NOTICE: c1: ahoj17, c2: 17 +NOTICE: c1: ahoj18, c2: 18 +NOTICE: c1: ahoj19, c2: 19 +NOTICE: c1: ahoj20, c2: 20 +NOTICE: c1: ahoj21, c2: 21 +NOTICE: c1: ahoj22, c2: 22 +NOTICE: c1: ahoj23, c2: 23 +NOTICE: c1: ahoj24, c2: 24 +NOTICE: c1: ahoj25, c2: 25 +NOTICE: c1: ahoj26, c2: 26 +NOTICE: c1: ahoj27, c2: 27 +NOTICE: c1: ahoj28, c2: 28 +NOTICE: c1: ahoj29, c2: 29 +NOTICE: c1: ahoj30, c2: 30 +drop table if exists foo; +NOTICE: table "foo" does not exist, skipping +create table foo(a int, b varchar, c numeric); +do $$ +declare c int; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'insert into foo values(:a, :b, :c)', gms_sql.native); + for i in 1..100 + loop + gms_sql.bind_variable(c, 'a', i); + gms_sql.bind_variable(c, 'b', 'Ahoj ' || i); + gms_sql.bind_variable(c, 'c', i + 0.033); + perform gms_sql.execute(c); + end loop; + gms_sql.close_cursor(c); +end; +$$; +select * from foo; + a | b | c +-----+----------+--------- + 1 | Ahoj 1 | 1.033 + 2 | Ahoj 2 | 2.033 + 3 | Ahoj 3 | 3.033 + 4 | Ahoj 4 | 4.033 + 5 | Ahoj 5 | 5.033 + 6 | Ahoj 6 | 6.033 + 7 | Ahoj 7 | 7.033 + 8 | Ahoj 8 | 8.033 + 9 | Ahoj 9 | 9.033 + 10 | Ahoj 10 | 10.033 + 11 | Ahoj 11 | 11.033 + 12 | Ahoj 12 | 12.033 + 13 | Ahoj 13 | 13.033 + 14 | Ahoj 14 | 14.033 + 15 | Ahoj 15 | 15.033 + 16 | Ahoj 16 | 16.033 + 17 | Ahoj 17 | 17.033 + 18 | Ahoj 18 | 18.033 + 19 | Ahoj 19 | 19.033 + 20 | Ahoj 20 | 20.033 + 21 | Ahoj 21 | 21.033 + 22 | Ahoj 22 | 22.033 + 23 | Ahoj 23 | 23.033 + 24 | Ahoj 24 | 24.033 + 25 | Ahoj 25 | 25.033 + 26 | Ahoj 26 | 26.033 + 27 | Ahoj 27 | 27.033 + 28 | Ahoj 28 | 28.033 + 29 | Ahoj 29 | 29.033 + 30 | Ahoj 30 | 30.033 + 31 | Ahoj 31 | 31.033 + 32 | Ahoj 32 | 32.033 + 33 | Ahoj 33 | 33.033 + 34 | Ahoj 34 | 34.033 + 35 | Ahoj 35 | 35.033 + 36 | Ahoj 36 | 36.033 + 37 | Ahoj 37 | 37.033 + 38 | Ahoj 38 | 38.033 + 39 | Ahoj 39 | 39.033 + 40 | Ahoj 40 | 40.033 + 41 | Ahoj 41 | 41.033 + 42 | Ahoj 42 | 42.033 + 43 | Ahoj 43 | 43.033 + 44 | Ahoj 44 | 44.033 + 45 | Ahoj 45 | 45.033 + 46 | Ahoj 46 | 46.033 + 47 | Ahoj 47 | 47.033 + 48 | Ahoj 48 | 48.033 + 49 | Ahoj 49 | 49.033 + 50 | Ahoj 50 | 50.033 + 51 | Ahoj 51 | 51.033 + 52 | Ahoj 52 | 52.033 + 53 | Ahoj 53 | 53.033 + 54 | Ahoj 54 | 54.033 + 55 | Ahoj 55 | 55.033 + 56 | Ahoj 56 | 56.033 + 57 | Ahoj 57 | 57.033 + 58 | Ahoj 58 | 58.033 + 59 | Ahoj 59 | 59.033 + 60 | Ahoj 60 | 60.033 + 61 | Ahoj 61 | 61.033 + 62 | Ahoj 62 | 62.033 + 63 | Ahoj 63 | 63.033 + 64 | Ahoj 64 | 64.033 + 65 | Ahoj 65 | 65.033 + 66 | Ahoj 66 | 66.033 + 67 | Ahoj 67 | 67.033 + 68 | Ahoj 68 | 68.033 + 69 | Ahoj 69 | 69.033 + 70 | Ahoj 70 | 70.033 + 71 | Ahoj 71 | 71.033 + 72 | Ahoj 72 | 72.033 + 73 | Ahoj 73 | 73.033 + 74 | Ahoj 74 | 74.033 + 75 | Ahoj 75 | 75.033 + 76 | Ahoj 76 | 76.033 + 77 | Ahoj 77 | 77.033 + 78 | Ahoj 78 | 78.033 + 79 | Ahoj 79 | 79.033 + 80 | Ahoj 80 | 80.033 + 81 | Ahoj 81 | 81.033 + 82 | Ahoj 82 | 82.033 + 83 | Ahoj 83 | 83.033 + 84 | Ahoj 84 | 84.033 + 85 | Ahoj 85 | 85.033 + 86 | Ahoj 86 | 86.033 + 87 | Ahoj 87 | 87.033 + 88 | Ahoj 88 | 88.033 + 89 | Ahoj 89 | 89.033 + 90 | Ahoj 90 | 90.033 + 91 | Ahoj 91 | 91.033 + 92 | Ahoj 92 | 92.033 + 93 | Ahoj 93 | 93.033 + 94 | Ahoj 94 | 94.033 + 95 | Ahoj 95 | 95.033 + 96 | Ahoj 96 | 96.033 + 97 | Ahoj 97 | 97.033 + 98 | Ahoj 98 | 98.033 + 99 | Ahoj 99 | 99.033 + 100 | Ahoj 100 | 100.033 +(100 rows) + +truncate foo; +do $$ +declare c int; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'insert into foo values(:a, :b, :c)', gms_sql.native); + for i in 1..100 + loop + gms_sql.bind_variable_f(c, 'a', i); + gms_sql.bind_variable_f(c, 'b', 'Ahoj ' || i); + gms_sql.bind_variable_f(c, 'c', i + 0.033); + perform gms_sql.execute(c); + end loop; + gms_sql.close_cursor(c); +end; +$$; +select * from foo; + a | b | c +-----+----------+--------- + 1 | Ahoj 1 | 1.033 + 2 | Ahoj 2 | 2.033 + 3 | Ahoj 3 | 3.033 + 4 | Ahoj 4 | 4.033 + 5 | Ahoj 5 | 5.033 + 6 | Ahoj 6 | 6.033 + 7 | Ahoj 7 | 7.033 + 8 | Ahoj 8 | 8.033 + 9 | Ahoj 9 | 9.033 + 10 | Ahoj 10 | 10.033 + 11 | Ahoj 11 | 11.033 + 12 | Ahoj 12 | 12.033 + 13 | Ahoj 13 | 13.033 + 14 | Ahoj 14 | 14.033 + 15 | Ahoj 15 | 15.033 + 16 | Ahoj 16 | 16.033 + 17 | Ahoj 17 | 17.033 + 18 | Ahoj 18 | 18.033 + 19 | Ahoj 19 | 19.033 + 20 | Ahoj 20 | 20.033 + 21 | Ahoj 21 | 21.033 + 22 | Ahoj 22 | 22.033 + 23 | Ahoj 23 | 23.033 + 24 | Ahoj 24 | 24.033 + 25 | Ahoj 25 | 25.033 + 26 | Ahoj 26 | 26.033 + 27 | Ahoj 27 | 27.033 + 28 | Ahoj 28 | 28.033 + 29 | Ahoj 29 | 29.033 + 30 | Ahoj 30 | 30.033 + 31 | Ahoj 31 | 31.033 + 32 | Ahoj 32 | 32.033 + 33 | Ahoj 33 | 33.033 + 34 | Ahoj 34 | 34.033 + 35 | Ahoj 35 | 35.033 + 36 | Ahoj 36 | 36.033 + 37 | Ahoj 37 | 37.033 + 38 | Ahoj 38 | 38.033 + 39 | Ahoj 39 | 39.033 + 40 | Ahoj 40 | 40.033 + 41 | Ahoj 41 | 41.033 + 42 | Ahoj 42 | 42.033 + 43 | Ahoj 43 | 43.033 + 44 | Ahoj 44 | 44.033 + 45 | Ahoj 45 | 45.033 + 46 | Ahoj 46 | 46.033 + 47 | Ahoj 47 | 47.033 + 48 | Ahoj 48 | 48.033 + 49 | Ahoj 49 | 49.033 + 50 | Ahoj 50 | 50.033 + 51 | Ahoj 51 | 51.033 + 52 | Ahoj 52 | 52.033 + 53 | Ahoj 53 | 53.033 + 54 | Ahoj 54 | 54.033 + 55 | Ahoj 55 | 55.033 + 56 | Ahoj 56 | 56.033 + 57 | Ahoj 57 | 57.033 + 58 | Ahoj 58 | 58.033 + 59 | Ahoj 59 | 59.033 + 60 | Ahoj 60 | 60.033 + 61 | Ahoj 61 | 61.033 + 62 | Ahoj 62 | 62.033 + 63 | Ahoj 63 | 63.033 + 64 | Ahoj 64 | 64.033 + 65 | Ahoj 65 | 65.033 + 66 | Ahoj 66 | 66.033 + 67 | Ahoj 67 | 67.033 + 68 | Ahoj 68 | 68.033 + 69 | Ahoj 69 | 69.033 + 70 | Ahoj 70 | 70.033 + 71 | Ahoj 71 | 71.033 + 72 | Ahoj 72 | 72.033 + 73 | Ahoj 73 | 73.033 + 74 | Ahoj 74 | 74.033 + 75 | Ahoj 75 | 75.033 + 76 | Ahoj 76 | 76.033 + 77 | Ahoj 77 | 77.033 + 78 | Ahoj 78 | 78.033 + 79 | Ahoj 79 | 79.033 + 80 | Ahoj 80 | 80.033 + 81 | Ahoj 81 | 81.033 + 82 | Ahoj 82 | 82.033 + 83 | Ahoj 83 | 83.033 + 84 | Ahoj 84 | 84.033 + 85 | Ahoj 85 | 85.033 + 86 | Ahoj 86 | 86.033 + 87 | Ahoj 87 | 87.033 + 88 | Ahoj 88 | 88.033 + 89 | Ahoj 89 | 89.033 + 90 | Ahoj 90 | 90.033 + 91 | Ahoj 91 | 91.033 + 92 | Ahoj 92 | 92.033 + 93 | Ahoj 93 | 93.033 + 94 | Ahoj 94 | 94.033 + 95 | Ahoj 95 | 95.033 + 96 | Ahoj 96 | 96.033 + 97 | Ahoj 97 | 97.033 + 98 | Ahoj 98 | 98.033 + 99 | Ahoj 99 | 99.033 + 100 | Ahoj 100 | 100.033 +(100 rows) + +truncate foo; +do $$ +declare + c int; + a int[]; + b varchar[]; + ca numeric[]; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'insert into foo values(:a, :b, :c)', gms_sql.v6); + a := ARRAY[1, 2, 3, 4, 5]; + b := ARRAY['Ahoj', 'Nazdar', 'Bazar']; + ca := ARRAY[3.14, 2.22, 3.8, 4]; + + perform gms_sql.bind_array(c, 'a', a); + perform gms_sql.bind_array(c, 'b', b); + perform gms_sql.bind_array(c, 'c', ca); + raise notice 'inserted rows %d', gms_sql.execute(c); + gms_sql.close_cursor(c); +end; +$$; +NOTICE: inserted rows 3d +select * from foo; + a | b | c +---+--------+------ + 1 | Ahoj | 3.14 + 2 | Nazdar | 2.22 + 3 | Bazar | 3.8 +(3 rows) + +truncate foo; +do $$ +declare + c int; + a int[]; + b varchar[]; + ca numeric[]; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'insert into foo values(:a, :b, :c)', gms_sql.v7); + a := ARRAY[1, 2, 3, 4, 5]; + b := ARRAY['Ahoj', 'Nazdar', 'Bazar']; + ca := ARRAY[3.14, 2.22, 3.8, 4]; + + perform gms_sql.bind_array(c, 'a', a, 2, 3); + perform gms_sql.bind_array(c, 'b', b, 3, 4); + perform gms_sql.bind_array(c, 'c', ca); + raise notice 'inserted rows %d', gms_sql.execute(c); + gms_sql.close_cursor(c); +end; +$$; +NOTICE: inserted rows 1d +select * from foo; + a | b | c +---+-------+----- + 3 | Bazar | 3.8 +(1 row) + +truncate foo; +do $$ +declare + c int; + a int[]; + b varchar[]; + ca numeric[]; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'select i, ''Ahoj'' || i, i + 0.003 from generate_series(1, 35) g(i)', 0); + gms_sql.define_array(c, 1, a, 10, 1); + gms_sql.define_array(c, 2, b, 10, 1); + gms_sql.define_array(c, 3, ca, 10, 1); + + perform gms_sql.execute(c); + while gms_sql.fetch_rows(c) > 0 + loop + gms_sql.column_value(c, 1, a); + gms_sql.column_value(c, 2, b); + gms_sql.column_value(c, 3, ca); + raise notice 'a = %', a; + raise notice 'b = %', b; + raise notice 'c = %', ca; + end loop; + gms_sql.close_cursor(c); +end; +$$; +NOTICE: a = {1,2,3,4,5,6,7,8,9,10} +NOTICE: b = {Ahoj1,Ahoj2,Ahoj3,Ahoj4,Ahoj5,Ahoj6,Ahoj7,Ahoj8,Ahoj9,Ahoj10} +NOTICE: c = {1.003,2.003,3.003,4.003,5.003,6.003,7.003,8.003,9.003,10.003} +NOTICE: a = {11,12,13,14,15,16,17,18,19,20} +NOTICE: b = {Ahoj11,Ahoj12,Ahoj13,Ahoj14,Ahoj15,Ahoj16,Ahoj17,Ahoj18,Ahoj19,Ahoj20} +NOTICE: c = {11.003,12.003,13.003,14.003,15.003,16.003,17.003,18.003,19.003,20.003} +NOTICE: a = {21,22,23,24,25,26,27,28,29,30} +NOTICE: b = {Ahoj21,Ahoj22,Ahoj23,Ahoj24,Ahoj25,Ahoj26,Ahoj27,Ahoj28,Ahoj29,Ahoj30} +NOTICE: c = {21.003,22.003,23.003,24.003,25.003,26.003,27.003,28.003,29.003,30.003} +NOTICE: a = {31,32,33,34,35} +NOTICE: b = {Ahoj31,Ahoj32,Ahoj33,Ahoj34,Ahoj35} +NOTICE: c = {31.003,32.003,33.003,34.003,35.003} +drop table foo; +do $$ +declare +l_curid int; +l_cnt int; +l_desctab gms_sql.desc_tab; +l_sqltext varchar(2000); +begin + l_sqltext='select * from pg_object;'; + l_curid := gms_sql.open_cursor(); + gms_sql.parse(l_curid, l_sqltext, 0); + gms_sql.describe_columns(l_curid, l_cnt, l_desctab); + for i in 1 .. l_desctab.count loop + raise notice '%,% ', l_desctab(i).col_name,l_desctab(i).col_type; + end loop; + gms_sql.close_cursor(l_curid); +end; +$$; +NOTICE: object_oid,109 +NOTICE: object_type,96 +NOTICE: creator,109 +NOTICE: ctime,181 +NOTICE: mtime,181 +NOTICE: createcsn,2 +NOTICE: changecsn,2 +NOTICE: valid,109 +create table t1(id int, name varchar(20)); +insert into t1 select generate_series(1,3), 'abcddd'; +create table t2(a int, b date); +insert into t2 values(1, '2022-12-11 10:00:01.123'); +insert into t2 values(3, '2022-12-12 12:00:11.13'); +do $$ +declare + c1 refcursor; + c2 refcursor; +begin + open c1 for select * from t1; + gms_sql.return_result(c1); + open c2 for select * from t2; + gms_sql.return_result(c2); +end; +$$; +ResultSet #1 + + id | name +----+-------- + 1 | abcddd + 2 | abcddd + 3 | abcddd +(3 rows) + +ResultSet #2 + + a | b +---+-------------------------- + 1 | Sun Dec 11 10:00:01 2022 + 3 | Mon Dec 12 12:00:11 2022 +(2 rows) + +create procedure test_result() as +declare + c1 refcursor; + c2 refcursor; +begin + open c1 for select * from t1; + gms_sql.return_result(c1); + open c2 for select * from t2; + gms_sql.return_result(c2); +end; +/ +call test_result(); +ResultSet #1 + + id | name +----+-------- + 1 | abcddd + 2 | abcddd + 3 | abcddd +(3 rows) + +ResultSet #2 + + a | b +---+-------------------------- + 1 | Sun Dec 11 10:00:01 2022 + 3 | Mon Dec 12 12:00:11 2022 +(2 rows) + + test_result +------------- + +(1 row) + +drop procedure test_result; +create procedure aam() as +declare +id1 int; +id2 int; +begin +id1 :=gms_sql.open_cursor(); +gms_sql.parse(id1,'select * from t1', 1); +perform gms_sql.execute(id1); +gms_sql.return_result(id1); +gms_sql.close_cursor(id1); +id2 :=gms_sql.open_cursor(); +gms_sql.parse(id2,'select * from t2', 2); +perform gms_sql.execute(id2); +gms_sql.return_result(id2); +gms_sql.close_cursor(id2); +end; +/ +call aam(); +ResultSet #1 + + id | name +----+-------- + 1 | abcddd + 2 | abcddd + 3 | abcddd +(3 rows) + +ResultSet #2 + + a | b +---+-------------------------- + 1 | Sun Dec 11 10:00:01 2022 + 3 | Mon Dec 12 12:00:11 2022 +(2 rows) + + aam +----- + +(1 row) + +drop procedure aam; +create table col_name_too_long(aaaaabbbbbcccccdddddeeeeefffffggg int, col2 text); +do $$ +declare +l_curid int; +l_cnt int; +l_desctab gms_sql.desc_tab; +l_desctab2 gms_sql.desc_tab2; +l_sqltext varchar(2000); +begin + l_sqltext='select * from t1;'; + l_curid := gms_sql.open_cursor(); + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns(l_curid, l_cnt, l_desctab); + for i in 1 .. l_desctab.count loop + raise notice '%', l_desctab(i).col_name; + end loop; + -- output col_name + l_sqltext='select * from col_name_too_long;'; + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns2(l_curid, l_cnt, l_desctab2); + for i in 1 .. l_desctab2.count loop + raise notice '%', l_desctab2(i).col_name; + end loop; + -- error + l_sqltext='select * from col_name_too_long;'; + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns(l_curid, l_cnt, l_desctab); + for i in 1 .. l_desctab.count loop + raise notice '%', l_desctab(i).col_name; + end loop; +end; +$$; +NOTICE: id +NOTICE: name +NOTICE: aaaaabbbbbcccccdddddeeeeefffffggg +NOTICE: col2 +ERROR: desc_rec.col_name(33) is more than 32 +CONTEXT: SQL statement "CALL gms_sql.describe_columns(l_curid,l_cnt,l_desctab)" +PL/pgSQL function inline_code_block line 26 at SQL statement +select gms_sql.is_open(0); + is_open +--------- + t +(1 row) + +select gms_sql.close_cursor(0); + close_cursor +-------------- + +(1 row) + +do $$ +declare +l_curid int; +l_cnt int; +l_desctab3 gms_sql.desc_tab3; +l_desctab4 gms_sql.desc_tab4; +l_sqltext varchar(2000); +begin + l_sqltext='select * from col_name_too_long;'; + l_curid := gms_sql.open_cursor(); + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns3(l_curid, l_cnt, l_desctab3); + for i in 1 .. l_desctab3.count loop + raise notice '%,%,%', l_desctab3(i).col_type,l_desctab3(i).col_type_name,l_desctab3(i).col_name; + end loop; + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns3(l_curid, l_cnt, l_desctab4); + for i in 1 .. l_desctab4.count loop + raise notice '%,%,%,%', l_desctab3(i).col_type,l_desctab4(i).col_type_name,l_desctab4(i).col_type_name_len,l_desctab4(i).col_name_len; + end loop; + gms_sql.close_cursor(l_curid); +end; +$$; +NOTICE: 2,,aaaaabbbbbcccccdddddeeeeefffffggg +NOTICE: 109,text,col2 +NOTICE: 2,,,33 +NOTICE: 109,text,4,4 +drop table t1,t2, col_name_too_long; +select gms_sql.open_cursor(); + open_cursor +------------- + 0 +(1 row) + +select gms_sql.is_open(0); + is_open +--------- + t +(1 row) + +select gms_sql.open_cursor(); + open_cursor +------------- + 1 +(1 row) + +select gms_sql.is_open(1); + is_open +--------- + t +(1 row) + +select gms_sql.open_cursor(); + open_cursor +------------- + 2 +(1 row) + +select gms_sql.is_open(2); + is_open +--------- + t +(1 row) + +select gms_sql.open_cursor(); + open_cursor +------------- + 3 +(1 row) + +select gms_sql.is_open(3); + is_open +--------- + t +(1 row) + +select gms_sql.close_cursor(0); + close_cursor +-------------- + +(1 row) + +select gms_sql.close_cursor(1); + close_cursor +-------------- + +(1 row) + +select gms_sql.close_cursor(2); + close_cursor +-------------- + +(1 row) + +select gms_sql.close_cursor(3); + close_cursor +-------------- + +(1 row) + +select gms_sql.is_open(3); + is_open +--------- + f +(1 row) + +select gms_sql.close_cursor(10000); +ERROR: cursor 10000 value of cursor id is out of range +CONTEXT: referenced column: close_cursor +select gms_sql.close_cursor(-1); +ERROR: cursor -1 value of cursor id is out of range +CONTEXT: referenced column: close_cursor diff --git a/contrib/gms_sql/gms_sql--1.0.sql b/contrib/gms_sql/gms_sql--1.0.sql new file mode 100644 index 0000000000..d5dac570e9 --- /dev/null +++ b/contrib/gms_sql/gms_sql--1.0.sql @@ -0,0 +1,117 @@ +/* contrib/gms_sql/gms_sql--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION gms_sql" to load this file. \quit + +-- gms_sql package begin +-- gms_sql schema +CREATE SCHEMA gms_sql; +GRANT USAGE ON SCHEMA gms_sql TO PUBLIC; +CREATE FUNCTION gms_sql.is_open(c int) RETURNS bool AS 'MODULE_PATHNAME', 'gms_sql_is_open' LANGUAGE c STABLE NOT FENCED; +CREATE FUNCTION gms_sql.open_cursor() RETURNS int AS 'MODULE_PATHNAME', 'gms_sql_open_cursor' LANGUAGE c STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.close_cursor(c int) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_close_cursor' STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.debug_cursor(c int) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_debug_cursor' STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.parse(c int, stmt varchar2, ver int) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_parse' STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.bind_variable(c int, name varchar2, value "any") LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_bind_variable' STABLE NOT FENCED; +CREATE FUNCTION gms_sql.bind_variable_f(c int, name varchar2, value "any") RETURNS void AS 'MODULE_PATHNAME', 'gms_sql_bind_variable_f' LANGUAGE c STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.bind_array(c int, name varchar2, value anyarray) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_bind_array_3' package STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.bind_array(c int, name varchar2, value anyarray, index1 int, index2 int) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_bind_array_5' package STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.define_column(c int, col int, value "any", column_size int DEFAULT -1) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_define_column' STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.define_array(c int, col int, value "anyarray", cnt int, lower_bnd int) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_define_array' STABLE NOT FENCED; +CREATE FUNCTION gms_sql.execute(c int) RETURNS bigint AS 'MODULE_PATHNAME', 'gms_sql_execute' LANGUAGE c STABLE NOT FENCED; +CREATE FUNCTION gms_sql.fetch_rows(c int) RETURNS int AS 'MODULE_PATHNAME', 'gms_sql_fetch_rows' LANGUAGE c STABLE NOT FENCED; +CREATE FUNCTION gms_sql.execute_and_fetch(c int, exact bool DEFAULT false) RETURNS int AS 'MODULE_PATHNAME', 'gms_sql_execute_and_fetch' LANGUAGE c STABLE NOT FENCED; +CREATE FUNCTION gms_sql.last_row_count() RETURNS int AS 'MODULE_PATHNAME', 'gms_sql_last_row_count' LANGUAGE c STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.column_value(c int, pos int, INOUT value anyelement) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_column_value' STABLE NOT FENCED; +CREATE FUNCTION gms_sql.column_value_f(c int, pos int, value anyelement) RETURNS anyelement AS 'MODULE_PATHNAME', 'gms_sql_column_value_f' LANGUAGE c STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.return_result(c refcursor, to_client bool DEFAULT false) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_return_result' PACKAGE STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.return_result(c int, to_client bool DEFAULT false) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_return_result_i' PACKAGE STABLE NOT FENCED; +CREATE FUNCTION gms_sql.v6() RETURNS int AS $$ +BEGIN +return 0; +END; +$$ language plpgsql; + +CREATE FUNCTION gms_sql.native() RETURNS int AS $$ +BEGIN +return 1; +END; +$$ language plpgsql; + +CREATE FUNCTION gms_sql.v7() RETURNS int AS $$ +BEGIN +return 2; +END; +$$ language plpgsql; + +CREATE TYPE gms_sql.desc_rec AS ( + col_type int, + col_max_len int, + col_name varchar2(32), + col_name_len int, + col_schema_name text, + col_schema_name_len int, + col_precision int, + col_scale int, + col_charsetid int, + col_charsetform int, + col_null_ok boolean); +CREATE TYPE gms_sql.desc_rec2 AS ( + col_type int, + col_max_len int, + col_name text, + col_name_len int, + col_schema_name text, + col_schema_name_len int, + col_precision int, + col_scale int, + col_charsetid int, + col_charsetform int, + col_null_ok boolean); +CREATE TYPE gms_sql.desc_rec3 AS ( + col_type int, + col_max_len int, + col_name text, + col_name_len int, + col_schema_name text, + col_schema_name_len int, + col_precision int, + col_scale int, + col_charsetid int, + col_charsetform int, + col_null_ok boolean, + col_type_name text, + col_type_name_len int); +CREATE TYPE gms_sql.desc_rec4 AS ( + col_type int, + col_max_len int, + col_name text, + col_name_len int, + col_schema_name text, + col_schema_name_len int, + col_precision int, + col_scale int, + col_charsetid int, + col_charsetform int, + col_null_ok boolean, + col_type_name text, + col_type_name_len int); + + +CREATE TYPE gms_sql.desc_tab IS TABLE OF gms_sql.desc_rec; +CREATE TYPE gms_sql.desc_tab2 IS TABLE OF gms_sql.desc_rec2; +CREATE TYPE gms_sql.desc_tab3 IS TABLE OF gms_sql.desc_rec3; +CREATE TYPE gms_sql.desc_tab4 IS TABLE OF gms_sql.desc_rec4; +CREATE TYPE gms_sql.number_table IS TABLE OF number; +CREATE TYPE gms_sql.varchar2_table IS TABLE OF varchar2; +CREATE TYPE gms_sql.date_table IS TABLE OF date; +CREATE TYPE gms_sql.blob_table IS TABLE OF blob; +CREATE TYPE gms_sql.clob_table IS TABLE OF clob; +CREATE TYPE gms_sql.binary_double_table IS TABLE OF number; + +CREATE FUNCTION gms_sql.describe_columns_f(c int, OUT col_cnt int, OUT desc_t gms_sql.desc_rec3[]) AS 'MODULE_PATHNAME', 'gms_sql_describe_columns_f' LANGUAGE c STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.describe_columns(c int, INOUT col_cnt int, INOUT desc_t gms_sql.desc_rec[]) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_describe_columns_f' STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.describe_columns2(c int, INOUT col_cnt int, INOUT desc_t gms_sql.desc_rec2[]) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_describe_columns_f' STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.describe_columns3(c int, INOUT col_cnt int, INOUT desc_t gms_sql.desc_rec3[]) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_describe_columns_f' PACKAGE STABLE NOT FENCED; +CREATE PROCEDURE gms_sql.describe_columns3(c int, INOUT col_cnt int, INOUT desc_t gms_sql.desc_rec4[]) LANGUAGE c AS 'MODULE_PATHNAME', 'gms_sql_describe_columns_f' PACKAGE STABLE NOT FENCED; +-- gms_sql package end diff --git a/contrib/gms_sql/gms_sql.control b/contrib/gms_sql/gms_sql.control new file mode 100644 index 0000000000..1ba418c3cb --- /dev/null +++ b/contrib/gms_sql/gms_sql.control @@ -0,0 +1,5 @@ +# gms_sql extension +comment = 'collection of sql data for PL/SQL applications' +default_version = '1.0' +module_pathname = '$libdir/gms_sql' +relocatable = true diff --git a/contrib/gms_sql/gms_sql.cpp b/contrib/gms_sql/gms_sql.cpp new file mode 100644 index 0000000000..e13eb5c3b1 --- /dev/null +++ b/contrib/gms_sql/gms_sql.cpp @@ -0,0 +1,2295 @@ +#include "postgres.h" +#include "fmgr.h" +#include "funcapi.h" +#include "commands/extension.h" + +#if PG_VERSION_NUM < 120000 + +#include "access/heapam.h" +#include "access/printtup.h" + +#endif +#include "access/transam.h" +#include "access/tupconvert.h" +#include "lib/stringinfo.h" +#include "parser/scansup.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/elog.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" +#include "utils/typcache.h" +#include "executor/spi_priv.h" +#include "libpq/libpq.h" +#include "gms_sql.h" + +PG_MODULE_MAGIC; + +static char *next_token(char *str, char **start, size_t *len, ofTokenType *typ, char **sep, size_t *seplen); + +PG_FUNCTION_INFO_V1(gms_sql_is_open); +PG_FUNCTION_INFO_V1(gms_sql_open_cursor); +PG_FUNCTION_INFO_V1(gms_sql_close_cursor); +PG_FUNCTION_INFO_V1(gms_sql_parse); +PG_FUNCTION_INFO_V1(gms_sql_bind_variable); +PG_FUNCTION_INFO_V1(gms_sql_bind_variable_f); +PG_FUNCTION_INFO_V1(gms_sql_bind_array_3); +PG_FUNCTION_INFO_V1(gms_sql_bind_array_5); +PG_FUNCTION_INFO_V1(gms_sql_define_column); +PG_FUNCTION_INFO_V1(gms_sql_define_array); +PG_FUNCTION_INFO_V1(gms_sql_execute); +PG_FUNCTION_INFO_V1(gms_sql_fetch_rows); +PG_FUNCTION_INFO_V1(gms_sql_execute_and_fetch); +PG_FUNCTION_INFO_V1(gms_sql_column_value); +PG_FUNCTION_INFO_V1(gms_sql_column_value_f); +PG_FUNCTION_INFO_V1(gms_sql_last_row_count); +PG_FUNCTION_INFO_V1(gms_sql_describe_columns); +PG_FUNCTION_INFO_V1(gms_sql_describe_columns_f); +PG_FUNCTION_INFO_V1(gms_sql_debug_cursor); +PG_FUNCTION_INFO_V1(gms_sql_return_result); +PG_FUNCTION_INFO_V1(gms_sql_return_result_i); + +static uint32 gms_sql_index; + +static THR_LOCAL uint64 last_row_count = 0; +static THR_LOCAL TransactionId last_lxid = InvalidTransactionId; +static THR_LOCAL int result_no = 0; + +void set_extension_index(uint32 index) +{ + gms_sql_index = index; +} + +void init_session_vars(void) +{ + RepallocSessionVarsArrayIfNecessary(); + + GmssqlContext* psc = + (GmssqlContext*)MemoryContextAllocZero(u_sess->self_mem_cxt, sizeof(GmssqlContext)); + u_sess->attr.attr_common.extension_session_vars_array[gms_sql_index] = psc; + + psc->gms_sql_cxt = NULL; + psc->gms_sql_cursors = NULL; + +} + +GmssqlContext* get_session_context() +{ + if (u_sess->attr.attr_common.extension_session_vars_array[gms_sql_index] == NULL) { + init_session_vars(); + } + return (GmssqlContext*)u_sess->attr.attr_common.extension_session_vars_array[gms_sql_index]; +} + +static void +create_cursors() +{ + MemoryContext persist_cxt = get_session_context()->gms_sql_cxt; + + if (!persist_cxt) { + persist_cxt = AllocSetContextCreate(u_sess->top_mem_cxt, + "gms_sql persist context", + ALLOCSET_DEFAULT_SIZES); + get_session_context()->gms_sql_cxt = persist_cxt; + get_session_context()->gms_sql_cursors = (CursorData*)MemoryContextAllocZero(persist_cxt, u_sess->attr.attr_common.maxOpenCursorCount * sizeof(CursorData)); + } +} +static void +open_cursor(CursorData *cursor, int cid) +{ + cursor->cid = cid; + MemoryContext persist_cxt = get_session_context()->gms_sql_cxt; + + cursor->cursor_cxt = AllocSetContextCreate(persist_cxt, + "gms_sql cursor context", + ALLOCSET_DEFAULT_SIZES); + cursor->assigned = true; +} + +/* + * FUNCTION gms_sql.open_cursor() RETURNS int + */ +Datum +gms_sql_open_cursor(PG_FUNCTION_ARGS) +{ + int i; + + (void) fcinfo; + CursorData *cursors; + + if (get_session_context()->gms_sql_cursors == NULL) + create_cursors(); + + cursors = get_session_context()->gms_sql_cursors; + /* find and initialize first free slot */ + for (i = 0; i < u_sess->attr.attr_common.maxOpenCursorCount; i++) { + if(!cursors[i].assigned) { + open_cursor(&cursors[i], i); + + PG_RETURN_INT32(i); + } + } + + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("too many opened cursors"), + errdetail("There is not free slot for new gms_sql's cursor."), + errhint("You should to close unused cursors"))); + + /* be msvc quiet */ + PG_RETURN_VOID(); +} + +static CursorData * +get_cursor(FunctionCallInfo fcinfo, bool should_be_assigned) +{ + CursorData *cursors; + CursorData *cursor; + int cid; + + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("cursor id is NULL"))); + + cid = PG_GETARG_INT32(0); + if (cid < 0 || cid >= u_sess->attr.attr_common.maxOpenCursorCount) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cursor %d value of cursor id is out of range", cid))); + + cursors = get_session_context()->gms_sql_cursors; + if (cursors == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_CURSOR), + errmsg("cursor is not open"))); + + cursor = &cursors[cid]; + if (!cursor->assigned && should_be_assigned) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_CURSOR), + errmsg("cursor is not valid"))); + + return cursor; +} + +/* + * CREATE FUNCTION gms_sql.is_open(c int) RETURNS bool; + */ +Datum +gms_sql_is_open(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + + cursor = get_cursor(fcinfo, false); + + PG_RETURN_BOOL(cursor->assigned); +} + +/* + * Release all sources assigned to cursor + */ +static void +close_cursor(CursorData *cursor) +{ + if (cursor->executed && cursor->portal) + SPI_cursor_close(cursor->portal); + + /* release all assigned memory */ + if (cursor->cursor_cxt) + MemoryContextDelete(cursor->cursor_cxt); + + if (cursor->cursor_xact_cxt) + MemoryContextDelete(cursor->cursor_xact_cxt); + + if (cursor->plan) + SPI_freeplan(cursor->plan); + + memset_s(cursor, sizeof(CursorData), 0, sizeof(CursorData)); + +} + +/* + * PROCEDURE gms_sql.close_cursor(c int) + */ +Datum +gms_sql_close_cursor(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + + cursor = get_cursor(fcinfo, false); + + close_cursor(cursor); + + PG_RETURN_VOID(); +} + +/* + * Print state of cursor - just for debug purposes + */ +Datum +gms_sql_debug_cursor(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + ListCell *lc; + + cursor = get_cursor(fcinfo, false); + + if (cursor->assigned) { + if (cursor->original_query) + elog(NOTICE, "orig query: \"%s\"", cursor->original_query); + + if (cursor->parsed_query) + elog(NOTICE, "parsed query: \"%s\"", cursor->parsed_query); + + } else + elog(NOTICE, "cursor is not assigned"); + + foreach(lc, cursor->variables) { + VariableData *var = (VariableData *) lfirst(lc); + + if (var->typoid != InvalidOid) { + Oid typOutput; + bool isVarlena; + char *str; + + getTypeOutputInfo(var->typoid, &typOutput, &isVarlena); + str = OidOutputFunctionCall(typOutput, var->value); + + elog(NOTICE, "variable \"%s\" is assigned to \"%s\"", var->refname, str); + } else + elog(NOTICE, "variable \"%s\" is not assigned", var->refname); + } + + foreach(lc, cursor->columns) + { + ColumnData *col = (ColumnData *) lfirst(lc); + + elog(NOTICE, "column definition for position %d is %s", + col->position, + format_type_with_typemod(col->typoid, col->typmod)); + } + + PG_RETURN_VOID(); +} + +/* + * Search a variable in cursor's variable list + */ +static VariableData * +get_var(CursorData *cursor, char *refname, int position, bool append) +{ + ListCell *lc; + VariableData *nvar; + MemoryContext oldcxt; + + foreach(lc, cursor->variables) { + VariableData *var = (VariableData *) lfirst(lc); + + if (strcmp(var->refname, refname) == 0) + return var; + } + + if (append) { + oldcxt = MemoryContextSwitchTo(cursor->cursor_cxt); + nvar = (VariableData*)palloc0(sizeof(VariableData)); + + nvar->refname = pstrdup(refname); + nvar->varno = cursor->nvariables + 1; + nvar->position = position; + + cursor->variables = lappend(cursor->variables, nvar); + cursor->nvariables += 1; + + MemoryContextSwitchTo(oldcxt); + + return nvar; + } else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("variable \"%s\" doesn't exists", refname))); + + /* be msvc quite */ + return NULL; +} + +/* + * PROCEDURE gms_sql.parse(c int, stmt varchar) + */ +Datum +gms_sql_parse(PG_FUNCTION_ARGS) +{ + char *query,*ptr; + char *start; + size_t len; + ofTokenType typ; + StringInfoData sinfo; + CursorData *cursor; + MemoryContext oldcxt; + + cursor = get_cursor(fcinfo, true); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("parsed query string is NULL"))); + + if (cursor->parsed_query) { + int cid = cursor->cid; + + close_cursor(cursor); + open_cursor(cursor, cid); + } + + query = text_to_cstring(PG_GETARG_TEXT_P(1)); + ptr = query; + + initStringInfo(&sinfo); + + while (ptr) { + char *startsep; + char *next_ptr; + size_t seplen; + + next_ptr = next_token(ptr, &start, &len, &typ, &startsep, &seplen); + if (next_ptr) { + if (typ == TOKEN_DOLAR_STR) { + appendStringInfo(&sinfo, "%.*s", (int) seplen, startsep); + appendStringInfo(&sinfo, "%.*s", (int) len, start); + appendStringInfo(&sinfo, "%.*s", (int) seplen, startsep); + } else if (typ == TOKEN_BIND_VAR) { + char *name = downcase_truncate_identifier(start, (int) len, false); + VariableData *var = get_var(cursor, name, (int) (ptr - query), true); + + appendStringInfo(&sinfo, "$%d", var->varno); + + pfree(name); + } else if (typ == TOKEN_EXT_STR) { + appendStringInfo(&sinfo, "e\'%.*s\'", (int) len, start); + } else if (typ == TOKEN_STR) { + appendStringInfo(&sinfo, "\'%.*s\'", (int) len, start); + } else if (typ == TOKEN_QIDENTIF) { + appendStringInfo(&sinfo, "\"%.*s\"", (int) len, start); + } else if (typ != TOKEN_NONE) { + appendStringInfo(&sinfo, "%.*s", (int) len, start); + } + } + + ptr = next_ptr; + } + + /* save result to persist context */ + oldcxt = MemoryContextSwitchTo(cursor->cursor_cxt); + cursor->original_query = pstrdup(query); + cursor->parsed_query = pstrdup(sinfo.data); + + MemoryContextSwitchTo(oldcxt); + + pfree(query); + pfree(sinfo.data); + + PG_RETURN_VOID(); +} + +/* + * Calling procedure can be slow, so there is a function alternative + */ +static Datum +bind_variable(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + VariableData *var; + char *varname, *varname_downcase; + Oid valtype; + bool is_unknown = false; + + cursor = get_cursor(fcinfo, true); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("name of bind variable is NULL"))); + + varname = text_to_cstring(PG_GETARG_TEXT_P(1)); + if (*varname == ':') + varname += 1; + + varname_downcase = downcase_truncate_identifier(varname, (int) strlen(varname), false); + var = get_var(cursor, varname_downcase, -1, false); + + valtype = get_fn_expr_argtype(fcinfo->flinfo, 2); + if (valtype == RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot to bind a value of record type"))); + + valtype = getBaseType(valtype); + if (valtype == UNKNOWNOID) { + is_unknown = true; + valtype = TEXTOID; + } + + if (var->typoid != InvalidOid) { + if (!var->typbyval) + pfree(DatumGetPointer(var->value)); + + var->isnull = true; + } + + var->typoid = valtype; + + if (!PG_ARGISNULL(2)) { + MemoryContext oldcxt; + + get_typlenbyval(var->typoid, &var->typlen, &var->typbyval); + + oldcxt = MemoryContextSwitchTo(cursor->cursor_cxt); + + if (is_unknown) + var->value = CStringGetTextDatum(DatumGetPointer(PG_GETARG_DATUM(2))); + else + var->value = datumCopy(PG_GETARG_DATUM(2), var->typbyval, var->typlen); + + var->isnull = false; + + MemoryContextSwitchTo(oldcxt); + } else + var->isnull = true; + + PG_RETURN_VOID(); +} + +/* + * CREATE PROCEDURE gms_sql.bind_variable(c int, name varchar2, value "any"); + */ +Datum +gms_sql_bind_variable(PG_FUNCTION_ARGS) +{ + return bind_variable(fcinfo); +} + +/* + * CREATE FUNCTION gms_sql.bind_variable_f(c int, name varchar2, value "any") RETURNS void; + */ +Datum +gms_sql_bind_variable_f(PG_FUNCTION_ARGS) +{ + return bind_variable(fcinfo); +} + +static void +bind_array(FunctionCallInfo fcinfo, int index1, int index2) +{ + CursorData *cursor; + VariableData *var; + char *varname, *varname_downcase; + Oid valtype; + Oid elementtype; + bool is_unknown = false; + + cursor = get_cursor(fcinfo, true); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("name of bind variable is NULL"))); + + varname = text_to_cstring(PG_GETARG_TEXT_P(1)); + if (*varname == ':') + varname += 1; + + varname_downcase = downcase_truncate_identifier(varname, (int) strlen(varname), false); + var = get_var(cursor, varname_downcase, -1, false); + + valtype = get_fn_expr_argtype(fcinfo->flinfo, 2); + if (valtype == RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot to bind a value of record type"))); + + valtype = getBaseType(valtype); + elementtype = get_element_type(valtype); + + if (!OidIsValid(elementtype)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("value is not a array"))); + + var->is_array = true; + var->typoid = valtype; + var->typelemid = elementtype; + + get_typlenbyval(elementtype, &var->typelemlen, &var->typelembyval); + + if (!PG_ARGISNULL(2)) { + MemoryContext oldcxt; + + get_typlenbyval(var->typoid, &var->typlen, &var->typbyval); + + oldcxt = MemoryContextSwitchTo(cursor->cursor_cxt); + + if (is_unknown) + var->value = CStringGetTextDatum(DatumGetPointer(PG_GETARG_DATUM(2))); + else + var->value = datumCopy(PG_GETARG_DATUM(2), var->typbyval, var->typlen); + + var->isnull = false; + + MemoryContextSwitchTo(oldcxt); + } else + var->isnull = true; + + var->index1 = index1; + var->index2 = index2; +} + +/* + * CREATE PROCEDURE gms_sql.bind_array(c int, name varchar2, value anyarray); + */ +Datum +gms_sql_bind_array_3(PG_FUNCTION_ARGS) +{ + bind_array(fcinfo, -1, -1); + + PG_RETURN_VOID(); +} + +/* + * CREATE PROCEDURE gms_sql.bind_array(c int, name varchar2, value anyarray, index1 int, index2 int); + */ +Datum +gms_sql_bind_array_5(PG_FUNCTION_ARGS) +{ + int index1, index2; + + if (PG_ARGISNULL(3) || PG_ARGISNULL(4)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("index is NULL"))); + + index1 = PG_GETARG_INT32(3); + index2 = PG_GETARG_INT32(4); + + if (index1 < 0 || index2 < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("index is below zero"))); + + if (index1 > index2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("index1 is greater than index2"))); + + bind_array(fcinfo, index1, index2); + + PG_RETURN_VOID(); +} + +static ColumnData * +get_col(CursorData *cursor, int position, bool append) +{ + ListCell *lc; + ColumnData *ncol; + MemoryContext oldcxt; + + foreach(lc, cursor->columns) { + ColumnData *col = (ColumnData *) lfirst(lc); + + if (col->position == position) + return col; + } + + if (append) { + oldcxt = MemoryContextSwitchTo(cursor->cursor_cxt); + ncol = (ColumnData*)palloc0(sizeof(ColumnData)); + + ncol->position = position; + if (cursor->max_colpos < position) + cursor->max_colpos = position; + + cursor->columns = lappend(cursor->columns, ncol); + + MemoryContextSwitchTo(oldcxt); + + return ncol; + } else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column no %d is not defined", position))); + + /* be msvc quite */ + return NULL; +} + +/* + * CREATE PROCEDURE gms_sql.define_column(c int, col int, value "any", column_size int DEFAULT -1); + */ +Datum +gms_sql_define_column(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + ColumnData *col; + Oid valtype; + Oid basetype; + int position; + int colsize; + TYPCATEGORY category; + bool ispreferred; + + cursor = get_cursor(fcinfo, true); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("column position (number) is NULL"))); + + position = PG_GETARG_INT32(1); + col = get_col(cursor, position, true); + + valtype = get_fn_expr_argtype(fcinfo->flinfo, 2); + if (valtype == RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot to define a column of record type"))); + + if (valtype == UNKNOWNOID) + valtype = TEXTOID; + + basetype = getBaseType(valtype); + + if (col->typoid != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column is defined already"))); + + col->typoid = valtype; + + if (PG_ARGISNULL(3)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("column_size is NULL"))); + + colsize = PG_GETARG_INT32(3); + + get_type_category_preferred(basetype, &category, &ispreferred); + col->typisstr = category == TYPCATEGORY_STRING; + col->typmod = (col->typisstr && colsize != -1) ? colsize + 4 : -1; + + get_typlenbyval(basetype, &col->typlen, &col->typbyval); + + col->rowcount = 1; + + PG_RETURN_VOID(); +} + +/* + * CREATE PROCEDURE gms_sql.define_array(c int, col int, value "anyarray", rowcount int, index1 int); + */ +Datum +gms_sql_define_array(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + ColumnData *col; + Oid valtype; + Oid basetype; + int position; + int rowcount; + int index1; + Oid elementtype; + TYPCATEGORY category; + bool ispreferred; + + cursor = get_cursor(fcinfo, true); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("column position (number) is NULL"))); + + position = PG_GETARG_INT32(1); + col = get_col(cursor, position, true); + + valtype = get_fn_expr_argtype(fcinfo->flinfo, 2); + if (valtype == RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot to define a column of record type"))); + + get_type_category_preferred(valtype, &category, &ispreferred); + if (category != TYPCATEGORY_ARRAY) + elog(ERROR, "defined value is not array"); + + col->typarrayoid = valtype; + + basetype = getBaseType(valtype); + elementtype = get_element_type(basetype); + + if (!OidIsValid(elementtype)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column is not a array"))); + + if (col->typoid != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column is defined already"))); + + col->typoid = elementtype; + + if (PG_ARGISNULL(3)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("cnt is NULL"))); + + rowcount = PG_GETARG_INT32(3); + if (rowcount <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cnt is less or equal to zero"))); + + col->rowcount = (uint64) rowcount; + + if (PG_ARGISNULL(4)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("lower_bnd is NULL"))); + + index1 = PG_GETARG_INT32(4); + if (index1 < 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lower_bnd is less than one"))); + + if (index1 != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("lower_bnd can be only only \"1\""))); + + col->index1 = index1; + + get_typlenbyval(col->typoid, &col->typlen, &col->typbyval); + + PG_RETURN_VOID(); +} + +static void +cursor_xact_cxt_deletion_callback(std::shared_ptr arg) +{ + CursorData *cur = std::static_pointer_cast(arg).get(); + + cur->cursor_xact_cxt = NULL; + cur->tuples_cxt = NULL; + + cur->processed = 0; + cur->nread = 0; + cur->executed = false; + cur->tupdesc = NULL; + cur->coltupdesc = NULL; + cur->casts = NULL; + cur->array_columns = NULL; +} +static void prepare_and_execute_cursor(CursorData *cursor) +{ + Datum *values; + Oid *types; + char *nulls; + ListCell *lc; + int i; + MemoryContext oldcxt; + uint64 batch_rows = 0; + + oldcxt = MemoryContextSwitchTo(cursor->cursor_xact_cxt); + + /* prepare query arguments */ + values = (Datum*)palloc(sizeof(Datum) * cursor->nvariables); + types = (Oid*)palloc(sizeof(Oid) * cursor->nvariables); + nulls = (char*)palloc(sizeof(char) * cursor->nvariables); + + i = 0; + foreach(lc, cursor->variables) { + VariableData *var = (VariableData *) lfirst(lc); + + if (var->is_array) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("a array (bulk) variable can be used only when no column is defined"))); + + if (!var->isnull) { + /* copy a value to xact memory context, to be independent on a outside */ + values[i] = datumCopy(var->value, var->typbyval, var->typlen); + nulls[i] = ' '; + } else + nulls[i] = 'n'; + + if (var->typoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("variable \"%s\" has not a value", var->refname))); + + types[i] = var->typoid; + i += 1; + } + + /* prepare or refresh target tuple descriptor, used for final tupconversion */ + if (cursor->tupdesc) + FreeTupleDesc(cursor->tupdesc); + +#if PG_VERSION_NUM >= 120000 + + cursor->coltupdesc = CreateTemplateTupleDesc(cursor->max_colpos); + +#else + + cursor->coltupdesc = CreateTemplateTupleDesc(cursor->max_colpos, false); + +#endif + + /* prepare current result column tupdesc */ + for (i = 1; i <= cursor->max_colpos; i++) { + ColumnData *col = get_col(cursor, i, false); + char genname[32]; + + snprintf_s(genname, 32, 31, "col%d", i); + + Assert(col->rowcount > 0); + + if (col->typarrayoid) { + if (batch_rows != 0) + batch_rows = col->rowcount < batch_rows ? col->rowcount : batch_rows; + else + batch_rows = col->rowcount; + cursor->array_columns = bms_add_member(cursor->array_columns, i); + } else { + /* in this case we cannot do batch of rows */ + batch_rows = 1; + } + + TupleDescInitEntry(cursor->coltupdesc, (AttrNumber) i, genname, col->typoid, col->typmod, 0); + } + + cursor->batch_rows = batch_rows; + Assert(cursor->coltupdesc->natts >= 0); + cursor->casts = (CastCacheData*)palloc0(sizeof(CastCacheData) * ((unsigned int) cursor->coltupdesc->natts)); + + MemoryContextSwitchTo(oldcxt); + + snprintf_s(cursor->cursorname, sizeof(cursor->cursorname), sizeof(cursor->cursorname) - 1, "__orafce_gms_sql_cursor_%d", cursor->cid); + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connact failed"); + + cursor->portal = SPI_cursor_open_with_args(cursor->cursorname, + cursor->parsed_query, + (int) cursor->nvariables, + types, + values, + nulls, + false, + 0); + + /* internal error */ + if (cursor->portal == NULL) + elog(ERROR, + "could not open cursor for query \"%s\": %s", + cursor->parsed_query, + SPI_result_code_string(SPI_result)); + + SPI_finish(); + + /* Describe portal and prepare cast cache */ + if (cursor->portal->tupDesc) { + int natts = 0; + TupleDesc tupdesc = cursor->portal->tupDesc; + + for (i = 0; i < tupdesc->natts; i++) { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + + natts += 1; + } + + if (natts != cursor->coltupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("number of defined columns is different than number of query's columns"))); + } + + cursor->executed = true; +} + + +static uint64 execute_spi_plan(CursorData *cursor) +{ + MemoryContext oldcxt; + Datum *values; + char *nulls; + ArrayIterator *iterators; + bool has_iterator = false; + bool has_value = true; + int max_index1 = -1; + int min_index2 = -1; + int max_rows = -1; + uint64 result = 0; + ListCell *lc; + int i; + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connact failed"); + + /* prepare, or reuse cached plan */ + if (!cursor->plan) { + Oid *types = NULL; + SPIPlanPtr plan; + + types = (Oid*)palloc(sizeof(Oid) * cursor->nvariables); + + i = 0; + foreach(lc, cursor->variables) { + VariableData *var = (VariableData *) lfirst(lc); + + if (var->typoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("variable \"%s\" has not a value", var->refname))); + + types[i++] = var->is_array ? var->typelemid : var->typoid; + } + + plan = SPI_prepare(cursor->parsed_query, (int) cursor->nvariables, types); + + if (!plan) + /* internal error */ + elog(ERROR, "cannot to prepare plan"); + + if (types) + pfree(types); + + SPI_keepplan(plan); + cursor->plan = plan; + } + + oldcxt = MemoryContextSwitchTo(cursor->result_cxt); + /* prepare query arguments */ + values = (Datum*)palloc(sizeof(Datum) * cursor->nvariables); + nulls = (char*)palloc(sizeof(char) * cursor->nvariables); + iterators = (ArrayIterator*)palloc(sizeof(ArrayIterator *) * cursor->nvariables); + has_value = true; + + i = 0; + foreach(lc, cursor->variables) { + VariableData *var = (VariableData *) lfirst(lc); + + if (var->is_array) { + if (!var->isnull) { + iterators[i] = array_create_iterator(DatumGetArrayTypeP(var->value), 0); + /* search do lowest common denominator */ + if (var->index1 != -1) { + if (max_index1 != -1) { + max_index1 = max_index1 < var->index1 ? var->index1 : max_index1; + min_index2 = min_index2 > var->index2 ? var->index2 : min_index2; + } else { + max_index1 = var->index1; + min_index2 = var->index2; + } + } + + has_iterator = true; + } else { + /* cannot to read data from NULL array */ + has_value = false; + break; + } + } else { + values[i] = var->value; + nulls[i] = var->isnull ? 'n' : ' '; + } + i += 1; + } + + if (has_iterator) { + if (has_value) { + if (max_index1 != -1) { + max_rows = min_index2 - max_index1 + 1; + has_value = max_rows > 0; + + if (has_value && max_index1 > 1) { + i = 0; + foreach(lc, cursor->variables) { + VariableData *var = (VariableData *) lfirst(lc); + if (var->is_array) { + int j; + + Assert(iterators[i]); + for (j = 1; j < max_index1; j++) { + Datum value; + bool isnull; + + has_value = array_iterate(iterators[i], &value, &isnull); + if (!has_value) + break; + } + + if (!has_value) + break; + } + i += 1; + } + } + } + } + while (has_value && (max_rows == -1 || max_rows > 0)) { + int rc; + i = 0; + foreach(lc, cursor->variables) { + VariableData *var = (VariableData *) lfirst(lc); + if (var->is_array) { + Datum value; + bool isnull; + + has_value = array_iterate(iterators[i], &value, &isnull); + if (!has_value) + break; + + values[i] = value; + nulls[i] = isnull ? 'n' : ' '; + } + i += 1; + } + if (!has_value) + break; + rc = SPI_execute_plan(cursor->plan, values, nulls, false, 0); + if (rc < 0) + /* internal error */ + elog(ERROR, "cannot to execute a query"); + + result += SPI_processed; + + if (max_rows > 0) + max_rows -= 1; + } + MemoryContextReset(cursor->result_cxt); + } else { + int rc = SPI_execute_plan(cursor->plan, values, nulls, false, 0); + if (rc < 0) + /* internal error */ + elog(ERROR, "cannot to execute a query"); + + result = SPI_processed; + } + + SPI_finish(); + MemoryContextSwitchTo(oldcxt); + return result; +} + +static uint64 +execute_query(CursorData *cursor) +{ + last_row_count = 0; + + /* clean space with saved result */ + if (!cursor->cursor_xact_cxt) { + MemoryContextCallback *mcb; + MemoryContext oldcxt; + + cursor->cursor_xact_cxt = AllocSetContextCreate(u_sess->top_transaction_mem_cxt, + "gms_sql transaction context", + ALLOCSET_DEFAULT_SIZES); + + oldcxt = MemoryContextSwitchTo(cursor->cursor_xact_cxt); + mcb = (MemoryContextCallback*)palloc0(sizeof(MemoryContextCallback)); + mcb->func = cursor_xact_cxt_deletion_callback; + mcb->arg = std::shared_ptr(cursor); + MemoryContextRegisterResetCallback(cursor->cursor_xact_cxt, mcb); + + MemoryContextSwitchTo(oldcxt); + } else { + MemoryContext save_cxt = cursor->cursor_xact_cxt; + /* free allocated memory in cursor_xact_cxt */ + MemoryContextReset(cursor->cursor_xact_cxt); + cursor->cursor_xact_cxt = save_cxt; + + cursor->casts = NULL; + cursor->tupdesc = NULL; + cursor->tuples_cxt = NULL; + } + + cursor->result_cxt = AllocSetContextCreate(cursor->cursor_xact_cxt, + "gms_sql short life context", + ALLOCSET_DEFAULT_SIZES); + + /* + * When column definitions are available, build final query + * and open cursor for fetching. When column definitions are + * missing, then the statement can be called with high frequency + * etc INSERT, UPDATE, so use cached plan. + */ + + if (cursor->columns) { + prepare_and_execute_cursor(cursor); + } else { + return execute_spi_plan(cursor); + } + return 0L; +} + +/* + * CREATE FUNCTION gms_sql.execute(c int) RETURNS bigint; + */ +Datum +gms_sql_execute(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + + cursor = get_cursor(fcinfo, true); + + PG_RETURN_INT64((int64) execute_query(cursor)); +} + +static uint64 +fetch_rows(CursorData *cursor, bool exact) +{ + uint64 can_read_rows; + + if (!cursor->executed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cursor is not executed"))); + + if (!cursor->portal) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cursor has not portal"))); + + if (cursor->nread == cursor->processed) { + MemoryContext oldcxt; + uint64 i; + int batch_rows; + + if (!exact) { + if (cursor->array_columns) + batch_rows = (1000 / cursor->batch_rows) * cursor->batch_rows; + else + batch_rows = 1000; + } else + batch_rows = 2; + + /* create or reset context for tuples */ + if (!cursor->tuples_cxt) + cursor->tuples_cxt = AllocSetContextCreate(cursor->cursor_xact_cxt, + "gms_sql tuples context", + ALLOCSET_DEFAULT_SIZES); + else + MemoryContextReset(cursor->tuples_cxt); + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connact failed"); + + /* try to fetch data from cursor */ + SPI_cursor_fetch(cursor->portal, true, batch_rows); + + if (SPI_tuptable == NULL) + elog(ERROR, "cannot fetch data"); + + if (exact && SPI_processed > 1) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ROWS), + errmsg("too many rows"), + errdetail("In exact mode only one row is expected"))); + + if (exact && SPI_processed == 0) + ereport(ERROR, + (errcode(ERRCODE_NO_DATA_FOUND), + errmsg("no data found"), + errdetail("In exact mode only one row is expected"))); + + oldcxt = MemoryContextSwitchTo(cursor->tuples_cxt); + + cursor->tupdesc = CreateTupleDescCopy(SPI_tuptable->tupdesc); + + for (i = 0; i < SPI_processed; i++) + cursor->tuples[i] = heap_copytuple(SPI_tuptable->vals[i]); + + MemoryContextSwitchTo(oldcxt); + + cursor->processed = SPI_processed; + cursor->nread = 0; + + SPI_finish(); + } + + if (cursor->processed - cursor->nread >= cursor->batch_rows) + can_read_rows = cursor->batch_rows; + else + can_read_rows = cursor->processed - cursor->nread; + + cursor->start_read = cursor->nread; + cursor->nread += can_read_rows; + last_row_count = can_read_rows; + return can_read_rows; +} + +/* + * CREATE FUNCTION gms_sql.fetch_rows(c int) RETURNS int; + */ +Datum +gms_sql_fetch_rows(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + + cursor = get_cursor(fcinfo, true); + + PG_RETURN_INT32(fetch_rows(cursor, false)); +} + +/* + * CREATE FUNCTION gms_sql.execute_and_fetch(c int, exact bool DEFAULT false) RETURNS int; + */ +Datum +gms_sql_execute_and_fetch(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + bool exact; + + cursor = get_cursor(fcinfo, true); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("exact option is NULL"))); + + exact = PG_GETARG_BOOL(1); + execute_query(cursor); + PG_RETURN_INT32(fetch_rows(cursor, exact)); +} + +/* + * CREATE FUNCTION gms_sql.last_row_count() RETURNS int; + */ +Datum +gms_sql_last_row_count(PG_FUNCTION_ARGS) +{ + (void) fcinfo; + PG_RETURN_INT32(last_row_count); +} + +/* + * Initialize cast case entry. + */ +static void +init_cast_cache_entry(CastCacheData *ccast, + Oid targettypid, + int32 targettypmod, + Oid sourcetypid) +{ + Oid funcoid; + Oid basetypid; + + basetypid = getBaseType(targettypid); + + if (targettypid != basetypid) + ccast->targettypid = targettypid; + else + ccast->targettypid = InvalidOid; + + ccast->targettypmod = targettypmod; + + if (sourcetypid == targettypid) + ccast->without_cast = targettypmod == -1; + else + ccast->without_cast = false; + + if (!ccast->without_cast) { + ccast->path = find_coercion_pathway(basetypid, + sourcetypid, + COERCION_ASSIGNMENT, + &funcoid); + + if (ccast->path == COERCION_PATH_NONE) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot to find cast from source type \"%s\" to target type \"%s\"", + format_type_be(sourcetypid), + format_type_be(basetypid)))); + + if (ccast->path == COERCION_PATH_FUNC) { + fmgr_info(funcoid, &ccast->finfo); + } else if (ccast->path == COERCION_PATH_COERCEVIAIO) { + bool typisvarlena; + + getTypeOutputInfo(sourcetypid, &funcoid, &typisvarlena); + fmgr_info(funcoid, &ccast->finfo_out); + getTypeInputInfo(targettypid, &funcoid, &ccast->typIOParam); + fmgr_info(funcoid, &ccast->finfo_in); + } + + if (targettypmod != -1) { + ccast->path_typmod = find_typmod_coercion_function(targettypid, + &funcoid); + if (ccast->path_typmod == COERCION_PATH_FUNC) + fmgr_info(funcoid, &ccast->finfo_typmod); + } + } + + ccast->isvalid = true; +} + +/* + * Apply cast rules to a value + */ +static Datum +cast_value(CastCacheData *ccast, Datum value, bool isnull) +{ + if (!isnull && !ccast->without_cast) { + if (ccast->path == COERCION_PATH_FUNC) { + value = FunctionCall1(&ccast->finfo, value); + } else if (ccast->path == COERCION_PATH_RELABELTYPE) { + value = value; + } else if (ccast->path == COERCION_PATH_COERCEVIAIO) { + char *str; + str = OutputFunctionCall(&ccast->finfo_out, value); + value = InputFunctionCall(&ccast->finfo_in, + str, + ccast->typIOParam, + ccast->targettypmod); + } else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported cast path yet %d", ccast->path))); + + if (ccast->targettypmod != -1 && ccast->path_typmod == COERCION_PATH_FUNC) + value = FunctionCall3(&ccast->finfo_typmod, + value, + Int32GetDatum(ccast->targettypmod), + BoolGetDatum(true)); + } + if (ccast->targettypid != InvalidOid) + domain_check(value, isnull, ccast->targettypid, NULL, NULL); + + return value; +} + +/* + * CALL statement is relatily slow in PLpgSQL - due repated parsing, planning. + * So I wrote two variant of this routine. When spi_transfer is true, then + * the value is copyied to SPI outer memory context. + */ +static Datum +column_value(CursorData *cursor, int pos, Oid targetTypeId, bool *isnull, bool spi_transfer) +{ + Datum value; + int32 columnTypeMode; + Oid columnTypeId; + CastCacheData *ccast; + + if (!cursor->executed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cursor is not executed"))); + + if (!cursor->tupdesc) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cursor is not fetched"))); + + if (!cursor->coltupdesc) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("no column is defined"))); + + if (pos < 1 && pos > cursor->coltupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("column position is of of range [1, %d]", + cursor->coltupdesc->natts))); + + columnTypeId = (TupleDescAttr(cursor->coltupdesc, pos - 1))->atttypid; + columnTypeMode = (TupleDescAttr(cursor->coltupdesc, pos - 1))->atttypmod; + + Assert(cursor->casts); + ccast = &cursor->casts[pos - 1]; + + if (!ccast->isvalid) { + Oid basetype = getBaseType(targetTypeId); + + init_cast_cache_entry(ccast, + columnTypeId, + columnTypeMode, + SPI_gettypeid(cursor->tupdesc, pos)); + + ccast->is_array = bms_is_member(pos, cursor->array_columns); + + if (ccast->is_array) { + ccast->array_targettypid = basetype != targetTypeId ? targetTypeId : InvalidOid; + + if (get_array_type(getBaseType(columnTypeId)) != basetype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("unexpected target type \"%s\" (expected type \"%s\")", + format_type_be(basetype), + format_type_be(get_array_type(getBaseType(columnTypeId)))))); + } else + ccast->array_targettypid = InvalidOid; + + get_typlenbyval(basetype, &ccast->typlen, &ccast->typbyval); + } + + if (ccast->is_array) { + ArrayBuildState *abs = NULL; + uint64 idx; + uint64 i; + + idx = cursor->start_read; + + for (i = 0; i < cursor->batch_rows; i++) { + if (idx < cursor->processed) { + value = SPI_getbinval(cursor->tuples[idx], cursor->tupdesc, pos, isnull); + value = cast_value(ccast, value, *isnull); + abs = accumArrayResult(abs, + value, + *isnull, + columnTypeId, + CurrentMemoryContext); + + idx += 1; + } + } + + value = makeArrayResult(abs, CurrentMemoryContext); + + if (ccast->array_targettypid != InvalidOid) + domain_check(value, isnull, ccast->array_targettypid, NULL, NULL); + } else { + /* Maybe it can be solved by uncached slower cast */ + if (targetTypeId != columnTypeId) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("unexpected target type \"%s\" (expected type \"%s\")", + format_type_be(targetTypeId), + format_type_be(columnTypeId)))); + + value = SPI_getbinval(cursor->tuples[cursor->start_read], cursor->tupdesc, pos, isnull); + + value = cast_value(ccast, value, *isnull); + } + + if (spi_transfer) + value = SPI_datumTransfer(value, ccast->typbyval, ccast->typlen); + + return value; +} + +/* + * CREATE PROCEDURE gms_sql.column_value(c int, pos int, INOUT value "any"); + * Note - CALL statement is slow from PLpgSQL block (against function execution). + * This is reason why this routine is in function form too. + */ +Datum +gms_sql_column_value(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + Datum value; + int pos; + bool isnull; + Oid targetTypeId; + MemoryContext oldcxt; + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connact failed"); + + cursor = get_cursor(fcinfo, true); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("column position (number) is NULL"))); + + pos = PG_GETARG_INT32(1); + oldcxt = MemoryContextSwitchTo(cursor->result_cxt); + targetTypeId = get_fn_expr_argtype(fcinfo->flinfo, 2); + value = column_value(cursor, pos, targetTypeId, &isnull, true); + + SPI_finish(); + MemoryContextSwitchTo(oldcxt); + MemoryContextReset(cursor->result_cxt); + + PG_RETURN_DATUM(value); +} + +/* + * CREATE FUNCTION gms_sql.column_value(c int, pos int, value anyelement) RETURNS anyelement; + * Note - CALL statement is slow from PLpgSQL block (against function execution). + * This is reason why this routine is in function form too. + */ +Datum +gms_sql_column_value_f(PG_FUNCTION_ARGS) +{ + return gms_sql_column_value(fcinfo); +} + +/****************************************************************** + * Simple parser - just for replacement of bind variables by + * PostgreSQL $ param placeholders. + * + ****************************************************************** + */ + +/* + * It doesn't work for multibyte encodings, but same implementation + * is in Postgres too. + */ +static bool +is_identif(unsigned char c) +{ + if (c >= 'a' && c <= 'z') + return true; + else if (c >= 'A' && c <= 'Z') + return true; + else if (c >= 0200) + return true; + else + return false; +} + +/* + * simple parser to detect :identif symbols in query + */ +static char * +next_token(char *str, char **start, size_t *len, ofTokenType *typ, char **sep, size_t *seplen) +{ + if (*str == '\0') { + *typ = TOKEN_NONE; + return NULL; + } + + /* reduce spaces */ + if (*str == ' ') { + *start = str++; + while (*str == ' ') + str++; + + *typ = TOKEN_SPACES; + *len = 1; + return str; + } + + /* Postgres's dolar strings */ + if (*str == '$' && (str[1] == '$' || + is_identif((unsigned char) str[1]) || str[1] == '_')) { + char *aux = str + 1; + char *endstr; + bool is_valid = false; + char *buffer; + + /* try to find end of separator */ + while (*aux) { + if (*aux == '$') { + is_valid = true; + aux++; + break; + } else if (is_identif((unsigned char) *aux) || + isdigit(*aux) || + *aux == '_') { + aux++; + } else + break; + } + + if (!is_valid) { + *typ = TOKEN_OTHER; + *len = 1; + *start = str; + return str + 1; + } + + /* now it looks like correct $ separator */ + *start = aux; *sep = str; + Assert(aux >= str); + *seplen = (size_t) (aux - str); + *typ = TOKEN_DOLAR_STR; + + /* try to find second instance */ + buffer = (char*)palloc(*seplen + 1); + memcpy(buffer, *sep, *seplen); + buffer[*seplen] = '\0'; + + endstr = strstr(aux, buffer); + if (endstr) { + Assert(endstr >= *start); + *len = (size_t) (endstr - *start); + return endstr + *seplen; + } else { + while (*aux) + aux++; + + Assert(aux >= *start); + *len = (size_t) (aux - *start); + return aux; + } + + return aux; + } + + /* Pair comments */ + if (*str == '/' && str[1] == '*') { + *start = str; str += 2; + while (*str) { + if (*str == '*' && str[1] == '/') { + str += 2; + break; + } + str++; + } + *typ = TOKEN_COMMENT; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str; + } + + /* Number */ + if (isdigit(*str) || (*str == '.' && isdigit(str[1]))) { + bool point = *str == '.'; + + *start = str++; + while (*str) { + if (isdigit(*str)) + str++; + else if (*str == '.' && !point) { + str++; point = true; + } else + break; + } + *typ = TOKEN_NUMBER; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str; + } + + /* Double colon :: */ + if (*str == ':' && str[1] == ':') { + *start = str; + *typ = TOKEN_DOUBLE_COLON; + *len = 2; + return str + 2; + } + + /* Bind variable placeholder */ + if (*str == ':' && + (is_identif((unsigned char) str[1]) || str[1] == '_')) { + *start = &str[1]; str += 2; + while (*str) { + if (is_identif((unsigned char) *str) || + isdigit(*str) || + *str == '_') + str++; + else + break; + } + *typ = TOKEN_BIND_VAR; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str; + } + + /* Extended string literal */ + if ((*str == 'e' || *str == 'E') && str[1] == '\'') { + *start = &str[2]; str += 2; + while (*str) { + if (*str == '\'') { + *typ = TOKEN_EXT_STR; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str + 1; + } + if (*str == '\\' && str[1] == '\'') + str += 2; + else if (*str == '\\' && str[1] == '\\') + str += 2; + else + str += 1; + } + + *typ = TOKEN_EXT_STR; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str; + } + + /* String literal */ + if (*str == '\'') { + *start = &str[1]; str += 1; + while (*str) { + if (*str == '\'') { + if (str[1] != '\'') { + *typ = TOKEN_STR; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str + 1; + } + str += 2; + } else + str += 1; + } + *typ = TOKEN_STR; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str; + } + + /* Quoted identifier */ + if (*str == '"') { + *start = &str[1]; str += 1; + while (*str) { + if (*str == '"') { + if (str[1] != '"') { + *typ = TOKEN_QIDENTIF; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str + 1; + } + str += 2; + } else + str += 1; + } + *typ = TOKEN_QIDENTIF; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str; + } + + /* Identifiers */ + if (is_identif((unsigned char) *str) || *str == '_') { + *start = str++; + while (*str) { + if (is_identif((unsigned char) *str) || + isdigit(*str) || + *str == '_') + str++; + else + break; + } + *typ = TOKEN_IDENTIF; + Assert(str >= *start); + *len = (size_t) (str - *start); + return str; + } + + /* Others */ + *typ = TOKEN_OTHER; + *start = str; + *len = 1; + return str + 1; +} + +typedef struct { + int type_id; + int type_col_num; + char* type_name; +} gms_sql_desc_rec_type; + +static gms_sql_desc_rec_type desc_rec_type_table[] = { + {1, 11, "desc_rec", }, + {2, 11, "desc_rec2"}, + {3, 13, "desc_rec3"}, + {4, 13, "desc_rec4"} +}; + +static +gms_sql_desc_rec_type* gms_sql_search_desc_rec_type(Oid typid) +{ + Oid nspOid; + Oid typOid; + int i; + nspOid = get_namespace_oid("gms_sql", false); + for (i = 0; i < 4; i++) { + typOid = GetSysCacheOid2(TYPENAMENSP, CStringGetDatum(desc_rec_type_table[i].type_name), ObjectIdGetDatum(nspOid)); + if (!OidIsValid(typOid)) { + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("gms_sql.%s type does not exist.", desc_rec_type_table[i].type_name))); + } + if (typid == typOid) + return &desc_rec_type_table[i]; + } + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("typeid %u is not gms_sql.desc_rec.", typid))); + return NULL; +} + +static int +map_type_code(Oid type_id) +{ + int code = 109; + + switch (type_id) { + case VARCHAROID: + case NVARCHAR2OID: + code = 1; + break; + case NUMERICOID: + case INT1OID: + case INT2OID: + case INT4OID: + case INT8OID: + case INT16OID: + case FLOAT4OID: + code = 2; + break; + case DATEOID: + code = 12; + break; + case FLOAT8OID: + code = 101; + break; + case RAWOID: + code = 23; + break; + case CHAROID: + code = 96; + break; + case CLOBOID: + code = 112; + break; + case BLOBOID: + code = 113; + break; + case JSONOID: + code = 119; + break; + case TIMESTAMPOID: + code = 180; + break; + case TIMESTAMPTZOID: + code = 181; + break; + case INTERVALOID: + code = 183; + break; + default: + break; + } + return code; +} +/* + * CREATE PROCEDURE gms_sql.describe_columns(c int, OUT col_cnt int, OUT desc_t gms_sql.desc_rec[]) + * + * Returns an array of column's descriptions. Attention, the typid are related to PostgreSQL type + * system. + */ +Datum +gms_sql_describe_columns(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + Datum *values; + bool *nulls; + TupleDesc tupdesc; + TupleDesc desc_rec_tupdesc; + TupleDesc cursor_tupdesc; + HeapTuple tuple; + Oid arraytypid; + Oid desc_rec_typid; + Oid *types = NULL; + ArrayBuildState *abuilder = NULL; + SPIPlanPtr plan; + CachedPlanSource *plansource = NULL; + int ncolumns = 0; + int rc; + int i = 0; + bool nonatomic; + gms_sql_desc_rec_type* rec_type; + MemoryContext callercxt = CurrentMemoryContext; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + arraytypid = TupleDescAttr(tupdesc, 1)->atttypid; + desc_rec_typid = get_element_type(arraytypid); + if (!OidIsValid(desc_rec_typid)) + elog(ERROR, "second output field must be an array"); + + rec_type = gms_sql_search_desc_rec_type(desc_rec_typid); + desc_rec_tupdesc = lookup_rowtype_tupdesc_copy(desc_rec_typid, -1); + values = (Datum*)palloc0(rec_type->type_col_num * sizeof(Datum)); + nulls = (bool*)palloc0(rec_type->type_col_num * sizeof(bool)); + cursor = get_cursor(fcinfo, true); + if (cursor->variables) { + ListCell *lc; + types = (Oid*)palloc(sizeof(Oid) * cursor->nvariables); + i = 0; + foreach(lc, cursor->variables) { + VariableData *var = (VariableData *) lfirst(lc); + if (var->typoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("variable \"%s\" has not a value", var->refname))); + + types[i++] = var->is_array ? var->typelemid : var->typoid; + } + } + /* + * Connect to SPI manager + */ + nonatomic = fcinfo->context && IsA(fcinfo->context, FunctionScanState) && + !castNode(FunctionScanState, fcinfo->context)->atomic; + + if ((rc = SPI_connect_ext(DestSPI, NULL, NULL, nonatomic ? SPI_OPT_NONATOMIC : 0)) != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc)); + + plan = SPI_prepare(cursor->parsed_query, (int) cursor->nvariables, types); + if (!plan || plan->magic != _SPI_PLAN_MAGIC) + elog(ERROR, "plan is not valid"); + + if (list_length(plan->plancache_list) != 1) + elog(ERROR, "plan is not single execution plany"); + + plansource = (CachedPlanSource *) linitial(plan->plancache_list); + cursor_tupdesc = plansource->resultDesc; + ncolumns = cursor_tupdesc->natts; + for (i = 0; i < ncolumns; i++) { + HeapTuple tp; + Form_pg_type typtup; + int type_code; + text *attname = NULL; + text *schname = NULL; + text *typname = NULL; + + Form_pg_attribute attr = TupleDescAttr(cursor_tupdesc, i); + + /* + * 0. col_type BINARY_INTEGER := 0, + * 1. col_max_len BINARY_INTEGER := 0, + * 2. col_name VARCHAR2(32) := '', + * 3. col_name_len BINARY_INTEGER := 0, + * 4. col_schema_name VARCHAR2(32) := '', + * 5. col_schema_name_len BINARY_INTEGER := 0, + * 6. col_precision BINARY_INTEGER := 0, + * 7. col_scale BINARY_INTEGER := 0, + * 8. col_charsetid BINARY_INTEGER := 0, + * 9. col_charsetform BINARY_INTEGER := 0, + * 10. col_null_ok BOOLEAN := TRUE + * 11. col_type_name varchar2 := '', + * 12. col_type_name_len BINARY_INTEGER := 0 ); + */ + memset_s(values, rec_type->type_col_num * sizeof(Datum), 0, rec_type->type_col_num * sizeof(Datum)); + memset_s(nulls, rec_type->type_col_num * sizeof(bool), 0, rec_type->type_col_num * sizeof(bool)); + type_code = map_type_code(attr->atttypid); + values[0] = Int32GetDatum(type_code); + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attr->atttypid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for type %u", attr->atttypid); + + typtup = (Form_pg_type) GETSTRUCT(tp); + values[1] = Int32GetDatum(0); + values[6] = Int32GetDatum(0); + values[7] = Int32GetDatum(0); + if (attr->attlen != -1) + values[1] = Int32GetDatum(attr->attlen); + else if (typtup->typcategory == 'S' && attr->atttypmod > VARHDRSZ) + values[1] = Int32GetDatum(attr->atttypmod - VARHDRSZ); + else if (attr->atttypid == NUMERICOID && attr->atttypmod > VARHDRSZ) { + values[6] = Int32GetDatum(((attr->atttypmod - VARHDRSZ) >> 16) & 0xffff); + values[7] = Int32GetDatum((((attr->atttypmod - VARHDRSZ) & 0x7ff) ^ 1024) - 1024); + } + + attname = cstring_to_text(NameStr(attr->attname)); + values[2] = PointerGetDatum(attname); + values[3] = DirectFunctionCall1(textlen, PointerGetDatum(attname)); + if (rec_type->type_id == 1 && DatumGetInt32(values[3]) > 32) + elog(ERROR, "desc_rec.col_name(%d) is more than 32", DatumGetInt32(values[3])); + + schname = cstring_to_text(get_namespace_name(typtup->typnamespace)); + values[4] = PointerGetDatum(schname); + values[5] = DirectFunctionCall1(textlen, PointerGetDatum(schname)); + values[8] = Int32GetDatum(0); + values[9] = Int32GetDatum(0); + values[10] = BoolGetDatum(true); + + if (attr->attnotnull) + values[10] = BoolGetDatum(false); + else if (typtup->typnotnull) + values[10] = BoolGetDatum(false); + + if (rec_type->type_id > 2) { + if (type_code == 109) { + typname = cstring_to_text(NameStr(typtup->typname)); + values[11] = PointerGetDatum(typname); + values[12] = DirectFunctionCall1(textlen, PointerGetDatum(typname)); + } else { + nulls[11] = true; + nulls[12] = true; + } + } + + tuple = heap_form_tuple(desc_rec_tupdesc, values, nulls); + abuilder = accumArrayResult(abuilder, + HeapTupleGetDatum(tuple), + false, + desc_rec_typid, + CurrentMemoryContext); + ReleaseSysCache(tp); + pfree_ext(attname); + pfree_ext(schname); + pfree_ext(typname); + } + memset_s(values, rec_type->type_col_num * sizeof(Datum), 0, rec_type->type_col_num * sizeof(Datum)); + memset_s(nulls, rec_type->type_col_num * sizeof(bool), 0, rec_type->type_col_num * sizeof(bool)); + values[0] = Int32GetDatum(ncolumns); + nulls[0] = false; + values[1] = makeArrayResult(abuilder, callercxt); + nulls[1] = false; + SPI_freeplan(plan); + if ((rc = SPI_finish()) != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc)); + MemoryContextSwitchTo(callercxt); + tuple = heap_form_tuple(tupdesc, values, nulls); + pfree(values); + pfree(nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} + +Datum +gms_sql_describe_columns_f(PG_FUNCTION_ARGS) +{ + return gms_sql_describe_columns(fcinfo); +} + +static bool +type_is_right_align(Oid type) +{ + switch (type) { + case INT2OID: + case INT4OID: + case INT8OID: + case INT16OID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + case OIDOID: + case XIDOID: + case CIDOID: + case CASHOID: + return true; + default: + return false; + } + } + +static int * +set_maxwidth(int nrows, TupleDesc desc, HeapTuple* tuples) +{ + int natts = desc->natts; + int *max_width; + char *value; + int i, j; + + /* get max_width from header */ + max_width = (int*) palloc0(natts * sizeof(int)); + for (i = 0; i < natts; i++) { + char *attrname = SPI_fname(desc, i + 1); + text *attrtext= cstring_to_text(attrname); + max_width[i] = DatumGetInt32(DirectFunctionCall1(textlen, PointerGetDatum(attrtext))); + pfree(attrname); + pfree(attrtext); + } + + /* get max_width from value */ + for (i = 0; i < nrows; i++) { + int len; + + for (j = 0; j < natts; j++) { + value = SPI_getvalue(tuples[i], desc, j + 1); + if (value) { + text *attrtext= cstring_to_text(value); + len = DatumGetInt32(DirectFunctionCall1(textlen, PointerGetDatum(attrtext))); + if (len > max_width[j]) + max_width[j] = len; + pfree(value); + pfree(attrtext); + } + } + } + return max_width; +} + +static void +print_buf_to_client(StringInfo buf, char *message) +{ + resetStringInfo(buf); + pq_beginmessage(buf, 'N'); + + if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3) { + pq_sendbyte(buf, PG_DIAG_MESSAGE_PRIMARY); + pq_sendstring(buf, message); + pq_sendbyte(buf, '\0'); + } else { + pq_sendstring(buf, message); + } + pq_endmessage_reuse(buf); + pq_flush(); + resetStringInfo(buf); +} + +static void +print_header_to_client(int *max_width, TupleDesc desc, StringInfo buf, StringInfo outbuf) +{ + int i, j; + TransactionId curlxid; + /* result number increase in transaction */ + curlxid = SPI_get_top_transaction_id(); + if (curlxid == last_lxid) + result_no++; + else + result_no = 1; + last_lxid = curlxid; + /* print ResultSet */ + appendStringInfo(buf, "ResultSet #%d\n", result_no); + print_buf_to_client(outbuf, buf->data); + /* print from header */ + resetStringInfo(buf); + for (i = 0; i < desc->natts; i++) { + char *attname = SPI_fname(desc, i + 1); + int nspace = max_width[i] - pg_mbstrlen(attname) + 2; + appendStringInfo(buf, "%-*s%s%-*s", nspace/2, "", attname, (nspace + 1) / 2, ""); + if (i < desc->natts - 1) + appendStringInfo(buf, "%s", "|"); + } + print_buf_to_client(outbuf, buf->data); + /* print horizontal line */ + resetStringInfo(buf); + for (i = 0; i < desc->natts; i++) { + for (j = 0; j < (max_width[i] + 2); j++) + appendStringInfo(buf, "%s", "-"); + + if (i < desc->natts - 1) + appendStringInfo(buf, "%s", "+"); + } + print_buf_to_client(outbuf, buf->data); + resetStringInfo(buf); +} + +static void +print_row_to_client(int *max_width, TupleDesc desc, HeapTuple tuple, StringInfo buf, StringInfo outbuf) +{ + char *value; + Datum datum; + int i; + int natts = desc->natts; + text *spacetext = cstring_to_text(" "); + for (i = 0; i < natts; i++) { + value = SPI_getvalue(tuple, desc, i + 1); + if (value) { + Oid typid = SPI_gettypeid(desc, i + 1); + text *attrtext= cstring_to_text(value); + if (type_is_right_align(typid)) { + datum = DirectFunctionCall3(rpad, PointerGetDatum(attrtext), max_width[i], PointerGetDatum(spacetext)); + } else { + datum = DirectFunctionCall3(lpad, PointerGetDatum(attrtext), max_width[i], PointerGetDatum(spacetext)); + } + char *line = DatumGetCString(DirectFunctionCall1(textout, datum)); + appendStringInfo(buf, "%s%s%s", " ", line, ((i < natts - 1) ? " |" : " ")); + pfree(value); + pfree(attrtext); + pfree(DatumGetPointer(datum)); + } else { + appendStringInfo(buf, "%*s", max_width[i] + 2, " "); + } + } + pfree(spacetext); + print_buf_to_client(outbuf, buf->data); + resetStringInfo(buf); +} + +static void +print_footer_to_client(unsigned int rows, StringInfo buf, StringInfo outbuf) +{ + appendStringInfo(buf, "(%u row%s)\n", rows, (rows > 1 ? "s" : "")); + print_buf_to_client(outbuf, buf->data); + resetStringInfo(buf); +} + +static void +return_result_to_client(int nrows, TupleDesc desc, HeapTuple* tuples) +{ + StringInfo buf; + StringInfo outbuf; + int *max_width; + int i; + + buf = makeStringInfo(); + outbuf = makeStringInfo(); + /* compute col width */ + max_width = set_maxwidth(nrows, desc, tuples); + print_header_to_client(max_width, desc, buf, outbuf); + /* print rows */ + for (i = 0; i < nrows; i++) { + print_row_to_client(max_width, desc, tuples[i], buf, outbuf); + } + /* print footer */ + print_footer_to_client(nrows, buf, outbuf); + pfree_ext(max_width); + FreeStringInfo(buf); + FreeStringInfo(outbuf); +} +Datum +gms_sql_return_result(PG_FUNCTION_ARGS) +{ + char* name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + Portal portal; + int rc = 0; + /* + * Connect to SPI manager + */ + SPI_STACK_LOG("connect", NULL, NULL); + if ((rc = SPI_connect() != SPI_OK_CONNECT)) + ereport(ERROR, + (errmodule(MOD_OPT), + (errcode(ERRCODE_SPI_CONNECTION_FAILURE), + errmsg("SPI_connect failed: %s", SPI_result_code_string(rc))))); + + portal = SPI_cursor_find(name); + if (portal == NULL) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_CURSOR), errmsg("cursor \"%s\" does not exist", name))); + + SPI_cursor_fetch(portal, true, FETCH_ALL); + SPI_STACK_LOG("finish", portal->sourceText, NULL); + return_result_to_client(SPI_processed, SPI_tuptable->tupdesc, SPI_tuptable->vals); + SPI_finish(); + PG_RETURN_VOID(); +} + +Datum +gms_sql_return_result_i(PG_FUNCTION_ARGS) +{ + CursorData *cursor; + int rc = 0; + Datum Values; + char Nulls; + + cursor = get_cursor(fcinfo, true); + /* + * Connect to SPI manager + */ + if ((rc = SPI_connect() != SPI_OK_CONNECT)) + ereport(ERROR, + (errmodule(MOD_OPT), + (errcode(ERRCODE_SPI_CONNECTION_FAILURE), + errmsg("SPI_connect failed: %s", SPI_result_code_string(rc))))); + + if (cursor->plan) + SPI_execute_plan(cursor->plan, &Values, &Nulls, false, 0); + else if (cursor->parsed_query) + SPI_execute(cursor->parsed_query, false, 0); + else + ereport(ERROR, + (errmodule(MOD_OPT), + (errcode(ERRCODE_SPI_EXECUTE_FAILURE), + errmsg("sql is not parsed: %s", SPI_result_code_string(rc))))); + + return_result_to_client(SPI_processed, SPI_tuptable->tupdesc, SPI_tuptable->vals); + SPI_finish(); + PG_RETURN_VOID(); +} diff --git a/contrib/gms_sql/gms_sql.h b/contrib/gms_sql/gms_sql.h new file mode 100644 index 0000000000..9434d91080 --- /dev/null +++ b/contrib/gms_sql/gms_sql.h @@ -0,0 +1,163 @@ +/*---------------------------------------------------------------------------------------* + * gms_sql.h + * + * Definition about gms_sql package. + * + * IDENTIFICATION + * contrib/gms_sql/gms_sql.h + * + * --------------------------------------------------------------------------------------- + */ +#ifndef GMS_SQL_H +#define GMS_SQL_H + + +#include "catalog/pg_type.h" +#include "executor/spi.h" +#include "parser/parse_coerce.h" +#include "utils/memutils.h" +/* + * It is used for transformation result data to form + * generated by column_value procedure or column + * value function. + */ +typedef struct +{ + bool isvalid; /* true, when this cast can be used */ + bool without_cast; /* true, when cast is not necessary */ + Oid targettypid; /* used for domains */ + Oid array_targettypid; /* used for array domains */ + int32 targettypmod; /* used for strings */ + bool typbyval; /* used for copy result to outer memory context */ + int16 typlen; /* used for copy result to outer memory context */ + bool is_array; + Oid funcoid; + Oid funcoid_typmod; + CoercionPathType path; + CoercionPathType path_typmod; + FmgrInfo finfo; + FmgrInfo finfo_typmod; + FmgrInfo finfo_out; + FmgrInfo finfo_in; + Oid typIOParam; +} CastCacheData; + +/* + * gms_sql cursor definition + */ +typedef struct +{ + int16 cid; + char *parsed_query; + char *original_query; + unsigned int nvariables; + int max_colpos; + List *variables; + List *columns; + char cursorname[32]; + Portal portal; /* one shot (execute) plan */ + SPIPlanPtr plan; + MemoryContext cursor_cxt; + MemoryContext cursor_xact_cxt; + MemoryContext tuples_cxt; + MemoryContext result_cxt; /* short life memory context */ + HeapTuple tuples[1000]; + TupleDesc coltupdesc; + TupleDesc tupdesc; + CastCacheData *casts; + uint64 processed; + uint64 nread; + uint64 start_read; + bool assigned; + bool executed; + Bitmapset *array_columns; /* set of array columns */ + uint64 batch_rows; /* how much rows should be fetched to fill target arrays */ +} CursorData; + +typedef struct GmssqlContext +{ + MemoryContext gms_sql_cxt = NULL; + CursorData *gms_sql_cursors = NULL; +}GmssqlContext; + +/* + * bind variable data + */ +typedef struct +{ + char *refname; + int position; + + Datum value; + + Oid typoid; + bool typbyval; + int16 typlen; + + bool isnull; + unsigned int varno; /* number of assigned placeholder of parsed query */ + bool is_array; /* true, when a value is assigned via bind_array */ + Oid typelemid; /* Oid of element of a array */ + bool typelembyval; + int16 typelemlen; + int index1; + int index2; +} VariableData; + +/* + * Query result column definition + */ +typedef struct +{ + int position; + + Oid typoid; + bool typbyval; + int16 typlen; + int32 typmod; + bool typisstr; + Oid typarrayoid; /* oid of requested array output value */ + uint64 rowcount; /* maximal rows of requested array */ + int index1; /* output array should be rewrited from this index */ +} ColumnData; + +typedef enum +{ + TOKEN_SPACES, + TOKEN_COMMENT, + TOKEN_NUMBER, + TOKEN_BIND_VAR, + TOKEN_STR, + TOKEN_EXT_STR, + TOKEN_DOLAR_STR, + TOKEN_IDENTIF, + TOKEN_QIDENTIF, + TOKEN_DOUBLE_COLON, + TOKEN_OTHER, + TOKEN_NONE +} ofTokenType; + +/* from gms_sql.cpp */ +extern "C" Datum gms_sql_is_open(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_open_cursor(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_close_cursor(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_parse(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_bind_variable(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_bind_variable_f(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_bind_array_3(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_bind_array_5(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_define_column(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_define_array(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_execute(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_fetch_rows(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_execute_and_fetch(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_column_value(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_column_value_f(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_last_row_count(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_describe_columns(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_describe_columns_f(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_debug_cursor(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_return_result(PG_FUNCTION_ARGS); +extern "C" Datum gms_sql_return_result_i(PG_FUNCTION_ARGS); + +#endif diff --git a/contrib/gms_sql/sql/gms_sql.sql b/contrib/gms_sql/sql/gms_sql.sql new file mode 100644 index 0000000000..9bb6a9d887 --- /dev/null +++ b/contrib/gms_sql/sql/gms_sql.sql @@ -0,0 +1,315 @@ +CREATE EXTENSION gms_sql; +set gms_sql_max_open_cursor_count = 501; +reset gms_sql_max_open_cursor_count; +show gms_sql_max_open_cursor_count; +do $$ +declare + c int; + strval varchar; + intval int; + nrows int default 30; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'select ''ahoj'' || i, i from generate_series(1, :nrows) g(i)', gms_sql.v6); + gms_sql.bind_variable(c, 'nrows', nrows); + gms_sql.define_column(c, 1, strval); + gms_sql.define_column(c, 2, intval); + perform gms_sql.execute(c); + while gms_sql.fetch_rows(c) > 0 + loop + gms_sql.column_value(c, 1, strval); + gms_sql.column_value(c, 2, intval); + raise notice 'c1: %, c2: %', strval, intval; + end loop; + gms_sql.close_cursor(c); +end; +$$; + +do $$ +declare + c int; + strval varchar; + intval int; + nrows int default 30; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'select ''ahoj'' || i, i from generate_series(1, :nrows) g(i)', gms_sql.v7); + gms_sql.bind_variable(c, 'nrows', nrows); + gms_sql.define_column(c, 1, strval); + gms_sql.define_column(c, 2, intval); + perform gms_sql.execute(c); + while gms_sql.fetch_rows(c) > 0 + loop + strval := gms_sql.column_value_f(c, 1, strval); + intval := gms_sql.column_value_f(c, 2, intval); + raise notice 'c1: %, c2: %', strval, intval; + end loop; + gms_sql.close_cursor(c); +end; +$$; + +drop table if exists foo; + +create table foo(a int, b varchar, c numeric); + +do $$ +declare c int; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'insert into foo values(:a, :b, :c)', gms_sql.native); + for i in 1..100 + loop + gms_sql.bind_variable(c, 'a', i); + gms_sql.bind_variable(c, 'b', 'Ahoj ' || i); + gms_sql.bind_variable(c, 'c', i + 0.033); + perform gms_sql.execute(c); + end loop; + gms_sql.close_cursor(c); +end; +$$; + +select * from foo; +truncate foo; + +do $$ +declare c int; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'insert into foo values(:a, :b, :c)', gms_sql.native); + for i in 1..100 + loop + gms_sql.bind_variable_f(c, 'a', i); + gms_sql.bind_variable_f(c, 'b', 'Ahoj ' || i); + gms_sql.bind_variable_f(c, 'c', i + 0.033); + perform gms_sql.execute(c); + end loop; + gms_sql.close_cursor(c); +end; +$$; + +select * from foo; +truncate foo; + +do $$ +declare + c int; + a int[]; + b varchar[]; + ca numeric[]; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'insert into foo values(:a, :b, :c)', gms_sql.v6); + a := ARRAY[1, 2, 3, 4, 5]; + b := ARRAY['Ahoj', 'Nazdar', 'Bazar']; + ca := ARRAY[3.14, 2.22, 3.8, 4]; + + perform gms_sql.bind_array(c, 'a', a); + perform gms_sql.bind_array(c, 'b', b); + perform gms_sql.bind_array(c, 'c', ca); + raise notice 'inserted rows %d', gms_sql.execute(c); + gms_sql.close_cursor(c); +end; +$$; + +select * from foo; +truncate foo; + +do $$ +declare + c int; + a int[]; + b varchar[]; + ca numeric[]; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'insert into foo values(:a, :b, :c)', gms_sql.v7); + a := ARRAY[1, 2, 3, 4, 5]; + b := ARRAY['Ahoj', 'Nazdar', 'Bazar']; + ca := ARRAY[3.14, 2.22, 3.8, 4]; + + perform gms_sql.bind_array(c, 'a', a, 2, 3); + perform gms_sql.bind_array(c, 'b', b, 3, 4); + perform gms_sql.bind_array(c, 'c', ca); + raise notice 'inserted rows %d', gms_sql.execute(c); + gms_sql.close_cursor(c); +end; +$$; + +select * from foo; +truncate foo; + +do $$ +declare + c int; + a int[]; + b varchar[]; + ca numeric[]; +begin + c := gms_sql.open_cursor(); + gms_sql.parse(c, 'select i, ''Ahoj'' || i, i + 0.003 from generate_series(1, 35) g(i)', 0); + gms_sql.define_array(c, 1, a, 10, 1); + gms_sql.define_array(c, 2, b, 10, 1); + gms_sql.define_array(c, 3, ca, 10, 1); + + perform gms_sql.execute(c); + while gms_sql.fetch_rows(c) > 0 + loop + gms_sql.column_value(c, 1, a); + gms_sql.column_value(c, 2, b); + gms_sql.column_value(c, 3, ca); + raise notice 'a = %', a; + raise notice 'b = %', b; + raise notice 'c = %', ca; + end loop; + gms_sql.close_cursor(c); +end; +$$; + +drop table foo; + +do $$ +declare +l_curid int; +l_cnt int; +l_desctab gms_sql.desc_tab; +l_sqltext varchar(2000); +begin + l_sqltext='select * from pg_object;'; + l_curid := gms_sql.open_cursor(); + gms_sql.parse(l_curid, l_sqltext, 0); + gms_sql.describe_columns(l_curid, l_cnt, l_desctab); + for i in 1 .. l_desctab.count loop + raise notice '%,% ', l_desctab(i).col_name,l_desctab(i).col_type; + end loop; + gms_sql.close_cursor(l_curid); +end; +$$; + +create table t1(id int, name varchar(20)); +insert into t1 select generate_series(1,3), 'abcddd'; +create table t2(a int, b date); +insert into t2 values(1, '2022-12-11 10:00:01.123'); +insert into t2 values(3, '2022-12-12 12:00:11.13'); + +do $$ +declare + c1 refcursor; + c2 refcursor; +begin + open c1 for select * from t1; + gms_sql.return_result(c1); + open c2 for select * from t2; + gms_sql.return_result(c2); +end; +$$; + + +create procedure test_result() as +declare + c1 refcursor; + c2 refcursor; +begin + open c1 for select * from t1; + gms_sql.return_result(c1); + open c2 for select * from t2; + gms_sql.return_result(c2); +end; +/ +call test_result(); +drop procedure test_result; + +create procedure aam() as +declare +id1 int; +id2 int; +begin +id1 :=gms_sql.open_cursor(); +gms_sql.parse(id1,'select * from t1', 1); +perform gms_sql.execute(id1); +gms_sql.return_result(id1); +gms_sql.close_cursor(id1); +id2 :=gms_sql.open_cursor(); +gms_sql.parse(id2,'select * from t2', 2); +perform gms_sql.execute(id2); +gms_sql.return_result(id2); +gms_sql.close_cursor(id2); +end; +/ +call aam(); +drop procedure aam; +create table col_name_too_long(aaaaabbbbbcccccdddddeeeeefffffggg int, col2 text); + +do $$ +declare +l_curid int; +l_cnt int; +l_desctab gms_sql.desc_tab; +l_desctab2 gms_sql.desc_tab2; +l_sqltext varchar(2000); +begin + l_sqltext='select * from t1;'; + l_curid := gms_sql.open_cursor(); + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns(l_curid, l_cnt, l_desctab); + for i in 1 .. l_desctab.count loop + raise notice '%', l_desctab(i).col_name; + end loop; + -- output col_name + l_sqltext='select * from col_name_too_long;'; + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns2(l_curid, l_cnt, l_desctab2); + for i in 1 .. l_desctab2.count loop + raise notice '%', l_desctab2(i).col_name; + end loop; + -- error + l_sqltext='select * from col_name_too_long;'; + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns(l_curid, l_cnt, l_desctab); + for i in 1 .. l_desctab.count loop + raise notice '%', l_desctab(i).col_name; + end loop; +end; +$$; +select gms_sql.is_open(0); +select gms_sql.close_cursor(0); +do $$ +declare +l_curid int; +l_cnt int; +l_desctab3 gms_sql.desc_tab3; +l_desctab4 gms_sql.desc_tab4; +l_sqltext varchar(2000); +begin + l_sqltext='select * from col_name_too_long;'; + l_curid := gms_sql.open_cursor(); + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns3(l_curid, l_cnt, l_desctab3); + for i in 1 .. l_desctab3.count loop + raise notice '%,%,%', l_desctab3(i).col_type,l_desctab3(i).col_type_name,l_desctab3(i).col_name; + end loop; + gms_sql.parse(l_curid, l_sqltext, 1); + gms_sql.describe_columns3(l_curid, l_cnt, l_desctab4); + for i in 1 .. l_desctab4.count loop + raise notice '%,%,%,%', l_desctab3(i).col_type,l_desctab4(i).col_type_name,l_desctab4(i).col_type_name_len,l_desctab4(i).col_name_len; + end loop; + gms_sql.close_cursor(l_curid); +end; +$$; + +drop table t1,t2, col_name_too_long; + +select gms_sql.open_cursor(); +select gms_sql.is_open(0); +select gms_sql.open_cursor(); +select gms_sql.is_open(1); +select gms_sql.open_cursor(); +select gms_sql.is_open(2); +select gms_sql.open_cursor(); +select gms_sql.is_open(3); +select gms_sql.close_cursor(0); +select gms_sql.close_cursor(1); +select gms_sql.close_cursor(2); +select gms_sql.close_cursor(3); +select gms_sql.is_open(3); +select gms_sql.close_cursor(10000); +select gms_sql.close_cursor(-1); diff --git a/src/bin/gs_guc/cluster_guc.conf b/src/bin/gs_guc/cluster_guc.conf index ea1daac54a..a51727497b 100755 --- a/src/bin/gs_guc/cluster_guc.conf +++ b/src/bin/gs_guc/cluster_guc.conf @@ -457,6 +457,7 @@ primary_slotname|string|0,0|NULL|NULL| psort_work_mem|int|64,2147483647|kB|Several running sessions may be storing in the action column to sort the table at the same time. Therefore, the total memory usage may be several times than psort_work_mem.| query_band|string|0,0|NULL|NULL| query_dop|int|1,64|NULL|NULL| +gms_sql_max_open_cursor_count|int|10,500|NULL|NULL| query_mem|int|0,2147483647|kB|Sets the memory to be reserved for a statement.| query_max_mem|int|0,2147483647|kB|Sets the max memory to be reserved for a statement.| quote_all_identifiers|bool|0,0|NULL|NULL| diff --git a/src/common/backend/parser/gram.y b/src/common/backend/parser/gram.y index 5013d70b3d..fac49ad0ee 100644 --- a/src/common/backend/parser/gram.y +++ b/src/common/backend/parser/gram.y @@ -17239,6 +17239,34 @@ CreateProcedureStmt: $$ = (Node *)n; } + | CREATE opt_or_replace definer_user PROCEDURE func_name_opt_arg proc_args + LANGUAGE ColId_or_Sconst AS func_as opt_createproc_opt_list + { + CreateFunctionStmt *n = makeNode(CreateFunctionStmt); + int count = get_outarg_num($6); + n->isOraStyle = false; + n->isPrivate = false; + n->replace = $2; + n->definer = $3; + if (n->replace && NULL != n->definer) { + parser_yyerror("not support DEFINER function"); + } + n->funcname = $5; + n->parameters = $6; + n->returnType = NULL; + if (0 == count) + { + n->returnType = makeTypeName("void"); + n->returnType->typmods = NULL; + n->returnType->arrayBounds = NULL; + } + n->options = $11; + n->options = lappend(n->options, makeDefElem("language", (Node *)makeString($8))); + n->options = lappend(n->options, makeDefElem("as", (Node *)$10)); + n->withClause = NIL; + n->isProcedure = true; + $$ = (Node *)n; + } ; CreatePackageStmt: diff --git a/src/common/backend/utils/misc/guc.cpp b/src/common/backend/utils/misc/guc.cpp index af52de897e..e43dd263de 100755 --- a/src/common/backend/utils/misc/guc.cpp +++ b/src/common/backend/utils/misc/guc.cpp @@ -331,6 +331,7 @@ const char* sync_guc_variable_namelist[] = {"work_mem", "default_text_search_config", "dynamic_library_path", "gin_fuzzy_search_limit", + "gms_sql_max_open_cursor_count", "local_preload_libraries", "deadlock_timeout", "array_nulls", @@ -2698,6 +2699,20 @@ static void InitConfigureNamesInt() NULL, NULL, NULL}, + {{"gms_sql_max_open_cursor_count", + PGC_USERSET, + NODE_ALL, + CLIENT_CONN_OTHER, + gettext_noop("Sets the maximum is open cursor num."), + NULL, + 0}, + &u_sess->attr.attr_common.maxOpenCursorCount, + 100, + 10, + 500, + NULL, + NULL, + NULL}, /* Can't be set in postgresql.conf */ {{"server_version_num", PGC_INTERNAL, diff --git a/src/common/backend/utils/mmgr/mcxt.cpp b/src/common/backend/utils/mmgr/mcxt.cpp index 7f0222ee8f..9de135b120 100644 --- a/src/common/backend/utils/mmgr/mcxt.cpp +++ b/src/common/backend/utils/mmgr/mcxt.cpp @@ -64,6 +64,7 @@ THR_LOCAL MemoryContext AlignMemoryContext = NULL; static void MemoryContextStatsInternal(MemoryContext context, int level); static void FreeMemoryContextList(List* context_list); +static void MemoryContextCallResetCallbacks(MemoryContext context); #ifdef PGXC void* allocTopCxt(size_t s); @@ -264,6 +265,7 @@ void std_MemoryContextReset(MemoryContext context) /* Nothing to do if no pallocs since startup or last reset */ if (!context->isReset) { RemoveMemoryContextInfo(context); + MemoryContextCallResetCallbacks(context); (*context->methods->reset)(context); context->isReset = true; } @@ -335,6 +337,13 @@ static void MemoryContextDeleteInternal(MemoryContext context, bool parent_locke MemoryContextCheck(context, context->session_id > 0); #endif + /* + * It's not entirely clear whether 'tis better to do this before or after + * delinking the context; but an error in a callback will likely result in + * leaking the whole context (if it's not a root context) if we do it + * after, so let's do it before. + */ + MemoryContextCallResetCallbacks(context); MemoryContext parent = context->parent; @@ -449,6 +458,52 @@ void std_MemoryContextDeleteChildren(MemoryContext context, List* context_list) } } +/* + * MemoryContextRegisterResetCallback + * Register a function to be called before next context reset/delete. + * Such callbacks will be called in reverse order of registration. + * + * The caller is responsible for allocating a MemoryContextCallback struct + * to hold the info about this callback request, and for filling in the + * "func" and "arg" fields in the struct to show what function to call with + * what argument. Typically the callback struct should be allocated within + * the specified context, since that means it will automatically be freed + * when no longer needed. + * + * There is no API for deregistering a callback once registered. If you + * want it to not do anything anymore, adjust the state pointed to by its + * "arg" to indicate that. + */ +void MemoryContextRegisterResetCallback(MemoryContext context, + MemoryContextCallback *cb) +{ + AssertArg(MemoryContextIsValid(context)); + + /* Push onto head so this will be called before older registrants. */ + cb->next = context->resetCbs; + context->resetCbs = cb; + /* Mark the context as non-reset (it probably is already). */ + context->isReset = false; +} + +/* + * MemoryContextCallResetCallbacks + * Internal function to call all registered callbacks for context. + */ +static void MemoryContextCallResetCallbacks(MemoryContext context) +{ + MemoryContextCallback *cb; + + /* + * We pop each callback from the list before calling. That way, if an + * error occurs inside the callback, we won't try to call it a second time + * in the likely event that we reset or delete the context later. + */ + while ((cb = context->resetCbs) != NULL) { + context->resetCbs = cb->next; + (*cb->func)(cb->arg); + } +} /* * std_MemoryContextResetAndDeleteChildren * Release all space allocated within a context and delete all diff --git a/src/gausskernel/runtime/executor/spi.cpp b/src/gausskernel/runtime/executor/spi.cpp index 8e58945b79..8471e2405a 100644 --- a/src/gausskernel/runtime/executor/spi.cpp +++ b/src/gausskernel/runtime/executor/spi.cpp @@ -1496,6 +1496,29 @@ void *SPI_palloc(Size size) return pointer; } +Datum SPI_datumTransfer(Datum value, bool typByVal, int typLen) +{ + MemoryContext old_ctx = NULL; + Datum result; + + if (u_sess->SPI_cxt._curid + 1 == u_sess->SPI_cxt._connected) { /* connected */ + if (u_sess->SPI_cxt._current != &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._curid + 1])) { + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("SPI stack corrupted when allocate, connected level: %d", u_sess->SPI_cxt._connected))); + } + + old_ctx = MemoryContextSwitchTo(u_sess->SPI_cxt._current->savedcxt); + } + + result = datumCopy(value, typByVal, typLen); + + if (old_ctx) { + (void)MemoryContextSwitchTo(old_ctx); + } + + return result; +} + void *SPI_repalloc(void *pointer, Size size) { /* No longer need to worry which context chunk was in... */ diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index 46c308f315..3c0647043e 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -144,6 +144,8 @@ extern void SPI_pfree(void* pointer); extern void SPI_freetuple(HeapTuple pointer); extern void SPI_freetuptable(SPITupleTable* tuptable); +extern Datum SPI_datumTransfer(Datum value, bool typByVal, int typLen); + extern Portal SPI_cursor_open(const char* name, SPIPlanPtr plan, Datum* Values, const char* Nulls, bool read_only); extern Portal SPI_cursor_open_with_args(const char* name, const char* src, int nargs, Oid* argtypes, Datum* Values, const char* Nulls, bool read_only, int cursorOptions, parse_query_func parser = GetRawParser()); diff --git a/src/include/knl/knl_guc/knl_session_attr_common.h b/src/include/knl/knl_guc/knl_session_attr_common.h index 54f8a4a44b..a7915cade2 100644 --- a/src/include/knl/knl_guc/knl_session_attr_common.h +++ b/src/include/knl/knl_guc/knl_session_attr_common.h @@ -102,6 +102,7 @@ typedef struct knl_session_attr_common { int tcp_keepalives_count; int tcp_user_timeout; int GinFuzzySearchLimit; + int maxOpenCursorCount; int server_version_num; int log_temp_files; int transaction_sync_naptime; diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h index 271594e811..bcf1919bcd 100644 --- a/src/include/utils/palloc.h +++ b/src/include/utils/palloc.h @@ -28,6 +28,8 @@ #ifndef PALLOC_H #define PALLOC_H #ifndef FRONTEND_PARSER +#include +#include #include "postgres.h" #include "c.h" #include "nodes/nodes.h" @@ -99,6 +101,21 @@ typedef struct McxtOperationMethods { void (*mcxt_check)(MemoryContext context, bool own_by_session); #endif } McxtOperationMethods; +/* + * A memory context can have callback functions registered on it. Any such + * function will be called once just before the context is next reset or + * deleted. The MemoryContextCallback struct describing such a callback + * typically would be allocated within the context itself, thereby avoiding + * any need to manage it explicitly (the reset/delete action will free it). + */ + +typedef void (*MemoryContextCallbackFunction) (std::shared_ptr arg); + +typedef struct MemoryContextCallback { + MemoryContextCallbackFunction func; /* function to call */ + std::shared_ptr arg; /* argument to pass it */ + struct MemoryContextCallback *next; /* next in list of callbacks */ +} MemoryContextCallback; typedef struct MemoryContextData { NodeTag type; /* identifies exact kind of context */ @@ -114,6 +131,7 @@ typedef struct MemoryContextData { MemoryContext prevchild; /* previous child of same parent */ MemoryContext nextchild; /* next child of same parent */ char* name; /* context name (just for debugging) */ + MemoryContextCallback *resetCbs; /* list of reset/delete callbacks */ pthread_rwlock_t lock; /* lock to protect members if the context is shared */ int level; /* context level */ uint64 session_id; /* session id of context owner */ @@ -140,6 +158,7 @@ extern THR_LOCAL PGDLLIMPORT MemoryContext TopMemoryContext; const uint64 BlkMagicNum = 0xDADADADADADADADA; const uint32 PremagicNum = 0xBABABABA; #endif + /* * Flags for MemoryContextAllocExtended. */ @@ -308,6 +327,10 @@ static inline MemoryContext MemoryContextSwitchTo(MemoryContext context) extern MemoryContext MemoryContextSwitchTo(MemoryContext context); #endif /* USE_INLINE && !FRONTEND */ +/* Registration of memory context reset/delete callbacks */ +extern void MemoryContextRegisterResetCallback(MemoryContext context, + MemoryContextCallback *cb); + /* * These are like standard strdup() except the copied string is * allocated in a context, not with malloc(). -- Gitee