Skip to content

Commit

Permalink
[feature] Allow setting the namespace on the result of sql:execute
Browse files Browse the repository at this point in the history
  • Loading branch information
Nico Verwer authored and adamretter committed Oct 21, 2024
1 parent bf48faa commit 5556f02
Showing 1 changed file with 104 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,17 @@
*/
package org.exist.xquery.modules.sql;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.exist.dom.memtree.*;
import org.exist.util.XMLReaderPool;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.exist.xquery.*;
import org.exist.xquery.value.*;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.exist.Namespaces;
import org.exist.dom.QName;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.exist.xquery.FunctionDSL.arities;
import static org.exist.xquery.FunctionDSL.arity;
import static org.exist.xquery.FunctionDSL.optParam;
import static org.exist.xquery.FunctionDSL.param;
import static org.exist.xquery.FunctionDSL.returnsOpt;
import static org.exist.xquery.modules.sql.SQLModule.NAMESPACE_URI;
import static org.exist.xquery.modules.sql.SQLModule.PREFIX;

import java.io.IOException;
import java.io.PrintStream;

import java.io.Reader;
import java.sql.Connection;
import java.sql.PreparedStatement;
Expand All @@ -51,15 +44,38 @@
import java.sql.Timestamp;
import java.sql.Types;

import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import javax.annotation.Nullable;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.exist.xquery.FunctionDSL.*;
import static org.exist.xquery.modules.sql.SQLModule.NAMESPACE_URI;
import static org.exist.xquery.modules.sql.SQLModule.PREFIX;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.Namespaces;
import org.exist.dom.QName;
import org.exist.dom.memtree.AppendingSAXAdapter;
import org.exist.dom.memtree.ElementImpl;
import org.exist.dom.memtree.MemTreeBuilder;
import org.exist.dom.memtree.ReferenceNode;
import org.exist.dom.memtree.SAXAdapter;
import org.exist.util.XMLReaderPool;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.ErrorCodes;
import org.exist.xquery.Expression;
import org.exist.xquery.FunctionDSL;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.BooleanValue;
import org.exist.xquery.value.DateTimeValue;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.IntegerValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;


/**
Expand All @@ -80,11 +96,31 @@ public class ExecuteFunction extends BasicFunction {
"connection-handle",
Type.LONG,
"The connection handle");
private static final FunctionParameterSequenceType FS_PARAM_SQL_STATEMENT = param(
"sql-statement",
Type.STRING,
"The SQL statement");
private static final FunctionParameterSequenceType FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME = param(
"make-node-from-column-name",
Type.BOOLEAN,
"The flag that indicates whether the xml nodes should be formed from the column names" +
" (in this mode a space in a Column Name will be replaced by an underscore!)");
private static final FunctionParameterSequenceType FS_PARAM_STATEMENT_HANDLE = param(
"statement-handle",
Type.LONG,
"The prepared statement handle");
private static final FunctionParameterSequenceType FS_PARAM_PARAMETERS = optParam(
"parameters",
Type.ELEMENT,
"Parameters for the prepared statement. e.g. <sql:parameters><sql:param sql:type=\"long\">1234</sql:param><sql:param sql:type=\"varchar\"><sql:null/></sql:param></sql:parameters>");
private static final FunctionParameterSequenceType FS_PARAM_NAMESPACE_URI = param(
"ns-uri",
Type.STRING,
"The uri of the result namespace.");
private static final FunctionParameterSequenceType FS_PARAM_NAMESPACE_PREFIX = param(
"ns-prefix",
Type.STRING,
"The prefix of the result namespace.");

static final FunctionSignature[] FS_EXECUTE = functionSignatures(
FS_EXECUTE_NAME,
Expand All @@ -93,14 +129,29 @@ public class ExecuteFunction extends BasicFunction {
arities(
arity(
FS_PARAM_CONNECTION_HANDLE,
param("sql-statement", Type.STRING, "The SQL statement"),
FS_PARAM_SQL_STATEMENT,
FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME
),
arity(
FS_PARAM_CONNECTION_HANDLE,
param("statement-handle", Type.LONG, "The prepared statement handle"),
optParam("parameters", Type.ELEMENT, "Parameters for the prepared statement. e.g. <sql:parameters><sql:param sql:type=\"long\">1234</sql:param><sql:param sql:type=\"varchar\"><sql:null/></sql:param></sql:parameters>"),
FS_PARAM_STATEMENT_HANDLE,
FS_PARAM_PARAMETERS,
FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME
),
arity(
FS_PARAM_CONNECTION_HANDLE,
FS_PARAM_SQL_STATEMENT,
FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME,
FS_PARAM_NAMESPACE_URI,
FS_PARAM_NAMESPACE_PREFIX
),
arity(
FS_PARAM_CONNECTION_HANDLE,
FS_PARAM_STATEMENT_HANDLE,
FS_PARAM_PARAMETERS,
FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME,
FS_PARAM_NAMESPACE_URI,
FS_PARAM_NAMESPACE_PREFIX
)
)
);
Expand Down Expand Up @@ -147,20 +198,30 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro

try {
final boolean makeNodeFromColumnName;
final String namespacePrefix;
final String namespaceUri;
final boolean executeResult;

// Static SQL or PreparedStatement?
if (args.length == 3) {
if (args.length == 3 || args.length == 5) {

// get the static SQL statement
sql = args[1].getStringValue();
stmt = con.createStatement();
makeNodeFromColumnName = ((BooleanValue) args[2].itemAt(0)).effectiveBooleanValue();
if (args.length == 5) {
namespaceUri = args[3].itemAt(0).getStringValue();
namespacePrefix = args[4].itemAt(0).getStringValue();
} else {
// The default namespace for result elements.
namespaceUri = NAMESPACE_URI;
namespacePrefix = PREFIX;
}

//execute the static SQL statement
executeResult = stmt.execute(sql);

} else if (args.length == 4) {
} else if (args.length == 4 || args.length == 6) {
//get the prepared statement
final long statementUID = ((IntegerValue) args[1].itemAt(0)).getLong();
final PreparedStatementWithSQL stmtWithSQL = SQLModule.retrievePreparedStatement(context, statementUID);
Expand All @@ -172,6 +233,14 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
}

makeNodeFromColumnName = ((BooleanValue) args[3].itemAt(0)).effectiveBooleanValue();
if (args.length == 6) {
namespaceUri = args[4].itemAt(0).getStringValue();
namespacePrefix = args[5].itemAt(0).getStringValue();
} else {
// The default namespace for result elements.
namespaceUri = NAMESPACE_URI;
namespacePrefix = PREFIX;
}

if (!args[2].isEmpty()) {
parametersElement = (Element) args[2].itemAt(0);
Expand All @@ -186,7 +255,7 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
}

// return the XML result set
return resultAsElement(makeNodeFromColumnName, executeResult, stmt, this);
return resultAsElement(makeNodeFromColumnName, namespacePrefix, namespaceUri, executeResult, stmt, this);

} catch (final SQLException sqle) {
LOG.error("sql:execute() Caught SQLException \"{}\" for SQL: \"{}\"", sqle.getMessage(), sql, sqle);
Expand Down Expand Up @@ -263,15 +332,15 @@ private void setParametersOnPreparedStatement(final Statement stmt, final Elemen
}
}

private ElementImpl resultAsElement(final boolean makeNodeFromColumnName,
private ElementImpl resultAsElement(final boolean makeNodeFromColumnName, final String namespacePrefix, final String namespaceUri,
final boolean executeResult, final Statement stmt, final Expression expression) throws SQLException, XPathException {
context.pushDocumentContext();
try {
final MemTreeBuilder builder = context.getDocumentBuilder();

builder.startDocument();

builder.startElement(new QName("result", NAMESPACE_URI, PREFIX), null);
builder.startElement(new QName("result", namespaceUri, namespacePrefix), null);
builder.addAttribute(new QName("count", null, null), "-1");
builder.addAttribute(new QName("updateCount", null, null), String.valueOf(stmt.getUpdateCount()));

Expand Down Expand Up @@ -300,7 +369,7 @@ private ElementImpl resultAsElement(final boolean makeNodeFromColumnName,
final int iColumns = rsmd.getColumnCount();

while (rs.next()) {
builder.startElement(new QName("row", NAMESPACE_URI, PREFIX), null);
builder.startElement(new QName("row", namespaceUri, namespacePrefix), null);
builder.addAttribute(new QName("index", null, null), String.valueOf(rs.getRow()));

// get each tuple in the row
Expand All @@ -321,7 +390,7 @@ private ElementImpl resultAsElement(final boolean makeNodeFromColumnName,
colElement = SQLUtils.escapeXmlAttr(columnName.replace(' ', '_'));
}

builder.startElement(new QName(colElement, NAMESPACE_URI, PREFIX), null);
builder.startElement(new QName(colElement, namespaceUri, namespacePrefix), null);

if (!makeNodeFromColumnName || columnName.length() <= 0) {
final String name;
Expand All @@ -334,7 +403,7 @@ private ElementImpl resultAsElement(final boolean makeNodeFromColumnName,
builder.addAttribute(new QName("name", null, null), name);
}

builder.addAttribute(new QName(TYPE_ATTRIBUTE_NAME, NAMESPACE_URI, PREFIX), rsmd.getColumnTypeName(i + 1));
builder.addAttribute(new QName(TYPE_ATTRIBUTE_NAME, namespaceUri, namespacePrefix), rsmd.getColumnTypeName(i + 1));
builder.addAttribute(new QName(TYPE_ATTRIBUTE_NAME, Namespaces.SCHEMA_NS, "xs"), Type.getTypeName(SQLUtils.sqlTypeToXMLType(rsmd.getColumnType(i + 1))));

//get the content
Expand All @@ -345,7 +414,7 @@ private ElementImpl resultAsElement(final boolean makeNodeFromColumnName,

if (rs.wasNull()) {
// Add a null indicator attribute if the value was SQL Null
builder.addAttribute(new QName("null", NAMESPACE_URI, PREFIX), "true");
builder.addAttribute(new QName("null", namespaceUri, namespacePrefix), "true");
} else {
try (final Reader charStream = sqlXml.getCharacterStream()) {
final InputSource src = new InputSource(charStream);
Expand Down Expand Up @@ -374,7 +443,7 @@ private ElementImpl resultAsElement(final boolean makeNodeFromColumnName,

if (rs.wasNull()) {
// Add a null indicator attribute if the value was SQL Null
builder.addAttribute(new QName("null", NAMESPACE_URI, PREFIX), "true");
builder.addAttribute(new QName("null", namespaceUri, namespacePrefix), "true");
} else {
if (colValue != null) {
builder.characters(colValue);
Expand Down

0 comments on commit 5556f02

Please sign in to comment.