diff --git a/build/script/utils/common.sh b/build/script/utils/common.sh
index 2d64317f05c29b961415b09473b9a42f80b4be87..932ee7a7df9c0d4437bebdd4a1face07317de1af 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 89547da238a25109c993c394afe585ffb509494c..f1e1842ae89ac32757ea9dbc255ec1c6dc42b555 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 02d3c947a90e2384a86a2cba4a6b922bc77be0c3..5e07600690fddfe2facad0429a49493ac084dd70 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
@@ -8426,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
@@ -8439,10 +8449,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 +8464,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 b628ab3ba5b888132430161a3a382150483013bf..47b087e7313f3401364027204d602b54f15f03f9 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 38e45df8650bbb983def0a94d360998aaa5f920f..bcb31b00bcaa75cd0bcca3442103eff7d1798cc8 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 5c0696258215e696b4b206f4c7f984739d8228a0..18c227149bd15c05612918a64b31d7a6416a16f9 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 0000000000000000000000000000000000000000..a6a7a6a4be8ae1bc2c9d529c54c74d196aa27c82
--- /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 2941d3e4c1f43d603f032814fa4cc6fa1bfbb7e2..4c5cf5ebb28296fdd956e5148a2f2682ad25010b 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 0000000000000000000000000000000000000000..6fff5bd172b60b48cfde1138ded08e1c4c9add4c
--- /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 fa64992f0b2443515ec32b80af1d001b1f1e8092..65f56eba350224148cd6f625383d541cb685565f 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 38d5bb5af5d2b9e4ffbed7150568d168b4497304..c53cdd5daab1ee8cc4b52c1ec005be4cdf8198b2 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 4e62f4528980d8d95ae201120ed342738bd40926..95f50cbc65d66f4f40c9513c3cf2d87650845582 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 f01ebaa89ec523f886662bf712f54bbbb79399bd..f5b81e7b30d485108b7ef8eaccb68e7d23b4be66 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 f59960c4a25dcc36cb3f83497082eac79b40e8d2..888223457bde258b9b3338a40b2a5206f7af43c4 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 276e4c473bd6162020e42c4f6e2b489e800e9c3d..56bda2a7f5d24d8b3769af3aa383b6e6f7afed74 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 20d6468a156c2b6127d634d31ab649a8317abadb..dad3f73bc10e8ca020634f09de5291018e748662 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 783d6339fd9fde67eae3959dea42ba2b2ef450f1..40e28ca2929712d925e427e2a871c662ceaee47b 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 65c36443c3bf53fdb6e10ad23397d1a77f578600..2fbb1e6bf3ccd2325ce3b5854cc4c05a228571e8 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 13ce37f1caba0b90ce84e5ed9ed2bc453d168f6b..aa6ce559891c96c9f407a343c787cfd380e656eb 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 a0fdb65881f72eb0cd82eb9ebff1d70d9064a2fe..2e5c5fa2e3149a2dc6a2a252e801fc6657644138 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 bb49d224767b15fdb46494e2fc8ad4b8a9a10ab6..31a4a2a509531eaf7fbfe1c02b0472f7e63dcd4e 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 c4be01acd804cc8744343f8a2c7ff7359699f663..dddf5227d34b2e164db858f1c162e5cfb2666e8e 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 99b9cc0783c08df9af1880eeae1beb0f3e8ca826..b94b5cf2cfdd5f6397c34c8261eab4a41d41778b 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 3cad0e7589e37214c4e0d44f96bde1d4811255bf..b452d69840054db1c03a15204524a97af3551997 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 b00450e1e102a0c4d1908b6c7a8ab3d2c3a4aafc..28ee9aaed68908aa1d8ad6967cdf178b351ddead 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 2b9067dcc682e37c25deb3a5282b0914d67e3ca6..e2ff5d5f95c1dde852de2a0de04a0e00304ee262 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 6093bb99d70c4a7aa46beba55282a9d257b80f9c..ecc7523b5215477d76473410b14715cce5e07064 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 eb205fd5ac54955b03b0ff9e9c66ccfebe3de5cd..3bd8d35db1a92680b4042b58db1b6bcbc58028b1 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 cd1fb636a00adbd2ca33fb4224ec579676c8fb16..f76356750475731703ead89b07d4ae473cc816e0 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 55bafc66f794fecb4f4d2550b6030fd1afe5cb25..58eb98ed43f4dd4ea87fb14d2970a3b2986a7189 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 15ff610fb4976b9bd32eb41ef528b620106ca636..5d1c7ad8709ae7b5b1fea7c37b2575f4106d9f4d 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 030ff32e56939a1c7e3337ad9122c8240779004f..b54726c5ecf6b979cf20be16547b6714f3eee5e6 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 7a37b96980918625eed6d2c728ba7f4cab834618..1148e5220e389f4eb1cbf43eba7c0197a05bd03d 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 5ce67f7673e5fa1c9ab1fc0e43de7dd4a43628c2..0bea2273814d5e62d6c57fe4c14fef10e2d31712 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 6ef6c71dcd2a2dcc6def86fef739805a2b36522c..bfb4f401fc917bb568f452767c3eccd6c20aedf0 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 9dd631d2ef6daf3e78c8b692a615ae03d162c92a..c6c08342cee5cc9ba35736d6aa7f54b746e684b1 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 0000000000000000000000000000000000000000..9e47bab66d7dccc94081dc3ece4d32ed8c525848
--- /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 a12c5c4ea3908f60071f78612e1e350e20999006..ac6336e67bedc5038f2302a74da6f3f87a389d81 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 679a91b0c8e5f28eeeec76671d1211f5a87a31c7..ce796386427b806762bc06dbff85410bad1c65dc 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 967e18e42735c682bcdccf008409b981295895eb..b18fbd9a3ca5caf375f02ddc525eb98c544541ac 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 d6568b6dd9547ea77e1580c0a3536ba1ae5ee093..53a7f383e1860603bf3e1a8ae227beb4b3be029f 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 7ec3bcf4f9d40574f91e1697ca683d96f4093e9f..59b81de9fb075673c3b0f5740c9a81590d205ed4 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 26df9f19eae9815c3dc0753df1667da13c112ceb..ccd2cf136a14c42ae3dfd6b7f7f5c37ddc789c31 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 0000000000000000000000000000000000000000..783f48985d66c7c7a3773160ab222608278b9fc1
--- /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 0000000000000000000000000000000000000000..a5a65724a5184e233fd0c7e5ddea8523fff68c67
--- /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 0000000000000000000000000000000000000000..04d77bb52f531790b4be0be5f67d23e4a7a84c89
--- /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 0000000000000000000000000000000000000000..f2a0e8315c1dc29cc21489385464f1a3cc7b49bb
--- /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 0000000000000000000000000000000000000000..b607a590894b6382c38641a48bf5f5af5711c5b6
--- /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 0000000000000000000000000000000000000000..a4cfb1483f9c3fb643e2c15f3fa55afb73b3bb26
--- /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 0000000000000000000000000000000000000000..d56be832184ab62733f806ed8dc5293f9a349dfa
--- /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 0000000000000000000000000000000000000000..32ec535a2725ca15e8a30aa37aeb4324930bb950
--- /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 0000000000000000000000000000000000000000..36064fb006eb4797b4f9ff5075ffb52da3920993
--- /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 0000000000000000000000000000000000000000..854e989eaf96b309380ef7428d66f62d2fea97bf
--- /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 0000000000000000000000000000000000000000..2bc149257e71535025cced8683557239b9c28212
--- /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 0000000000000000000000000000000000000000..d1a36f362391c26988a7e24e33a13f4a373d8cb2
--- /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 0000000000000000000000000000000000000000..4db75b074f88e665e67b0ca328bcea240b5db113
--- /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 0000000000000000000000000000000000000000..214437030d82e545810eb57081fe5a9c85f7c1d1
--- /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 0000000000000000000000000000000000000000..bfc116a014a61c60d21e611df9f16bc1581b8a7c
--- /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 0000000000000000000000000000000000000000..3ec331c0f0af393e569b64be1f4bfb3bd9774a76
--- /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 0000000000000000000000000000000000000000..9f6d8f2f29e721dcae0db02c33e9309bdd1d420a
--- /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 0000000000000000000000000000000000000000..419e1ceb4438742aeb4fd4e2877ce4c94e2fce6f
--- /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 0000000000000000000000000000000000000000..7239492c72fbdeacff2214cabbb5411bab7743cc
--- /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 0000000000000000000000000000000000000000..7e1146ffaa48716cb27c4998bd7172e49d0cc09f
--- /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 0000000000000000000000000000000000000000..5b37924293752c423f10145a2c3faa22282f4cf1
--- /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 0000000000000000000000000000000000000000..bad7eae2f1e48450f5af1c6cc078d3e6f8092f01
--- /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 0000000000000000000000000000000000000000..23fc9f8773c311a0a4929d352c3f307781ee755c
--- /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 0000000000000000000000000000000000000000..e31b686fee4785be76b0a41dcc1a182477f88312
--- /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 54db9d894a9decd245fecad407337e8434f6b089..35eb13130e6949eb8574914e8c1a2648b755fba3 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 0000000000000000000000000000000000000000..e17aff34a7f0557af6ba029d2e817c3518abdccc
--- /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 0000000000000000000000000000000000000000..171836518f92935d2e24feee6189019fc80bf3f9
--- /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 0000000000000000000000000000000000000000..2ccf0443e8aa383a43992b16a6d3825069de78d7
--- /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 0000000000000000000000000000000000000000..735ecf1f806d3554b5518e560f4acb1d5bd7a8dd
--- /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 0000000000000000000000000000000000000000..fc2e0cd598ddf56e025eeea936e2e90bac8c57a4
--- /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 0000000000000000000000000000000000000000..96d204928618ce8ed032e2954e3396318439498c
--- /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 0000000000000000000000000000000000000000..702cbdabd14f824650ff63a7800b3d7b4cf7b896
--- /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 0000000000000000000000000000000000000000..4e957eeaffb602f69e3c9d7c49e52838f48b0fca
--- /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 0000000000000000000000000000000000000000..d4b61e41dd1088013ee39c90fe5db8e2abe243c9
--- /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 0000000000000000000000000000000000000000..3031eef2e697caec0b910273c653fb3bf8529e30
--- /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 0000000000000000000000000000000000000000..cb22ba923f99ffe79385a3b89f659e89c0ed9b91
--- /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 0000000000000000000000000000000000000000..8bab4888592efdb4c96731a51b45fe3a83ca8fa3
--- /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 0000000000000000000000000000000000000000..cc1e19b5dd38b345b266e07e6b3d96c63d05474b
--- /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 0000000000000000000000000000000000000000..2170a0ae00c3ec6e23ece47431949a544fc8e808
--- /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 0000000000000000000000000000000000000000..a93e7a745f249f0b19d557b3a57319e52bfaa17b
--- /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 0000000000000000000000000000000000000000..a5bdbda2a329119ccd9aa1fa44d10f9cb2746712
--- /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 0000000000000000000000000000000000000000..e6f93f60e6f770c575db0780f560731894a1a670
--- /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 0000000000000000000000000000000000000000..93bb38efe1a88637806eb461eaffcafa4a6b2807
--- /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 0000000000000000000000000000000000000000..958866d44b29880797e6fbe768b73a184f6b6817
--- /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 0000000000000000000000000000000000000000..f0ac569b2f42f88d0db43ea1cfbfd9935e9e844f
--- /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 0000000000000000000000000000000000000000..e9550453a96bb947b7fb5998061a5e07ac2f7c89
--- /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 0000000000000000000000000000000000000000..b868325bc7844b692b1829b64bf6d33d8407181f
--- /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 0000000000000000000000000000000000000000..9531ecf1e769211cd41464f79b054ee191bd88a4
--- /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 0000000000000000000000000000000000000000..5a1a6711fb0d742928f0865355589a63d9a381dd
--- /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";