From 7aa0f396091ed5ea86bae84253d477dfd3f5bf3b Mon Sep 17 00:00:00 2001 From: LittleGanyu Date: Wed, 26 Jun 2024 16:03:03 +0800 Subject: [PATCH 1/3] add codes for xmldom --- build/script/utils/common.sh | 5 + build/script/utils/make_compile.sh | 3 +- configure | 16 +- src/common/backend/utils/adt/rowtypes.cpp | 19 +- src/common/backend/utils/fmgr/dfmgr.cpp | 2 +- src/common/pl/plpgsql/src/pl_exec.cpp | 6 +- src/common/pl/plpython/.gitignore | 3 + src/common/pl/plpython/Makefile | 16 +- src/common/pl/plpython/data/book.xml | 19 + src/common/pl/plpython/nls.mk | 2 +- src/common/pl/plpython/plpy_cursorobject.cpp | 49 +- src/common/pl/plpython/plpy_cursorobject.h | 2 + src/common/pl/plpython/plpy_elog.cpp | 10 +- src/common/pl/plpython/plpy_exec.cpp | 25 +- src/common/pl/plpython/plpy_main.cpp | 347 +- src/common/pl/plpython/plpy_main.h | 23 +- src/common/pl/plpython/plpy_planobject.cpp | 56 +- src/common/pl/plpython/plpy_planobject.h | 1 + src/common/pl/plpython/plpy_plpymodule.cpp | 60 +- src/common/pl/plpython/plpy_procedure.cpp | 130 +- src/common/pl/plpython/plpy_procedure.h | 1 + src/common/pl/plpython/plpy_resultobject.cpp | 7 +- src/common/pl/plpython/plpy_spi.cpp | 51 +- src/common/pl/plpython/plpy_spi.h | 1 + src/common/pl/plpython/plpy_subxactobject.cpp | 24 +- src/common/pl/plpython/plpy_typeio.cpp | 161 +- src/common/pl/plpython/plpy_typeio.h | 8 +- src/common/pl/plpython/plpy_util.cpp | 37 - src/common/pl/plpython/plpython3u--1.0.sql | 3392 +++++++++++++++++ src/common/pl/plpython/po/zh_CN.po | 230 +- src/common/port/path.cpp | 14 + .../process/threadpool/knl_session.cpp | 13 + .../storage/access/common/heaptuple.cpp | 26 + src/gausskernel/storage/ipc/Makefile | 2 +- src/gausskernel/storage/ipc/ipc.cpp | 3 + src/gausskernel/storage/ipc/ipci.cpp | 3 + src/gausskernel/storage/ipc/plpython_init.cpp | 38 + .../knl/knl_guc/knl_session_attr_common.h | 3 + src/include/knl/knl_session.h | 3 + .../pl/plpython => include}/plpy_util.h | 5 - .../pl/plpython => include}/plpython.h | 46 +- src/include/port.h | 1 + src/include/storage/lock/lock.h | 10 + src/include/storage/plpython_init.h | 25 + .../plpython3u/plpython_composite.out | 542 +++ .../expected/plpython3u/plpython_do.out | 9 + .../expected/plpython3u/plpython_drop.out | 6 + .../expected/plpython3u/plpython_error.out | 408 ++ .../expected/plpython3u/plpython_global.out | 52 + .../plpython3u/plpython_global_session.out | 41 + .../plpython3u/plpython_gms_xmldom.out | 1871 +++++++++ .../plpython3u/plpython_gms_xslprocessor.out | 256 ++ .../expected/plpython3u/plpython_import.out | 79 + .../expected/plpython3u/plpython_newline.out | 30 + .../expected/plpython3u/plpython_params.out | 64 + .../expected/plpython3u/plpython_populate.out | 22 + .../expected/plpython3u/plpython_quote.out | 54 + .../expected/plpython3u/plpython_record.out | 373 ++ .../expected/plpython3u/plpython_schema.out | 43 + .../expected/plpython3u/plpython_setof.out | 124 + .../expected/plpython3u/plpython_spi.out | 474 +++ .../plpython3u/plpython_subtransaction.out | 477 +++ .../expected/plpython3u/plpython_test.out | 80 + .../expected/plpython3u/plpython_trigger.out | 567 +++ .../expected/plpython3u/plpython_types.out | 601 +++ .../expected/plpython3u/plpython_unicode.out | 32 + .../expected/plpython3u/plpython_void.out | 30 + src/test/regress/parallel_schedule0 | 18 + src/test/regress/parallel_schedule0plpython | 17 + .../sql/plpython3u/plpython_composite.sql | 197 + .../regress/sql/plpython3u/plpython_do.sql | 5 + .../regress/sql/plpython3u/plpython_drop.sql | 8 + .../regress/sql/plpython3u/plpython_error.sql | 301 ++ .../sql/plpython3u/plpython_global.sql | 38 + .../plpython3u/plpython_global_session.sql | 18 + .../sql/plpython3u/plpython_gms_xmldom.sql | 1405 +++++++ .../plpython3u/plpython_gms_xslprocessor.sql | 258 ++ .../sql/plpython3u/plpython_import.sql | 68 + .../sql/plpython3u/plpython_newline.sql | 20 + .../sql/plpython3u/plpython_params.sql | 42 + .../sql/plpython3u/plpython_populate.sql | 27 + .../regress/sql/plpython3u/plpython_quote.sql | 32 + .../sql/plpython3u/plpython_record.sql | 167 + .../sql/plpython3u/plpython_schema.sql | 39 + .../regress/sql/plpython3u/plpython_setof.sql | 63 + .../regress/sql/plpython3u/plpython_spi.sql | 317 ++ .../plpython3u/plpython_subtransaction.sql | 296 ++ .../regress/sql/plpython3u/plpython_test.sql | 53 + .../sql/plpython3u/plpython_trigger.sql | 408 ++ .../regress/sql/plpython3u/plpython_types.sql | 322 ++ .../sql/plpython3u/plpython_unicode.sql | 25 + .../regress/sql/plpython3u/plpython_void.sql | 22 + 92 files changed, 14628 insertions(+), 671 deletions(-) create mode 100644 src/common/pl/plpython/.gitignore create mode 100644 src/common/pl/plpython/data/book.xml create mode 100644 src/gausskernel/storage/ipc/plpython_init.cpp rename src/{common/pl/plpython => include}/plpy_util.h (69%) rename src/{common/pl/plpython => include}/plpython.h (77%) create mode 100644 src/include/storage/plpython_init.h create mode 100644 src/test/regress/expected/plpython3u/plpython_composite.out create mode 100644 src/test/regress/expected/plpython3u/plpython_do.out create mode 100644 src/test/regress/expected/plpython3u/plpython_drop.out create mode 100644 src/test/regress/expected/plpython3u/plpython_error.out create mode 100644 src/test/regress/expected/plpython3u/plpython_global.out create mode 100644 src/test/regress/expected/plpython3u/plpython_global_session.out create mode 100644 src/test/regress/expected/plpython3u/plpython_gms_xmldom.out create mode 100644 src/test/regress/expected/plpython3u/plpython_gms_xslprocessor.out create mode 100644 src/test/regress/expected/plpython3u/plpython_import.out create mode 100644 src/test/regress/expected/plpython3u/plpython_newline.out create mode 100644 src/test/regress/expected/plpython3u/plpython_params.out create mode 100644 src/test/regress/expected/plpython3u/plpython_populate.out create mode 100644 src/test/regress/expected/plpython3u/plpython_quote.out create mode 100644 src/test/regress/expected/plpython3u/plpython_record.out create mode 100644 src/test/regress/expected/plpython3u/plpython_schema.out create mode 100644 src/test/regress/expected/plpython3u/plpython_setof.out create mode 100644 src/test/regress/expected/plpython3u/plpython_spi.out create mode 100644 src/test/regress/expected/plpython3u/plpython_subtransaction.out create mode 100644 src/test/regress/expected/plpython3u/plpython_test.out create mode 100644 src/test/regress/expected/plpython3u/plpython_trigger.out create mode 100644 src/test/regress/expected/plpython3u/plpython_types.out create mode 100644 src/test/regress/expected/plpython3u/plpython_unicode.out create mode 100644 src/test/regress/expected/plpython3u/plpython_void.out create mode 100644 src/test/regress/parallel_schedule0plpython create mode 100644 src/test/regress/sql/plpython3u/plpython_composite.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_do.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_drop.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_error.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_global.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_global_session.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_gms_xmldom.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_gms_xslprocessor.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_import.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_newline.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_params.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_populate.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_quote.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_record.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_schema.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_setof.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_spi.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_subtransaction.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_test.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_trigger.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_types.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_unicode.sql create mode 100644 src/test/regress/sql/plpython3u/plpython_void.sql diff --git a/build/script/utils/common.sh b/build/script/utils/common.sh index 2d64317f05..932ee7a7df 100644 --- a/build/script/utils/common.sh +++ b/build/script/utils/common.sh @@ -124,6 +124,11 @@ export LD_LIBRARY_PATH=$BINARYLIBS_PATH/zstd/lib:$LD_LIBRARY_PATH export PATH=$BUILD_TOOLS_PATH/gcc$gcc_version/gcc/bin:$PATH export JAVA_HOME=${PLATFORM_PATH}/huaweijdk8/${PLATFORM_ARCH}/jdk +export GAUSS_PYTHON_HOME=${PLATFORM_PATH}/python3.7 +export CPLUS_INCLUDE_PATH=$GAUSS_PYTHON_HOME/include/python3.7m:$CPLUS_INCLUDE_PATH +export LD_LIBRARY_PATH=$BINARYLIBS_PATH/openssl/comm/lib:$LD_LIBRARY_PATH +export LD_LIBRARY_PATH=$PLATFORM_PATH/python3.7/lib:$LD_LIBRARY_PATH + declare ERR_FAILED=1 declare ERR_OK=0 diff --git a/build/script/utils/make_compile.sh b/build/script/utils/make_compile.sh index 89547da238..f1e1842ae8 100644 --- a/build/script/utils/make_compile.sh +++ b/build/script/utils/make_compile.sh @@ -160,8 +160,9 @@ function install_gaussdb() fi shared_opt="--gcc-version=${gcc_version}.${gcc_sub_version} --prefix="${BUILD_DIR}" --3rd=${binarylib_dir} --enable-thread-safety ${enable_readline} ${with_tassl} --without-zlib" + shared_opt+=" PYTHON=${binarylib_dir}/kernel/platform/python3.7/bin/python3 --with-python" if [ "$product_mode"x == "opengauss"x ]; then - GAUSSDB_EXTRA_FLAGS=" " + GAUSSDB_EXTRA_FLAGS=" " if [[ "$PLATFORM_ARCH"x == "x86_64"x || "$PLATFORM_ARCH"x == "aarch64"x ]] ; then extra_config_opt+=" --enable-mot --enable-bbox --enable-htap" diff --git a/configure b/configure index 02d3c947a9..daf5792cc0 100755 --- a/configure +++ b/configure @@ -8439,10 +8439,12 @@ $as_echo "$as_me: error: distutils module not found" >&2;} fi { $as_echo "$as_me:$LINENO: checking Python configuration directory" >&5 $as_echo_n "checking Python configuration directory... " >&6; } -python_majorversion=`${PYTHON} -c "import sys; print(sys.version[0])"` -python_version=`${PYTHON} -c "import sys; print(sys.version[:3])"` -python_configdir=`${PYTHON} -c "import distutils.sysconfig; print(' '.join(filter(None,distutils.sysconfig.get_config_vars('LIBPL'))))"` -python_includespec=`${PYTHON} -c "import distutils.sysconfig; print('-I'+distutils.sysconfig.get_python_inc())"` +python_base=`echo ${PYTHON%/*/*}` +hardware_name=`uname -m` +python_majorversion=3 +python_version=3.7 +python_configdir="${python_base}/lib/python3.7/config-3.7m-${hardware_name}-linux-gnu" +python_includespec="-I${python_base}/include/python3.7m" # This should be enough of a message. { $as_echo "$as_me:$LINENO: result: $python_configdir" >&5 @@ -8452,9 +8454,9 @@ $as_echo "$python_configdir" >&6; } { $as_echo "$as_me:$LINENO: checking how to link an embedded Python application" >&5 $as_echo_n "checking how to link an embedded Python application... " >&6; } -python_libdir=`${PYTHON} -c "import distutils.sysconfig; print(' '.join(filter(None,distutils.sysconfig.get_config_vars('LIBDIR'))))"` -python_ldlibrary=`${PYTHON} -c "import distutils.sysconfig; print(' '.join(filter(None,distutils.sysconfig.get_config_vars('LDLIBRARY'))))"` -python_so=`${PYTHON} -c "import distutils.sysconfig; print(' '.join(filter(None,distutils.sysconfig.get_config_vars('SO'))))"` +python_libdir="${python_base}/lib" +python_ldlibrary="libpython3.7m.so" +python_so=".cpython-37m-${hardware_name}-linux-gnu.so" ldlibrary=`echo "${python_ldlibrary}" | sed "s/${python_so}$//"` if test x"${python_libdir}" != x"" -a x"${python_ldlibrary}" != x"" -a x"${python_ldlibrary}" != x"${ldlibrary}" diff --git a/src/common/backend/utils/adt/rowtypes.cpp b/src/common/backend/utils/adt/rowtypes.cpp index b628ab3ba5..47b087e731 100644 --- a/src/common/backend/utils/adt/rowtypes.cpp +++ b/src/common/backend/utils/adt/rowtypes.cpp @@ -67,12 +67,8 @@ Datum record_in(PG_FUNCTION_ARGS) { char* string = PG_GETARG_CSTRING(0); Oid tupType = PG_GETARG_OID(1); - -#ifdef NOT_USED - int32 typmod = PG_GETARG_INT32(2); -#endif + int32 tupTypmod = PG_GETARG_INT32(2); HeapTupleHeader result; - int32 tupTypmod; TupleDesc tupdesc; HeapTuple tuple; RecordIOData* my_extra = NULL; @@ -91,10 +87,9 @@ Datum record_in(PG_FUNCTION_ARGS) * anonymous type is wanted. Note that for RECORD, what we'll probably * actually get is RECORD's typelem, ie, zero. */ - if (tupType == InvalidOid || tupType == RECORDOID) + if (tupType == RECORDOID && tupTypmod < 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("input of anonymous composite types is not implemented"))); - tupTypmod = -1; /* for all non-anonymous types */ /* * This comes from the composite type's pg_type.oid and stores system oids @@ -546,12 +541,8 @@ Datum record_recv(PG_FUNCTION_ARGS) { StringInfo buf = (StringInfo)PG_GETARG_POINTER(0); Oid tupType = PG_GETARG_OID(1); - -#ifdef NOT_USED - int32 typmod = PG_GETARG_INT32(2); -#endif + int32 tupTypmod = PG_GETARG_INT32(2); HeapTupleHeader result; - int32 tupTypmod; TupleDesc tupdesc; HeapTuple tuple; RecordIOData* my_extra = NULL; @@ -569,10 +560,10 @@ Datum record_recv(PG_FUNCTION_ARGS) * anonymous type is wanted. Note that for RECORD, what we'll probably * actually get is RECORD's typelem, ie, zero. */ - if (tupType == InvalidOid || tupType == RECORDOID) + if (tupType == RECORDOID && tupTypmod < 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("input of anonymous composite types is not implemented"))); - tupTypmod = -1; /* for all non-anonymous types */ + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; diff --git a/src/common/backend/utils/fmgr/dfmgr.cpp b/src/common/backend/utils/fmgr/dfmgr.cpp index 38e45df865..bcb31b00bc 100644 --- a/src/common/backend/utils/fmgr/dfmgr.cpp +++ b/src/common/backend/utils/fmgr/dfmgr.cpp @@ -273,7 +273,7 @@ void* internal_load_library(const char* libname) #endif #ifdef ENABLE_PYTHON3 - #define PYTHON_LIB_NAME "libpython3.so" + #define PYTHON_LIB_NAME "libpython3.7m.so" #endif /* * In C++, when you try to open a shared library use dlopen with flag RTLD_NOW, diff --git a/src/common/pl/plpgsql/src/pl_exec.cpp b/src/common/pl/plpgsql/src/pl_exec.cpp index 5c06962582..18c227149b 100644 --- a/src/common/pl/plpgsql/src/pl_exec.cpp +++ b/src/common/pl/plpgsql/src/pl_exec.cpp @@ -1551,8 +1551,10 @@ Datum plpgsql_exec_autonm_function(PLpgSQL_function* func, firstnsp = false; } } - appendStringInfoChar(&buf, ';'); - (void)u_sess->SPI_cxt.autonomous_session->ExecSimpleQuery(buf.data, NULL, 0); + if (!firstnsp) { + appendStringInfoChar(&buf, ';'); + (void)u_sess->SPI_cxt.autonomous_session->ExecSimpleQuery(buf.data, NULL, 0); + } list_free_ext(search_path); if (buf.data != NULL) { pfree(buf.data); diff --git a/src/common/pl/plpython/.gitignore b/src/common/pl/plpython/.gitignore new file mode 100644 index 0000000000..a6a7a6a4be --- /dev/null +++ b/src/common/pl/plpython/.gitignore @@ -0,0 +1,3 @@ +# autogenerated from src/backend/utils/errcodes.txt, do not edit +# there is deliberately not an #ifndef SPIEXCEPTIONS_H here +spiexceptions.h \ No newline at end of file diff --git a/src/common/pl/plpython/Makefile b/src/common/pl/plpython/Makefile index 2941d3e4c1..4c5cf5ebb2 100644 --- a/src/common/pl/plpython/Makefile +++ b/src/common/pl/plpython/Makefile @@ -101,7 +101,8 @@ REGRESS = \ plpython_quote \ plpython_composite \ plpython_subtransaction \ - plpython_drop + plpython_drop \ + plpython_gms_xmldom # where to find psql for running the tests PSQLDIR = $(bindir) @@ -110,13 +111,22 @@ include $(top_srcdir)/src/Makefile.shlib all: all-lib +python_bin_dir := $(shell dirname $(PYTHON)) -install: all install-lib install-data +# Install PYTHONHOME to PGHOME/python directory +install-python: + $(MKDIR_P) '$(DESTDIR)$(bindir)/../python/' + cp -r $(python_bin_dir)/../* '$(DESTDIR)$(bindir)/../python/' + +install: all install-lib install-data install-python installdirs: installdirs-lib $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' -uninstall: uninstall-lib uninstall-data +uninstall-python: + rm -rf '$(DESTDIR)$(bindir)/../python/' + +uninstall: uninstall-lib uninstall-data uninstall-python install-data: installdirs $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/' diff --git a/src/common/pl/plpython/data/book.xml b/src/common/pl/plpython/data/book.xml new file mode 100644 index 0000000000..6fff5bd172 --- /dev/null +++ b/src/common/pl/plpython/data/book.xml @@ -0,0 +1,19 @@ + + + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 562 + + diff --git a/src/common/pl/plpython/nls.mk b/src/common/pl/plpython/nls.mk index fa64992f0b..65f56eba35 100644 --- a/src/common/pl/plpython/nls.mk +++ b/src/common/pl/plpython/nls.mk @@ -1,6 +1,6 @@ # src/pl/plpython/nls.mk CATALOG_NAME = plpython -AVAIL_LANGUAGES = cs de es fr it ja pl pt_BR ro ru zh_CN +AVAIL_LANGUAGES = zh_CN GETTEXT_FILES = plpy_cursorobject.cpp plpy_elog.cpp plpy_exec.cpp plpy_main.cpp plpy_planobject.cpp plpy_plpymodule.cpp \ plpy_procedure.cpp plpy_resultobject.cpp plpy_spi.cpp plpy_subxactobject.cpp plpy_typeio.cpp plpy_util.cpp GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS) PLy_elog:2 PLy_exception_set:2 PLy_exception_set_plural:2,3 diff --git a/src/common/pl/plpython/plpy_cursorobject.cpp b/src/common/pl/plpython/plpy_cursorobject.cpp index 38d5bb5af5..c53cdd5daa 100644 --- a/src/common/pl/plpython/plpy_cursorobject.cpp +++ b/src/common/pl/plpython/plpy_cursorobject.cpp @@ -9,6 +9,7 @@ #include "access/xact.h" #include "mb/pg_wchar.h" +#include "utils/memutils.h" #include "plpython.h" @@ -22,7 +23,6 @@ #include "plpy_spi.h" static PyObject* PLy_cursor_query(const char* query); -static PyObject* PLy_cursor_plan(PyObject* ob, PyObject* args); static void PLy_cursor_dealloc(PyObject* arg); static PyObject* PLy_cursor_iternext(PyObject* self); static PyObject* PLy_cursor_fetch(PyObject* self, PyObject* args); @@ -90,8 +90,7 @@ PyObject* PLy_cursor(PyObject* self, PyObject* args) if (PyArg_ParseTuple(args, "O|O", &plan, &planargs)) { return PLy_cursor_plan(plan, planargs); } - - PLy_exception_set(g_plpy_t_context.PLy_exc_error, "plpy.cursor expected a query or a plan"); + PLy_exception_set(g_ply_ctx->PLy_exc_error, "plpy.cursor expected a query or a plan"); return NULL; } @@ -106,7 +105,18 @@ static PyObject* PLy_cursor_query(const char* query) } cursor->portalname = NULL; cursor->closed = false; - PLy_typeinfo_init(&cursor->result); + /* + * Due to Python GC, cursors may be released by other sessions after + * the original session ends. Therefore, allocate memory in an + * instance-level MemoryContext + */ + cursor->mcxt = AllocSetContextCreate(g_ply_ctx->ply_mctx, + "PL/Python cursor context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE, + SHARED_CONTEXT); + PLy_typeinfo_init(&cursor->result, cursor->mcxt); oldcontext = CurrentMemoryContext; oldowner = t_thrd.utils_cxt.CurrentResourceOwner; @@ -133,7 +143,7 @@ static PyObject* PLy_cursor_query(const char* query) elog(ERROR, "SPI_cursor_open() failed: %s", SPI_result_code_string(SPI_result)); } - cursor->portalname = PLy_strdup(portal->name); + cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name); PLy_spi_subtransaction_commit(oldcontext, oldowner); } @@ -148,7 +158,7 @@ static PyObject* PLy_cursor_query(const char* query) return (PyObject*)cursor; } -static PyObject* PLy_cursor_plan(PyObject* ob, PyObject* args) +PyObject* PLy_cursor_plan(PyObject* ob, PyObject* args) { PLyCursorObject* cursor = NULL; volatile int nargs; @@ -194,7 +204,18 @@ static PyObject* PLy_cursor_plan(PyObject* ob, PyObject* args) } cursor->portalname = NULL; cursor->closed = false; - PLy_typeinfo_init(&cursor->result); + /* + * Due to Python GC, cursors may be released by other sessions after + * the original session ends. Therefore, allocate memory in an + * instance-level MemoryContext + */ + cursor->mcxt = AllocSetContextCreate(g_ply_ctx->ply_mctx, + "PL/Python cursor context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE, + SHARED_CONTEXT); + PLy_typeinfo_init(&cursor->result, cursor->mcxt); oldcontext = CurrentMemoryContext; oldowner = t_thrd.utils_cxt.CurrentResourceOwner; @@ -245,7 +266,7 @@ static PyObject* PLy_cursor_plan(PyObject* ob, PyObject* args) elog(ERROR, "SPI_cursor_open() failed: %s", SPI_result_code_string(SPI_result)); } - cursor->portalname = PLy_strdup(portal->name); + cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name); PLy_spi_subtransaction_commit(oldcontext, oldowner); } @@ -291,12 +312,13 @@ static void PLy_cursor_dealloc(PyObject* arg) if (PortalIsValid(portal)) { SPI_cursor_close(portal); } + cursor->closed = true; } - PLy_free(cursor->portalname); - cursor->portalname = NULL; - - PLy_typeinfo_dealloc(&cursor->result); + if (cursor->mcxt) { + MemoryContextDelete(cursor->mcxt); + cursor->mcxt = NULL; + } arg->ob_type->tp_free(arg); } @@ -411,7 +433,8 @@ static PyObject* PLy_cursor_fetch(PyObject* self, PyObject* args) ret->rows = PyList_New(SPI_processed); for (uint32 i = 0; i < SPI_processed; i++) { - PyObject *row = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[i], SPI_tuptable->tupdesc, true); + PyObject* row = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[i], SPI_tuptable->tupdesc, true); + PyList_SetItem(ret->rows, i, row); } } diff --git a/src/common/pl/plpython/plpy_cursorobject.h b/src/common/pl/plpython/plpy_cursorobject.h index 4e62f45289..95f50cbc65 100644 --- a/src/common/pl/plpython/plpy_cursorobject.h +++ b/src/common/pl/plpython/plpy_cursorobject.h @@ -11,9 +11,11 @@ typedef struct PLyCursorObject { PyObject_HEAD char* portalname; PLyTypeInfo result; bool closed; + MemoryContext mcxt; } PLyCursorObject; extern void PLy_cursor_init_type(void); extern PyObject* PLy_cursor(PyObject* self, PyObject* args); +extern PyObject* PLy_cursor_plan(PyObject* ob, PyObject* args); #endif /* PLPY_CURSOROBJECT_H */ diff --git a/src/common/pl/plpython/plpy_elog.cpp b/src/common/pl/plpython/plpy_elog.cpp index f01ebaa89e..f5b81e7b30 100644 --- a/src/common/pl/plpython/plpy_elog.cpp +++ b/src/common/pl/plpython/plpy_elog.cpp @@ -47,11 +47,11 @@ void PLy_elog(int elevel, const char* fmt, ...) PyErr_Fetch(&exc, &val, &tb); if (exc != NULL) { - if (PyErr_GivenExceptionMatches(val, g_plpy_t_context.PLy_exc_spi_error)) { + if (PyErr_GivenExceptionMatches(val, g_ply_ctx->PLy_exc_spi_error)) { PLy_get_spi_error_data(val, &sqlerrcode, &detail, &hint, &query, &position); hint = pstrdup(hint); query = pstrdup(query); - } else if (PyErr_GivenExceptionMatches(val, g_plpy_t_context.PLy_exc_fatal)) { + } else if (PyErr_GivenExceptionMatches(val, g_ply_ctx->PLy_exc_fatal)) { elevel = FATAL; } } @@ -66,7 +66,7 @@ void PLy_elog(int elevel, const char* fmt, ...) bool success = false; va_start(ap, fmt); - success = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); + success = appendStringInfoVA(&emsg, dgettext(PG_TEXTDOMAIN("plpython"), fmt), ap); va_end(ap); if (success) { break; @@ -435,7 +435,7 @@ void PLy_exception_set(PyObject* exc, const char* fmt, ...) errno_t rc = EOK; va_start(ap, fmt); - rc = vsnprintf_s(buf, sizeof(buf), sizeof(buf) - 1, dgettext(TEXTDOMAIN, fmt), ap); + rc = vsnprintf_s(buf, sizeof(buf), sizeof(buf) - 1, dgettext(PG_TEXTDOMAIN("plpython"), fmt), ap); securec_check_ss(rc, "\0", "\0"); va_end(ap); @@ -450,7 +450,7 @@ void PLy_exception_set_plural(PyObject* exc, const char* fmt_singular, const cha errno_t rc = EOK; va_start(ap, n); - rc = vsnprintf_s(buf, sizeof(buf), sizeof(buf) - 1, dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n), ap); + rc = vsnprintf_s(buf, sizeof(buf), sizeof(buf) - 1, dngettext(PG_TEXTDOMAIN("plpython"), fmt_singular, fmt_plural, n), ap); securec_check_ss(rc, "\0", "\0"); va_end(ap); diff --git a/src/common/pl/plpython/plpy_exec.cpp b/src/common/pl/plpython/plpy_exec.cpp index f59960c4a2..888223457b 100644 --- a/src/common/pl/plpython/plpy_exec.cpp +++ b/src/common/pl/plpython/plpy_exec.cpp @@ -125,7 +125,7 @@ Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure* proc) } fcinfo->isnull = true; - PG_TRY_RETURN((Datum)NULL); + PG_TRY_RETURN(((Datum)NULL)); } } @@ -177,6 +177,8 @@ Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure* proc) rv = PLyObject_ToCompositeDatum(&proc->result, desc, plrv); fcinfo->isnull = (rv == (Datum)NULL); + + ReleaseTupleDesc(desc); } else { fcinfo->isnull = false; rv = (proc->result.out.d.func)(&proc->result.out.d, -1, plrv); @@ -489,8 +491,7 @@ static PyObject* PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure* p pltevent = PyString_FromString("INSERT"); PyDict_SetItemString(pltdata, "old", Py_None); - pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att, - !TRIGGER_FIRED_BEFORE(tdata->tg_event)); + pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att, !TRIGGER_FIRED_BEFORE(tdata->tg_event)); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); *rv = tdata->tg_trigtuple; @@ -505,8 +506,7 @@ static PyObject* PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure* p } else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) { pltevent = PyString_FromString("UPDATE"); - pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, tdata->tg_relation->rd_att, - !TRIGGER_FIRED_BEFORE(tdata->tg_event)); + pytnew = PLyDict_FromTuple(&(proc->result), tdata->tg_newtuple, tdata->tg_relation->rd_att, !TRIGGER_FIRED_BEFORE(tdata->tg_event)); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); pytold = PLyDict_FromTuple(&(proc->result), tdata->tg_trigtuple, tdata->tg_relation->rd_att, true); @@ -638,7 +638,7 @@ static HeapTuple PLy_modify_tuple(PLyProcedure* proc, PyObject* pltd, TriggerDat } atti = attn - 1; - if (ISGENERATEDCOL(tupdesc, atti)) + if (ISGENERATEDCOL(tupdesc, atti)) ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), errmsg("cannot set generated column \"%s\"", plattstr))); @@ -721,7 +721,7 @@ static void plpython_trigger_error_callback(void* arg) static PyObject* PLy_procedure_call(PLyProcedure* proc, char* kargs, PyObject* vargs) { PyObject* rv = NULL; - int volatile save_subxact_level = list_length(g_plpy_t_context.explicit_subtransactions); + int volatile save_subxact_level = list_length(u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions); PyDict_SetItemString(proc->globals, kargs, vargs); @@ -738,7 +738,7 @@ static PyObject* PLy_procedure_call(PLyProcedure* proc, char* kargs, PyObject* v * started, you cannot *unnest* subtransactions, only *nest* them * without closing. */ - Assert(list_length(g_plpy_t_context.explicit_subtransactions) >= save_subxact_level); + Assert(list_length(u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions) >= save_subxact_level); } PG_CATCH(); { @@ -765,10 +765,10 @@ static void PLy_abort_open_subtransactions(int save_subxact_level) { Assert(save_subxact_level >= 0); - while (list_length(g_plpy_t_context.explicit_subtransactions) > save_subxact_level) { + while (list_length(u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions) > save_subxact_level) { PLySubtransactionData* subtransactiondata = NULL; - Assert(g_plpy_t_context.explicit_subtransactions != NIL); + Assert(u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions != NIL); ereport(WARNING, (errmsg("forcibly aborting a subtransaction that has not been exited"))); @@ -784,11 +784,10 @@ static void PLy_abort_open_subtransactions(int save_subxact_level) SPI_restore_connection(); - subtransactiondata = (PLySubtransactionData*)linitial(g_plpy_t_context.explicit_subtransactions); - g_plpy_t_context.explicit_subtransactions = list_delete_first(g_plpy_t_context.explicit_subtransactions); + subtransactiondata = (PLySubtransactionData*)linitial(u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions); + u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions = list_delete_first(u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions); MemoryContextSwitchTo(subtransactiondata->oldcontext); t_thrd.utils_cxt.CurrentResourceOwner = subtransactiondata->oldowner; - PLy_free(subtransactiondata); } } diff --git a/src/common/pl/plpython/plpy_main.cpp b/src/common/pl/plpython/plpy_main.cpp index 276e4c473b..56bda2a7f5 100644 --- a/src/common/pl/plpython/plpy_main.cpp +++ b/src/common/pl/plpython/plpy_main.cpp @@ -30,7 +30,6 @@ #include "plpy_plpymodule.h" #include "plpy_procedure.h" - /* exported functions */ #if PY_MAJOR_VERSION >= 3 /* Use separate names to avoid clash in pg_pltemplate */ @@ -71,82 +70,213 @@ static void PLy_init_interp(void); static PLyExecutionContext* PLy_push_execution_context(void); static void PLy_pop_execution_context(void); static void AuditPlpythonFunction(Oid funcoid, const char* funcname, AuditResult result); +static void init_ply_session_ctx(void); +static void release_ply_session_ctx(ply_session_ctx* ctx); +static void setup_python_envs(void); +static void init_ply_globals_ctx(void); -static const int plpython_python_version = PY_MAJOR_VERSION; - +ply_globals_ctx* g_ply_ctx; /* this doesn't need to be global; use PLy_current_execution_context() */ -static THR_LOCAL PLyExecutionContext* PLy_execution_contexts = NULL; -THR_LOCAL plpy_t_context_struct g_plpy_t_context = {0}; -pthread_mutex_t g_plyLocaleMutex = PTHREAD_MUTEX_INITIALIZER; +/* + * there are problems such as memory leaks, deadlocks, crashes in 'plpy', + * so turn it off by default. + */ +const bool enable_plpy = false; -void PG_init(void) +/* + * Perform one-time setup of PL/Python, after checking for a conflict + * with other versions of Python. + */ +static void PLy_initialize(void) { - /* Be sure we do initialization only once (should be redundant now) */ - const int** version_ptr; + /* initialize python interpreter, only executed once in the process */ + if (!plpython_state->is_init) { + MemoryContext old; - if (g_plpy_t_context.inited) { - return; - } + plpython_state->release_ply_session_ctx_callback = release_ply_session_ctx; + Assert(PY_MAJOR_VERSION >= 3); + + setup_python_envs(); + + init_ply_globals_ctx(); - /* Be sure we don't run Python 2 and 3 in the same session (might crash) */ - version_ptr = (const int**)find_rendezvous_variable("plpython_python_version"); - if (!(*version_ptr)) { - *version_ptr = &plpython_python_version; + Assert(g_ply_ctx); + + old = MemoryContextSwitchTo(g_ply_ctx->ply_mctx); + PyImport_AppendInittab("plpy", PyInit_plpy); + + Py_Initialize(); + PyImport_ImportModule("plpy"); + PLy_init_interp(); + PLy_init_plpy(); + if (PyErr_Occurred()) + PLy_elog(FATAL, "untrapped error in initialization"); + + MemoryContextSwitchTo(old); + + plpython_state->is_init = true; } else { - if (**version_ptr != plpython_python_version) { - ereport(FATAL, - (errmsg("Python major version mismatch in session"), - errdetail("This session has previously used Python major version %d, and it is now attempting to " - "use Python major version %d.", - **version_ptr, - plpython_python_version), - errhint("Start a new session to use a different Python major version."))); - } + g_ply_ctx = plpython_state->ply_globals_ctx; } - pg_bindtextdomain(TEXTDOMAIN); + /* initialize session */ + if (!u_sess->plpython_ctx) { + pg_bindtextdomain(PG_TEXTDOMAIN("plpython")); + init_ply_session_ctx(); + Assert(u_sess->attr.attr_common.g_ply_session_ctx); + u_sess->plpython_ctx = u_sess->attr.attr_common.g_ply_session_ctx; + } + else { + u_sess->attr.attr_common.g_ply_session_ctx = (ply_session_ctx*)u_sess->plpython_ctx; + } +} -#if PY_MAJOR_VERSION >= 3 - PyImport_AppendInittab("plpy", PyInit_plpy); -#endif +void _PG_init(void) +{ + /* Only support Python 3.8 */ + if (PY_MAJOR_VERSION != 3 || PY_MINOR_VERSION != 7) { + ereport(ERROR, (errmsg("Python version is not 3.7, check if PYTHONHOME is valid."))); + } +} -#if PY_MAJOR_VERSION < 3 - if (!PyEval_ThreadsInitialized()) { - PyEval_InitThreads(); +static void setup_python_envs(void) +{ + /* + * openGauss requires PYTHONHOME to be set (reason unknown, crashes otherwise) + * Therefore during 'make install' we install the corresponding PYTHONHOME + * under PGHOME/python directory. Set PYTHONHOME before initializing Python. + */ + char pyhome[MAXPGPATH]; + size_t size; + + get_pythonhome_path(my_exec_path, pyhome); + + size = mbstowcs(NULL, pyhome, strlen(pyhome)); + if (size < 0) { + PLy_elog(FATAL, "untrapped error in initialization"); } -#endif - Py_Initialize(); -#if PY_MAJOR_VERSION >= 3 - PyImport_ImportModule("plpy"); -#endif + wchar_t w_pythonhome[size]; + mbstowcs(w_pythonhome, pyhome, strlen(pyhome)); + w_pythonhome[size] = 0; -#if PY_MAJOR_VERSION >= 3 - if (!PyEval_ThreadsInitialized()) { - PyEval_InitThreads(); - PyEval_SaveThread(); + Py_SetPythonHome(w_pythonhome); +} + +static void init_ply_globals_ctx(void) +{ + /* Initialized only once and accessible by all processes, hence using a global variable is straightforward */ + static ply_globals_ctx _ply_globals_ctx; + /* + * Reserved an extra connection, so free list is never NULL, + * simplifies boundary condition handling + */ + int max_connections = g_instance.attr.attr_network.MaxConnections + 1; + memset_s(&_ply_globals_ctx, sizeof(_ply_globals_ctx), 0, sizeof(_ply_globals_ctx)); + + g_ply_ctx = &_ply_globals_ctx; + g_ply_ctx->ply_mctx = AllocSetContextCreate(INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DEFAULT), + "plpy global context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE, + SHARED_CONTEXT); + g_ply_ctx->numberFreeContexts = max_connections; + g_ply_ctx->numberContexts = max_connections; + g_ply_ctx->sessionContexts = (ply_session_ctx*)MemoryContextAllocZero(g_ply_ctx->ply_mctx, max_connections * sizeof(ply_session_ctx)); + + for (int i = 0; i < max_connections; i++) { + ply_session_ctx* ctx = &g_ply_ctx->sessionContexts[i]; + ctx->ply_ctx_id = i; + if (i == max_connections - 1) { + ctx->next_free_context = NULL; /*the last one*/ + } else { + ctx->next_free_context = &g_ply_ctx->sessionContexts[i + 1]; + } } -#endif - PLy_init_interp(); - PLy_init_plpy(); - if (PyErr_Occurred()) { + g_ply_ctx->free_session_head = &g_ply_ctx->sessionContexts[0]; + g_ply_ctx->free_session_tail = &g_ply_ctx->sessionContexts[max_connections-1]; + + plpython_state->ply_globals_ctx = g_ply_ctx; +} + +static void init_ply_session_ctx() +{ + if (g_ply_ctx->numberFreeContexts <= 1) { PLy_elog(FATAL, "untrapped error in initialization"); } + Assert(g_ply_ctx->free_session_head); + + u_sess->attr.attr_common.g_ply_session_ctx = g_ply_ctx->free_session_head; + g_ply_ctx->free_session_head = u_sess->attr.attr_common.g_ply_session_ctx->next_free_context; + g_ply_ctx->numberFreeContexts--; + u_sess->attr.attr_common.g_ply_session_ctx->next_free_context = NULL; + Assert(g_ply_ctx->free_session_head); + + Assert(!u_sess->attr.attr_common.g_ply_session_ctx->session_mctx); + char contextName[128]; + snprintf(contextName, sizeof(contextName), "PL/Python session context %d", u_sess->attr.attr_common.g_ply_session_ctx->ply_ctx_id); + u_sess->attr.attr_common.g_ply_session_ctx->session_mctx = AllocSetContextCreate(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DEFAULT), + contextName, + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + snprintf(contextName, sizeof(contextName), "PL/Python temp context %d", u_sess->attr.attr_common.g_ply_session_ctx->ply_ctx_id); + u_sess->attr.attr_common.g_ply_session_ctx->session_tmp_mctx = AllocSetContextCreate(u_sess->attr.attr_common.g_ply_session_ctx->session_mctx, + contextName, + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions = NULL; + u_sess->attr.attr_common.g_ply_session_ctx->PLy_execution_contexts = NULL; + u_sess->attr.attr_common.g_ply_session_ctx->PLy_session_gd = PyDict_New(); + if (!u_sess->attr.attr_common.g_ply_session_ctx->PLy_session_gd) + PLy_elog(ERROR, "could not create globals"); + Assert(!u_sess->attr.attr_common.g_ply_session_ctx->PLy_procedure_cache); init_procedure_caches(); +} + +static void release_ply_session_ctx(ply_session_ctx* ctx) +{ + if (ctx->PLy_procedure_cache) + { + HASH_SEQ_STATUS scan; + PLyProcedureEntry* entry = NULL; + PLyProcedure* proc = NULL; + hash_seq_init(&scan, ctx->PLy_procedure_cache); + while ((entry = (PLyProcedureEntry *)hash_seq_search(&scan))) { + proc = entry->proc; + PLy_procedure_delete(proc); + } - g_plpy_t_context.explicit_subtransactions = NIL; + hash_destroy(ctx->PLy_procedure_cache); + ctx->PLy_procedure_cache = NULL; + } + if (ctx->PLy_session_gd) { + Py_DECREF(ctx->PLy_session_gd); + ctx->PLy_session_gd = NULL; + } - PLy_execution_contexts = NULL; + if (ctx->session_mctx) { + MemoryContextDelete(ctx->session_mctx); + ctx->session_tmp_mctx = NULL; + ctx->session_mctx = NULL; + } - g_plpy_t_context.inited = true; -} + ctx->explicit_subtransactions = NULL; + ctx->PLy_execution_contexts = NULL; + Assert(ctx->next_free_context == NULL); + Assert(g_ply_ctx->free_session_tail); + Assert(g_ply_ctx->numberFreeContexts > 0); + g_ply_ctx->free_session_tail->next_free_context = ctx; + g_ply_ctx->free_session_tail = ctx; -void _PG_init(void) -{ - PG_init(); + g_ply_ctx->numberFreeContexts++;; } /* @@ -155,7 +285,6 @@ void _PG_init(void) */ void PLy_init_interp(void) { - static PyObject* PLy_interp_safe_globals = NULL; PyObject* mainmod = NULL; mainmod = PyImport_AddModule("__main__"); @@ -163,14 +292,10 @@ void PLy_init_interp(void) PLy_elog(ERROR, "could not import \"__main__\" module"); } Py_INCREF(mainmod); - g_plpy_t_context.PLy_interp_globals = PyModule_GetDict(mainmod); - PLy_interp_safe_globals = PyDict_New(); - if (PLy_interp_safe_globals == NULL) { - PLy_elog(ERROR, "could not create globals"); - } - PyDict_SetItemString(g_plpy_t_context.PLy_interp_globals, "GD", PLy_interp_safe_globals); + + g_ply_ctx->PLy_interp_globals = PyModule_GetDict(mainmod); Py_DECREF(mainmod); - if (g_plpy_t_context.PLy_interp_globals == NULL || PyErr_Occurred()) { + if (g_ply_ctx->PLy_interp_globals == NULL || PyErr_Occurred()) { PLy_elog(ERROR, "could not initialize globals"); } } @@ -190,16 +315,10 @@ Datum plpython_validator(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } - AutoMutexLock plpythonLock(&g_plyLocaleMutex); - if (g_plpy_t_context.Ply_LockLevel == 0) { - plpythonLock.lock(); - } - - PyLock pyLock(&(g_plpy_t_context.Ply_LockLevel)); - PG_TRY(); { - PG_init(); + PlPyGilAcquire(); + PLy_initialize(); /* Get the new function's pg_proc entry */ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); @@ -209,9 +328,6 @@ Datum plpython_validator(PG_FUNCTION_ARGS) procStruct = (Form_pg_proc)GETSTRUCT(tuple); is_trigger = PLy_procedure_is_trigger(procStruct); - if (is_trigger) { - elog(ERROR, "PL/Python does not support trigger"); - } ReleaseSysCache(tuple); @@ -220,8 +336,6 @@ Datum plpython_validator(PG_FUNCTION_ARGS) } PG_CATCH(); { - pyLock.Reset(); - plpythonLock.unLock(); PG_RE_THROW(); } PG_END_TRY(); @@ -238,21 +352,14 @@ Datum plpython2_validator(PG_FUNCTION_ARGS) Datum plpython_call_handler(PG_FUNCTION_ARGS) { - AutoMutexLock plpythonLock(&g_plyLocaleMutex); - - if (g_plpy_t_context.Ply_LockLevel == 0) { - plpythonLock.lock(); - } - Datum retval; PLyExecutionContext* exec_ctx = NULL; ErrorContextCallback plerrcontext; - PyLock pyLock(&(g_plpy_t_context.Ply_LockLevel)); - PG_TRY(); { - PG_init(); + PlPyGilAcquire(); + PLy_initialize(); /* Note: SPI_finish() happens in plpy_exec.cpp, which is dubious design */ if (SPI_connect() != SPI_OK_CONNECT) { @@ -275,8 +382,6 @@ Datum plpython_call_handler(PG_FUNCTION_ARGS) } PG_CATCH(); { - pyLock.Reset(); - plpythonLock.unLock(); PG_RE_THROW(); } PG_END_TRY(); @@ -287,7 +392,6 @@ Datum plpython_call_handler(PG_FUNCTION_ARGS) PG_TRY(); { if (CALLED_AS_TRIGGER(fcinfo)) { - elog(ERROR, "PL/Python does not support trigger"); Relation tgrel = ((TriggerData*)fcinfo->context)->tg_relation; HeapTuple trv; @@ -307,13 +411,8 @@ Datum plpython_call_handler(PG_FUNCTION_ARGS) } PG_CATCH(); { - if (AUDIT_EXEC_ENABLED) { - AuditPlpythonFunction(funcoid, proc->proname, AUDIT_FAILED); - } PLy_pop_execution_context(); PyErr_Clear(); - pyLock.Reset(); - plpythonLock.unLock(); PG_RE_THROW(); } PG_END_TRY(); @@ -327,8 +426,6 @@ Datum plpython_call_handler(PG_FUNCTION_ARGS) } PG_CATCH(); { - pyLock.Reset(); - plpythonLock.unLock(); PG_RE_THROW(); } PG_END_TRY(); @@ -345,11 +442,6 @@ Datum plpython2_call_handler(PG_FUNCTION_ARGS) Datum plpython_inline_handler(PG_FUNCTION_ARGS) { - AutoMutexLock plpythonLock(&g_plyLocaleMutex); - if (g_plpy_t_context.Ply_LockLevel == 0) { - plpythonLock.lock(); - } - PyLock pyLock(&(g_plpy_t_context.Ply_LockLevel)); InlineCodeBlock* codeblock = (InlineCodeBlock*)DatumGetPointer(PG_GETARG_DATUM(0)); FunctionCallInfoData fake_fcinfo; @@ -361,7 +453,8 @@ Datum plpython_inline_handler(PG_FUNCTION_ARGS) PG_TRY(); { - PG_init(); + PlPyGilAcquire(); + PLy_initialize(); /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */ if (SPI_connect() != SPI_OK_CONNECT) { @@ -380,7 +473,13 @@ Datum plpython_inline_handler(PG_FUNCTION_ARGS) rc = memset_s(&proc, sizeof(PLyProcedure), 0, sizeof(PLyProcedure)); securec_check(rc, "\0", "\0"); - proc.pyname = PLy_strdup("__plpython_inline_block"); + proc.mcxt = AllocSetContextCreate(u_sess->attr.attr_common.g_ply_session_ctx->session_mctx, + "__plpython_inline_block", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block"); proc.result.out.d.typoid = VOIDOID; /* @@ -401,8 +500,6 @@ Datum plpython_inline_handler(PG_FUNCTION_ARGS) } PG_CATCH(); { - plpythonLock.unLock(); - pyLock.Reset(); PG_RE_THROW(); } PG_END_TRY(); @@ -425,8 +522,6 @@ Datum plpython_inline_handler(PG_FUNCTION_ARGS) PLy_pop_execution_context(); PLy_procedure_delete(&proc); PyErr_Clear(); - plpythonLock.unLock(); - pyLock.Reset(); PG_RE_THROW(); } PG_END_TRY(); @@ -443,8 +538,6 @@ Datum plpython_inline_handler(PG_FUNCTION_ARGS) } PG_CATCH(); { - plpythonLock.unLock(); - pyLock.Reset(); PG_RE_THROW(); } PG_END_TRY(); @@ -488,40 +581,54 @@ static void plpython_inline_error_callback(void* arg) PLyExecutionContext* PLy_current_execution_context(void) { - if (PLy_execution_contexts == NULL) { + if (u_sess->attr.attr_common.g_ply_session_ctx->PLy_execution_contexts == NULL) { elog(ERROR, "no Python function is currently executing"); } - return PLy_execution_contexts; + return u_sess->attr.attr_common.g_ply_session_ctx->PLy_execution_contexts; +} + +MemoryContext PLy_get_scratch_context(PLyExecutionContext *context) +{ + /* + * A scratch context might never be needed in a given plpython procedure, + * so allocate it on first request. + */ + if (context->scratch_ctx == NULL) + context->scratch_ctx = + AllocSetContextCreate(u_sess->attr.attr_common.g_ply_session_ctx->session_mctx, + "PL/Python scratch context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + return context->scratch_ctx; } static PLyExecutionContext* PLy_push_execution_context(void) { - PLyExecutionContext* context = (PLyExecutionContext*)PLy_malloc(sizeof(PLyExecutionContext)); + PLyExecutionContext *context; + + context = (PLyExecutionContext *)MemoryContextAlloc(u_sess->attr.attr_common.g_ply_session_ctx->session_mctx, sizeof(PLyExecutionContext)); context->curr_proc = NULL; - context->scratch_ctx = AllocSetContextCreate(u_sess->top_transaction_mem_cxt, - "PL/Python scratch context", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); - context->next = PLy_execution_contexts; - PLy_execution_contexts = context; + context->scratch_ctx = NULL; + context->next = u_sess->attr.attr_common.g_ply_session_ctx->PLy_execution_contexts; + u_sess->attr.attr_common.g_ply_session_ctx->PLy_execution_contexts = context; return context; } static void PLy_pop_execution_context(void) { - PLyExecutionContext* context = PLy_execution_contexts; + PLyExecutionContext* context = u_sess->attr.attr_common.g_ply_session_ctx->PLy_execution_contexts; if (context == NULL) { elog(ERROR, "no Python function is currently executing"); } - PLy_execution_contexts = context->next; + u_sess->attr.attr_common.g_ply_session_ctx->PLy_execution_contexts = context->next; - MemoryContextDelete(context->scratch_ctx); - PLy_free(context); + if (context->scratch_ctx) + MemoryContextDelete(context->scratch_ctx); } static void AuditPlpythonFunction(Oid funcoid, const char* funcname, AuditResult result) @@ -541,7 +648,7 @@ static void AuditPlpythonFunction(Oid funcoid, const char* funcname, AuditResult } } else { // for abnormal function - rc = snprintf_s(details, PGAUDIT_MAXLENGTH, PGAUDIT_MAXLENGTH - 1, + rc = snprintf_s(details, PGAUDIT_MAXLENGTH, PGAUDIT_MAXLENGTH - 1, "Execute PLpython function(%s). ", funcname); } diff --git a/src/common/pl/plpython/plpy_main.h b/src/common/pl/plpython/plpy_main.h index 20d6468a15..dad3f73bc1 100644 --- a/src/common/pl/plpython/plpy_main.h +++ b/src/common/pl/plpython/plpy_main.h @@ -7,26 +7,6 @@ #include "plpy_procedure.h" -class PyLock { -public: - explicit PyLock(int* lockLevel) : m_lockLevel(lockLevel) - { - (*m_lockLevel)++; - } - - ~PyLock() - { - (*m_lockLevel)--; - } - void Reset() - { - (*m_lockLevel) = 0; - } - -private: - int* m_lockLevel; -}; - /* * A stack of PL/Python execution contexts. Each time user-defined Python code * is called, an execution context is created and put on the stack. After the @@ -41,4 +21,7 @@ typedef struct PLyExecutionContext { /* Get the current execution context */ extern PLyExecutionContext* PLy_current_execution_context(void); +/* Get the scratch memory context for specified execution context */ +extern MemoryContext PLy_get_scratch_context(PLyExecutionContext *context); + #endif /* PLPY_MAIN_H */ diff --git a/src/common/pl/plpython/plpy_planobject.cpp b/src/common/pl/plpython/plpy_planobject.cpp index 783d6339fd..40e28ca292 100644 --- a/src/common/pl/plpython/plpy_planobject.cpp +++ b/src/common/pl/plpython/plpy_planobject.cpp @@ -8,17 +8,25 @@ #include "knl/knl_variable.h" #include "plpython.h" - #include "plpy_planobject.h" - +#include "plpy_cursorobject.h" #include "plpy_elog.h" +#include "plpy_spi.h" +#include "utils/memutils.h" static void PLy_plan_dealloc(PyObject* arg); +static PyObject *PLy_plan_cursor(PyObject *self, PyObject *args); +static PyObject *PLy_plan_execute(PyObject *self, PyObject *args); static PyObject* PLy_plan_status(PyObject* self, PyObject* args); static char PLy_plan_doc[] = {"Store a PostgreSQL plan"}; -static PyMethodDef PLy_plan_methods[] = {{"status", PLy_plan_status, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL}}; +static PyMethodDef PLy_plan_methods[] = { + {"cursor", PLy_plan_cursor, METH_VARARGS, NULL}, + {"execute", PLy_plan_execute, METH_VARARGS, NULL}, + {"status", PLy_plan_status, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; static PyTypeObject PLy_PlanType = { PyVarObject_HEAD_INIT(NULL, 0) "PLyPlan", /* tp_name */ @@ -74,6 +82,7 @@ PyObject* PLy_plan_new(void) ob->types = NULL; ob->values = NULL; ob->args = NULL; + ob->mcxt = NULL; return (PyObject*)ob; } @@ -89,31 +98,46 @@ static void PLy_plan_dealloc(PyObject* arg) if (ob->plan != NULL) { SPI_freeplan(ob->plan); + ob->plan = NULL; } - if (ob->types != NULL) { - PLy_free(ob->types); - } - if (ob->values != NULL) { - PLy_free(ob->values); - } - if (ob->args != NULL) { - int i; - for (i = 0; i < ob->nargs; i++) { - PLy_typeinfo_dealloc(&ob->args[i]); - } - PLy_free(ob->args); + if (ob->mcxt) { + MemoryContextDelete(ob->mcxt); + ob->mcxt = NULL; } arg->ob_type->tp_free(arg); } +static PyObject *PLy_plan_cursor(PyObject *self, PyObject *args) +{ + PyObject *planargs = NULL; + + if (!PyArg_ParseTuple(args, "|O", &planargs)) + return NULL; + + return PLy_cursor_plan(self, planargs); +} + +static PyObject *PLy_plan_execute(PyObject *self, PyObject *args) +{ + PyObject *list = NULL; + long limit = 0; + + if (!PyArg_ParseTuple(args, "|Ol", &list, &limit)) + return NULL; + + return PLy_spi_execute_plan(self, list, limit); +} + static PyObject* PLy_plan_status(PyObject* self, PyObject* args) { if (PyArg_ParseTuple(args, "")) { Py_INCREF(Py_True); return Py_True; } - PLy_exception_set(g_plpy_t_context.PLy_exc_error, "plan.status takes no arguments"); + + PLy_exception_set(g_ply_ctx->PLy_exc_error, "plan.status takes no arguments"); + return NULL; } diff --git a/src/common/pl/plpython/plpy_planobject.h b/src/common/pl/plpython/plpy_planobject.h index 65c36443c3..2fbb1e6bf3 100644 --- a/src/common/pl/plpython/plpy_planobject.h +++ b/src/common/pl/plpython/plpy_planobject.h @@ -14,6 +14,7 @@ typedef struct PLyPlanObject { Oid* types; Datum* values; PLyTypeInfo* args; + MemoryContext mcxt; } PLyPlanObject; extern void PLy_plan_init_type(void); diff --git a/src/common/pl/plpython/plpy_plpymodule.cpp b/src/common/pl/plpython/plpy_plpymodule.cpp index 13ce37f1ca..aa6ce55989 100644 --- a/src/common/pl/plpython/plpy_plpymodule.cpp +++ b/src/common/pl/plpython/plpy_plpymodule.cpp @@ -121,6 +121,21 @@ PyMODINIT_FUNC PyInit_plpy(void) { PyObject* m = NULL; + if (!enable_plpy) { + static PyMethodDef plpy_safe_methods[] = { /* just disable the problematic functions*/ + {"debug", PLy_debug, METH_VARARGS, NULL}, + {"log", PLy_log, METH_VARARGS, NULL}, + {"info", PLy_info, METH_VARARGS, NULL}, + {"notice", PLy_notice, METH_VARARGS, NULL}, + {"warning", PLy_warning, METH_VARARGS, NULL}, + {"error", PLy_error, METH_VARARGS, NULL}, + {"fatal", PLy_fatal, METH_VARARGS, NULL}, + {"quote_literal", PLy_quote_literal, METH_VARARGS, NULL}, + {"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL}, + {"quote_ident", PLy_quote_ident, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL}}; + PLy_module.m_methods = plpy_safe_methods; + } m = PyModule_Create(&PLy_module); if (m == NULL) { return NULL; @@ -145,10 +160,12 @@ void PLy_init_plpy(void) /* * initialize plpy module */ - PLy_plan_init_type(); - PLy_result_init_type(); - PLy_subtransaction_init_type(); - PLy_cursor_init_type(); + if (enable_plpy) { + PLy_plan_init_type(); + PLy_result_init_type(); + PLy_subtransaction_init_type(); + PLy_cursor_init_type(); + } #if PY_MAJOR_VERSION >= 3 PyModule_Create(&PLy_module); @@ -202,20 +219,20 @@ static void PLy_add_exceptions(PyObject* plpy) PLy_elog(ERROR, "could not add the spiexceptions module"); } - g_plpy_t_context.PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL); - g_plpy_t_context.PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL); - g_plpy_t_context.PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL); + g_ply_ctx->PLy_exc_error = PyErr_NewException("plpy.Error", NULL, NULL); + g_ply_ctx->PLy_exc_fatal = PyErr_NewException("plpy.Fatal", NULL, NULL); + g_ply_ctx->PLy_exc_spi_error = PyErr_NewException("plpy.SPIError", NULL, NULL); - if (g_plpy_t_context.PLy_exc_error == NULL || g_plpy_t_context.PLy_exc_fatal == NULL || - g_plpy_t_context.PLy_exc_spi_error == NULL) + if (g_ply_ctx->PLy_exc_error == NULL || g_ply_ctx->PLy_exc_fatal == NULL || + g_ply_ctx->PLy_exc_spi_error == NULL) PLy_elog(ERROR, "could not create the base SPI exceptions"); - Py_INCREF(g_plpy_t_context.PLy_exc_error); - PyModule_AddObject(plpy, "Error", g_plpy_t_context.PLy_exc_error); - Py_INCREF(g_plpy_t_context.PLy_exc_fatal); - PyModule_AddObject(plpy, "Fatal", g_plpy_t_context.PLy_exc_fatal); - Py_INCREF(g_plpy_t_context.PLy_exc_spi_error); - PyModule_AddObject(plpy, "SPIError", g_plpy_t_context.PLy_exc_spi_error); + Py_INCREF(g_ply_ctx->PLy_exc_error); + PyModule_AddObject(plpy, "Error", g_ply_ctx->PLy_exc_error); + Py_INCREF(g_ply_ctx->PLy_exc_fatal); + PyModule_AddObject(plpy, "Fatal", g_ply_ctx->PLy_exc_fatal); + Py_INCREF(g_ply_ctx->PLy_exc_spi_error); + PyModule_AddObject(plpy, "SPIError", g_ply_ctx->PLy_exc_spi_error); errno_t rc = EOK; rc = memset_s(&hash_ctl, sizeof(hash_ctl), 0, sizeof(hash_ctl)); @@ -224,9 +241,11 @@ static void PLy_add_exceptions(PyObject* plpy) hash_ctl.keysize = sizeof(int); hash_ctl.entrysize = sizeof(PLyExceptionEntry); hash_ctl.hash = tag_hash; - g_plpy_t_context.PLy_spi_exceptions = hash_create("Plpy SPI exceptions", 512, &hash_ctl, HASH_ELEM | HASH_FUNCTION); + /* spi exceptions, allocate in instance-level context to avoid PLy_spi_exceptions lifetime issue */ + hash_ctl.hcxt = g_ply_ctx->ply_mctx; - PLy_generate_spi_exceptions(excmod, g_plpy_t_context.PLy_exc_spi_error); + g_ply_ctx->PLy_spi_exceptions = hash_create("Plpy SPI exceptions", 512, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + PLy_generate_spi_exceptions(excmod, g_ply_ctx->PLy_exc_spi_error); } /* @@ -258,9 +277,8 @@ static void PLy_generate_spi_exceptions(PyObject* mod, PyObject* base) Py_INCREF(exc); PyModule_AddObject(mod, exception_map[i].classname, exc); entry = (PLyExceptionEntry*)hash_search( - g_plpy_t_context.PLy_spi_exceptions, &exception_map[i].sqlstate, HASH_ENTER, &found); + g_ply_ctx->PLy_spi_exceptions, &exception_map[i].sqlstate, HASH_ENTER, &found); entry->exc = exc; - Assert(!found); } } @@ -381,7 +399,7 @@ static PyObject* PLy_output(volatile int level, PyObject* self, PyObject* args) } if (so == NULL || ((sv = PyString_AsString(so)) == NULL)) { level = ERROR; - sv = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog"); + sv = dgettext(PG_TEXTDOMAIN("plpython"), "could not parse error message in plpy.elog"); } oldcontext = CurrentMemoryContext; @@ -405,7 +423,7 @@ static PyObject* PLy_output(volatile int level, PyObject* self, PyObject* args) Py_XDECREF(so); /* Make Python raise the exception */ - PLy_exception_set(g_plpy_t_context.PLy_exc_error, "%s", edata->message); + PLy_exception_set(g_ply_ctx->PLy_exc_error, "%s", edata->message); return NULL; } PG_END_TRY(); diff --git a/src/common/pl/plpython/plpy_procedure.cpp b/src/common/pl/plpython/plpy_procedure.cpp index a0fdb65881..2e5c5fa2e3 100644 --- a/src/common/pl/plpython/plpy_procedure.cpp +++ b/src/common/pl/plpython/plpy_procedure.cpp @@ -35,8 +35,10 @@ void init_procedure_caches(void) hash_ctl.keysize = sizeof(PLyProcedureKey); hash_ctl.entrysize = sizeof(PLyProcedureEntry); hash_ctl.hash = tag_hash; - g_plpy_t_context.PLy_procedure_cache = - hash_create("PL/Python procedures", 32, &hash_ctl, HASH_ELEM | HASH_FUNCTION); + hash_ctl.hcxt = u_sess->attr.attr_common.g_ply_session_ctx->session_mctx; + + u_sess->attr.attr_common.g_ply_session_ctx->PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); } /* @@ -86,9 +88,10 @@ PLyProcedure* PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger) * anything. */ if (use_cache) { + Assert(u_sess->attr.attr_common.g_ply_session_ctx->PLy_procedure_cache); key.fn_oid = fn_oid; key.fn_rel = fn_rel; - entry = (PLyProcedureEntry*)hash_search(g_plpy_t_context.PLy_procedure_cache, &key, HASH_ENTER, &found); + entry = (PLyProcedureEntry*)hash_search(u_sess->attr.attr_common.g_ply_session_ctx->PLy_procedure_cache, &key, HASH_ENTER, &found); proc = entry->proc; } @@ -102,8 +105,9 @@ PLyProcedure* PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger) } } else if (!PLy_procedure_valid(proc, procTup)) { /* Found it, but it's invalid, free and reuse the cache entry */ - PLy_procedure_delete(proc); - PLy_free(proc); + entry->proc = NULL; + if (proc) + PLy_procedure_delete(proc); proc = PLy_procedure_create(procTup, fn_oid, is_trigger); entry->proc = proc; } @@ -113,7 +117,7 @@ PLyProcedure* PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger) { /* Do not leave an uninitialised entry in the cache */ if (use_cache) { - hash_search(g_plpy_t_context.PLy_procedure_cache, &key, HASH_REMOVE, NULL); + hash_search(u_sess->attr.attr_common.g_ply_session_ctx->PLy_procedure_cache, &key, HASH_REMOVE, NULL); } PG_RE_THROW(); } @@ -132,18 +136,19 @@ static PLyProcedure* PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; PLyProcedure* proc = NULL; - char* volatile procSource = NULL; - Datum prosrcdatum; - bool isnull = false; - int i, rv; + MemoryContext cxt; + MemoryContext oldcxt; + int rv; procStruct = (Form_pg_proc)GETSTRUCT(procTup); rv = snprintf_s(procName, sizeof(procName), sizeof(procName) - 1, - "__plpython_procedure_%s_%u", + "__plpython_procedure_%s_%u_%d", NameStr(procStruct->proname), - fn_oid); + fn_oid, + /* Use ply_ctx_id for session isolation, to avoid overwriting functions defined in other sessions */ + u_sess->attr.attr_common.g_ply_session_ctx->ply_ctx_id); if (rv >= (int)sizeof(procName) || rv < 0) { elog(ERROR, "procedure name would overrun buffer"); } @@ -155,27 +160,43 @@ static PLyProcedure* PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is } } - proc = (PLyProcedure*)PLy_malloc(sizeof(PLyProcedure)); - proc->proname = PLy_strdup(NameStr(procStruct->proname)); - proc->pyname = PLy_strdup(procName); - proc->fn_xmin = HeapTupleGetRawXmin(procTup); - proc->fn_tid = procTup->t_self; - /* Remember if function is STABLE/IMMUTABLE */ - proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE); - PLy_typeinfo_init(&proc->result); - for (i = 0; i < FUNC_MAX_ARGS; i++) { - PLy_typeinfo_init(&proc->args[i]); - } - proc->nargs = 0; - proc->code = proc->statics = NULL; - proc->globals = NULL; - proc->is_setof = procStruct->proretset; - proc->setof = NULL; - proc->src = NULL; - proc->argnames = NULL; + cxt = AllocSetContextCreate(u_sess->attr.attr_common.g_ply_session_ctx->session_mctx, + procName, + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + oldcxt = MemoryContextSwitchTo(cxt); + + proc = (PLyProcedure*)palloc0(sizeof(PLyProcedure)); + proc->mcxt = cxt; PG_TRY(); { + Datum prosrcdatum; + bool isnull; + char *procSource; + int i; + + proc->proname = pstrdup(NameStr(procStruct->proname)); + proc->pyname = pstrdup(procName); + + proc->fn_xmin = HeapTupleGetRawXmin(procTup); + proc->fn_tid = procTup->t_self; + /* Remember if function is STABLE/IMMUTABLE */ + proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE); + PLy_typeinfo_init(&proc->result, proc->mcxt); + for (i = 0; i < FUNC_MAX_ARGS; i++) { + PLy_typeinfo_init(&proc->args[i], proc->mcxt); + } + proc->nargs = 0; + proc->code = proc->statics = NULL; + proc->globals = NULL; + proc->is_setof = procStruct->proretset; + proc->setof = NULL; + proc->src = NULL; + proc->argnames = NULL; + + /* * get information required for output conversion of the return value, * but only if this isn't a trigger. @@ -230,7 +251,7 @@ static PLyProcedure* PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is Oid* types = NULL; char** names = NULL; char* modes = NULL; - int i, pos, total; + int pos, total; /* extract argument type info from the pg_proc tuple */ total = get_func_arg_info(procTup, &types, &names, &modes); @@ -247,7 +268,7 @@ static PLyProcedure* PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is } } - proc->argnames = (char**)PLy_malloc0(sizeof(char*) * proc->nargs); + proc->argnames = (char**)palloc0(sizeof(char*) * proc->nargs); for (i = pos = 0; i < total; i++) { HeapTuple argTypeTup; Form_pg_type argTypeStruct; @@ -281,7 +302,7 @@ static PLyProcedure* PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is } /* get argument name */ - proc->argnames[pos] = names ? PLy_strdup(names[i]) : NULL; + proc->argnames[pos] = names ? pstrdup(names[i]) : NULL; ReleaseSysCache(argTypeTup); @@ -301,19 +322,17 @@ static PLyProcedure* PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is PLy_procedure_compile(proc, procSource); pfree(procSource); - procSource = NULL; } PG_CATCH(); { + MemoryContextSwitchTo(oldcxt); PLy_procedure_delete(proc); - if (procSource != NULL) { - pfree(procSource); - } PG_RE_THROW(); } PG_END_TRY(); + MemoryContextSwitchTo(oldcxt); return proc; } @@ -325,13 +344,14 @@ void PLy_procedure_compile(PLyProcedure* proc, const char* src) PyObject* crv = NULL; char* msrc = NULL; - proc->globals = PyDict_Copy(g_plpy_t_context.PLy_interp_globals); + proc->globals = PyDict_Copy(g_ply_ctx->PLy_interp_globals); /* * SD is private preserved data between calls. GD is global data shared by * all functions */ proc->statics = PyDict_New(); + PyDict_SetItemString(proc->globals, "GD", u_sess->attr.attr_common.g_ply_session_ctx->PLy_session_gd); PyDict_SetItemString(proc->globals, "SD", proc->statics); /* @@ -339,7 +359,7 @@ void PLy_procedure_compile(PLyProcedure* proc, const char* src) */ msrc = PLy_procedure_munge_source(proc->pyname, src); /* Save the mangled source for later inclusion in tracebacks */ - proc->src = PLy_strdup(msrc); + proc->src = MemoryContextStrdup(proc->mcxt, msrc); crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL); pfree(msrc); @@ -371,36 +391,9 @@ void PLy_procedure_compile(PLyProcedure* proc, const char* src) void PLy_procedure_delete(PLyProcedure* proc) { - int i; - - Py_XDECREF(proc->code); Py_XDECREF(proc->statics); Py_XDECREF(proc->globals); - if (proc->proname != NULL) { - PLy_free(proc->proname); - } - if (proc->pyname) { - PLy_free(proc->pyname); - } - for (i = 0; i < proc->nargs; i++) { - if (proc->args[i].is_rowtype == 1) { - if (proc->args[i].in.r.atts) { - PLy_free(proc->args[i].in.r.atts); - } - if (proc->args[i].out.r.atts) { - PLy_free(proc->args[i].out.r.atts); - } - } - if (proc->argnames && proc->argnames[i]) { - PLy_free(proc->argnames[i]); - } - } - if (proc->src) { - PLy_free(proc->src); - } - if (proc->argnames) { - PLy_free(proc->argnames); - } + MemoryContextDelete(proc->mcxt); } /* @@ -450,7 +443,8 @@ static bool PLy_procedure_valid(PLyProcedure* proc, HeapTuple procTup) int i; bool valid = false; - Assert(proc != NULL); + if (proc == NULL) + return false; /* If the pg_proc tuple has changed, it's not valid */ if (!(proc->fn_xmin == HeapTupleGetRawXmin(procTup) && ItemPointerEquals(&proc->fn_tid, &procTup->t_self))) { diff --git a/src/common/pl/plpython/plpy_procedure.h b/src/common/pl/plpython/plpy_procedure.h index bb49d22476..31a4a2a509 100644 --- a/src/common/pl/plpython/plpy_procedure.h +++ b/src/common/pl/plpython/plpy_procedure.h @@ -11,6 +11,7 @@ extern void init_procedure_caches(void); /* cached procedure data */ typedef struct PLyProcedure { + MemoryContext mcxt; /* context holding this PLyProcedure and its subsidiary data */ char* proname; /* SQL name of procedure */ char* pyname; /* Python name of procedure */ TransactionId fn_xmin; diff --git a/src/common/pl/plpython/plpy_resultobject.cpp b/src/common/pl/plpython/plpy_resultobject.cpp index c4be01acd8..dddf5227d3 100644 --- a/src/common/pl/plpython/plpy_resultobject.cpp +++ b/src/common/pl/plpython/plpy_resultobject.cpp @@ -144,7 +144,8 @@ static PyObject* PLy_result_colnames(PyObject* self, PyObject* unused) int i; if (!ob->tupdesc) { - PLy_exception_set(g_plpy_t_context.PLy_exc_error, "command did not produce a result set"); + + PLy_exception_set(g_ply_ctx->PLy_exc_error, "command did not produce a result set"); return NULL; } @@ -163,7 +164,7 @@ static PyObject* PLy_result_coltypes(PyObject* self, PyObject* unused) int i; if (!ob->tupdesc) { - PLy_exception_set(g_plpy_t_context.PLy_exc_error, "command did not produce a result set"); + PLy_exception_set(g_ply_ctx->PLy_exc_error, "command did not produce a result set"); return NULL; } @@ -182,7 +183,7 @@ static PyObject* PLy_result_coltypmods(PyObject* self, PyObject* unused) int i; if (!ob->tupdesc) { - PLy_exception_set(g_plpy_t_context.PLy_exc_error, "command did not produce a result set"); + PLy_exception_set(g_ply_ctx->PLy_exc_error, "command did not produce a result set"); return NULL; } diff --git a/src/common/pl/plpython/plpy_spi.cpp b/src/common/pl/plpython/plpy_spi.cpp index 99b9cc0783..b94b5cf2cf 100644 --- a/src/common/pl/plpython/plpy_spi.cpp +++ b/src/common/pl/plpython/plpy_spi.cpp @@ -22,11 +22,11 @@ #include "plpy_elog.h" #include "plpy_main.h" #include "plpy_planobject.h" +#include "plpy_plpymodule.h" #include "plpy_procedure.h" #include "plpy_resultobject.h" static PyObject* PLy_spi_execute_query(char* query, long limit); -static PyObject* PLy_spi_execute_plan(PyObject* ob, PyObject* list, long limit); static PyObject* PLy_spi_execute_fetch_result(SPITupleTable* tuptable, int rows, int status); static void PLy_spi_exception_set(PyObject* excclass, ErrorData* edata); @@ -57,12 +57,27 @@ PyObject* PLy_spi_prepare(PyObject* self, PyObject* args) return NULL; } + /* + * Because of the gc problem, it is possible that the lifecycle of the plan + * object is longer than the session, so the memory has to be requested at + * the level of the instance + */ + plan->mcxt = AllocSetContextCreate(g_ply_ctx->ply_mctx, + "PL/Python plan context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE, + SHARED_CONTEXT); + oldcontext = MemoryContextSwitchTo(plan->mcxt); + nargs = list ? PySequence_Length(list) : 0; plan->nargs = nargs; - plan->types = nargs ? (Oid*)PLy_malloc(sizeof(Oid) * nargs) : NULL; - plan->values = nargs ? (Datum*)PLy_malloc(sizeof(Datum) * nargs) : NULL; - plan->args = nargs ? (PLyTypeInfo*)PLy_malloc(sizeof(PLyTypeInfo) * nargs) : NULL; + plan->types = nargs ? (Oid*)palloc(sizeof(Oid) * nargs) : NULL; + plan->values = nargs ? (Datum*)palloc(sizeof(Datum) * nargs) : NULL; + plan->args = nargs ? (PLyTypeInfo*)palloc(sizeof(PLyTypeInfo) * nargs) : NULL; + + MemoryContextSwitchTo(oldcontext); oldcontext = CurrentMemoryContext; oldowner = t_thrd.utils_cxt.CurrentResourceOwner; @@ -78,7 +93,7 @@ PyObject* PLy_spi_prepare(PyObject* self, PyObject* args) * isn't properly initialized the Py_DECREF(plan) will go boom */ for (i = 0; i < nargs; i++) { - PLy_typeinfo_init(&plan->args[i]); + PLy_typeinfo_init(&plan->args[i], plan->mcxt); plan->values[i] = PointerGetDatum(NULL); } @@ -185,11 +200,12 @@ PyObject* PLy_spi_execute(PyObject* self, PyObject* args) return PLy_spi_execute_plan(plan, list, limit); } - PLy_exception_set(g_plpy_t_context.PLy_exc_error, "plpy.execute expected a query or a plan"); + PLy_exception_set(g_ply_ctx->PLy_exc_error, "plpy.execute expected a query or a plan"); + return NULL; } -static PyObject* PLy_spi_execute_plan(PyObject* ob, PyObject* list, long limit) +PyObject* PLy_spi_execute_plan(PyObject* ob, PyObject* list, long limit) { volatile int nargs; int i, rv; @@ -310,7 +326,8 @@ static PyObject* PLy_spi_execute_plan(PyObject* ob, PyObject* list, long limit) if (rv < 0) { PLy_exception_set( - g_plpy_t_context.PLy_exc_spi_error, "SPI_execute_plan failed: %s", SPI_result_code_string(rv)); + g_ply_ctx->PLy_exc_spi_error, "SPI_execute_plan failed: %s", SPI_result_code_string(rv)); + return NULL; } @@ -348,7 +365,7 @@ static PyObject* PLy_spi_execute_query(char* query, long limit) if (rv < 0) { Py_XDECREF(ret); - PLy_exception_set(g_plpy_t_context.PLy_exc_spi_error, "SPI_execute failed: %s", SPI_result_code_string(rv)); + PLy_exception_set(g_ply_ctx->PLy_exc_spi_error, "SPI_execute failed: %s", SPI_result_code_string(rv)); return NULL; } @@ -373,7 +390,8 @@ static PyObject* PLy_spi_execute_fetch_result(SPITupleTable* tuptable, int rows, Py_DECREF(result->nrows); result->nrows = PyInt_FromLong(rows); - PLy_typeinfo_init(&args); + + PLy_typeinfo_init(&args, u_sess->attr.attr_common.g_ply_session_ctx->session_tmp_mctx); oldcontext = CurrentMemoryContext; PG_TRY(); @@ -397,7 +415,7 @@ static PyObject* PLy_spi_execute_fetch_result(SPITupleTable* tuptable, int rows, PLy_input_tuple_funcs(&args, tuptable->tupdesc); for (i = 0; i < rows; i++) { - PyObject *row = PLyDict_FromTuple(&args, tuptable->vals[i], tuptable->tupdesc, true); + PyObject* row = PLyDict_FromTuple(&args, tuptable->vals[i], tuptable->tupdesc, true); PyList_SetItem(result->rows, i, row); } @@ -407,16 +425,16 @@ static PyObject* PLy_spi_execute_fetch_result(SPITupleTable* tuptable, int rows, { MemoryContextSwitchTo(oldcontext); if (!PyErr_Occurred()) { - PLy_exception_set(g_plpy_t_context.PLy_exc_error, "unrecognized error in PLy_spi_execute_fetch_result"); + PLy_exception_set(g_ply_ctx->PLy_exc_error, "unrecognized error in PLy_spi_execute_fetch_result"); } - PLy_typeinfo_dealloc(&args); + MemoryContextReset(u_sess->attr.attr_common.g_ply_session_ctx->session_tmp_mctx); SPI_freetuptable(tuptable); Py_DECREF(result); return NULL; } PG_END_TRY(); - PLy_typeinfo_dealloc(&args); + MemoryContextReset(u_sess->attr.attr_common.g_ply_session_ctx->session_tmp_mctx); SPI_freetuptable(tuptable); } @@ -515,10 +533,11 @@ void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldown SPI_restore_connection(); /* Look up the correct exception */ - entry = (PLyExceptionEntry*)hash_search(g_plpy_t_context.PLy_spi_exceptions, &(edata->sqlerrcode), HASH_FIND, NULL); + entry = (PLyExceptionEntry*)hash_search(g_ply_ctx->PLy_spi_exceptions, &(edata->sqlerrcode), HASH_FIND, NULL); + /* We really should find it, but just in case have a fallback */ Assert(entry != NULL); - exc = entry ? entry->exc : g_plpy_t_context.PLy_exc_spi_error; + exc = entry ? entry->exc : g_ply_ctx->PLy_exc_spi_error; /* Make Python raise the exception */ PLy_spi_exception_set(exc, edata); FreeErrorData(edata); diff --git a/src/common/pl/plpython/plpy_spi.h b/src/common/pl/plpython/plpy_spi.h index 3cad0e7589..b452d69840 100644 --- a/src/common/pl/plpython/plpy_spi.h +++ b/src/common/pl/plpython/plpy_spi.h @@ -14,6 +14,7 @@ extern PyObject* PLy_spi_prepare(PyObject* self, PyObject* args); extern PyObject* PLy_spi_execute(PyObject* self, PyObject* args); +extern PyObject *PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit); typedef struct PLyExceptionEntry { int sqlstate; /* hash key, must be first */ diff --git a/src/common/pl/plpython/plpy_subxactobject.cpp b/src/common/pl/plpython/plpy_subxactobject.cpp index b00450e1e1..28ee9aaed6 100644 --- a/src/common/pl/plpython/plpy_subxactobject.cpp +++ b/src/common/pl/plpython/plpy_subxactobject.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * + * * openGauss is licensed under Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: @@ -26,6 +26,7 @@ #include "access/xact.h" #include "executor/spi.h" +#include "utils/memutils.h" #include "plpython.h" @@ -135,7 +136,9 @@ static PyObject* PLy_subtransaction_enter(PyObject* self, PyObject* unused) subxact->started = true; oldcontext = CurrentMemoryContext; - subxactdata = (PLySubtransactionData*)PLy_malloc(sizeof(*subxactdata)); + subxactdata = (PLySubtransactionData *) MemoryContextAlloc(u_sess->attr.attr_common.g_ply_session_ctx->session_mctx, + sizeof(PLySubtransactionData)); + subxactdata->oldcontext = oldcontext; subxactdata->oldowner = t_thrd.utils_cxt.CurrentResourceOwner; @@ -146,10 +149,13 @@ static PyObject* PLy_subtransaction_enter(PyObject* self, PyObject* unused) #endif BeginInternalSubTransaction(NULL); - /* Do not want to leave the previous memory context */ - MemoryContextSwitchTo(oldcontext); + /* Be sure that cells of explicit_subtransactions list are long-lived */ + MemoryContextSwitchTo(u_sess->attr.attr_common.g_ply_session_ctx->session_mctx); - g_plpy_t_context.explicit_subtransactions = lcons(subxactdata, g_plpy_t_context.explicit_subtransactions); + u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions = lcons(subxactdata, u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions); + + /* Caller wants to stay in original memory context */ + MemoryContextSwitchTo(oldcontext); Py_INCREF(self); return self; @@ -188,7 +194,7 @@ static PyObject* PLy_subtransaction_exit(PyObject* self, PyObject* args) return NULL; } - if (g_plpy_t_context.explicit_subtransactions == NIL) { + if (u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions == NIL) { PLy_exception_set(PyExc_ValueError, "there is no subtransaction to exit from"); return NULL; } @@ -217,12 +223,12 @@ static PyObject* PLy_subtransaction_exit(PyObject* self, PyObject* args) #endif } - subxactdata = (PLySubtransactionData*)linitial(g_plpy_t_context.explicit_subtransactions); - g_plpy_t_context.explicit_subtransactions = list_delete_first(g_plpy_t_context.explicit_subtransactions); + subxactdata = (PLySubtransactionData*)linitial(u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions); + u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions = list_delete_first(u_sess->attr.attr_common.g_ply_session_ctx->explicit_subtransactions); MemoryContextSwitchTo(subxactdata->oldcontext); t_thrd.utils_cxt.CurrentResourceOwner = subxactdata->oldowner; - PLy_free(subxactdata); + pfree(subxactdata); /* * AtEOSubXact_SPI() should not have popped any SPI context, but just in diff --git a/src/common/pl/plpython/plpy_typeio.cpp b/src/common/pl/plpython/plpy_typeio.cpp index 2b9067dcc6..e2ff5d5f95 100644 --- a/src/common/pl/plpython/plpy_typeio.cpp +++ b/src/common/pl/plpython/plpy_typeio.cpp @@ -27,8 +27,8 @@ #include "plpy_main.h" /* I/O function caching */ -static void PLy_input_datum_func2(PLyDatumToOb* arg, Oid typeOid, HeapTuple typeTup); -static void PLy_output_datum_func2(PLyObToDatum* arg, HeapTuple typeTup); +static void PLy_input_datum_func2(PLyDatumToOb* arg, MemoryContext arg_mcxt, Oid typeOid, HeapTuple typeTup); +static void PLy_output_datum_func2(PLyObToDatum* arg, MemoryContext arg_mcxt, HeapTuple typeTup); /* conversion from Datums to Python objects */ static PyObject* PLyBool_FromBool(PLyDatumToOb* arg, Datum d); @@ -58,10 +58,8 @@ static Datum PLyMapping_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyObject* static Datum PLySequence_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyObject* sequence); static Datum PLyGenericObject_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyObject* object); -/* make allocations in the TopMemoryContext */ -static void perm_fmgr_info(Oid functionId, FmgrInfo* finfo); -void PLy_typeinfo_init(PLyTypeInfo* arg) +void PLy_typeinfo_init(PLyTypeInfo* arg, MemoryContext mcxt) { arg->is_rowtype = -1; arg->in.r.natts = arg->out.r.natts = 0; @@ -70,30 +68,7 @@ void PLy_typeinfo_init(PLyTypeInfo* arg) arg->typ_relid = InvalidOid; arg->typrel_xmin = InvalidTransactionId; ItemPointerSetInvalid(&arg->typrel_tid); -} - -void PLy_typeinfo_dealloc(PLyTypeInfo* arg) -{ - if (arg->is_rowtype == 1) { - int i; - - for (i = 0; i < arg->in.r.natts; i++) { - if (arg->in.r.atts[i].elm != NULL) { - PLy_free(arg->in.r.atts[i].elm); - } - } - if (arg->in.r.atts) { - PLy_free(arg->in.r.atts); - } - for (i = 0; i < arg->out.r.natts; i++) { - if (arg->out.r.atts[i].elm != NULL) { - PLy_free(arg->out.r.atts[i].elm); - } - } - if (arg->out.r.atts) { - PLy_free(arg->out.r.atts); - } - } + arg->mcxt = mcxt; } /* @@ -106,7 +81,7 @@ void PLy_input_datum_func(PLyTypeInfo* arg, Oid typeOid, HeapTuple typeTup) elog(ERROR, "PLyTypeInfo struct is initialized for Tuple"); } arg->is_rowtype = 0; - PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup); + PLy_input_datum_func2(&(arg->in.d), arg->mcxt, typeOid, typeTup); } void PLy_output_datum_func(PLyTypeInfo* arg, HeapTuple typeTup) @@ -115,12 +90,15 @@ void PLy_output_datum_func(PLyTypeInfo* arg, HeapTuple typeTup) elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple"); } arg->is_rowtype = 0; - PLy_output_datum_func2(&(arg->out.d), typeTup); + PLy_output_datum_func2(&(arg->out.d), arg->mcxt, typeTup); } void PLy_input_tuple_funcs(PLyTypeInfo* arg, TupleDesc desc) { int i; + MemoryContext oldcxt; + oldcxt = MemoryContextSwitchTo(arg->mcxt); + if (arg->is_rowtype == 0) { elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); @@ -129,10 +107,10 @@ void PLy_input_tuple_funcs(PLyTypeInfo* arg, TupleDesc desc) if (arg->in.r.natts != desc->natts) { if (arg->in.r.atts) { - PLy_free(arg->in.r.atts); + pfree(arg->in.r.atts); } arg->in.r.natts = desc->natts; - arg->in.r.atts = (PLyDatumToOb*)PLy_malloc0(desc->natts * sizeof(PLyDatumToOb)); + arg->in.r.atts = (PLyDatumToOb*)palloc0(desc->natts * sizeof(PLyDatumToOb)); } /* Can this be an unnamed tuple? If not, then an Assert would be enough */ @@ -179,15 +157,19 @@ void PLy_input_tuple_funcs(PLyTypeInfo* arg, TupleDesc desc) elog(ERROR, "cache lookup failed for type %u", desc->attrs[i].atttypid); } - PLy_input_datum_func2(&(arg->in.r.atts[i]), desc->attrs[i].atttypid, typeTup); + PLy_input_datum_func2(&(arg->in.r.atts[i]), arg->mcxt, + TupleDescAttr(desc, i)->atttypid, typeTup); ReleaseSysCache(typeTup); } + MemoryContextSwitchTo(oldcxt); } void PLy_output_tuple_funcs(PLyTypeInfo* arg, TupleDesc desc) { int i; + MemoryContext oldcxt; + oldcxt = MemoryContextSwitchTo(arg->mcxt); if (arg->is_rowtype == 0) { elog(ERROR, "PLyTypeInfo struct is initialized for a Datum"); @@ -196,10 +178,10 @@ void PLy_output_tuple_funcs(PLyTypeInfo* arg, TupleDesc desc) if (arg->out.r.natts != desc->natts) { if (arg->out.r.atts) { - PLy_free(arg->out.r.atts); + pfree(arg->out.r.atts); } arg->out.r.natts = desc->natts; - arg->out.r.atts = (PLyObToDatum*)PLy_malloc0(desc->natts * sizeof(PLyObToDatum)); + arg->out.r.atts = (PLyObToDatum*)palloc0(desc->natts * sizeof(PLyObToDatum)); } Assert(OidIsValid(desc->tdtypeid)); @@ -241,10 +223,11 @@ void PLy_output_tuple_funcs(PLyTypeInfo* arg, TupleDesc desc) elog(ERROR, "cache lookup failed for type %u", desc->attrs[i].atttypid); } - PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup); + PLy_output_datum_func2(&(arg->out.r.atts[i]), arg->mcxt, typeTup); ReleaseSysCache(typeTup); } + MemoryContextSwitchTo(oldcxt); } void PLy_output_record_funcs(PLyTypeInfo* arg, TupleDesc desc) @@ -274,12 +257,12 @@ void PLy_output_record_funcs(PLyTypeInfo* arg, TupleDesc desc) /* * Transform a tuple into a Python dict object. */ -PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc, bool include_generated) +PyObject* PLyDict_FromTuple(PLyTypeInfo* info, HeapTuple tuple, TupleDesc desc, bool include_generated) { PyObject* volatile dict = NULL; PLyExecutionContext* exec_ctx = PLy_current_execution_context(); + MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx); MemoryContext oldcontext = CurrentMemoryContext; - int i; if (info->is_rowtype != 1) { elog(ERROR, "PLyTypeInfo structure describes a datum"); @@ -292,11 +275,12 @@ PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc, PG_TRY(); { + int i; /* * Do the work in the scratch context to avoid leaking memory from the * datatype output function calls. */ - MemoryContextSwitchTo(exec_ctx->scratch_ctx); + MemoryContextSwitchTo(scratch_context); for (i = 0; i < info->in.r.natts; i++) { char* key = NULL; Datum vattr; @@ -312,7 +296,6 @@ PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc, if (!include_generated) continue; } - key = NameStr(desc->attrs[i].attname); vattr = heap_getattr(tuple, (i + 1), desc, &is_null); @@ -325,7 +308,7 @@ PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc, } } MemoryContextSwitchTo(oldcontext); - MemoryContextReset(exec_ctx->scratch_ctx); + MemoryContextReset(scratch_context); } PG_CATCH(); { @@ -363,12 +346,14 @@ Datum PLyObject_ToCompositeDatum(PLyTypeInfo* info, TupleDesc desc, PyObject* pl return datum; } -static void PLy_output_datum_func2(PLyObToDatum* arg, HeapTuple typeTup) +static void PLy_output_datum_func2(PLyObToDatum* arg, MemoryContext arg_mcxt, HeapTuple typeTup) { Form_pg_type typeStruct = (Form_pg_type)GETSTRUCT(typeTup); Oid element_type; + MemoryContext oldcxt; + oldcxt = MemoryContextSwitchTo(arg_mcxt); - perm_fmgr_info(typeStruct->typinput, &arg->typfunc); + fmgr_info_cxt(typeStruct->typinput, &arg->typfunc, arg_mcxt); arg->typoid = HeapTupleGetOid(typeTup); arg->typmod = -1; arg->typioparam = getTypeIOParam(typeTup); @@ -402,13 +387,10 @@ static void PLy_output_datum_func2(PLyObToDatum* arg, HeapTuple typeTup) Oid funcid; if (type_is_rowtype(element_type)) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("PL/Python functions cannot return type %s", format_type_be(arg->typoid)), - errdetail("PL/Python does not support conversion to arrays of row types."))); + arg->func = PLyObject_ToComposite; } - arg->elm = (PLyObToDatum*)PLy_malloc0(sizeof(*arg->elm)); + arg->elm = (PLyObToDatum*)palloc0(sizeof(*arg->elm)); arg->elm->func = arg->func; arg->func = PLySequence_ToArray; @@ -422,17 +404,20 @@ static void PLy_output_datum_func2(PLyObToDatum* arg, HeapTuple typeTup) &dummy_delim, &arg->elm->typioparam, &funcid); - perm_fmgr_info(funcid, &arg->elm->typfunc); + fmgr_info_cxt(funcid, &arg->elm->typfunc, arg_mcxt); } + MemoryContextSwitchTo(oldcxt); } -static void PLy_input_datum_func2(PLyDatumToOb* arg, Oid typeOid, HeapTuple typeTup) +static void PLy_input_datum_func2(PLyDatumToOb* arg, MemoryContext arg_mcxt, Oid typeOid, HeapTuple typeTup) { Form_pg_type typeStruct = (Form_pg_type)GETSTRUCT(typeTup); Oid element_type = get_element_type(typeOid); + MemoryContext oldcxt; + oldcxt = MemoryContextSwitchTo(arg_mcxt); /* Get the type's conversion information */ - perm_fmgr_info(typeStruct->typoutput, &arg->typfunc); + fmgr_info_cxt(typeStruct->typoutput, &arg->typfunc, arg_mcxt); arg->typoid = HeapTupleGetOid(typeTup); arg->typmod = -1; arg->typioparam = getTypeIOParam(typeTup); @@ -478,7 +463,7 @@ static void PLy_input_datum_func2(PLyDatumToOb* arg, Oid typeOid, HeapTuple type char dummy_delim; Oid funcid; - arg->elm = (PLyDatumToOb*)PLy_malloc0(sizeof(*arg->elm)); + arg->elm = (PLyDatumToOb*)palloc0(sizeof(*arg->elm)); arg->elm->func = arg->func; arg->func = PLyList_FromArray; arg->elm->typoid = element_type; @@ -491,8 +476,9 @@ static void PLy_input_datum_func2(PLyDatumToOb* arg, Oid typeOid, HeapTuple type &dummy_delim, &arg->elm->typioparam, &funcid); - perm_fmgr_info(funcid, &arg->elm->typfunc); + fmgr_info_cxt(funcid, &arg->elm->typfunc, arg_mcxt); } + MemoryContextSwitchTo(oldcxt); } static PyObject* PLyBool_FromBool(PLyDatumToOb* arg, Datum d) @@ -759,7 +745,7 @@ static Datum PLyObject_ToComposite(PLyObToDatum* arg, int32 typmod, PyObject* pl errno_t rc = memset_s(&info, sizeof(PLyTypeInfo), 0, sizeof(PLyTypeInfo)); securec_check(rc, "\0", "\0"); - PLy_typeinfo_init(&info); + PLy_typeinfo_init(&info, u_sess->attr.attr_common.g_ply_session_ctx->session_tmp_mctx); /* Mark it as needing output routines lookup */ info.is_rowtype = 2; @@ -773,7 +759,9 @@ static Datum PLyObject_ToComposite(PLyObToDatum* arg, int32 typmod, PyObject* pl */ rv = PLyObject_ToCompositeDatum(&info, desc, plrv); - PLy_typeinfo_dealloc(&info); + ReleaseTupleDesc(desc); + + MemoryContextReset(u_sess->attr.attr_common.g_ply_session_ctx->session_tmp_mctx); return rv; } @@ -860,11 +848,6 @@ static Datum PLySequence_ToArray(PLyObToDatum* arg, int32 typmod, PyObject* plrv nulls[i] = true; } else { nulls[i] = false; - - /* - * We don't support arrays of row types yet, so the first argument - * can be NULL. - */ elems[i] = arg->elm->func(arg->elm, -1, obj); } Py_XDECREF(obj); @@ -893,23 +876,32 @@ static Datum PLySequence_ToArray(PLyObToDatum* arg, int32 typmod, PyObject* plrv static Datum PLyString_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyObject* string) { + Datum result; HeapTuple typeTup; + PLyTypeInfo locinfo; + + /* Create a dummy PLyTypeInfo */ + MemSet(&locinfo, 0, sizeof(PLyTypeInfo)); + PLy_typeinfo_init(&locinfo, u_sess->attr.attr_common.g_ply_session_ctx->session_tmp_mctx); typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid)); - if (!HeapTupleIsValid(typeTup)) { + if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid); - } - PLy_output_datum_func2(&info->out.d, typeTup); + PLy_output_datum_func2(&locinfo.out.d, locinfo.mcxt, typeTup); ReleaseSysCache(typeTup); - ReleaseTupleDesc(desc); - return PLyObject_ToDatum(&info->out.d, info->out.d.typmod, string); + result = PLyObject_ToDatum(&locinfo.out.d, desc->tdtypmod, string); + + MemoryContextReset(u_sess->attr.attr_common.g_ply_session_ctx->session_tmp_mctx); + + return result; } static Datum PLyMapping_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyObject* mapping) { + Datum result; HeapTuple tuple; Datum* values = NULL; bool* nulls = NULL; @@ -967,15 +959,18 @@ static Datum PLyMapping_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyObject* } tuple = heap_form_tuple(desc, values, nulls); - ReleaseTupleDesc(desc); + result = heap_copy_tuple_as_datum(tuple, desc); + heap_freetuple(tuple); + pfree(values); pfree(nulls); - return HeapTupleGetDatum(tuple); + return result; } static Datum PLySequence_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyObject* sequence) { + Datum result; HeapTuple tuple; Datum* values = NULL; bool* nulls = NULL; @@ -1048,15 +1043,18 @@ static Datum PLySequence_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyObject } tuple = heap_form_tuple(desc, values, nulls); - ReleaseTupleDesc(desc); + result = heap_copy_tuple_as_datum(tuple, desc); + heap_freetuple(tuple); + pfree(values); pfree(nulls); - return HeapTupleGetDatum(tuple); + return result; } static Datum PLyGenericObject_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyObject* object) { + Datum result; HeapTuple tuple; Datum* values = NULL; bool* nulls = NULL; @@ -1113,26 +1111,11 @@ static Datum PLyGenericObject_ToComposite(PLyTypeInfo* info, TupleDesc desc, PyO } tuple = heap_form_tuple(desc, values, nulls); - ReleaseTupleDesc(desc); + result = heap_copy_tuple_as_datum(tuple, desc); + heap_freetuple(tuple); + pfree(values); pfree(nulls); - return HeapTupleGetDatum(tuple); -} - -/* - * This routine is a crock, and so is everyplace that calls it. The problem - * is that the cached form of plpython functions/queries is allocated permanently - * (mostly via malloc()) and never released until backend exit. Subsidiary - * data structures such as fmgr info records therefore must live forever - * as well. A better implementation would store all this stuff in a per- - * function memory context that could be reclaimed at need. In the meantime, - * fmgr_info_cxt must be called specifying - * THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_CBB) so that whatever - * it might allocate, and whatever the eventual function might allocate using - * fn_mcxt, will live forever too. - */ -static void perm_fmgr_info(Oid functionId, FmgrInfo* finfo) -{ - fmgr_info_cxt(functionId, finfo, THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_CBB)); + return result; } diff --git a/src/common/pl/plpython/plpy_typeio.h b/src/common/pl/plpython/plpy_typeio.h index 6093bb99d7..ecc7523b52 100644 --- a/src/common/pl/plpython/plpy_typeio.h +++ b/src/common/pl/plpython/plpy_typeio.h @@ -78,10 +78,12 @@ typedef struct PLyTypeInfo { Oid typ_relid; TransactionId typrel_xmin; ItemPointerData typrel_tid; + + /* context for subsidiary data (doesn't belong to this struct though) */ + MemoryContext mcxt; } PLyTypeInfo; -extern void PLy_typeinfo_init(PLyTypeInfo* arg); -extern void PLy_typeinfo_dealloc(PLyTypeInfo* arg); +extern void PLy_typeinfo_init(PLyTypeInfo* arg, MemoryContext mcxt); extern void PLy_input_datum_func(PLyTypeInfo* arg, Oid typeOid, HeapTuple typeTup); extern void PLy_output_datum_func(PLyTypeInfo* arg, HeapTuple typeTup); @@ -95,6 +97,6 @@ extern void PLy_output_record_funcs(PLyTypeInfo* arg, TupleDesc desc); extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo* info, TupleDesc desc, PyObject* plrv); /* conversion from heap tuples to Python dictionaries */ -extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc, bool include_generated); +extern PyObject* PLyDict_FromTuple(PLyTypeInfo* info, HeapTuple tuple, TupleDesc desc, bool include_generated); #endif /* PLPY_TYPEIO_H */ diff --git a/src/common/pl/plpython/plpy_util.cpp b/src/common/pl/plpython/plpy_util.cpp index eb205fd5ac..3bd8d35db1 100644 --- a/src/common/pl/plpython/plpy_util.cpp +++ b/src/common/pl/plpython/plpy_util.cpp @@ -17,43 +17,6 @@ #include "plpy_elog.h" -void* PLy_malloc(size_t bytes) -{ - /* We need our allocations to be long-lived, so use t_thrd.top_mem_cxt */ - return MemoryContextAlloc(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_CBB), bytes); -} - -void* PLy_malloc0(size_t bytes) -{ - void* ptr = PLy_malloc(bytes); - - if (bytes > 0) { - errno_t rc = memset_s(ptr, bytes, 0, bytes); - securec_check(rc, "\0", "\0"); - } - return ptr; -} - -char* PLy_strdup(const char* str) -{ - char* result = NULL; - size_t len; - errno_t rc = EOK; - - len = strlen(str) + 1; - result = (char*)PLy_malloc(len); - rc = memcpy_s(result, len, str, len); - securec_check(rc, "\0", "\0"); - - return result; -} - -/* define this away */ -void PLy_free(void* ptr) -{ - pfree(ptr); -} - /* * Convert a Python unicode object to a Python string/bytes object in * openGauss server encoding. Reference ownership is passed to the diff --git a/src/common/pl/plpython/plpython3u--1.0.sql b/src/common/pl/plpython/plpython3u--1.0.sql index cd1fb636a0..f763567504 100644 --- a/src/common/pl/plpython/plpython3u--1.0.sql +++ b/src/common/pl/plpython/plpython3u--1.0.sql @@ -9,3 +9,3395 @@ CREATE PROCEDURAL LANGUAGE plpython3u; COMMENT ON PROCEDURAL LANGUAGE plpython3u IS 'PL/Python3U untrusted procedural language'; + +--" +--gms_xmldom +create schema gms_xmldom; +GRANT USAGE ON SCHEMA gms_xmldom TO public; + +create domain gms_xmldom.DOMType as text; +create type gms_xmldom.DOMNode as (id text); +create type gms_xmldom.DOMNamedNodeMap as (id text); +create type gms_xmldom.DOMNodeList as (id text); +create type gms_xmldom.DOMAttr as (id text); +create type gms_xmldom.DOMCDataSection as (id text); +create type gms_xmldom.DOMCharacterData as (id text); +create type gms_xmldom.DOMComment as (id text); +create type gms_xmldom.DOMDocumentFragment as (id text); +create type gms_xmldom.DOMElement as (id text); +create type gms_xmldom.DOMEntity as (id text); +create type gms_xmldom.DOMNotation as (id text); +create type gms_xmldom.DOMProcessingInstruction as (id text); +create type gms_xmldom.DOMText as (id text); +create type gms_xmldom.DOMImplementation as (id text); +create type gms_xmldom.DOMDocumentType as (id text); +create type gms_xmldom.DOMDocument as (id text); + +--gms_xmldom.makeNode +create function gms_xmldom.makeNode(t gms_xmldom.DOMText) +returns gms_xmldom.DOMNode +as $$ + return t +$$ language plpython3u package; +create function gms_xmldom.makeNode(com gms_xmldom.DOMComment) +returns gms_xmldom.DOMNode +as $$ + return com +$$ language plpython3u package; +create function gms_xmldom.makeNode(cds gms_xmldom.DOMCDATASection) +returns gms_xmldom.DOMNode +as $$ + return cds +$$ language plpython3u package; +create function gms_xmldom.makeNode(dt gms_xmldom.DOMDocumentType) +returns gms_xmldom.DOMNode +as $$ + return dt +$$ language plpython3u package; +create function gms_xmldom.makeNode(n gms_xmldom.DOMNotation) +returns gms_xmldom.DOMNode +as $$ + return n +$$ language plpython3u package; +create function gms_xmldom.makeNode(ent gms_xmldom.DOMEntity) +returns gms_xmldom.DOMNode +as $$ + return ent +$$ language plpython3u package; +create function gms_xmldom.makeNode(pi gms_xmldom.DOMProcessingInstruction) +returns gms_xmldom.DOMNode +as $$ + return pi +$$ language plpython3u package; +create function gms_xmldom.makeNode(df gms_xmldom.DOMDocumentFragment) +returns gms_xmldom.DOMNode +as $$ + return df +$$ language plpython3u package; +create function gms_xmldom.makeNode(doc gms_xmldom.DOMDocument) +returns gms_xmldom.DOMNode +as $$ + return doc +$$ language plpython3u package; +create function gms_xmldom.makeNode(elem gms_xmldom.DOMElement) +returns gms_xmldom.DOMNode +as $$ + return elem +$$ language plpython3u package; + +--gms_xmldom.isNull(n DOMNode) +create function gms_xmldom.isNull(n gms_xmldom.DOMNode) +returns boolean +as $$ + if n["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(di DOMImplementation) +create function gms_xmldom.isNull(di gms_xmldom.DOMImplementation) +returns boolean +as $$ + if di["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(nl DOMNodeList) +create function gms_xmldom.isNull(nl gms_xmldom.DOMNodeList) +returns boolean +as $$ + if nl["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(nnm DOMNamedNodeMap) +create function gms_xmldom.isNull(nnm gms_xmldom.DOMNamedNodeMap) +returns boolean +as $$ + if nnm["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(cd DOMCharacterData) +create function gms_xmldom.isNull(cd gms_xmldom.DOMCharacterData) +returns boolean +as $$ + if cd["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(a DOMAttr) +create function gms_xmldom.isNull(a gms_xmldom.DOMAttr) +returns boolean +as $$ + if a["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(elem DOMElement) +create function gms_xmldom.isNull(elem gms_xmldom.DOMElement) +returns boolean +as $$ + if elem["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(t DOMText) +create function gms_xmldom.isNull(t gms_xmldom.DOMText) +returns boolean +as $$ + if t["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(com DOMComment) +create function gms_xmldom.isNull(com gms_xmldom.DOMComment) +returns boolean +as $$ + if com["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(cds DOMCDATASection) +create function gms_xmldom.isNull(cds gms_xmldom.DOMCDATASection) +returns boolean +as $$ + if cds["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(dt DOMDocumentType) +create function gms_xmldom.isNull(dt gms_xmldom.DOMDocumentType) +returns boolean +as $$ + if dt["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(n DOMNotation) +create function gms_xmldom.isNull(n gms_xmldom.DOMNotation) +returns boolean +as $$ + if n["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(ent DOMEntity) +create function gms_xmldom.isNull(ent gms_xmldom.DOMEntity) +returns boolean +as $$ + if ent["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(pi DOMProcessingInstruction) +create function gms_xmldom.isNull(pi gms_xmldom.DOMProcessingInstruction) +returns boolean +as $$ + if pi["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(df DOMDocumentFragment) +create function gms_xmldom.isNull(df gms_xmldom.DOMDocumentFragment) +returns boolean +as $$ + if df["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; +--gms_xmldom.isNull(doc DOMDocument) +create function gms_xmldom.isNull(doc gms_xmldom.DOMDocument) +returns boolean +as $$ + if doc["id"] not in GD: + return True + else: + return False +$$ language plpython3u package; + +--gms_xmldom.freeNode(n DOMnode) +create function gms_xmldom.freeNode(n gms_xmldom.DOMnode) +returns void +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return + domNode = GD[n["id"]] + stack = [domNode] + while stack: + root = stack.pop() + tmpid = str(id(root)) + if tmpid in GD: + GD.pop(tmpid) + for child in root.childNodes: + stack.append(child) + domNode.unlink() +$$ language plpython3u package; + +--gms_xmldom.freeNodeList(nl DOMNodeList) +create function gms_xmldom.freeNodeList(nl gms_xmldom.DOMNodeList) +returns void +as $$ + from xml.dom import minidom + if nl["id"] not in GD or not GD[nl["id"]]: + return + nodeList = GD[nl["id"]] + for node in nodeList: + strid = str(id(node)) + if strid in GD: + GD.pop(strid) + node.unlink() + GD.pop(nl["id"]) +$$ language plpython3u package; +--gms_xmldom.freeDocument +create function gms_xmldom.freeDocument(doc gms_xmldom.DOMDocument) +returns void +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + return + docNode = GD[doc["id"]] + stack = [docNode] + while stack: + root = stack.pop() + tmpid = str(id(root)) + if tmpid in GD: + GD.pop(tmpid) + for child in root.childNodes: + stack.append(child) + docNode.unlink() +$$ language plpython3u package; + +--gms_xmldom.getFirstChild(n DOMNode) +create function gms_xmldom.getFirstChild(n gms_xmldom.DOMNode) +returns gms_xmldom.DOMNode +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return [""] + domNode = GD[n["id"]] + if domNode.hasChildNodes == False: + return [""] + childNode = domNode.firstChild + resid = str(id(childNode)) + if resid not in GD: + GD[resid] = childNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.getLocalName +create function gms_xmldom.getLocalName(a gms_xmldom.DOMAttr) +returns varchar2 +as $$ + from xml.dom import minidom + if a["id"] not in GD or not GD[a["id"]]: + return "" + attrNode = GD[a["id"]] + return attrNode.localName +$$ language plpython3u package; +create function gms_xmldom.getLocalName(elem gms_xmldom.DOMElement) +returns varchar2 +as $$ + from xml.dom import minidom + if elem["id"] not in GD or not GD[elem["id"]]: + return "" + attrNode = GD[elem["id"]] + return attrNode.localName +$$ language plpython3u package; +create function gms_xmldom.getLocalName(n gms_xmldom.DOMnode, data OUT VARCHAR2) +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return "" + attrNode = GD[n["id"]] + data = attrNode.localName + return data +$$ language plpython3u package; + +--gms_xmldom.getNodeType +create function gms_xmldom.getNodeType(n gms_xmldom.DOMNode) +returns INTEGER +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return None + domNode = GD[n["id"]] + return domNode.nodeType +$$ language plpython3u package; + +--gms_xmldom.internal_writexml(DOMNode, VARCHAR2) +create function gms_xmldom.internal_writexml(n gms_xmldom.DOMNode, dbencoding VARCHAR2) +returns clob +as $$ + from xml.dom import minidom + from xml.dom import Node + if n["id"] not in GD or not GD[n["id"]]: + return "" + domNode = GD[n["id"]] + cl = '' + #If the document node needs to be written in the specified character set + if domNode.nodeType == Node.DOCUMENT_NODE: + #If a character set is specified, the specified character set is used. + #If no character set is specified, the database character set is used + tarEncoding = dbencoding + if domNode.encoding != None: + tarEncoding = domNode.encoding + cl = domNode.toprettyxml(indent=" ", newl="\n", encoding=tarEncoding) + cl = cl.decode(encoding=tarEncoding) + if domNode.version != None and domNode.version != "1.0": + versionInfo = "version=\"" + domNode.version + "\"" + cl = cl.replace("version=\"1.0\"", versionInfo) + else: + cl = domNode.toprettyxml(indent=" ") + return cl +$$ language plpython3u package; +--gms_xmldom.internal_writexml(DOMDocument, VARCHAR2) +create function gms_xmldom.internal_writexml(doc gms_xmldom.DOMDocument, dbencoding varchar2) +returns clob +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + return "" + docNode = GD[doc["id"]] + #If a character set is specified, the specified character set is used. + #If no character set is specified, the database character set is used + tarEncoding = dbencoding + if docNode.encoding != None: + tarEncoding = docNode.encoding + cl = docNode.toprettyxml(indent=" ", newl="\n", encoding=tarEncoding) + cl = cl.decode(encoding=tarEncoding) + #modify version + if docNode.version != None and docNode.version != "1.0": + versionInfo = "version=\"" + docNode.version + "\"" + cl = cl.replace("version=\"1.0\"", versionInfo) + return cl +$$ language plpython3u package; +--gms_xmldom.internal_writexml(DOMNode, number, number, varchar2) +create function gms_xmldom.internal_writexml( + n gms_xmldom.DOMNode, + pflag IN NUMBER, + indent IN NUMBER, + dbencoding varchar2) +returns clob +as $$ + from xml.dom import minidom + from xml.dom import Node + iPflag = int(pflag) + iIndent = int(indent) + if iPflag < 0 or iPflag > 72 or iIndent < 0 or iIndent > 12: + raise plpy.Error("The specified printing option is invalid") + if n["id"] not in GD or not GD[n["id"]]: + return "" + domNode = GD[n["id"]] + + indentstr = "" + newl = "\n" + + lowPflag = iPflag & 7 + # According to Oracle, a low three digit of 4 or 5 indicates no line break + if lowPflag == 4 or lowPflag == 5: + newl = "" + else: + i = 0 + while i < iIndent: + indentstr += " " + i += 1 + cl = '' + if domNode.nodeType == Node.DOCUMENT_NODE: + tarEncoding = dbencoding + if domNode.encoding != None: + tarEncoding = domNode.encoding + cl = domNode.toprettyxml(indentstr, newl, tarEncoding) + cl = cl.decode(encoding=tarEncoding) + if domNode.version != None and domNode.version != "1.0": + versionInfo = "version=\"" + domNode.version + "\"" + cl = cl.replace("version=\"1.0\"", versionInfo) + else: + cl = domNode.toprettyxml(indentstr, newl) + return cl +$$ language plpython3u package; +--gms_xmldom.internal_writexml(DOMDocument, number, number, varchar2) +create function gms_xmldom.internal_writexml( + doc gms_xmldom.DOMDocument, + pflag IN NUMBER, + indent IN NUMBER, + dbencoding varchar2) +returns clob +as $$ + from xml.dom import minidom + iPflag = int(pflag) + iIndent = int(indent) + if iPflag < 0 or iPflag > 72 or iIndent < 0 or iIndent > 12: + raise plpy.Error("The specified printing option is invalid") + if doc["id"] not in GD or not GD[doc["id"]]: + return "" + docNode = GD[doc["id"]] + + indentstr = "" + newl = "\n" + + lowPflag = iPflag & 7 + # According to Oracle, a low three digit of 4 or 5 indicates no line break + if lowPflag == 4 or lowPflag == 5: + newl = "" + else: + i = 0 + while i < iIndent: + indentstr += " " + i += 1 + tarEncoding = dbencoding + if docNode.encoding != None: + tarEncoding = docNode.encoding + cl = docNode.toprettyxml(indentstr, newl, tarEncoding) + cl = cl.decode(encoding=tarEncoding) + if docNode.version != None and docNode.version != "1.0": + versionInfo = "version=\"" + docNode.version + "\"" + cl = cl.replace("version=\"1.0\"", versionInfo) + return cl +$$ language plpython3u package; +--gms_xmldom.writeToClob(DOMNode, clob) +create function gms_xmldom.writeToClob(n gms_xmldom.DOMNode, cl IN OUT CLOB) +as $$ +declare + dbencoding varchar2; +begin + show server_encoding into dbencoding; + cl := gms_xmldom.internal_writexml(n, dbencoding); + return cl; +end; +$$ language plpgsql package; +--gms_xmldom.writeToClob(DOMNode, clob, number, number) +create function gms_xmldom.writeToClob(n gms_xmldom.DOMNode, cl IN OUT CLOB, pflag IN NUMBER, indent IN NUMBER) +as $$ +declare + dbencoding varchar2; +begin + show server_encoding into dbencoding; + cl := gms_xmldom.internal_writexml(n, pflag, indent, dbencoding); + return cl; +end; +$$ language plpgsql package; +--gms_xmldom.writeToClob(DOMDocument, clob) +create function gms_xmldom.writeToClob(doc gms_xmldom.DOMDocument, cl IN OUT CLOB) +as $$ +declare + dbencoding varchar2; +begin + show server_encoding into dbencoding; + cl := gms_xmldom.internal_writexml(doc, dbencoding); + return cl; +end; +$$ language plpgsql package; +--gms_xmldom.writeToClob(DOMDocument, clob, number, number) +create function gms_xmldom.writeToClob(doc gms_xmldom.DOMDocument, cl IN OUT CLOB, pflag IN NUMBER, indent IN NUMBER) +as $$ +declare + dbencoding varchar2; +begin + show server_encoding into dbencoding; + cl := gms_xmldom.internal_writexml(doc, pflag, indent, dbencoding); + return cl; +end; +$$ language plpgsql package; + +--gms_xmldom.writeToBuffer(DOMNode, VARCHAR2) +create function gms_xmldom.writeToBuffer(n gms_xmldom.DOMNode, buffer IN OUT VARCHAR2) +as $$ +declare + db_encoding varchar2; +begin + show server_encoding into db_encoding; + buffer := gms_xmldom.internal_writexml(n, db_encoding); + return buffer; +end; +$$ language plpgsql package; +--gms_xmldom.writeToBuffer(DOMDocument, VARCHAR2) +create function gms_xmldom.writeToBuffer(doc gms_xmldom.DOMDocument, buffer IN OUT VARCHAR2) +as $$ +declare + db_encoding varchar2; +begin + show server_encoding into db_encoding; + buffer := gms_xmldom.internal_writexml(doc, db_encoding); + return buffer; +end; +$$ language plpgsql package; +--gms_xmldom.writeToBuffer(DOMDocumentFragment, VARCHAR2) +create function gms_xmldom.writeToBuffer(df gms_xmldom.DOMDocumentFragment, buffer IN OUT VARCHAR2) +as $$ + import os + from xml.dom import minidom + import io + if df["id"] not in GD or not GD[df["id"]]: + return "" + dfNode = GD[df["id"]] + writer = io.StringIO() + for node in dfNode.childNodes: + node.writexml(writer, " ", "", "") + buffer = writer.getvalue() + return buffer +$$ language plpython3u package; + +--gms_xmldom.getChildNodes +create function gms_xmldom.getChildNodes(n gms_xmldom.DOMNode) +returns gms_xmldom.DOMNodeList +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return [""] + domNode = GD[n["id"]] + childNodes = domNode.childNodes + resid = str(id(childNodes)) + if resid not in GD: + GD[resid] = childNodes + return [resid] +$$ language plpython3u package; + +--gms_xmldom.getLength +create function gms_xmldom.getLength(nl gms_xmldom.DOMNodeList) +returns integer +as $$ + from xml.dom import minidom + if nl["id"] not in GD or not GD[nl["id"]]: + return 0 + nodeList = GD[nl["id"]] + return nodeList.length +$$ language plpython3u package; +create function gms_xmldom.getLength(nnm gms_xmldom.DOMNamedNodeMap) +returns integer +as $$ + from xml.dom import minidom + if nnm["id"] not in GD or not GD[nnm["id"]]: + return 0 + nnmNode = GD[nnm["id"]] + return nnmNode.length +$$ language plpython3u package; +create function gms_xmldom.getLength(cd gms_xmldom.DOMCharacterData) +returns integer +as $$ + from xml.dom import minidom + if cd["id"] not in GD or not GD[cd["id"]]: + return 0 + cdNode = GD[cd["id"]] + return cdNode.length +$$ language plpython3u package; + +--gms_xmldom.item +create function gms_xmldom.item(nl gms_xmldom.DOMNodeList, idx IN INTEGER) +returns gms_xmldom.DOMNode +as $$ + from xml.dom import minidom + if nl["id"] not in GD or not GD[nl["id"]]: + return [""] + nodeList = GD[nl["id"]] + domNode = nodeList.item(idx) + if domNode == None: + return [""] + resid = str(id(domNode)) + if resid not in GD: + GD[resid] = domNode + return [resid] +$$ language plpython3u package; +create function gms_xmldom.item(nnm gms_xmldom.DOMNamedNodeMap, idx IN INTEGER) +returns gms_xmldom.DOMNode +as $$ + from xml.dom import minidom + if nnm["id"] not in GD or not GD[nnm["id"]]: + return [""] + nnmNode = GD[nnm["id"]] + domNode = nnmNode.item(idx) + if domNode == None: + return [""] + resid = str(id(domNode)) + if resid not in GD: + GD[resid] = domNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.makeElement +create function gms_xmldom.makeElement(n gms_xmldom.DOMNode) +returns gms_xmldom.DOMElement +as $$ + from xml.dom import minidom + from xml.dom import Node + if n["id"] not in GD or not GD[n["id"]]: + return [""] + domNode = GD[n["id"]] + if domNode.nodeType == Node.ELEMENT_NODE: + return n + else: + nodeName = "" + if domNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE: + nodeName = "DocumentFragment" + elif domNode.nodeType == Node.ATTRIBUTE_NODE: + nodeName = "Attribute" + elif domNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE: + nodeName = "ProcessingInstruction" + elif domNode.nodeType == Node.TEXT_NODE: + nodeName = "Text" + elif domNode.nodeType == Node.COMMENT_NODE: + nodeName = "Comment" + elif domNode.nodeType == Node.CDATA_SECTION_NODE: + nodeName = "CDATASection" + elif domNode.nodeType == Node.DOCUMENT_TYPE_NODE: + nodeName = "DocumentType" + elif domNode.nodeType == Node.ENTITY_NODE: + nodeName = "Entity" + elif domNode.nodeType == Node.NOTATION_NODE: + nodeName = "Notation" + elif domNode.nodeType == Node.DOCUMENT_NODE: + nodeName = "Document" + raise plpy.Error("Node type %s cannot be converted to the target type" %nodeName) +$$ language plpython3u package; + +--gms_xmldom.getElementsByTagName +create function gms_xmldom.getElementsByTagName(elem gms_xmldom.DOMElement, name IN VARCHAR2) +returns gms_xmldom.DOMNodeList +as $$ + from xml.dom import minidom + if elem["id"] not in GD or not GD[elem["id"]]: + return [""] + elemNode = GD[elem["id"]] + nlist = elemNode.getElementsByTagName(name) + resid = str(id(nlist)) + if resid not in GD: + GD[resid] = nlist + return [resid] +$$ language plpython3u package; +create function gms_xmldom.getElementsByTagName(elem gms_xmldom.DOMElement, name IN VARCHAR2, ns varchar2) +returns gms_xmldom.DOMNodeList +as $$ + from xml.dom import minidom + if elem["id"] not in GD or not GD[elem["id"]]: + return [""] + elemNode = GD[elem["id"]] + nlist = elemNode.getElementsByTagNameNS(ns, name) + resid = str(id(nlist)) + if resid not in GD: + GD[resid] = nlist + return [resid] +$$ language plpython3u package; +create function gms_xmldom.getElementsByTagName(doc gms_xmldom.DOMDocument, tagname IN VARCHAR2) +returns gms_xmldom.DOMNodeList +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + return [""] + docNode = GD[doc["id"]] + nlist = docNode.getElementsByTagName(tagname) + resid = str(id(nlist)) + if resid not in GD: + GD[resid] = nlist + return [resid] +$$ language plpython3u package; + +--gms_xmldom.cloneNode +create function gms_xmldom.cloneNode(n gms_xmldom.DOMNode, deep boolean) +returns gms_xmldom.DOMNode +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return [""] + domNode = GD[n["id"]] + clnNode = domNode.cloneNode(deep) + resid = str(id(clnNode)) + if resid not in GD: + GD[resid] = clnNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.getNodeName +create function gms_xmldom.getNodeName(n gms_xmldom.DOMNode) +returns VARCHAR2 +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return "" + domNode = GD[n["id"]] + return domNode.nodeName +$$ language plpython3u package; + +--gms_xmldom.createDocument +create function gms_xmldom.createDocument( + namespaceuri IN VARCHAR2, + qualifiedname IN VARCHAR2, + doctype IN gms_xmldom.DOMType:= NULL) +returns gms_xmldom.DOMDocument +as $$ + from xml.dom import minidom + strdoctype = None + if doctype != None and doctype in GD: + strdoctype = GD[doctype] + domNode = minidom.getDOMImplementation() + docNode = domNode.createDocument(namespaceuri, qualifiedname, strdoctype) + resid = str(id(docNode)) + if resid not in GD: + GD[resid] = docNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.createElement +create function gms_xmldom.createElement(doc gms_xmldom.DOMDocument, tagname IN VARCHAR2) +returns gms_xmldom.DOMElement +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + elemNode = docNode.createElement(tagname) + resid = str(id(elemNode)) + if resid not in GD: + GD[resid] = elemNode + return [resid] +$$ language plpython3u package; +create function gms_xmldom.createElement(doc gms_xmldom.DOMDocument, tagname IN VARCHAR2, ns IN VARCHAR2) +returns gms_xmldom.DOMElement +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + elemNode = docNode.createElementNS(ns, tagname) + resid = str(id(elemNode)) + if resid not in GD: + GD[resid] = elemNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.createDocumentFragment +create function gms_xmldom.createDocumentFragment(doc gms_xmldom.DOMDocument) +returns gms_xmldom.DOMDocumentFragment +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + dfNode = docNode.createDocumentFragment() + resid = str(id(dfNode)) + if resid not in GD: + GD[resid] = dfNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.createTextNode +create function gms_xmldom.createTextNode(doc gms_xmldom.DOMDocument, data IN VARCHAR2) +returns gms_xmldom.DOMText +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + txtNode = docNode.createTextNode(data) + resid = str(id(txtNode)) + if resid not in GD: + GD[resid] = txtNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.createComment +create function gms_xmldom.createComment(doc gms_xmldom.DOMDocument, data IN VARCHAR2) +returns gms_xmldom.DOMComment +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + comNode = docNode.createComment(data) + resid = str(id(comNode)) + if resid not in GD: + GD[resid] = comNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.createCDATASection +create function gms_xmldom.createCDATASection(doc gms_xmldom.DOMDocument, data IN VARCHAR2) +returns gms_xmldom.DOMCDATASection +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + cdsNode = docNode.createCDATASection(data) + resid = str(id(cdsNode)) + if resid not in GD: + GD[resid] = cdsNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.createProcessingInstruction +create function gms_xmldom.createProcessingInstruction(doc gms_xmldom.DOMDocument, target IN VARCHAR2, data IN VARCHAR2) +returns gms_xmldom.DOMProcessingInstruction +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + piNode = docNode.createProcessingInstruction(target, data) + resid = str(id(piNode)) + if resid not in GD: + GD[resid] = piNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.createAttribute +create function gms_xmldom.createAttribute(doc gms_xmldom.DOMDocument, name IN VARCHAR2) +returns gms_xmldom.DOMAttr +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + attrNode = docNode.createAttribute(name) + resid = str(id(attrNode)) + if resid not in GD: + GD[resid] = attrNode + return [resid] +$$ language plpython3u package; +create function gms_xmldom.createAttribute(doc gms_xmldom.DOMDocument, name IN VARCHAR2, ns IN VARCHAR2) +returns gms_xmldom.DOMAttr +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + attrNode = docNode.createAttributeNS(ns, name) + resid = str(id(attrNode)) + if resid not in GD: + GD[resid] = attrNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.appendChild +create function gms_xmldom.appendChild(n gms_xmldom.DOMNode, newchild IN gms_xmldom.DOMNode) +returns gms_xmldom.DOMNode +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + raise plpy.Error("DOMNode is empty or invalid") + domNode = GD[n["id"]] + if newchild["id"] not in GD or not GD[newchild["id"]]: + raise plpy.Error("DOMNode is empty or invalid") + newNode = GD[newchild["id"]] + resNode = domNode.appendChild(newNode) + resid = str(id(resNode)) + if resid not in GD: + GD[resid] = resNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.getDocumentElement +create function gms_xmldom.getDocumentElement(doc gms_xmldom.DOMDocument) +returns gms_xmldom.DOMElement +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + return [""] + docNode = GD[doc["id"]] + elemNode = docNode.documentElement + resid = str(id(elemNode)) + if resid not in GD: + GD[resid] = elemNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.setAttribute +create function gms_xmldom.setAttribute(elem gms_xmldom.DOMElement, name IN VARCHAR2, newvalue IN VARCHAR2) +returns void +as $$ + from xml.dom import minidom + if elem["id"] not in GD or not GD[elem["id"]]: + return + elemNode = GD[elem["id"]] + elemNode.setAttribute(name, newvalue) +$$ language plpython3u package; +create function gms_xmldom.setAttribute(elem gms_xmldom.DOMElement, name IN VARCHAR2, newvalue IN VARCHAR2, ns IN VARCHAR2) +returns void +as $$ + from xml.dom import minidom + if elem["id"] not in GD or not GD[elem["id"]]: + return + elemNode = GD[elem["id"]] + elemNode.setAttributeNS(ns, name, newvalue) +$$ language plpython3u package; + +--gms_xmldom.setAttributeNode +create function gms_xmldom.setAttributeNode(elem gms_xmldom.DOMElement, newattr IN gms_xmldom.DOMAttr) +returns gms_xmldom.DOMAttr +as $$ + from xml.dom import minidom + if elem["id"] not in GD or not GD[elem["id"]]: + raise plpy.Error("DOMElement is empty or invalid") + elemNode = GD[elem["id"]] + if newattr["id"] not in GD or not GD[newattr["id"]]: + raise plpy.Error("DOMAttr is empty or invalid") + attrNode = GD[newattr["id"]] + resAttrNode = elemNode.setAttributeNode(attrNode) + resid = str(id(resAttrNode)) + if resid not in GD: + GD[resid] = resAttrNode + return [resid] +$$ language plpython3u package; +create function gms_xmldom.setAttributeNode(elem gms_xmldom.DOMElement, newattr IN gms_xmldom.DOMAttr, ns IN VARCHAR2) +returns gms_xmldom.DOMAttr +as $$ + from xml.dom import minidom + if elem["id"] not in GD or not GD[elem["id"]]: + raise plpy.Error("DOMElement is empty or invalid") + elemNode = GD[elem["id"]] + if newattr["id"] not in GD or not GD[newattr["id"]]: + raise plpy.Error("DOMAttr is empty or invalid") + attrNode = GD[newattr["id"]] + attrNode.namespaceURI = ns + resAttrNode = elemNode.setAttributeNode(attrNode) + resid = str(id(resAttrNode)) + if resid not in GD: + GD[resid] = resAttrNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.getAttributes +create function gms_xmldom.getAttributes(n gms_xmldom.DOMNode) +returns gms_xmldom.DOMNamedNodeMap +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return [""] + domNode = GD[n["id"]] + nnmNode = domNode.attributes + resid = str(id(nnmNode)) + if resid not in GD: + GD[resid] = nnmNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.getNodeValue +create function gms_xmldom.getNodeValue(n gms_xmldom.DOMNode) +returns varchar2 +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return "" + domNode = GD[n["id"]] + return domNode.nodeValue +$$ language plpython3u package; +create function gms_xmldom.getNodeValueAsClob(n gms_xmldom.domnode) +returns clob +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return "" + domNode = GD[n["id"]] + return domNode.nodeValue +$$ language plpython3u package; + +--gms_xmldom.getChildrenByTagName +create function gms_xmldom.getChildrenByTagName(elem gms_xmldom.DOMElement, name varchar2) +returns gms_xmldom.DOMNodeList +as $$ + from xml.dom import minidom + from xml.dom.minicompat import NodeList + from xml.dom import Node + if elem["id"] not in GD or not GD[elem["id"]]: + return [""] + elemNode = GD[elem["id"]] + nodeList = NodeList() + for node in elemNode.childNodes: + if node.nodeType == Node.ELEMENT_NODE: + if name == "*" or name == node.tagName: + nodeList.append(node) + resid = str(id(nodeList)) + if resid not in GD: + GD[resid] = nodeList + return [resid] +$$ language plpython3u package; +create function gms_xmldom.getChildrenByTagName(elem gms_xmldom.DOMElement, name varchar2, ns varchar2) +returns gms_xmldom.DOMNodeList +as $$ + from xml.dom import minidom + from xml.dom.minicompat import NodeList + from xml.dom import Node + if elem["id"] not in GD or not GD[elem["id"]]: + return [""] + elemNode = GD[elem["id"]] + nodeList = NodeList() + for node in elemNode.childNodes: + if node.nodeType == Node.ELEMENT_NODE: + if(name == "*" or name == node.tagName) and (ns == "*" or node.namespaceURI == ns): + nodeList.append(node) + resid = str(id(nodeList)) + if resid not in GD: + GD[resid] = nodeList + return [resid] +$$ language plpython3u package; + +--gms_xmldom.getOwnerDocument +create function gms_xmldom.getOwnerDocument(n gms_xmldom.DOMNode) +returns gms_xmldom.DOMDocument +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return [""] + dom = GD[n["id"]] + document = dom.ownerDocument + resid = str(id(document)) + if resid not in GD: + GD[resid] = document + return [resid] +$$ language plpython3u package; +--gms_xmldom.newDOMDocument +create function gms_xmldom.newDOMDocument() +returns gms_xmldom.DOMDocument +as $$ + from xml.dom import minidom + docNode = minidom.Document() + resid = str(id(docNode)) + if resid not in GD: + GD[resid] = docNode + return [resid] +$$ language plpython3u package; +create function gms_xmldom.newDOMDocument(xmldoc IN xml) +returns gms_xmldom.DOMDocument +as $$ + from xml.dom import minidom + import os + import io + #Remove extra Spaces and newlines from the string + strxml = '' + buffer = io.StringIO(xmldoc) + lines = buffer.readlines() + for line in lines: + tmpLine = line.replace('\n', '') + tmpLine = tmpLine.strip() + if tmpLine == '': + continue + strxml += tmpLine + docNode = minidom.parseString(strxml) + resid = str(id(docNode)) + if resid not in GD: + GD[resid] = docNode + return [resid] +$$ language plpython3u package; +create function gms_xmldom.newDOMDocument(xmldoc clob) +returns gms_xmldom.DOMDocument +as $$ + from xml.dom import minidom + import io + #Remove extra Spaces and newlines from the string + strxml = '' + buffer = io.StringIO(xmldoc) + lines = buffer.readlines() + for line in lines: + tmpLine = line.replace('\n', '') + tmpLine = tmpLine.strip() + if tmpLine == '': + continue + strxml += tmpLine + docNode = minidom.parseString(strxml) + resid = str(id(docNode)) + if resid not in GD: + GD[resid] = docNode + return [resid] +$$ language plpython3u package; +create function gms_xmldom.newDOMDocument(filename text) +returns gms_xmldom.DOMDocument +as $$ + from xml.dom import minidom + #Remove extra Spaces and newlines from the file + strxml = '' + with open(filename, 'r') as fp: + lines = fp.readlines() + for line in lines: + tmpLine = line.replace('\n', '') + tmpLine = tmpLine.strip() + if tmpLine == '': + continue + strxml += tmpLine + docNode = minidom.parseString(strxml) + resid = str(id(docNode)) + if resid not in GD: + GD[resid] = docNode + return [resid] +$$ language plpython3u package; + +--gms_xmldom.hasChildNodes +create function gms_xmldom.hasChildNodes(n gms_xmldom.DOMNode) +returns boolean +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + return False + domNode = GD[n["id"]] + return domNode.hasChildNodes() +$$ language plpython3u package; + +--gms_xmldom.insertBefore +create function gms_xmldom.insertBefore( + n gms_xmldom.DOMNode, + newchild IN gms_xmldom.DOMNode, + refchild IN gms_xmldom.DOMNode) +returns gms_xmldom.DOMNode +as $$ + from xml.dom import minidom + if n["id"] not in GD or not GD[n["id"]]: + raise plpy.Error("DOMNode is empty or invalid") + domNode = GD[n["id"]] + if newchild["id"] not in GD or not GD[newchild["id"]]: + raise plpy.Error("newChild is empty or invalid") + newDom = GD[newchild["id"]] + if refchild["id"] not in GD or not GD[refchild["id"]]: + raise plpy.Error("refChild is empty or invalid") + refDom = GD[refchild["id"]] + res = domNode.childNodes.index(refDom) + newDom = domNode.insertBefore(newDom, refDom) + resid = str(id(newDom)) + if resid not in GD: + GD[resid] = newDom + return [resid] +$$ language plpython3u package; + +create function gms_xmldom.setVersion(doc gms_xmldom.DOMDocument, version VARCHAR2) +returns void +as $$ + from xml.dom import minidom + if doc["id"] not in GD or not GD[doc["id"]]: + raise plpy.Error("DOMDocument is empty or invalid") + docNode = GD[doc["id"]] + docNode.version = version +$$ language plpython3u package; + +create function gms_xmldom.ELEMENT_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.ELEMENT_NODE +$$language plpython3u; +create function gms_xmldom.ATTRIBUTE_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.ATTRIBUTE_NODE +$$language plpython3u; +create function gms_xmldom.TEXT_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.TEXT_NODE +$$language plpython3u; +create function gms_xmldom.CDATA_SECTION_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.CDATA_SECTION_NODE +$$language plpython3u; +create function gms_xmldom.ENTITY_REFERENCE_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.ENTITY_REFERENCE_NODE +$$language plpython3u; +create function gms_xmldom.ENTITY_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.ENTITY_NODE +$$language plpython3u; +create function gms_xmldom.PROCESSING_INSTRUCTION_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.PROCESSING_INSTRUCTION_NODE +$$language plpython3u; +create function gms_xmldom.COMMENT_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.COMMENT_NODE +$$language plpython3u; +create function gms_xmldom.DOCUMENT_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.DOCUMENT_NODE +$$language plpython3u; +create function gms_xmldom.DOCUMENT_TYPE_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.DOCUMENT_TYPE_NODE +$$language plpython3u; +create function gms_xmldom.DOCUMENT_FRAGMENT_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.DOCUMENT_FRAGMENT_NODE +$$language plpython3u; +create function gms_xmldom.NOTATION_NODE() returns INTEGER +as $$ + from xml.dom import Node + return Node.NOTATION_NODE +$$language plpython3u; + +create function gms_xmldom.makeCharacterData(n gms_xmldom.DOMNode) +returns gms_xmldom.DOMCharacterData +as $$ + if n["id"] not in GD or not GD[n["id"]]: + return "" + return [n["id"]] +$$ language plpython3u package; + +--gms_xmlparser +create schema gms_xmlparser; +GRANT USAGE ON SCHEMA gms_xmlparser TO public; +create type gms_xmlparser.Parser as (id text); + +create function gms_xmlparser.newParser() returns gms_xmlparser.Parser +as $$ + from xml.dom.expatbuilder import ExpatBuilder + builder = ExpatBuilder() + resid = str(id(builder)) + if resid not in GD: + GD[resid] = builder + return [resid] +$$language plpython3u package; + +create function gms_xmlparser.getDocument(p gms_xmlparser.Parser) +returns gms_xmldom.DOMDocument +as $$ + from xml.dom.expatbuilder import ExpatBuilder + if p["id"] not in GD or not GD[p["id"]]: + return [""] + builder = GD[p["id"]] + docNode = builder.document + resid = str(id(docNode)) + if resid not in GD: + GD[resid] = docNode + return [resid] +$$language plpython3u package; + +create function gms_xmlparser.parseClob(p gms_xmlparser.Parser, doc CLOB) +returns void +as $$ + from xml.dom.expatbuilder import ExpatBuilder + import io + if p["id"] not in GD or not GD[p["id"]]: + return + builder = GD[p["id"]] + #Remove extra Spaces and newlines from the string + strxml = '' + buffer = io.StringIO(doc) + lines = buffer.readlines() + for line in lines: + tmpLine = line.replace('\n', '') + tmpLine = tmpLine.strip() + if tmpLine == '': + continue + strxml += tmpLine + docNode = builder.parseString(strxml) + builder.document = docNode +$$language plpython3u package; + +create function gms_xmlparser.parseBuffer(p gms_xmlparser.Parser,doc VARCHAR2) +returns void +as $$ + from xml.dom.expatbuilder import ExpatBuilder + import io + if p["id"] not in GD or not GD[p["id"]]: + return + builder = GD[p["id"]] + #Remove extra Spaces and newlines from the string + strxml = '' + buffer = io.StringIO(doc) + lines = buffer.readlines() + for line in lines: + tmpLine = line.replace('\n', '') + tmpLine = tmpLine.strip() + if tmpLine == '': + continue + strxml += tmpLine + docNode = builder.parseString(strxml) + builder.document = docNode +$$language plpython3u package; + +create or replace function gms_xmlparser.getDirPathByDirectory(dir VARCHAR2) +returns VARCHAR2 +as $$ +declare + canread boolean; + dirpath varchar2; +begin + canread := has_directory_privilege(current_user, dir, 'READ'); + if canread = true then + select p.dirpath into dirpath from pg_directory p where p.dirname = dir; + if dirpath = NULL then + raise exception 'directory "%" does not exist', dir; + end if; + else + raise exception 'You do not have privileges for the directory'; + end if; + return dirpath; +end; +$$language plpgsql; + +create or replace function gms_xmlparser.setbasedir(p gms_xmlparser.Parser, dir VARCHAR2) +returns void +as $$ + if p is None or p["id"] is None or dir is None: + return + keystr = p["id"] + 'dir' + GD[keystr] = dir +$$language plpython3u package; + +create or replace function gms_xmlparser.splitDirAndFilename( + p gms_xmlparser.Parser, + filepath VARCHAR2, + dir out VARCHAR2, + xmlfile out VARCHAR2) +returns setof record +as $$ + import os + head_tail = os.path.split(filepath) + if head_tail[0] != '': + dir = head_tail[0] + xmlfile = head_tail[1] + else: + if p != None: + keystr = p["id"] + 'dir' + if keystr not in GD: + raise plpy.error('Invalid resource handle or path name "%s"' %head_tail[1]) + dir = GD[keystr] + xmlfile = head_tail[1] + else: + raise plpy.error('Invalid resource handle or path name "%s"' %head_tail[1]) + return [(dir, xmlfile)] +$$language plpython3u package; + +create or replace function gms_xmlparser.parseFileWithParser(p gms_xmlparser.Parser, file VARCHAR2) +returns void +as $$ + from xml.dom.expatbuilder import ExpatBuilder + if p["id"] not in GD or not GD[p["id"]]: + return + builder = GD[p["id"]] + #Remove extra Spaces and newlines from the file + strxml = '' + with open(file, 'r') as fp: + lines = fp.readlines() + for line in lines: + tmpLine = line.replace('\n', '') + tmpLine = tmpLine.strip() + if tmpLine == '': + continue + strxml += tmpLine + docNode = builder.parseString(strxml) + builder.document = docNode +$$language plpython3u package; + +create or replace function gms_xmlparser.parse(p gms_xmlparser.Parser, file VARCHAR2) +returns void +as $$ +declare + dirname VARCHAR2; + filename VARCHAR2; + filepath VARCHAR2; +begin + if p is null or file is null then + raise exception 'input arguments cannot be null'; + end if; + select dir, xmlfile into dirname, filename from gms_xmlparser.splitDirAndFilename(p, file); + filepath := gms_xmlparser.getDirPathByDirectory(dirname); + gms_xmlparser.parseFileWithParser(p, filepath || '/' || filename); +end; +$$language plpgsql package; + +create or replace function gms_xmlparser.parseFile(file VARCHAR2) +returns gms_xmldom.DOMDocument +as $$ + from xml.dom import expatbuilder + #Remove extra Spaces and newlines from the file + strxml = '' + with open(file, 'r') as fp: + lines = fp.readlines() + for line in lines: + tmpLine = line.replace('\n', '') + tmpLine = tmpLine.strip() + if tmpLine == '': + continue + strxml += tmpLine + docNode = expatbuilder.parseString(strxml) + resid = str(id(docNode)) + if resid not in GD: + GD[resid] = docNode + return [resid] +$$language plpython3u package; + +create or replace function gms_xmlparser.parse(file VARCHAR2) +returns gms_xmldom.DOMDocument +as $$ +declare + dirname VARCHAR2; + filename VARCHAR2; + filepath VARCHAR2; + doc gms_xmldom.DOMDocument; +begin + if file is null then + raise exception 'input arguments cannot be null'; + end if; + select dir, xmlfile into dirname, filename from gms_xmlparser.splitDirAndFilename(null, file); + filepath := gms_xmlparser.getDirPathByDirectory(dirname); + doc := gms_xmlparser.parseFile(filepath || '/' || filename); + return doc; +end; +$$language plpgsql package; + +create or replace function gms_xmlparser.freeparser(p gms_xmlparser.Parser) +returns void +as $$ + from xml.dom.expatbuilder import ExpatBuilder + if p is None or p["id"] is None or p["id"] not in GD: + return + builder = GD[p["id"]] + del builder + GD.pop(p["id"]) +$$language plpython3u package; + +create schema gms_xslprocessor; +GRANT USAGE ON SCHEMA gms_xslprocessor TO public; + +create or replace function gms_xslprocessor.valueofbuffer( + xmldata VARCHAR2, + pattern VARCHAR2, + namespace VARCHAR2) +returns VARCHAR2 +as $$ + import xml.etree.ElementTree as ET + global pattern + initpattern = pattern + root = ET.fromstring(xmldata) + #remove'/text()' + pattern = pattern.replace('/text()', '') + if pattern[len(pattern) - 1] == '/': + plpy.error("XML parse failed. Invalid token in: '%s'"%initpattern) + if pattern[0] == '/': + return '' + value = root.findtext(pattern, namespace) + root.clear() + del root + return value +$$language plpython3u package; + +create or replace function gms_xslprocessor.valueof( + n gms_XMLDOM.DOMNODE, + pattern VARCHAR2, + val OUT VARCHAR2, + namespace VARCHAR2 default NULL) +as $$ +declare + xmlbuf VARCHAR2; + compatibility char(1); +begin + select datcompatibility into compatibility from pg_database where datname=current_database(); + if compatibility != 'A' THEN + raise exception 'gms_xslprocessor package only supported in database which dbcompatibility=''A''.'; + end if; + if n is null then + return ''; + end if; + if pattern is null then + raise exception 'XPath compilation failed: Xpath is null'; + end if; + gms_xmldom.writeToBuffer(n, xmlbuf); + val := gms_xslprocessor.valueofbuffer(xmlbuf, pattern, namespace); +end; +$$language plpgsql package; + +create or replace function gms_xslprocessor.selectnodesbuffer( + xmldata VARCHAR2, + xpath_expr VARCHAR2, + namespace VARCHAR2 default NULL) +returns gms_xmldom.DOMNodeList +as $$ + import xml.dom.minidom as dom + from xml.dom.minicompat import NodeList + doc = dom.parseString(xmldata) + root = doc.documentElement + if namespace: + nodes = root.getElementsByTagNameNS(namespace, xpath_expr) + else: + nodes = root.getElementsByTagName(xpath_expr) + + nodelist = NodeList() + for node in nodes: + nodelist.append(node) + + resid = str(id(nodelist)) + if resid not in GD: + GD[resid] = nodelist + return [resid] +$$language plpython3u package; + +create or replace function gms_xslprocessor.selectnodes( + n gms_XMLDOM.DOMNODE, + pattern VARCHAR2, + namespace VARCHAR2 default NULL) +return gms_xmldom.DOMNodeList +as +declare + xmlbuf VARCHAR2; + res gms_xmldom.DOMNodeList; + compatibility char(1); +begin + select datcompatibility into compatibility from pg_database where datname=current_database(); + if compatibility != 'A' THEN + raise exception 'gms_xslprocessor package only supported in database which dbcompatibility=''A''.'; + end if; + if n is null then + return NULL; + end if; + if pattern is null then + raise exception 'XPath compilation failed: Xpath is null'; + end if; + gms_xmldom.writeToBuffer(n, xmlbuf); + res := gms_xslprocessor.selectnodesbuffer(xmlbuf,pattern,namespace); + return res; +end; +; + +set search_path to public; +create function generate_oracle_package_sql() +returns text +as $c_sql$ +c_sql = ''' +/* " + * ----------------------------------- utl_encode --------------------------------------- + */ +CREATE SCHEMA utl_encode; +GRANT USAGE ON SCHEMA utl_encode TO public; + +/* base64_encode internal fun*/ +CREATE OR REPLACE FUNCTION utl_encode.__BASE64_ENCODE( + b IN BYTEA +) RETURNS BYTEA AS $$ + +if 'base64' in SD: + base64 = SD['base64'] +else: + import base64 + SD['base64'] = base64 + +res = base64.b64encode(b) + +return res +END; +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION utl_encode.BASE64_ENCODE( + r IN RAW +) RETURNS RAW AS $$ +DECLARE + b BYTEA; + res RAW; +BEGIN + --check parameter + if r is NULL then + raise exception USING ERRCODE = 'P0019', message = 'bad argument'; + end if; + + b := utl_encode.__BASE64_ENCODE(rawtobytea(r)); + res := byteatoraw(b); + return res; +END; +$$ LANGUAGE PLPGSQL; + +/* base64_decode internal fun*/ +CREATE OR REPLACE FUNCTION utl_encode.__BASE64_DECODE( + b IN BYTEA +) RETURNS BYTEA AS $$ + +if 'base64' in SD: + base64 = SD['base64'] +else: + import base64 + SD['base64'] = base64 + +try: + #-- b'===' is used for "binascii.Error: Incorrect padding" + res = base64.b64decode(b + b'===') +except: + #-- for "binascii.Error: Invalid base64-encoded string" + buf = b.rstrip(b'=')[:-1] + res = base64.b64decode(buf + b'===') + +return res +END; +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION utl_encode.BASE64_DECODE( + r IN RAW +) RETURNS RAW AS $$ +DECLARE + b BYTEA; + res RAW; +BEGIN + --check parameter + if r is NULL then + raise exception USING ERRCODE = 'P0019', message = 'bad argument'; + end if; + + b := utl_encode.__BASE64_DECODE(rawtobytea(r)); + res := byteatoraw(b); + return res; +END; +$$ LANGUAGE PLPGSQL; + +/* + * mimeheader_encode internal fun + * return '=????=' + */ +CREATE OR REPLACE FUNCTION utl_encode.__MIMEHEADER_ENCODE( + buf IN VARCHAR2, + encode_charset IN VARCHAR2, + encoding IN INTEGER, + res_buf OUT VARCHAR2, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2 +) RETURNS SETOF RECORD AS $$ +#--import +if 'base64' in SD: + base64 = SD['base64'] +else: + import base64 + SD['base64'] = base64 + +if 'quopri' in SD: + quopri = SD['quopri'] +else: + import quopri + SD['quopri'] = quopri + +#--check parameter +if encoding not in [1,2]: + return [(None, 'P0019', 'bad argument')] +#--not support character set +try: + 'a'.encode(encode_charset) +except LookupError: + return [(None, 'P0020', 'invalid Character set')] +if ('utf-16' == encode_charset or 'utf16' == encode_charset or 'UTF-16' == encode_charset + or 'UTF16' == encode_charset or 'utf-32' == encode_charset or 'utf32' == encode_charset + or 'UTF-32' == encode_charset or 'UTF32' == encode_charset): + return [(None, 'P0020', 'invalid Character set')] + +#--base64 +if 1 == encoding: + encoded_text = base64.b64encode(buf.encode(encode_charset)); + encoding_str = 'B' +#--quoted_printable +else: + encoded_text = quopri.encodestring(buf.encode(encode_charset)); + encoding_str = 'Q' +#--Concatenate the resulting string +res = '=?' + str(encode_charset) + '?' + encoding_str + '?' + str(encoded_text)[2:-1] + '?=' +return [(res, '0', '0')] +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION utl_encode.MIMEHEADER_ENCODE( + buf IN VARCHAR2, + encode_charset IN VARCHAR2 DEFAULT NULL, + encoding IN INTEGER DEFAULT NULL +) RETURNS VARCHAR2 AS $$ +DECLARE + type read_type is record( + res VARCHAR2, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; +BEGIN + --check parameter + if buf is NULL then + raise exception USING ERRCODE = 'P0019', message = 'bad argument'; + end if; + --get db charset + if encode_charset is NULL then + show server_encoding into encode_charset; + end if; + -- null is quoted_printable + if encoding is NULL then + encoding = 2; + end if; + read_type_res := utl_encode.__MIMEHEADER_ENCODE(buf, encode_charset, encoding); + if read_type_res.errcode != '0' then + raise exception USING ERRCODE = read_type_res.errcode, message = read_type_res.errmsg; + end if; + return read_type_res.res; +END; +$$ LANGUAGE PLPGSQL; + + +/* quoted_printable_decode internal fun */ +CREATE OR REPLACE FUNCTION utl_encode.__QUOTED_PRINTABLE_DECODE( + b IN BYTEA +) RETURNS BYTEA AS $$ + +if 'quopri' in SD: + quopri = SD['quopri'] +else: + import quopri + SD['quopri'] = quopri + +res = quopri.decodestring(b) +return res +END; +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION utl_encode.QUOTED_PRINTABLE_DECODE( + r IN RAW +) RETURNS RAW AS $$ +DECLARE + b BYTEA; + res RAW; +BEGIN + --check parameter + if r is NULL then + raise exception USING ERRCODE = 'P0019', message = 'bad argument'; + end if; + b := utl_encode.__QUOTED_PRINTABLE_DECODE(rawtobytea(r)); + res := byteatoraw(b); + return res; +END; +$$ LANGUAGE PLPGSQL; + + +/* text_decode internal fun*/ +CREATE OR REPLACE FUNCTION utl_encode.__TEXT_DECODE( + buf IN VARCHAR2, + encode_charset IN VARCHAR2, + encoding IN INTEGER, + res_buf OUT VARCHAR2, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2 +) RETURNS SETOF RECORD AS $$ + +if 'base64' in SD: + base64 = SD['base64'] +else: + import base64 + SD['base64'] = base64 + +if 'quopri' in SD: + quopri = SD['quopri'] +else: + import quopri + SD['quopri'] = quopri + +#--check parameter +if encoding not in [1,2]: + return [(None, 'P0019', 'bad argument')] +#--not support character set +try: + 'a'.encode(encode_charset) +except LookupError: + return [(None, 'P0020', 'invalid Character set')] +if ('utf-16' == encode_charset or 'utf16' == encode_charset or 'UTF-16' == encode_charset + or 'UTF16' == encode_charset or 'utf-32' == encode_charset or 'utf32' == encode_charset + or 'UTF-32' == encode_charset or 'UTF32' == encode_charset): + return [(None, 'P0020', 'invalid Character set')] + +#--base64 +if 1 == encoding: + b_buf = buf.encode('utf8') + try: + #-- b'===' is used for "binascii.Error: Incorrect padding" + res = base64.b64decode(b_buf + b'===') + except: + #-- for "binascii.Error: Invalid base64-encoded string" + b = b_buf.rstrip(b'=')[:-1] + res = base64.b64decode(b + b'===') + res = res.decode(encode_charset, errors = 'replace'); +#--quoted_printable +else: + #--str to bytes + b_buf = buf.encode('utf8') + res = quopri.decodestring(b_buf).decode(encode_charset, errors = 'replace'); +return [(res, '0', '0')] +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION utl_encode.TEXT_DECODE( + buf IN VARCHAR2, + encode_charset IN VARCHAR2 DEFAULT NULL, + encoding IN INTEGER DEFAULT NULL +) RETURNS VARCHAR2 AS $$ +DECLARE + type read_type is record( + res VARCHAR2, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; +BEGIN + --check parameter + if buf is NULL then + raise exception USING ERRCODE = 'P0019', message = 'bad argument'; + end if; + --get db charset + if encode_charset is NULL then + show server_encoding into encode_charset; + end if; + -- null is quoted_printable + if encoding is NULL then + encoding = 2; + end if; + read_type_res := utl_encode.__TEXT_DECODE(buf, encode_charset, encoding); + if read_type_res.errcode != '0' then + raise exception USING ERRCODE = read_type_res.errcode, message = read_type_res.errmsg; + end if; + return read_type_res.res; +END; +$$ LANGUAGE PLPGSQL; + +/* uudecode internal fun*/ +CREATE OR REPLACE FUNCTION utl_encode.__UUDECODE( + r IN BYTEA, + b_res OUT BYTEA, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2 +) RETURNS SETOF RECORD AS $$ + +if 'binascii' in SD: + binascii = SD['binascii'] +else: + import binascii + SD['binascii'] = binascii + +if 'BytesIO' in SD: + BytesIO = SD['BytesIO'] +else: + from io import BytesIO + SD['BytesIO'] = BytesIO + +buf = BytesIO(r) +res = b"" +while 1: + s = buf.readline() + if not s or s == b'end\\n' or s == b'end': + break + if b'begin' in s: + continue + #--skip the blank line + if b'\\n' == s: + continue + try: + data = binascii.a2b_uu(s) + except binascii.Error: + #--Workaround for broken uuencoders by /Fredrik Lundh + nbytes = (((s[0]-32) & 63) * 4 + 5) // 3 + try: + data = binascii.a2b_uu(s[:nbytes]) + except: + if res.__len__() == 0: + return [(None, 'P0021', 'invalid encoded string')] + else: + break + res = res + data +#--need to clear the terminator +res = res.replace(b'\\x00', b'') +return [(res, '0', '0')] +END; +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION utl_encode.UUDECODE( + r IN RAW +) RETURNS RAW AS $$ +DECLARE + type read_type is record( + res BYTEA, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + res RAW; +BEGIN + --check parameter + if r is NULL then + raise exception USING ERRCODE = 'P0019', message = 'bad argument'; + end if; + read_type_res := utl_encode.__UUDECODE(rawtobytea(r)); + if read_type_res.errcode != '0' then + raise exception USING ERRCODE = read_type_res.errcode, message = read_type_res.errmsg; + end if; + res := byteatoraw(read_type_res.res); + return res; +END; +$$ LANGUAGE PLPGSQL; + +/* + * ----------------------------------- utl_tcp ------------------------------------------ + * --create package needs to be public + */ +set search_path to public; +CREATE OR REPLACE PACKAGE UTL_TCP +AS + TYPE connection IS RECORD( + remote_host VARCHAR2(255), + remote_port INTEGER, + local_host VARCHAR2(255), + local_port INTEGER, + charset VARCHAR2(30), + newline VARCHAR2(2), + tx_timeout INTEGER, + private_sd INTEGER + ); + + CRLF CONSTANT VARCHAR2(2) := E'\\r\\n'; + + FUNCTION OPEN_CONNECTION (remote_host IN VARCHAR2, + remote_port IN INTEGER, + local_host IN VARCHAR2 DEFAULT NULL, + local_port IN INTEGER DEFAULT NULL, + in_buffer_size IN INTEGER DEFAULT NULL, + out_buffer_size IN INTEGER DEFAULT NULL, + charset IN VARCHAR2 DEFAULT NULL, + newline IN VARCHAR2 DEFAULT E'\\r\\n', + tx_timeout IN INTEGER DEFAULT NULL) RETURN connection; + FUNCTION CLOSE_CONNECTION (c IN OUT connection) RETURN connection; + FUNCTION CLOSE_ALL_CONNECTIONS RETURN VOID; + FUNCTION AVAILABLE ( + c IN connection, + timeout IN INTEGER DEFAULT 0) RETURN INTEGER; + --raw + FUNCTION READ_RAW (c IN OUT connection, + data IN OUT RAW, + data_len OUT INTEGER, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE + ) RETURN SETOF RECORD; + FUNCTION WRITE_RAW (c IN connection, + data IN RAW, + len IN INTEGER DEFAULT NULL) RETURN INTEGER; + FUNCTION GET_RAW (c IN connection, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE) RETURN RAW; + --text + FUNCTION READ_TEXT (c IN OUT connection, + data IN OUT VARCHAR2, + data_len OUT INTEGER, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE + ) RETURN SETOF RECORD; + FUNCTION WRITE_TEXT (c IN connection, + data IN VARCHAR2, + len IN INTEGER DEFAULT NULL) RETURN INTEGER; + FUNCTION GET_TEXT (c IN connection, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE) RETURN VARCHAR2; + --line + FUNCTION READ_LINE (c IN OUT connection, + data IN OUT VARCHAR2, + data_len OUT INTEGER, + remove_crlf IN BOOLEAN DEFAULT FALSE, + peek IN BOOLEAN DEFAULT FALSE + ) RETURN SETOF RECORD; + FUNCTION WRITE_LINE (c IN connection, + data IN VARCHAR2 DEFAULT NULL) RETURN INTEGER; + FUNCTION GET_LINE (c IN connection, + remove_crlf IN BOOLEAN DEFAULT FALSE, + peek IN BOOLEAN DEFAULT FALSE) RETURN VARCHAR2; + FUNCTION FLUSH (c IN OUT connection) RETURN connection; +END UTL_TCP; +; +---------------------------------------- +--utl_tcp internal function +---------------------------------------- +create schema utl_tcp_internal; +GRANT USAGE ON SCHEMA utl_tcp_internal TO public; +set search_path to utl_tcp_internal; +--open_connection +-- io.BufferedReader: +-- When there is data in the buffer, the length is n. +-- Call peek to read data longer than n, the buffer will not be refreshed, +-- only n data can be read +-- _pyio.BufferedReader: +-- Only flush buffers blocking +-- And when buffer_size=0, the SocketIO cannot use peek +-- So implement a BufferReader +CREATE OR REPLACE FUNCTION UTL_TCP__OPEN_CONNECTION (remote_host IN VARCHAR2, + remote_port IN INTEGER, + local_host IN VARCHAR2 DEFAULT NULL, + local_port IN INTEGER DEFAULT NULL, + in_buffer_size IN INTEGER DEFAULT NULL, + out_buffer_size IN INTEGER DEFAULT NULL, + charset IN VARCHAR2 DEFAULT NULL, + newline IN VARCHAR2 DEFAULT E'\\r\\n', + tx_timeout IN INTEGER DEFAULT NULL, + c OUT public.utl_tcp.connection, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2) +RETURNS SETOF RECORD +AS $$ +#--"check parameter" +global in_buffer_size +global out_buffer_size +global newline +global charset + +#--"user don't care about buffer" +if None == in_buffer_size: + in_buffer_size = 4096 +if None == out_buffer_size: + out_buffer_size = 4096 + +#--"set default charset" +if None == charset: + charset = 'utf8' +#--"Python does not support the Oracle character set US7ASCII" +if 'US7ASCII' == charset: + charset = 'GBK' + +if (remote_host == None or remote_port == None or in_buffer_size < 0 + or in_buffer_size > 32767 or out_buffer_size < 0 + or out_buffer_size > 32767): + return [(None, 'P0015', 'bad argument')] +if tx_timeout != None and tx_timeout < 0: + return [(None, 'P0015', 'bad argument')] + +newline = '\\r\\n' + +#--"Actully, oracle's minimun buffer size is 5" +if in_buffer_size > 0 and in_buffer_size < 5: + in_buffer_size = 5 +if out_buffer_size > 0 and out_buffer_size < 5: + out_buffer_size = 5 + +#--"not support character set, mainly because of the bytes of newline" +try: + 'a'.encode(charset) +except LookupError: + return [(None, 'P0015', 'unsupported character set')] +if ('utf-16' == charset or 'utf16' == charset or 'UTF-16' == charset + or 'UTF16' == charset or 'utf-32' == charset or 'utf32' == charset + or 'UTF-32' == charset or 'UTF32' == charset): + return [(None, 'P0015', 'unsupported character set')] + +#--"the maximum number of connections is 15" +if 'utl_tcp_connections' in GD: + if GD['utl_tcp_connections'] >= 15: + return [(None, 'P0017', 'too many open connections')] + else: + GD['utl_tcp_connections'] = GD['utl_tcp_connections'] + 1 +else: + GD['utl_tcp_connections'] = 1 + + +if 'socket' in SD: + socket = SD['socket'] +else: + import socket + SD['socket'] = socket + +#--"to save every connection's socket" +if 'utl_tcp_con' not in GD: + GD['utl_tcp_con'] = {} + +#--"to save buffer" +if 'utl_tcp_inbuffer' not in GD: + GD['utl_tcp_inbuffer'] = {} + +if 'utl_tcp_outbuffer' not in GD: + GD['utl_tcp_outbuffer'] = {} + +#--"every connection's id" +if 'utl_tcp_sd_num' in GD: + private_sd = GD['utl_tcp_sd_num'] + 1 + GD['utl_tcp_sd_num'] = private_sd +else: + GD['utl_tcp_sd_num'] = 0 + private_sd = 0 + +socket_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + +#--"bind loacl host and port" +if local_host != None and local_port != None: + socket_fd.bind((local_host, local_port)) + +socket_fd.settimeout(tx_timeout) +try: + socket_fd.connect((remote_host, remote_port)) +except socket.gaierror: + return [(None, 'P0014', 'network error: Connect failed because target host or object does not exist')] +except socket.error: + return [(None, 'P0014', 'network error: TNS:no listener')] + +#--"set buffer, 0 mean not used" +outbuffer = socket_fd.makefile('wb', buffering = out_buffer_size) + +GD['utl_tcp_inbuffer'][private_sd] = {} +GD['utl_tcp_inbuffer'][private_sd]['buf'] = b"" +GD['utl_tcp_inbuffer'][private_sd]['size'] = in_buffer_size +GD['utl_tcp_inbuffer'][private_sd]['pos'] = 0 + +GD['utl_tcp_outbuffer'][private_sd] = {} +GD['utl_tcp_outbuffer'][private_sd]['buf'] = outbuffer +GD['utl_tcp_outbuffer'][private_sd]['size'] = out_buffer_size + +GD['utl_tcp_con'][private_sd] = socket_fd + +return [([remote_host, remote_port, local_host, local_port, charset, newline, + tx_timeout, private_sd], '0', '0')] +$$ LANGUAGE plpython3u; + +--close +CREATE OR REPLACE FUNCTION UTL_TCP__CLOSE_CONNECTION (c IN OUT public.utl_tcp.connection) +AS $$ +if 'socket' in SD: + socket = SD['socket'] +else: + import socket; + SD['socket'] = socket + +private_sd = c['private_sd'] +#--"clear buffer" +if 'utl_tcp_inbuffer' in GD and private_sd in GD['utl_tcp_inbuffer']: + del GD['utl_tcp_inbuffer'][private_sd]['buf'] + del GD['utl_tcp_inbuffer'][private_sd]['size'] + del GD['utl_tcp_inbuffer'][private_sd]['pos'] + del GD['utl_tcp_inbuffer'][private_sd] + +if 'utl_tcp_outbuffer' in GD and private_sd in GD['utl_tcp_outbuffer']: + GD['utl_tcp_outbuffer'][private_sd]['buf'].flush() + GD['utl_tcp_outbuffer'][private_sd]['buf'].__del__() + del GD['utl_tcp_outbuffer'][private_sd]['buf'] + del GD['utl_tcp_outbuffer'][private_sd]['size'] + del GD['utl_tcp_outbuffer'][private_sd] + +#--"close socket" +if 'utl_tcp_con' in GD and private_sd in GD['utl_tcp_con']: + socket_fd = GD['utl_tcp_con'][private_sd] + socket_fd.close() + del GD['utl_tcp_con'][private_sd] + +#--"clear connection" +c['remote_host'] = None +c['remote_port'] = None +c['local_host'] = None +c['local_port'] = None +c['charset'] = None +c['newline'] = None +c['tx_timeout'] = None +c['private_sd'] = None + +#--"reduce connection number" +if 'utl_tcp_connections' in GD: + GD['utl_tcp_connections'] = GD['utl_tcp_connections'] - 1 + +$$ LANGUAGE plpython3u; + + +-- close all connection +CREATE OR REPLACE FUNCTION UTL_TCP__CLOSE_ALL_CONNECTIONS +RETURNS VOID +AS $$ +if 'socket' in SD: + socket = SD['socket'] +else: + import socket + SD['socket'] = socket + +#--"clear buffer" +if 'utl_tcp_inbuffer' in GD: + for sd in GD['utl_tcp_inbuffer']: + try: + del GD['utl_tcp_inbuffer'][sd]['buf'] + del GD['utl_tcp_inbuffer'][sd]['size'] + del GD['utl_tcp_inbuffer'][sd]['pos'] + except: + break + del GD['utl_tcp_inbuffer'] + +if 'utl_tcp_outbuffer' in GD: + for sd in GD['utl_tcp_outbuffer']: + try: + GD['utl_tcp_outbuffer'][sd]['buf'].flush() + GD['utl_tcp_outbuffer'][sd]['buf'].__del__() + del GD['utl_tcp_inbuffer'][sd]['buf'] + del GD['utl_tcp_inbuffer'][sd]['size'] + except: + break + del GD['utl_tcp_outbuffer'] + +#--"close socket" +if 'utl_tcp_con' in GD: + for sd in GD['utl_tcp_con']: + GD['utl_tcp_con'][sd].close() + del GD['utl_tcp_con'] + +#--"clear the id of the connection" +if 'utl_tcp_sd_num' in GD: + del GD['utl_tcp_sd_num'] + +#--"clear connection number" +if 'utl_tcp_connections' in GD: + del GD['utl_tcp_connections'] + +return None +$$ LANGUAGE plpython3u; + + +--available +CREATE OR REPLACE FUNCTION UTL_TCP__AVAILABLE ( + c IN public.utl_tcp.connection, + timeout IN INTEGER DEFAULT 0) +RETURNS INTEGER +AS $$ +if 'socket' in SD: + socket = SD['socket'] +else: + import socket; + SD['socket'] = socket + +private_sd = c['private_sd'] +tx_timeout = c['tx_timeout'] +if ('utl_tcp_con' in GD and private_sd in GD['utl_tcp_con'] + and 'utl_tcp_inbuffer' in GD and private_sd in GD['utl_tcp_inbuffer']): + socket_fd = GD['utl_tcp_con'][private_sd] + inbuff = GD['utl_tcp_inbuffer'][private_sd] + + #--"not using buff" + if inbuff['size'] == 0: + socket_fd.settimeout(timeout) + recv_buff = socket_fd.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) + try: + data = socket_fd.recv(recv_buff, socket.MSG_PEEK) + except: + return 0 + finally: + socket_fd.settimeout(tx_timeout) + ava_len = data.__len__() + if ava_len == 0: + ava_len = 1 + return ava_len + + #--"Implement java-like input stream available" + socket_fd.settimeout(timeout) + if inbuff['buf'].__len__() > 0: + socket_fd.settimeout(tx_timeout) + return inbuff['buf'].__len__() + hava = inbuff['buf'].__len__() - inbuff['pos'] + try: + data = socket_fd.recv(inbuff['size'] - hava) + except: + return 0 + finally: + socket_fd.settimeout(tx_timeout) + inbuff['buf'] = inbuff['buf'][inbuff['pos']:] + data + inbuff['pos'] = 0 + ava_len = inbuff['buf'].__len__() + if ava_len == 0: + ava_len = 1 + return ava_len +#--"network error" +else: + return -1 +$$ LANGUAGE plpython3u; + +--read_raw +CREATE OR REPLACE FUNCTION UTL_TCP__READ_RAW (c IN OUT public.utl_tcp.connection, + data IN OUT BYTEA, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE, + data_len OUT INTEGER, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2) +RETURNS SETOF RECORD +AS $$ +#--"check parameter" +global len +if len < 0: + return [(c, None, None, 'P0015', 'bad argument')] +if len == 0: + return [(c, b'', 0, '0', '0')] + +if 'socket' in SD: + socket = SD['socket'] +else: + import socket; + SD['socket'] = socket + +private_sd = c['private_sd'] +if ('utl_tcp_con' in GD and private_sd in GD['utl_tcp_con'] + and 'utl_tcp_inbuffer' in GD and private_sd in GD['utl_tcp_inbuffer']): + socket_fd = GD['utl_tcp_con'][private_sd] + inbuff = GD['utl_tcp_inbuffer'][private_sd] + + hava = inbuff['buf'].__len__() - inbuff['pos'] + want = min(inbuff['size'] - hava, len) + if peek: + #--"not using buff" + if inbuff['size'] == 0: + read_len = 0 + data = b"" + while read_len < len: + try: + data = data + socket_fd.recv(len, socket.MSG_PEEK) + except socket.timeout: + #--timeout, but has data + if data.__len__() != 0: + return [(c, data, data_len, '0', '0')] + else: + return [(c, None, None, 'P0016', 'transfer timeout')] + #--"no more data, server close socket" + if read_len == data.__len__(): + break + read_len = data.__len__() + #--"no data read" + if read_len == 0: + return [(c, None, None, 'P0013', 'end-of-input reached')] + return [(c, data, read_len, '0', '0')] + + #--using buff + if len > inbuff['size']: + return [(c, None, None, 'P0012', 'buffer too small')] + if len > hava: + data_b = b'' + try: + data_b = socket_fd.recv(want) + except socket.timeout: + if hava == 0: + return [(c, None, None, 'P0016', 'transfer timeout')] + else: + data_b = b"" + inbuff['buf'] = inbuff['buf'][inbuff['pos']:] + data_b + inbuff['pos'] = 0 + data = inbuff['buf'][:len] + + else: + #--"not using buff" + if inbuff['size'] == 0: + read_len = 0 + data = b"" + while read_len < len: + try: + data = data + socket_fd.recv(len) + except socket.timeout: + #--timeout, but has data + if data.__len__() != 0: + return [(c, data, data_len, '0', '0')] + else: + return [(c, None, None, 'P0016', 'transfer timeout')] + #--"no more data, server close socket" + if read_len == data.__len__(): + break + read_len = data.__len__() + #--"no data read" + if read_len == 0: + return [(c, None, None, 'P0013', 'end-of-input reached')] + return [(c, data, read_len, '0', '0')] + + #--"using buff, read buffer first" + if hava > 0: + #--"Not enough data in buffer, read from socket" + if len > hava: + data = inbuff['buf'][inbuff['pos']:] + read_len = 0 + need_len = len - hava + data_b = b"" + while read_len < need_len: + #--"wanner to read one more buffer size data" + try: + data_b_tmp = socket_fd.recv(need_len + inbuff['size']) + except socket.timeout: + if data.__len__() == 0: + return [(c, None, None, 'P0016', 'transfer timeout')] + #--"no more data" + if data_b_tmp.__len__() == 0: + len = data.__len__() + inbuff['buf'] = b"" + inbuff['pos'] = 0 + return [(c, data, data.__len__(), '0', '0')] + data = data + data_b_tmp[:len - hava] + data_b = data_b + data_b_tmp + read_len = data_b.__len__() + len = data.__len__() + #--"refresh buffer" + inbuff['buf'] = data_b[len - hava:] + inbuff['pos'] = 0 + else: + #--"read from buffer" + len_tmp = inbuff['pos'] + len + data = inbuff['buf'][inbuff['pos']:len_tmp] + inbuff['pos'] = len_tmp + #--"buffer has no data" + else: + read_len = 0 + need_len = len + data_b = b"" + while read_len < need_len: + try: + data_b = socket_fd.recv(len + inbuff['size']) + except socket.timeout: + if read_len == 0: + return [(c, None, None, 'P0016', 'transfer timeout')] + else: + break + read_len = data_b.__len__() + data = data_b[:len] + len = data.__len__() + #--"refresh buffer" + if data_b.__len__() > len: + inbuff['buf'] = data_b[len:] + else: + inbuff['buf'] = b"" + inbuff['pos'] = 0 + + if data.__len__() == 0: + return [(c, None, None, 'P0013', 'end-of-input reached')] + #--"len is parameter, can not use len(data)" + return [(c, data, data.__len__(), '0', '0')] +else: + return [(c, None, None, 'P0014', 'network error: not connected')] +$$ LANGUAGE plpython3u; + +--write_raw +CREATE OR REPLACE FUNCTION UTL_TCP__WRITE_RAW (c IN public.utl_tcp.connection, + data IN BYTEA, + len IN INTEGER DEFAULT NULL, + data_len OUT INTEGER, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2) +RETURNS SETOF RECORD +AS $$ +#--"check parameter" +global len +if len < 0: + return [(None, 'P0015', 'bad argument')] +if data.__len__() < len: + return [(None, 'P0015', 'bad argument')] + +if 'socket' in SD: + socket = SD['socket'] +else: + import socket; + SD['socket'] = socket + +private_sd = c['private_sd'] +if ('utl_tcp_con' in GD and private_sd in GD['utl_tcp_con'] + and 'utl_tcp_outbuffer' in GD and private_sd in GD['utl_tcp_outbuffer']): + outbuffer = GD['utl_tcp_outbuffer'][private_sd]['buf'] + write_len = outbuffer.write(data[:len]) + + return [(write_len, '0', '0')] +else: + return [(None, 'P0014', 'network error: not connected')] +$$ LANGUAGE plpython3u; + +--read_text +-- Now "len" means that the character length is not the byte length, +-- so it needs to be decoded to get the corresponding length + +-- Read the buffer first, +-- and then read from the socket if the data is insufficient +CREATE OR REPLACE FUNCTION UTL_TCP__READ_TEXT (c IN OUT public.utl_tcp.connection, + data IN OUT VARCHAR2, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE, + data_len OUT INTEGER, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2) +RETURNS SETOF RECORD +AS $$ +#--"check parameter" +global len + +if len < 0: + return [(c, None, None, 'P0015', 'bad argument')] +if len == 0: + return [(c, '', 0, '0', '0')] + +if 'socket' in SD: + socket = SD['socket'] +else: + import socket; + SD['socket'] = socket + +private_sd = c['private_sd'] +charset = c['charset'] +if ('utl_tcp_con' in GD and private_sd in GD['utl_tcp_con'] + and 'utl_tcp_inbuffer' in GD and private_sd in GD['utl_tcp_inbuffer']): + socket_fd = GD['utl_tcp_con'][private_sd] + inbuff = GD['utl_tcp_inbuffer'][private_sd] + + hava = inbuff['buf'].__len__() - inbuff['pos'] + want = inbuff['size'] - hava + if len > 32767: + len = 32767 + if peek: + #--"not using buffer" + if inbuff['size'] == 0: + read_len = 0 + data_b = b"" + #--"want to read enough data" + recv_buff = len * 4 + data_b_tmp = b"" + while read_len < len: + try: + data_b_tmp = socket_fd.recv(recv_buff, socket.MSG_PEEK) + except socket.timeout: + if read_len == 0: + return [(c, None, None, 'P0016', 'transfer timeout')] + else: + data_b_tmp = b"" + #--"no more data" + if data_b_tmp.__len__() == 0: + break + data_b = data_b + data_b_tmp + data = data_b.decode(charset, errors='ignore') + read_len = data.__len__() + + if data_b_tmp.__len__() == recv_buff: + break + #--"no data read" + if data.__len__() == 0: + return [(c, None, None, 'P0013', 'end-of-input reached')] + if read_len > len: + data = data[:len] + read_len = len + return [(c, data, read_len, '0', '0')] + + #--"read from buff" + buff = inbuff['buf'][inbuff['pos']:] + if len > inbuff['size']: + return [(c, None, None, 'P0012', 'buffer too small')] + #--"read buffer first. Len now means character length, decode data" + while True: + try: + data = buff.decode(charset) + break + except UnicodeError: + buff = buff[:-1] + if buff.__len__() == 0: + data = '' + break + #--"no need to reflash buffer" + if data.__len__() >= len: + data = data[:len] + return [(c, data, data.__len__(), '0', '0')] + + #--"reflash buffer" + else: + #--"Prefix decoding error" + prefix_err = False + if data.__len__() == 0 and hava > 2: + prefix_err = True + buff = inbuff['buf'][inbuff['pos']:] + data = buff.decode(charset,errors='ignore') + if data.__len__() >= len: + data = data[:len] + return [(c, data, len, '0', '0')] + + #--"now read data from socket" + data_b = b"" + try: + data_b = socket_fd.recv(want) + except socket.timeout: + if data.__len__() == 0: + return [(c, None, None, 'P0016', 'transfer timeout')] + else: + data_b = b"" + #--"no more data" + if data_b.__len__() == 0: + #--"no data read" + if data.__len__() == 0: + buff = inbuff['buf'][inbuff['pos']:] + if buff.__len__() == 0: + return [(c, None, None, 'P0013', 'end-of-input reached')] + return [(c, None, None, 'P0018', 'partial multibyte character')] + #--"Returns the data previously read from buffer" + else: + len = data.__len__() + return [(c, data, len, '0', '0')] + #--"refresh buffer" + inbuff['buf'] = inbuff['buf'][inbuff['pos']:] + data_b + inbuff['pos'] = 0 + buff = inbuff['buf'] + #--"read from buffer" + while True and not(prefix_err): + try: + data = buff.decode(charset) + break + except UnicodeError: + buff = buff[:-1] + if buff.__len__() == 0: + return [(c, None, None, 'P0013', 'end-of-input reached')] + if prefix_err: + data = buff.decode(charset,errors='ignore') + if data.__len__() >= len: + data = data[:len] + return [(c, data, len, '0', '0')] + else: + return [(c, None, None, 'P0012', 'buffer too small')] + #--"peek = false" + else: + #--"read buff first" + buff = inbuff['buf'][inbuff['pos']:] + longer = True + while True: + try: + data= buff.decode(charset) + break + except UnicodeError: + if buff.__len__() == 0: + break + buff = buff[:-1] + if data.__len__() >= len: + longer = False + data = data[:len] + len = data.__len__() + + #--"update buff read pos" + read_len = data.encode(charset).__len__() + inbuff['pos'] = inbuff['pos'] + read_len + + #--"the data want to read > the data in the buffer" + if longer: + data_tmp = '' + need_len = len + #--"prefix decoding error" + prefix_err = False + if data.__len__() == 0 and hava > 5: + prefix_err = True + #--"the rest of the data can't be decode" + + buff = inbuff['buf'][inbuff['pos']:] + read_len = data.__len__() + want_len = inbuff['size'] + if want_len == 0: + want_len = len + i = 1 + data_b_tmp = buff[:5] + data_b = b'' + while read_len < need_len: + #--"if has prefix decoding error, do not refresh the buffer first" + if not(prefix_err): + #--"Read buffer size data to refresh buffer" + if inbuff['pos'] >= inbuff['buf'].__len__() or buff.__len__() != 0: + try: + data_b = socket_fd.recv(want_len) + except socket.timeout: + if data.__len__() == 0: + return [(c, None, None, 'P0016', 'transfer timeout')] + else: + data_b = b"" + #--"no more data" + if data_b.__len__() == 0: + if data_b_tmp == buff: + break + while True: + try: + data_tmp = data_b_tmp.decode(charset) + data = data + data_tmp + len = data.__len__() + break + except UnicodeError: + data_b_tmp = data_b_tmp[:-1] + if data_b_tmp.__len__() == 0: + len = data.__len__() + break + break + #--"refresh buffer" + inbuff['buf'] = buff + data_b + inbuff['pos'] = 0 + #--"read from buffer" + while True: + #--"deocding data" + try: + #--"get bytes of data from buffer first" + if data_b_tmp.__len__() == 0: + raise UnicodeError + + #--"prefix decoding error" + if data_b_tmp.__len__() == 5: + data_tmp = data_b_tmp.decode(charset, errors = 'ignore') + i = 6 + need_len = need_len + prefix_err = False + #--"normal decode" + else: + data_tmp = data_b_tmp.decode(charset) + + #--"update buffer read pos" + inbuff['pos'] = inbuff['pos'] + i - 1 + data = data + data_tmp + read_len = data.__len__() + i = 1 + #--"enough data has been read" + if read_len == need_len: + len = read_len + return [(c, data, len, '0', '0')] + #--"read_len < need_len, read more data to decode" + else: + raise UnicodeError + except UnicodeError: + #--"the buffer is not enough data and needs to be refreshed" + if inbuff['pos'] + i > inbuff['buf'].__len__(): + buff = data_b_tmp + break + #--"In order to read the exact number of bytes, + #-- read data without flushing the buffer" + data_b_tmp = inbuff['buf'][inbuff['pos']:inbuff['pos'] + i] + i = i + 1 + + + if data.__len__() == 0: + buff = inbuff['buf'][inbuff['pos']:] + if buff.__len__() == 0: + return [(c, None, None, 'P0013', 'end-of-input reached')] + return [(c, None, None, 'P0018', 'partial multibyte character')] + + return [(c, data, len, '0', '0')] +else: + return [(c, None, None, 'P0014', 'network error: not connected')] +$$ LANGUAGE plpython3u; + +--write_text +CREATE OR REPLACE FUNCTION UTL_TCP__WRITE_TEXT (c IN OUT public.utl_tcp.connection, + data IN VARCHAR2, + len IN INTEGER DEFAULT NULL, + w_len OUT INTEGER, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2) +RETURNS SETOF RECORD +AS $$ +global len +global data +#--"check parameter" +if len < 0: + return [(c, None, 'P0015', 'bad argument')] + +if 'socket' in SD: + socket = SD['socket'] +else: + import socket; + SD['socket'] = socket + +private_sd = c['private_sd'] +charset = c['charset'] +if ('utl_tcp_con' in GD and private_sd in GD['utl_tcp_con'] + and 'utl_tcp_outbuffer' in GD and private_sd in GD['utl_tcp_outbuffer']): + if str(type(data)) == "": + data = '' + socket_fd = GD['utl_tcp_con'][private_sd] + outbuffer = GD['utl_tcp_outbuffer'][private_sd]['buf'] + if len > data.__len__(): + return [(c, None, 'P0015', 'bad argument')] + data = data[:len] + data_bytes = data.encode(charset) + write_len = outbuffer.write(data_bytes) + return [(c, len, '0', '0')] +else: + return [(c, None, 'P0014', 'network error: not connected')] + +$$ LANGUAGE plpython3u; + +--read_line +-- read enough data first, and then find the newline +CREATE OR REPLACE FUNCTION UTL_TCP__READ_LINE (c IN OUT public.utl_tcp.connection, + data IN OUT VARCHAR2, + remove_crlf IN BOOLEAN DEFAULT FALSE, + peek IN BOOLEAN DEFAULT FALSE, + data_len OUT INTEGER, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2) +RETURNS SETOF RECORD +AS $$ +global remove_crlf +global peek +if remove_crlf == None: + return [(c, None, None, 'P0015', 'bad argument')] +if peek == None: + return [(c, None, None, 'P0015', 'bad argument')] + +if 'socket' in SD: + socket = SD['socket'] +else: + import socket; + SD['socket'] = socket + +private_sd = c['private_sd'] +charset = c['charset'] +if ('utl_tcp_con' in GD and private_sd in GD['utl_tcp_con'] + and 'utl_tcp_inbuffer' in GD and private_sd in GD['utl_tcp_inbuffer']): + socket_fd = GD['utl_tcp_con'][private_sd] + inbuff = GD['utl_tcp_inbuffer'][private_sd] + + len = 0 + newline_b_crlf = c['newline'].encode(charset) + newline_b_cr = '\\r'.encode(charset) + newline_b_lf = '\\n'.encode(charset) + newline_b = newline_b_crlf + #--"not using buffer" + if inbuff['size'] == 0: + pos = -1 + data_b = b"" + while pos == -1: + #--"want to read enough data" + recv_buff = 32767 * 4 + try: + data_b_tmp = socket_fd.recv(recv_buff, socket.MSG_PEEK) + except socket.timeout: + if data_b.__len__() == 0: + return [(c, None, None, 'P0016', 'transfer timeout')] + else: + data_b_tmp = b"" + data_b = data_b + data_b_tmp + pos = data_b.find(newline_b_crlf) + if pos == -1: + pos = data_b.find(newline_b_cr) + newline_b = newline_b_cr + if pos == -1: + pos = data_b.find(newline_b_lf) + newline_b = newline_b_lf + #--"no more data" + if data_b_tmp.__len__() == 0 or data_b_tmp.__len__() == recv_buff: + break + #--"newline found" + if pos != -1: + pos = pos + newline_b.__len__() + data_b = data_b[:pos] + #--"Intercept the required data" + if not(peek): + socket_fd.recv(data_b.__len__()) + #--"remove crlf" + if pos != -1 and remove_crlf: + data_b = data_b[:-newline_b.__len__()] + + data = data_b.decode(charset, errors='ignore') + len = data.__len__() + + if len == 0: + return [(c, None, None, 'P0013', 'end-of-input reached')] + + return [(c, data, len, '0', '0')] + + #--"find newline in buffer" + read_data = inbuff['buf'][inbuff['pos']:] + pos = read_data.find(newline_b_crlf) + if pos == -1: + pos = read_data.find(newline_b_cr) + newline_b = newline_b_cr + if pos == -1: + pos = read_data.find(newline_b_lf) + newline_b = newline_b_lf + + #--"in oracle 12c, Only buffer-sized data can be read, + #-- otherwise a "numeric or value error" error will be reported. + #-- This is implemented as a "buffer too small" error" + + #--"read all data in buff or need to load data to buff" + while pos == -1: + hava = inbuff['buf'].__len__() - inbuff['pos'] + want = inbuff['size'] - hava + if want > 0: + try: + recv_data = socket_fd.recv(want) + except socket.timeout: + if hava == 0: + return [(c, None, None, 'P0016', 'transfer timeout')] + else: + recv_data = b"" + #--"refresh buffer" + inbuff['buf'] = inbuff['buf'][inbuff['pos']:] + recv_data + inbuff['pos'] = 0 + read_data = inbuff['buf'] + #--"no newlines in buffer" + else: + #--"want to read enough data" + recv_buff = 32767 * 4 + try: + recv_data = socket_fd.recv(recv_buff, socket.MSG_PEEK) + except socket.timeout: + if hava == 0: + return [(c, None, None, 'P0016', 'transfer timeout')] + else: + recv_data = b"" + if recv_data.__len__() != 0: + return [(c, None, None, 'P0012', 'buffer too small')] + + #--"no data can be read" + if read_data.__len__() == 0: + return [(c, None, None, 'P0013', 'end-of-input reached')] + pos = read_data.find(newline_b_crlf) + if pos == -1: + pos = read_data.find(newline_b_cr) + newline_b = newline_b_cr + if pos == -1: + pos = read_data.find(newline_b_lf) + newline_b = newline_b_lf + if peek or recv_data.__len__() == 0: + break + #--"read all data" + if pos == -1: + data = read_data.decode(charset, errors = 'replace') + len = read_data.__len__() + if peek == False: + inbuff['buf'] = b"" + inbuff['pos'] = 0 + return [(c, data, len, '0', '0')] + + if peek == False: + inbuff['pos'] = inbuff['pos'] + pos + newline_b.__len__() + if remove_crlf == False: + pos = pos + newline_b.__len__() + data = read_data[:pos].decode(charset, errors = 'replace') + len = pos + return [(c, data, len, '0', '0')] +else: + return [(c, None, None, 'P0014', 'network error: not connected')] +$$ LANGUAGE plpython3u; + +--write_line +CREATE OR REPLACE FUNCTION UTL_TCP__WRITE_LINE (c IN OUT public.utl_tcp.connection, + data IN VARCHAR2, + w_len OUT INTEGER, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2) +RETURNS SETOF RECORD +AS $$ +global data +if 'socket' in SD: + socket = SD['socket'] +else: + import socket; + SD['socket'] = socket + +private_sd = c['private_sd'] +charset = c['charset'] +newline = c['newline'] +if ('utl_tcp_con' in GD and private_sd in GD['utl_tcp_con'] + and 'utl_tcp_outbuffer' in GD and private_sd in GD['utl_tcp_outbuffer']): + if str(type(data)) == "": + data = '' + outbuffer = GD['utl_tcp_outbuffer'][private_sd]['buf'] + data_tmp = data + newline + data_bytes = data_tmp.encode(charset) + write_len = outbuffer.write(data_bytes) + return [(c, write_len, '0', '0')] +else: + return [(c, None, 'P0014', 'network error: not connected')] + +$$ LANGUAGE plpython3u; + +--flush +CREATE OR REPLACE FUNCTION UTL_TCP__FLUSH (c IN OUT public.utl_tcp.connection, + errcode OUT VARCHAR2, + errmsg OUT VARCHAR2) +RETURNS SETOF RECORD +AS $$ + +private_sd = c['private_sd'] +if ('utl_tcp_con' in GD and private_sd in GD['utl_tcp_con'] + and 'utl_tcp_outbuffer' in GD and private_sd in GD['utl_tcp_outbuffer']): + outbuffer = GD['utl_tcp_outbuffer'][private_sd]['buf'] + outbuffer.flush() + return [(c, '0' ,'0')] +else: + return [(c, 'P0014', 'network error: not connected')] + +$$ LANGUAGE plpython3u; +---------------------------------------- +--create package body utl_tcp +---------------------------------------- +set search_path to public; +CREATE OR REPLACE PACKAGE BODY UTL_TCP +AS + --open_connection + FUNCTION OPEN_CONNECTION (remote_host IN VARCHAR2, + remote_port IN INTEGER, + local_host IN VARCHAR2 DEFAULT NULL, + local_port IN INTEGER DEFAULT NULL, + in_buffer_size IN INTEGER DEFAULT NULL, + out_buffer_size IN INTEGER DEFAULT NULL, + charset IN VARCHAR2 DEFAULT NULL, + newline IN VARCHAR2 DEFAULT E'\\r\\n', + tx_timeout IN INTEGER DEFAULT NULL) RETURN connection AS + DECLARE + type read_type is record( + c public.utl_tcp.connection, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + c public.utl_tcp.connection; + errcode VARCHAR2; + errmsg VARCHAR2; + BEGIN + read_type_res := utl_tcp_internal.UTL_TCP__OPEN_CONNECTION(remote_host, remote_port, local_host, local_port, + in_buffer_size, out_buffer_size, charset, newline, + tx_timeout); + c := read_type_res.c; + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + return c; + END; + --close_connection + FUNCTION CLOSE_CONNECTION (c IN OUT connection) RETURN connection AS + BEGIN + c := utl_tcp_internal.UTL_TCP__CLOSE_CONNECTION(c); + END; + --close_all_connection + FUNCTION CLOSE_ALL_CONNECTIONS RETURN VOID AS + BEGIN + utl_tcp_internal.UTL_TCP__CLOSE_ALL_CONNECTIONS(); + END; + -- available + FUNCTION AVAILABLE (c IN public.utl_tcp.connection, + timeout IN INTEGER DEFAULT 0) RETURN INTEGER AS + DECLARE + data_len INTEGER; + BEGIN + if timeout < 0 then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = 'P0015',message = 'bad argument'; + end if; + data_len := utl_tcp_internal.UTL_TCP__AVAILABLE(c, timeout); + if data_len < 0 then + CLOSE_CONNECTION(c); + raise exception using ERRCODE = 'P0014',message = 'network error: not connected'; + end if; + return data_len; + END; + + -- read_raw + FUNCTION READ_RAW (c IN OUT connection, + data IN OUT RAW, + data_len OUT INTEGER, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE + ) RETURN SETOF RECORD AS + DECLARE + type read_type is record( + c connection, + data BYTEA, + data_len INTEGER, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + BEGIN + read_type_res := utl_tcp_internal.UTL_TCP__READ_RAW(c, read_type_res.data, len, peek); + + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + + c := read_type_res.c; + + data := byteatoraw(read_type_res.data); + data_len := read_type_res.data_len; + return next; + END; + + --write_raw + FUNCTION WRITE_RAW (c IN connection, + data IN RAW, + len IN INTEGER DEFAULT NULL) RETURN INTEGER AS + DECLARE + type read_type is record( + data_len INTEGER, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + data_tmp BYTEA; + BEGIN + data_tmp := rawtobytea(data); + if len is NULL then + len = length(data_tmp); + end if; + read_type_res := utl_tcp_internal.UTL_TCP__WRITE_RAW(c, data_tmp, len); + + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + + return read_type_res.data_len; + END; + + --get_raw + FUNCTION GET_RAW (c IN connection, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE) RETURN RAW AS + DECLARE + type read_type is record( + c connection, + data BYTEA, + data_len INTEGER, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + data RAW; + BEGIN + read_type_res := utl_tcp_internal.UTL_TCP__READ_RAW(c, read_type_res.data, len, peek); + + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + + c := read_type_res.c; + + data := byteatoraw(read_type_res.data); + return data; + END; + + --read_text + FUNCTION READ_TEXT (c IN OUT connection, + data IN OUT VARCHAR2, + data_len OUT INTEGER, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE + ) RETURN SETOF RECORD AS + DECLARE + type read_type is record( + c connection, + data VARCHAR2, + data_len INTEGER, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + BEGIN + read_type_res := utl_tcp_internal.UTL_TCP__READ_TEXT(c, data, len, peek); + + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + + c := read_type_res.c; + data := read_type_res.data; + data_len := read_type_res.data_len; + return NEXT; + END; + + --write_text + FUNCTION WRITE_TEXT (c IN connection, + data IN VARCHAR2, + len IN INTEGER DEFAULT NULL) RETURN INTEGER AS + DECLARE + type read_type is record( + c connection, + data_len INTEGER, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + data_tmp BYTEA; + BEGIN + if len is NULL then + if data is NULL then + len := 0; + else + len := length(data); + end if; + end if; + read_type_res := utl_tcp_internal.UTL_TCP__WRITE_TEXT(c, data, len); + + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + + return read_type_res.data_len; + END; + + --get_text:call utl_tcp__read_text function + FUNCTION GET_TEXT (c IN connection, + len IN INTEGER DEFAULT 1, + peek IN BOOLEAN DEFAULT FALSE) RETURN VARCHAR2 AS + DECLARE + type read_type is record( + c connection, + data VARCHAR2, + data_len INTEGER, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + BEGIN + read_type_res := utl_tcp_internal.UTL_TCP__READ_TEXT(c, read_type_res.data, len, peek); + + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + + return read_type_res.data; + END; + + --read_line + FUNCTION READ_LINE (c IN OUT connection, + data IN OUT VARCHAR2, + data_len OUT INTEGER, + remove_crlf IN BOOLEAN DEFAULT FALSE, + peek IN BOOLEAN DEFAULT FALSE + ) RETURN SETOF RECORD AS + DECLARE + type read_type is record( + c connection, + data VARCHAR2, + data_len INTEGER, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + BEGIN + read_type_res := utl_tcp_internal.UTL_TCP__READ_LINE(c, data, remove_crlf, peek); + + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + + c := read_type_res.c; + data := read_type_res.data; + data_len := read_type_res.data_len; + return NEXT; + END; + + --write_line + FUNCTION WRITE_LINE (c IN connection, + data IN VARCHAR2 DEFAULT NULL) RETURN INTEGER AS + DECLARE + type read_type is record( + c connection, + data_len INTEGER, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + data_tmp BYTEA; + BEGIN + read_type_res := utl_tcp_internal.UTL_TCP__WRITE_LINE(c, data); + + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + + return read_type_res.data_len; + END; + + /*--get_line: call utl_tcp__read_line*/ + FUNCTION GET_LINE (c IN connection, + remove_crlf IN BOOLEAN DEFAULT FALSE, + peek IN BOOLEAN DEFAULT FALSE) RETURN VARCHAR2 AS + DECLARE + type read_type is record( + c connection, + data VARCHAR2, + data_len INTEGER, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + BEGIN + read_type_res := utl_tcp_internal.UTL_TCP__READ_LINE(c, read_type_res.data, remove_crlf, peek); + + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode,message = errmsg; + end if; + + return read_type_res.data; + END; + + --flush + FUNCTION FLUSH (c IN OUT connection) RETURN connection AS + DECLARE + type read_type is record( + c connection, + errcode VARCHAR2, + errmsg VARCHAR2 + ); + read_type_res read_type; + errcode VARCHAR2; + errmsg VARCHAR2; + BEGIN + read_type_res := utl_tcp_internal.utl_tcp__flush(c); + errcode := read_type_res.errcode; + errmsg := read_type_res.errmsg; + if errcode != '0' then + CLOSE_CONNECTION(c); + raise exception USING ERRCODE = errcode, message = errmsg; + end if; + + return c; + END; + +end UTL_TCP; +''' +return c_sql +$c_sql$ language plpython3u; + + +DO LANGUAGE 'plpgsql' +$BODY$ +declare + dbcmpt varchar(10); + c_sql text; +BEGIN + select datcompatibility into dbcmpt from pg_database where datname=current_database(); + if dbcmpt = 'A' then -- if compatibility = 'A', execute oracle package sql file + select generate_oracle_package_sql() into c_sql; + execute c_sql; + end if; +END +$BODY$; + +drop function generate_oracle_package_sql(); +set search_path to default; diff --git a/src/common/pl/plpython/po/zh_CN.po b/src/common/pl/plpython/po/zh_CN.po index 55bafc66f7..58eb98ed43 100644 --- a/src/common/pl/plpython/po/zh_CN.po +++ b/src/common/pl/plpython/po/zh_CN.po @@ -1,14 +1,8 @@ -# LANGUAGE message translation file for plpython -# Copyright (C) 2010 PostgreSQL Global Development Group -# This file is distributed under the same license as the PostgreSQL package. -# FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: PostgreSQL 9.0\n" "Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n" -"POT-Creation-Date: 2013-01-29 13:39+0000\n" -"PO-Revision-Date: 2012-10-19 20:50+0800\n" "Last-Translator: Xiong He \n" "Language-Team: Weibin \n" "Language: zh_CN\n" @@ -18,478 +12,326 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 1.5.4\n" -#: plpy_cursorobject.c:98 #, c-format msgid "plpy.cursor expected a query or a plan" msgstr "plpy.cursor期望一个查询或一个计划" -#: plpy_cursorobject.c:171 #, c-format msgid "plpy.cursor takes a sequence as its second argument" msgstr "plpy.cursor将一个序列作为它的第二个参数" -#: plpy_cursorobject.c:187 plpy_spi.c:222 #, c-format msgid "could not execute plan" msgstr "无法执行计划" -#: plpy_cursorobject.c:190 plpy_spi.c:225 #, c-format msgid "Expected sequence of %d argument, got %d: %s" msgid_plural "Expected sequence of %d arguments, got %d: %s" msgstr[0] "期望%d序列参数,但是得到%d: %s" -# sql_help.h:345 -#: plpy_cursorobject.c:340 #, c-format msgid "iterating a closed cursor" msgstr "遍历一个关闭的游标" -#: plpy_cursorobject.c:348 plpy_cursorobject.c:415 #, c-format msgid "iterating a cursor in an aborted subtransaction" msgstr "在终止的子事务里遍历一个游标" -# sql_help.h:109 -#: plpy_cursorobject.c:407 #, c-format msgid "fetch from a closed cursor" msgstr "从关闭的游标里获取结果" -#: plpy_cursorobject.c:486 #, c-format msgid "closing a cursor in an aborted subtransaction" msgstr "在终止的子事务里关闭一个游标" -#: plpy_elog.c:103 plpy_elog.c:104 plpy_plpymodule.c:420 #, c-format msgid "%s" msgstr "%s" -#: plpy_exec.c:90 #, c-format msgid "unsupported set function return mode" msgstr "不支持集合函数返回模式" -#: plpy_exec.c:91 #, c-format -msgid "" -"PL/Python set-returning functions only support returning only value per call." -msgstr "PL/Python集合返回函数只支持在每次调用时返回值." +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "PL/Pythonset-returning函数只支持在每次调用时返回一个值。" -#: plpy_exec.c:103 #, c-format msgid "returned object cannot be iterated" msgstr "所返回的对象无法迭代" -#: plpy_exec.c:104 #, c-format msgid "PL/Python set-returning functions must return an iterable object." msgstr "PL/Python集合返回函数必须返回一个可迭代的对象." -#: plpy_exec.c:129 #, c-format msgid "error fetching next item from iterator" msgstr "当从迭代器中取回下一个成员时出现错误" -#: plpy_exec.c:164 #, c-format msgid "PL/Python function with return type \"void\" did not return None" msgstr "返回类型为\"void\"的PL/Python函数不返回None" -#: plpy_exec.c:288 plpy_exec.c:314 #, c-format msgid "unexpected return value from trigger procedure" msgstr "在触发器存储过程出现非期望的返回值" -#: plpy_exec.c:289 #, c-format msgid "Expected None or a string." msgstr "期望空值或一个字符串" -#: plpy_exec.c:304 #, c-format -msgid "" -"PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" -msgstr "" -"在DELETE触发器中的PL/Python 触发器函数返回 \"MODIFY\" -- 忽略" +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "在DELETE触发器中的PL/Python 触发器函数返回 \"MODIFY\" -- 忽略" -#: plpy_exec.c:315 #, c-format msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." msgstr "期望None, \"OK\", \"SKIP\", 或\"MODIFY\"" -#: plpy_exec.c:396 #, c-format msgid "PyList_SetItem() failed, while setting up arguments" msgstr "当设置参数的同时, 执行PyList_SetItem()失败" -#: plpy_exec.c:400 #, c-format msgid "PyDict_SetItemString() failed, while setting up arguments" msgstr "当设置参数的同时, 执行PyDict_SetItemString()失败" -#: plpy_exec.c:412 #, c-format -msgid "" -"function returning record called in context that cannot accept type record" -msgstr "" -"返回值类型是记录的函数在不接受使用记录类型的环境中调用" +msgid "function returning record called in context that cannot accept type record" +msgstr "返回值类型是记录的函数在不接受使用记录类型的环境中调用" -#: plpy_exec.c:450 #, c-format msgid "while creating return value" msgstr "同时在创建返回值" -#: plpy_exec.c:474 #, c-format msgid "could not create new dictionary while building trigger arguments" msgstr "在构建触发器参数的同时无法创建新的字典." -#: plpy_exec.c:664 #, c-format msgid "TD[\"new\"] deleted, cannot modify row" -msgstr "TD[\"new\"] 已删除, 无法修改记录" +msgstr "TD[\"new\"] 已删除,无法修改记录" -#: plpy_exec.c:667 #, c-format msgid "TD[\"new\"] is not a dictionary" msgstr "TD[\"new\"]不是一个字典" -#: plpy_exec.c:691 #, c-format msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" msgstr "在顺序位置%d的TD[\"new\"]字典键值不是字符串" -#: plpy_exec.c:697 #, c-format -msgid "" -"key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering " -"row" -msgstr "" -"在 TD[\"new\"]中找到的键 \"%s\"在正在触发的记录中不是作为列而存在." -"" +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "在 TD[\"new\"]中找到的键 \"%s\"在正在触发的记录中不是作为列而存在." + +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "无法设置生成的列 \"%s\"" -#: plpy_exec.c:778 #, c-format msgid "while modifying trigger row" msgstr "同时正在修改触发器记录" -#: plpy_exec.c:839 #, c-format msgid "forcibly aborting a subtransaction that has not been exited" msgstr "强行终止一个还未退出的子事务" -#: plpy_main.c:101 +#, c-format +msgid "PYTHONHOME env is not set" +msgstr "环境变量PYTHONHOME未设置" + +#, c-format +msgid "Python version is not 3.7, check if PYTHONHOME is valid." +msgstr "python版本号不是v3.7,请检查PYTHONHOME是否有效" + #, c-format msgid "Python major version mismatch in session" msgstr "在会话中Python的主版本不匹配" -#: plpy_main.c:102 #, c-format -msgid "" -"This session has previously used Python major version %d, and it is now " -"attempting to use Python major version %d." -msgstr "" -"这个会话先前已经使用的Python主版本是%d, 现在它试图使用的Python主版本是%d " -"" +msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d." +msgstr "这个会话先前已经使用的Python主版本是%d,现在它试图使用的Python主版本是%d " -#: plpy_main.c:104 #, c-format msgid "Start a new session to use a different Python major version." msgstr "启动一个新的会话来使用一个不同的Python的主要版本" -#: plpy_main.c:119 #, c-format msgid "untrapped error in initialization" msgstr "在初始化过程中出现无法捕获的错误" -#: plpy_main.c:142 #, c-format msgid "could not import \"__main__\" module" msgstr "无法导入模块\"__main__\" " -#: plpy_main.c:147 #, c-format msgid "could not create globals" msgstr "无法创建全局变量" -#: plpy_main.c:151 #, c-format msgid "could not initialize globals" msgstr "无法初始化全局变量" -#: plpy_main.c:351 #, c-format msgid "PL/Python function \"%s\"" msgstr "PL/Python函数\"%s\"" -#: plpy_main.c:358 #, c-format msgid "PL/Python anonymous code block" msgstr "PL/Python匿名代码块" -#: plpy_planobject.c:126 #, c-format msgid "plan.status takes no arguments" msgstr "plan.status不带有参数" -#: plpy_plpymodule.c:178 plpy_plpymodule.c:181 #, c-format msgid "could not import \"plpy\" module" msgstr "无法导入模块\"plpy\" " -#: plpy_plpymodule.c:196 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "无法创建spiexceptions模块" + #, c-format msgid "could not add the spiexceptions module" msgstr "无法添加spiexceptions模块 " -# fe-connect.c:891 -#: plpy_plpymodule.c:217 #, c-format msgid "could not create the base SPI exceptions" msgstr "无法创建基本的SPI异常" -#: plpy_plpymodule.c:253 plpy_plpymodule.c:257 #, c-format msgid "could not generate SPI exceptions" msgstr "无法产生SPI异常" -#: plpy_plpymodule.c:388 #, c-format msgid "could not unpack arguments in plpy.elog" msgstr "无法解析plpy.elog中的参数" -#: plpy_plpymodule.c:396 msgid "could not parse error message in plpy.elog" msgstr "无法解析在plpy.elog中的错误消息" -#: plpy_procedure.c:199 #, c-format msgid "trigger functions can only be called as triggers" msgstr "触发器函数只能以触发器的形式调用" -#: plpy_procedure.c:204 plpy_typeio.c:406 #, c-format msgid "PL/Python functions cannot return type %s" msgstr "PL/Python函数不能返回类型%s" -#: plpy_procedure.c:286 #, c-format msgid "PL/Python functions cannot accept type %s" msgstr "PL/Python函数不能接受类型%s" -#: plpy_procedure.c:382 #, c-format msgid "could not compile PL/Python function \"%s\"" msgstr "无法编译PL/Python函数\"%s\"" -#: plpy_procedure.c:385 #, c-format msgid "could not compile anonymous PL/Python code block" msgstr "无法编译PL/Python中的匿名代码块" -#: plpy_resultobject.c:145 plpy_resultobject.c:165 plpy_resultobject.c:185 #, c-format msgid "command did not produce a result set" msgstr "命令没有产生结果集" -#: plpy_spi.c:56 #, c-format msgid "second argument of plpy.prepare must be a sequence" msgstr "plpy.prepare的第二个参数必须是一个序列" -#: plpy_spi.c:105 #, c-format msgid "plpy.prepare: type name at ordinal position %d is not a string" msgstr "plpy.prepare: 在顺序位置%d的类型名称不是string" -#: plpy_spi.c:137 #, c-format msgid "plpy.prepare does not support composite types" msgstr "plpy.prepare不支持使用组合类型" -#: plpy_spi.c:187 #, c-format msgid "plpy.execute expected a query or a plan" msgstr "plpy.execute期望一个查询或一个计划" -#: plpy_spi.c:206 #, c-format msgid "plpy.execute takes a sequence as its second argument" msgstr "plpy.execute将一个序列作为它的第二个参数" -#: plpy_spi.c:330 #, c-format msgid "SPI_execute_plan failed: %s" msgstr "执行SPI_execute_plan失败: %s" -#: plpy_spi.c:372 #, c-format msgid "SPI_execute failed: %s" msgstr "SPI_execute执行失败: %s" -#: plpy_spi.c:439 #, c-format msgid "unrecognized error in PLy_spi_execute_fetch_result" msgstr "在PLy_spi_execute_fetch_result中出现无法识别的错误" -#: plpy_subxactobject.c:122 #, c-format msgid "this subtransaction has already been entered" msgstr "已经进入该子事务" -#: plpy_subxactobject.c:128 plpy_subxactobject.c:180 #, c-format msgid "this subtransaction has already been exited" msgstr "已经退出该子事务" -#: plpy_subxactobject.c:174 #, c-format msgid "this subtransaction has not been entered" msgstr "该子事务仍没有进入" -#: plpy_subxactobject.c:186 #, c-format msgid "there is no subtransaction to exit from" msgstr "没有子事务可以退出" -#: plpy_typeio.c:291 #, c-format msgid "could not create new dictionary" msgstr "无法创建新的字典" -#: plpy_typeio.c:408 #, c-format msgid "PL/Python does not support conversion to arrays of row types." -msgstr "PL/Python不支持对行类型数组的转换." - -#: plpy_typeio.c:584 -#, c-format -msgid "cannot convert multidimensional array to Python list" -msgstr "无法将多维数组转换为Python列表" - -#: plpy_typeio.c:585 -#, c-format -msgid "PL/Python only supports one-dimensional arrays." -msgstr "PL/Python只支持使用一维数组" - -#: plpy_typeio.c:591 -#, c-format -msgid "could not create new Python list" -msgstr "无法创建新的Python列表" +msgstr "PL/Python不支持对行类型数组的转换。" -#: plpy_typeio.c:650 #, c-format msgid "could not create bytes representation of Python object" msgstr "无法创建Python对象的字节表达式" -#: plpy_typeio.c:742 #, c-format msgid "could not create string representation of Python object" msgstr "无法创建Python对象的字符串表达式" -#: plpy_typeio.c:753 #, c-format -msgid "" -"could not convert Python object into cstring: Python string representation " -"appears to contain null bytes" -msgstr "" -"无法将Python对象转换为cstring: Python字符串表达式可能包含空字节" -"似乎包含空字节" +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "无法将Python对象转换为cstring: Python字符串表达式可能包含空字节" -#: plpy_typeio.c:787 #, c-format -msgid "" -"return value of function with array return type is not a Python sequence" -msgstr "" -"带有数组返回类型的函数返回值不是一个Python序列" +msgid "return value of function with array return type is not a Python sequence" +msgstr "带有数组返回类型的函数返回值不是一个Python序列" -#: plpy_typeio.c:886 #, c-format msgid "key \"%s\" not found in mapping" msgstr "在映射中没有找到键\"%s\"" -#: plpy_typeio.c:887 #, c-format -msgid "" -"To return null in a column, add the value None to the mapping with the key " -"named after the column." -msgstr "" -"为了在一列中返回空值, " -"需要在列的后面对带有已命名键的映射添加值None" +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "为了在一列中返回空值, 需要在列的后面对带有已命名键的映射添加值None" -#: plpy_typeio.c:935 #, c-format msgid "length of returned sequence did not match number of columns in row" msgstr "所返回序列的长度与在记录中列的数量不匹配" -#: plpy_typeio.c:1043 #, c-format msgid "attribute \"%s\" does not exist in Python object" msgstr "在Python对象中不存在属性\"%s\"" -#: plpy_typeio.c:1044 #, c-format -msgid "" -"To return null in a column, let the returned object have an attribute named " -"after column with value None." -msgstr "" -"为了在一列中返回空值, " -"需要让返回的对象在带有值None列后面的带有已命名属性" +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "为了在一列中返回空值, 需要让返回的对象在带有值None的列后面的带有已命名属性" -#: plpy_util.c:70 #, c-format msgid "could not convert Python Unicode object to bytes" msgstr "无法将Python中以Unicode编码的对象转换为PostgreSQL服务器字节码" -#: plpy_util.c:75 #, c-format msgid "could not extract bytes from encoded string" msgstr "无法从已编码字符串里提取相应字节码值" - -#~ msgid "PL/Python function \"%s\" failed" -#~ msgstr "PL/Python函数 \"%s\" 执行失败" - -#~ msgid "" -#~ "could not create string representation of Python object in PL/Python " -#~ "function \"%s\" while creating return value" -#~ msgstr "" -#~ "在创建返回值时, 无法计算在PL/Python函数\"%s\"中Python对象的字符串表达式" - -#~ msgid "" -#~ "could not compute string representation of Python object in PL/Python " -#~ "function \"%s\" while modifying trigger row" -#~ msgstr "" -#~ "在修改触发器记录的同时无法计算在PL/Python函数\"%s\"中Python对象的字符串表" -#~ "达式" - -#~ msgid "out of memory" -#~ msgstr "内存溢出" - -#~ msgid "PL/Python: %s" -#~ msgstr "PL/Python: %s" - -#~ msgid "could not create procedure cache" -#~ msgstr "无法创建存储过程缓存" - -#~ msgid "unrecognized error in PLy_spi_execute_query" -#~ msgstr "在PLy_spi_execute_query中出现无法识别的错误" - -#~ msgid "unrecognized error in PLy_spi_execute_plan" -#~ msgstr "在PLy_spi_execute_plan中出现无法识别的错误" - -#~ msgid "unrecognized error in PLy_spi_prepare" -#~ msgstr "在PLy_spi_prepare中无法识别的错误" - -#~ msgid "invalid arguments for plpy.prepare" -#~ msgstr " plpy.prepare的无效参数" - -#~ msgid "transaction aborted" -#~ msgstr "事务终止" - -#~ msgid "PyCObject_FromVoidPtr() failed" -#~ msgstr "执行PyCObject_FromVoidPtr()失败" - -#~ msgid "PyCObject_AsVoidPtr() failed" -#~ msgstr "执行PyCObject_AsVoidPtr()失败" diff --git a/src/common/port/path.cpp b/src/common/port/path.cpp index 15ff610fb4..5d1c7ad870 100644 --- a/src/common/port/path.cpp +++ b/src/common/port/path.cpp @@ -640,6 +640,20 @@ void get_pkglib_path(const char* my_exec_path, char* ret_path) make_relative_path(ret_path, MAXPGPATH, PKGLIBDIR, PGBINDIR, my_exec_path); } +void get_pythonhome_path(const char* my_exec_path, char* ret_path) +{ + /* + * MakeFile太难改了, 这里通过相对路径简单获取一下PYTHONHOME + * PYTHONHOME 安装在 PGHOME/python 目录下 + */ + char pghome[MAXPGPATH]; + strncpy_s(pghome, MAXPGPATH, my_exec_path, MAXPGPATH); /*PGHOME/bin/vastbase*/ + get_parent_directory(pghome); /* PGHOME/bin/ */ + get_parent_directory(pghome); /* PGHOME */ + snprintf(ret_path, MAXPGPATH, "%s/python", pghome); /*PGHOME/python*/ +} + + /* * get_locale_path */ diff --git a/src/gausskernel/process/threadpool/knl_session.cpp b/src/gausskernel/process/threadpool/knl_session.cpp index 030ff32e56..b54726c5ec 100755 --- a/src/gausskernel/process/threadpool/knl_session.cpp +++ b/src/gausskernel/process/threadpool/knl_session.cpp @@ -61,6 +61,7 @@ #include "access/heapam.h" #include "workload/workload.h" #include "parser/scanner.h" +#include "storage/plpython_init.h" #include "pgstat.h" #include "access/datavec/bitvec.h" @@ -116,6 +117,7 @@ static void knl_u_attr_init(knl_session_attr* attr) attr->attr_sql.enable_upsert_to_merge = false; attr->attr_common.extension_session_vars_array_size = 0; attr->attr_common.extension_session_vars_array = NULL; + attr->attr_common.g_ply_session_ctx = NULL; } void knl_u_executor_init(knl_u_executor_context* exec_cxt) @@ -1734,10 +1736,21 @@ void use_fake_session() SetThreadLocalGUC(u_sess); } +void free_plpython_session_context(knl_session_context* session) +{ + if (session->plpython_ctx) { + if (plpython_state->release_ply_session_ctx_callback) + plpython_state->release_ply_session_ctx_callback((ply_session_ctx*)session->plpython_ctx); + session->plpython_ctx = NULL; + } +} + void free_session_context(knl_session_context* session) { Assert(u_sess == session); + free_plpython_session_context(session); + /* free the locale cache */ freeLocaleCache(false); diff --git a/src/gausskernel/storage/access/common/heaptuple.cpp b/src/gausskernel/storage/access/common/heaptuple.cpp index 7a37b96980..1148e5220e 100644 --- a/src/gausskernel/storage/access/common/heaptuple.cpp +++ b/src/gausskernel/storage/access/common/heaptuple.cpp @@ -726,6 +726,32 @@ Tuple heapam_copytuple(Tuple tuple) return heap_copytuple((HeapTuple)tuple); } +/* ---------------- + * heap_copy_tuple_as_datum + * + * copy a tuple as a composite-type Datum + * ---------------- + */ +Datum +heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc) +{ + HeapTupleHeader td; + /* + * Fast path for easy case: just make a palloc'd copy and insert the + * correct composite-Datum header fields (since those may not be set if + * the given tuple came from disk, rather than from heap_form_tuple). + */ + td = (HeapTupleHeader) palloc(tuple->t_len); + int rc = memcpy_s((char *) td, tuple->t_len, (char *) tuple->t_data, tuple->t_len); + securec_check(rc, "\0", "\0"); + + HeapTupleHeaderSetDatumLength(td, tuple->t_len); + HeapTupleHeaderSetTypeId(td, tupleDesc->tdtypeid); + HeapTupleHeaderSetTypMod(td, tupleDesc->tdtypmod); + + return PointerGetDatum(td); +} + /* * heap_form_tuple * construct a tuple from the given values[] and isnull[] arrays, diff --git a/src/gausskernel/storage/ipc/Makefile b/src/gausskernel/storage/ipc/Makefile index 5ce67f7673..0bea227381 100644 --- a/src/gausskernel/storage/ipc/Makefile +++ b/src/gausskernel/storage/ipc/Makefile @@ -17,6 +17,6 @@ ifneq "$(MAKECMDGOALS)" "clean" endif endif OBJS = ipc.o ipci.o pmsignal.o procarray.o procsignal.o shmem.o shmqueue.o \ - sinval.o sinvaladt.o standby.o + sinval.o sinvaladt.o standby.o plpython_init.o include $(top_srcdir)/src/gausskernel/common.mk diff --git a/src/gausskernel/storage/ipc/ipc.cpp b/src/gausskernel/storage/ipc/ipc.cpp index 6ef6c71dcd..bfb4f401fc 100644 --- a/src/gausskernel/storage/ipc/ipc.cpp +++ b/src/gausskernel/storage/ipc/ipc.cpp @@ -36,6 +36,7 @@ #include "storage/latch.h" #include "storage/procarray.h" #include "gssignal/gs_signal.h" +#include "storage/plpython_init.h" #include "storage/pmsignal.h" #include "access/gtm.h" #include "access/ustore/undo/knl_uundoapi.h" @@ -280,6 +281,8 @@ void proc_exit(int code) DestoryAutonomousSession(true); } + free_plpython_session_context(u_sess); + /* Clean up everything that must be cleaned up */ proc_exit_prepare(code); diff --git a/src/gausskernel/storage/ipc/ipci.cpp b/src/gausskernel/storage/ipc/ipci.cpp index 9dd631d2ef..c6c08342ce 100644 --- a/src/gausskernel/storage/ipc/ipci.cpp +++ b/src/gausskernel/storage/ipc/ipci.cpp @@ -65,6 +65,7 @@ #include "storage/ipc.h" #include "storage/pg_shmem.h" #include "storage/pmsignal.h" +#include "storage/plpython_init.h" #include "storage/predicate.h" #include "storage/procsignal.h" #include "storage/smgr/segment.h" @@ -330,6 +331,8 @@ void CreateSharedMemoryAndSemaphores(bool makePrivate, int port) SSInitXminInfo(); } + PlpythonShmemInit(); + /* * Set up process table */ diff --git a/src/gausskernel/storage/ipc/plpython_init.cpp b/src/gausskernel/storage/ipc/plpython_init.cpp new file mode 100644 index 0000000000..9e47bab66d --- /dev/null +++ b/src/gausskernel/storage/ipc/plpython_init.cpp @@ -0,0 +1,38 @@ +#include "storage/plpython_init.h" +#include "storage/shmem.h" +#include "knl/knl_thread.h" +#include "storage/lock/lock.h" + +PlPythonState* plpython_state = NULL; + +Size PlpythonShmemSize(void) +{ + return sizeof(PlPythonState); +} + +void PlpythonShmemInit(void) +{ + bool found; + plpython_state = (PlPythonState*)ShmemInitStruct("plpython_state", PlpythonShmemSize(), &found); + if (found) { + return; + } + memset_s(plpython_state, sizeof(PlPythonState), 0, sizeof(PlPythonState)); +} + +extern void PlPyGilAcquire(void) +{ + LOCKTAG tag; + LockAcquireResult result; + uint64 self = u_sess->session_id; + + SET_LOCKTAG_PLPY_GIL(tag); + + result = LockAcquire(&tag, AccessExclusiveLock, false, false); + if (result == LOCKACQUIRE_OK || result == LOCKACQUIRE_ALREADY_HELD) { + pg_atomic_write_u64(&plpython_state->granted_session_id, self); + } else { + elog(ERROR, "cant not hold pypython GIL"); + } +} + 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 a12c5c4ea3..ac6336e67b 100644 --- a/src/include/knl/knl_guc/knl_session_attr_common.h +++ b/src/include/knl/knl_guc/knl_session_attr_common.h @@ -40,6 +40,7 @@ #define SRC_INCLUDE_KNL_KNL_SESSION_ATTR_COMMON_H_ #include "knl/knl_guc/knl_guc_common.h" +#include "plpython.h" typedef struct knl_session_attr_common { bool enable_beta_features; @@ -259,6 +260,8 @@ typedef struct knl_session_attr_common { #endif bool enable_aggr_coerce_type; bool enable_nonowner_remote_ddl; + /* save session level objects, set when the session first uses plpython */ + ply_session_ctx* g_ply_session_ctx; } knl_session_attr_common; #endif /* SRC_INCLUDE_KNL_KNL_SESSION_ATTR_COMMON_H_ */ diff --git a/src/include/knl/knl_session.h b/src/include/knl/knl_session.h index 679a91b0c8..ce79638642 100644 --- a/src/include/knl/knl_session.h +++ b/src/include/knl/knl_session.h @@ -3145,6 +3145,8 @@ typedef struct knl_session_context { knl_u_plancache_context pcache_cxt; knl_u_parameterization_context param_cxt; knl_u_plpgsql_context plsql_cxt; + /* plpython is a plugin, avoid exposing its definition in the kernel, use void* */ + void* plpython_ctx; knl_u_postgres_context postgres_cxt; knl_u_proc_context proc_cxt; knl_u_ps_context ps_cxt; @@ -3253,6 +3255,7 @@ extern void knl_u_relmap_init(knl_u_relmap_context* relmap_cxt); extern void knl_session_init(knl_session_context* sess_cxt); extern void knl_u_executor_init(knl_u_executor_context* exec_cxt); extern knl_session_context* create_session_context(MemoryContext parent, uint64 id); +extern void free_plpython_session_context(knl_session_context* session); extern void free_session_context(knl_session_context* session); extern void use_fake_session(); extern bool stp_set_commit_rollback_err_msg(stp_xact_err_type type); diff --git a/src/common/pl/plpython/plpy_util.h b/src/include/plpy_util.h similarity index 69% rename from src/common/pl/plpython/plpy_util.h rename to src/include/plpy_util.h index 967e18e427..b18fbd9a3c 100644 --- a/src/common/pl/plpython/plpy_util.h +++ b/src/include/plpy_util.h @@ -6,11 +6,6 @@ #ifndef PLPY_UTIL_H #define PLPY_UTIL_H -extern void* PLy_malloc(size_t bytes); -extern void* PLy_malloc0(size_t bytes); -extern char* PLy_strdup(const char* str); -extern void PLy_free(void* ptr); - extern PyObject* PLyUnicode_Bytes(PyObject* unicode); extern char* PLyUnicode_AsString(PyObject* unicode); diff --git a/src/common/pl/plpython/plpython.h b/src/include/plpython.h similarity index 77% rename from src/common/pl/plpython/plpython.h rename to src/include/plpython.h index d6568b6dd9..53a7f383e1 100644 --- a/src/common/pl/plpython/plpython.h +++ b/src/include/plpython.h @@ -119,24 +119,51 @@ typedef int Py_ssize_t; #undef TEXTDOMAIN #define TEXTDOMAIN PG_TEXTDOMAIN("plpython") -typedef struct plpy_t_context_struct { - bool inited; - MemoryContext plpython_func_cxt; - HTAB* PLy_spi_exceptions; +struct PLyExecutionContext; +typedef struct ply_session_ctx { + /* + * OG has only one python interpreter, use this id to isolate sessions, + * avoid defining the same function in different sessions + */ + int ply_ctx_id; + /* session level context */ + MemoryContext session_mctx; + /* temporary objects can use this context */ + MemoryContext session_tmp_mctx; + /* GD variable, isolated between sessions according to pg's implementation */ + PyObject* PLy_session_gd; + /* PL/Python execution stack */ + struct PLyExecutionContext* PLy_execution_contexts; + /* session level function compilation cache */ HTAB* PLy_procedure_cache; - /* a list of nested explicit subtransactions */ List* explicit_subtransactions; + struct ply_session_ctx *next_free_context; +} ply_session_ctx; + +typedef struct ply_globals_ctx { + /* instance level MemoryContext */ + MemoryContext ply_mctx; + /* plpython interpreter default global variables */ + PyObject* PLy_interp_globals; + PyObject* PLy_exc_error; PyObject* PLy_exc_fatal; PyObject* PLy_exc_spi_error; - PyObject* PLy_interp_globals; + /* spi exceptions */ + HTAB* PLy_spi_exceptions; - int Ply_LockLevel; -} plpy_t_context_struct; + int numberContexts; + ply_session_ctx* sessionContexts; + + int numberFreeContexts; + ply_session_ctx* free_session_head; + ply_session_ctx* free_session_tail; +} ply_globals_ctx; -extern THR_LOCAL plpy_t_context_struct g_plpy_t_context; +extern ply_globals_ctx* g_ply_ctx; /* save instance level objects */ +extern const bool enable_plpy; #include #include @@ -163,5 +190,6 @@ extern THR_LOCAL plpy_t_context_struct g_plpy_t_context; * just include it everywhere. */ #include "plpy_util.h" +#include "storage/plpython_init.h" #endif /* PLPYTHON_H */ diff --git a/src/include/port.h b/src/include/port.h index 7ec3bcf4f9..59b81de9fb 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -57,6 +57,7 @@ extern void get_pkginclude_path(const char* my_exec_path, char* ret_path); extern void get_includeserver_path(const char* my_exec_path, char* ret_path); extern void get_lib_path(const char* my_exec_path, char* ret_path); extern void get_pkglib_path(const char* my_exec_path, char* ret_path); +extern void get_pythonhome_path(const char* my_exec_path, char* ret_path); extern void get_locale_path(const char* my_exec_path, char* ret_path); extern void get_doc_path(const char* my_exec_path, char* ret_path); extern void get_html_path(const char* my_exec_path, char* ret_path); diff --git a/src/include/storage/lock/lock.h b/src/include/storage/lock/lock.h index 26df9f19ea..ccd2cf136a 100644 --- a/src/include/storage/lock/lock.h +++ b/src/include/storage/lock/lock.h @@ -180,6 +180,7 @@ typedef enum LockTagType { LOCKTAG_SUBTRANSACTION, /* subtransaction (for waiting for subxact done) */ /* ID info for a transaction is its TransactionId + SubTransactionId */ LOCKTAG_UID, + LOCKTAG_PLPY_GIL, LOCK_EVENT_NUM } LockTagType; @@ -326,6 +327,15 @@ typedef struct LOCKTAG { (locktag).locktag_type = LOCKTAG_PARTITION_SEQUENCE, \ (locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD) +#define SET_LOCKTAG_PLPY_GIL(locktag) \ + ((locktag).locktag_field1 = 0, \ + (locktag).locktag_field2 = 0, \ + (locktag).locktag_field3 = 0, \ + (locktag).locktag_field4 = 0, \ + (locktag).locktag_field5 = 0, \ + (locktag).locktag_type = LOCKTAG_PLPY_GIL, \ + (locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD) + #define SET_LOCKTAG_CSTORE_FREESPACE(locktag, id1, id2) \ ((locktag).locktag_field1 = (id1), \ (locktag).locktag_field2 = (id2), \ diff --git a/src/include/storage/plpython_init.h b/src/include/storage/plpython_init.h new file mode 100644 index 0000000000..783f48985d --- /dev/null +++ b/src/include/storage/plpython_init.h @@ -0,0 +1,25 @@ +#ifndef _PLPYTHON_INIT_H_ +#define _PLPYTHON_INIT_H_ +#include "postgres.h" + +/*forward declarations */ +struct ply_globals_ctx; +struct ply_session_ctx; + +typedef struct PlPythonState +{ + bool is_init; + /* Record the session that the current lock is granted to */ + uint64 granted_session_id; + /* Callback function to clean up session memory */ + void (*release_ply_session_ctx_callback) (ply_session_ctx* ctx); + struct ply_globals_ctx* ply_globals_ctx; +} PlPythonState; + +extern PlPythonState* plpython_state; + +extern Size PlpythonShmemSize(void); +extern void PlpythonShmemInit(void); +extern void PlPyGilAcquire(void); + +#endif /*_PLPYTHON_INIT_H_*/ \ No newline at end of file diff --git a/src/test/regress/expected/plpython3u/plpython_composite.out b/src/test/regress/expected/plpython3u/plpython_composite.out new file mode 100644 index 0000000000..a5a65724a5 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_composite.out @@ -0,0 +1,542 @@ +CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$ +return (1, 2) +$$ LANGUAGE plpython3u; +SELECT multiout_simple(); + multiout_simple +----------------- + (1,2) +(1 row) + +SELECT * FROM multiout_simple(); + i | j +---+--- + 1 | 2 +(1 row) + +SELECT i, j + 2 FROM multiout_simple(); + i | ?column? +---+---------- + 1 | 4 +(1 row) + +SELECT (multiout_simple()).j + 3; + ?column? +---------- + 5 +(1 row) + +CREATE FUNCTION multiout_simple_setof(n integer = 1, OUT integer, OUT integer) RETURNS SETOF record AS $$ +return [(1, 2)] * n +$$ LANGUAGE plpython3u; +SELECT multiout_simple_setof(); + multiout_simple_setof +----------------------- + (1,2) +(1 row) + +SELECT * FROM multiout_simple_setof(); + column1 | column2 +---------+--------- + 1 | 2 +(1 row) + +SELECT * FROM multiout_simple_setof(3); + column1 | column2 +---------+--------- + 1 | 2 + 1 | 2 + 1 | 2 +(3 rows) + +CREATE FUNCTION multiout_record_as(typ text, + first text, OUT first text, + second integer, OUT second integer, + retnull boolean) RETURNS record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) +$$ LANGUAGE plpython3u; +SELECT * FROM multiout_record_as('dict', 'foo', 1, 'f'); + first | second +-------+-------- + foo | 1 +(1 row) + +SELECT multiout_record_as('dict', 'foo', 1, 'f'); + multiout_record_as +-------------------- + (foo,1) +(1 row) + +SELECT * FROM multiout_record_as('dict', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('dict', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('dict', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('dict', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('dict', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('tuple', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('tuple', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('tuple', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('tuple', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('tuple', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('list', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('list', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('list', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('list', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('list', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('obj', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('obj', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('obj', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('obj', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('obj', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('str', 'one', 1, false); + first | second +-------+-------- + 'one' | 1 +(1 row) + +SELECT * FROM multiout_record_as('str', 'one', 2, false); + first | second +-------+-------- + 'one' | 2 +(1 row) + +SELECT *, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s); + f | s | snull +-----+---+------- + xxx | | t +(1 row) + +SELECT *, f IS NULL AS fnull, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s); + f | s | fnull | snull +---+---+-------+------- + | | t | t +(1 row) + +SELECT * FROM multiout_record_as('obj', NULL, 10, 'f'); + first | second +-------+-------- + | 10 +(1 row) + +CREATE FUNCTION multiout_return_table() RETURNS TABLE (x integer, y text) AS $$ +return [{'x': 4, 'y' :'four'}, + {'x': 7, 'y' :'seven'}, + {'x': 0, 'y' :'zero'}] +$$ LANGUAGE plpython3u; +SELECT * FROM multiout_return_table(); + x | y +---+------- + 4 | four + 7 | seven + 0 | zero +(3 rows) + +CREATE FUNCTION multiout_array(OUT integer[], OUT text) RETURNS SETOF record AS $$ +yield [[1], 'a'] +yield [[1,2], 'b'] +yield [[1,2,3], None] +$$ LANGUAGE plpython3u; +SELECT * FROM multiout_array(); + column1 | column2 +---------+--------- + {1} | a + {1,2} | b + {1,2,3} | +(3 rows) + +CREATE FUNCTION singleout_composite(OUT type_record) AS $$ +return {'first': 1, 'second': 2} +$$ LANGUAGE plpython3u; +CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$ +return [{'first': 1, 'second': 2}, + {'first': 3, 'second': 4 }] +$$ LANGUAGE plpython3u; +SELECT * FROM singleout_composite(); + first | second +-------+-------- + 1 | 2 +(1 row) + +SELECT * FROM multiout_composite(); + first | second +-------+-------- + 1 | 2 + 3 | 4 +(2 rows) + +-- composite OUT parameters in functions returning RECORD not supported yet +CREATE FUNCTION multiout_composite(INOUT n integer, OUT type_record) AS $$ +return (n, (n * 2, n * 3)) +$$ LANGUAGE plpython3u; +CREATE FUNCTION multiout_table_type_setof(typ text, returnnull boolean, INOUT n integer, OUT table_record) RETURNS SETOF record AS $$ +if returnnull: + d = None +elif typ == 'dict': + d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'} +elif typ == 'tuple': + d = (n * 2, n * 3) +elif typ == 'list': + d = [ n * 2, n * 3 ] +elif typ == 'obj': + class d: pass + d.first = n * 2 + d.second = n * 3 +elif typ == 'str': + d = "(%r,%r)" % (n * 2, n * 3) +for i in range(n): + yield (i, d) +$$ LANGUAGE plpython3u; +SELECT * FROM multiout_composite(2); + n | column2 +---+--------- + 2 | (4,6) +(1 row) + +SELECT * FROM multiout_table_type_setof('dict', 'f', 3); + n | column2 +---+--------- + 0 | (6,9) + 1 | (6,9) + 2 | (6,9) +(3 rows) + +SELECT * FROM multiout_table_type_setof('dict', 'f', 7); + n | column2 +---+--------- + 0 | (14,21) + 1 | (14,21) + 2 | (14,21) + 3 | (14,21) + 4 | (14,21) + 5 | (14,21) + 6 | (14,21) +(7 rows) + +SELECT * FROM multiout_table_type_setof('tuple', 'f', 2); + n | column2 +---+--------- + 0 | (4,6) + 1 | (4,6) +(2 rows) + +SELECT * FROM multiout_table_type_setof('tuple', 'f', 3); + n | column2 +---+--------- + 0 | (6,9) + 1 | (6,9) + 2 | (6,9) +(3 rows) + +SELECT * FROM multiout_table_type_setof('list', 'f', 2); + n | column2 +---+--------- + 0 | (4,6) + 1 | (4,6) +(2 rows) + +SELECT * FROM multiout_table_type_setof('list', 'f', 3); + n | column2 +---+--------- + 0 | (6,9) + 1 | (6,9) + 2 | (6,9) +(3 rows) + +SELECT * FROM multiout_table_type_setof('obj', 'f', 4); + n | column2 +---+--------- + 0 | (8,12) + 1 | (8,12) + 2 | (8,12) + 3 | (8,12) +(4 rows) + +SELECT * FROM multiout_table_type_setof('obj', 'f', 5); + n | column2 +---+--------- + 0 | (10,15) + 1 | (10,15) + 2 | (10,15) + 3 | (10,15) + 4 | (10,15) +(5 rows) + +SELECT * FROM multiout_table_type_setof('str', 'f', 6); + n | column2 +---+--------- + 0 | (12,18) + 1 | (12,18) + 2 | (12,18) + 3 | (12,18) + 4 | (12,18) + 5 | (12,18) +(6 rows) + +SELECT * FROM multiout_table_type_setof('str', 'f', 7); + n | column2 +---+--------- + 0 | (14,21) + 1 | (14,21) + 2 | (14,21) + 3 | (14,21) + 4 | (14,21) + 5 | (14,21) + 6 | (14,21) +(7 rows) + +SELECT * FROM multiout_table_type_setof('dict', 't', 3); + n | column2 +---+--------- + 0 | + 1 | + 2 | +(3 rows) + +-- check what happens if a type changes under us +CREATE TABLE changing ( + i integer, + j integer +); +CREATE FUNCTION changing_test(OUT n integer, OUT changing) RETURNS SETOF record AS $$ +return [(1, {'i': 1, 'j': 2}), + (1, (3, 4))] +$$ LANGUAGE plpython3u; +SELECT * FROM changing_test(); + n | column2 +---+--------- + 1 | (1,2) + 1 | (3,4) +(2 rows) + +ALTER TABLE changing DROP COLUMN j; +SELECT * FROM changing_test(); +ERROR: length of returned sequence did not match number of columns in row +CONTEXT: while creating return value +SELECT * FROM changing_test(); +ERROR: length of returned sequence did not match number of columns in row +CONTEXT: while creating return value +ALTER TABLE changing ADD COLUMN j integer; +SELECT * FROM changing_test(); + n | column2 +---+--------- + 1 | (1,2) + 1 | (3,4) +(2 rows) + +-- tables of composite types (not yet implemented) +CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$ +yield {'tab': [['first', 1], ['second', 2]], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +yield {'tab': [['first', 1], ['second', 2]], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +yield {'tab': [['first', 1], ['second', 2]], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +$$ LANGUAGE plpython3u; +SELECT * FROM composite_types_table(); + tab | typ +----------------------------+---------------------------- + {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"} + {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"} + {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"} +(3 rows) + +-- check what happens if the output record descriptor changes +CREATE FUNCTION return_record(t text) RETURNS record AS $$ +return {'t': t, 'val': 10} +$$ LANGUAGE plpython3u; +SELECT * FROM return_record('abc') AS r(t text, val integer); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('abc') AS r(t text, val bigint); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('abc') AS r(t text, val integer); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('abc') AS r(t varchar(30), val integer); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('abc') AS r(t varchar(100), val integer); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('999') AS r(val text, t integer); + val | t +-----+----- + 10 | 999 +(1 row) + +CREATE FUNCTION return_record_2(t text) RETURNS record AS $$ +return {'v1':1,'v2':2,t:3} +$$ LANGUAGE plpython3u; +SELECT * FROM return_record_2('v3') AS (v3 int, v2 int, v1 int); + v3 | v2 | v1 +----+----+---- + 3 | 2 | 1 +(1 row) + +SELECT * FROM return_record_2('v3') AS (v2 int, v3 int, v1 int); + v2 | v3 | v1 +----+----+---- + 2 | 3 | 1 +(1 row) + +SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int); + v1 | v4 | v2 +----+----+---- + 1 | 3 | 2 +(1 row) + +SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int); + v1 | v4 | v2 +----+----+---- + 1 | 3 | 2 +(1 row) + +-- error +SELECT * FROM return_record_2('v4') AS (v1 int, v3 int, v2 int); +ERROR: key "v3" not found in mapping +HINT: To return null in a column, add the value None to the mapping with the key named after the column. +CONTEXT: while creating return value +-- works +SELECT * FROM return_record_2('v3') AS (v1 int, v3 int, v2 int); + v1 | v3 | v2 +----+----+---- + 1 | 3 | 2 +(1 row) + +SELECT * FROM return_record_2('v3') AS (v1 int, v2 int, v3 int); + v1 | v2 | v3 +----+----+---- + 1 | 2 | 3 +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_do.out b/src/test/regress/expected/plpython3u/plpython_do.out new file mode 100644 index 0000000000..04d77bb52f --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_do.out @@ -0,0 +1,9 @@ +DO $$ plpy.notice("This is plpythonu.") $$ LANGUAGE plpython3u; +NOTICE: This is plpythonu. +DO $$ plpy.notice("This is plpython2u.") $$ LANGUAGE plpython3u; +NOTICE: This is plpython2u. +DO $$ raise Exception("error test") $$ LANGUAGE plpython3u; +ERROR: Exception: error test +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 1, in + raise Exception("error test") diff --git a/src/test/regress/expected/plpython3u/plpython_drop.out b/src/test/regress/expected/plpython3u/plpython_drop.out new file mode 100644 index 0000000000..f2a0e8315c --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_drop.out @@ -0,0 +1,6 @@ +-- +-- For paranoia's sake, don't leave an untrusted language sitting around +-- +SET client_min_messages = WARNING; +DROP EXTENSION plpython3u CASCADE; +DROP EXTENSION IF EXISTS plpython2u CASCADE; diff --git a/src/test/regress/expected/plpython3u/plpython_error.out b/src/test/regress/expected/plpython3u/plpython_error.out new file mode 100644 index 0000000000..b607a59089 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_error.out @@ -0,0 +1,408 @@ +-- test error handling, i forgot to restore Warn_restart in +-- the trigger handler once. the errors and subsequent core dump were +-- interesting. +/* Flat out Python syntax error + */ +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (, line 2) +/* With check_function_bodies = false the function should get defined + * and the error reported when called + */ +SET check_function_bodies = false; +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (, line 2) +CONTEXT: referenced column: python_syntax_error +/* Run the function twice to check if the hashtable entry gets cleaned up */ +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (, line 2) +CONTEXT: referenced column: python_syntax_error +RESET check_function_bodies; +/* Flat out syntax error + */ +CREATE FUNCTION sql_syntax_error() RETURNS text + AS +'plpy.execute("syntax error")' + LANGUAGE plpython3u; +SELECT sql_syntax_error(); +ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax" +LINE 1: syntax error + ^ +QUERY: syntax error +CONTEXT: Traceback (most recent call last): + PL/Python function "sql_syntax_error", line 1, in + plpy.execute("syntax error") +referenced column: sql_syntax_error +/* check the handling of uncaught python exceptions + */ +CREATE FUNCTION exception_index_invalid(text) RETURNS text + AS +'return args[1]' + LANGUAGE plpython3u; +SELECT exception_index_invalid('test'); +ERROR: IndexError: list index out of range +CONTEXT: Traceback (most recent call last): + PL/Python function "exception_index_invalid", line 1, in + return args[1] +referenced column: exception_index_invalid +/* check handling of nested exceptions + */ +CREATE FUNCTION exception_index_invalid_nested() RETURNS text + AS +'rv = plpy.execute("SELECT test5(''foo'')") +return rv[0]' + LANGUAGE plpython3u; +SELECT exception_index_invalid_nested(); +ERROR: spiexceptions.UndefinedFunction: function test5(unknown) does not exist +LINE 1: SELECT test5('foo') + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +QUERY: SELECT test5('foo') +CONTEXT: Traceback (most recent call last): + PL/Python function "exception_index_invalid_nested", line 1, in + rv = plpy.execute("SELECT test5('foo')") +referenced column: exception_index_invalid_nested +/* a typo + */ +CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + SD["plan"] = plpy.prepare(q, [ "test" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_uncaught('rick'); +ERROR: spiexceptions.UndefinedObject: type "test" does not exist +CONTEXT: Traceback (most recent call last): + PL/Python function "invalid_type_uncaught", line 3, in + SD["plan"] = plpy.prepare(q, [ "test" ]) +referenced column: invalid_type_uncaught +/* for what it's worth catch the exception generated by + * the typo, and return None + */ +CREATE FUNCTION invalid_type_caught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.notice(str(ex)) + return None +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_caught('rick'); +NOTICE: type "test" does not exist +CONTEXT: referenced column: invalid_type_caught + invalid_type_caught +--------------------- + +(1 row) + +/* for what it's worth catch the exception generated by + * the typo, and reraise it as a plain error + */ +CREATE FUNCTION invalid_type_reraised(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.error(str(ex)) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_reraised('rick'); +ERROR: plpy.Error: type "test" does not exist +CONTEXT: Traceback (most recent call last): + PL/Python function "invalid_type_reraised", line 6, in + plpy.error(str(ex)) +referenced column: invalid_type_reraised +/* no typo no messing about + */ +CREATE FUNCTION valid_type(a text) RETURNS text + AS +'if "plan" not in SD: + SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT valid_type('rick'); + valid_type +------------ + +(1 row) + +/* error in nested functions to get a traceback +*/ +CREATE FUNCTION nested_error() RETURNS text + AS +'def fun1(): + plpy.error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; +SELECT nested_error(); +ERROR: plpy.Error: boom +CONTEXT: Traceback (most recent call last): + PL/Python function "nested_error", line 10, in + fun3() + PL/Python function "nested_error", line 8, in fun3 + fun2() + PL/Python function "nested_error", line 5, in fun2 + fun1() + PL/Python function "nested_error", line 2, in fun1 + plpy.error("boom") +referenced column: nested_error +/* raising plpy.Error is just like calling plpy.error +*/ +CREATE FUNCTION nested_error_raise() RETURNS text + AS +'def fun1(): + raise plpy.Error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; +SELECT nested_error_raise(); +ERROR: plpy.Error: boom +CONTEXT: Traceback (most recent call last): + PL/Python function "nested_error_raise", line 10, in + fun3() + PL/Python function "nested_error_raise", line 8, in fun3 + fun2() + PL/Python function "nested_error_raise", line 5, in fun2 + fun1() + PL/Python function "nested_error_raise", line 2, in fun1 + raise plpy.Error("boom") +referenced column: nested_error_raise +/* using plpy.warning should not produce a traceback +*/ +CREATE FUNCTION nested_warning() RETURNS text + AS +'def fun1(): + plpy.warning("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "you''ve been warned" +' + LANGUAGE plpython3u; +SELECT nested_warning(); +WARNING: boom +CONTEXT: referenced column: nested_warning + nested_warning +-------------------- + you've been warned +(1 row) + +/* AttributeError at toplevel used to give segfaults with the traceback +*/ +CREATE FUNCTION toplevel_attribute_error() RETURNS void AS +$$ +plpy.nonexistent +$$ LANGUAGE plpython3u; +SELECT toplevel_attribute_error(); +ERROR: AttributeError: module 'plpy' has no attribute 'nonexistent' +CONTEXT: Traceback (most recent call last): + PL/Python function "toplevel_attribute_error", line 2, in + plpy.nonexistent +referenced column: toplevel_attribute_error +/* Calling PL/Python functions from SQL and vice versa should not lose context. + */ +CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$ +def first(): + second() + +def second(): + third() + +def third(): + plpy.execute("select sql_error()") + +first() +$$ LANGUAGE plpython3u; +set vbplsql_check to off; +CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$ +begin + select 1/0; +end +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$ +begin + select python_traceback(); +end +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$ +plpy.execute("select sql_error()") +$$ LANGUAGE plpython3u; +SELECT python_traceback(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "python_traceback", line 11, in + first() + PL/Python function "python_traceback", line 3, in first + second() + PL/Python function "python_traceback", line 6, in second + third() + PL/Python function "python_traceback", line 9, in third + plpy.execute("select sql_error()") +referenced column: python_traceback +SELECT sql_error(); +ERROR: division by zero +CONTEXT: SQL statement "select 1/0" +PL/pgSQL function public.sql_error() line 3 at SQL statement +referenced column: sql_error +SELECT python_from_sql_error(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "python_traceback", line 11, in + first() + PL/Python function "python_traceback", line 3, in first + second() + PL/Python function "python_traceback", line 6, in second + third() + PL/Python function "python_traceback", line 9, in third + plpy.execute("select sql_error()") +referenced column: python_traceback +SQL statement "select python_traceback()" +PL/pgSQL function public.python_from_sql_error() line 3 at SQL statement +referenced column: python_from_sql_error +SELECT sql_from_python_error(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "sql_from_python_error", line 2, in + plpy.execute("select sql_error()") +referenced column: sql_from_python_error +/* check catching specific types of exceptions + */ +CREATE TABLE specific ( + i integer PRIMARY KEY +); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "specific_pkey" for table "specific" +CREATE FUNCTION specific_exception(i integer) RETURNS void AS +$$ +from plpy import spiexceptions +try: + plpy.execute("insert into specific values (%s)" % (i or "NULL")); +except spiexceptions.NotNullViolation as e: + plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate) +except spiexceptions.UniqueViolation as e: + plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate) +$$ LANGUAGE plpython3u; +SELECT specific_exception(2); + specific_exception +-------------------- + +(1 row) + +SELECT specific_exception(NULL); +NOTICE: Violated the NOT NULL constraint, sqlstate 23502 +CONTEXT: referenced column: specific_exception + specific_exception +-------------------- + +(1 row) + +SELECT specific_exception(2); +NOTICE: Violated the UNIQUE constraint, sqlstate 23505 +CONTEXT: referenced column: specific_exception + specific_exception +-------------------- + +(1 row) + +/* SPI errors in PL/Python functions should preserve the SQLSTATE value + */ +CREATE FUNCTION python_unique_violation() RETURNS void AS $$ +plpy.execute("insert into specific values (1)") +plpy.execute("insert into specific values (1)") +$$ LANGUAGE plpython3u; +CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$ +begin + begin + perform python_unique_violation(); + exception when unique_violation then + return 'ok'; + end; + return 'not reached'; +end; +$$ language plpgsql; +SELECT catch_python_unique_violation(); + catch_python_unique_violation +------------------------------- + ok +(1 row) + +/* manually starting subtransactions - a bad idea + */ +CREATE FUNCTION manual_subxact() RETURNS void AS $$ +plpy.execute("savepoint save") +plpy.execute("create table foo(x integer)") +plpy.execute("rollback to save") +$$ LANGUAGE plpython3u; +SELECT manual_subxact(); +ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION +CONTEXT: Traceback (most recent call last): + PL/Python function "manual_subxact", line 2, in + plpy.execute("savepoint save") +referenced column: manual_subxact +/* same for prepared plans + */ +CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$ +save = plpy.prepare("savepoint save") +rollback = plpy.prepare("rollback to save") +plpy.execute(save) +plpy.execute("create table foo(x integer)") +plpy.execute(rollback) +$$ LANGUAGE plpython3u; +SELECT manual_subxact_prepared(); +ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION +CONTEXT: Traceback (most recent call last): + PL/Python function "manual_subxact_prepared", line 4, in + plpy.execute(save) +referenced column: manual_subxact_prepared diff --git a/src/test/regress/expected/plpython3u/plpython_global.out b/src/test/regress/expected/plpython3u/plpython_global.out new file mode 100644 index 0000000000..a4cfb1483f --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_global.out @@ -0,0 +1,52 @@ +-- +-- check static and global data (SD and GD) +-- +CREATE FUNCTION global_test_one() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_one" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_one" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; +CREATE FUNCTION global_test_two() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_two" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_two" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; +CREATE FUNCTION static_test() returns int4 + AS +'if "call" in SD: + SD["call"] = SD["call"] + 1 +else: + SD["call"] = 1 +return SD["call"] +' + LANGUAGE plpython3u; +SELECT static_test(); + static_test +------------- + 1 +(1 row) + +SELECT static_test(); + static_test +------------- + 2 +(1 row) + +SELECT global_test_one(); + global_test_one +-------------------------------------------------------- + SD: set by global_test_one, GD: set by global_test_one +(1 row) + +SELECT global_test_two(); + global_test_two +-------------------------------------------------------- + SD: set by global_test_two, GD: set by global_test_one +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_global_session.out b/src/test/regress/expected/plpython3u/plpython_global_session.out new file mode 100644 index 0000000000..d56be83218 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_global_session.out @@ -0,0 +1,41 @@ +-- +-- check static and global data (SD and GD), session 间不共享 +-- +CREATE FUNCTION global_test_three() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_three" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_three" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; +SELECT static_test(); + static_test +------------- + 1 +(1 row) + +SELECT static_test(); + static_test +------------- + 2 +(1 row) + +SELECT global_test_three(); + global_test_three +------------------------------------------------------------ + SD: set by global_test_three, GD: set by global_test_three +(1 row) + +SELECT global_test_two(); + global_test_two +---------------------------------------------------------- + SD: set by global_test_two, GD: set by global_test_three +(1 row) + +SELECT global_test_one(); + global_test_one +---------------------------------------------------------- + SD: set by global_test_one, GD: set by global_test_three +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_gms_xmldom.out b/src/test/regress/expected/plpython3u/plpython_gms_xmldom.out new file mode 100644 index 0000000000..32ec535a27 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_gms_xmldom.out @@ -0,0 +1,1871 @@ +--(1)newDOMDocument +--(2makeNode(doc DOMDocument)) +--(3)writeToClob(doc DOMDocument, cl IN OUT CLOB) +DECLARE + doc gms_xmldom.DOMDocument; + elem gms_xmldom.DOMElement; + root gms_xmldom.DOMNode; + elemNode gms_xmldom.DOMNode; + cl clob; + appResNode gms_xmldom.DOMNode; +BEGIN + doc := gms_xmldom.newDomDocument; + root := gms_xmldom.makeNode(doc); + elem := gms_xmldom.createElement(doc, 'root'); + elemNode := gms_xmldom.makeNode(elem); + appResNode := gms_xmldom.appendChild(root, elemNode); + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', cl; +END; +/ +NOTICE: + + +--(4)newDOMDocument(xmldoc IN xml) +DECLARE + doc gms_xmldom.DOMDocument; + cl clob; + x xml; +BEGIN + x := xml('ramesh'); + doc := gms_xmldom.newDomDocument(x); + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', cl; +END; +/ +NOTICE: + + ramesh + + +--(5)newDOMDocument(xmldoc clob) +DECLARE + doc gms_xmldom.DOMDocument; + cl clob; + s clob; +BEGIN + s := 'ramesh'; + doc := gms_xmldom.newDomDocument(s); + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', cl; +END; +/ +NOTICE: + + ramesh + + +--(6)isNull(com DOMDocument) +--(7)isNull(n DOMNode) +--(8)createElement(doc DOMDocument, tagname IN VARCHAR2) +--(9)isNull(elem DOMElement) +--(10)makeNode(elem DOMElement) +--(11)createTextNode(doc DOMDocument, data IN VARCHAR2) +--(12)isNull(t DOMText) +--(13)makeNode(t DOMText) +--(14)setAttribute(elem DOMElement, name IN VARCHAR2, newvalue IN VARCHAR2) +--(15)writeToBuffer(n DOMDocument, buffer IN OUT VARCHAR2) +DECLARE + doc gms_xmldom.DOMDocument; + root gms_xmldom.DOMNode; + + booklist gms_xmldom.DOMElement; + listNode gms_xmldom.DOMNode; + + bookElem gms_xmldom.DOMElement; + bookElemNode gms_xmldom.DOMNode; + + titleElem gms_xmldom.DOMElement; + titleElemNode gms_xmldom.DOMNode; + titleText gms_xmldom.DOMText; + titleTextNode gms_xmldom.DOMNode; + + authorElem gms_xmldom.DOMElement; + authorElemNode gms_xmldom.DOMNode; + authorText gms_xmldom.DOMText; + authorTextNode gms_xmldom.DOMNode; + + pageElem gms_xmldom.DOMElement; + pageElemNode gms_xmldom.DOMNode; + pageText gms_xmldom.DOMText; + pageTextNode gms_xmldom.DOMNode; + + resnode gms_xmldom.DOMNode; + + buffer varchar2; + isNull boolean; +BEGIN + /*root*/ + doc := gms_xmldom.newDOMDocument; + isNull := gms_xmldom.isNull(doc); + raise notice '%', ('DOMDocument : ' || case when isNull then 'Y' else 'N' end); + root := gms_xmldom.makeNode(doc); + isNull := gms_xmldom.isNull(root); + raise notice '%', ('DOMNode : ' || case when isNull then 'Y' else 'N' end); + /*booklist*/ + booklist := gms_xmldom.createElement(doc, 'booklist'); + isNull := gms_xmldom.isNull(booklist); + raise notice '%', ('DOMElement : ' || case when isNull then 'Y' else 'N' end); + gms_xmldom.setAttribute(booklist, 'type', 'science and engineering'); + listNode := gms_xmldom.makeNode(booklist); + + /*book*/ + bookElem := gms_xmldom.createElement(doc, 'book'); + gms_xmldom.setAttribute(bookElem, 'category', 'python'); + bookElemNode := gms_xmldom.makeNode(bookElem); + /*title*/ + titleElem := gms_xmldom.createElement(doc, 'title'); + titleElemNode := gms_xmldom.makeNode(titleElem); + /*attribute of title*/ + titleText := gms_xmldom.createTextNode(doc, 'learning python'); + isNull := gms_xmldom.isNull(titleText); + raise notice '%', ('DOMText : ' || case when isNull then 'Y' else 'N' end); + titleTextNode := gms_xmldom.makeNode(titleText); + resnode := gms_xmldom.appendChild(titleElemNode, titleTextNode); + /*author*/ + authorElem := gms_xmldom.createElement(doc, 'author'); + authorElemNode := gms_xmldom.makeNode(authorElem); + /*attribute of author*/ + authorText := gms_xmldom.createTextNode(doc, '张三'); + authorTextNode := gms_xmldom.makeNode(authorText); + authorTextNode := gms_xmldom.appendChild(authorElemNode, authorTextNode); + /*pageNumber*/ + pageElem := gms_xmldom.createElement(doc, 'pageNumber'); + pageElemNode := gms_xmldom.makeNode(pageElem); + /*attribute of pageNumber*/ + pageText := gms_xmldom.createTextNode(doc, '600'); + pageTextNode := gms_xmldom.makeNode(pageText); + resnode := gms_xmldom.appendChild(pageElemNode, pageTextNode); + + resnode := gms_xmldom.appendChild(bookElemNode, titleElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, authorElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, pageElemNode); + resnode := gms_xmldom.appendChild(listNode, bookElemNode); + resnode := gms_xmldom.appendChild(root, listNode); + + buffer := gms_xmldom.writeToBuffer(doc, buffer); + raise notice '%', buffer; +END; +/ +NOTICE: DOMDocument : N +NOTICE: DOMNode : N +NOTICE: DOMElement : N +NOTICE: DOMText : N +NOTICE: + + + learning python + 张三 + 600 + + + +--(16)createComment(doc DOMDocument, data IN VARCHAR2) +--(17)isNull(com DOMComment) +--(18)makeNode(com DOMComment) +--(19)createProcessingInstruction(doc DOMDocument, target IN VARCHAR2, data IN VARCHAR2) +--(20)isNull(pi DOMProcessingInstruction) +--(21)makeNode(pi DOMProcessingInstruction) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node1 gms_xmldom.DOMNODE; + comment gms_xmldom.DOMComment; + commentNode gms_xmldom.DOMNode; + resNode gms_xmldom.DOMNode; + + procInstruc gms_xmldom.DOMProcessingInstruction; + procInstrucNode gms_xmldom.DOMNode; + wclob clob; + isNull boolean; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getFirstChild(docNode); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node1 := gms_xmldom.item(nodeList, 0); + + --创建和插入comment节点 + comment := gms_xmldom.createComment(doc, 'This is the introduction of books'); + isNull := gms_xmldom.isNull(comment); + raise notice '%', ('DOMComment : ' || case when isNull then 'Y' else 'N' end); + commentNode := gms_xmldom.makeNode(comment); + resNode := gms_xmldom.insertBefore(bookListNode, commentNode, node1); + + --创建和插入ProcessingInstruction节点 + procInstruc := gms_xmldom.createProcessingInstruction(doc, 'xml', 'version="2.0"'); + isNull := gms_xmldom.isNull(procInstruc); + raise notice '%', ('DOMProcessingInstruction : ' || case when isNull then 'Y' else 'N' end); + procInstrucNode := gms_xmldom.makeNode(procInstruc); + resNode := gms_xmldom.insertBefore(docNode, procInstrucNode, bookListNode); + + wclob := gms_xmldom.writeToClob(doc, wclob); + --输出修改后的clob内容 + raise notice '%', wclob; +END; +/ +NOTICE: DOMComment : N +NOTICE: DOMProcessingInstruction : N +NOTICE: + + + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +--(22)createCDATASection(doc gms_xmldom.DOMDocument, data IN VARCHAR2) +--(23)isNull(cds DOMCDATASection) +--(24)makeNode(cds DOMCDATASection) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node1 gms_xmldom.DOMNODE; + redNode gms_xmldom.DOMNODE; + wclob clob; + + cds gms_xmldom.DOMCDataSection; + cdsNode gms_xmldom.DOMNode; + isNull boolean; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + + bookListNode := gms_xmldom.getFirstChild(docNode); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node1 := gms_xmldom.item(nodeList, 0); + + cds := gms_xmldom.createCDataSection(doc, '<>&'); + isNull := gms_xmldom.isNull(cds); + raise notice '%', ('DOMCDataSection : ' || case when isNull then 'Y' else 'N' end); + cdsNode := gms_xmldom.makeNode(cds); + redNode := gms_xmldom.appendChild(node1, cdsNode); + + cds := gms_xmldom.createCDataSection(doc, '&'); + cdsNode := gms_xmldom.makeNode(cds); + redNode := gms_xmldom.appendChild(node1, cdsNode); + + cds := gms_xmldom.createCDataSection(doc, ']]'); + cdsNode := gms_xmldom.makeNode(cds); + redNode := gms_xmldom.appendChild(node1, cdsNode); + + wclob := gms_xmldom.writeToClob(doc, wclob); + --输出修改后的clob内容 + raise notice '%', wclob; +END; +/ +NOTICE: DOMCDataSection : N +NOTICE: + + + learning math + 张三 + 561 +&]]> + + +--插入失败 +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node1 gms_xmldom.DOMNODE; + redNode gms_xmldom.DOMNODE; + wclob clob; + + cds gms_xmldom.DOMCDataSection; + cdsNode gms_xmldom.DOMNode; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + + bookListNode := gms_xmldom.getFirstChild(docNode); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node1 := gms_xmldom.item(nodeList, 0); + + cds := gms_xmldom.createCDataSection(doc, ']]>'); + cdsNode := gms_xmldom.makeNode(cds); + redNode := gms_xmldom.appendChild(node1, cdsNode); + + wclob := gms_xmldom.writeToClob(doc, wclob); + --输出修改后的clob内容 + raise notice '%', wclob; +END; +/ +ERROR: ValueError: ']]>' not allowed in a CDATA section +CONTEXT: Traceback (most recent call last): + PL/Python function "internal_writexml", line 11, in + cl = docNode.toprettyxml(indent=" ", newl="\n", encoding=tarEncoding) + PL/Python function "internal_writexml", line 58, in toprettyxml + PL/Python function "internal_writexml", line 1795, in writexml + PL/Python function "internal_writexml", line 869, in writexml + PL/Python function "internal_writexml", line 869, in writexml + PL/Python function "internal_writexml", line 1195, in writexml +PL/pgSQL function gms_xmldom.writetoclob(gms_xmldom.domdocument,clob) line 6 at assignment +PL/pgSQL function inline_code_block line 32 at assignment +--(25)createDocument(namespaceuri IN VARCHAR2, qualifiedname IN VARCHAR2, doctype IN DOMType:= NULL) +--(26)createElement(doc gms_xmldom.DOMDocument, tagname IN VARCHAR2, ns IN VARCHAR2) +--(27)setAttribute(elem gms_xmldom.DOMElement, name IN VARCHAR2, newvalue IN VARCHAR2, ns IN VARCHAR2) +DECLARE + doc gms_xmldom.DOMDocument; + rootElem gms_xmldom.DOMElement; + rootNode gms_xmldom.DOMNode; + elem gms_xmldom.DOMElement; + elemNode gms_xmldom.DOMNode; + wclob clob; + resNode gms_xmldom.DOMNode; +BEGIN + doc := gms_xmldom.createDocument('http://www.runoob.com/xml/', 'xml', null); + rootElem := gms_xmldom.getDocumentElement(doc); + rootNode := gms_xmldom.makeNode(rootElem); + + elem := gms_xmldom.createElement(doc, 'head', 'http://www.runoob.com/xml/'); + gms_xmldom.setAttribute(elem, 'id', 'headDoc', 'http://www.runoob.com/xml/'); + elemNode := gms_xmldom.makeNode(elem); + resNode := gms_xmldom.appendChild(rootNode, elemNode); + + elem := gms_xmldom.createElement(doc, 'body', 'http://www.runoob.com/xml/'); + gms_xmldom.setAttribute(elem, 'id', 'bodyDoc', 'http://www.runoob.com/xml/'); + elemNode := gms_xmldom.makeNode(elem); + resNode := gms_xmldom.appendChild(rootNode, elemNode); + + wclob :=gms_xmldom.writeToClob(doc, wclob); + --输出clob内容 + raise notice '%', wclob; +END; +/ +NOTICE: + + + + + +--(28)makeElement(n DOMNode) +--(29)getElementsByTagName(elem DOMElement, name IN VARCHAR2, ns varchar2) +--(30)createAttribute(doc DOMDocument, name IN VARCHAR2, ns IN VARCHAR2) +--(31)setAttributeNode(elem DOMElement, newattr IN DOMAttr, ns IN VARCHAR2) +--(32)getChildrenByTagName(elem DOMElement, name varchar2, ns varchar2) +--(33)item(nl DOMNodeList, idx IN PLS_INTEGER) +DECLARE + doc gms_xmldom.DOMDocument; + root gms_xmldom.DOMNode; + rootElem gms_xmldom.DOMElement; + bookElem gms_xmldom.DOMElement; + bookElemNode gms_xmldom.DOMNode; + titleElem gms_xmldom.DOMElement; + titleElemNode gms_xmldom.DOMNode; + titleText gms_xmldom.DOMText; + titleTextNode gms_xmldom.DOMNode; + authorElem gms_xmldom.DOMElement; + authorElemNode gms_xmldom.DOMNode; + authorText gms_xmldom.DOMText; + authorTextNode gms_xmldom.DOMNode; + pageElem gms_xmldom.DOMElement; + pageElemNode gms_xmldom.DOMNode; + pageText gms_xmldom.DOMText; + pageTextNode gms_xmldom.DOMNode; + resnode gms_xmldom.DOMNode; + cl clob; + + bookListNode gms_xmldom.DOMNode; + bookListElem gms_xmldom.DOMElement; + nodeList gms_xmldom.DOMNodelist; + node gms_xmldom.DOMNode; + nodeElem gms_xmldom.DOMElement; + attr gms_xmldom.DOMAttr; + resAttr gms_xmldom.DOMAttr; + len integer; +BEGIN + --booklist + doc := gms_xmldom.createDocument('http://www.runoob.com/xml/', 'booklist', null); + rootElem := gms_xmldom.getDocumentElement(doc); + gms_xmldom.setAttribute(rootElem, 'type', 'science and engineering', 'http://www.runoob.com/xml/'); + root := gms_xmldom.makeNode(rootElem); + --book + bookElem := gms_xmldom.createElement(doc, 'book', 'http://www.runoob.com/xml/'); + gms_xmldom.setAttribute(bookElem, 'category', 'python', 'http://www.runoob.com/xml/'); + bookElemNode := gms_xmldom.makeNode(bookElem); + --title + titleElem := gms_xmldom.createElement(doc, 'book', 'http://www.runoob.com/xml/'); + titleElemNode := gms_xmldom.makeNode(titleElem); + --attribute of title + titleText := gms_xmldom.createTextNode(doc, 'learning python'); + titleTextNode := gms_xmldom.makeNode(titleText); + resnode := gms_xmldom.appendChild(titleElemNode, titleTextNode); + --author + authorElem := gms_xmldom.createElement(doc, 'author', 'http://www.runoob.com/xml/'); + authorElemNode := gms_xmldom.makeNode(authorElem); + --attribute of author + authorText := gms_xmldom.createTextNode(doc, '张三'); + authorTextNode := gms_xmldom.makeNode(authorText); + authorTextNode := gms_xmldom.appendChild(authorElemNode, authorTextNode); + --pageNumber + pageElem := gms_xmldom.createElement(doc, 'pageNumber', 'http://www.runoob.com/xml/'); + pageElemNode := gms_xmldom.makeNode(pageElem); + --attribute of pageNumber + pageText := gms_xmldom.createTextNode(doc, '600'); + pageTextNode := gms_xmldom.makeNode(pageText); + resnode := gms_xmldom.appendChild(pageElemNode, pageTextNode); + + resnode := gms_xmldom.appendChild(bookElemNode, titleElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, authorElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, pageElemNode); + resnode := gms_xmldom.appendChild(root, bookElemNode); + + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', cl; + + root := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getFirstChild(root); + bookListElem := gms_xmldom.makeElement(bookListNode); + nodeList := gms_xmldom.getChildrenByTagName(bookListElem, 'book', 'http://www.runoob.com/xml/'); + --nodeList := gms_xmldom.getElementsByTagName(bookListElem, 'book', 'http://www.runoob.com/xml/'); + len := gms_xmldom.getLength(nodeList); + for i in 0..len-1 loop + node := gms_xmldom.item(nodeList,i); + nodeElem := gms_xmldom.makeElement(node); + attr := gms_xmldom.createAttribute(doc, 'num', 'http://www.runoob.com/xml/'); + resAttr := gms_xmldom.setAttributeNode(nodeElem, attr, 'http://www.runoob.com/xml/'); + end loop; + --输出修改后的clob内容 + cl := gms_xmldom.writetoclob(doc,cl); + raise notice '%', ('设置属性名后:' || cl); +END; +/ +NOTICE: + + + learning python + 张三 + 600 + + + +NOTICE: 设置属性名后: + + + learning python + 张三 + 600 + + + +--(34)createAttribute(doc DOMDocument, name IN VARCHAR2) +--(35)isNull(a DOMAttr) +--(36)setAttributeNode(elem DOMElement, newattr IN DOMAttr) +--(37)getElementsByTagName(doc DOMElement, tagname IN VARCHAR2) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docElem gms_xmldom.DOMElement; + nodeList gms_xmldom.DOMNodelist; + node gms_xmldom.DOMNODE; + wclob clob; + elemNode gms_xmldom.DOMElement; + attr1 gms_XMLDOM.DOMAttr; + attr2 gms_XMLDOM.DOMAttr; + isNull boolean; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + doc := gms_xmldom.newDOMDocument(var); + --打印设置属性名前的wclob + wclob := gms_xmldom.writeToClob(doc, wclob); + raise notice '%', ('设置属性名前:' || wclob); + + docElem := gms_xmldom.getDocumentElement(doc); + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodeList, 0); + elemNode := gms_XMLDOM.makeElement(node); + --创建DOMAttr属性节点 + attr1 := gms_xmldom.createAttribute(doc,'category'); + isNull := gms_xmldom.isNull(attr1); + raise notice '%', ('DOMAttr : ' || case when isNull then 'Y' else 'N' end); + attr2 := gms_xmldom.setAttributeNode(elemNode, attr1); + + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改后的clob内容 + raise notice '%', ('设置属性名后:' || wclob); +END; +/ +NOTICE: 设置属性名前: + + + learning math + 张三 + 561 + + + +NOTICE: DOMAttr : N +NOTICE: 设置属性名后: + + + learning math + 张三 + 561 + + + +--(38)getElementsByTagName(doc DOMDocument, tagname IN VARCHAR2) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + nodeList gms_xmldom.DOMNodelist; + node gms_xmldom.DOMNODE; + wclob clob; + elemNode gms_xmldom.DOMElement; + attr1 gms_XMLDOM.DOMAttr; + attr2 gms_XMLDOM.DOMAttr; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + doc := gms_xmldom.newDOMDocument(var); + --打印设置属性名前的wclob + wclob := gms_xmldom.writeToClob(doc, wclob); + raise notice '%', ('设置属性名前:' || wclob); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodeList, 0); + elemNode := gms_XMLDOM.makeElement(node); + --创建DOMAttr属性节点 + attr1 := gms_xmldom.createAttribute(doc,'category'); + attr2 := gms_xmldom.setAttributeNode(elemNode, attr1); + + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改后的clob内容 + raise notice '%', ('设置属性名后:' || wclob); +END; +/ +NOTICE: 设置属性名前: + + + learning math + 张三 + 561 + + + +NOTICE: 设置属性名后: + + + learning math + 张三 + 561 + + + +--(39)createDocumentFragment(doc DOMDocument) +--(40)isNull(df DOMDocumentFragment) +--(41)makeNode(df DOMDocumentFragment) +--(42)writeToBuffer(n DOMDocumentFragment, buffer IN OUT VARCHAR2) +DECLARE + doc gms_XMLDOM.DOMDocument; + docfragment gms_XMLDOM.DOMDocumentFragment; + docfragmentnode gms_XMLDOM.DOMnode; + createelem gms_XMLDOM.DOMElement; + elemnode gms_XMLDOM.DOMNode; + text gms_XMLDOM.DOMText; + textnode gms_XMLDOM.DOMNode; + resNode gms_XMLDOM.DOMNode; + buf varchar2(4000); + isNull boolean; +BEGIN + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(); + docfragment := gms_XMLDOM.createDocumentFragment(doc); + isNull := gms_xmldom.isNull(docfragment); + raise notice '%', ('DOMDocumentFragment : ' || case when isNull then 'Y' else 'N' end); + docfragmentnode := gms_XMLDOM.makeNode(docfragment ); + --在文档片段添加内容 + --创建DOMElement对象 + createelem := gms_XMLDOM.createElement(doc,'test'); + elemnode := gms_XMLDOM.makeNode(createelem); + --创建内容 + text := gms_XMLDOM.createTextnode(doc,'testtext'); + textnode := gms_XMLDOM.makeNode(text); + --添加到指定位置 + resNode := gms_XMLDOM.appendChild(elemnode,textnode); + resNode := gms_XMLDOM.appendChild(docfragmentnode,elemnode); + --写入buffer + buf := gms_xmldom.writetobuffer(docfragment,buf); + --输出修改后的clob内容 + raise notice '%', ('文档片段为:' ||buf); +END; +/ +NOTICE: DOMDocumentFragment : N +NOTICE: 文档片段为: testtext +--(43)writeToClob(n DOMNode, cl IN OUT CLOB) +--(44)writeToClob(n DOMNode, cl IN OUT CLOB, pflag IN NUMBER, indent IN NUMBER) +--(45)writeToClob(doc DOMDocument, cl IN OUT CLOB, pflag IN NUMBER, indent IN NUMBER) +--(46)writeToBuffer(n DOMNode, buffer IN OUT VARCHAR2) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node1 gms_xmldom.DOMNODE; + wclob clob; + buffer varchar2; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getFirstChild(docNode); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node1 := gms_xmldom.item(nodeList, 0); + wclob := gms_xmldom.writeToClob(node1, wclob); + raise notice '%', ('writeToClob(DOMNode, clob):' || wclob); + + wclob := gms_xmldom.writeToClob(node1, wclob, 4, 0); + raise notice '%', ('writeToClob(DOMNode, clob, number, number):' || wclob); + + wclob := gms_xmldom.writeToClob(doc, wclob, 4, 0); + raise notice '%', ('writeToClob(DOMDocument, clob, number, number):' || wclob); + + buffer := gms_xmldom.writeToBuffer(node1, buffer); + raise notice '%', ('writeToBuffer(DOMNode, varchar2):' || buffer); +END; +/ +NOTICE: writeToClob(DOMNode, clob): + learning math + 张三 + 561 + + +NOTICE: writeToClob(DOMNode, clob, number, number):learning math张三561 +NOTICE: writeToClob(DOMDocument, clob, number, number):learning math张三561learning Python李四600learning C++王二500 +NOTICE: writeToBuffer(DOMNode, varchar2): + learning math + 张三 + 561 + + +--(47)setVersion(doc DOMDocument, version VARCHAR2) +DECLARE + doc gms_xmldom.DOMDocument; + cl clob; + x xml; +BEGIN + x := xml('ramesh'); + doc := gms_xmldom.newDomDocument(x); + gms_xmldom.setVersion(doc, '2.0'); + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', (cl); +END; +/ +NOTICE: + + ramesh + + +--(48)getFirstChild(n DOMNode) +--(49)getNodeName(n DOMNode) +--(50)getChildrenByTagName(elem DOMElement, name varchar2) +--(51)getDocumentElement(doc DOMDocument) +--(52)getNodeValue(n DOMNode) +--(53)getChildNodes(n DOMNode) +--(54)getLength(nl DOMNodeList) +--(55)getNodeType(n DOMNode) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodeList; + node gms_xmldom.DOMNode; + titleNode gms_xmldom.DOMNode; + elemNode gms_xmldom.DOMElement; + txt gms_xmldom.DOMText; + textNode gms_xmldom.DOMNode; + wclob clob; + llen integer; + n integer := 0; +BEGIN + var := xml(' + + + learning math + 张三 + 561 + + + + learning Python + 李四 + 600 + + + + learning C++ + 王二 + 500 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + + wclob := gms_xmldom.writeToClob(doc, wclob); + raise notice '%', ('xml内容是:' || wclob); + + --getDocumentElement + elemNode := gms_xmldom.getDocumentElement(doc); + bookListNode := gms_xmldom.getFirstChild(docNode); + --getChildrenByTagName + nodeList := gms_xmldom.getChildrenByTagName(elemNode, 'book'); + node := gms_xmldom.item(nodeList, 0); + --getFirstChild,getNodeName + titleNode := gms_xmldom.getFirstChild(node); + wclob := gms_xmldom.writeToClob(titleNode, wclob); + raise notice '%', (wclob); + raise notice '%', ('The nodeName is:' || gms_xmldom.getNodeName(titleNode)); + --element节点的nodeValue,为空 + raise notice '%', ('The nodeValue is:' || gms_xmldom.getNodeValue(titleNode)); + txt := gms_xmldom.getFirstChild(titleNode); + textNode := gms_xmldom.makeNode(txt); + raise notice '%', ('The nodeValue is:' || gms_xmldom.getNodeValue(textNode)); + --getChildNodes + nodeList := gms_xmldom.getChildNodes(bookListNode); + llen := gms_xmldom.getLength(nodeList); + raise notice '%', ('booklist子节点长度为:' || llen ); + + for i in 0..(llen-1) loop + node := gms_xmldom.item(nodeList, i); + --getNodeType + if gms_xmldom.getNodeType(node) = gms_xmldom.COMMENT_NODE then + n := n+1; + --comment节点的nodeValue + raise notice '%', ('第'||'个备注为:'||gms_xmldom.getNodeValue(node)); + end if; + end loop; + +END; +/ +NOTICE: xml内容是: + + + + learning math + 张三 + 561 + + + + learning Python + 李四 + 600 + + + + learning C++ + 王二 + 500 + + + +NOTICE: learning math + +NOTICE: The nodeName is:title +NOTICE: The nodeValue is: +NOTICE: The nodeValue is:learning math +NOTICE: booklist子节点长度为:6 +NOTICE: 第个备注为:这是第一个book节点 +NOTICE: 第个备注为:这是第二个book节点 +NOTICE: 第个备注为:这是第三个book节点 +--(56)getLocalName(a DOMAttr) +--(57)getLocalName(elem DOMElement) +--(58)getLocalName(n DOMnode, data OUT VARCHAR2) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + bookNode gms_xmldom.DOMNode; + titleElem gms_xmldom.DOMElement; + nodeList gms_xmldom.DOMNodelist; + + attr gms_xmldom.DOMAttr; + wclob clob; + localName varchar2; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + + wclob := gms_xmldom.writeToClob(doc, wclob); + raise notice '%', ('xml内容是:' || wclob); + + bookListNode := gms_xmldom.getFirstChild(docNode); + bookNode := gms_xmldom.getFirstChild(bookListNode); + nodeList := gms_xmldom.getChildNodes(bookNode); + titleElem := gms_xmldom.item(nodeList, 0); + + --element的localName + localName := gms_xmldom.getLocalName(titleElem); + raise notice '%', ('element的localName:' || localName); + --node的localName + localName := gms_xmldom.getLocalName(bookNode); + raise notice '%', ('node的localName:' || localName); + + --attr的LocalName + attr := gms_xmldom.createAttribute(doc, 'name'); + localName := gms_xmldom.getLocalName(attr); + raise notice '%', ('attr的LocalName:' || localName); +END; +/ +NOTICE: xml内容是: + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +NOTICE: element的localName:title +NOTICE: node的localName:book +NOTICE: attr的LocalName:name +--(59)hasChildNodes(n DOMNode) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + childnode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + wclob clob; + haschild boolean; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + docNode := gms_XMLDOM.makeNode(doc); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改前的内容 + raise notice '%', ('xml内容是:' || wclob); + + --获取存在子节点的节点 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,1); + --判断该节点是否有子节点 + haschild := gms_XMLDOM.hasChildNodes(node); + raise notice '%', ('The result is:' || case when haschild then 'Y' else 'N' end); + + --获取不存在子节点的节点 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'press'); + node := gms_XMLDOM.item(nodelist,0); + --判断该节点是否有子节点 + haschild := gms_XMLDOM.hasChildNodes(node); + raise notice '%', ('The result is:' || case when haschild then 'Y' else 'N' end); +END; +/ +NOTICE: xml内容是: + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +NOTICE: The result is:Y +NOTICE: The result is:N +--(60)cloneNode(n DOMNode, deep boolean)(无子项) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + childnode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + clonenode gms_XMLDOM.DOMNode; + resNode gms_XMLDOM.DOMNode; + insertnode gms_XMLDOM.DOMNode; + wclob clob; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改前的内容 + raise notice '%', ('克隆前:' ||wclob); + + docelem := gms_xmldom.getDocumentElement(doc); + docNode := gms_XMLDOM.makeNode(docelem); + --获取对应子元素 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,0); + --克隆节点,不克隆节点的子节点 + clonenode := gms_XMLDOM.clonenode(node,false); + insertnode := gms_XMLDOM.insertbefore(docNode,clonenode,node); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改后的clob内容 + raise notice '%', ('克隆(无子项)后:' ||wclob); +END; +/ +NOTICE: 克隆前: + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +NOTICE: 克隆(无子项)后: + + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +--cloneNode(n DOMNode, deep boolean)(有子项) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + childnode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + clonenode gms_XMLDOM.DOMNode; + insertnode gms_XMLDOM.DOMNode; + n gms_XMLDOM.DOMNode; + wclob clob; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改前的内容 + raise notice '%', ('克隆前:' ||wclob); + + docelem := gms_xmldom.getDocumentElement(doc); + docNode := gms_XMLDOM.makeNode(docelem); + --获取对应子元素 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,0); + --克隆节点,克隆节点的子节点 + clonenode := gms_XMLDOM.clonenode(node, true); + insertnode := gms_XMLDOM.insertbefore(docNode,clonenode,node); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改后的clob内容 + raise notice '%', ('克隆(有子项)后:' ||wclob); +END; +/ +NOTICE: 克隆前: + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +NOTICE: 克隆(有子项)后: + + + learning math + 张三 + 561 + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +--(61)getAttributes(n DOMNode) +--(62)isNull(nnm DOMNamedNodeMap) +--(63)getLength(nnm DOMNamedNodeMap) +--(64)item(nnm DOMNamedNodeMap, idx IN PLS_INTEGER) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + node gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + nodelist gms_XMLDOM.DOMNodelist; + attrmap gms_XMLDOM.DOMNamedNodeMap; + length integer; + isNull boolean; + attr gms_xmldom.DOMAttr; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + --返回新的document实例 + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc ); + docelem := gms_xmldom.getDocumentElement(doc); + nodelist := gms_xmldom.getElementsByTagName(docelem,'book'); + node := gms_xmldom.item(nodelist,0); + --获取第一个EMP节点的属性 + attrmap := gms_xmldom.getAttributes(node); + isNull := gms_xmldom.isNull(attrmap); + raise notice '%', ('isNull(DOMNamedNodeMap):' || case when isNull then 'Y' else 'N' end); + + --查看map的长度验证结果 + length := gms_xmldom.getlength(attrmap); + raise notice '%', ('第一个book节点的属性长度为:' || length ); + + --item(DOMNamedNodeMap) + attr := gms_xmldom.item(attrmap, 0); +END; +/ +NOTICE: isNull(DOMNamedNodeMap):N +NOTICE: 第一个book节点的属性长度为:3 +--(65)freeDocument(doc DOMDocument) +--(66)freeNodeList(nl DOMNodeList) +--(67)isNull(nl DOMNodeList) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + nodeList gms_xmldom.DOMNodelist; + wclob clob; + isNull boolean; + node gms_XMLDOM.DOMNode; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + --返回一个新document实例 + doc := gms_XMLDOM.newDOMDocument(var); + --设置XML文档信息 + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出clob内容 + raise notice '%', (wclob); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node := gms_xmldom.item(nodeList, 0); + --释放node + gms_xmldom.freeDocument(doc); + --检查node是否释放成功 + isNull := gms_xmldom.isNull(doc); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + --释放了父节点,子节点是否释放 + isNull := gms_xmldom.isNull(nodeList); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + isNull := gms_xmldom.isNull(node); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + + node := gms_xmldom.item(nodeList, 0); + wclob := gms_xmldom.writetoclob(node, wclob); + raise notice '%', (wclob); + + gms_xmldom.freeNodeList(nodeList); + isNull := gms_xmldom.isNull(nodeList); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + isNull := gms_xmldom.isNull(node); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + + wclob := gms_xmldom.writetoclob(node, wclob); + raise notice '%', (wclob); +END; +/ +NOTICE: + + + learning math + 张三 + 561 + + + +NOTICE: The result is : Y +NOTICE: The result is : N +NOTICE: The result is : Y +NOTICE: + +NOTICE: The result is : Y +NOTICE: The result is : Y +NOTICE: +--(68)freeNode(nl DOMNode) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + nodeList gms_xmldom.DOMNodelist; + wclob clob; + isNull boolean; + node gms_XMLDOM.DOMNode; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + --返回一个新document实例 + doc := gms_XMLDOM.newDOMDocument(var); + --设置XML文档信息 + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出clob内容 + raise notice '%', (wclob); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node := gms_xmldom.item(nodeList, 0); + --检查node是否释放成功 + isNull := gms_xmldom.isNull(doc); + raise notice '%', ('The document is : ' || case when isNull then 'Y' else 'N' end); + isNull := gms_xmldom.isNull(node); + raise notice '%', ('The node is : ' || case when isNull then 'Y' else 'N' end); + wclob := gms_xmldom.writetoclob(node, wclob); + raise notice '%', (wclob); + + gms_xmldom.freeNode(node); + isNull := gms_xmldom.isNull(node); + raise notice '%', ('The node is : ' || case when isNull then 'Y' else 'N' end); +END; +/ +NOTICE: + + + learning math + 张三 + 561 + + + +NOTICE: The document is : N +NOTICE: The node is : N +NOTICE: + learning math + 张三 + 561 + + +NOTICE: The node is : Y +--(69)insertBefore(n DOMNode, newchild IN DOMNode) +--在同一位置插入同名同内容的节点 +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + elemnode gms_XMLDOM.DOMNode; + bookListNode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + createelem gms_XMLDOM.DOMElement; + resNode gms_XMLDOM.DOMNode; + text gms_XMLDOM.DOMText; + textnode gms_XMLDOM.DOMNode; + buf varchar2(4000); +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getfirstchild(docNode); + + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + raise notice '%', ('XML内容:' ||buf); + + --获取对应子元素 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,0); + --输出buf内容 + buf := gms_xmldom.writetobuffer(node,buf); + raise notice '%', ('book内容:' ||buf); + + --创建DOMElement对象 + createelem := gms_XMLDOM.createelement(doc,'test'); + elemnode := gms_XMLDOM.makenode(createelem); + --创建内容 + text := gms_XMLDOM.createTextnode(doc,'testtext'); + textnode := gms_XMLDOM.makenode(text); + resNode := gms_XMLDOM.appendchild(elemnode,textnode); + + --添加到指定位置 + resNode := gms_XMLDOM.insertbefore(bookListNode, elemnode, node); + --写入buffer + buf := gms_xmldom.writetobuffer(doc, buf); + --输出buf内容 + raise notice '%', ('第一次appendchild后:' ||buf); + + createelem := gms_XMLDOM.createelement(doc,'test'); + elemnode := gms_XMLDOM.makenode(createelem); + --创建内容 + text := gms_XMLDOM.createTextnode(doc,'testtext'); + textnode := gms_XMLDOM.makenode(text); + resNode := gms_XMLDOM.appendchild(elemnode,textnode); + resNode := gms_XMLDOM.insertbefore(bookListNode, elemnode, node); + --写入buffer + buf := gms_xmldom.writetobuffer(doc, buf); + --输出buf内容 + raise notice '%', ('第一次appendchild后:' ||buf); +END; +/ +NOTICE: XML内容: + + + learning math + 张三 + 561 + + + +NOTICE: book内容: + learning math + 张三 + 561 + + +NOTICE: 第一次appendchild后: + + testtext + + learning math + 张三 + 561 + + + +NOTICE: 第一次appendchild后: + + testtext + testtext + + learning math + 张三 + 561 + + + +--(70)appendChild(n DOMNode, newchild IN DOMNode) +--插入同名同内容节点 +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + elemnode gms_XMLDOM.DOMNode; + bookListNode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + createelem gms_XMLDOM.DOMElement; + resNode gms_XMLDOM.DOMNode; + text gms_XMLDOM.DOMText; + textnode gms_XMLDOM.DOMNode; + buf varchar2(4000); +BEGIN + var := xml(' + + learning math + 张三 + 561 + testtext + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getfirstchild(docNode); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + --输出buf内容 + raise notice '%', ('XML内容:' ||buf); + --获取对应子元素 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,0); + + --创建DOMElement对象 + createelem := gms_XMLDOM.createelement(doc,'test'); + elemnode := gms_XMLDOM.makenode(createelem); + --创建内容 + text := gms_XMLDOM.createTextnode(doc,'testtext'); + textnode := gms_XMLDOM.makenode(text); + resNode := gms_XMLDOM.appendchild(elemnode,textnode); + + --添加到指定位置 + resNode := gms_XMLDOM.appendchild(node,elemnode); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + --输出buf内容 + raise notice '%', ('appendchild后:' ||buf); +END; +/ +NOTICE: XML内容: + + + learning math + 张三 + 561 + testtext + + + +NOTICE: appendchild后: + + + learning math + 张三 + 561 + testtext + testtext + + + +--(71)getNodeValueAsClob(n domnode) +--获取CData片段节点的值 +DECLARE + var clob; + doc gms_XMLDOM.DOMDocument; + ndoc gms_xmldom.DOMNode; + node gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + listlength integer; + buf varchar2(4000); + n integer := 0; +BEGIN + var := xml(' + + + learning math + 张三 + 561 + + + + learning Python + 李四 + 600 + + + + learning C++ + 王二 + 500 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + ndoc := gms_xmldom.makenode(doc); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + raise notice '%', ('XML内容:' ||buf); + + node := gms_xmldom.getfirstchild(ndoc); + --获取第一层子节点列表 + nodelist := gms_xmldom.getchildnodes(node); + listlength := gms_XMLDOM.getlength(nodelist); + --检索第一层子节点 + FOR i in 0..(listlength-1) loop + node := gms_XMLDOM.item(nodelist,i); + --筛选CDATA片段节点 + IF gms_xmldom.getnodetype(node) = gms_xmldom.cdata_section_node THEN + n := n+1; + --输出CDATA片段节点的值 + raise notice '%', ('第' || n || '个CDATA片段值为' || gms_XMLDOM.getNodeValueAsClob(node) ); + END IF; + END LOOP; +END; +/ +NOTICE: XML内容: + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +NOTICE: 第1个CDATA片段值为testcdata1 +NOTICE: 第2个CDATA片段值为testcdata2 +NOTICE: 第3个CDATA片段值为testcdata3 +--(72)getOwnerDocument(n DOMNode) +DECLARE + var clob; + doc gms_XMLDOM.DOMDocument; + doc1 gms_XMLDOM.DOMDocument; + ndoc gms_xmldom.DOMNode; + node gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + buf varchar2(4000); +BEGIN + var := ' + + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'; + --返回新的document + doc := gms_XMLDOM.newDOMDocument(var); + ndoc := gms_xmldom.makenode(doc); + --无关联document的节点 + doc1 := gms_XMLDOM.getownerdocument(node); + --写入buffer + buf := gms_xmldom.writetobuffer(doc1,buf); + --输出buf内容 + raise notice '%', ('无关联document的节点:' ||buf); + --获取节点列表 + node := gms_XMLDOM.getfirstchild(ndoc); + nodelist := gms_XMLDOM.getchildnodes(node); + node := gms_XMLDOM.item(nodelist,0); + --有关联document的节点 + doc1 := gms_XMLDOM.getownerdocument(node); + --写入buffer + buf := gms_xmldom.writetobuffer(doc1,buf); + --输出buf内容 + raise notice '%', ('有关联document的节点:' ||buf); +END; +/ +NOTICE: 无关联document的节点: +NOTICE: 有关联document的节点: + + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +--(73)gms_xmlparser.newParser +--(74)gms_xmlparser.parseClob(p gms_xmlparser.Parser, doc CLOB) +--(75)gms_xmlparser.getDocument(p gms_xmlparser.Parser) +DECLARE + var clob; + parser gms_XMLPARSER.parser; + doc gms_XMLDOM.DOMDocument; + buf varchar2(4000); +BEGIN + var := ' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'; + parser := gms_XMLPARSER.newParser(); + gms_XMLPARSER.parseClob(parser, var); + doc := gms_XMLPARSER.getDocument(parser); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + --输出buf内容 + raise notice '%', ('XML内容:' ||buf); +END; +/ +NOTICE: XML内容: + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + + + +--(76)gms_xmlparser.parseBuffer(p gms_xmlparser.Parser,doc VARCHAR2) +DECLARE + var varchar2(4000); + parser gms_XMLPARSER.parser; + doc gms_XMLDOM.DOMDocument; + buf varchar2(4000); +BEGIN + var := ' + + + + + +]> + + &name; +
&address;
+
'; + parser := gms_XMLPARSER.newParser(); + gms_XMLPARSER.parsebuffer(parser,var); + doc := gms_XMLPARSER.getDocument(parser); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + --输出buf内容 + raise notice '%', ('XML内容:' ||buf); +END; +/ +NOTICE: XML内容: +]> + + 青岛 +
宁夏路
+
+ +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + wclob clob; +BEGIN + var := xml(' + + learning math + + 张三 + 561 + + + +'); + doc := gms_xmldom.newDOMDocument(var); + + wclob := gms_xmldom.writeToClob(doc, wclob); + --输出修改后的clob内容 + raise notice '%', ('xml内容为:' || wclob); +END; +/ +NOTICE: xml内容为: + + + learning math + 张三 + 561 + + + +DECLARE + l_dom gms_xmldom.DOMDocument; + l_clob clob := ''; + v_element gms_xmldom.domelement; + l_nodelist1 gms_xmldom.domnodelist; + l_nodelist2 gms_xmldom.domnodelist; + l_node gms_xmldom.DOMNode; +begin + l_dom := gms_xmldom.newDomDocument(l_clob); + l_nodelist1 := gms_xmldom.getelementsbytagname(l_dom,'jwg'); + l_node := gms_xmldom.item(l_nodelist1,0); + v_element := gms_xmldom.makeelement(l_node); + l_nodelist2 := gms_xmldom.getelementsbytagname(l_dom,'abc'); + l_node := gms_xmldom.item(l_nodelist2,0); + raise notice '%', (gms_xmldom.getnodetype(gms_xmldom.getfirstchild(l_node))); +end; +/ +NOTICE: +DECLARE + l_dom gms_xmldom.DOMDocument; + l_clob clob := ''; + v_element gms_xmldom.domelement; + l_nodelist gms_xmldom.domnodelist; + l_node gms_xmldom.DOMNode; +begin + l_dom := gms_xmldom.newDomDocument(l_clob); + l_nodelist := gms_xmldom.getelementsbytagname(l_dom,'abc'); + l_node := gms_xmldom.item(l_nodelist,0); + gms_xmldom.freeNode(gms_xmldom.getfirstchild(l_node)); +end; +/ +--makeCharacterData函数 +declare + v_clob clob; + v_doc gms_xmldom.domdocument; + v_nodelist gms_xmldom.domnodelist; + v_node1 gms_xmldom.domnode; + v_chardata1 gms_xmldom.DOMCharacterData; + v_char1 varchar2(100); +begin + v_clob:=' + + + 手动测试 + 测试勣 + 中文测试 + + '; + + v_doc := gms_xmldom.newdomdocument(v_clob); + v_nodelist := gms_xmldom.getelementsbytagname(v_doc, 'persons'); + v_node1 := gms_xmldom.getfirstchild(gms_xmldom.item(v_nodelist, 0)); + v_char1 := gms_xmldom.getnodevalue(v_node1); + raise notice '%', (v_char1); + + v_chardata1 := gms_xmldom.makeCharacterData(v_node1); + raise notice '%', (gms_XMLDOM.GETLENGTH(v_chardata1)); +end; +/ +NOTICE: 中文测试 +NOTICE: 4 diff --git a/src/test/regress/expected/plpython3u/plpython_gms_xslprocessor.out b/src/test/regress/expected/plpython3u/plpython_gms_xslprocessor.out new file mode 100644 index 0000000000..36064fb006 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_gms_xslprocessor.out @@ -0,0 +1,256 @@ +----gms_xslprocessor.selectnodes +----gms_xslprocessor.valueof +declare + x xml; + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + x := xml( + ' + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + Harry Potter + J.K. Rowling + 2005 + 29.99 + + ' + ); + doc := gms_xmldom.newDomDocument(x); + node := gms_xmldom.makenode(gms_xmldom.getDocumentElement(doc)); + nodelist := gms_xslprocessor.selectnodes(node,'book'); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ +NOTICE: title: Everyday Italian, author: Giada De Laurentiis +NOTICE: title: Harry Potter, author: J.K. Rowling +declare + x xml; + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + x := xml( + ' + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + Harry Potter + J.K. Rowling + 2005 + 29.99 + + ' + ); + doc := gms_xmldom.newDomDocument(x); + node := gms_xmldom.makenode(gms_xmldom.getDocumentElement(doc)); + nodelist := gms_xslprocessor.selectnodes(node,'//*[not(*)]'); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ +--路径错误 +declare + x xml; + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + x := xml( + ' + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + Harry Potter + J.K. Rowling + 2005 + 29.99 + + ' + ); + doc := gms_xmldom.newDomDocument(x); + node := gms_xmldom.makenode(gms_xmldom.getDocumentElement(doc)); + nodelist := gms_xslprocessor.selectnodes(node,'book/author/year'); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ +--入参为null +declare + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + nodelist := gms_xslprocessor.selectnodes(NULL,'book/author/year'); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ +declare + x xml; + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + x := xml( + ' + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + Harry Potter + J.K. Rowling + 2005 + 29.99 + + ' + ); + doc := gms_xmldom.newDomDocument(x); + node := gms_xmldom.makenode(gms_xmldom.getDocumentElement(doc)); + nodelist := gms_xslprocessor.selectnodes(node,''); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ +ERROR: XPath compilation failed: Xpath is null +CONTEXT: PL/pgSQL function inline_code_block line 28 at assignment +--指定namespace +DECLARE + doc gms_xmldom.DOMDocument; + root gms_xmldom.DOMNode; + rootElem gms_xmldom.DOMElement; + bookElem gms_xmldom.DOMElement; + bookElemNode gms_xmldom.DOMNode; + titleElem gms_xmldom.DOMElement; + titleElemNode gms_xmldom.DOMNode; + titleText gms_xmldom.DOMText; + titleTextNode gms_xmldom.DOMNode; + authorElem gms_xmldom.DOMElement; + authorElemNode gms_xmldom.DOMNode; + authorText gms_xmldom.DOMText; + authorTextNode gms_xmldom.DOMNode; + pageElem gms_xmldom.DOMElement; + pageElemNode gms_xmldom.DOMNode; + pageText gms_xmldom.DOMText; + pageTextNode gms_xmldom.DOMNode; + resnode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node gms_xmldom.DOMNode; + nodeElem gms_xmldom.DOMElement; + attr gms_xmldom.DOMAttr; + resAttr gms_xmldom.DOMAttr; + len integer; +BEGIN + --booklist + doc := gms_xmldom.createDocument('http://www.runoob.com/xml/', 'booklist', null); + rootElem := gms_xmldom.getDocumentElement(doc); + gms_xmldom.setAttribute(rootElem, 'type', 'science and engineering', 'http://www.runoob.com/xml/'); + root := gms_xmldom.makeNode(rootElem); + --book + bookElem := gms_xmldom.createElement(doc, 'book', 'http://www.runoob.com/xml/'); + gms_xmldom.setAttribute(bookElem, 'category', 'python', 'http://www.runoob.com/xml/'); + bookElemNode := gms_xmldom.makeNode(bookElem); + --title + titleElem := gms_xmldom.createElement(doc, 'book', 'http://www.runoob.com/xml/'); + titleElemNode := gms_xmldom.makeNode(titleElem); + --attribute of title + titleText := gms_xmldom.createTextNode(doc, 'learning python'); + titleTextNode := gms_xmldom.makeNode(titleText); + resnode := gms_xmldom.appendChild(titleElemNode, titleTextNode); + --author + authorElem := gms_xmldom.createElement(doc, 'author', 'http://www.runoob.com/xml/'); + authorElemNode := gms_xmldom.makeNode(authorElem); + --attribute of author + authorText := gms_xmldom.createTextNode(doc, '张三'); + authorTextNode := gms_xmldom.makeNode(authorText); + authorTextNode := gms_xmldom.appendChild(authorElemNode, authorTextNode); + --pageNumber + pageElem := gms_xmldom.createElement(doc, 'pageNumber', 'http://www.runoob.com/xml/'); + pageElemNode := gms_xmldom.makeNode(pageElem); + --attribute of pageNumber + pageText := gms_xmldom.createTextNode(doc, '600'); + pageTextNode := gms_xmldom.makeNode(pageText); + resnode := gms_xmldom.appendChild(pageElemNode, pageTextNode); + resnode := gms_xmldom.appendChild(bookElemNode, titleElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, authorElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, pageElemNode); + resnode := gms_xmldom.appendChild(root, bookElemNode); + + nodelist := gms_xslprocessor.selectnodes(root,'book','http://www.runoob.com/xml/'); + len := gms_xmldom.getLength(nodeList); + for i in 0..len-1 loop + node := gms_xmldom.item(nodeList,i); + nodeElem := gms_xmldom.makeElement(node); + attr := gms_xmldom.createAttribute(doc, 'num', 'http://www.runoob.com/xml/'); + resAttr := gms_xmldom.setAttributeNode(nodeElem, attr, 'http://www.runoob.com/xml/'); + end loop; +END; +/ diff --git a/src/test/regress/expected/plpython3u/plpython_import.out b/src/test/regress/expected/plpython3u/plpython_import.out new file mode 100644 index 0000000000..854e989eaf --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_import.out @@ -0,0 +1,79 @@ +-- import python modules +CREATE FUNCTION import_fail() returns text + AS +'try: + import foosocket +except ImportError: + return "failed as expected" +return "succeeded, that wasn''t supposed to happen"' + LANGUAGE plpython3u; +CREATE FUNCTION import_succeed() returns text + AS +'try: + import array + import bisect + import calendar + import cmath + import errno + import math + import operator + import random + import re + import string + import time +except Exception as ex: + plpy.notice("import failed -- %s" % str(ex)) + return "failed, that wasn''t supposed to happen" +return "succeeded, as expected"' + LANGUAGE plpython3u; +CREATE FUNCTION import_test_one(p text) RETURNS text + AS +'try: + import hashlib + digest = hashlib.sha1(p.encode("ascii")) +except ImportError: + import sha + digest = sha.new(p) +return digest.hexdigest()' + LANGUAGE plpython3u; +CREATE FUNCTION import_test_two(u users) RETURNS text + AS +'plain = u["fname"] + u["lname"] +try: + import hashlib + digest = hashlib.sha1(plain.encode("ascii")) +except ImportError: + import sha + digest = sha.new(plain); +return "sha hash of " + plain + " is " + digest.hexdigest()' + LANGUAGE plpython3u; +-- import python modules +-- +SELECT import_fail(); + import_fail +-------------------- + failed as expected +(1 row) + +SELECT import_succeed(); + import_succeed +------------------------ + succeeded, as expected +(1 row) + +-- test import and simple argument handling +-- +SELECT import_test_one('sha hash of this string'); + import_test_one +------------------------------------------ + a04e23cb9b1a09cd1051a04a7c571aae0f90346c +(1 row) + +-- test import and tuple argument handling +-- +select import_test_two(users) from users where fname = 'willem'; + import_test_two +------------------------------------------------------------------- + sha hash of willemdoe is 3cde6b574953b0ca937b4d76ebc40d534d910759 +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_newline.out b/src/test/regress/expected/plpython3u/plpython_newline.out new file mode 100644 index 0000000000..2bc149257e --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_newline.out @@ -0,0 +1,30 @@ +-- +-- Universal Newline Support +-- +CREATE OR REPLACE FUNCTION newline_lf() RETURNS integer AS +E'x = 100\ny = 23\nreturn x + y\n' +LANGUAGE plpython3u; +CREATE OR REPLACE FUNCTION newline_cr() RETURNS integer AS +E'x = 100\ry = 23\rreturn x + y\r' +LANGUAGE plpython3u; +CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS +E'x = 100\r\ny = 23\r\nreturn x + y\r\n' +LANGUAGE plpython3u; +SELECT newline_lf(); + newline_lf +------------ + 123 +(1 row) + +SELECT newline_cr(); + newline_cr +------------ + 123 +(1 row) + +SELECT newline_crlf(); + newline_crlf +-------------- + 123 +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_params.out b/src/test/regress/expected/plpython3u/plpython_params.out new file mode 100644 index 0000000000..d1a36f3623 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_params.out @@ -0,0 +1,64 @@ +-- +-- Test named and nameless parameters +-- +CREATE FUNCTION test_param_names0(integer, integer) RETURNS int AS $$ +return args[0] + args[1] +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$ +assert a0 == args[0] +assert a1 == args[1] +return True +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$ +assert u == args[0] +if isinstance(u, dict): + # stringify dict the hard way because otherwise the order is implementation-dependent + u_keys = list(u.keys()) + u_keys.sort() + s = '{' + ', '.join([repr(k) + ': ' + repr(u[k]) for k in u_keys]) + '}' +else: + s = str(u) +return s +$$ LANGUAGE plpython3u; +-- use deliberately wrong parameter names +CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$ +try: + assert a1 == args[0] + return False +except NameError as e: + assert e.args[0].find("a1") > -1 + return True +$$ LANGUAGE plpython3u; +SELECT test_param_names0(2,7); + test_param_names0 +------------------- + 9 +(1 row) + +SELECT test_param_names1(1,'text'); + test_param_names1 +------------------- + t +(1 row) + +SELECT test_param_names2(users) from users; + test_param_names2 +----------------------------------------------------------------------- + {'fname': 'jane', 'lname': 'doe', 'userid': 1, 'username': 'j_doe'} + {'fname': 'john', 'lname': 'doe', 'userid': 2, 'username': 'johnd'} + {'fname': 'willem', 'lname': 'doe', 'userid': 3, 'username': 'w_doe'} + {'fname': 'rick', 'lname': 'smith', 'userid': 4, 'username': 'slash'} +(4 rows) + +SELECT test_param_names2(NULL); + test_param_names2 +------------------- + None +(1 row) + +SELECT test_param_names3(1); + test_param_names3 +------------------- + t +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_populate.out b/src/test/regress/expected/plpython3u/plpython_populate.out new file mode 100644 index 0000000000..4db75b074f --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_populate.out @@ -0,0 +1,22 @@ +INSERT INTO users (fname, lname, username) VALUES ('jane', 'doe', 'j_doe'); +INSERT INTO users (fname, lname, username) VALUES ('john', 'doe', 'johnd'); +INSERT INTO users (fname, lname, username) VALUES ('willem', 'doe', 'w_doe'); +INSERT INTO users (fname, lname, username) VALUES ('rick', 'smith', 'slash'); +-- multi table tests +-- +INSERT INTO taxonomy (name) VALUES ('HIV I') ; +INSERT INTO taxonomy (name) VALUES ('HIV II') ; +INSERT INTO taxonomy (name) VALUES ('HCV') ; +INSERT INTO entry (accession, txid) VALUES ('A00001', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00002', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00003', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00004', '2') ; +INSERT INTO entry (accession, txid) VALUES ('A00005', '2') ; +INSERT INTO entry (accession, txid) VALUES ('A00006', '3') ; +INSERT INTO sequences (sequence, eid, product, multipart) VALUES ('ABCDEF', 1, 'env', 'true') ; +INSERT INTO xsequences (sequence, pid) VALUES ('GHIJKL', 1) ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 2, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 3, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 4, 'gag') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 5, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 6, 'ns1') ; diff --git a/src/test/regress/expected/plpython3u/plpython_quote.out b/src/test/regress/expected/plpython3u/plpython_quote.out new file mode 100644 index 0000000000..214437030d --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_quote.out @@ -0,0 +1,54 @@ +-- test quoting functions +CREATE FUNCTION quote(t text, how text) RETURNS text AS $$ + if how == "literal": + return plpy.quote_literal(t) + elif how == "nullable": + return plpy.quote_nullable(t) + elif how == "ident": + return plpy.quote_ident(t) + else: + raise plpy.Error("unrecognized quote type %s" % how) +$$ LANGUAGE plpython3u; +SELECT quote(t, 'literal') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''''), + ('xyzv')) AS v(t); + quote +----------- + 'abc' + 'a''bc' + '''abc''' + '''' + 'xyzv' +(5 rows) + +SELECT quote(t, 'nullable') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + (NULL)) AS v(t); + quote +----------- + 'abc' + 'a''bc' + '''abc''' + NULL + '''' + NULL +(6 rows) + +SELECT quote(t, 'ident') FROM (VALUES + ('abc'), + ('a b c'), + ('a " ''abc''')) AS v(t); + quote +-------------- + abc + "a b c" + "a "" 'abc'" +(3 rows) + diff --git a/src/test/regress/expected/plpython3u/plpython_record.out b/src/test/regress/expected/plpython3u/plpython_record.out new file mode 100644 index 0000000000..bfc116a014 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_record.out @@ -0,0 +1,373 @@ +-- +-- Test returning tuples +-- +CREATE TABLE table_record ( + first text, + second int4 + ) ; +CREATE TYPE type_record AS ( + first text, + second int4 + ) ; +CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$ +return first + '_in_to_out'; +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_in_out_params_multi(first in text, + second out text, third out text) AS $$ +return (first + '_record_in_to_out_1', first + '_record_in_to_out_2'); +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_inout_params(first inout text) AS $$ +return first + '_inout'; +$$ LANGUAGE plpython3u; +-- Test tuple returning functions +SELECT * FROM test_table_record_as('dict', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('dict', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_table_record_as('dict', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_table_record_as('dict', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_table_record_as('dict', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('tuple', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('tuple', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_table_record_as('tuple', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_table_record_as('tuple', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_table_record_as('tuple', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('list', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('list', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_table_record_as('list', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_table_record_as('list', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_table_record_as('list', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('obj', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('obj', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_table_record_as('obj', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_table_record_as('obj', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_table_record_as('obj', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('dict', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('dict', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_type_record_as('dict', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_type_record_as('dict', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_type_record_as('dict', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('tuple', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('tuple', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_type_record_as('tuple', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_type_record_as('tuple', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_type_record_as('tuple', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('list', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('list', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_type_record_as('list', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_type_record_as('list', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_type_record_as('list', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('obj', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('obj', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_type_record_as('obj', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_type_record_as('obj', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_type_record_as('obj', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('str', 'one', 1, false); + first | second +-------+-------- + 'one' | 1 +(1 row) + +SELECT * FROM test_in_out_params('test_in'); + second +------------------- + test_in_in_to_out +(1 row) + +SELECT * FROM test_in_out_params_multi('test_in'); + second | third +----------------------------+---------------------------- + test_in_record_in_to_out_1 | test_in_record_in_to_out_2 +(1 row) + +SELECT * FROM test_inout_params('test_in'); + first +--------------- + test_in_inout +(1 row) + +-- try changing the return types and call functions again +ALTER TABLE table_record ADD COLUMN tmp_col text; +ALTER TABLE table_record DROP COLUMN first; +ALTER TABLE table_record DROP COLUMN second; +ALTER TABLE table_record ADD COLUMN first text; +ALTER TABLE table_record ADD COLUMN second int4; +ALTER TABLE table_record DROP COLUMN tmp_col; +SELECT * FROM test_table_record_as('obj', 'one', 1, false); + first | second +-------+-------- + one | 1 +(1 row) + +ALTER TYPE type_record ADD ATTRIBUTE tmp_attr int; +ALTER TYPE type_record DROP ATTRIBUTE first; +ALTER TYPE type_record DROP ATTRIBUTE second; +ALTER TYPE type_record ADD ATTRIBUTE first text; +ALTER TYPE type_record ADD ATTRIBUTE second int4; +ALTER TYPE type_record DROP ATTRIBUTE tmp_attr; +SELECT * FROM test_type_record_as('obj', 'one', 1, false); + first | second +-------+-------- + one | 1 +(1 row) + +-- errors cases +CREATE FUNCTION test_type_record_error1() RETURNS type_record AS $$ + return { 'first': 'first' } +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_record_error1(); +ERROR: key "second" not found in mapping +HINT: To return null in a column, add the value None to the mapping with the key named after the column. +CONTEXT: while creating return value +CREATE FUNCTION test_type_record_error2() RETURNS type_record AS $$ + return [ 'first' ] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_record_error2(); +ERROR: length of returned sequence did not match number of columns in row +CONTEXT: while creating return value +CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$ + class type_record: pass + type_record.first = 'first' + return type_record +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_record_error3(); +ERROR: attribute "second" does not exist in Python object +HINT: To return null in a column, let the returned object have an attribute named after column with value None. +CONTEXT: while creating return value +CREATE FUNCTION test_type_record_error4() RETURNS type_record AS $$ + return 'foo' +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_record_error4(); +ERROR: malformed record literal: "foo" +DETAIL: Missing left parenthesis. +CONTEXT: while creating return value diff --git a/src/test/regress/expected/plpython3u/plpython_schema.out b/src/test/regress/expected/plpython3u/plpython_schema.out new file mode 100644 index 0000000000..3ec331c0f0 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_schema.out @@ -0,0 +1,43 @@ +CREATE TABLE users ( + fname text not null, + lname text not null, + username text, + userid serial, + PRIMARY KEY(lname, fname) + ) ; +NOTICE: CREATE TABLE will create implicit sequence "users_userid_seq" for serial column "users.userid" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "users_pkey" for table "users" +CREATE INDEX users_username_idx ON users(username); +CREATE INDEX users_fname_idx ON users(fname); +CREATE INDEX users_lname_idx ON users(lname); +CREATE INDEX users_userid_idx ON users(userid); +CREATE TABLE taxonomy ( + id serial primary key, + name text unique + ) ; +NOTICE: CREATE TABLE will create implicit sequence "taxonomy_id_seq" for serial column "taxonomy.id" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "taxonomy_pkey" for table "taxonomy" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "taxonomy_name_key" for table "taxonomy" +CREATE TABLE entry ( + accession text not null primary key, + eid serial unique, + txid int2 not null references taxonomy(id) + ) ; +NOTICE: CREATE TABLE will create implicit sequence "entry_eid_seq" for serial column "entry.eid" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "entry_pkey" for table "entry" +NOTICE: CREATE TABLE / UNIQUE will create implicit index "entry_eid_key" for table "entry" +CREATE TABLE sequences ( + eid int4 not null references entry(eid), + pid serial primary key, + product text not null, + sequence text not null, + multipart bool default 'false' + ) ; +NOTICE: CREATE TABLE will create implicit sequence "sequences_pid_seq" for serial column "sequences.pid" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "sequences_pkey" for table "sequences" +CREATE INDEX sequences_product_idx ON sequences(product) ; +CREATE TABLE xsequences ( + pid int4 not null references sequences(pid), + sequence text not null + ) ; +CREATE INDEX xsequences_pid_idx ON xsequences(pid) ; diff --git a/src/test/regress/expected/plpython3u/plpython_setof.out b/src/test/regress/expected/plpython3u/plpython_setof.out new file mode 100644 index 0000000000..9f6d8f2f29 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_setof.out @@ -0,0 +1,124 @@ +-- +-- Test returning SETOF +-- +CREATE FUNCTION test_setof_error() RETURNS SETOF text AS $$ +return 37 +$$ LANGUAGE plpython3u; +SELECT test_setof_error(); +ERROR: returned object cannot be iterated +DETAIL: PL/Python set-returning functions must return an iterable object. +CONTEXT: referenced column: test_setof_error +CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$ +return [ content ]*count +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$ +t = () +for i in range(count): + t += ( content, ) +return t +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$ +class producer: + def __init__ (self, icount, icontent): + self.icontent = icontent + self.icount = icount + def __iter__ (self): + return self + def __next__ (self): + if self.icount == 0: + raise StopIteration + self.icount -= 1 + return self.icontent +return producer(count, content) +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_setof_spi_in_iterator() RETURNS SETOF text AS +$$ + for s in ('Hello', 'Brave', 'New', 'World'): + yield s +$$ +LANGUAGE plpython3u; +-- Test set returning functions +SELECT test_setof_as_list(0, 'list'); + test_setof_as_list +-------------------- +(0 rows) + +SELECT test_setof_as_list(1, 'list'); + test_setof_as_list +-------------------- + list +(1 row) + +SELECT test_setof_as_list(2, 'list'); + test_setof_as_list +-------------------- + list + list +(2 rows) + +SELECT test_setof_as_list(2, null); + test_setof_as_list +-------------------- + + +(2 rows) + +SELECT test_setof_as_tuple(0, 'tuple'); + test_setof_as_tuple +--------------------- +(0 rows) + +SELECT test_setof_as_tuple(1, 'tuple'); + test_setof_as_tuple +--------------------- + tuple +(1 row) + +SELECT test_setof_as_tuple(2, 'tuple'); + test_setof_as_tuple +--------------------- + tuple + tuple +(2 rows) + +SELECT test_setof_as_tuple(2, null); + test_setof_as_tuple +--------------------- + + +(2 rows) + +SELECT test_setof_as_iterator(0, 'list'); + test_setof_as_iterator +------------------------ +(0 rows) + +SELECT test_setof_as_iterator(1, 'list'); + test_setof_as_iterator +------------------------ + list +(1 row) + +SELECT test_setof_as_iterator(2, 'list'); + test_setof_as_iterator +------------------------ + list + list +(2 rows) + +SELECT test_setof_as_iterator(2, null); + test_setof_as_iterator +------------------------ + + +(2 rows) + +SELECT test_setof_spi_in_iterator(); + test_setof_spi_in_iterator +---------------------------- + Hello + Brave + New + World +(4 rows) + diff --git a/src/test/regress/expected/plpython3u/plpython_spi.out b/src/test/regress/expected/plpython3u/plpython_spi.out new file mode 100644 index 0000000000..419e1ceb44 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_spi.out @@ -0,0 +1,474 @@ +-- +-- nested calls +-- +CREATE FUNCTION nested_call_one(a text) RETURNS text + AS +'q = "SELECT nested_call_two(''%s'')" % a +r = plpy.execute(q) +return r[0]' + LANGUAGE plpython3u ; +CREATE FUNCTION nested_call_two(a text) RETURNS text + AS +'q = "SELECT nested_call_three(''%s'')" % a +r = plpy.execute(q) +return r[0]' + LANGUAGE plpython3u ; +CREATE FUNCTION nested_call_three(a text) RETURNS text + AS +'return a' + LANGUAGE plpython3u ; +-- some spi stuff +CREATE FUNCTION spi_prepared_plan_test_one(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT count(*) FROM users WHERE lname = $1" + SD["myplan"] = plpy.prepare(q, [ "text" ]) +try: + rv = plpy.execute(SD["myplan"], [a]) + return "there are " + str(rv[0]["count"]) + " " + str(a) + "s" +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; +CREATE FUNCTION spi_prepared_plan_test_two(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT count(*) FROM users WHERE lname = $1" + SD["myplan"] = plpy.prepare(q, [ "text" ]) +try: + rv = SD["myplan"].execute([a]) + return "there are " + str(rv[0]["count"]) + " " + str(a) + "s" +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; +CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a + SD["myplan"] = plpy.prepare(q) +try: + rv = plpy.execute(SD["myplan"]) + if len(rv): + return rv[0]["count"] +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; +CREATE FUNCTION join_sequences(s sequences) RETURNS text + AS +'if not s["multipart"]: + return s["sequence"] +q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"] +rv = plpy.execute(q) +seq = s["sequence"] +for r in rv: + seq = seq + r["sequence"] +return seq +' + LANGUAGE plpython3u; +-- +-- spi and nested calls +-- +select nested_call_one('pass this along'); + nested_call_one +----------------------------------------------------------------- + {'nested_call_two': "{'nested_call_three': 'pass this along'}"} +(1 row) + +select spi_prepared_plan_test_one('doe'); + spi_prepared_plan_test_one +---------------------------- + there are 3 does +(1 row) + +select spi_prepared_plan_test_two('smith'); + spi_prepared_plan_test_two +---------------------------- + there are 1 smiths +(1 row) + +select spi_prepared_plan_test_nested('smith'); + spi_prepared_plan_test_nested +------------------------------- + there are 1 smiths +(1 row) + +SELECT join_sequences(sequences) FROM sequences; + join_sequences +---------------- + ABCDEFGHIJKL + ABCDEF + ABCDEF + ABCDEF + ABCDEF + ABCDEF +(6 rows) + +SELECT join_sequences(sequences) FROM sequences + WHERE join_sequences(sequences) ~* '^A'; + join_sequences +---------------- + ABCDEFGHIJKL + ABCDEF + ABCDEF + ABCDEF + ABCDEF + ABCDEF +(6 rows) + +SELECT join_sequences(sequences) FROM sequences + WHERE join_sequences(sequences) ~* '^B'; + join_sequences +---------------- +(0 rows) + +-- +-- plan and result objects +-- +CREATE FUNCTION result_metadata_test(cmd text) RETURNS int +AS $$ +plan = plpy.prepare(cmd) +plpy.info(plan.status()) # not really documented or useful +result = plpy.execute(plan) +if result.status() > 0: + plpy.info(result.colnames()) + plpy.info(result.coltypes()) + plpy.info(result.coltypmods()) + return result.nrows() +else: + return None +$$ LANGUAGE plpython3u; +SELECT result_metadata_test($$SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'$$); +INFO: True +CONTEXT: referenced column: result_metadata_test +INFO: ['foo', 'bar'] +CONTEXT: referenced column: result_metadata_test +INFO: [23, 25] +CONTEXT: referenced column: result_metadata_test +INFO: [-1, -1] +CONTEXT: referenced column: result_metadata_test + result_metadata_test +---------------------- + 2 +(1 row) + +SELECT result_metadata_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$); +INFO: True +CONTEXT: referenced column: result_metadata_test +ERROR: plpy.Error: command did not produce a result set +CONTEXT: Traceback (most recent call last): + PL/Python function "result_metadata_test", line 6, in + plpy.info(result.colnames()) +referenced column: result_metadata_test +CREATE FUNCTION result_nrows_test(cmd text) RETURNS int +AS $$ +result = plpy.execute(cmd) +return result.nrows() +$$ LANGUAGE plpython3u; +SELECT result_nrows_test($$SELECT 1$$); + result_nrows_test +------------------- + 1 +(1 row) + +SELECT result_nrows_test($$CREATE TEMPORARY TABLE foo2 (a int, b text)$$); + result_nrows_test +------------------- + 0 +(1 row) + +SELECT result_nrows_test($$INSERT INTO foo2 VALUES (1, 'one'), (2, 'two')$$); + result_nrows_test +------------------- + 2 +(1 row) + +SELECT result_nrows_test($$UPDATE foo2 SET b = '' WHERE a = 2$$); + result_nrows_test +------------------- + 1 +(1 row) + +CREATE FUNCTION result_len_test(cmd text) RETURNS int +AS $$ +result = plpy.execute(cmd) +return len(result) +$$ LANGUAGE plpython3u; +SELECT result_len_test($$SELECT 1$$); + result_len_test +----------------- + 1 +(1 row) + +SELECT result_len_test($$CREATE TEMPORARY TABLE foo3 (a int, b text)$$); + result_len_test +----------------- + 0 +(1 row) + +SELECT result_len_test($$INSERT INTO foo3 VALUES (1, 'one'), (2, 'two')$$); + result_len_test +----------------- + 0 +(1 row) + +SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$); + result_len_test +----------------- + 0 +(1 row) + +CREATE FUNCTION result_subscript_test() RETURNS void +AS $$ +result = plpy.execute("SELECT 1 AS c UNION ALL SELECT 2 " + "UNION ALL SELECT 3 UNION ALL SELECT 4") + +plpy.info(result[1]['c']) +plpy.info(result[-1]['c']) + +plpy.info([item['c'] for item in result[1:3]]) +plpy.info([item['c'] for item in result[::2]]) + +result[-1] = {'c': 1000} +result[:2] = [{'c': 10}, {'c': 100}] +plpy.info([item['c'] for item in result[:]]) + +# raises TypeError, but the message differs on Python 2.6, so silence it +try: + plpy.info(result['foo']) +except TypeError: + pass +else: + assert False, "TypeError not raised" + +$$ LANGUAGE plpython3u; +SELECT result_subscript_test(); +INFO: 2 +CONTEXT: referenced column: result_subscript_test +INFO: 4 +CONTEXT: referenced column: result_subscript_test +INFO: [2, 3] +CONTEXT: referenced column: result_subscript_test +INFO: [1, 3] +CONTEXT: referenced column: result_subscript_test +INFO: [10, 100, 3, 1000] +CONTEXT: referenced column: result_subscript_test + result_subscript_test +----------------------- + +(1 row) + +CREATE FUNCTION result_empty_test() RETURNS void +AS $$ +result = plpy.execute("select 1 where false") + +plpy.info(result[:]) + +$$ LANGUAGE plpython3u; +SELECT result_empty_test(); +INFO: [] +CONTEXT: referenced column: result_empty_test + result_empty_test +------------------- + +(1 row) + +CREATE FUNCTION result_str_test(cmd text) RETURNS text +AS $$ +plan = plpy.prepare(cmd) +result = plpy.execute(plan) +return str(result) +$$ LANGUAGE plpython3u; +SELECT result_str_test($$SELECT 1 AS foo UNION SELECT 2$$); + result_str_test +------------------------------------------------------------ + +(1 row) + +SELECT result_str_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$); + result_str_test +-------------------------------------- + +(1 row) + +-- cursor objects +CREATE FUNCTION simple_cursor_test() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +does = 0 +for row in res: + if row['lname'] == 'doe': + does += 1 +return does +$$ LANGUAGE plpython3u; +CREATE FUNCTION double_cursor_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +res.close() +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_fetch() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +assert len(res.fetch(3)) == 3 +assert len(res.fetch(3)) == 1 +assert len(res.fetch(3)) == 0 +assert len(res.fetch(3)) == 0 +try: + # use next() or __next__(), the method name changed in + # http://www.python.org/dev/peps/pep-3114/ + try: + res.next() + except AttributeError: + res.__next__() +except StopIteration: + pass +else: + assert False, "StopIteration not raised" +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_mix_next_and_fetch() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users order by fname") +assert len(res.fetch(2)) == 2 + +item = None +try: + item = res.next() +except AttributeError: + item = res.__next__() +assert item['fname'] == 'rick' + +assert len(res.fetch(2)) == 1 +$$ LANGUAGE plpython3u; +CREATE FUNCTION fetch_after_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +try: + res.fetch(1) +except ValueError: + pass +else: + assert False, "ValueError not raised" +$$ LANGUAGE plpython3u; +CREATE FUNCTION next_after_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +try: + try: + res.next() + except AttributeError: + res.__next__() +except ValueError: + pass +else: + assert False, "ValueError not raised" +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_fetch_next_empty() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users where false") +assert len(res.fetch(1)) == 0 +try: + try: + res.next() + except AttributeError: + res.__next__() +except StopIteration: + pass +else: + assert False, "StopIteration not raised" +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_plan() RETURNS SETOF text AS $$ +plan = plpy.prepare( + "select fname, lname from users where fname like $1 || '%' order by fname", + ["text"]) +for row in plpy.cursor(plan, ["w"]): + yield row['fname'] +for row in plan.cursor(["j"]): + yield row['fname'] +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_plan_wrong_args() RETURNS SETOF text AS $$ +plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'", + ["text"]) +c = plpy.cursor(plan, ["a", "b"]) +$$ LANGUAGE plpython3u; +CREATE TYPE test_composite_type AS ( + a1 int, + a2 varchar +); +CREATE OR REPLACE FUNCTION plan_composite_args() RETURNS test_composite_type AS $$ +plan = plpy.prepare("select $1 as c1", ["test_composite_type"]) +res = plpy.execute(plan, [{"a1": 3, "a2": "label"}]) +return res[0]["c1"] +$$ LANGUAGE plpython3u; +CREATE OR REPLACE FUNCTION test_python_gil_pg_locks() RETURNS text +AS $$ +result = plpy.execute("select locktype,granted from pg_locks where locktype='plpytyhon_gil'") +return str(result[:]) +$$ LANGUAGE plpython3u; +select test_python_gil_pg_locks(); + test_python_gil_pg_locks +-------------------------------------------------- + [{'locktype': 'plpytyhon_gil', 'granted': True}] +(1 row) + +SELECT simple_cursor_test(); + simple_cursor_test +-------------------- + 3 +(1 row) + +SELECT double_cursor_close(); + double_cursor_close +--------------------- + +(1 row) + +SELECT cursor_fetch(); + cursor_fetch +-------------- + +(1 row) + +SELECT cursor_mix_next_and_fetch(); + cursor_mix_next_and_fetch +--------------------------- + +(1 row) + +SELECT fetch_after_close(); + fetch_after_close +------------------- + +(1 row) + +SELECT next_after_close(); + next_after_close +------------------ + +(1 row) + +SELECT cursor_fetch_next_empty(); + cursor_fetch_next_empty +------------------------- + +(1 row) + +SELECT cursor_plan(); + cursor_plan +------------- + willem + jane + john +(3 rows) + +SELECT cursor_plan_wrong_args(); +ERROR: TypeError: Expected sequence of 1 argument, got 2: ['a', 'b'] +CONTEXT: Traceback (most recent call last): + PL/Python function "cursor_plan_wrong_args", line 4, in + c = plpy.cursor(plan, ["a", "b"]) +referenced column: cursor_plan_wrong_args +SELECT plan_composite_args(); + plan_composite_args +--------------------- + (3,label) +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_subtransaction.out b/src/test/regress/expected/plpython3u/plpython_subtransaction.out new file mode 100644 index 0000000000..7239492c72 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_subtransaction.out @@ -0,0 +1,477 @@ +-- +-- Test explicit subtransactions +-- +-- Test table to see if transactions get properly rolled back +CREATE TABLE subtransaction_tbl ( + i integer +); +-- Explicit case for Python <2.6 +CREATE FUNCTION subtransaction_test(what_error text = NULL) RETURNS text +AS $$ +import sys +subxact = plpy.subtransaction() +subxact.__enter__() +exc = True +try: + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + if what_error == "SPI": + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") + elif what_error == "Python": + plpy.attribute_error + except: + exc = False + subxact.__exit__(*sys.exc_info()) + raise +finally: + if exc: + subxact.__exit__(None, None, None) +$$ LANGUAGE plpython3u; +SELECT subtransaction_test(); + subtransaction_test +--------------------- + +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 + 2 +(2 rows) + +TRUNCATE subtransaction_tbl; +SELECT subtransaction_test('SPI'); +ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops" +LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops') + ^ +QUERY: INSERT INTO subtransaction_tbl VALUES ('oops') +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_test", line 11, in + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") +referenced column: subtransaction_test +SELECT * FROM subtransaction_tbl; + i +--- +(0 rows) + +TRUNCATE subtransaction_tbl; +SELECT subtransaction_test('Python'); +ERROR: AttributeError: module 'plpy' has no attribute 'attribute_error' +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_test", line 13, in + plpy.attribute_error +referenced column: subtransaction_test +SELECT * FROM subtransaction_tbl; + i +--- +(0 rows) + +TRUNCATE subtransaction_tbl; +-- Context manager case for Python >=2.6 +CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS text +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + if what_error == "SPI": + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") + elif what_error == "Python": + plpy.attribute_error +$$ LANGUAGE plpython3u; +SELECT subtransaction_ctx_test(); + subtransaction_ctx_test +------------------------- + +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 + 2 +(2 rows) + +TRUNCATE subtransaction_tbl; +SELECT subtransaction_ctx_test('SPI'); +ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for integer: "oops" +LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops') + ^ +QUERY: INSERT INTO subtransaction_tbl VALUES ('oops') +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_ctx_test", line 6, in + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") +referenced column: subtransaction_ctx_test +SELECT * FROM subtransaction_tbl; + i +--- +(0 rows) + +TRUNCATE subtransaction_tbl; +SELECT subtransaction_ctx_test('Python'); +ERROR: AttributeError: module 'plpy' has no attribute 'attribute_error' +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_ctx_test", line 8, in + plpy.attribute_error +referenced column: subtransaction_ctx_test +SELECT * FROM subtransaction_tbl; + i +--- +(0 rows) + +TRUNCATE subtransaction_tbl; +-- Nested subtransactions +CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text +AS $$ +plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + try: + with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (3)") + plpy.execute("error") + except plpy.SPIError as e: + if not swallow: + raise + plpy.notice("Swallowed %r" % e) +return "ok" +$$ LANGUAGE plpython3u; +SELECT subtransaction_nested_test(); +ERROR: spiexceptions.SyntaxError: syntax error at or near "error" +LINE 1: error + ^ +QUERY: error +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_nested_test", line 8, in + plpy.execute("error") +referenced column: subtransaction_nested_test +SELECT * FROM subtransaction_tbl; + i +--- +(0 rows) + +TRUNCATE subtransaction_tbl; +SELECT subtransaction_nested_test('t'); +NOTICE: Swallowed SyntaxError('syntax error at or near "error"') +CONTEXT: referenced column: subtransaction_nested_test + subtransaction_nested_test +---------------------------- + ok +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 + 2 +(2 rows) + +TRUNCATE subtransaction_tbl; +-- Nested subtransactions that recursively call code dealing with +-- subtransactions +CREATE FUNCTION subtransaction_deeply_nested_test() RETURNS text +AS $$ +plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + plpy.execute("SELECT subtransaction_nested_test('t')") +return "ok" +$$ LANGUAGE plpython3u; +SELECT subtransaction_deeply_nested_test(); +NOTICE: Swallowed SyntaxError('syntax error at or near "error"') +CONTEXT: referenced column: subtransaction_nested_test +SQL statement "SELECT subtransaction_nested_test('t')" +referenced column: subtransaction_deeply_nested_test + subtransaction_deeply_nested_test +----------------------------------- + ok +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 + 2 + 1 + 2 +(4 rows) + +TRUNCATE subtransaction_tbl; +-- Error conditions from not opening/closing subtransactions +CREATE FUNCTION subtransaction_exit_without_enter() RETURNS void +AS $$ +plpy.subtransaction().__exit__(None, None, None) +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_enter_without_exit() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_exit_twice() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +plpy.subtransaction().__exit__(None, None, None) +plpy.subtransaction().__exit__(None, None, None) +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_enter_twice() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +plpy.subtransaction().__enter__() +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_exit_same_subtransaction_twice() RETURNS void +AS $$ +s = plpy.subtransaction() +s.__enter__() +s.__exit__(None, None, None) +s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_enter_same_subtransaction_twice() RETURNS void +AS $$ +s = plpy.subtransaction() +s.__enter__() +s.__enter__() +s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; +-- No warnings here, as the subtransaction gets indeed closed +CREATE FUNCTION subtransaction_enter_subtransaction_in_with() RETURNS void +AS $$ +with plpy.subtransaction() as s: + s.__enter__() +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_exit_subtransaction_in_with() RETURNS void +AS $$ +with plpy.subtransaction() as s: + s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; +SELECT subtransaction_exit_without_enter(); +ERROR: ValueError: this subtransaction has not been entered +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_exit_without_enter", line 2, in + plpy.subtransaction().__exit__(None, None, None) +referenced column: subtransaction_exit_without_enter +SELECT subtransaction_enter_without_exit(); +WARNING: forcibly aborting a subtransaction that has not been exited +CONTEXT: referenced column: subtransaction_enter_without_exit + subtransaction_enter_without_exit +----------------------------------- + +(1 row) + +SELECT subtransaction_exit_twice(); +WARNING: forcibly aborting a subtransaction that has not been exited +CONTEXT: referenced column: subtransaction_exit_twice +ERROR: ValueError: this subtransaction has not been entered +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_exit_twice", line 3, in + plpy.subtransaction().__exit__(None, None, None) +referenced column: subtransaction_exit_twice +SELECT subtransaction_enter_twice(); +WARNING: forcibly aborting a subtransaction that has not been exited +CONTEXT: referenced column: subtransaction_enter_twice +WARNING: forcibly aborting a subtransaction that has not been exited +CONTEXT: referenced column: subtransaction_enter_twice + subtransaction_enter_twice +---------------------------- + +(1 row) + +SELECT subtransaction_exit_same_subtransaction_twice(); +ERROR: ValueError: this subtransaction has already been exited +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_exit_same_subtransaction_twice", line 5, in + s.__exit__(None, None, None) +referenced column: subtransaction_exit_same_subtransaction_twice +SELECT subtransaction_enter_same_subtransaction_twice(); +WARNING: forcibly aborting a subtransaction that has not been exited +CONTEXT: referenced column: subtransaction_enter_same_subtransaction_twice +ERROR: ValueError: this subtransaction has already been entered +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in + s.__enter__() +referenced column: subtransaction_enter_same_subtransaction_twice +SELECT subtransaction_enter_subtransaction_in_with(); +ERROR: ValueError: this subtransaction has already been entered +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_enter_subtransaction_in_with", line 3, in + s.__enter__() +referenced column: subtransaction_enter_subtransaction_in_with +SELECT subtransaction_exit_subtransaction_in_with(); +ERROR: ValueError: this subtransaction has already been exited +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_exit_subtransaction_in_with", line 3, in + s.__exit__(None, None, None) +referenced column: subtransaction_exit_subtransaction_in_with +-- Make sure we don't get a "current transaction is aborted" error +SELECT 1 as test; + test +------ + 1 +(1 row) + +-- Mix explicit subtransactions and normal SPI calls +CREATE FUNCTION subtransaction_mix_explicit_and_implicit() RETURNS void +AS $$ +p = plpy.prepare("INSERT INTO subtransaction_tbl VALUES ($1)", ["integer"]) +try: + with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute(p, [2]) + plpy.execute(p, ["wrong"]) +except plpy.SPIError: + plpy.warning("Caught a SPI error from an explicit subtransaction") + +try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute(p, [2]) + plpy.execute(p, ["wrong"]) +except plpy.SPIError: + plpy.warning("Caught a SPI error") +$$ LANGUAGE plpython3u; +SELECT subtransaction_mix_explicit_and_implicit(); +WARNING: Caught a SPI error from an explicit subtransaction +CONTEXT: referenced column: subtransaction_mix_explicit_and_implicit +WARNING: Caught a SPI error +CONTEXT: referenced column: subtransaction_mix_explicit_and_implicit + subtransaction_mix_explicit_and_implicit +------------------------------------------ + +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 + 2 +(2 rows) + +TRUNCATE subtransaction_tbl; +-- Alternative method names for Python <2.6 +CREATE FUNCTION subtransaction_alternative_names() RETURNS void +AS $$ +s = plpy.subtransaction() +s.enter() +s.exit(None, None, None) +$$ LANGUAGE plpython3u; +SELECT subtransaction_alternative_names(); + subtransaction_alternative_names +---------------------------------- + +(1 row) + +-- try/catch inside a subtransaction block +CREATE FUNCTION try_catch_inside_subtransaction() RETURNS void +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('a')") + except plpy.SPIError: + plpy.notice("caught") +$$ LANGUAGE plpython3u; +SELECT try_catch_inside_subtransaction(); +NOTICE: caught +CONTEXT: referenced column: try_catch_inside_subtransaction + try_catch_inside_subtransaction +--------------------------------- + +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 +(1 row) + +TRUNCATE subtransaction_tbl; +ALTER TABLE subtransaction_tbl ADD PRIMARY KEY (i); +NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "subtransaction_tbl_pkey" for table "subtransaction_tbl" +CREATE FUNCTION pk_violation_inside_subtransaction() RETURNS void +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + except plpy.SPIError: + plpy.notice("caught") +$$ LANGUAGE plpython3u; +SELECT pk_violation_inside_subtransaction(); +NOTICE: caught +CONTEXT: referenced column: pk_violation_inside_subtransaction + pk_violation_inside_subtransaction +------------------------------------ + +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 +(1 row) + +DROP TABLE subtransaction_tbl; +-- cursor/subtransactions interactions +CREATE FUNCTION cursor_in_subxact() RETURNS int AS $$ +with plpy.subtransaction(): + cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)") + cur.fetch(10) +fetched = cur.fetch(10); +return int(fetched[5]["i"]) +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_aborted_subxact() RETURNS int AS $$ +try: + with plpy.subtransaction(): + cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)") + cur.fetch(10); + plpy.execute("select no_such_function()") +except plpy.SPIError: + fetched = cur.fetch(10) + return int(fetched[5]["i"]) +return 0 # not reached +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_plan_aborted_subxact() RETURNS int AS $$ +try: + with plpy.subtransaction(): + plpy.execute('create temporary table tmp(i) ' + 'as select generate_series(1, 10)') + plan = plpy.prepare("select i from tmp") + cur = plpy.cursor(plan) + plpy.execute("select no_such_function()") +except plpy.SPIError: + fetched = cur.fetch(5) + return fetched[2]["i"] +return 0 # not reached +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_close_aborted_subxact() RETURNS boolean AS $$ +try: + with plpy.subtransaction(): + cur = plpy.cursor('select 1') + plpy.execute("select no_such_function()") +except plpy.SPIError: + cur.close() + return True +return False # not reached +$$ LANGUAGE plpython3u; +SELECT cursor_in_subxact(); + cursor_in_subxact +------------------- + 16 +(1 row) + +SELECT cursor_aborted_subxact(); +ERROR: ValueError: iterating a cursor in an aborted subtransaction +CONTEXT: Traceback (most recent call last): + PL/Python function "cursor_aborted_subxact", line 8, in + fetched = cur.fetch(10) +referenced column: cursor_aborted_subxact +SELECT cursor_plan_aborted_subxact(); +ERROR: ValueError: iterating a cursor in an aborted subtransaction +CONTEXT: Traceback (most recent call last): + PL/Python function "cursor_plan_aborted_subxact", line 10, in + fetched = cur.fetch(5) +referenced column: cursor_plan_aborted_subxact +SELECT cursor_close_aborted_subxact(); +ERROR: ValueError: closing a cursor in an aborted subtransaction +CONTEXT: Traceback (most recent call last): + PL/Python function "cursor_close_aborted_subxact", line 7, in + cur.close() +referenced column: cursor_close_aborted_subxact diff --git a/src/test/regress/expected/plpython3u/plpython_test.out b/src/test/regress/expected/plpython3u/plpython_test.out new file mode 100644 index 0000000000..7e1146ffaa --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_test.out @@ -0,0 +1,80 @@ +-- first some tests of basic functionality +CREATE EXTENSION plpython3u; +-- really stupid function just to get the module loaded +CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u; +select stupid(); + stupid +-------- + zarkon +(1 row) + +-- check 2/3 versioning +CREATE FUNCTION stupidn() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u; +select stupidn(); + stupidn +--------- + zarkon +(1 row) + +-- test multiple arguments +CREATE FUNCTION argument_test_one(u users, a1 text, a2 text) RETURNS text + AS +'keys = list(u.keys()) +keys.sort() +out = [] +for key in keys: + out.append("%s: %s" % (key, u[key])) +words = a1 + " " + a2 + " => {" + ", ".join(out) + "}" +return words' + LANGUAGE plpython3u; +select argument_test_one(users, fname, lname) from users where lname = 'doe' order by 1; + argument_test_one +----------------------------------------------------------------------- + jane doe => {fname: jane, lname: doe, userid: 1, username: j_doe} + john doe => {fname: john, lname: doe, userid: 2, username: johnd} + willem doe => {fname: willem, lname: doe, userid: 3, username: w_doe} +(3 rows) + +-- check module contents +CREATE FUNCTION module_contents() RETURNS text AS +$$ +contents = list(filter(lambda x: not x.startswith("__"), dir(plpy))) +contents.sort() +return ", ".join(contents) +$$ LANGUAGE plpython3u; +select module_contents(); + module_contents +------------------------------------------------------------------------------------------------------------------------------------ + Error, Fatal, SPIError, debug, error, fatal, info, log, notice, quote_ident, quote_literal, quote_nullable, spiexceptions, warning +(1 row) + +CREATE FUNCTION elog_test() RETURNS void +AS $$ +plpy.debug('debug') +plpy.log('log') +plpy.info('info') +plpy.info(37) +plpy.info() +plpy.info('info', 37, [1, 2, 3]) +plpy.notice('notice') +plpy.warning('warning') +plpy.error('error') +$$ LANGUAGE plpython3u; +SELECT elog_test(); +INFO: info +CONTEXT: referenced column: elog_test +INFO: 37 +CONTEXT: referenced column: elog_test +INFO: () +CONTEXT: referenced column: elog_test +INFO: ('info', 37, [1, 2, 3]) +CONTEXT: referenced column: elog_test +NOTICE: notice +CONTEXT: referenced column: elog_test +WARNING: warning +CONTEXT: referenced column: elog_test +ERROR: plpy.Error: error +CONTEXT: Traceback (most recent call last): + PL/Python function "elog_test", line 10, in + plpy.error('error') +referenced column: elog_test diff --git a/src/test/regress/expected/plpython3u/plpython_trigger.out b/src/test/regress/expected/plpython3u/plpython_trigger.out new file mode 100644 index 0000000000..5b37924293 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_trigger.out @@ -0,0 +1,567 @@ +-- these triggers are dedicated to HPHC of RI who +-- decided that my kid's name was william not willem, and +-- vigorously resisted all efforts at correction. they have +-- since gone bankrupt... +CREATE FUNCTION users_insert() returns trigger + AS +'if TD["new"]["fname"] == None or TD["new"]["lname"] == None: + return "SKIP" +if TD["new"]["username"] == None: + TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"] + rv = "MODIFY" +else: + rv = None +if TD["new"]["fname"] == "william": + TD["new"]["fname"] = TD["args"][0] + rv = "MODIFY" +return rv' + LANGUAGE plpython3u; +CREATE FUNCTION users_update() returns trigger + AS +'if TD["event"] == "UPDATE": + if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]: + return "SKIP" +return None' + LANGUAGE plpython3u; +CREATE FUNCTION users_delete() RETURNS trigger + AS +'if TD["old"]["fname"] == TD["args"][0]: + return "SKIP" +return None' + LANGUAGE plpython3u; +CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW + EXECUTE PROCEDURE users_insert ('willem'); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW + EXECUTE PROCEDURE users_update ('willem'); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW + EXECUTE PROCEDURE users_delete ('willem'); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +-- quick peek at the table +-- +SELECT * FROM users; + fname | lname | username | userid +--------+-------+----------+-------- + jane | doe | j_doe | 1 + john | doe | johnd | 2 + willem | doe | w_doe | 3 + rick | smith | slash | 4 +(4 rows) + +-- should fail +-- +UPDATE users SET fname = 'william' WHERE fname = 'willem'; +-- should modify william to willem and create username +-- +INSERT INTO users (fname, lname) VALUES ('william', 'smith'); +INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle'); +SELECT * FROM users; + fname | lname | username | userid +---------+--------+----------+-------- + jane | doe | j_doe | 1 + john | doe | johnd | 2 + willem | doe | w_doe | 3 + rick | smith | slash | 4 + willem | smith | w_smith | 5 + charles | darwin | beagle | 6 +(6 rows) + +-- dump trigger data +CREATE TABLE trigger_test + (i int, v text ); +CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpython3u AS $$ + +if 'relid' in TD: + TD['relid'] = "bogus:12345" + +skeys = list(TD.keys()) +skeys.sort() +for key in skeys: + val = TD[key] + if not isinstance(val, dict): + plpy.notice("TD[" + key + "] => " + str(val)) + else: + # print dicts the hard way because otherwise the order is implementation-dependent + valkeys = list(val.keys()) + valkeys.sort() + plpy.notice("TD[" + key + "] => " + '{' + ', '.join([repr(k) + ': ' + repr(val[k]) for k in valkeys]) + '}') + +return None + +$$; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +CREATE TRIGGER show_trigger_data_trig_stmt +BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(23,'skidoo'); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +insert into trigger_test values(1,'insert'); +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => STATEMENT +NOTICE: TD[name] => show_trigger_data_trig_stmt +NOTICE: TD[new] => None +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 1, 'v': 'insert'} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 1, 'v': 'insert'} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +update trigger_test set v = 'update' where i = 1; +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => STATEMENT +NOTICE: TD[name] => show_trigger_data_trig_stmt +NOTICE: TD[new] => None +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 1, 'v': 'update'} +NOTICE: TD[old] => {'i': 1, 'v': 'insert'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 1, 'v': 'update'} +NOTICE: TD[old] => {'i': 1, 'v': 'insert'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +delete from trigger_test; +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => STATEMENT +NOTICE: TD[name] => show_trigger_data_trig_stmt +NOTICE: TD[new] => None +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 1, 'v': 'update'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 1, 'v': 'update'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +truncate table trigger_test; +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => TRUNCATE +NOTICE: TD[level] => STATEMENT +NOTICE: TD[name] => show_trigger_data_trig_stmt +NOTICE: TD[new] => None +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +DROP TRIGGER show_trigger_data_trig_stmt on trigger_test; +DROP TRIGGER show_trigger_data_trig_before on trigger_test; +DROP TRIGGER show_trigger_data_trig_after on trigger_test; +insert into trigger_test values(1,'insert'); +CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; +CREATE TRIGGER show_trigger_data_trig +INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view +FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view'); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +insert into trigger_test_view values(2,'insert'); +NOTICE: TD[args] => ['24', 'skidoo view'] +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig +NOTICE: TD[new] => {'i': 2, 'v': 'insert'} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_view +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => INSTEAD OF +update trigger_test_view set v = 'update' where i = 1; +NOTICE: TD[args] => ['24', 'skidoo view'] +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig +NOTICE: TD[new] => {'i': 1, 'v': 'update'} +NOTICE: TD[old] => {'i': 1, 'v': 'insert'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_view +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => INSTEAD OF +delete from trigger_test_view; +NOTICE: TD[args] => ['24', 'skidoo view'] +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 1, 'v': 'insert'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_view +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => INSTEAD OF +DROP FUNCTION trigger_data() CASCADE; +NOTICE: drop cascades to trigger show_trigger_data_trig on view trigger_test_view +DROP VIEW trigger_test_view; +delete from trigger_test; +-- +-- trigger error handling +-- +INSERT INTO trigger_test VALUES (0, 'zero'); +-- returning non-string from trigger function +CREATE FUNCTION stupid1() RETURNS trigger +AS $$ + return 37 +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger1 +BEFORE INSERT ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid1(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +INSERT INTO trigger_test VALUES (1, 'one'); +ERROR: unexpected return value from trigger procedure +DETAIL: Expected None or a string. +DROP TRIGGER stupid_trigger1 ON trigger_test; +-- returning MODIFY from DELETE trigger +CREATE FUNCTION stupid2() RETURNS trigger +AS $$ + return "MODIFY" +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger2 +BEFORE DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid2(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +DELETE FROM trigger_test WHERE i = 0; +WARNING: PL/Python trigger function returned "MODIFY" in a DELETE trigger -- ignored +DROP TRIGGER stupid_trigger2 ON trigger_test; +INSERT INTO trigger_test VALUES (0, 'zero'); +-- returning unrecognized string from trigger function +CREATE FUNCTION stupid3() RETURNS trigger +AS $$ + return "foo" +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger3 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid3(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: unexpected return value from trigger procedure +DETAIL: Expected None, "OK", "SKIP", or "MODIFY". +DROP TRIGGER stupid_trigger3 ON trigger_test; +-- Unicode variant +CREATE FUNCTION stupid3u() RETURNS trigger +AS $$ + return "foo" +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger3 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid3u(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: unexpected return value from trigger procedure +DETAIL: Expected None, "OK", "SKIP", or "MODIFY". +DROP TRIGGER stupid_trigger3 ON trigger_test; +-- deleting the TD dictionary +CREATE FUNCTION stupid4() RETURNS trigger +AS $$ + del TD["new"] + return "MODIFY"; +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger4 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid4(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: TD["new"] deleted, cannot modify row +CONTEXT: while modifying trigger row +DROP TRIGGER stupid_trigger4 ON trigger_test; +-- TD not a dictionary +CREATE FUNCTION stupid5() RETURNS trigger +AS $$ + TD["new"] = ['foo', 'bar'] + return "MODIFY"; +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger5 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid5(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: TD["new"] is not a dictionary +CONTEXT: while modifying trigger row +DROP TRIGGER stupid_trigger5 ON trigger_test; +-- TD not having string keys +CREATE FUNCTION stupid6() RETURNS trigger +AS $$ + TD["new"] = {1: 'foo', 2: 'bar'} + return "MODIFY"; +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger6 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid6(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: TD["new"] dictionary key at ordinal position 0 is not a string +CONTEXT: while modifying trigger row +DROP TRIGGER stupid_trigger6 ON trigger_test; +-- TD keys not corresponding to row columns +CREATE FUNCTION stupid7() RETURNS trigger +AS $$ + TD["new"] = {'v': 'foo', 'a': 'bar'} + return "MODIFY"; +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger7 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid7(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: key "a" found in TD["new"] does not exist as a column in the triggering row +CONTEXT: while modifying trigger row +DROP TRIGGER stupid_trigger7 ON trigger_test; +-- Unicode variant +CREATE FUNCTION stupid7u() RETURNS trigger +AS $$ + TD["new"] = {'v': 'foo', 'a': 'bar'} + return "MODIFY" +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger7 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid7u(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: key "a" found in TD["new"] does not exist as a column in the triggering row +CONTEXT: while modifying trigger row +DROP TRIGGER stupid_trigger7 ON trigger_test; +-- calling a trigger function directly +SELECT stupid7(); +ERROR: trigger functions can only be called as triggers +CONTEXT: referenced column: stupid7 +-- +-- Null values +-- +SELECT * FROM trigger_test; + i | v +---+------ + 0 | zero +(1 row) + +CREATE FUNCTION test_null() RETURNS trigger +AS $$ + TD["new"]['v'] = None + return "MODIFY" +$$ LANGUAGE plpython3u; +CREATE TRIGGER test_null_trigger +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE test_null(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +UPDATE trigger_test SET v = 'null' WHERE i = 0; +DROP TRIGGER test_null_trigger ON trigger_test; +SELECT * FROM trigger_test; + i | v +---+--- + 0 | +(1 row) + +-- +-- Test that triggers honor typmod when assigning to tuple fields, +-- as per an early 9.0 bug report +-- +SET DateStyle = 'ISO'; +CREATE FUNCTION set_modif_time() RETURNS trigger AS $$ + TD['new']['modif_time'] = '2010-10-13 21:57:28.930486' + return 'MODIFY' +$$ LANGUAGE plpython3u; +CREATE TABLE pb (a TEXT, modif_time TIMESTAMP(0) WITHOUT TIME ZONE); +CREATE TRIGGER set_modif_time BEFORE UPDATE ON pb + FOR EACH ROW EXECUTE PROCEDURE set_modif_time(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +INSERT INTO pb VALUES ('a', '2010-10-09 21:57:33.930486'); +SELECT * FROM pb; + a | modif_time +---+--------------------- + a | 2010-10-09 21:57:33 +(1 row) + +UPDATE pb SET a = 'b'; +SELECT * FROM pb; + a | modif_time +---+--------------------- + b | 2010-10-13 21:57:28 +(1 row) + +-- triggers for tables with composite types +CREATE TABLE comp1 (i integer, j boolean); +CREATE TYPE comp2 AS (k integer, l boolean); +CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2); +CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$ + TD['new']['f1'] = (3, False) + TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10} + return 'MODIFY' +$$ LANGUAGE plpython3u; +CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +INSERT INTO composite_trigger_test VALUES (NULL, NULL); +SELECT * FROM composite_trigger_test; + f1 | f2 +-------+------- + (3,f) | (7,t) +(1 row) + +-- triggers with composite type columns (bug #6559) +CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2); +CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpython3u; +CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f')); +SELECT * FROM composite_trigger_noop_test; + f1 | f2 +-------+------- + | + (1,f) | + (,t) | (1,f) +(3 rows) + +-- nested composite types +CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer); +CREATE TABLE composite_trigger_nested_test(c comp3); +CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpython3u; +CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +INSERT INTO composite_trigger_nested_test VALUES (NULL); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3)); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL)); +SELECT * FROM composite_trigger_nested_test; + c +------------------- + + ("(1,f)",,3) + ("(,t)","(1,f)",) +(3 rows) + +-- check that using a function as a trigger over two tables works correctly +CREATE FUNCTION trig1234() RETURNS trigger LANGUAGE plpython3u AS $$ + TD["new"]["data"] = '1234' + return 'MODIFY' +$$; +CREATE TABLE a(data text); +CREATE TABLE b(data int); -- different type conversion +CREATE TRIGGER a_t BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE trig1234(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +CREATE TRIGGER b_t BEFORE INSERT ON b FOR EACH ROW EXECUTE PROCEDURE trig1234(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +INSERT INTO a DEFAULT VALUES; +SELECT * FROM a; + data +------ + 1234 +(1 row) + +DROP TABLE a; +INSERT INTO b DEFAULT VALUES; +SELECT * FROM b; + data +------ + 1234 +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_types.out b/src/test/regress/expected/plpython3u/plpython_types.out new file mode 100644 index 0000000000..bad7eae2f1 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_types.out @@ -0,0 +1,601 @@ +-- +-- Test data type behavior +-- +-- +-- Base/common types +-- +CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_bool(true); +INFO: (True, ) + test_type_conversion_bool +--------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_bool(false); +INFO: (False, ) + test_type_conversion_bool +--------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool(null); +INFO: (None, ) + test_type_conversion_bool +--------------------------- + +(1 row) + +-- test various other ways to express Booleans in Python +CREATE FUNCTION test_type_conversion_bool_other(n int) RETURNS bool AS $$ +# numbers +if n == 0: + ret = 0 +elif n == 1: + ret = 5 +# strings +elif n == 2: + ret = '' +elif n == 3: + ret = 'fa' # true in Python, false in PostgreSQL +# containers +elif n == 4: + ret = [] +elif n == 5: + ret = [0] +plpy.info(ret, not not ret) +return ret +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_bool_other(0); +INFO: (0, False) + test_type_conversion_bool_other +--------------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool_other(1); +INFO: (5, True) + test_type_conversion_bool_other +--------------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_bool_other(2); +INFO: ('', False) + test_type_conversion_bool_other +--------------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool_other(3); +INFO: ('fa', True) + test_type_conversion_bool_other +--------------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_bool_other(4); +INFO: ([], False) + test_type_conversion_bool_other +--------------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool_other(5); +INFO: ([0], True) + test_type_conversion_bool_other +--------------------------------- + t +(1 row) + +CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_char('a'); +INFO: ('a', ) + test_type_conversion_char +--------------------------- + a +(1 row) + +SELECT * FROM test_type_conversion_char(null); +INFO: (None, ) + test_type_conversion_char +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_int2(100::int2); +INFO: (100, ) + test_type_conversion_int2 +--------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_int2(-100::int2); +INFO: (-100, ) + test_type_conversion_int2 +--------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_int2(null); +INFO: (None, ) + test_type_conversion_int2 +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_int4(100); +INFO: (100, ) + test_type_conversion_int4 +--------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_int4(-100); +INFO: (-100, ) + test_type_conversion_int4 +--------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_int4(null); +INFO: (None, ) + test_type_conversion_int4 +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_int8(100); +INFO: (100, ) + test_type_conversion_int8 +--------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_int8(-100); +INFO: (-100, ) + test_type_conversion_int8 +--------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_int8(5000000000); +INFO: (5000000000, ) + test_type_conversion_int8 +--------------------------- + 5000000000 +(1 row) + +SELECT * FROM test_type_conversion_int8(null); +INFO: (None, ) + test_type_conversion_int8 +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +/* The current implementation converts numeric to float. */ +SELECT * FROM test_type_conversion_numeric(100); +INFO: (100.0, ) + test_type_conversion_numeric +------------------------------ + 100.0 +(1 row) + +SELECT * FROM test_type_conversion_numeric(-100); +INFO: (-100.0, ) + test_type_conversion_numeric +------------------------------ + -100.0 +(1 row) + +SELECT * FROM test_type_conversion_numeric(5000000000.5); +INFO: (5000000000.5, ) + test_type_conversion_numeric +------------------------------ + 5000000000.5 +(1 row) + +SELECT * FROM test_type_conversion_numeric(null); +INFO: (None, ) + test_type_conversion_numeric +------------------------------ + +(1 row) + +CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_float4(100); +INFO: (100.0, ) + test_type_conversion_float4 +----------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_float4(-100); +INFO: (-100.0, ) + test_type_conversion_float4 +----------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_float4(5000.5); +INFO: (5000.5, ) + test_type_conversion_float4 +----------------------------- + 5000.5 +(1 row) + +SELECT * FROM test_type_conversion_float4(null); +INFO: (None, ) + test_type_conversion_float4 +----------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_float8(100); +INFO: (100.0, ) + test_type_conversion_float8 +----------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_float8(-100); +INFO: (-100.0, ) + test_type_conversion_float8 +----------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_float8(5000000000.5); +INFO: (5000000000.5, ) + test_type_conversion_float8 +----------------------------- + 5000000000.5 +(1 row) + +SELECT * FROM test_type_conversion_float8(null); +INFO: (None, ) + test_type_conversion_float8 +----------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_text('hello world'); +INFO: ('hello world', ) + test_type_conversion_text +--------------------------- + hello world +(1 row) + +SELECT * FROM test_type_conversion_text(null); +INFO: (None, ) + test_type_conversion_text +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_bytea('hello world'); +INFO: (b'hello world', ) + test_type_conversion_bytea +---------------------------- + \x68656c6c6f20776f726c64 +(1 row) + +SELECT * FROM test_type_conversion_bytea(E'null\\000byte'); +INFO: (b'null\x00byte', ) + test_type_conversion_bytea +---------------------------- + \x6e756c6c0062797465 +(1 row) + +SELECT * FROM test_type_conversion_bytea(null); +INFO: (None, ) + test_type_conversion_bytea +---------------------------- + +(1 row) + +CREATE FUNCTION test_type_marshal() RETURNS bytea AS $$ +import marshal +return marshal.dumps('hello world') +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_type_unmarshal(x bytea) RETURNS text AS $$ +import marshal +try: + return marshal.loads(x) +except ValueError as e: + return 'FAILED: ' + str(e) +$$ LANGUAGE plpython3u; +SELECT test_type_unmarshal(x) FROM test_type_marshal() x; + test_type_unmarshal +--------------------- + hello world +(1 row) + +-- +-- Domains +-- +CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL); +CREATE FUNCTION test_type_conversion_booltrue(x booltrue, y bool) RETURNS booltrue AS $$ +return y +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_booltrue(true, true); + test_type_conversion_booltrue +------------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_booltrue(false, true); +ERROR: value for domain booltrue violates check constraint "booltrue_check" +SELECT * FROM test_type_conversion_booltrue(true, false); +ERROR: value for domain booltrue violates check constraint "booltrue_check" +CONTEXT: while creating return value +CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); +CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ +plpy.info(x, type(x)) +return y +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_uint2(100::uint2, 50); +INFO: (100, ) + test_type_conversion_uint2 +---------------------------- + 50 +(1 row) + +SELECT * FROM test_type_conversion_uint2(100::uint2, -50); +INFO: (100, ) +ERROR: value for domain uint2 violates check constraint "uint2_check" +CONTEXT: while creating return value +SELECT * FROM test_type_conversion_uint2(null, 1); +INFO: (None, ) + test_type_conversion_uint2 +---------------------------- + 1 +(1 row) + +CREATE DOMAIN nnint AS int CHECK (VALUE IS NOT NULL); +CREATE FUNCTION test_type_conversion_nnint(x nnint, y int) RETURNS nnint AS $$ +return y +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_nnint(10, 20); + test_type_conversion_nnint +---------------------------- + 20 +(1 row) + +SELECT * FROM test_type_conversion_nnint(null, 20); +ERROR: value for domain nnint violates check constraint "nnint_check" +SELECT * FROM test_type_conversion_nnint(10, null); +ERROR: value for domain nnint violates check constraint "nnint_check" +CONTEXT: while creating return value +CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); +CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ +plpy.info(x, type(x)) +return y +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold'); +INFO: (b'hello wold', ) + test_type_conversion_bytea10 +------------------------------ + \x68656c6c6f20776f6c64 +(1 row) + +SELECT * FROM test_type_conversion_bytea10('hello world', 'hello wold'); +ERROR: value for domain bytea10 violates check constraint "bytea10_check" +SELECT * FROM test_type_conversion_bytea10('hello word', 'hello world'); +INFO: (b'hello word', ) +ERROR: value for domain bytea10 violates check constraint "bytea10_check" +CONTEXT: while creating return value +SELECT * FROM test_type_conversion_bytea10(null, 'hello word'); +ERROR: value for domain bytea10 violates check constraint "bytea10_check" +SELECT * FROM test_type_conversion_bytea10('hello word', null); +INFO: (b'hello word', ) +ERROR: value for domain bytea10 violates check constraint "bytea10_check" +CONTEXT: while creating return value +-- +-- Arrays +-- +CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]); +INFO: ([0, 100], ) + test_type_conversion_array_int4 +--------------------------------- + {0,100} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[0,-100,55]); +INFO: ([0, -100, 55], ) + test_type_conversion_array_int4 +--------------------------------- + {0,-100,55} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[NULL,1]); +INFO: ([None, 1], ) + test_type_conversion_array_int4 +--------------------------------- + {NULL,1} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[]::integer[]); +INFO: ([], ) + test_type_conversion_array_int4 +--------------------------------- + {} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(NULL); +INFO: (None, ) + test_type_conversion_array_int4 +--------------------------------- + +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[[1,2,3],[4,5,6]]); +INFO: ([[1, 2, 3], [4, 5, 6]], ) +ERROR: invalid input syntax for integer: "[1, 2, 3]" +CONTEXT: while creating return value +CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']); +INFO: (['foo', 'bar'], ) + test_type_conversion_array_text +--------------------------------- + {foo,bar} +(1 row) + +CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]); +INFO: ([b'\xde\xad\xbe\xef', None], ) + test_type_conversion_array_bytea +---------------------------------- + {"\\xdeadbeef",NULL} +(1 row) + +CREATE FUNCTION test_type_conversion_array_mixed1() RETURNS text[] AS $$ +return [123, 'abc'] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_mixed1(); + test_type_conversion_array_mixed1 +----------------------------------- + {123,abc} +(1 row) + +CREATE FUNCTION test_type_conversion_array_mixed2() RETURNS int[] AS $$ +return [123, 'abc'] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_mixed2(); +ERROR: invalid input syntax for integer: "abc" +CONTEXT: while creating return value +CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ +return [None] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_record(); + test_type_conversion_array_record +----------------------------------- + {NULL} +(1 row) + +CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$ +return 'abc' +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_string(); + test_type_conversion_array_string +----------------------------------- + {a,b,c} +(1 row) + +CREATE FUNCTION test_type_conversion_array_tuple() RETURNS text[] AS $$ +return ('abc', 'def') +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_tuple(); + test_type_conversion_array_tuple +---------------------------------- + {abc,def} +(1 row) + +CREATE FUNCTION test_type_conversion_array_error() RETURNS int[] AS $$ +return 5 +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_error(); +ERROR: return value of function with array return type is not a Python sequence +CONTEXT: while creating return value +--- +--- Composite types +--- +CREATE TABLE employee ( + name text, + basesalary integer, + bonus integer +); +INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10); +CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$ +return e['basesalary'] + e['bonus'] +$$ LANGUAGE plpython3u; +SELECT name, test_composite_table_input(employee.*) FROM employee; + name | test_composite_table_input +------+---------------------------- + John | 110 + Mary | 210 +(2 rows) + +ALTER TABLE employee DROP bonus; +SELECT name, test_composite_table_input(employee.*) FROM employee; +ERROR: KeyError: 'bonus' +CONTEXT: Traceback (most recent call last): + PL/Python function "test_composite_table_input", line 2, in + return e['basesalary'] + e['bonus'] +referenced column: test_composite_table_input +ALTER TABLE employee ADD bonus integer; +UPDATE employee SET bonus = 10; +SELECT name, test_composite_table_input(employee.*) FROM employee; + name | test_composite_table_input +------+---------------------------- + John | 110 + Mary | 210 +(2 rows) + +CREATE TYPE named_pair AS ( + i integer, + j integer +); +CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$ +return sum(p.values()) +$$ LANGUAGE plpython3u; +SELECT test_composite_type_input(row(1, 2)); + test_composite_type_input +--------------------------- + 3 +(1 row) + +ALTER TYPE named_pair RENAME TO named_pair_2; +SELECT test_composite_type_input(row(1, 2)); + test_composite_type_input +--------------------------- + 3 +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_unicode.out b/src/test/regress/expected/plpython3u/plpython_unicode.out new file mode 100644 index 0000000000..23fc9f8773 --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_unicode.out @@ -0,0 +1,32 @@ +-- +-- Unicode handling +-- +SET client_encoding TO UTF8; +CREATE TABLE unicode_test ( + testvalue text NOT NULL +); +CREATE FUNCTION unicode_return() RETURNS text AS E' +return "\\x80" +' LANGUAGE plpython3u; +CREATE FUNCTION unicode_trigger() RETURNS trigger AS E' +TD["new"]["testvalue"] = "\\x80" +return "MODIFY" +' LANGUAGE plpython3u; +CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test + FOR EACH ROW EXECUTE PROCEDURE unicode_trigger(); +WARNING: Trigger function with non-plpgsql type is not recommended. +DETAIL: Non-plpgsql trigger function are not shippable by default. +HINT: Unshippable trigger may lead to bad performance. +SELECT unicode_return(); + unicode_return +---------------- + \u0080 +(1 row) + +INSERT INTO unicode_test (testvalue) VALUES ('test'); +SELECT * FROM unicode_test; + testvalue +----------- + \u0080 +(1 row) + diff --git a/src/test/regress/expected/plpython3u/plpython_void.out b/src/test/regress/expected/plpython3u/plpython_void.out new file mode 100644 index 0000000000..e31b686fee --- /dev/null +++ b/src/test/regress/expected/plpython3u/plpython_void.out @@ -0,0 +1,30 @@ +-- +-- Tests for functions that return void +-- +CREATE FUNCTION test_void_func1() RETURNS void AS $$ +x = 10 +$$ LANGUAGE plpython3u; +-- illegal: can't return non-None value in void-returning func +CREATE FUNCTION test_void_func2() RETURNS void AS $$ +return 10 +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_return_none() RETURNS int AS $$ +None +$$ LANGUAGE plpython3u; +-- Tests for functions returning void +SELECT test_void_func1(), test_void_func1() IS NULL AS "is null"; + test_void_func1 | is null +-----------------+--------- + | f +(1 row) + +SELECT test_void_func2(); -- should fail +ERROR: PL/Python function with return type "void" did not return None +CONTEXT: while creating return value +referenced column: test_void_func2 +SELECT test_return_none(), test_return_none() IS NULL AS "is null"; + test_return_none | is null +------------------+--------- + | t +(1 row) + diff --git a/src/test/regress/parallel_schedule0 b/src/test/regress/parallel_schedule0 index 54db9d894a..35eb13130e 100644 --- a/src/test/regress/parallel_schedule0 +++ b/src/test/regress/parallel_schedule0 @@ -1163,3 +1163,21 @@ test: dump_alter_index_disable test: to_timestamp_default cast test: dump_alter_index_invisible nls_lower + +# plpython tests +test: plpython3u/plpython_schema +test: plpython3u/plpython_populate +test: plpython3u/plpython_test +test: plpython3u/plpython_do +test: plpython3u/plpython_global +test: plpython3u/plpython_global_session +test: plpython3u/plpython_import +#test: plpython3u/plpython_spi +test: plpython3u/plpython_newline plpython3u/plpython_void plpython3u/plpython_params plpython3u/plpython_setof plpython3u/plpython_record +test: plpython3u/plpython_types +#test: plpython3u/plpython_error +test: plpython3u/plpython_unicode plpython3u/plpython_quote +test: plpython3u/plpython_composite +#test: plpython3u/plpython_subtransaction +test: plpython3u/plpython_gms_xslprocessor plpython3u/plpython_gms_xmldom +test: plpython3u/plpython_drop diff --git a/src/test/regress/parallel_schedule0plpython b/src/test/regress/parallel_schedule0plpython new file mode 100644 index 0000000000..e17aff34a7 --- /dev/null +++ b/src/test/regress/parallel_schedule0plpython @@ -0,0 +1,17 @@ +# plpython tests +test: plpython3u/plpython_schema +test: plpython3u/plpython_populate +test: plpython3u/plpython_test +test: plpython3u/plpython_do +test: plpython3u/plpython_global +test: plpython3u/plpython_global_session +test: plpython3u/plpython_import +#test: plpython3u/plpython_spi +test: plpython3u/plpython_newline plpython3u/plpython_void plpython3u/plpython_params plpython3u/plpython_setof plpython3u/plpython_record +test: plpython3u/plpython_types +#test: plpython3u/plpython_error +test: plpython3u/plpython_unicode plpython3u/plpython_quote +test: plpython3u/plpython_composite +#test: plpython3u/plpython_subtransaction +test: plpython3u/plpython_gms_xslprocessor plpython3u/plpython_gms_xmldom +test: plpython3u/plpython_drop diff --git a/src/test/regress/sql/plpython3u/plpython_composite.sql b/src/test/regress/sql/plpython3u/plpython_composite.sql new file mode 100644 index 0000000000..171836518f --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_composite.sql @@ -0,0 +1,197 @@ +CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$ +return (1, 2) +$$ LANGUAGE plpython3u; + +SELECT multiout_simple(); +SELECT * FROM multiout_simple(); +SELECT i, j + 2 FROM multiout_simple(); +SELECT (multiout_simple()).j + 3; + +CREATE FUNCTION multiout_simple_setof(n integer = 1, OUT integer, OUT integer) RETURNS SETOF record AS $$ +return [(1, 2)] * n +$$ LANGUAGE plpython3u; + +SELECT multiout_simple_setof(); +SELECT * FROM multiout_simple_setof(); +SELECT * FROM multiout_simple_setof(3); + +CREATE FUNCTION multiout_record_as(typ text, + first text, OUT first text, + second integer, OUT second integer, + retnull boolean) RETURNS record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) +$$ LANGUAGE plpython3u; + +SELECT * FROM multiout_record_as('dict', 'foo', 1, 'f'); +SELECT multiout_record_as('dict', 'foo', 1, 'f'); + +SELECT * FROM multiout_record_as('dict', null, null, false); +SELECT * FROM multiout_record_as('dict', 'one', null, false); +SELECT * FROM multiout_record_as('dict', null, 2, false); +SELECT * FROM multiout_record_as('dict', 'three', 3, false); +SELECT * FROM multiout_record_as('dict', null, null, true); + +SELECT * FROM multiout_record_as('tuple', null, null, false); +SELECT * FROM multiout_record_as('tuple', 'one', null, false); +SELECT * FROM multiout_record_as('tuple', null, 2, false); +SELECT * FROM multiout_record_as('tuple', 'three', 3, false); +SELECT * FROM multiout_record_as('tuple', null, null, true); + +SELECT * FROM multiout_record_as('list', null, null, false); +SELECT * FROM multiout_record_as('list', 'one', null, false); +SELECT * FROM multiout_record_as('list', null, 2, false); +SELECT * FROM multiout_record_as('list', 'three', 3, false); +SELECT * FROM multiout_record_as('list', null, null, true); + +SELECT * FROM multiout_record_as('obj', null, null, false); +SELECT * FROM multiout_record_as('obj', 'one', null, false); +SELECT * FROM multiout_record_as('obj', null, 2, false); +SELECT * FROM multiout_record_as('obj', 'three', 3, false); +SELECT * FROM multiout_record_as('obj', null, null, true); + +SELECT * FROM multiout_record_as('str', 'one', 1, false); +SELECT * FROM multiout_record_as('str', 'one', 2, false); + +SELECT *, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s); +SELECT *, f IS NULL AS fnull, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s); +SELECT * FROM multiout_record_as('obj', NULL, 10, 'f'); + +CREATE FUNCTION multiout_return_table() RETURNS TABLE (x integer, y text) AS $$ +return [{'x': 4, 'y' :'four'}, + {'x': 7, 'y' :'seven'}, + {'x': 0, 'y' :'zero'}] +$$ LANGUAGE plpython3u; + +SELECT * FROM multiout_return_table(); + +CREATE FUNCTION multiout_array(OUT integer[], OUT text) RETURNS SETOF record AS $$ +yield [[1], 'a'] +yield [[1,2], 'b'] +yield [[1,2,3], None] +$$ LANGUAGE plpython3u; + +SELECT * FROM multiout_array(); + +CREATE FUNCTION singleout_composite(OUT type_record) AS $$ +return {'first': 1, 'second': 2} +$$ LANGUAGE plpython3u; + +CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$ +return [{'first': 1, 'second': 2}, + {'first': 3, 'second': 4 }] +$$ LANGUAGE plpython3u; + +SELECT * FROM singleout_composite(); +SELECT * FROM multiout_composite(); + +-- composite OUT parameters in functions returning RECORD not supported yet +CREATE FUNCTION multiout_composite(INOUT n integer, OUT type_record) AS $$ +return (n, (n * 2, n * 3)) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION multiout_table_type_setof(typ text, returnnull boolean, INOUT n integer, OUT table_record) RETURNS SETOF record AS $$ +if returnnull: + d = None +elif typ == 'dict': + d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'} +elif typ == 'tuple': + d = (n * 2, n * 3) +elif typ == 'list': + d = [ n * 2, n * 3 ] +elif typ == 'obj': + class d: pass + d.first = n * 2 + d.second = n * 3 +elif typ == 'str': + d = "(%r,%r)" % (n * 2, n * 3) +for i in range(n): + yield (i, d) +$$ LANGUAGE plpython3u; + +SELECT * FROM multiout_composite(2); +SELECT * FROM multiout_table_type_setof('dict', 'f', 3); +SELECT * FROM multiout_table_type_setof('dict', 'f', 7); +SELECT * FROM multiout_table_type_setof('tuple', 'f', 2); +SELECT * FROM multiout_table_type_setof('tuple', 'f', 3); +SELECT * FROM multiout_table_type_setof('list', 'f', 2); +SELECT * FROM multiout_table_type_setof('list', 'f', 3); +SELECT * FROM multiout_table_type_setof('obj', 'f', 4); +SELECT * FROM multiout_table_type_setof('obj', 'f', 5); +SELECT * FROM multiout_table_type_setof('str', 'f', 6); +SELECT * FROM multiout_table_type_setof('str', 'f', 7); +SELECT * FROM multiout_table_type_setof('dict', 't', 3); + +-- check what happens if a type changes under us + +CREATE TABLE changing ( + i integer, + j integer +); + +CREATE FUNCTION changing_test(OUT n integer, OUT changing) RETURNS SETOF record AS $$ +return [(1, {'i': 1, 'j': 2}), + (1, (3, 4))] +$$ LANGUAGE plpython3u; + +SELECT * FROM changing_test(); +ALTER TABLE changing DROP COLUMN j; +SELECT * FROM changing_test(); +SELECT * FROM changing_test(); +ALTER TABLE changing ADD COLUMN j integer; +SELECT * FROM changing_test(); + +-- tables of composite types (not yet implemented) + +CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$ +yield {'tab': [['first', 1], ['second', 2]], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +yield {'tab': [['first', 1], ['second', 2]], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +yield {'tab': [['first', 1], ['second', 2]], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +$$ LANGUAGE plpython3u; + +SELECT * FROM composite_types_table(); + +-- check what happens if the output record descriptor changes +CREATE FUNCTION return_record(t text) RETURNS record AS $$ +return {'t': t, 'val': 10} +$$ LANGUAGE plpython3u; + +SELECT * FROM return_record('abc') AS r(t text, val integer); +SELECT * FROM return_record('abc') AS r(t text, val bigint); +SELECT * FROM return_record('abc') AS r(t text, val integer); +SELECT * FROM return_record('abc') AS r(t varchar(30), val integer); +SELECT * FROM return_record('abc') AS r(t varchar(100), val integer); +SELECT * FROM return_record('999') AS r(val text, t integer); + +CREATE FUNCTION return_record_2(t text) RETURNS record AS $$ +return {'v1':1,'v2':2,t:3} +$$ LANGUAGE plpython3u; + +SELECT * FROM return_record_2('v3') AS (v3 int, v2 int, v1 int); +SELECT * FROM return_record_2('v3') AS (v2 int, v3 int, v1 int); +SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int); +SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int); +-- error +SELECT * FROM return_record_2('v4') AS (v1 int, v3 int, v2 int); +-- works +SELECT * FROM return_record_2('v3') AS (v1 int, v3 int, v2 int); +SELECT * FROM return_record_2('v3') AS (v1 int, v2 int, v3 int); diff --git a/src/test/regress/sql/plpython3u/plpython_do.sql b/src/test/regress/sql/plpython3u/plpython_do.sql new file mode 100644 index 0000000000..2ccf0443e8 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_do.sql @@ -0,0 +1,5 @@ +DO $$ plpy.notice("This is plpythonu.") $$ LANGUAGE plpython3u; + +DO $$ plpy.notice("This is plpython2u.") $$ LANGUAGE plpython3u; + +DO $$ raise Exception("error test") $$ LANGUAGE plpython3u; diff --git a/src/test/regress/sql/plpython3u/plpython_drop.sql b/src/test/regress/sql/plpython3u/plpython_drop.sql new file mode 100644 index 0000000000..735ecf1f80 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_drop.sql @@ -0,0 +1,8 @@ +-- +-- For paranoia's sake, don't leave an untrusted language sitting around +-- +SET client_min_messages = WARNING; + +DROP EXTENSION plpython3u CASCADE; + +DROP EXTENSION IF EXISTS plpython2u CASCADE; diff --git a/src/test/regress/sql/plpython3u/plpython_error.sql b/src/test/regress/sql/plpython3u/plpython_error.sql new file mode 100644 index 0000000000..fc2e0cd598 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_error.sql @@ -0,0 +1,301 @@ +-- test error handling, i forgot to restore Warn_restart in +-- the trigger handler once. the errors and subsequent core dump were +-- interesting. + +/* Flat out Python syntax error + */ +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; + +/* With check_function_bodies = false the function should get defined + * and the error reported when called + */ +SET check_function_bodies = false; + +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; + +SELECT python_syntax_error(); +/* Run the function twice to check if the hashtable entry gets cleaned up */ +SELECT python_syntax_error(); + +RESET check_function_bodies; + +/* Flat out syntax error + */ +CREATE FUNCTION sql_syntax_error() RETURNS text + AS +'plpy.execute("syntax error")' + LANGUAGE plpython3u; + +SELECT sql_syntax_error(); + + +/* check the handling of uncaught python exceptions + */ +CREATE FUNCTION exception_index_invalid(text) RETURNS text + AS +'return args[1]' + LANGUAGE plpython3u; + +SELECT exception_index_invalid('test'); + + +/* check handling of nested exceptions + */ +CREATE FUNCTION exception_index_invalid_nested() RETURNS text + AS +'rv = plpy.execute("SELECT test5(''foo'')") +return rv[0]' + LANGUAGE plpython3u; + +SELECT exception_index_invalid_nested(); + + +/* a typo + */ +CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + SD["plan"] = plpy.prepare(q, [ "test" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; + +SELECT invalid_type_uncaught('rick'); + + +/* for what it's worth catch the exception generated by + * the typo, and return None + */ +CREATE FUNCTION invalid_type_caught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.notice(str(ex)) + return None +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; + +SELECT invalid_type_caught('rick'); + + +/* for what it's worth catch the exception generated by + * the typo, and reraise it as a plain error + */ +CREATE FUNCTION invalid_type_reraised(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.error(str(ex)) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; + +SELECT invalid_type_reraised('rick'); + + +/* no typo no messing about + */ +CREATE FUNCTION valid_type(a text) RETURNS text + AS +'if "plan" not in SD: + SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; + +SELECT valid_type('rick'); + +/* error in nested functions to get a traceback +*/ +CREATE FUNCTION nested_error() RETURNS text + AS +'def fun1(): + plpy.error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; + +SELECT nested_error(); + +/* raising plpy.Error is just like calling plpy.error +*/ +CREATE FUNCTION nested_error_raise() RETURNS text + AS +'def fun1(): + raise plpy.Error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; + +SELECT nested_error_raise(); + +/* using plpy.warning should not produce a traceback +*/ +CREATE FUNCTION nested_warning() RETURNS text + AS +'def fun1(): + plpy.warning("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "you''ve been warned" +' + LANGUAGE plpython3u; + +SELECT nested_warning(); + +/* AttributeError at toplevel used to give segfaults with the traceback +*/ +CREATE FUNCTION toplevel_attribute_error() RETURNS void AS +$$ +plpy.nonexistent +$$ LANGUAGE plpython3u; + +SELECT toplevel_attribute_error(); + +/* Calling PL/Python functions from SQL and vice versa should not lose context. + */ +CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$ +def first(): + second() + +def second(): + third() + +def third(): + plpy.execute("select sql_error()") + +first() +$$ LANGUAGE plpython3u; + +set vbplsql_check to off; +CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$ +begin + select 1/0; +end +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$ +begin + select python_traceback(); +end +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$ +plpy.execute("select sql_error()") +$$ LANGUAGE plpython3u; + +SELECT python_traceback(); +SELECT sql_error(); +SELECT python_from_sql_error(); +SELECT sql_from_python_error(); + +/* check catching specific types of exceptions + */ +CREATE TABLE specific ( + i integer PRIMARY KEY +); + +CREATE FUNCTION specific_exception(i integer) RETURNS void AS +$$ +from plpy import spiexceptions +try: + plpy.execute("insert into specific values (%s)" % (i or "NULL")); +except spiexceptions.NotNullViolation as e: + plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate) +except spiexceptions.UniqueViolation as e: + plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate) +$$ LANGUAGE plpython3u; + +SELECT specific_exception(2); +SELECT specific_exception(NULL); +SELECT specific_exception(2); + +/* SPI errors in PL/Python functions should preserve the SQLSTATE value + */ +CREATE FUNCTION python_unique_violation() RETURNS void AS $$ +plpy.execute("insert into specific values (1)") +plpy.execute("insert into specific values (1)") +$$ LANGUAGE plpython3u; + +CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$ +begin + begin + perform python_unique_violation(); + exception when unique_violation then + return 'ok'; + end; + return 'not reached'; +end; +$$ language plpgsql; + +SELECT catch_python_unique_violation(); + +/* manually starting subtransactions - a bad idea + */ +CREATE FUNCTION manual_subxact() RETURNS void AS $$ +plpy.execute("savepoint save") +plpy.execute("create table foo(x integer)") +plpy.execute("rollback to save") +$$ LANGUAGE plpython3u; + +SELECT manual_subxact(); + +/* same for prepared plans + */ +CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$ +save = plpy.prepare("savepoint save") +rollback = plpy.prepare("rollback to save") +plpy.execute(save) +plpy.execute("create table foo(x integer)") +plpy.execute(rollback) +$$ LANGUAGE plpython3u; + +SELECT manual_subxact_prepared(); diff --git a/src/test/regress/sql/plpython3u/plpython_global.sql b/src/test/regress/sql/plpython3u/plpython_global.sql new file mode 100644 index 0000000000..96d2049286 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_global.sql @@ -0,0 +1,38 @@ +-- +-- check static and global data (SD and GD) +-- + +CREATE FUNCTION global_test_one() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_one" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_one" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; + +CREATE FUNCTION global_test_two() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_two" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_two" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; + + +CREATE FUNCTION static_test() returns int4 + AS +'if "call" in SD: + SD["call"] = SD["call"] + 1 +else: + SD["call"] = 1 +return SD["call"] +' + LANGUAGE plpython3u; + + +SELECT static_test(); +SELECT static_test(); +SELECT global_test_one(); +SELECT global_test_two(); diff --git a/src/test/regress/sql/plpython3u/plpython_global_session.sql b/src/test/regress/sql/plpython3u/plpython_global_session.sql new file mode 100644 index 0000000000..702cbdabd1 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_global_session.sql @@ -0,0 +1,18 @@ +-- +-- check static and global data (SD and GD), session 间不共享 +-- + +CREATE FUNCTION global_test_three() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_three" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_three" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; + +SELECT static_test(); +SELECT static_test(); +SELECT global_test_three(); +SELECT global_test_two(); +SELECT global_test_one(); diff --git a/src/test/regress/sql/plpython3u/plpython_gms_xmldom.sql b/src/test/regress/sql/plpython3u/plpython_gms_xmldom.sql new file mode 100644 index 0000000000..4e957eeaff --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_gms_xmldom.sql @@ -0,0 +1,1405 @@ +--(1)newDOMDocument +--(2makeNode(doc DOMDocument)) +--(3)writeToClob(doc DOMDocument, cl IN OUT CLOB) +DECLARE + doc gms_xmldom.DOMDocument; + elem gms_xmldom.DOMElement; + root gms_xmldom.DOMNode; + elemNode gms_xmldom.DOMNode; + cl clob; + appResNode gms_xmldom.DOMNode; +BEGIN + doc := gms_xmldom.newDomDocument; + root := gms_xmldom.makeNode(doc); + elem := gms_xmldom.createElement(doc, 'root'); + elemNode := gms_xmldom.makeNode(elem); + appResNode := gms_xmldom.appendChild(root, elemNode); + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', cl; +END; +/ +--(4)newDOMDocument(xmldoc IN xml) +DECLARE + doc gms_xmldom.DOMDocument; + cl clob; + x xml; +BEGIN + x := xml('ramesh'); + doc := gms_xmldom.newDomDocument(x); + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', cl; +END; +/ +--(5)newDOMDocument(xmldoc clob) +DECLARE + doc gms_xmldom.DOMDocument; + cl clob; + s clob; +BEGIN + s := 'ramesh'; + doc := gms_xmldom.newDomDocument(s); + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', cl; +END; +/ +--(6)isNull(com DOMDocument) +--(7)isNull(n DOMNode) +--(8)createElement(doc DOMDocument, tagname IN VARCHAR2) +--(9)isNull(elem DOMElement) +--(10)makeNode(elem DOMElement) +--(11)createTextNode(doc DOMDocument, data IN VARCHAR2) +--(12)isNull(t DOMText) +--(13)makeNode(t DOMText) +--(14)setAttribute(elem DOMElement, name IN VARCHAR2, newvalue IN VARCHAR2) +--(15)writeToBuffer(n DOMDocument, buffer IN OUT VARCHAR2) +DECLARE + doc gms_xmldom.DOMDocument; + root gms_xmldom.DOMNode; + + booklist gms_xmldom.DOMElement; + listNode gms_xmldom.DOMNode; + + bookElem gms_xmldom.DOMElement; + bookElemNode gms_xmldom.DOMNode; + + titleElem gms_xmldom.DOMElement; + titleElemNode gms_xmldom.DOMNode; + titleText gms_xmldom.DOMText; + titleTextNode gms_xmldom.DOMNode; + + authorElem gms_xmldom.DOMElement; + authorElemNode gms_xmldom.DOMNode; + authorText gms_xmldom.DOMText; + authorTextNode gms_xmldom.DOMNode; + + pageElem gms_xmldom.DOMElement; + pageElemNode gms_xmldom.DOMNode; + pageText gms_xmldom.DOMText; + pageTextNode gms_xmldom.DOMNode; + + resnode gms_xmldom.DOMNode; + + buffer varchar2; + isNull boolean; +BEGIN + /*root*/ + doc := gms_xmldom.newDOMDocument; + isNull := gms_xmldom.isNull(doc); + raise notice '%', ('DOMDocument : ' || case when isNull then 'Y' else 'N' end); + root := gms_xmldom.makeNode(doc); + isNull := gms_xmldom.isNull(root); + raise notice '%', ('DOMNode : ' || case when isNull then 'Y' else 'N' end); + /*booklist*/ + booklist := gms_xmldom.createElement(doc, 'booklist'); + isNull := gms_xmldom.isNull(booklist); + raise notice '%', ('DOMElement : ' || case when isNull then 'Y' else 'N' end); + gms_xmldom.setAttribute(booklist, 'type', 'science and engineering'); + listNode := gms_xmldom.makeNode(booklist); + + /*book*/ + bookElem := gms_xmldom.createElement(doc, 'book'); + gms_xmldom.setAttribute(bookElem, 'category', 'python'); + bookElemNode := gms_xmldom.makeNode(bookElem); + /*title*/ + titleElem := gms_xmldom.createElement(doc, 'title'); + titleElemNode := gms_xmldom.makeNode(titleElem); + /*attribute of title*/ + titleText := gms_xmldom.createTextNode(doc, 'learning python'); + isNull := gms_xmldom.isNull(titleText); + raise notice '%', ('DOMText : ' || case when isNull then 'Y' else 'N' end); + titleTextNode := gms_xmldom.makeNode(titleText); + resnode := gms_xmldom.appendChild(titleElemNode, titleTextNode); + /*author*/ + authorElem := gms_xmldom.createElement(doc, 'author'); + authorElemNode := gms_xmldom.makeNode(authorElem); + /*attribute of author*/ + authorText := gms_xmldom.createTextNode(doc, '张三'); + authorTextNode := gms_xmldom.makeNode(authorText); + authorTextNode := gms_xmldom.appendChild(authorElemNode, authorTextNode); + /*pageNumber*/ + pageElem := gms_xmldom.createElement(doc, 'pageNumber'); + pageElemNode := gms_xmldom.makeNode(pageElem); + /*attribute of pageNumber*/ + pageText := gms_xmldom.createTextNode(doc, '600'); + pageTextNode := gms_xmldom.makeNode(pageText); + resnode := gms_xmldom.appendChild(pageElemNode, pageTextNode); + + resnode := gms_xmldom.appendChild(bookElemNode, titleElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, authorElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, pageElemNode); + resnode := gms_xmldom.appendChild(listNode, bookElemNode); + resnode := gms_xmldom.appendChild(root, listNode); + + buffer := gms_xmldom.writeToBuffer(doc, buffer); + raise notice '%', buffer; +END; +/ +--(16)createComment(doc DOMDocument, data IN VARCHAR2) +--(17)isNull(com DOMComment) +--(18)makeNode(com DOMComment) +--(19)createProcessingInstruction(doc DOMDocument, target IN VARCHAR2, data IN VARCHAR2) +--(20)isNull(pi DOMProcessingInstruction) +--(21)makeNode(pi DOMProcessingInstruction) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node1 gms_xmldom.DOMNODE; + comment gms_xmldom.DOMComment; + commentNode gms_xmldom.DOMNode; + resNode gms_xmldom.DOMNode; + + procInstruc gms_xmldom.DOMProcessingInstruction; + procInstrucNode gms_xmldom.DOMNode; + wclob clob; + isNull boolean; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getFirstChild(docNode); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node1 := gms_xmldom.item(nodeList, 0); + + --创建和插入comment节点 + comment := gms_xmldom.createComment(doc, 'This is the introduction of books'); + isNull := gms_xmldom.isNull(comment); + raise notice '%', ('DOMComment : ' || case when isNull then 'Y' else 'N' end); + commentNode := gms_xmldom.makeNode(comment); + resNode := gms_xmldom.insertBefore(bookListNode, commentNode, node1); + + --创建和插入ProcessingInstruction节点 + procInstruc := gms_xmldom.createProcessingInstruction(doc, 'xml', 'version="2.0"'); + isNull := gms_xmldom.isNull(procInstruc); + raise notice '%', ('DOMProcessingInstruction : ' || case when isNull then 'Y' else 'N' end); + procInstrucNode := gms_xmldom.makeNode(procInstruc); + resNode := gms_xmldom.insertBefore(docNode, procInstrucNode, bookListNode); + + wclob := gms_xmldom.writeToClob(doc, wclob); + --输出修改后的clob内容 + raise notice '%', wclob; +END; +/ +--(22)createCDATASection(doc gms_xmldom.DOMDocument, data IN VARCHAR2) +--(23)isNull(cds DOMCDATASection) +--(24)makeNode(cds DOMCDATASection) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node1 gms_xmldom.DOMNODE; + redNode gms_xmldom.DOMNODE; + wclob clob; + + cds gms_xmldom.DOMCDataSection; + cdsNode gms_xmldom.DOMNode; + isNull boolean; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + + bookListNode := gms_xmldom.getFirstChild(docNode); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node1 := gms_xmldom.item(nodeList, 0); + + cds := gms_xmldom.createCDataSection(doc, '<>&'); + isNull := gms_xmldom.isNull(cds); + raise notice '%', ('DOMCDataSection : ' || case when isNull then 'Y' else 'N' end); + cdsNode := gms_xmldom.makeNode(cds); + redNode := gms_xmldom.appendChild(node1, cdsNode); + + cds := gms_xmldom.createCDataSection(doc, '&'); + cdsNode := gms_xmldom.makeNode(cds); + redNode := gms_xmldom.appendChild(node1, cdsNode); + + cds := gms_xmldom.createCDataSection(doc, ']]'); + cdsNode := gms_xmldom.makeNode(cds); + redNode := gms_xmldom.appendChild(node1, cdsNode); + + wclob := gms_xmldom.writeToClob(doc, wclob); + --输出修改后的clob内容 + raise notice '%', wclob; +END; +/ +--插入失败 +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node1 gms_xmldom.DOMNODE; + redNode gms_xmldom.DOMNODE; + wclob clob; + + cds gms_xmldom.DOMCDataSection; + cdsNode gms_xmldom.DOMNode; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + + bookListNode := gms_xmldom.getFirstChild(docNode); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node1 := gms_xmldom.item(nodeList, 0); + + cds := gms_xmldom.createCDataSection(doc, ']]>'); + cdsNode := gms_xmldom.makeNode(cds); + redNode := gms_xmldom.appendChild(node1, cdsNode); + + wclob := gms_xmldom.writeToClob(doc, wclob); + --输出修改后的clob内容 + raise notice '%', wclob; +END; +/ + +--(25)createDocument(namespaceuri IN VARCHAR2, qualifiedname IN VARCHAR2, doctype IN DOMType:= NULL) +--(26)createElement(doc gms_xmldom.DOMDocument, tagname IN VARCHAR2, ns IN VARCHAR2) +--(27)setAttribute(elem gms_xmldom.DOMElement, name IN VARCHAR2, newvalue IN VARCHAR2, ns IN VARCHAR2) +DECLARE + doc gms_xmldom.DOMDocument; + rootElem gms_xmldom.DOMElement; + rootNode gms_xmldom.DOMNode; + elem gms_xmldom.DOMElement; + elemNode gms_xmldom.DOMNode; + wclob clob; + resNode gms_xmldom.DOMNode; +BEGIN + doc := gms_xmldom.createDocument('http://www.runoob.com/xml/', 'xml', null); + rootElem := gms_xmldom.getDocumentElement(doc); + rootNode := gms_xmldom.makeNode(rootElem); + + elem := gms_xmldom.createElement(doc, 'head', 'http://www.runoob.com/xml/'); + gms_xmldom.setAttribute(elem, 'id', 'headDoc', 'http://www.runoob.com/xml/'); + elemNode := gms_xmldom.makeNode(elem); + resNode := gms_xmldom.appendChild(rootNode, elemNode); + + elem := gms_xmldom.createElement(doc, 'body', 'http://www.runoob.com/xml/'); + gms_xmldom.setAttribute(elem, 'id', 'bodyDoc', 'http://www.runoob.com/xml/'); + elemNode := gms_xmldom.makeNode(elem); + resNode := gms_xmldom.appendChild(rootNode, elemNode); + + wclob :=gms_xmldom.writeToClob(doc, wclob); + --输出clob内容 + raise notice '%', wclob; +END; +/ +--(28)makeElement(n DOMNode) +--(29)getElementsByTagName(elem DOMElement, name IN VARCHAR2, ns varchar2) +--(30)createAttribute(doc DOMDocument, name IN VARCHAR2, ns IN VARCHAR2) +--(31)setAttributeNode(elem DOMElement, newattr IN DOMAttr, ns IN VARCHAR2) +--(32)getChildrenByTagName(elem DOMElement, name varchar2, ns varchar2) +--(33)item(nl DOMNodeList, idx IN PLS_INTEGER) +DECLARE + doc gms_xmldom.DOMDocument; + root gms_xmldom.DOMNode; + rootElem gms_xmldom.DOMElement; + bookElem gms_xmldom.DOMElement; + bookElemNode gms_xmldom.DOMNode; + titleElem gms_xmldom.DOMElement; + titleElemNode gms_xmldom.DOMNode; + titleText gms_xmldom.DOMText; + titleTextNode gms_xmldom.DOMNode; + authorElem gms_xmldom.DOMElement; + authorElemNode gms_xmldom.DOMNode; + authorText gms_xmldom.DOMText; + authorTextNode gms_xmldom.DOMNode; + pageElem gms_xmldom.DOMElement; + pageElemNode gms_xmldom.DOMNode; + pageText gms_xmldom.DOMText; + pageTextNode gms_xmldom.DOMNode; + resnode gms_xmldom.DOMNode; + cl clob; + + bookListNode gms_xmldom.DOMNode; + bookListElem gms_xmldom.DOMElement; + nodeList gms_xmldom.DOMNodelist; + node gms_xmldom.DOMNode; + nodeElem gms_xmldom.DOMElement; + attr gms_xmldom.DOMAttr; + resAttr gms_xmldom.DOMAttr; + len integer; +BEGIN + --booklist + doc := gms_xmldom.createDocument('http://www.runoob.com/xml/', 'booklist', null); + rootElem := gms_xmldom.getDocumentElement(doc); + gms_xmldom.setAttribute(rootElem, 'type', 'science and engineering', 'http://www.runoob.com/xml/'); + root := gms_xmldom.makeNode(rootElem); + --book + bookElem := gms_xmldom.createElement(doc, 'book', 'http://www.runoob.com/xml/'); + gms_xmldom.setAttribute(bookElem, 'category', 'python', 'http://www.runoob.com/xml/'); + bookElemNode := gms_xmldom.makeNode(bookElem); + --title + titleElem := gms_xmldom.createElement(doc, 'book', 'http://www.runoob.com/xml/'); + titleElemNode := gms_xmldom.makeNode(titleElem); + --attribute of title + titleText := gms_xmldom.createTextNode(doc, 'learning python'); + titleTextNode := gms_xmldom.makeNode(titleText); + resnode := gms_xmldom.appendChild(titleElemNode, titleTextNode); + --author + authorElem := gms_xmldom.createElement(doc, 'author', 'http://www.runoob.com/xml/'); + authorElemNode := gms_xmldom.makeNode(authorElem); + --attribute of author + authorText := gms_xmldom.createTextNode(doc, '张三'); + authorTextNode := gms_xmldom.makeNode(authorText); + authorTextNode := gms_xmldom.appendChild(authorElemNode, authorTextNode); + --pageNumber + pageElem := gms_xmldom.createElement(doc, 'pageNumber', 'http://www.runoob.com/xml/'); + pageElemNode := gms_xmldom.makeNode(pageElem); + --attribute of pageNumber + pageText := gms_xmldom.createTextNode(doc, '600'); + pageTextNode := gms_xmldom.makeNode(pageText); + resnode := gms_xmldom.appendChild(pageElemNode, pageTextNode); + + resnode := gms_xmldom.appendChild(bookElemNode, titleElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, authorElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, pageElemNode); + resnode := gms_xmldom.appendChild(root, bookElemNode); + + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', cl; + + root := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getFirstChild(root); + bookListElem := gms_xmldom.makeElement(bookListNode); + nodeList := gms_xmldom.getChildrenByTagName(bookListElem, 'book', 'http://www.runoob.com/xml/'); + --nodeList := gms_xmldom.getElementsByTagName(bookListElem, 'book', 'http://www.runoob.com/xml/'); + len := gms_xmldom.getLength(nodeList); + for i in 0..len-1 loop + node := gms_xmldom.item(nodeList,i); + nodeElem := gms_xmldom.makeElement(node); + attr := gms_xmldom.createAttribute(doc, 'num', 'http://www.runoob.com/xml/'); + resAttr := gms_xmldom.setAttributeNode(nodeElem, attr, 'http://www.runoob.com/xml/'); + end loop; + --输出修改后的clob内容 + cl := gms_xmldom.writetoclob(doc,cl); + raise notice '%', ('设置属性名后:' || cl); +END; +/ + +--(34)createAttribute(doc DOMDocument, name IN VARCHAR2) +--(35)isNull(a DOMAttr) +--(36)setAttributeNode(elem DOMElement, newattr IN DOMAttr) +--(37)getElementsByTagName(doc DOMElement, tagname IN VARCHAR2) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docElem gms_xmldom.DOMElement; + nodeList gms_xmldom.DOMNodelist; + node gms_xmldom.DOMNODE; + wclob clob; + elemNode gms_xmldom.DOMElement; + attr1 gms_XMLDOM.DOMAttr; + attr2 gms_XMLDOM.DOMAttr; + isNull boolean; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + doc := gms_xmldom.newDOMDocument(var); + --打印设置属性名前的wclob + wclob := gms_xmldom.writeToClob(doc, wclob); + raise notice '%', ('设置属性名前:' || wclob); + + docElem := gms_xmldom.getDocumentElement(doc); + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodeList, 0); + elemNode := gms_XMLDOM.makeElement(node); + --创建DOMAttr属性节点 + attr1 := gms_xmldom.createAttribute(doc,'category'); + isNull := gms_xmldom.isNull(attr1); + raise notice '%', ('DOMAttr : ' || case when isNull then 'Y' else 'N' end); + attr2 := gms_xmldom.setAttributeNode(elemNode, attr1); + + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改后的clob内容 + raise notice '%', ('设置属性名后:' || wclob); +END; +/ +--(38)getElementsByTagName(doc DOMDocument, tagname IN VARCHAR2) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + nodeList gms_xmldom.DOMNodelist; + node gms_xmldom.DOMNODE; + wclob clob; + elemNode gms_xmldom.DOMElement; + attr1 gms_XMLDOM.DOMAttr; + attr2 gms_XMLDOM.DOMAttr; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + doc := gms_xmldom.newDOMDocument(var); + --打印设置属性名前的wclob + wclob := gms_xmldom.writeToClob(doc, wclob); + raise notice '%', ('设置属性名前:' || wclob); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodeList, 0); + elemNode := gms_XMLDOM.makeElement(node); + --创建DOMAttr属性节点 + attr1 := gms_xmldom.createAttribute(doc,'category'); + attr2 := gms_xmldom.setAttributeNode(elemNode, attr1); + + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改后的clob内容 + raise notice '%', ('设置属性名后:' || wclob); +END; +/ + +--(39)createDocumentFragment(doc DOMDocument) +--(40)isNull(df DOMDocumentFragment) +--(41)makeNode(df DOMDocumentFragment) +--(42)writeToBuffer(n DOMDocumentFragment, buffer IN OUT VARCHAR2) +DECLARE + doc gms_XMLDOM.DOMDocument; + docfragment gms_XMLDOM.DOMDocumentFragment; + docfragmentnode gms_XMLDOM.DOMnode; + createelem gms_XMLDOM.DOMElement; + elemnode gms_XMLDOM.DOMNode; + text gms_XMLDOM.DOMText; + textnode gms_XMLDOM.DOMNode; + resNode gms_XMLDOM.DOMNode; + buf varchar2(4000); + isNull boolean; +BEGIN + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(); + docfragment := gms_XMLDOM.createDocumentFragment(doc); + isNull := gms_xmldom.isNull(docfragment); + raise notice '%', ('DOMDocumentFragment : ' || case when isNull then 'Y' else 'N' end); + docfragmentnode := gms_XMLDOM.makeNode(docfragment ); + --在文档片段添加内容 + --创建DOMElement对象 + createelem := gms_XMLDOM.createElement(doc,'test'); + elemnode := gms_XMLDOM.makeNode(createelem); + --创建内容 + text := gms_XMLDOM.createTextnode(doc,'testtext'); + textnode := gms_XMLDOM.makeNode(text); + --添加到指定位置 + resNode := gms_XMLDOM.appendChild(elemnode,textnode); + resNode := gms_XMLDOM.appendChild(docfragmentnode,elemnode); + --写入buffer + buf := gms_xmldom.writetobuffer(docfragment,buf); + --输出修改后的clob内容 + raise notice '%', ('文档片段为:' ||buf); +END; +/ +--(43)writeToClob(n DOMNode, cl IN OUT CLOB) +--(44)writeToClob(n DOMNode, cl IN OUT CLOB, pflag IN NUMBER, indent IN NUMBER) +--(45)writeToClob(doc DOMDocument, cl IN OUT CLOB, pflag IN NUMBER, indent IN NUMBER) +--(46)writeToBuffer(n DOMNode, buffer IN OUT VARCHAR2) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node1 gms_xmldom.DOMNODE; + + wclob clob; + buffer varchar2; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getFirstChild(docNode); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node1 := gms_xmldom.item(nodeList, 0); + wclob := gms_xmldom.writeToClob(node1, wclob); + raise notice '%', ('writeToClob(DOMNode, clob):' || wclob); + + wclob := gms_xmldom.writeToClob(node1, wclob, 4, 0); + raise notice '%', ('writeToClob(DOMNode, clob, number, number):' || wclob); + + wclob := gms_xmldom.writeToClob(doc, wclob, 4, 0); + raise notice '%', ('writeToClob(DOMDocument, clob, number, number):' || wclob); + + buffer := gms_xmldom.writeToBuffer(node1, buffer); + raise notice '%', ('writeToBuffer(DOMNode, varchar2):' || buffer); +END; +/ +--(47)setVersion(doc DOMDocument, version VARCHAR2) +DECLARE + doc gms_xmldom.DOMDocument; + cl clob; + x xml; +BEGIN + x := xml('ramesh'); + doc := gms_xmldom.newDomDocument(x); + gms_xmldom.setVersion(doc, '2.0'); + cl := gms_xmldom.writeToClob(doc, cl); + raise notice '%', (cl); +END; +/ +--(48)getFirstChild(n DOMNode) +--(49)getNodeName(n DOMNode) +--(50)getChildrenByTagName(elem DOMElement, name varchar2) +--(51)getDocumentElement(doc DOMDocument) +--(52)getNodeValue(n DOMNode) +--(53)getChildNodes(n DOMNode) +--(54)getLength(nl DOMNodeList) +--(55)getNodeType(n DOMNode) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodeList; + node gms_xmldom.DOMNode; + titleNode gms_xmldom.DOMNode; + elemNode gms_xmldom.DOMElement; + txt gms_xmldom.DOMText; + textNode gms_xmldom.DOMNode; + + wclob clob; + llen integer; + n integer := 0; +BEGIN + var := xml(' + + + learning math + 张三 + 561 + + + + learning Python + 李四 + 600 + + + + learning C++ + 王二 + 500 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + + wclob := gms_xmldom.writeToClob(doc, wclob); + raise notice '%', ('xml内容是:' || wclob); + + --getDocumentElement + elemNode := gms_xmldom.getDocumentElement(doc); + bookListNode := gms_xmldom.getFirstChild(docNode); + --getChildrenByTagName + nodeList := gms_xmldom.getChildrenByTagName(elemNode, 'book'); + node := gms_xmldom.item(nodeList, 0); + --getFirstChild,getNodeName + titleNode := gms_xmldom.getFirstChild(node); + wclob := gms_xmldom.writeToClob(titleNode, wclob); + raise notice '%', (wclob); + raise notice '%', ('The nodeName is:' || gms_xmldom.getNodeName(titleNode)); + --element节点的nodeValue,为空 + raise notice '%', ('The nodeValue is:' || gms_xmldom.getNodeValue(titleNode)); + txt := gms_xmldom.getFirstChild(titleNode); + textNode := gms_xmldom.makeNode(txt); + raise notice '%', ('The nodeValue is:' || gms_xmldom.getNodeValue(textNode)); + --getChildNodes + nodeList := gms_xmldom.getChildNodes(bookListNode); + llen := gms_xmldom.getLength(nodeList); + raise notice '%', ('booklist子节点长度为:' || llen ); + + for i in 0..(llen-1) loop + node := gms_xmldom.item(nodeList, i); + --getNodeType + if gms_xmldom.getNodeType(node) = gms_xmldom.COMMENT_NODE then + n := n+1; + --comment节点的nodeValue + raise notice '%', ('第'||'个备注为:'||gms_xmldom.getNodeValue(node)); + end if; + end loop; + +END; +/ +--(56)getLocalName(a DOMAttr) +--(57)getLocalName(elem DOMElement) +--(58)getLocalName(n DOMnode, data OUT VARCHAR2) +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + docNode gms_xmldom.DOMNode; + bookListNode gms_xmldom.DOMNode; + bookNode gms_xmldom.DOMNode; + titleElem gms_xmldom.DOMElement; + nodeList gms_xmldom.DOMNodelist; + + attr gms_xmldom.DOMAttr; + + wclob clob; + localName varchar2; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + + wclob := gms_xmldom.writeToClob(doc, wclob); + raise notice '%', ('xml内容是:' || wclob); + + bookListNode := gms_xmldom.getFirstChild(docNode); + bookNode := gms_xmldom.getFirstChild(bookListNode); + nodeList := gms_xmldom.getChildNodes(bookNode); + titleElem := gms_xmldom.item(nodeList, 0); + + --element的localName + localName := gms_xmldom.getLocalName(titleElem); + raise notice '%', ('element的localName:' || localName); + --node的localName + localName := gms_xmldom.getLocalName(bookNode); + raise notice '%', ('node的localName:' || localName); + + --attr的LocalName + attr := gms_xmldom.createAttribute(doc, 'name'); + localName := gms_xmldom.getLocalName(attr); + raise notice '%', ('attr的LocalName:' || localName); +END; +/ +--(59)hasChildNodes(n DOMNode) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + childnode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + wclob clob; + haschild boolean; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + docNode := gms_XMLDOM.makeNode(doc); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改前的内容 + raise notice '%', ('xml内容是:' || wclob); + + --获取存在子节点的节点 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,1); + --判断该节点是否有子节点 + haschild := gms_XMLDOM.hasChildNodes(node); + raise notice '%', ('The result is:' || case when haschild then 'Y' else 'N' end); + + --获取不存在子节点的节点 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'press'); + node := gms_XMLDOM.item(nodelist,0); + --判断该节点是否有子节点 + haschild := gms_XMLDOM.hasChildNodes(node); + raise notice '%', ('The result is:' || case when haschild then 'Y' else 'N' end); +END; +/ + +--(60)cloneNode(n DOMNode, deep boolean)(无子项) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + childnode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + clonenode gms_XMLDOM.DOMNode; + resNode gms_XMLDOM.DOMNode; + insertnode gms_XMLDOM.DOMNode; + wclob clob; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改前的内容 + raise notice '%', ('克隆前:' ||wclob); + + docelem := gms_xmldom.getDocumentElement(doc); + docNode := gms_XMLDOM.makeNode(docelem); + --获取对应子元素 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,0); + --克隆节点,不克隆节点的子节点 + clonenode := gms_XMLDOM.clonenode(node,false); + insertnode := gms_XMLDOM.insertbefore(docNode,clonenode,node); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改后的clob内容 + raise notice '%', ('克隆(无子项)后:' ||wclob); +END; +/ +--cloneNode(n DOMNode, deep boolean)(有子项) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + childnode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + clonenode gms_XMLDOM.DOMNode; + insertnode gms_XMLDOM.DOMNode; + n gms_XMLDOM.DOMNode; + wclob clob; +BEGIN + var := xml(' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改前的内容 + raise notice '%', ('克隆前:' ||wclob); + + docelem := gms_xmldom.getDocumentElement(doc); + docNode := gms_XMLDOM.makeNode(docelem); + --获取对应子元素 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,0); + --克隆节点,克隆节点的子节点 + clonenode := gms_XMLDOM.clonenode(node, true); + insertnode := gms_XMLDOM.insertbefore(docNode,clonenode,node); + --写入clob + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出修改后的clob内容 + raise notice '%', ('克隆(有子项)后:' ||wclob); +END; +/ + +--(61)getAttributes(n DOMNode) +--(62)isNull(nnm DOMNamedNodeMap) +--(63)getLength(nnm DOMNamedNodeMap) +--(64)item(nnm DOMNamedNodeMap, idx IN PLS_INTEGER) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + node gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + nodelist gms_XMLDOM.DOMNodelist; + attrmap gms_XMLDOM.DOMNamedNodeMap; + length integer; + isNull boolean; + attr gms_xmldom.DOMAttr; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + --返回新的document实例 + doc := gms_xmldom.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc ); + docelem := gms_xmldom.getDocumentElement(doc); + nodelist := gms_xmldom.getElementsByTagName(docelem,'book'); + node := gms_xmldom.item(nodelist,0); + --获取第一个EMP节点的属性 + attrmap := gms_xmldom.getAttributes(node); + isNull := gms_xmldom.isNull(attrmap); + raise notice '%', ('isNull(DOMNamedNodeMap):' || case when isNull then 'Y' else 'N' end); + + --查看map的长度验证结果 + length := gms_xmldom.getlength(attrmap); + raise notice '%', ('第一个book节点的属性长度为:' || length ); + + --item(DOMNamedNodeMap) + attr := gms_xmldom.item(attrmap, 0); +END; +/ + +--(65)freeDocument(doc DOMDocument) +--(66)freeNodeList(nl DOMNodeList) +--(67)isNull(nl DOMNodeList) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + nodeList gms_xmldom.DOMNodelist; + wclob clob; + isNull boolean; + node gms_XMLDOM.DOMNode; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + --返回一个新document实例 + doc := gms_XMLDOM.newDOMDocument(var); + --设置XML文档信息 + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出clob内容 + raise notice '%', (wclob); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node := gms_xmldom.item(nodeList, 0); + --释放node + gms_xmldom.freeDocument(doc); + --检查node是否释放成功 + isNull := gms_xmldom.isNull(doc); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + --释放了父节点,子节点是否释放 + isNull := gms_xmldom.isNull(nodeList); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + isNull := gms_xmldom.isNull(node); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + + node := gms_xmldom.item(nodeList, 0); + wclob := gms_xmldom.writetoclob(node, wclob); + raise notice '%', (wclob); + + gms_xmldom.freeNodeList(nodeList); + isNull := gms_xmldom.isNull(nodeList); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + isNull := gms_xmldom.isNull(node); + raise notice '%', ('The result is : ' || case when isNull then 'Y' else 'N' end); + + wclob := gms_xmldom.writetoclob(node, wclob); + raise notice '%', (wclob); +END; +/ + +--(68)freeNode(nl DOMNode) +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + nodeList gms_xmldom.DOMNodelist; + wclob clob; + isNull boolean; + node gms_XMLDOM.DOMNode; +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + --返回一个新document实例 + doc := gms_XMLDOM.newDOMDocument(var); + --设置XML文档信息 + wclob := gms_xmldom.writetoclob(doc,wclob); + --输出clob内容 + raise notice '%', (wclob); + + nodeList := gms_xmldom.getElementsByTagName(doc, 'book'); + node := gms_xmldom.item(nodeList, 0); + --检查node是否释放成功 + isNull := gms_xmldom.isNull(doc); + raise notice '%', ('The document is : ' || case when isNull then 'Y' else 'N' end); + isNull := gms_xmldom.isNull(node); + raise notice '%', ('The node is : ' || case when isNull then 'Y' else 'N' end); + wclob := gms_xmldom.writetoclob(node, wclob); + raise notice '%', (wclob); + + gms_xmldom.freeNode(node); + isNull := gms_xmldom.isNull(node); + raise notice '%', ('The node is : ' || case when isNull then 'Y' else 'N' end); +END; +/ + +--(69)insertBefore(n DOMNode, newchild IN DOMNode) +--在同一位置插入同名同内容的节点 +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + elemnode gms_XMLDOM.DOMNode; + bookListNode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + createelem gms_XMLDOM.DOMElement; + resNode gms_XMLDOM.DOMNode; + text gms_XMLDOM.DOMText; + textnode gms_XMLDOM.DOMNode; + buf varchar2(4000); +BEGIN + var := xml(' + + learning math + 张三 + 561 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getfirstchild(docNode); + + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + raise notice '%', ('XML内容:' ||buf); + + --获取对应子元素 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,0); + --输出buf内容 + buf := gms_xmldom.writetobuffer(node,buf); + raise notice '%', ('book内容:' ||buf); + + --创建DOMElement对象 + createelem := gms_XMLDOM.createelement(doc,'test'); + elemnode := gms_XMLDOM.makenode(createelem); + --创建内容 + text := gms_XMLDOM.createTextnode(doc,'testtext'); + textnode := gms_XMLDOM.makenode(text); + resNode := gms_XMLDOM.appendchild(elemnode,textnode); + + --添加到指定位置 + resNode := gms_XMLDOM.insertbefore(bookListNode, elemnode, node); + --写入buffer + buf := gms_xmldom.writetobuffer(doc, buf); + --输出buf内容 + raise notice '%', ('第一次appendchild后:' ||buf); + + createelem := gms_XMLDOM.createelement(doc,'test'); + elemnode := gms_XMLDOM.makenode(createelem); + --创建内容 + text := gms_XMLDOM.createTextnode(doc,'testtext'); + textnode := gms_XMLDOM.makenode(text); + resNode := gms_XMLDOM.appendchild(elemnode,textnode); + resNode := gms_XMLDOM.insertbefore(bookListNode, elemnode, node); + --写入buffer + buf := gms_xmldom.writetobuffer(doc, buf); + --输出buf内容 + raise notice '%', ('第一次appendchild后:' ||buf); +END; +/ +--(70)appendChild(n DOMNode, newchild IN DOMNode) +--插入同名同内容节点 +DECLARE + var xml; + doc gms_XMLDOM.DOMDocument; + docNode gms_XMLDOM.DOMNode; + docelem gms_XMLDOM.DOMElement; + node gms_XMLDOM.DOMNode; + elemnode gms_XMLDOM.DOMNode; + bookListNode gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + createelem gms_XMLDOM.DOMElement; + resNode gms_XMLDOM.DOMNode; + text gms_XMLDOM.DOMText; + textnode gms_XMLDOM.DOMNode; + buf varchar2(4000); +BEGIN + var := xml(' + + learning math + 张三 + 561 + testtext + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + docNode := gms_xmldom.makeNode(doc); + bookListNode := gms_xmldom.getfirstchild(docNode); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + --输出buf内容 + raise notice '%', ('XML内容:' ||buf); + --获取对应子元素 + nodelist := gms_XMLDOM.getElementsByTagName(doc, 'book'); + node := gms_XMLDOM.item(nodelist,0); + + --创建DOMElement对象 + createelem := gms_XMLDOM.createelement(doc,'test'); + elemnode := gms_XMLDOM.makenode(createelem); + --创建内容 + text := gms_XMLDOM.createTextnode(doc,'testtext'); + textnode := gms_XMLDOM.makenode(text); + resNode := gms_XMLDOM.appendchild(elemnode,textnode); + + --添加到指定位置 + resNode := gms_XMLDOM.appendchild(node,elemnode); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + --输出buf内容 + raise notice '%', ('appendchild后:' ||buf); +END; +/ + +--(71)getNodeValueAsClob(n domnode) +--获取CData片段节点的值 +DECLARE + var clob; + doc gms_XMLDOM.DOMDocument; + ndoc gms_xmldom.DOMNode; + node gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + listlength integer; + buf varchar2(4000); + n integer := 0; +BEGIN + var := xml(' + + + learning math + 张三 + 561 + + + + learning Python + 李四 + 600 + + + + learning C++ + 王二 + 500 + +'); + --返回新的document实例 + doc := gms_XMLDOM.newDOMDocument(var); + ndoc := gms_xmldom.makenode(doc); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + raise notice '%', ('XML内容:' ||buf); + + node := gms_xmldom.getfirstchild(ndoc); + --获取第一层子节点列表 + nodelist := gms_xmldom.getchildnodes(node); + listlength := gms_XMLDOM.getlength(nodelist); + --检索第一层子节点 + FOR i in 0..(listlength-1) loop + node := gms_XMLDOM.item(nodelist,i); + --筛选CDATA片段节点 + IF gms_xmldom.getnodetype(node) = gms_xmldom.cdata_section_node THEN + n := n+1; + --输出CDATA片段节点的值 + raise notice '%', ('第' || n || '个CDATA片段值为' || gms_XMLDOM.getNodeValueAsClob(node) ); + END IF; + END LOOP; +END; +/ + +--(72)getOwnerDocument(n DOMNode) +DECLARE + var clob; + doc gms_XMLDOM.DOMDocument; + doc1 gms_XMLDOM.DOMDocument; + ndoc gms_xmldom.DOMNode; + node gms_XMLDOM.DOMNode; + nodelist gms_XMLDOM.DOMNodelist; + buf varchar2(4000); +BEGIN + var := ' + + + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'; + --返回新的document + doc := gms_XMLDOM.newDOMDocument(var); + ndoc := gms_xmldom.makenode(doc); + --无关联document的节点 + doc1 := gms_XMLDOM.getownerdocument(node); + --写入buffer + buf := gms_xmldom.writetobuffer(doc1,buf); + --输出buf内容 + raise notice '%', ('无关联document的节点:' ||buf); + --获取节点列表 + node := gms_XMLDOM.getfirstchild(ndoc); + nodelist := gms_XMLDOM.getchildnodes(node); + node := gms_XMLDOM.item(nodelist,0); + --有关联document的节点 + doc1 := gms_XMLDOM.getownerdocument(node); + --写入buffer + buf := gms_xmldom.writetobuffer(doc1,buf); + --输出buf内容 + raise notice '%', ('有关联document的节点:' ||buf); +END; +/ +--(73)gms_xmlparser.newParser +--(74)gms_xmlparser.parseClob(p gms_xmlparser.Parser, doc CLOB) +--(75)gms_xmlparser.getDocument(p gms_xmlparser.Parser) +DECLARE + var clob; + parser gms_XMLPARSER.parser; + doc gms_XMLDOM.DOMDocument; + buf varchar2(4000); +BEGIN + var := ' + + learning math + 张三 + 561 + + + learning Python + 李四 + 600 + + + learning C++ + 王二 + 500 + +'; + parser := gms_XMLPARSER.newParser(); + gms_XMLPARSER.parseClob(parser, var); + doc := gms_XMLPARSER.getDocument(parser); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + --输出buf内容 + raise notice '%', ('XML内容:' ||buf); +END; +/ + +--(76)gms_xmlparser.parseBuffer(p gms_xmlparser.Parser,doc VARCHAR2) +DECLARE + var varchar2(4000); + parser gms_XMLPARSER.parser; + doc gms_XMLDOM.DOMDocument; + buf varchar2(4000); +BEGIN + var := ' + + + + + +]> + + &name; +
&address;
+
'; + parser := gms_XMLPARSER.newParser(); + gms_XMLPARSER.parsebuffer(parser,var); + doc := gms_XMLPARSER.getDocument(parser); + --写入buffer + buf := gms_xmldom.writetobuffer(doc,buf); + --输出buf内容 + raise notice '%', ('XML内容:' ||buf); +END; +/ + +DECLARE + var xml; + doc gms_xmldom.DOMDocument; + wclob clob; +BEGIN + var := xml(' + + learning math + + 张三 + 561 + + + +'); + doc := gms_xmldom.newDOMDocument(var); + + wclob := gms_xmldom.writeToClob(doc, wclob); + --输出修改后的clob内容 + raise notice '%', ('xml内容为:' || wclob); +END; +/ + +DECLARE + l_dom gms_xmldom.DOMDocument; + l_clob clob := ''; + v_element gms_xmldom.domelement; + l_nodelist1 gms_xmldom.domnodelist; + l_nodelist2 gms_xmldom.domnodelist; + l_node gms_xmldom.DOMNode; +begin + l_dom := gms_xmldom.newDomDocument(l_clob); + l_nodelist1 := gms_xmldom.getelementsbytagname(l_dom,'jwg'); + l_node := gms_xmldom.item(l_nodelist1,0); + v_element := gms_xmldom.makeelement(l_node); + l_nodelist2 := gms_xmldom.getelementsbytagname(l_dom,'abc'); + l_node := gms_xmldom.item(l_nodelist2,0); + raise notice '%', (gms_xmldom.getnodetype(gms_xmldom.getfirstchild(l_node))); +end; +/ +DECLARE + l_dom gms_xmldom.DOMDocument; + l_clob clob := ''; + v_element gms_xmldom.domelement; + l_nodelist gms_xmldom.domnodelist; + l_node gms_xmldom.DOMNode; +begin + l_dom := gms_xmldom.newDomDocument(l_clob); + l_nodelist := gms_xmldom.getelementsbytagname(l_dom,'abc'); + l_node := gms_xmldom.item(l_nodelist,0); + gms_xmldom.freeNode(gms_xmldom.getfirstchild(l_node)); +end; +/ + +--makeCharacterData函数 +declare + v_clob clob; + v_doc gms_xmldom.domdocument; + v_nodelist gms_xmldom.domnodelist; + v_node1 gms_xmldom.domnode; + v_chardata1 gms_xmldom.DOMCharacterData; + v_char1 varchar2(100); +begin + v_clob:=' + + + 手动测试 + 测试勣 + 中文测试 + + '; + + v_doc := gms_xmldom.newdomdocument(v_clob); + v_nodelist := gms_xmldom.getelementsbytagname(v_doc, 'persons'); + v_node1 := gms_xmldom.getfirstchild(gms_xmldom.item(v_nodelist, 0)); + v_char1 := gms_xmldom.getnodevalue(v_node1); + raise notice '%', (v_char1); + + v_chardata1 := gms_xmldom.makeCharacterData(v_node1); + raise notice '%', (gms_XMLDOM.GETLENGTH(v_chardata1)); +end; +/ \ No newline at end of file diff --git a/src/test/regress/sql/plpython3u/plpython_gms_xslprocessor.sql b/src/test/regress/sql/plpython3u/plpython_gms_xslprocessor.sql new file mode 100644 index 0000000000..d4b61e41dd --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_gms_xslprocessor.sql @@ -0,0 +1,258 @@ +----gms_xslprocessor.selectnodes +----gms_xslprocessor.valueof + +declare + x xml; + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + x := xml( + ' + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + Harry Potter + J.K. Rowling + 2005 + 29.99 + + ' + ); + doc := gms_xmldom.newDomDocument(x); + node := gms_xmldom.makenode(gms_xmldom.getDocumentElement(doc)); + nodelist := gms_xslprocessor.selectnodes(node,'book'); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ + +declare + x xml; + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + x := xml( + ' + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + Harry Potter + J.K. Rowling + 2005 + 29.99 + + ' + ); + doc := gms_xmldom.newDomDocument(x); + node := gms_xmldom.makenode(gms_xmldom.getDocumentElement(doc)); + nodelist := gms_xslprocessor.selectnodes(node,'//*[not(*)]'); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ + +--路径错误 +declare + x xml; + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + x := xml( + ' + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + Harry Potter + J.K. Rowling + 2005 + 29.99 + + ' + ); + doc := gms_xmldom.newDomDocument(x); + node := gms_xmldom.makenode(gms_xmldom.getDocumentElement(doc)); + nodelist := gms_xslprocessor.selectnodes(node,'book/author/year'); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ + +--入参为null +declare + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + nodelist := gms_xslprocessor.selectnodes(NULL,'book/author/year'); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ + +declare + x xml; + doc gms_xmldom.DOMDocument; + node gms_xmldom.DOMNODE; + nodelist gms_XMLDOM.DOMNodelist; + len int; + title varchar2(100); + author varchar2(100); + outstr text; +begin + x := xml( + ' + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + Harry Potter + J.K. Rowling + 2005 + 29.99 + + ' + ); + doc := gms_xmldom.newDomDocument(x); + node := gms_xmldom.makenode(gms_xmldom.getDocumentElement(doc)); + nodelist := gms_xslprocessor.selectnodes(node,''); + len := gms_xmldom.getLength(nodelist); + for i in 0..len-1 loop + node := gms_xmldom.item(nodelist, i); + gms_xslprocessor.valueof(node, 'title/text()', title, null); + gms_xslprocessor.valueof(node, 'author/text()', author, null); + outstr := 'title: ' || title || ', ' || 'author: ' || author; + raise notice '%', outstr; + end loop; +end; +/ + +--指定namespace +DECLARE + doc gms_xmldom.DOMDocument; + root gms_xmldom.DOMNode; + rootElem gms_xmldom.DOMElement; + bookElem gms_xmldom.DOMElement; + bookElemNode gms_xmldom.DOMNode; + titleElem gms_xmldom.DOMElement; + titleElemNode gms_xmldom.DOMNode; + titleText gms_xmldom.DOMText; + titleTextNode gms_xmldom.DOMNode; + authorElem gms_xmldom.DOMElement; + authorElemNode gms_xmldom.DOMNode; + authorText gms_xmldom.DOMText; + authorTextNode gms_xmldom.DOMNode; + pageElem gms_xmldom.DOMElement; + pageElemNode gms_xmldom.DOMNode; + pageText gms_xmldom.DOMText; + pageTextNode gms_xmldom.DOMNode; + resnode gms_xmldom.DOMNode; + nodeList gms_xmldom.DOMNodelist; + node gms_xmldom.DOMNode; + nodeElem gms_xmldom.DOMElement; + attr gms_xmldom.DOMAttr; + resAttr gms_xmldom.DOMAttr; + len integer; +BEGIN + --booklist + doc := gms_xmldom.createDocument('http://www.runoob.com/xml/', 'booklist', null); + rootElem := gms_xmldom.getDocumentElement(doc); + gms_xmldom.setAttribute(rootElem, 'type', 'science and engineering', 'http://www.runoob.com/xml/'); + root := gms_xmldom.makeNode(rootElem); + --book + bookElem := gms_xmldom.createElement(doc, 'book', 'http://www.runoob.com/xml/'); + gms_xmldom.setAttribute(bookElem, 'category', 'python', 'http://www.runoob.com/xml/'); + bookElemNode := gms_xmldom.makeNode(bookElem); + --title + titleElem := gms_xmldom.createElement(doc, 'book', 'http://www.runoob.com/xml/'); + titleElemNode := gms_xmldom.makeNode(titleElem); + --attribute of title + titleText := gms_xmldom.createTextNode(doc, 'learning python'); + titleTextNode := gms_xmldom.makeNode(titleText); + resnode := gms_xmldom.appendChild(titleElemNode, titleTextNode); + --author + authorElem := gms_xmldom.createElement(doc, 'author', 'http://www.runoob.com/xml/'); + authorElemNode := gms_xmldom.makeNode(authorElem); + --attribute of author + authorText := gms_xmldom.createTextNode(doc, '张三'); + authorTextNode := gms_xmldom.makeNode(authorText); + authorTextNode := gms_xmldom.appendChild(authorElemNode, authorTextNode); + --pageNumber + pageElem := gms_xmldom.createElement(doc, 'pageNumber', 'http://www.runoob.com/xml/'); + pageElemNode := gms_xmldom.makeNode(pageElem); + --attribute of pageNumber + pageText := gms_xmldom.createTextNode(doc, '600'); + pageTextNode := gms_xmldom.makeNode(pageText); + resnode := gms_xmldom.appendChild(pageElemNode, pageTextNode); + resnode := gms_xmldom.appendChild(bookElemNode, titleElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, authorElemNode); + resnode := gms_xmldom.appendChild(bookElemNode, pageElemNode); + resnode := gms_xmldom.appendChild(root, bookElemNode); + + nodelist := gms_xslprocessor.selectnodes(root,'book','http://www.runoob.com/xml/'); + len := gms_xmldom.getLength(nodeList); + for i in 0..len-1 loop + node := gms_xmldom.item(nodeList,i); + nodeElem := gms_xmldom.makeElement(node); + attr := gms_xmldom.createAttribute(doc, 'num', 'http://www.runoob.com/xml/'); + resAttr := gms_xmldom.setAttributeNode(nodeElem, attr, 'http://www.runoob.com/xml/'); + end loop; +END; +/ diff --git a/src/test/regress/sql/plpython3u/plpython_import.sql b/src/test/regress/sql/plpython3u/plpython_import.sql new file mode 100644 index 0000000000..3031eef2e6 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_import.sql @@ -0,0 +1,68 @@ +-- import python modules + +CREATE FUNCTION import_fail() returns text + AS +'try: + import foosocket +except ImportError: + return "failed as expected" +return "succeeded, that wasn''t supposed to happen"' + LANGUAGE plpython3u; + + +CREATE FUNCTION import_succeed() returns text + AS +'try: + import array + import bisect + import calendar + import cmath + import errno + import math + import operator + import random + import re + import string + import time +except Exception as ex: + plpy.notice("import failed -- %s" % str(ex)) + return "failed, that wasn''t supposed to happen" +return "succeeded, as expected"' + LANGUAGE plpython3u; + +CREATE FUNCTION import_test_one(p text) RETURNS text + AS +'try: + import hashlib + digest = hashlib.sha1(p.encode("ascii")) +except ImportError: + import sha + digest = sha.new(p) +return digest.hexdigest()' + LANGUAGE plpython3u; + +CREATE FUNCTION import_test_two(u users) RETURNS text + AS +'plain = u["fname"] + u["lname"] +try: + import hashlib + digest = hashlib.sha1(plain.encode("ascii")) +except ImportError: + import sha + digest = sha.new(plain); +return "sha hash of " + plain + " is " + digest.hexdigest()' + LANGUAGE plpython3u; + + +-- import python modules +-- +SELECT import_fail(); +SELECT import_succeed(); + +-- test import and simple argument handling +-- +SELECT import_test_one('sha hash of this string'); + +-- test import and tuple argument handling +-- +select import_test_two(users) from users where fname = 'willem'; diff --git a/src/test/regress/sql/plpython3u/plpython_newline.sql b/src/test/regress/sql/plpython3u/plpython_newline.sql new file mode 100644 index 0000000000..cb22ba923f --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_newline.sql @@ -0,0 +1,20 @@ +-- +-- Universal Newline Support +-- + +CREATE OR REPLACE FUNCTION newline_lf() RETURNS integer AS +E'x = 100\ny = 23\nreturn x + y\n' +LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION newline_cr() RETURNS integer AS +E'x = 100\ry = 23\rreturn x + y\r' +LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS +E'x = 100\r\ny = 23\r\nreturn x + y\r\n' +LANGUAGE plpython3u; + + +SELECT newline_lf(); +SELECT newline_cr(); +SELECT newline_crlf(); diff --git a/src/test/regress/sql/plpython3u/plpython_params.sql b/src/test/regress/sql/plpython3u/plpython_params.sql new file mode 100644 index 0000000000..8bab488859 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_params.sql @@ -0,0 +1,42 @@ +-- +-- Test named and nameless parameters +-- + +CREATE FUNCTION test_param_names0(integer, integer) RETURNS int AS $$ +return args[0] + args[1] +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$ +assert a0 == args[0] +assert a1 == args[1] +return True +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$ +assert u == args[0] +if isinstance(u, dict): + # stringify dict the hard way because otherwise the order is implementation-dependent + u_keys = list(u.keys()) + u_keys.sort() + s = '{' + ', '.join([repr(k) + ': ' + repr(u[k]) for k in u_keys]) + '}' +else: + s = str(u) +return s +$$ LANGUAGE plpython3u; + +-- use deliberately wrong parameter names +CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$ +try: + assert a1 == args[0] + return False +except NameError as e: + assert e.args[0].find("a1") > -1 + return True +$$ LANGUAGE plpython3u; + + +SELECT test_param_names0(2,7); +SELECT test_param_names1(1,'text'); +SELECT test_param_names2(users) from users; +SELECT test_param_names2(NULL); +SELECT test_param_names3(1); diff --git a/src/test/regress/sql/plpython3u/plpython_populate.sql b/src/test/regress/sql/plpython3u/plpython_populate.sql new file mode 100644 index 0000000000..cc1e19b5dd --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_populate.sql @@ -0,0 +1,27 @@ +INSERT INTO users (fname, lname, username) VALUES ('jane', 'doe', 'j_doe'); +INSERT INTO users (fname, lname, username) VALUES ('john', 'doe', 'johnd'); +INSERT INTO users (fname, lname, username) VALUES ('willem', 'doe', 'w_doe'); +INSERT INTO users (fname, lname, username) VALUES ('rick', 'smith', 'slash'); + + +-- multi table tests +-- + +INSERT INTO taxonomy (name) VALUES ('HIV I') ; +INSERT INTO taxonomy (name) VALUES ('HIV II') ; +INSERT INTO taxonomy (name) VALUES ('HCV') ; + +INSERT INTO entry (accession, txid) VALUES ('A00001', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00002', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00003', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00004', '2') ; +INSERT INTO entry (accession, txid) VALUES ('A00005', '2') ; +INSERT INTO entry (accession, txid) VALUES ('A00006', '3') ; + +INSERT INTO sequences (sequence, eid, product, multipart) VALUES ('ABCDEF', 1, 'env', 'true') ; +INSERT INTO xsequences (sequence, pid) VALUES ('GHIJKL', 1) ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 2, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 3, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 4, 'gag') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 5, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 6, 'ns1') ; diff --git a/src/test/regress/sql/plpython3u/plpython_quote.sql b/src/test/regress/sql/plpython3u/plpython_quote.sql new file mode 100644 index 0000000000..2170a0ae00 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_quote.sql @@ -0,0 +1,32 @@ +-- test quoting functions + +CREATE FUNCTION quote(t text, how text) RETURNS text AS $$ + if how == "literal": + return plpy.quote_literal(t) + elif how == "nullable": + return plpy.quote_nullable(t) + elif how == "ident": + return plpy.quote_ident(t) + else: + raise plpy.Error("unrecognized quote type %s" % how) +$$ LANGUAGE plpython3u; + +SELECT quote(t, 'literal') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''''), + ('xyzv')) AS v(t); + +SELECT quote(t, 'nullable') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + (NULL)) AS v(t); + +SELECT quote(t, 'ident') FROM (VALUES + ('abc'), + ('a b c'), + ('a " ''abc''')) AS v(t); diff --git a/src/test/regress/sql/plpython3u/plpython_record.sql b/src/test/regress/sql/plpython3u/plpython_record.sql new file mode 100644 index 0000000000..a93e7a745f --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_record.sql @@ -0,0 +1,167 @@ +-- +-- Test returning tuples +-- + +CREATE TABLE table_record ( + first text, + second int4 + ) ; + +CREATE TYPE type_record AS ( + first text, + second int4 + ) ; + + +CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$ +return first + '_in_to_out'; +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_in_out_params_multi(first in text, + second out text, third out text) AS $$ +return (first + '_record_in_to_out_1', first + '_record_in_to_out_2'); +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_inout_params(first inout text) AS $$ +return first + '_inout'; +$$ LANGUAGE plpython3u; + + +-- Test tuple returning functions +SELECT * FROM test_table_record_as('dict', null, null, false); +SELECT * FROM test_table_record_as('dict', 'one', null, false); +SELECT * FROM test_table_record_as('dict', null, 2, false); +SELECT * FROM test_table_record_as('dict', 'three', 3, false); +SELECT * FROM test_table_record_as('dict', null, null, true); + +SELECT * FROM test_table_record_as('tuple', null, null, false); +SELECT * FROM test_table_record_as('tuple', 'one', null, false); +SELECT * FROM test_table_record_as('tuple', null, 2, false); +SELECT * FROM test_table_record_as('tuple', 'three', 3, false); +SELECT * FROM test_table_record_as('tuple', null, null, true); + +SELECT * FROM test_table_record_as('list', null, null, false); +SELECT * FROM test_table_record_as('list', 'one', null, false); +SELECT * FROM test_table_record_as('list', null, 2, false); +SELECT * FROM test_table_record_as('list', 'three', 3, false); +SELECT * FROM test_table_record_as('list', null, null, true); + +SELECT * FROM test_table_record_as('obj', null, null, false); +SELECT * FROM test_table_record_as('obj', 'one', null, false); +SELECT * FROM test_table_record_as('obj', null, 2, false); +SELECT * FROM test_table_record_as('obj', 'three', 3, false); +SELECT * FROM test_table_record_as('obj', null, null, true); + +SELECT * FROM test_type_record_as('dict', null, null, false); +SELECT * FROM test_type_record_as('dict', 'one', null, false); +SELECT * FROM test_type_record_as('dict', null, 2, false); +SELECT * FROM test_type_record_as('dict', 'three', 3, false); +SELECT * FROM test_type_record_as('dict', null, null, true); + +SELECT * FROM test_type_record_as('tuple', null, null, false); +SELECT * FROM test_type_record_as('tuple', 'one', null, false); +SELECT * FROM test_type_record_as('tuple', null, 2, false); +SELECT * FROM test_type_record_as('tuple', 'three', 3, false); +SELECT * FROM test_type_record_as('tuple', null, null, true); + +SELECT * FROM test_type_record_as('list', null, null, false); +SELECT * FROM test_type_record_as('list', 'one', null, false); +SELECT * FROM test_type_record_as('list', null, 2, false); +SELECT * FROM test_type_record_as('list', 'three', 3, false); +SELECT * FROM test_type_record_as('list', null, null, true); + +SELECT * FROM test_type_record_as('obj', null, null, false); +SELECT * FROM test_type_record_as('obj', 'one', null, false); +SELECT * FROM test_type_record_as('obj', null, 2, false); +SELECT * FROM test_type_record_as('obj', 'three', 3, false); +SELECT * FROM test_type_record_as('obj', null, null, true); + +SELECT * FROM test_type_record_as('str', 'one', 1, false); + +SELECT * FROM test_in_out_params('test_in'); +SELECT * FROM test_in_out_params_multi('test_in'); +SELECT * FROM test_inout_params('test_in'); + +-- try changing the return types and call functions again + +ALTER TABLE table_record ADD COLUMN tmp_col text; +ALTER TABLE table_record DROP COLUMN first; +ALTER TABLE table_record DROP COLUMN second; +ALTER TABLE table_record ADD COLUMN first text; +ALTER TABLE table_record ADD COLUMN second int4; +ALTER TABLE table_record DROP COLUMN tmp_col; + +SELECT * FROM test_table_record_as('obj', 'one', 1, false); + +ALTER TYPE type_record ADD ATTRIBUTE tmp_attr int; +ALTER TYPE type_record DROP ATTRIBUTE first; +ALTER TYPE type_record DROP ATTRIBUTE second; +ALTER TYPE type_record ADD ATTRIBUTE first text; +ALTER TYPE type_record ADD ATTRIBUTE second int4; +ALTER TYPE type_record DROP ATTRIBUTE tmp_attr; + +SELECT * FROM test_type_record_as('obj', 'one', 1, false); + +-- errors cases + +CREATE FUNCTION test_type_record_error1() RETURNS type_record AS $$ + return { 'first': 'first' } +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_record_error1(); + + +CREATE FUNCTION test_type_record_error2() RETURNS type_record AS $$ + return [ 'first' ] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_record_error2(); + + +CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$ + class type_record: pass + type_record.first = 'first' + return type_record +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_record_error3(); + +CREATE FUNCTION test_type_record_error4() RETURNS type_record AS $$ + return 'foo' +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_record_error4(); diff --git a/src/test/regress/sql/plpython3u/plpython_schema.sql b/src/test/regress/sql/plpython3u/plpython_schema.sql new file mode 100644 index 0000000000..a5bdbda2a3 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_schema.sql @@ -0,0 +1,39 @@ +CREATE TABLE users ( + fname text not null, + lname text not null, + username text, + userid serial, + PRIMARY KEY(lname, fname) + ) ; + +CREATE INDEX users_username_idx ON users(username); +CREATE INDEX users_fname_idx ON users(fname); +CREATE INDEX users_lname_idx ON users(lname); +CREATE INDEX users_userid_idx ON users(userid); + + +CREATE TABLE taxonomy ( + id serial primary key, + name text unique + ) ; + +CREATE TABLE entry ( + accession text not null primary key, + eid serial unique, + txid int2 not null references taxonomy(id) + ) ; + +CREATE TABLE sequences ( + eid int4 not null references entry(eid), + pid serial primary key, + product text not null, + sequence text not null, + multipart bool default 'false' + ) ; +CREATE INDEX sequences_product_idx ON sequences(product) ; + +CREATE TABLE xsequences ( + pid int4 not null references sequences(pid), + sequence text not null + ) ; +CREATE INDEX xsequences_pid_idx ON xsequences(pid) ; diff --git a/src/test/regress/sql/plpython3u/plpython_setof.sql b/src/test/regress/sql/plpython3u/plpython_setof.sql new file mode 100644 index 0000000000..e6f93f60e6 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_setof.sql @@ -0,0 +1,63 @@ +-- +-- Test returning SETOF +-- + +CREATE FUNCTION test_setof_error() RETURNS SETOF text AS $$ +return 37 +$$ LANGUAGE plpython3u; + +SELECT test_setof_error(); + + +CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$ +return [ content ]*count +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$ +t = () +for i in range(count): + t += ( content, ) +return t +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$ +class producer: + def __init__ (self, icount, icontent): + self.icontent = icontent + self.icount = icount + def __iter__ (self): + return self + def __next__ (self): + if self.icount == 0: + raise StopIteration + self.icount -= 1 + return self.icontent +return producer(count, content) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_setof_spi_in_iterator() RETURNS SETOF text AS +$$ + for s in ('Hello', 'Brave', 'New', 'World'): + yield s +$$ +LANGUAGE plpython3u; + + +-- Test set returning functions +SELECT test_setof_as_list(0, 'list'); +SELECT test_setof_as_list(1, 'list'); +SELECT test_setof_as_list(2, 'list'); +SELECT test_setof_as_list(2, null); + +SELECT test_setof_as_tuple(0, 'tuple'); +SELECT test_setof_as_tuple(1, 'tuple'); +SELECT test_setof_as_tuple(2, 'tuple'); +SELECT test_setof_as_tuple(2, null); + +SELECT test_setof_as_iterator(0, 'list'); +SELECT test_setof_as_iterator(1, 'list'); +SELECT test_setof_as_iterator(2, 'list'); +SELECT test_setof_as_iterator(2, null); + +SELECT test_setof_spi_in_iterator(); + diff --git a/src/test/regress/sql/plpython3u/plpython_spi.sql b/src/test/regress/sql/plpython3u/plpython_spi.sql new file mode 100644 index 0000000000..93bb38efe1 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_spi.sql @@ -0,0 +1,317 @@ +-- +-- nested calls +-- + +CREATE FUNCTION nested_call_one(a text) RETURNS text + AS +'q = "SELECT nested_call_two(''%s'')" % a +r = plpy.execute(q) +return r[0]' + LANGUAGE plpython3u ; + +CREATE FUNCTION nested_call_two(a text) RETURNS text + AS +'q = "SELECT nested_call_three(''%s'')" % a +r = plpy.execute(q) +return r[0]' + LANGUAGE plpython3u ; + +CREATE FUNCTION nested_call_three(a text) RETURNS text + AS +'return a' + LANGUAGE plpython3u ; + +-- some spi stuff + +CREATE FUNCTION spi_prepared_plan_test_one(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT count(*) FROM users WHERE lname = $1" + SD["myplan"] = plpy.prepare(q, [ "text" ]) +try: + rv = plpy.execute(SD["myplan"], [a]) + return "there are " + str(rv[0]["count"]) + " " + str(a) + "s" +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; + +CREATE FUNCTION spi_prepared_plan_test_two(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT count(*) FROM users WHERE lname = $1" + SD["myplan"] = plpy.prepare(q, [ "text" ]) +try: + rv = SD["myplan"].execute([a]) + return "there are " + str(rv[0]["count"]) + " " + str(a) + "s" +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; + +CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a + SD["myplan"] = plpy.prepare(q) +try: + rv = plpy.execute(SD["myplan"]) + if len(rv): + return rv[0]["count"] +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; + +CREATE FUNCTION join_sequences(s sequences) RETURNS text + AS +'if not s["multipart"]: + return s["sequence"] +q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"] +rv = plpy.execute(q) +seq = s["sequence"] +for r in rv: + seq = seq + r["sequence"] +return seq +' + LANGUAGE plpython3u; +-- +-- spi and nested calls +-- +select nested_call_one('pass this along'); +select spi_prepared_plan_test_one('doe'); +select spi_prepared_plan_test_two('smith'); +select spi_prepared_plan_test_nested('smith'); + +SELECT join_sequences(sequences) FROM sequences; +SELECT join_sequences(sequences) FROM sequences + WHERE join_sequences(sequences) ~* '^A'; +SELECT join_sequences(sequences) FROM sequences + WHERE join_sequences(sequences) ~* '^B'; +-- +-- plan and result objects +-- + +CREATE FUNCTION result_metadata_test(cmd text) RETURNS int +AS $$ +plan = plpy.prepare(cmd) +plpy.info(plan.status()) # not really documented or useful +result = plpy.execute(plan) +if result.status() > 0: + plpy.info(result.colnames()) + plpy.info(result.coltypes()) + plpy.info(result.coltypmods()) + return result.nrows() +else: + return None +$$ LANGUAGE plpython3u; + +SELECT result_metadata_test($$SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'$$); +SELECT result_metadata_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$); + +CREATE FUNCTION result_nrows_test(cmd text) RETURNS int +AS $$ +result = plpy.execute(cmd) +return result.nrows() +$$ LANGUAGE plpython3u; + +SELECT result_nrows_test($$SELECT 1$$); +SELECT result_nrows_test($$CREATE TEMPORARY TABLE foo2 (a int, b text)$$); +SELECT result_nrows_test($$INSERT INTO foo2 VALUES (1, 'one'), (2, 'two')$$); +SELECT result_nrows_test($$UPDATE foo2 SET b = '' WHERE a = 2$$); + +CREATE FUNCTION result_len_test(cmd text) RETURNS int +AS $$ +result = plpy.execute(cmd) +return len(result) +$$ LANGUAGE plpython3u; + +SELECT result_len_test($$SELECT 1$$); +SELECT result_len_test($$CREATE TEMPORARY TABLE foo3 (a int, b text)$$); +SELECT result_len_test($$INSERT INTO foo3 VALUES (1, 'one'), (2, 'two')$$); +SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$); + +CREATE FUNCTION result_subscript_test() RETURNS void +AS $$ +result = plpy.execute("SELECT 1 AS c UNION ALL SELECT 2 " + "UNION ALL SELECT 3 UNION ALL SELECT 4") + +plpy.info(result[1]['c']) +plpy.info(result[-1]['c']) + +plpy.info([item['c'] for item in result[1:3]]) +plpy.info([item['c'] for item in result[::2]]) + +result[-1] = {'c': 1000} +result[:2] = [{'c': 10}, {'c': 100}] +plpy.info([item['c'] for item in result[:]]) + +# raises TypeError, but the message differs on Python 2.6, so silence it +try: + plpy.info(result['foo']) +except TypeError: + pass +else: + assert False, "TypeError not raised" + +$$ LANGUAGE plpython3u; + +SELECT result_subscript_test(); + +CREATE FUNCTION result_empty_test() RETURNS void +AS $$ +result = plpy.execute("select 1 where false") + +plpy.info(result[:]) + +$$ LANGUAGE plpython3u; + +SELECT result_empty_test(); + +CREATE FUNCTION result_str_test(cmd text) RETURNS text +AS $$ +plan = plpy.prepare(cmd) +result = plpy.execute(plan) +return str(result) +$$ LANGUAGE plpython3u; + +SELECT result_str_test($$SELECT 1 AS foo UNION SELECT 2$$); +SELECT result_str_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$); + +-- cursor objects + +CREATE FUNCTION simple_cursor_test() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +does = 0 +for row in res: + if row['lname'] == 'doe': + does += 1 +return does +$$ LANGUAGE plpython3u; + +CREATE FUNCTION double_cursor_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +res.close() +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_fetch() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +assert len(res.fetch(3)) == 3 +assert len(res.fetch(3)) == 1 +assert len(res.fetch(3)) == 0 +assert len(res.fetch(3)) == 0 +try: + # use next() or __next__(), the method name changed in + # http://www.python.org/dev/peps/pep-3114/ + try: + res.next() + except AttributeError: + res.__next__() +except StopIteration: + pass +else: + assert False, "StopIteration not raised" +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_mix_next_and_fetch() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users order by fname") +assert len(res.fetch(2)) == 2 + +item = None +try: + item = res.next() +except AttributeError: + item = res.__next__() +assert item['fname'] == 'rick' + +assert len(res.fetch(2)) == 1 +$$ LANGUAGE plpython3u; + +CREATE FUNCTION fetch_after_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +try: + res.fetch(1) +except ValueError: + pass +else: + assert False, "ValueError not raised" +$$ LANGUAGE plpython3u; + +CREATE FUNCTION next_after_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +try: + try: + res.next() + except AttributeError: + res.__next__() +except ValueError: + pass +else: + assert False, "ValueError not raised" +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_fetch_next_empty() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users where false") +assert len(res.fetch(1)) == 0 +try: + try: + res.next() + except AttributeError: + res.__next__() +except StopIteration: + pass +else: + assert False, "StopIteration not raised" +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_plan() RETURNS SETOF text AS $$ +plan = plpy.prepare( + "select fname, lname from users where fname like $1 || '%' order by fname", + ["text"]) +for row in plpy.cursor(plan, ["w"]): + yield row['fname'] +for row in plan.cursor(["j"]): + yield row['fname'] +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_plan_wrong_args() RETURNS SETOF text AS $$ +plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'", + ["text"]) +c = plpy.cursor(plan, ["a", "b"]) +$$ LANGUAGE plpython3u; + +CREATE TYPE test_composite_type AS ( + a1 int, + a2 varchar +); + +CREATE OR REPLACE FUNCTION plan_composite_args() RETURNS test_composite_type AS $$ +plan = plpy.prepare("select $1 as c1", ["test_composite_type"]) +res = plpy.execute(plan, [{"a1": 3, "a2": "label"}]) +return res[0]["c1"] +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION test_python_gil_pg_locks() RETURNS text +AS $$ +result = plpy.execute("select locktype,granted from pg_locks where locktype='plpytyhon_gil'") +return str(result[:]) +$$ LANGUAGE plpython3u; + +select test_python_gil_pg_locks(); + +SELECT simple_cursor_test(); +SELECT double_cursor_close(); +SELECT cursor_fetch(); +SELECT cursor_mix_next_and_fetch(); +SELECT fetch_after_close(); +SELECT next_after_close(); +SELECT cursor_fetch_next_empty(); +SELECT cursor_plan(); +SELECT cursor_plan_wrong_args(); +SELECT plan_composite_args(); diff --git a/src/test/regress/sql/plpython3u/plpython_subtransaction.sql b/src/test/regress/sql/plpython3u/plpython_subtransaction.sql new file mode 100644 index 0000000000..958866d44b --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_subtransaction.sql @@ -0,0 +1,296 @@ +-- +-- Test explicit subtransactions +-- + +-- Test table to see if transactions get properly rolled back + +CREATE TABLE subtransaction_tbl ( + i integer +); + +-- Explicit case for Python <2.6 + +CREATE FUNCTION subtransaction_test(what_error text = NULL) RETURNS text +AS $$ +import sys +subxact = plpy.subtransaction() +subxact.__enter__() +exc = True +try: + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + if what_error == "SPI": + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") + elif what_error == "Python": + plpy.attribute_error + except: + exc = False + subxact.__exit__(*sys.exc_info()) + raise +finally: + if exc: + subxact.__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +SELECT subtransaction_test(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; +SELECT subtransaction_test('SPI'); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; +SELECT subtransaction_test('Python'); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +-- Context manager case for Python >=2.6 + +CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS text +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + if what_error == "SPI": + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") + elif what_error == "Python": + plpy.attribute_error +$$ LANGUAGE plpython3u; + +SELECT subtransaction_ctx_test(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; +SELECT subtransaction_ctx_test('SPI'); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; +SELECT subtransaction_ctx_test('Python'); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +-- Nested subtransactions + +CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text +AS $$ +plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + try: + with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (3)") + plpy.execute("error") + except plpy.SPIError as e: + if not swallow: + raise + plpy.notice("Swallowed %r" % e) +return "ok" +$$ LANGUAGE plpython3u; + +SELECT subtransaction_nested_test(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +SELECT subtransaction_nested_test('t'); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +-- Nested subtransactions that recursively call code dealing with +-- subtransactions + +CREATE FUNCTION subtransaction_deeply_nested_test() RETURNS text +AS $$ +plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + plpy.execute("SELECT subtransaction_nested_test('t')") +return "ok" +$$ LANGUAGE plpython3u; + +SELECT subtransaction_deeply_nested_test(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +-- Error conditions from not opening/closing subtransactions + +CREATE FUNCTION subtransaction_exit_without_enter() RETURNS void +AS $$ +plpy.subtransaction().__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_enter_without_exit() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_exit_twice() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +plpy.subtransaction().__exit__(None, None, None) +plpy.subtransaction().__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_enter_twice() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +plpy.subtransaction().__enter__() +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_exit_same_subtransaction_twice() RETURNS void +AS $$ +s = plpy.subtransaction() +s.__enter__() +s.__exit__(None, None, None) +s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_enter_same_subtransaction_twice() RETURNS void +AS $$ +s = plpy.subtransaction() +s.__enter__() +s.__enter__() +s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +-- No warnings here, as the subtransaction gets indeed closed +CREATE FUNCTION subtransaction_enter_subtransaction_in_with() RETURNS void +AS $$ +with plpy.subtransaction() as s: + s.__enter__() +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_exit_subtransaction_in_with() RETURNS void +AS $$ +with plpy.subtransaction() as s: + s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +SELECT subtransaction_exit_without_enter(); +SELECT subtransaction_enter_without_exit(); +SELECT subtransaction_exit_twice(); +SELECT subtransaction_enter_twice(); +SELECT subtransaction_exit_same_subtransaction_twice(); +SELECT subtransaction_enter_same_subtransaction_twice(); +SELECT subtransaction_enter_subtransaction_in_with(); +SELECT subtransaction_exit_subtransaction_in_with(); + +-- Make sure we don't get a "current transaction is aborted" error +SELECT 1 as test; + +-- Mix explicit subtransactions and normal SPI calls + +CREATE FUNCTION subtransaction_mix_explicit_and_implicit() RETURNS void +AS $$ +p = plpy.prepare("INSERT INTO subtransaction_tbl VALUES ($1)", ["integer"]) +try: + with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute(p, [2]) + plpy.execute(p, ["wrong"]) +except plpy.SPIError: + plpy.warning("Caught a SPI error from an explicit subtransaction") + +try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute(p, [2]) + plpy.execute(p, ["wrong"]) +except plpy.SPIError: + plpy.warning("Caught a SPI error") +$$ LANGUAGE plpython3u; + +SELECT subtransaction_mix_explicit_and_implicit(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +-- Alternative method names for Python <2.6 + +CREATE FUNCTION subtransaction_alternative_names() RETURNS void +AS $$ +s = plpy.subtransaction() +s.enter() +s.exit(None, None, None) +$$ LANGUAGE plpython3u; + +SELECT subtransaction_alternative_names(); + +-- try/catch inside a subtransaction block + +CREATE FUNCTION try_catch_inside_subtransaction() RETURNS void +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('a')") + except plpy.SPIError: + plpy.notice("caught") +$$ LANGUAGE plpython3u; + +SELECT try_catch_inside_subtransaction(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +ALTER TABLE subtransaction_tbl ADD PRIMARY KEY (i); + +CREATE FUNCTION pk_violation_inside_subtransaction() RETURNS void +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + except plpy.SPIError: + plpy.notice("caught") +$$ LANGUAGE plpython3u; + +SELECT pk_violation_inside_subtransaction(); +SELECT * FROM subtransaction_tbl; + +DROP TABLE subtransaction_tbl; + +-- cursor/subtransactions interactions + +CREATE FUNCTION cursor_in_subxact() RETURNS int AS $$ +with plpy.subtransaction(): + cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)") + cur.fetch(10) +fetched = cur.fetch(10); +return int(fetched[5]["i"]) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_aborted_subxact() RETURNS int AS $$ +try: + with plpy.subtransaction(): + cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)") + cur.fetch(10); + plpy.execute("select no_such_function()") +except plpy.SPIError: + fetched = cur.fetch(10) + return int(fetched[5]["i"]) +return 0 # not reached +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_plan_aborted_subxact() RETURNS int AS $$ +try: + with plpy.subtransaction(): + plpy.execute('create temporary table tmp(i) ' + 'as select generate_series(1, 10)') + plan = plpy.prepare("select i from tmp") + cur = plpy.cursor(plan) + plpy.execute("select no_such_function()") +except plpy.SPIError: + fetched = cur.fetch(5) + return fetched[2]["i"] +return 0 # not reached +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_close_aborted_subxact() RETURNS boolean AS $$ +try: + with plpy.subtransaction(): + cur = plpy.cursor('select 1') + plpy.execute("select no_such_function()") +except plpy.SPIError: + cur.close() + return True +return False # not reached +$$ LANGUAGE plpython3u; + +SELECT cursor_in_subxact(); +SELECT cursor_aborted_subxact(); +SELECT cursor_plan_aborted_subxact(); +SELECT cursor_close_aborted_subxact(); diff --git a/src/test/regress/sql/plpython3u/plpython_test.sql b/src/test/regress/sql/plpython3u/plpython_test.sql new file mode 100644 index 0000000000..f0ac569b2f --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_test.sql @@ -0,0 +1,53 @@ +-- first some tests of basic functionality +CREATE EXTENSION plpython3u; + +-- really stupid function just to get the module loaded +CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u; + +select stupid(); + +-- check 2/3 versioning +CREATE FUNCTION stupidn() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u; + +select stupidn(); + +-- test multiple arguments +CREATE FUNCTION argument_test_one(u users, a1 text, a2 text) RETURNS text + AS +'keys = list(u.keys()) +keys.sort() +out = [] +for key in keys: + out.append("%s: %s" % (key, u[key])) +words = a1 + " " + a2 + " => {" + ", ".join(out) + "}" +return words' + LANGUAGE plpython3u; + +select argument_test_one(users, fname, lname) from users where lname = 'doe' order by 1; + + +-- check module contents +CREATE FUNCTION module_contents() RETURNS text AS +$$ +contents = list(filter(lambda x: not x.startswith("__"), dir(plpy))) +contents.sort() +return ", ".join(contents) +$$ LANGUAGE plpython3u; + +select module_contents(); + + +CREATE FUNCTION elog_test() RETURNS void +AS $$ +plpy.debug('debug') +plpy.log('log') +plpy.info('info') +plpy.info(37) +plpy.info() +plpy.info('info', 37, [1, 2, 3]) +plpy.notice('notice') +plpy.warning('warning') +plpy.error('error') +$$ LANGUAGE plpython3u; + +SELECT elog_test(); diff --git a/src/test/regress/sql/plpython3u/plpython_trigger.sql b/src/test/regress/sql/plpython3u/plpython_trigger.sql new file mode 100644 index 0000000000..e9550453a9 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_trigger.sql @@ -0,0 +1,408 @@ +-- these triggers are dedicated to HPHC of RI who +-- decided that my kid's name was william not willem, and +-- vigorously resisted all efforts at correction. they have +-- since gone bankrupt... + +CREATE FUNCTION users_insert() returns trigger + AS +'if TD["new"]["fname"] == None or TD["new"]["lname"] == None: + return "SKIP" +if TD["new"]["username"] == None: + TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"] + rv = "MODIFY" +else: + rv = None +if TD["new"]["fname"] == "william": + TD["new"]["fname"] = TD["args"][0] + rv = "MODIFY" +return rv' + LANGUAGE plpython3u; + + +CREATE FUNCTION users_update() returns trigger + AS +'if TD["event"] == "UPDATE": + if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]: + return "SKIP" +return None' + LANGUAGE plpython3u; + + +CREATE FUNCTION users_delete() RETURNS trigger + AS +'if TD["old"]["fname"] == TD["args"][0]: + return "SKIP" +return None' + LANGUAGE plpython3u; + + +CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW + EXECUTE PROCEDURE users_insert ('willem'); + +CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW + EXECUTE PROCEDURE users_update ('willem'); + +CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW + EXECUTE PROCEDURE users_delete ('willem'); + + +-- quick peek at the table +-- +SELECT * FROM users; + +-- should fail +-- +UPDATE users SET fname = 'william' WHERE fname = 'willem'; + +-- should modify william to willem and create username +-- +INSERT INTO users (fname, lname) VALUES ('william', 'smith'); +INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle'); + +SELECT * FROM users; + + +-- dump trigger data + +CREATE TABLE trigger_test + (i int, v text ); + +CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpython3u AS $$ + +if 'relid' in TD: + TD['relid'] = "bogus:12345" + +skeys = list(TD.keys()) +skeys.sort() +for key in skeys: + val = TD[key] + if not isinstance(val, dict): + plpy.notice("TD[" + key + "] => " + str(val)) + else: + # print dicts the hard way because otherwise the order is implementation-dependent + valkeys = list(val.keys()) + valkeys.sort() + plpy.notice("TD[" + key + "] => " + '{' + ', '.join([repr(k) + ': ' + repr(val[k]) for k in valkeys]) + '}') + +return None + +$$; + +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER show_trigger_data_trig_stmt +BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +insert into trigger_test values(1,'insert'); +update trigger_test set v = 'update' where i = 1; +delete from trigger_test; +truncate table trigger_test; + +DROP TRIGGER show_trigger_data_trig_stmt on trigger_test; +DROP TRIGGER show_trigger_data_trig_before on trigger_test; +DROP TRIGGER show_trigger_data_trig_after on trigger_test; + +insert into trigger_test values(1,'insert'); +CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; + +CREATE TRIGGER show_trigger_data_trig +INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view +FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view'); + +insert into trigger_test_view values(2,'insert'); +update trigger_test_view set v = 'update' where i = 1; +delete from trigger_test_view; + +DROP FUNCTION trigger_data() CASCADE; +DROP VIEW trigger_test_view; +delete from trigger_test; + + +-- +-- trigger error handling +-- + +INSERT INTO trigger_test VALUES (0, 'zero'); + + +-- returning non-string from trigger function + +CREATE FUNCTION stupid1() RETURNS trigger +AS $$ + return 37 +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger1 +BEFORE INSERT ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid1(); + +INSERT INTO trigger_test VALUES (1, 'one'); + +DROP TRIGGER stupid_trigger1 ON trigger_test; + + +-- returning MODIFY from DELETE trigger + +CREATE FUNCTION stupid2() RETURNS trigger +AS $$ + return "MODIFY" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger2 +BEFORE DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid2(); + +DELETE FROM trigger_test WHERE i = 0; + +DROP TRIGGER stupid_trigger2 ON trigger_test; + +INSERT INTO trigger_test VALUES (0, 'zero'); + + +-- returning unrecognized string from trigger function + +CREATE FUNCTION stupid3() RETURNS trigger +AS $$ + return "foo" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger3 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid3(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger3 ON trigger_test; + + +-- Unicode variant + +CREATE FUNCTION stupid3u() RETURNS trigger +AS $$ + return "foo" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger3 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid3u(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger3 ON trigger_test; + + +-- deleting the TD dictionary + +CREATE FUNCTION stupid4() RETURNS trigger +AS $$ + del TD["new"] + return "MODIFY"; +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger4 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid4(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger4 ON trigger_test; + + +-- TD not a dictionary + +CREATE FUNCTION stupid5() RETURNS trigger +AS $$ + TD["new"] = ['foo', 'bar'] + return "MODIFY"; +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger5 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid5(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger5 ON trigger_test; + + +-- TD not having string keys + +CREATE FUNCTION stupid6() RETURNS trigger +AS $$ + TD["new"] = {1: 'foo', 2: 'bar'} + return "MODIFY"; +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger6 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid6(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger6 ON trigger_test; + + +-- TD keys not corresponding to row columns + +CREATE FUNCTION stupid7() RETURNS trigger +AS $$ + TD["new"] = {'v': 'foo', 'a': 'bar'} + return "MODIFY"; +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger7 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid7(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger7 ON trigger_test; + + +-- Unicode variant + +CREATE FUNCTION stupid7u() RETURNS trigger +AS $$ + TD["new"] = {'v': 'foo', 'a': 'bar'} + return "MODIFY" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger7 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid7u(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger7 ON trigger_test; + + +-- calling a trigger function directly + +SELECT stupid7(); + + +-- +-- Null values +-- + +SELECT * FROM trigger_test; + +CREATE FUNCTION test_null() RETURNS trigger +AS $$ + TD["new"]['v'] = None + return "MODIFY" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER test_null_trigger +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE test_null(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER test_null_trigger ON trigger_test; + +SELECT * FROM trigger_test; + + +-- +-- Test that triggers honor typmod when assigning to tuple fields, +-- as per an early 9.0 bug report +-- + +SET DateStyle = 'ISO'; + +CREATE FUNCTION set_modif_time() RETURNS trigger AS $$ + TD['new']['modif_time'] = '2010-10-13 21:57:28.930486' + return 'MODIFY' +$$ LANGUAGE plpython3u; + +CREATE TABLE pb (a TEXT, modif_time TIMESTAMP(0) WITHOUT TIME ZONE); + +CREATE TRIGGER set_modif_time BEFORE UPDATE ON pb + FOR EACH ROW EXECUTE PROCEDURE set_modif_time(); + +INSERT INTO pb VALUES ('a', '2010-10-09 21:57:33.930486'); +SELECT * FROM pb; +UPDATE pb SET a = 'b'; +SELECT * FROM pb; + + +-- triggers for tables with composite types + +CREATE TABLE comp1 (i integer, j boolean); +CREATE TYPE comp2 AS (k integer, l boolean); + +CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2); + +CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$ + TD['new']['f1'] = (3, False) + TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10} + return 'MODIFY' +$$ LANGUAGE plpython3u; + +CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f(); + +INSERT INTO composite_trigger_test VALUES (NULL, NULL); +SELECT * FROM composite_trigger_test; + + +-- triggers with composite type columns (bug #6559) + +CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2); + +CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpython3u; + +CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f(); + +INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f')); +SELECT * FROM composite_trigger_noop_test; + + +-- nested composite types + +CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer); + +CREATE TABLE composite_trigger_nested_test(c comp3); + +CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpython3u; + +CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f(); + +INSERT INTO composite_trigger_nested_test VALUES (NULL); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3)); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL)); +SELECT * FROM composite_trigger_nested_test; + +-- check that using a function as a trigger over two tables works correctly +CREATE FUNCTION trig1234() RETURNS trigger LANGUAGE plpython3u AS $$ + TD["new"]["data"] = '1234' + return 'MODIFY' +$$; + +CREATE TABLE a(data text); +CREATE TABLE b(data int); -- different type conversion + +CREATE TRIGGER a_t BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE trig1234(); +CREATE TRIGGER b_t BEFORE INSERT ON b FOR EACH ROW EXECUTE PROCEDURE trig1234(); + +INSERT INTO a DEFAULT VALUES; +SELECT * FROM a; +DROP TABLE a; +INSERT INTO b DEFAULT VALUES; +SELECT * FROM b; \ No newline at end of file diff --git a/src/test/regress/sql/plpython3u/plpython_types.sql b/src/test/regress/sql/plpython3u/plpython_types.sql new file mode 100644 index 0000000000..b868325bc7 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_types.sql @@ -0,0 +1,322 @@ +-- +-- Test data type behavior +-- + +-- +-- Base/common types +-- + +CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_bool(true); +SELECT * FROM test_type_conversion_bool(false); +SELECT * FROM test_type_conversion_bool(null); + + +-- test various other ways to express Booleans in Python +CREATE FUNCTION test_type_conversion_bool_other(n int) RETURNS bool AS $$ +# numbers +if n == 0: + ret = 0 +elif n == 1: + ret = 5 +# strings +elif n == 2: + ret = '' +elif n == 3: + ret = 'fa' # true in Python, false in PostgreSQL +# containers +elif n == 4: + ret = [] +elif n == 5: + ret = [0] +plpy.info(ret, not not ret) +return ret +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_bool_other(0); +SELECT * FROM test_type_conversion_bool_other(1); +SELECT * FROM test_type_conversion_bool_other(2); +SELECT * FROM test_type_conversion_bool_other(3); +SELECT * FROM test_type_conversion_bool_other(4); +SELECT * FROM test_type_conversion_bool_other(5); + + +CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_char('a'); +SELECT * FROM test_type_conversion_char(null); + + +CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_int2(100::int2); +SELECT * FROM test_type_conversion_int2(-100::int2); +SELECT * FROM test_type_conversion_int2(null); + + +CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_int4(100); +SELECT * FROM test_type_conversion_int4(-100); +SELECT * FROM test_type_conversion_int4(null); + + +CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_int8(100); +SELECT * FROM test_type_conversion_int8(-100); +SELECT * FROM test_type_conversion_int8(5000000000); +SELECT * FROM test_type_conversion_int8(null); + + +CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +/* The current implementation converts numeric to float. */ +SELECT * FROM test_type_conversion_numeric(100); +SELECT * FROM test_type_conversion_numeric(-100); +SELECT * FROM test_type_conversion_numeric(5000000000.5); +SELECT * FROM test_type_conversion_numeric(null); + + +CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_float4(100); +SELECT * FROM test_type_conversion_float4(-100); +SELECT * FROM test_type_conversion_float4(5000.5); +SELECT * FROM test_type_conversion_float4(null); + + +CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_float8(100); +SELECT * FROM test_type_conversion_float8(-100); +SELECT * FROM test_type_conversion_float8(5000000000.5); +SELECT * FROM test_type_conversion_float8(null); + + +CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_text('hello world'); +SELECT * FROM test_type_conversion_text(null); + + +CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_bytea('hello world'); +SELECT * FROM test_type_conversion_bytea(E'null\\000byte'); +SELECT * FROM test_type_conversion_bytea(null); + + +CREATE FUNCTION test_type_marshal() RETURNS bytea AS $$ +import marshal +return marshal.dumps('hello world') +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_type_unmarshal(x bytea) RETURNS text AS $$ +import marshal +try: + return marshal.loads(x) +except ValueError as e: + return 'FAILED: ' + str(e) +$$ LANGUAGE plpython3u; + +SELECT test_type_unmarshal(x) FROM test_type_marshal() x; + + +-- +-- Domains +-- + +CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL); + +CREATE FUNCTION test_type_conversion_booltrue(x booltrue, y bool) RETURNS booltrue AS $$ +return y +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_booltrue(true, true); +SELECT * FROM test_type_conversion_booltrue(false, true); +SELECT * FROM test_type_conversion_booltrue(true, false); + + +CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); + +CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ +plpy.info(x, type(x)) +return y +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_uint2(100::uint2, 50); +SELECT * FROM test_type_conversion_uint2(100::uint2, -50); +SELECT * FROM test_type_conversion_uint2(null, 1); + + +CREATE DOMAIN nnint AS int CHECK (VALUE IS NOT NULL); + +CREATE FUNCTION test_type_conversion_nnint(x nnint, y int) RETURNS nnint AS $$ +return y +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_nnint(10, 20); +SELECT * FROM test_type_conversion_nnint(null, 20); +SELECT * FROM test_type_conversion_nnint(10, null); + + +CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); + +CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ +plpy.info(x, type(x)) +return y +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold'); +SELECT * FROM test_type_conversion_bytea10('hello world', 'hello wold'); +SELECT * FROM test_type_conversion_bytea10('hello word', 'hello world'); +SELECT * FROM test_type_conversion_bytea10(null, 'hello word'); +SELECT * FROM test_type_conversion_bytea10('hello word', null); + + +-- +-- Arrays +-- + +CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]); +SELECT * FROM test_type_conversion_array_int4(ARRAY[0,-100,55]); +SELECT * FROM test_type_conversion_array_int4(ARRAY[NULL,1]); +SELECT * FROM test_type_conversion_array_int4(ARRAY[]::integer[]); +SELECT * FROM test_type_conversion_array_int4(NULL); +SELECT * FROM test_type_conversion_array_int4(ARRAY[[1,2,3],[4,5,6]]); + + +CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']); + + +CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]); + + +CREATE FUNCTION test_type_conversion_array_mixed1() RETURNS text[] AS $$ +return [123, 'abc'] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_mixed1(); + + +CREATE FUNCTION test_type_conversion_array_mixed2() RETURNS int[] AS $$ +return [123, 'abc'] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_mixed2(); + + +CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ +return [None] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_record(); + + +CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$ +return 'abc' +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_string(); + +CREATE FUNCTION test_type_conversion_array_tuple() RETURNS text[] AS $$ +return ('abc', 'def') +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_tuple(); + +CREATE FUNCTION test_type_conversion_array_error() RETURNS int[] AS $$ +return 5 +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_error(); + + +--- +--- Composite types +--- + +CREATE TABLE employee ( + name text, + basesalary integer, + bonus integer +); + +INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10); + +CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$ +return e['basesalary'] + e['bonus'] +$$ LANGUAGE plpython3u; + +SELECT name, test_composite_table_input(employee.*) FROM employee; + +ALTER TABLE employee DROP bonus; + +SELECT name, test_composite_table_input(employee.*) FROM employee; + +ALTER TABLE employee ADD bonus integer; +UPDATE employee SET bonus = 10; + +SELECT name, test_composite_table_input(employee.*) FROM employee; + +CREATE TYPE named_pair AS ( + i integer, + j integer +); + +CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$ +return sum(p.values()) +$$ LANGUAGE plpython3u; + +SELECT test_composite_type_input(row(1, 2)); + +ALTER TYPE named_pair RENAME TO named_pair_2; + +SELECT test_composite_type_input(row(1, 2)); diff --git a/src/test/regress/sql/plpython3u/plpython_unicode.sql b/src/test/regress/sql/plpython3u/plpython_unicode.sql new file mode 100644 index 0000000000..9531ecf1e7 --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_unicode.sql @@ -0,0 +1,25 @@ +-- +-- Unicode handling +-- + +SET client_encoding TO UTF8; + +CREATE TABLE unicode_test ( + testvalue text NOT NULL +); + +CREATE FUNCTION unicode_return() RETURNS text AS E' +return "\\x80" +' LANGUAGE plpython3u; + +CREATE FUNCTION unicode_trigger() RETURNS trigger AS E' +TD["new"]["testvalue"] = "\\x80" +return "MODIFY" +' LANGUAGE plpython3u; + +CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test + FOR EACH ROW EXECUTE PROCEDURE unicode_trigger(); + +SELECT unicode_return(); +INSERT INTO unicode_test (testvalue) VALUES ('test'); +SELECT * FROM unicode_test; diff --git a/src/test/regress/sql/plpython3u/plpython_void.sql b/src/test/regress/sql/plpython3u/plpython_void.sql new file mode 100644 index 0000000000..5a1a6711fb --- /dev/null +++ b/src/test/regress/sql/plpython3u/plpython_void.sql @@ -0,0 +1,22 @@ +-- +-- Tests for functions that return void +-- + +CREATE FUNCTION test_void_func1() RETURNS void AS $$ +x = 10 +$$ LANGUAGE plpython3u; + +-- illegal: can't return non-None value in void-returning func +CREATE FUNCTION test_void_func2() RETURNS void AS $$ +return 10 +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_return_none() RETURNS int AS $$ +None +$$ LANGUAGE plpython3u; + + +-- Tests for functions returning void +SELECT test_void_func1(), test_void_func1() IS NULL AS "is null"; +SELECT test_void_func2(); -- should fail +SELECT test_return_none(), test_return_none() IS NULL AS "is null"; -- Gitee From 5ba58648f491e6a3011d4c479c51fbf61cb760c4 Mon Sep 17 00:00:00 2001 From: LittleGanyu Date: Thu, 17 Apr 2025 17:01:25 +0800 Subject: [PATCH 2/3] fix configure --- configure | 51 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/configure b/configure index daf5792cc0..aefc8ff0bc 100755 --- a/configure +++ b/configure @@ -6387,27 +6387,29 @@ $as_echo_n "checking whether to build Python modules... " >&6; } -# Check whether --with-python was given. -if test "${with_python+set}" = set; then - withval=$with_python; - case $withval in - yes) - : - ;; - no) - : - ;; - *) - { { $as_echo "$as_me:$LINENO: error: no argument expected for --with-python option" >&5 -$as_echo "$as_me: error: no argument expected for --with-python option" >&2;} - { (exit 1); exit 1; }; } - ;; - esac - -else - with_python=no - -fi +# # Check whether --with-python was given. +# if test "${with_python+set}" = set; then +# withval=$with_python; +# case $withval in +# yes) +# : +# ;; +# no) +# : +# ;; +# *) +# { { $as_echo "$as_me:$LINENO: error: no argument expected for --with-python option" >&5 +# $as_echo "$as_me: error: no argument expected for --with-python option" >&2;} +# { (exit 1); exit 1; }; } +# ;; +# esac + +# else +# with_python=no + +# fi + +with_python=yes if test "$with_gssapi" = yes; then cat >>confdefs.h <<\_ACEOF @@ -8376,6 +8378,13 @@ $as_echo "$as_me: WARNING: *** need to worry about this, because the Perl output is pre-generated.)" >&2;} fi +export GAUSS_PYTHON_HOME=${with_3rdpartydir}/kernel/platform/python3.7 +export PYTHONHOME=$GAUSS_PYTHON_HOME +export CPLUS_INCLUDE_PATH=$GAUSS_PYTHON_HOME/include/python3.7m:$CPLUS_INCLUDE_PATH +export LD_LIBRARY_PATH=$PLATFORM_PATH/python3.7/lib:$LD_LIBRARY_PATH + +PYTHON=$PYTHONHOME/bin/python3 + if test "$with_python" = yes; then # Extract the first word of "python", so it can be a program name with args. set dummy python; ac_word=$2 -- Gitee From 4b2cf3453e4f9c8433934027849ee3c534b75104 Mon Sep 17 00:00:00 2001 From: LittleGanyu Date: Thu, 17 Apr 2025 17:37:09 +0800 Subject: [PATCH 3/3] add debug info --- configure | 1 + 1 file changed, 1 insertion(+) diff --git a/configure b/configure index aefc8ff0bc..5e07600690 100755 --- a/configure +++ b/configure @@ -8435,6 +8435,7 @@ fi { $as_echo "$as_me:$LINENO: checking for Python distutils module" >&5 $as_echo_n "checking for Python distutils module... " >&6; } +$as_echo ${"${PYTHON}" -c 'import distutils'} if "${PYTHON}" -c 'import distutils' 2>&5 then { $as_echo "$as_me:$LINENO: result: yes" >&5 -- Gitee