Skip to content

Commit

Permalink
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 committed Jan 10, 2024
1 parent c5c1a0d commit 5b4756f
Showing 1 changed file with 83 additions and 32 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 @@ -85,6 +101,9 @@ public class ExecuteFunction extends BasicFunction {
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!)");
// Parameters for setting the namespace of result elements.
private static final FunctionParameterSequenceType FS_PARAM_NAMESPACE_PREFIX = param("ns-prefix", Type.STRING, "The prefix of the result namespace.");
private static final FunctionParameterSequenceType FS_PARAM_NAMESPACE_URI = param("ns-uri", Type.STRING, "The uri of the result namespace.");

static final FunctionSignature[] FS_EXECUTE = functionSignatures(
FS_EXECUTE_NAME,
Expand All @@ -101,6 +120,20 @@ public class ExecuteFunction extends BasicFunction {
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_MAKE_NODE_FROM_COLUMN_NAME
),
// New function signatures for setting the namespace of result elements.
arity(
FS_PARAM_CONNECTION_HANDLE,
param("sql-statement", Type.STRING, "The SQL statement"),
FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME,
FS_PARAM_NAMESPACE_PREFIX, FS_PARAM_NAMESPACE_URI
),
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_MAKE_NODE_FROM_COLUMN_NAME,
FS_PARAM_NAMESPACE_PREFIX, FS_PARAM_NAMESPACE_URI
)
)
);
Expand Down Expand Up @@ -147,20 +180,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) {
namespacePrefix = args[3].itemAt(0).getStringValue();
namespaceUri = args[4].itemAt(0).getStringValue();
} else {
// The default namespace for result elements.
namespacePrefix = PREFIX;
namespaceUri = NAMESPACE_URI;
}

//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 +215,14 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
}

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

if (!args[2].isEmpty()) {
parametersElement = (Element) args[2].itemAt(0);
Expand All @@ -186,7 +237,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 @@ -264,15 +315,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,

Check warning on line 318 in extensions/modules/sql/src/main/java/org/exist/xquery/modules/sql/ExecuteFunction.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

extensions/modules/sql/src/main/java/org/exist/xquery/modules/sql/ExecuteFunction.java#L318

The method 'resultAsElement(boolean, String, String, boolean, Statement, Expression)' has an NPath complexity of 1348, current threshold is 200
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 @@ -301,7 +352,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 @@ -322,7 +373,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 @@ -335,7 +386,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 @@ -346,7 +397,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 @@ -375,7 +426,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 5b4756f

Please sign in to comment.