1378 lines
43 KiB
C++
1378 lines
43 KiB
C++
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
#include "fmgr.h"
|
|
#include "funcapi.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/int16.h"
|
|
#include "utils/int8.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/numeric.h"
|
|
#include "utils/numeric_gs.h"
|
|
#include "utils/portal.h"
|
|
#include "utils/xml.h"
|
|
#include "executor/spi.h"
|
|
#include "tcop/tcopprot.h"
|
|
#include "commands/extension.h"
|
|
#include <libxml/chvalid.h>
|
|
#include <libxml/parser.h>
|
|
#include <libxml/parserInternals.h>
|
|
#include <libxml/tree.h>
|
|
#include <libxml/uri.h>
|
|
#include <libxml/xmlerror.h>
|
|
#include <libxml/xmlversion.h>
|
|
#include <libxml/xmlwriter.h>
|
|
#include <libxml/xpath.h>
|
|
#include <libxml/xpathInternals.h>
|
|
#include "gms_xmlgen.h"
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
PG_FUNCTION_INFO_V1(ctxhandle_in);
|
|
PG_FUNCTION_INFO_V1(ctxhandle_out);
|
|
PG_FUNCTION_INFO_V1(close_context);
|
|
PG_FUNCTION_INFO_V1(convert_xml);
|
|
PG_FUNCTION_INFO_V1(convert_clob);
|
|
PG_FUNCTION_INFO_V1(get_num_rows_processed);
|
|
PG_FUNCTION_INFO_V1(get_xml_by_ctx_id);
|
|
PG_FUNCTION_INFO_V1(get_xml_by_query);
|
|
PG_FUNCTION_INFO_V1(new_context_by_query);
|
|
PG_FUNCTION_INFO_V1(new_context_by_cursor);
|
|
PG_FUNCTION_INFO_V1(new_context_from_hierarchy);
|
|
PG_FUNCTION_INFO_V1(restart_query);
|
|
PG_FUNCTION_INFO_V1(set_convert_special_chars);
|
|
PG_FUNCTION_INFO_V1(set_max_rows);
|
|
PG_FUNCTION_INFO_V1(set_null_handling);
|
|
PG_FUNCTION_INFO_V1(set_row_set_tag);
|
|
PG_FUNCTION_INFO_V1(set_row_tag);
|
|
PG_FUNCTION_INFO_V1(set_skip_rows);
|
|
PG_FUNCTION_INFO_V1(use_item_tags_for_coll);
|
|
|
|
static uint32 g_xmlgen_extension_id;
|
|
|
|
#define TOP_CONTEXT ((XMLGenTopContext *)(u_sess->attr.attr_common.extension_session_vars_array[g_xmlgen_extension_id]))
|
|
|
|
void set_extension_index(uint32 index)
|
|
{
|
|
g_xmlgen_extension_id = index;
|
|
}
|
|
|
|
void init_session_vars(void)
|
|
{
|
|
RepallocSessionVarsArrayIfNecessary();
|
|
|
|
XMLGenTopContext *g_xmlgen_mem_ctx =
|
|
(XMLGenTopContext *)MemoryContextAllocZero(u_sess->self_mem_cxt, sizeof(XMLGenTopContext));
|
|
|
|
g_xmlgen_mem_ctx->memctx = u_sess->self_mem_cxt;
|
|
g_xmlgen_mem_ctx->xmlgen_context_list = NIL;
|
|
g_xmlgen_mem_ctx->xmlgen_context_id = 0;
|
|
|
|
u_sess->attr.attr_common.extension_session_vars_array[g_xmlgen_extension_id] = g_xmlgen_mem_ctx;
|
|
}
|
|
|
|
/**
|
|
* get XMLGenContext by ctx_id
|
|
* @param {int64} ctx_id
|
|
* @return {XMLGenContext} ctx
|
|
*/
|
|
static XMLGenContext *get_xmlgen_context(int64 ctx_id)
|
|
{
|
|
if (TOP_CONTEXT->xmlgen_context_list == NIL) {
|
|
return NULL;
|
|
}
|
|
|
|
ListCell *lc = NULL;
|
|
foreach (lc, TOP_CONTEXT->xmlgen_context_list) {
|
|
XMLGenContext *ctx = (XMLGenContext *)lfirst(lc);
|
|
if (ctx->ctx_id == ctx_id) {
|
|
return ctx;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* free XMLGenContext
|
|
* @param {XMLGenContext*} ctx - XMLGenContext pointer
|
|
* @return {void}
|
|
*/
|
|
static void free_xmlgen_context(XMLGenContext *ctx)
|
|
{
|
|
if (ctx->memctx != NULL) {
|
|
MemoryContextDelete(ctx->memctx);
|
|
}
|
|
|
|
MemoryContext oldcontext = MemoryContextSwitchTo(TOP_CONTEXT->memctx);
|
|
|
|
TOP_CONTEXT->xmlgen_context_list = list_delete(TOP_CONTEXT->xmlgen_context_list, (void *)ctx);
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
MemoryContextReset(ctx->memctx);
|
|
pfree_ext(ctx);
|
|
}
|
|
|
|
/**
|
|
* unescape XML data
|
|
* @param {const char *} str - escaped string data
|
|
* @return {char *} result - unescaped string data
|
|
*/
|
|
static char *convert_decode(const char *str)
|
|
{
|
|
StringInfoData buf;
|
|
const char *p = NULL;
|
|
int charlen = 0;
|
|
|
|
initStringInfo(&buf);
|
|
p = str;
|
|
while (*p) {
|
|
if (*p == '&') {
|
|
if (strncmp(p, "<", 4) == 0) {
|
|
appendStringInfoString(&buf, "<");
|
|
charlen = 4;
|
|
} else if (strncmp(p, ">", 4) == 0) {
|
|
appendStringInfoString(&buf, ">");
|
|
charlen = 4;
|
|
} else if (strncmp(p, """, 6) == 0) {
|
|
appendStringInfoString(&buf, "\"");
|
|
charlen = 6;
|
|
} else if (strncmp(p, "'", 6) == 0) {
|
|
appendStringInfoString(&buf, "'");
|
|
charlen = 6;
|
|
} else if (strncmp(p, "&", 5) == 0) {
|
|
appendStringInfoString(&buf, "&");
|
|
charlen = 5;
|
|
} else {
|
|
appendStringInfoString(&buf, "&");
|
|
charlen = 1;
|
|
}
|
|
} else {
|
|
charlen = pg_mblen(p);
|
|
for (int i = 0; i < charlen; i++) {
|
|
appendStringInfoCharMacro(&buf, p[i]);
|
|
}
|
|
}
|
|
p += charlen;
|
|
}
|
|
return buf.data;
|
|
}
|
|
|
|
/**
|
|
* escape XML data
|
|
* @param {const char *} str - unescaped string data
|
|
* @return {char *} result - escaped string data
|
|
*/
|
|
static char *convert_encode(const char *str)
|
|
{
|
|
StringInfoData buf;
|
|
const char *p = NULL;
|
|
int charlen = 0;
|
|
|
|
initStringInfo(&buf);
|
|
p = str;
|
|
while (*p) {
|
|
charlen = pg_mblen(p);
|
|
switch (*p) {
|
|
case '&':
|
|
appendStringInfoString(&buf, "&");
|
|
break;
|
|
case '<':
|
|
appendStringInfoString(&buf, "<");
|
|
break;
|
|
case '>':
|
|
appendStringInfoString(&buf, ">");
|
|
break;
|
|
case '"':
|
|
appendStringInfoString(&buf, """);
|
|
break;
|
|
case '\'':
|
|
appendStringInfoString(&buf, "'");
|
|
break;
|
|
default: {
|
|
for (int i = 0; i < charlen; i++) {
|
|
appendStringInfoCharMacro(&buf, p[i]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
p += charlen;
|
|
}
|
|
return buf.data;
|
|
}
|
|
|
|
/**
|
|
* map date value to xml value
|
|
* @param {Datum} value - bytea datum value
|
|
* @return {char *} result - xml value
|
|
*/
|
|
static char *map_date_value_to_xml(Datum value)
|
|
{
|
|
DateADT date;
|
|
struct pg_tm tm;
|
|
char buf[MAXDATELEN + 1];
|
|
|
|
date = DatumGetDateADT(value);
|
|
if (DATE_NOT_FINITE(date)) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("date out of range"),
|
|
errdetail("XML does not support infinite date values.")));
|
|
}
|
|
j2date(date + POSTGRES_EPOCH_JDATE, &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
|
|
EncodeDateOnly(&tm, USE_XSD_DATES, buf);
|
|
|
|
return pstrdup(buf);
|
|
}
|
|
|
|
/**
|
|
* map timestamp value to xml value
|
|
* @param {Datum} value - bytea datum value
|
|
* @return {char *} result - xml value
|
|
*/
|
|
static char *map_timestamp_value_to_xml(Datum value, bool is_tz)
|
|
{
|
|
TimestampTz timestamp;
|
|
struct pg_tm tm;
|
|
int tz;
|
|
fsec_t fsec;
|
|
const char *tzn = NULL;
|
|
char buf[MAXDATELEN + 1];
|
|
|
|
timestamp = DatumGetTimestamp(value);
|
|
|
|
if (TIMESTAMP_NOT_FINITE(timestamp)) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"),
|
|
errdetail("XML does not support infinite timestamp values.")));
|
|
} else if (timestamp2tm(timestamp, is_tz ? &tz : NULL, &tm, &fsec, is_tz ? &tzn : NULL, NULL) == 0) {
|
|
EncodeDateTime(&tm, fsec, is_tz, is_tz ? tz : 0, is_tz ? tzn : NULL, USE_XSD_DATES, buf);
|
|
} else {
|
|
ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range")));
|
|
}
|
|
|
|
return pstrdup(buf);
|
|
}
|
|
|
|
/**
|
|
* map bytea value to xml value
|
|
* @param {Datum} value - bytea datum value
|
|
* @return {char *} result - xml value
|
|
*/
|
|
static char *map_bytea_value_to_xml(Datum value)
|
|
{
|
|
bytea *bstr = DatumGetByteaPP(value);
|
|
volatile xmlBufferPtr buf = NULL;
|
|
volatile xmlTextWriterPtr writer = NULL;
|
|
char *result = NULL;
|
|
|
|
PG_TRY();
|
|
{
|
|
buf = xmlBufferCreate();
|
|
if (buf == NULL) {
|
|
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("could not allocate xmlBuffer")));
|
|
}
|
|
writer = xmlNewTextWriterMemory(buf, 0);
|
|
if (writer == NULL) {
|
|
xmlBufferFree(buf);
|
|
ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("could not allocate xmlTextWriter")));
|
|
}
|
|
if (u_sess->attr.attr_common.xmlbinary == XMLBINARY_BASE64) {
|
|
xmlTextWriterWriteBase64(writer, VARDATA_ANY(bstr), 0, VARSIZE_ANY_EXHDR(bstr));
|
|
} else {
|
|
xmlTextWriterWriteBinHex(writer, VARDATA_ANY(bstr), 0, VARSIZE_ANY_EXHDR(bstr));
|
|
}
|
|
/* we MUST do this now to flush data out to the buffer */
|
|
xmlFreeTextWriter(writer);
|
|
writer = NULL;
|
|
|
|
result = pstrdup((const char *)xmlBufferContent(buf));
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
if (writer) {
|
|
xmlFreeTextWriter(writer);
|
|
}
|
|
if (buf) {
|
|
xmlBufferFree(buf);
|
|
}
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
|
|
xmlBufferFree(buf);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* map array value to xml value
|
|
* @param {Datum} value - array datum value
|
|
* @param {bool} xml_escape_strings - is convert special chars
|
|
* @param {char *} array_tag_suffix - array tag suffix
|
|
* @param {int} indent_level - indentation level
|
|
* @return {char *} result - xml value
|
|
*/
|
|
char *map_array_value_to_xml(Datum value, bool xml_escape_strings, char *array_tag_suffix, int indent_level)
|
|
{
|
|
ArrayType *array = NULL;
|
|
Oid elmtype;
|
|
int16 elmlen;
|
|
bool elmbyval = false;
|
|
char elmalign;
|
|
int num_elems;
|
|
Datum *elem_values = NULL;
|
|
bool *elem_nulls = NULL;
|
|
StringInfoData buf;
|
|
int i;
|
|
|
|
array = DatumGetArrayTypeP(value);
|
|
elmtype = ARR_ELEMTYPE(array);
|
|
get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
|
|
const char *type_name = get_typename(elmtype);
|
|
const char *item_suffix = array_tag_suffix == NULL ? "" : array_tag_suffix;
|
|
|
|
deconstruct_array(array, elmtype, elmlen, elmbyval, elmalign, &elem_values, &elem_nulls, &num_elems);
|
|
|
|
initStringInfo(&buf);
|
|
|
|
for (i = 0; i < num_elems; i++) {
|
|
if (elem_nulls[i])
|
|
continue;
|
|
appendStringInfoString(&buf, "\n");
|
|
appendStringInfoSpaces(&buf, (indent_level + 1) * SPACE_PER_INDENTATION);
|
|
appendStringInfo(&buf, "<%s%s>", type_name, item_suffix);
|
|
appendStringInfoString(&buf, map_sql_value_to_xml(elem_values[i], elmtype, xml_escape_strings, array_tag_suffix,
|
|
indent_level + 1));
|
|
appendStringInfo(&buf, "</%s%s>", type_name, item_suffix);
|
|
}
|
|
appendStringInfoString(&buf, "\n");
|
|
appendStringInfoSpaces(&buf, indent_level * SPACE_PER_INDENTATION);
|
|
pfree_ext(elem_values);
|
|
pfree_ext(elem_nulls);
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
/**
|
|
* map sql column value to xml value
|
|
* @param {Datum} value - column value
|
|
* @param {Oid} type - column type
|
|
* @param {bool} xml_escape_strings - is convert special chars
|
|
* @param {char *} array_tag_suffix - array tag suffix
|
|
* @param {int} indent_level - indentation level
|
|
* @return {char *} result - xml value
|
|
*/
|
|
static char *map_sql_value_to_xml(Datum value, Oid type, bool xml_escape_strings, char *array_tag_suffix,
|
|
int indent_level)
|
|
{
|
|
if (type_is_array_domain(type)) {
|
|
return map_array_value_to_xml(value, xml_escape_strings, array_tag_suffix, indent_level);
|
|
} else {
|
|
switch (type) {
|
|
case BOOLOID:
|
|
return (char *)(DatumGetBool(value) ? "true" : "false");
|
|
case DATEOID:
|
|
return map_date_value_to_xml(value);
|
|
case TIMESTAMPOID:
|
|
return map_timestamp_value_to_xml(value, false);
|
|
case TIMESTAMPTZOID:
|
|
return map_timestamp_value_to_xml(value, true);
|
|
case BYTEAOID:
|
|
return map_bytea_value_to_xml(value);
|
|
default: {
|
|
Oid typeOut;
|
|
bool isvarlena = false;
|
|
char *str = NULL;
|
|
|
|
getTypeOutputInfo(type, &typeOut, &isvarlena);
|
|
str = OidOutputFunctionCall(typeOut, value);
|
|
|
|
if (type == XMLOID || !xml_escape_strings) {
|
|
return str;
|
|
}
|
|
|
|
return convert_encode(str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* convert datum to int
|
|
* @param {Datum} datum
|
|
* @return {Oid} type - datum type
|
|
*/
|
|
static int convert_datum_to_int(Datum datum, Oid type)
|
|
{
|
|
Oid type_out;
|
|
bool is_varlena = false;
|
|
getTypeOutputInfo(type, &type_out, &is_varlena);
|
|
char *str = OidOutputFunctionCall(type_out, datum);
|
|
int result = 0;
|
|
return sscanf_s(str, "%d", &result) == 1 ? result : -1;
|
|
}
|
|
|
|
/**
|
|
* add node to hierarchy xml
|
|
* @param {xmlNodePtr} pre_node
|
|
* @param {xmlNodePtr} cur_node
|
|
* @param {int} pre_level
|
|
* @param {int} cur_level
|
|
* @return {xmlNodePtr} return cur_node, or NULL otherwise
|
|
*/
|
|
static xmlNodePtr add_node_to_hierarchy_xml(xmlNodePtr pre_node, xmlNodePtr cur_node, int pre_level, int cur_level)
|
|
{
|
|
if (pre_node == NULL) {
|
|
return NULL;
|
|
}
|
|
xmlNodePtr pre_node_inner_pointer = pre_node;
|
|
int pre_level_inner = pre_level;
|
|
if (cur_level > pre_level_inner + 1) {
|
|
return NULL;
|
|
}
|
|
int64 i = MAX_DEPTH;
|
|
while (i > 0) {
|
|
if (cur_level == pre_level_inner + 1) {
|
|
xmlAddChild(pre_node_inner_pointer, cur_node);
|
|
return cur_node;
|
|
}
|
|
if (pre_node_inner_pointer->parent == NULL) {
|
|
return NULL;
|
|
}
|
|
pre_node_inner_pointer = pre_node_inner_pointer->parent;
|
|
pre_level_inner -= 1;
|
|
i--;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* convert hierarchy sql row to xml element
|
|
* @param {int64} count_rows
|
|
* @return {xmlDocPtr} result
|
|
*/
|
|
static xmlDocPtr SPI_hierarchy_sql_row_to_xml_element(int64 count_rows)
|
|
{
|
|
xmlDocPtr result = xmlNewDoc(BAD_CAST "1.0");
|
|
bool is_first_node = true;
|
|
bool is_null = false;
|
|
int pre_level = -1;
|
|
xmlNodePtr pre_node = NULL;
|
|
int cur_level = -1;
|
|
xmlNodePtr cur_node = NULL;
|
|
Datum level_datum = 0;
|
|
|
|
for (int64 i = 0; i < count_rows; i++) {
|
|
level_datum = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &is_null);
|
|
if (is_null) {
|
|
if (result != NULL) {
|
|
xmlFreeDoc(result);
|
|
}
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("invalid hierarchy query result")));
|
|
}
|
|
cur_level = convert_datum_to_int(level_datum, SPI_gettypeid(SPI_tuptable->tupdesc, 1));
|
|
if (cur_level < 0) {
|
|
if (result != NULL) {
|
|
xmlFreeDoc(result);
|
|
}
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("invalid hierarchy query level")));
|
|
}
|
|
|
|
Datum xml_node = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &is_null);
|
|
if (is_null) {
|
|
char *xml_name = map_sql_identifier_to_xml_name(SPI_fname(SPI_tuptable->tupdesc, 2), true, false);
|
|
cur_node = xmlNewNode(0, BAD_CAST xml_name);
|
|
} else {
|
|
char *xml_value = map_sql_value_to_xml(xml_node, SPI_gettypeid(SPI_tuptable->tupdesc, 2), false);
|
|
xmlDocPtr doc_from_xml_value = xmlParseMemory(xml_value, (int)(strlen(xml_value)));
|
|
cur_node = xmlCopyNode(xmlDocGetRootElement(doc_from_xml_value), 1);
|
|
xmlFreeDoc(doc_from_xml_value);
|
|
}
|
|
if (is_first_node) {
|
|
xmlNodePtr old_node = xmlDocSetRootElement(result, cur_node);
|
|
xmlFreeNode(old_node);
|
|
is_first_node = false;
|
|
} else {
|
|
xmlNodePtr cur_node_tmp = add_node_to_hierarchy_xml(pre_node, cur_node, pre_level, cur_level);
|
|
if (cur_node_tmp == NULL) {
|
|
xmlFreeNode(cur_node);
|
|
xmlFreeDoc(result);
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("invalid hierarchy query node")));
|
|
}
|
|
cur_node = cur_node_tmp;
|
|
}
|
|
pre_node = cur_node;
|
|
pre_level = cur_level;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* convert hierarchy sql row to string
|
|
* @param {int64} count_rows
|
|
* @param {StringInfo} result
|
|
* @param {char *} row_set_tag - row-set tag name
|
|
* @return {void}
|
|
*/
|
|
static void SPI_hierarchy_sql_row_to_string(int64 count_rows, StringInfo result, char *row_set_tag)
|
|
{
|
|
xmlDocPtr xml_doc = SPI_hierarchy_sql_row_to_xml_element(count_rows);
|
|
if (row_set_tag != NULL) {
|
|
xmlNodePtr cur_node = xmlNewNode(0, BAD_CAST row_set_tag);
|
|
xmlNodePtr old_node = xmlDocSetRootElement(xml_doc, cur_node);
|
|
xmlAddChild(cur_node, old_node);
|
|
}
|
|
xmlChar *out_buf = NULL;
|
|
int out_len = 0;
|
|
xmlDocDumpFormatMemoryEnc(xml_doc, &out_buf, &out_len, "utf-8", 1);
|
|
appendStringInfo(result, "%s", out_buf);
|
|
xmlFree(out_buf);
|
|
xmlFreeDoc(xml_doc);
|
|
}
|
|
|
|
/**
|
|
* handle query to xml hierarchy
|
|
* @param {XMLGenContext *} ctx
|
|
* @return {StringInfo} result
|
|
*/
|
|
static StringInfo query_to_xml_hierarchy(XMLGenContext *ctx)
|
|
{
|
|
StringInfo result = makeStringInfo();
|
|
const char *query = (const char *)ctx->query_string;
|
|
if (SPI_connect() != SPI_OK_CONNECT) {
|
|
SPI_finish();
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE), errmsg("SPI_connect failed")));
|
|
}
|
|
|
|
if (SPI_execute(query, true, 0) != SPI_OK_SELECT) {
|
|
SPI_finish();
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("fail to execute query")));
|
|
}
|
|
|
|
if (SPI_tuptable->tupdesc->natts != 2 ||
|
|
(SPI_gettypeid(SPI_tuptable->tupdesc, 1) != NUMERICOID && SPI_gettypeid(SPI_tuptable->tupdesc, 1) != INT4OID) ||
|
|
(SPI_gettypeid(SPI_tuptable->tupdesc, 2) != XMLOID &&
|
|
strcasecmp(SPI_gettype(SPI_tuptable->tupdesc, 2), XMLTYPE_STR) != 0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("invalid query result")));
|
|
}
|
|
|
|
int64 count_rows = (int64)SPI_processed;
|
|
SPI_hierarchy_sql_row_to_string(count_rows, result,
|
|
ctx->is_hierarchy_set_row_set_tag ? ctx->row_set_tag_name : NULL);
|
|
ctx->processed_rows = count_rows;
|
|
SPI_finish();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* generate xml doc root element start
|
|
* @param {StringInfo} result
|
|
* @param {const char*} eltname - root element name
|
|
* @param {int64} null_flag
|
|
* @return {void}
|
|
*/
|
|
static void xml_root_element_start(StringInfo result, const char *eltname, int64 null_flag)
|
|
{
|
|
if (eltname == NULL) {
|
|
return;
|
|
}
|
|
appendStringInfo(result, XML_HEADER);
|
|
appendStringInfo(result, "<%s", eltname);
|
|
if (null_flag == NULL_FLAG_NULL_ATTR) {
|
|
appendStringInfo(result, " %s", XML_XSI_ATTR);
|
|
}
|
|
|
|
appendStringInfo(result, ">\n");
|
|
}
|
|
|
|
/**
|
|
* generate xml doc root element end
|
|
* @param {StringInfo} result
|
|
* @param {const char*} eltname - root element name
|
|
* @return {void}
|
|
*/
|
|
static void xml_root_element_end(StringInfo result, const char *eltname)
|
|
{
|
|
if (eltname == NULL) {
|
|
return;
|
|
}
|
|
appendStringInfo(result, "</%s>\n", eltname);
|
|
}
|
|
|
|
/**
|
|
* convert sql row to xml elemeny by SPI
|
|
* @param {int64} rownum - convert data from this rownum
|
|
* @param {StringInfo} result
|
|
* @param {int64} null_flag - how to display null element
|
|
* @param {char *} row_tag - row tag name
|
|
* @param {bool} is_convert_special_chars - is convert special chars
|
|
* @param {char *} item_tag - tag suffix for array item
|
|
* @param {int64} indentation_level - indentations before data
|
|
* @return {void}
|
|
*/
|
|
static void SPI_sql_row_to_xml_element(int64 rownum, StringInfo result, int64 null_flag, char *row_tag,
|
|
bool is_convert_special_chars, char *item_tag, int64 indentation_level)
|
|
{
|
|
if (row_tag != NULL) {
|
|
if (result->len == 0) {
|
|
appendStringInfo(result, XML_HEADER);
|
|
appendStringInfo(result, "<%s", row_tag);
|
|
if (null_flag == NULL_FLAG_NULL_ATTR) {
|
|
appendStringInfo(result, " %s", XML_XSI_ATTR);
|
|
}
|
|
appendStringInfo(result, ">\n");
|
|
} else {
|
|
appendStringInfoSpaces(result, indentation_level * SPACE_PER_INDENTATION);
|
|
appendStringInfo(result, "<%s>\n", row_tag);
|
|
}
|
|
}
|
|
|
|
for (int i = 1; i <= SPI_tuptable->tupdesc->natts; i++) {
|
|
char *colname = map_sql_identifier_to_xml_name(SPI_fname(SPI_tuptable->tupdesc, i), true, false);
|
|
bool isnull = false;
|
|
Datum colval = SPI_getbinval(SPI_tuptable->vals[rownum], SPI_tuptable->tupdesc, i, &isnull);
|
|
|
|
if (isnull) {
|
|
if (colname == NULL) {
|
|
continue;
|
|
}
|
|
if (null_flag == NULL_FLAG_NULL_ATTR) {
|
|
appendStringInfoSpaces(result, indentation_level * SPACE_PER_INDENTATION);
|
|
appendStringInfo(result, "%s%s<%s%s xsi:nil=\"true\"/>\n", result->len == 0 ? XML_HEADER : "",
|
|
row_tag == NULL ? "" : " ", result->len == 0 ? XML_XSI_ATTR : "", colname);
|
|
} else if (null_flag == NULL_FLAG_EMPTY_TAG) {
|
|
appendStringInfoSpaces(result, indentation_level * SPACE_PER_INDENTATION);
|
|
appendStringInfo(result, "%s%s<%s/>\n", result->len == 0 ? XML_HEADER : "", row_tag == NULL ? "" : " ",
|
|
colname);
|
|
}
|
|
} else if (colname != NULL) {
|
|
appendStringInfoSpaces(result, indentation_level * SPACE_PER_INDENTATION);
|
|
appendStringInfo(result, "%s%s<%s%s>%s</%s>\n", result->len == 0 ? XML_HEADER : "",
|
|
row_tag == NULL ? "" : " ", colname,
|
|
result->len == 0 && null_flag == NULL_FLAG_NULL_ATTR ? XML_XSI_ATTR : "",
|
|
map_sql_value_to_xml(colval, SPI_gettypeid(SPI_tuptable->tupdesc, i),
|
|
is_convert_special_chars, item_tag, indentation_level + 1),
|
|
colname);
|
|
}
|
|
}
|
|
|
|
if (row_tag != NULL) {
|
|
appendStringInfoSpaces(result, indentation_level * SPACE_PER_INDENTATION);
|
|
appendStringInfo(result, "</%s>\n", row_tag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* check the xml is valid, fail if has no root element or has multiple ones
|
|
* @param {const XMLGenContext *} ctx - xmlgen contxt pointer
|
|
* @param {int64} rows - rows in xml
|
|
* @param {int64} columns - columns in a row
|
|
* @return {void}
|
|
*/
|
|
static void check_xml_valid(const XMLGenContext *ctx, int64 rows, int64 columns)
|
|
{
|
|
if (ctx->row_set_tag_name == NULL && (rows != 1 || (ctx->row_tag_name == NULL && columns != 1))) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("the xml has multiple root nodes")));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* convert query to xml
|
|
* @param {const XMLGenContext *} ctx - xmlgen contxt pointer
|
|
* @return {StringInfo} result
|
|
*/
|
|
static StringInfo query_to_xml_flat(XMLGenContext *ctx)
|
|
{
|
|
StringInfo result = NULL;
|
|
int64 skip_rows = ctx->skip_rows;
|
|
errno_t et = EOK;
|
|
char *row_tag = NULL;
|
|
char *row_set_tag = NULL;
|
|
Portal portal = NULL;
|
|
|
|
if (ctx->row_tag_name != NULL) {
|
|
row_tag = static_cast<char *>(palloc(sizeof(char) * (strlen(ctx->row_tag_name) + 1)));
|
|
et = strcpy_s(row_tag, strlen(ctx->row_tag_name) + 1, ctx->row_tag_name);
|
|
securec_check(et, "\0", "\0");
|
|
}
|
|
|
|
if (ctx->row_set_tag_name != NULL) {
|
|
row_set_tag = static_cast<char *>(palloc(sizeof(char) * (strlen(ctx->row_set_tag_name) + 1)));
|
|
et = strcpy_s(row_set_tag, strlen(ctx->row_set_tag_name) + 1, ctx->row_set_tag_name);
|
|
securec_check(et, "\0", "\0");
|
|
}
|
|
|
|
int64 null_flag = ctx->null_flag;
|
|
int64 processed_rows = 0;
|
|
int64 count_rows;
|
|
ctx->processed_rows = 0;
|
|
result = makeStringInfo();
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT) {
|
|
pfree_ext(row_tag);
|
|
pfree_ext(row_set_tag);
|
|
SPI_finish();
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE), errmsg("SPI_connect failed")));
|
|
}
|
|
|
|
if (ctx->is_cursor || ctx->skip_rows != 0 || ctx->max_rows != 0) {
|
|
if (ctx->is_cursor) {
|
|
portal = SPI_cursor_find((const char *)ctx->query_string);
|
|
} else {
|
|
SPIPlanPtr plan = NULL;
|
|
if ((plan = SPI_prepare((const char *)ctx->query_string, 0, NULL)) == NULL) {
|
|
SPI_finish();
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_PREPARE_FAILURE), errmsg("SPI_prepare failed")));
|
|
}
|
|
|
|
if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, false)) == NULL) {
|
|
SPI_finish();
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE), errmsg("SPI_cursor_open failed")));
|
|
}
|
|
}
|
|
if (portal == NULL) {
|
|
pfree_ext(row_tag);
|
|
pfree_ext(row_set_tag);
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_CURSOR), errmsg("cursor \"%s\" does not exist", ctx->query_string)));
|
|
}
|
|
check_uint_valid(ctx->current_row + ctx->skip_rows);
|
|
SPI_cursor_move(portal, true, ctx->current_row + ctx->skip_rows);
|
|
SPI_cursor_fetch(portal, true, ctx->max_rows >= 0 ? ctx->max_rows : UINT_MAX);
|
|
} else {
|
|
if (SPI_execute(ctx->query_string, true, 0) != SPI_OK_SELECT) {
|
|
pfree_ext(row_tag);
|
|
pfree_ext(row_set_tag);
|
|
SPI_finish();
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE), errmsg("fail to execute query")));
|
|
}
|
|
}
|
|
|
|
count_rows = (int64)SPI_processed;
|
|
check_xml_valid(ctx, count_rows, SPI_tuptable->tupdesc->natts);
|
|
if (ctx->max_rows != 0 && count_rows > 0) {
|
|
xml_root_element_start(result, row_set_tag == NULL ? row_tag : row_set_tag, null_flag);
|
|
for (int64 i = 0; i < count_rows; i++) {
|
|
SPI_sql_row_to_xml_element(i, result, null_flag, (row_set_tag == NULL || row_tag == NULL) ? NULL : row_tag,
|
|
ctx->is_convert_special_chars, ctx->item_tag_name,
|
|
(row_set_tag == NULL && row_tag == NULL) ? 0 : 1);
|
|
processed_rows++;
|
|
}
|
|
ctx->processed_rows = processed_rows;
|
|
ctx->current_row += processed_rows + skip_rows;
|
|
xml_root_element_end(result, row_set_tag == NULL ? row_tag : row_set_tag);
|
|
}
|
|
|
|
if (!ctx->is_cursor && portal != NULL) {
|
|
SPI_cursor_close(portal);
|
|
}
|
|
SPI_finish();
|
|
pfree_ext(row_tag);
|
|
pfree_ext(row_set_tag);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* top functin to handle converting query to xml
|
|
* @param {const XMLGenContext *} ctx - xmlgen contxt pointer
|
|
* @return {StringInfo} result
|
|
*/
|
|
static StringInfo query_to_xml_internal(XMLGenContext *ctx)
|
|
{
|
|
if (ctx->is_from_hierarchy) {
|
|
return query_to_xml_hierarchy(ctx);
|
|
} else {
|
|
return query_to_xml_flat(ctx);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* top functin to handle converting query to xml
|
|
* @param {const XMLGenContext *} ctx - xmlgen contxt pointer
|
|
* @return {StringInfo} result
|
|
*/
|
|
static void check_query_string_valid(char *query_str)
|
|
{
|
|
if (query_str == NULL) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid query string")));
|
|
}
|
|
List *queryTree = pg_parse_query(query_str);
|
|
if (queryTree == NULL || queryTree->length != 1) {
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("can not parse sql: %s", query_str)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* create a new XMLGenContext with specific id
|
|
* @param {int64} ctx_id
|
|
* @param {XMLGenContext *} result
|
|
*/
|
|
static XMLGenContext *create_xmlgen_context(int64 ctx_id)
|
|
{
|
|
|
|
if (TOP_CONTEXT == NULL) {
|
|
init_session_vars();
|
|
}
|
|
|
|
MemoryContext oldcontext = MemoryContextSwitchTo(TOP_CONTEXT->memctx);
|
|
XMLGenContext *ctx = static_cast<XMLGenContext *>(palloc0(sizeof(XMLGenContext)));
|
|
ctx->memctx = AllocSetContextCreate(TOP_CONTEXT->memctx, "XMLGENContextMemory", ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
oldcontext = MemoryContextSwitchTo(ctx->memctx);
|
|
|
|
ctx->ctx_id = ctx_id;
|
|
ctx->query_string = NULL;
|
|
ctx->null_flag = 0;
|
|
ctx->row_tag_name = "ROW";
|
|
ctx->row_set_tag_name = "ROWSET";
|
|
ctx->is_convert_special_chars = true;
|
|
ctx->max_rows = -1;
|
|
ctx->current_row = 0;
|
|
ctx->skip_rows = 0;
|
|
ctx->processed_rows = 0;
|
|
ctx->is_from_hierarchy = false;
|
|
ctx->is_hierarchy_set_row_set_tag = false;
|
|
ctx->is_cursor = false;
|
|
ctx->is_query = false;
|
|
ctx->item_tag_name = NULL;
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
return ctx;
|
|
}
|
|
|
|
/**
|
|
* create a new XMLGenContext by sequnce, and add it to global context list
|
|
* @param {char *} query_str
|
|
* @param {bool} is_from_herarchy
|
|
* @param {bool} is_cursor
|
|
*/
|
|
static int64 new_xmlgen_context(char *query_str, bool is_from_hierarchy, bool is_cursor)
|
|
{
|
|
MemoryContext oldcontext = NULL;
|
|
int64 new_ctx_id = ++(TOP_CONTEXT->xmlgen_context_id);
|
|
check_uint_valid(new_ctx_id);
|
|
XMLGenContext *ctx = create_xmlgen_context(new_ctx_id);
|
|
|
|
oldcontext = MemoryContextSwitchTo(ctx->memctx);
|
|
char *qs = static_cast<char *>(palloc(sizeof(char) * strlen(query_str) + 1));
|
|
errno_t et = strcpy_s(qs, strlen(query_str) + 1, query_str);
|
|
securec_check(et, "\0", "\0");
|
|
ctx->query_string = qs;
|
|
MemoryContextSwitchTo(oldcontext);
|
|
ctx->is_from_hierarchy = is_from_hierarchy;
|
|
ctx->is_cursor = is_cursor;
|
|
if (TOP_CONTEXT->xmlgen_context_list != NIL && !IsA(TOP_CONTEXT->xmlgen_context_list, List)) {
|
|
TOP_CONTEXT->xmlgen_context_list = NIL;
|
|
}
|
|
|
|
oldcontext = MemoryContextSwitchTo(TOP_CONTEXT->memctx);
|
|
TOP_CONTEXT->xmlgen_context_list = lappend(TOP_CONTEXT->xmlgen_context_list, (void *)ctx);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
return new_ctx_id;
|
|
}
|
|
|
|
/**
|
|
* cast string to ctxhandle
|
|
* @param {string} str
|
|
* @return {ctxhandle} result
|
|
*/
|
|
Datum ctxhandle_in(PG_FUNCTION_ARGS)
|
|
{
|
|
char *str = PG_GETARG_CSTRING(0);
|
|
int64 result;
|
|
(void)scanint8(str, false, &result);
|
|
|
|
PG_RETURN_NUMERIC(convert_int64_to_numeric(result, 0));
|
|
}
|
|
|
|
/**
|
|
* cast ctxhandle to string
|
|
* @param {ctxhandle} val
|
|
* @return {string} result
|
|
*/
|
|
Datum ctxhandle_out(PG_FUNCTION_ARGS)
|
|
{
|
|
Numeric num = PG_GETARG_NUMERIC(0);
|
|
char *str = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num)));
|
|
PG_RETURN_CSTRING(str);
|
|
}
|
|
|
|
/**
|
|
* close xmlgen context and release all resources, including the SQL cursor and bind and define buffers.
|
|
* @param {ctxhandle} ctx - the context handle to close.
|
|
* @return {void}
|
|
*/
|
|
Datum close_context(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
|
|
if (ctx != NULL) {
|
|
free_xmlgen_context(ctx);
|
|
}
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/**
|
|
* converts the xml data into the escaped or unescapes XML equivalent,
|
|
* and returns XML clob data in endcoded or decoded format.
|
|
* @param {varchar2} xmlData - the xml clob data to encoded or decoded.
|
|
* @param {number} flag - the flag setting. 1 for decode, others for encode(default).
|
|
* @return {void}
|
|
*/
|
|
Datum convert_xml(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid parameter")));
|
|
}
|
|
int64 flag = 0;
|
|
if (!PG_ARGISNULL(1)) {
|
|
flag = get_floor_numeric_int64(PG_GETARG_DATUM(1));
|
|
}
|
|
|
|
if (flag < 0 || flag > UINT_MAX) {
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
char *xmlstr = text_to_cstring(PG_GETARG_TEXT_PP(0));
|
|
char *str = NULL;
|
|
|
|
if (flag == 1) {
|
|
str = convert_decode((const char *)xmlstr);
|
|
} else {
|
|
str = convert_encode((const char *)xmlstr);
|
|
}
|
|
|
|
VarChar *result = (VarChar *)cstring_to_text((const char *)str);
|
|
pfree(str);
|
|
pfree(xmlstr);
|
|
PG_RETURN_VARCHAR_P(result);
|
|
}
|
|
|
|
/**
|
|
* converts the xml data into the escaped or unescapes XML equivalent,
|
|
* and returns XML clob data in endcoded or decoded format.
|
|
* @param {clob} xmlData - the xml clob data to encoded or decoded.
|
|
* @param {number} flag - the flag setting; 1 for decode, others for encode.
|
|
* @return {clob} result
|
|
*/
|
|
Datum convert_clob(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid parameter")));
|
|
}
|
|
int64 flag = 0;
|
|
if (!PG_ARGISNULL(1)) {
|
|
flag = get_floor_numeric_int64(PG_GETARG_DATUM(1));
|
|
}
|
|
|
|
char *xmlstr = text_to_cstring((const text *)PG_GETARG_BYTEA_PP(0));
|
|
if (flag < 0 || flag > UINT_MAX) {
|
|
pfree(xmlstr);
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
char *str = NULL;
|
|
|
|
if (flag == 1) {
|
|
str = convert_decode((const char *)xmlstr);
|
|
} else {
|
|
str = convert_encode((const char *)xmlstr);
|
|
}
|
|
|
|
xmltype *result = (xmltype *)cstring_to_text((const char *)str);
|
|
pfree(str);
|
|
pfree(xmlstr);
|
|
PG_RETURN_XML_P(result);
|
|
}
|
|
|
|
/**
|
|
* retrieves the number of SQL rows processed when generating the xml using the getxml functions call.
|
|
* @param {ctxhandle} ctx_id
|
|
* @return {number} result
|
|
*/
|
|
Datum get_num_rows_processed(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
if (ctx == NULL) {
|
|
PG_RETURN_NULL();
|
|
}
|
|
PG_RETURN_NUMERIC(convert_int64_to_numeric(ctx->processed_rows, 0));
|
|
}
|
|
|
|
/**
|
|
* gets xml document by context id
|
|
* @param {ctxhandle} ctx_id
|
|
* @param {number} dtdOrSchema - a flag to generate a DTD or a schema, default 0
|
|
* @return {clob} result - xml data
|
|
*/
|
|
Datum get_xml_by_ctx_id(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid gms_xmlgen context id")));
|
|
}
|
|
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
int64 dtd_or_schema = get_floor_numeric_int64(PG_GETARG_DATUM(1));
|
|
check_uint_valid(dtd_or_schema);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
if (ctx == NULL || ctx->query_string == NULL) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid gms_xmlgen context found")));
|
|
}
|
|
StringInfo si = NULL;
|
|
xmltype *res = NULL;
|
|
si = query_to_xml_internal(ctx);
|
|
res = stringinfo_to_xmltype(si);
|
|
DestroyStringInfo(si);
|
|
PG_RETURN_XML_P(res);
|
|
}
|
|
|
|
/**
|
|
* gets xml document by query string
|
|
* @param {varchar2} query string
|
|
* @param {number} dtdOrSchema - a flag to generate a DTD or a schema, default 0
|
|
* @return {clob} result - xml data
|
|
*/
|
|
Datum get_xml_by_query(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid query string")));
|
|
}
|
|
char *query_str = text_to_cstring(PG_GETARG_TEXT_PP(0));
|
|
check_query_string_valid(query_str);
|
|
int64 dtd_or_schema = get_floor_numeric_int64(PG_GETARG_DATUM(1));
|
|
check_uint_valid(dtd_or_schema);
|
|
XMLGenContext *ctx = create_xmlgen_context(0);
|
|
ctx->query_string = query_str;
|
|
StringInfo si = query_to_xml_internal(ctx);
|
|
xmltype *res = stringinfo_to_xmltype(si);
|
|
DestroyStringInfo(si);
|
|
pfree_ext(query_str);
|
|
free_xmlgen_context(ctx);
|
|
PG_RETURN_XML_P(res);
|
|
}
|
|
|
|
/**
|
|
* generate and return a new context handle from a query string
|
|
* @param {varchar2} query string
|
|
* @return {ctxhandle} result - context id
|
|
*/
|
|
Datum new_context_by_query(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid query string")));
|
|
}
|
|
char *query_str = text_to_cstring(PG_GETARG_TEXT_PP(0));
|
|
int64 result = new_xmlgen_context(query_str);
|
|
PG_RETURN_NUMERIC(convert_int64_to_numeric((int64)result, 0));
|
|
}
|
|
|
|
/**
|
|
* generate and return a new context handle from a query string in the form of a PL/SQL ref cursor
|
|
* @param {sys_refcursor} query string
|
|
* @return {ctxhandle} result - context id
|
|
*/
|
|
Datum new_context_by_cursor(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
char *query_str = text_to_cstring(PG_GETARG_TEXT_PP(0));
|
|
int64 result = new_xmlgen_context(query_str, false, true);
|
|
PG_RETURN_NUMERIC(convert_int64_to_numeric((int64)result, 0));
|
|
}
|
|
|
|
/**
|
|
* generate and return a new hierarchy context handle from query string
|
|
* @param {varchar2} query string
|
|
* @return {ctxhandle} result - context id
|
|
*/
|
|
Datum new_context_from_hierarchy(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid query string")));
|
|
}
|
|
char *query_str = text_to_cstring(PG_GETARG_TEXT_PP(0));
|
|
int64 result = new_xmlgen_context(query_str, true);
|
|
PG_RETURN_NUMERIC(convert_int64_to_numeric((int64)result, 0));
|
|
}
|
|
|
|
/**
|
|
* restart the query and generate the XML from the first row
|
|
* used to start executing the query again, without having to create a new context
|
|
* @param {ctxhandle} ctx_id
|
|
* @return {void}
|
|
*/
|
|
Datum restart_query(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid gms_xmlgen context id")));
|
|
}
|
|
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
if (ctx == NULL) {
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
if (ctx->is_cursor) {
|
|
if (SPI_connect() != SPI_OK_CONNECT) {
|
|
SPI_finish();
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE), errmsg("SPI_connect failed")));
|
|
}
|
|
|
|
Portal portal = SPI_cursor_find((const char *)ctx->query_string);
|
|
if (portal == NULL) {
|
|
SPI_finish();
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_CURSOR), errmsg("cursor \"%s\" does not exist", ctx->query_string)));
|
|
}
|
|
|
|
SPI_cursor_move(portal, false,
|
|
ctx->current_row + (ctx->max_rows < 0 || ctx->processed_rows < ctx->max_rows ? 1 : 0));
|
|
|
|
SPI_finish();
|
|
}
|
|
ctx->current_row = 0;
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
/**
|
|
* set whether or not special characters in the XML data must be converted into their escaped XML equivalent
|
|
* @param {ctxhandle} ctx_id
|
|
* @param {boolean} is_convert
|
|
* @return {void}
|
|
*/
|
|
Datum set_convert_special_chars(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
bool is_convert = PG_GETARG_BOOL(1);
|
|
if (ctx == NULL) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
ctx->is_convert_special_chars = is_convert;
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/**
|
|
* set the maximum number of rows to fetch from the SQL query result for every invocation of the GETXML functions call
|
|
* @param {ctxhandle} ctx_id
|
|
* @param {number} max_rows
|
|
* @return {void}
|
|
*/
|
|
Datum set_max_rows(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
int64 max_rows = get_floor_numeric_int64(PG_GETARG_DATUM(1));
|
|
check_uint_valid(max_rows);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
if (ctx == NULL) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
if (ctx->is_from_hierarchy) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("this operation is invalid in the hierarchy context")));
|
|
}
|
|
|
|
ctx->max_rows = max_rows;
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/**
|
|
* set null handling options, handled through the flag parameter setting
|
|
* @param {ctxhandle} ctx_id
|
|
* @param {number} flag - 0 for drop nulls(default); 1 for null attribute; 2 for empty tags
|
|
* @return {void}
|
|
*/
|
|
Datum set_null_handling(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
int64 flag = get_floor_numeric_int64(PG_GETARG_DATUM(1));
|
|
check_uint_valid(flag);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
if (ctx != NULL) {
|
|
ctx->null_flag = flag;
|
|
}
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/**
|
|
* set the name of the root element of the xml
|
|
* @param {ctxhandle} ctx_id
|
|
* @param {varchar2} row_set_tag_name
|
|
* @return {void}
|
|
*/
|
|
Datum set_row_set_tag(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
if (ctx == NULL) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
if (ctx->is_from_hierarchy) {
|
|
ctx->is_hierarchy_set_row_set_tag = true;
|
|
}
|
|
|
|
if (PG_ARGISNULL(1)) {
|
|
ctx->row_set_tag_name = NULL;
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
char *row_set_tag_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
|
|
MemoryContext oldcontext = MemoryContextSwitchTo(ctx->memctx);
|
|
char *row_set_tag_inner = static_cast<char *>(palloc(sizeof(char) * strlen(row_set_tag_name) + 1));
|
|
errno_t et = strcpy_s(row_set_tag_inner, strlen(row_set_tag_name) + 1, row_set_tag_name);
|
|
securec_check(et, "\0", "\0");
|
|
ctx->row_set_tag_name = map_sql_identifier_to_xml_name(row_set_tag_inner, false, false);
|
|
pfree_ext(row_set_tag_inner);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
pfree_ext(row_set_tag_name);
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/**
|
|
* set the name of the element separating all the rows.
|
|
* @param {ctxhandle} ctx_id
|
|
* @param {varchar2} row_tag_name
|
|
* @return {void}
|
|
*/
|
|
Datum set_row_tag(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
if (ctx == NULL) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
if (ctx->is_from_hierarchy) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("this operation is invalid in the hierarchy context")));
|
|
}
|
|
|
|
if (PG_ARGISNULL(1)) {
|
|
ctx->row_tag_name = NULL;
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
char *row_tag_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
|
|
MemoryContext oldcontext = MemoryContextSwitchTo(ctx->memctx);
|
|
|
|
char *row_tag_inner = static_cast<char *>(palloc(sizeof(char) * strlen(row_tag_name) + 1));
|
|
errno_t et = strcpy_s(row_tag_inner, strlen(row_tag_name) + 1, row_tag_name);
|
|
securec_check(et, "\0", "\0");
|
|
ctx->row_tag_name = map_sql_identifier_to_xml_name(row_tag_inner, false, false);
|
|
pfree_ext(row_tag_inner);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
pfree_ext(row_tag_name);
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/**
|
|
* skip a given number of rows before generating the XML output for every call to the getxml functions
|
|
* @param {ctxhandle} ctx_id
|
|
* @param {number} skip_rows
|
|
* @return {void}
|
|
*/
|
|
Datum set_skip_rows(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
int64 skip_rows = get_floor_numeric_int64(PG_GETARG_DATUM(1));
|
|
check_uint_valid(skip_rows);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
if (ctx == NULL) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
if (ctx->is_from_hierarchy) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("this operation is invalid in the hierarchy context")));
|
|
}
|
|
|
|
ctx->skip_rows = skip_rows;
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/**
|
|
* override the default name of the collection elements.
|
|
* @param {ctxhandle} ctx_id
|
|
* @return {void}
|
|
*/
|
|
Datum use_item_tags_for_coll(PG_FUNCTION_ARGS)
|
|
{
|
|
CHECK_XML_SUPPORT();
|
|
|
|
if (PG_ARGISNULL(0)) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
int64 ctx_id = get_floor_numeric_int64(PG_GETARG_DATUM(0));
|
|
check_uint_valid(ctx_id);
|
|
XMLGenContext *ctx = get_xmlgen_context(ctx_id);
|
|
if (ctx == NULL) {
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
ctx->item_tag_name = "_ITEM";
|
|
PG_RETURN_VOID();
|
|
}
|