• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.16
/extensions/modules/sql/src/main/java/org/exist/xquery/modules/sql/ExecuteFunction.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.xquery.modules.sql;
50

51
import static java.nio.charset.StandardCharsets.UTF_8;
52
import static org.exist.xquery.FunctionDSL.arities;
53
import static org.exist.xquery.FunctionDSL.arity;
54
import static org.exist.xquery.FunctionDSL.optParam;
55
import static org.exist.xquery.FunctionDSL.param;
56
import static org.exist.xquery.FunctionDSL.returnsOpt;
57
import static org.exist.xquery.modules.sql.SQLModule.NAMESPACE_URI;
58
import static org.exist.xquery.modules.sql.SQLModule.PREFIX;
59

60
import java.io.IOException;
61
import java.io.PrintStream;
62
import java.io.Reader;
63
import java.sql.Connection;
64
import java.sql.PreparedStatement;
65
import java.sql.ResultSet;
66
import java.sql.ResultSetMetaData;
67
import java.sql.SQLException;
68
import java.sql.SQLRecoverableException;
69
import java.sql.SQLXML;
70
import java.sql.Statement;
71
import java.sql.Timestamp;
72
import java.sql.Types;
73

74
import javax.annotation.Nullable;
75

76
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
77
import org.apache.logging.log4j.LogManager;
78
import org.apache.logging.log4j.Logger;
79
import org.exist.Namespaces;
80
import org.exist.dom.QName;
81
import org.exist.dom.memtree.AppendingSAXAdapter;
82
import org.exist.dom.memtree.ElementImpl;
83
import org.exist.dom.memtree.MemTreeBuilder;
84
import org.exist.dom.memtree.SAXAdapter;
85
import org.exist.util.XMLReaderPool;
86
import org.exist.xquery.BasicFunction;
87
import org.exist.xquery.ErrorCodes;
88
import org.exist.xquery.Expression;
89
import org.exist.xquery.FunctionDSL;
90
import org.exist.xquery.FunctionSignature;
91
import org.exist.xquery.XPathException;
92
import org.exist.xquery.XQueryContext;
93
import org.exist.xquery.value.BooleanValue;
94
import org.exist.xquery.value.DateTimeValue;
95
import org.exist.xquery.value.FunctionParameterSequenceType;
96
import org.exist.xquery.value.FunctionReturnSequenceType;
97
import org.exist.xquery.value.IntegerValue;
98
import org.exist.xquery.value.Sequence;
99
import org.exist.xquery.value.Type;
100
import org.w3c.dom.Element;
101
import org.w3c.dom.Node;
102
import org.w3c.dom.NodeList;
103
import org.xml.sax.InputSource;
104
import org.xml.sax.XMLReader;
105

106

107
/**
108
 * SQL Module Extension ExecuteFunction.
109
 *
110
 * Execute a SQL statement against a SQL capable Database
111
 *
112
 * @author <a href="mailto:adam@exist-db.org">Adam Retter</a>
113
 * @version 1.2.0
114
 */
115
public class ExecuteFunction extends BasicFunction {
116

117
    private static final Logger LOG = LogManager.getLogger(ExecuteFunction.class);
1✔
118

119
    private static final String FS_EXECUTE_NAME = "execute";
120

121
    private static final FunctionParameterSequenceType FS_PARAM_CONNECTION_HANDLE = param(
1✔
122
            "connection-handle",
123
            Type.LONG,
124
            "The connection handle");
125
    private static final FunctionParameterSequenceType FS_PARAM_SQL_STATEMENT = param(
1✔
126
            "sql-statement",
127
            Type.STRING,
128
            "The SQL statement");
129
    private static final FunctionParameterSequenceType FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME = param(
1✔
130
            "make-node-from-column-name",
131
            Type.BOOLEAN,
132
            "The flag that indicates whether the xml nodes should be formed from the column names" +
133
                    " (in this mode a space in a Column Name will be replaced by an underscore!)");
134
    private static final FunctionParameterSequenceType FS_PARAM_STATEMENT_HANDLE = param(
1✔
135
            "statement-handle",
136
            Type.LONG,
137
            "The prepared statement handle");
138
    private static final FunctionParameterSequenceType FS_PARAM_PARAMETERS = optParam(
1✔
139
            "parameters",
140
            Type.ELEMENT,
141
            "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>");
142
    private static final FunctionParameterSequenceType FS_PARAM_NAMESPACE_URI = param(
1✔
143
            "ns-uri",
144
            Type.STRING,
145
            "The uri of the result namespace.");
146
    private static final FunctionParameterSequenceType FS_PARAM_NAMESPACE_PREFIX = param(
1✔
147
            "ns-prefix",
148
            Type.STRING,
149
            "The prefix of the result namespace.");
150

151
    static final FunctionSignature[] FS_EXECUTE = functionSignatures(
1✔
152
            FS_EXECUTE_NAME,
153
            "Executes a prepared SQL statement against a SQL db.",
154
            returnsOpt(Type.ELEMENT, "the results"),
1✔
155
            arities(
1✔
156
                    arity(
1✔
157
                            FS_PARAM_CONNECTION_HANDLE,
158
                            FS_PARAM_SQL_STATEMENT,
159
                            FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME
160
                    ),
161
                    arity(
1✔
162
                            FS_PARAM_CONNECTION_HANDLE,
163
                            FS_PARAM_STATEMENT_HANDLE,
164
                            FS_PARAM_PARAMETERS,
165
                            FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME
166
                    ),
167
                    arity(
1✔
168
                            FS_PARAM_CONNECTION_HANDLE,
169
                            FS_PARAM_SQL_STATEMENT,
170
                            FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME,
171
                            FS_PARAM_NAMESPACE_URI,
172
                            FS_PARAM_NAMESPACE_PREFIX
173
                    ),
174
                    arity(
1✔
175
                            FS_PARAM_CONNECTION_HANDLE,
176
                            FS_PARAM_STATEMENT_HANDLE,
177
                            FS_PARAM_PARAMETERS,
178
                            FS_PARAM_MAKE_NODE_FROM_COLUMN_NAME,
179
                            FS_PARAM_NAMESPACE_URI,
180
                            FS_PARAM_NAMESPACE_PREFIX
181
                    )
182
            )
183
    );
184

185
    private final static String PARAMETERS_ELEMENT_NAME = "parameters";
186
    private final static String PARAM_ELEMENT_NAME = "param";
187
    private final static String TYPE_ATTRIBUTE_NAME = "type";
188

189
    /**
190
     * ExecuteFunction Constructor.
191
     *
192
     * @param context   The Context of the calling XQuery
193
     * @param signature The function signature
194
     */
195
    public ExecuteFunction(final XQueryContext context, final FunctionSignature signature) {
196
        super(context, signature);
1✔
197
    }
1✔
198

199
    /**
200
     * evaluate the call to the XQuery execute() function, it is really the main entry point of this class.
201
     *
202
     * @param args            arguments from the execute() function call
203
     * @param contextSequence the Context Sequence to operate on (not used here internally!)
204
     *
205
     * @return An element representing the SQL result set
206
     *
207
     * @throws XPathException if an error occurs whilst executing the query
208
     */
209
    @Override
210
    public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
211

212
        // get the Connection
213
        final long connectionUID = ((IntegerValue) args[0].itemAt(0)).getLong();
1✔
214
        final Connection con = SQLModule.retrieveConnection(context, connectionUID);
1✔
215
        if (con == null) {
1!
216
            throw new XPathException(this, "No such SQL Connection");
×
217
        }
218

219
        Element parametersElement = null;
1✔
220

221
        //setup the SQL statement
222
        String sql = null;
1✔
223
        Statement stmt = null;
1✔
224

225
        try {
226
            final boolean makeNodeFromColumnName;
227
            final String namespacePrefix;
228
            final String namespaceUri;
229
            final boolean executeResult;
230

231
            // Static SQL or PreparedStatement?
232
            if (args.length == 3 || args.length == 5) {
1!
233

234
                // get the static SQL statement
235
                sql = args[1].getStringValue();
1✔
236
                stmt = con.createStatement();
1✔
237
                makeNodeFromColumnName = ((BooleanValue) args[2].itemAt(0)).effectiveBooleanValue();
1✔
238
                if (args.length == 5) {
1!
239
                    namespaceUri = args[3].itemAt(0).getStringValue();
×
240
                    namespacePrefix = args[4].itemAt(0).getStringValue();
×
241
                } else {
242
                    // The default namespace for result elements.
243
                    namespaceUri = NAMESPACE_URI;
1✔
244
                    namespacePrefix = PREFIX;
1✔
245
                }
246

247
                //execute the static SQL statement
248
                executeResult = stmt.execute(sql);
1✔
249

250
            } else if (args.length == 4 || args.length == 6) {
1!
251
                //get the prepared statement
252
                final long statementUID = ((IntegerValue) args[1].itemAt(0)).getLong();
1✔
253
                final PreparedStatementWithSQL stmtWithSQL = SQLModule.retrievePreparedStatement(context, statementUID);
1✔
254
                sql = stmtWithSQL.getSql();
1✔
255
                stmt = stmtWithSQL.getStmt();
1✔
256

257
                if (stmt.getConnection() != con) {
1!
258
                    throw new XPathException(this, "SQL Connection does not match that used for creating the PreparedStatement");
×
259
                }
260

261
                makeNodeFromColumnName = ((BooleanValue) args[3].itemAt(0)).effectiveBooleanValue();
1✔
262
                if (args.length == 6) {
1!
263
                    namespaceUri = args[4].itemAt(0).getStringValue();
×
264
                    namespacePrefix = args[5].itemAt(0).getStringValue();
×
265
                } else {
266
                    // The default namespace for result elements.
267
                    namespaceUri = NAMESPACE_URI;
1✔
268
                    namespacePrefix = PREFIX;
1✔
269
                }
270

271
                if (!args[2].isEmpty()) {
1!
272
                    parametersElement = (Element) args[2].itemAt(0);
1✔
273
                    setParametersOnPreparedStatement(stmt, parametersElement);
1✔
274
                }
275

276
                //execute the PreparedStatement
277
                executeResult = ((PreparedStatement) stmt).execute();
1✔
278

279
            } else {
1✔
280
                throw new XPathException(this, "Unknown function call: " + getSignature());
×
281
            }
282

283
            // return the XML result set
284
            return resultAsElement(makeNodeFromColumnName, namespacePrefix, namespaceUri, executeResult, stmt, this);
1✔
285

286
        } catch (final SQLException sqle) {
1✔
287
            LOG.error("sql:execute() Caught SQLException \"{}\" for SQL: \"{}\"", sqle.getMessage(), sql, sqle);
1✔
288
            return sqlExceptionAsElement(sqle, sql, parametersElement);
1✔
289

290
        } finally {
291
            // if it's not a prepared statement then close it
292
            if (stmt != null && !(stmt instanceof PreparedStatement)) {
1!
293
                try {
294
                    stmt.close();
1✔
295
                } catch (final SQLException se) {
×
296
                    LOG.warn("Unable to close JDBC PreparedStatement: {}", se.getMessage(), se);
×
297
                }
1✔
298
            }
299

300
        }
301
    }
302

303
    private void setParametersOnPreparedStatement(final Statement stmt, final Element parametersElement) throws SQLException, XPathException {
304
        final String ns = parametersElement.getNamespaceURI();
1✔
305
        if (ns != null && ns.equals(NAMESPACE_URI) && parametersElement.getLocalName().equals(PARAMETERS_ELEMENT_NAME)) {
1!
306
            final NodeList paramElements = parametersElement.getElementsByTagNameNS(NAMESPACE_URI, PARAM_ELEMENT_NAME);
1✔
307

308
            for (int i = 0; i < paramElements.getLength(); i++) {
1✔
309
                final Element param = ((Element) paramElements.item(i));
1✔
310
                Node child = param.getFirstChild();
1✔
311

312
                final int sqlType;
313
                final String type = param.getAttributeNS(NAMESPACE_URI, TYPE_ATTRIBUTE_NAME);
1✔
314
                if (!type.isEmpty()) {
1✔
315
                    sqlType = SQLUtils.sqlTypeFromString(type);
1✔
316
                } else {
317
                    throw new XPathException(this, ErrorCodes.ERROR, "<sql:param> must contain attribute sql:type");
1✔
318
                }
319

320
                final String value;
321
                if (child != null) {
1!
322
                    if (child instanceof final Element elem) {
×
323
                        // check for <sql:null/>
324
                        if ("null".equals(elem.getLocalName()) && NAMESPACE_URI.equals(elem.getNamespaceURI())) {
×
325
                            value = null;
×
326
                        } else {
327
                            value = child.getNodeValue();
×
328
                        }
329

330
                    } else {
331
                        value = child.getNodeValue();
×
332
                    }
333
                } else {
334
                    if (sqlType == Types.VARCHAR || sqlType == Types.LONGVARCHAR
1!
335
                            || sqlType == Types.NVARCHAR || sqlType == Types.LONGNVARCHAR
336
                            || sqlType == Types.CLOB || sqlType == Types.NCLOB) {
337
                        // for string data an empty sql:param element means the empty string
338
                        value = "";
1✔
339
                    } else {
340
                        // otherwise for other types empty means null which is the same as <sql:null/>
341
                        value = null;
1✔
342
                    }
343
                }
344

345
                if (sqlType == Types.TIMESTAMP) {
1!
346
                    final DateTimeValue dv = new DateTimeValue(this, value);
×
347
                    final Timestamp timestampValue = new Timestamp(dv.getDate().getTime());
×
348
                    ((PreparedStatement) stmt).setTimestamp(i + 1, timestampValue);
×
349

350
                } else {
×
351
                    ((PreparedStatement) stmt).setObject(i + 1, value, sqlType);
1✔
352
                }
353
            }
354
        }
355
    }
1✔
356

357
    private ElementImpl resultAsElement(final boolean makeNodeFromColumnName, final String namespacePrefix, final String namespaceUri,
358
            final boolean executeResult, final Statement stmt, final Expression expression) throws SQLException, XPathException {
359
        context.pushDocumentContext();
1✔
360
        try {
361
            final MemTreeBuilder builder = context.getDocumentBuilder();
1✔
362

363
            builder.startDocument();
1✔
364

365
            builder.startElement(new QName("result", namespaceUri, namespacePrefix), null);
1✔
366
            builder.addAttribute(new QName("count", null, null), "-1");
1✔
367
            builder.addAttribute(new QName("updateCount", null, null), String.valueOf(stmt.getUpdateCount()));
1✔
368

369
            int rowCount = 0;
1✔
370
            ResultSet rs = null;
1✔
371
            try {
372

373
                if (executeResult) {
1!
374
                    rs = stmt.getResultSet();
1✔
375
                }
376

377
                // for executing Stored Procedures that return results (e.g. SQL Server)
378
                if (rs == null) {
1!
379
                    try {
380
                        rs = stmt.getGeneratedKeys();
×
381
                    } catch (final SQLException e) {
×
382
                        // no-op - getGeneratedKeys is not always supported
383
                    }
×
384
                }
385

386
                if (rs != null) {
1!
387
                    /* SQL Query returned results */
388

389
                    // iterate through the result set building an XML document
390
                    final ResultSetMetaData rsmd = rs.getMetaData();
1✔
391
                    final int iColumns = rsmd.getColumnCount();
1✔
392

393
                    while (rs.next()) {
1✔
394
                        builder.startElement(new QName("row", namespaceUri, namespacePrefix), null);
1✔
395
                        builder.addAttribute(new QName("index", null, null), String.valueOf(rs.getRow()));
1✔
396

397
                        // get each tuple in the row
398
                        for (int i = 0; i < iColumns; i++) {
1✔
399
                            final String columnName = rsmd.getColumnLabel(i + 1);
1✔
400

401
                            if (columnName != null) {
1!
402

403
                                String colElement = "field";
1✔
404

405
                                if (makeNodeFromColumnName && !columnName.isEmpty()) {
1!
406
                                    // use column names as the XML node
407

408
                                    /*
409
                                     * Spaces in column names are replaced with
410
                                     * underscore's
411
                                     */
412
                                    colElement = SQLUtils.escapeXmlAttr(columnName.replace(' ', '_'));
×
413
                                }
414

415
                                builder.startElement(new QName(colElement, namespaceUri, namespacePrefix), null);
1✔
416

417
                                if (!makeNodeFromColumnName || columnName.length() <= 0) {
1!
418
                                    final String name;
419
                                    if (!columnName.isEmpty()) {
1!
420
                                        name = SQLUtils.escapeXmlAttr(columnName);
1✔
421
                                    } else {
422
                                        name = "Column: " + (i + 1);
×
423
                                    }
424

425
                                    builder.addAttribute(new QName("name", null, null), name);
1✔
426
                                }
427

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

431
                                //get the content
432
                                if (rsmd.getColumnType(i + 1) == Types.SQLXML) {
1!
433
                                    //parse sqlxml value
434
                                    try {
435
                                        final SQLXML sqlXml = rs.getSQLXML(i + 1);
×
436

437
                                        if (rs.wasNull()) {
×
438
                                            // Add a null indicator attribute if the value was SQL Null
439
                                            builder.addAttribute(new QName("null", namespaceUri, namespacePrefix), "true");
×
440
                                        } else {
441
                                            try (final Reader charStream = sqlXml.getCharacterStream()) {
×
442
                                                final InputSource src = new InputSource(charStream);
×
443
                                                final XMLReaderPool parserPool = context.getBroker().getBrokerPool().getParserPool();
×
444
                                                XMLReader reader = null;
×
445
                                                try {
446
                                                    reader = parserPool.borrowXMLReader();
×
447

448
                                                    final SAXAdapter adapter = new AppendingSAXAdapter(expression, builder);
×
449
                                                    reader.setContentHandler(adapter);
×
450
                                                    reader.setProperty(Namespaces.SAX_LEXICAL_HANDLER, adapter);
×
451
                                                    reader.parse(src);
×
452
                                                } finally {
453
                                                    if (reader != null) {
×
454
                                                        parserPool.returnXMLReader(reader);
×
455
                                                    }
456
                                                }
457
                                            }
458
                                        }
459
                                    } catch (final Exception e) {
×
460
                                        throw new XPathException(this, "Could not parse column of type SQLXML: " + e.getMessage(), e);
×
461
                                    }
×
462
                                } else {
463
                                    //otherwise assume string value
464
                                    final String colValue = rs.getString(i + 1);
1✔
465

466
                                    if (rs.wasNull()) {
1✔
467
                                        // Add a null indicator attribute if the value was SQL Null
468
                                        builder.addAttribute(new QName("null", namespaceUri, namespacePrefix), "true");
1✔
469
                                    } else {
470
                                        if (colValue != null) {
1!
471
                                            builder.characters(colValue);
1✔
472
                                        }
473
                                    }
474
                                }
475

476
                                builder.endElement();
1✔
477
                            }
478
                        }
479

480
                        builder.endElement();
1✔
481
                        rowCount++;
1✔
482
                    }
483
                }
484

485
                // close `result` element
486
                builder.endElement();
1✔
487

488
                // Change the root element count attribute to have the correct value
489
                final ElementImpl docElement = (ElementImpl) builder.getDocument().getDocumentElement();
1✔
490
                final Node count = docElement.getNode().getAttributes().getNamedItem("count");
1✔
491
                if (count != null) {
1!
492
                    count.setNodeValue(String.valueOf(rowCount));
1✔
493
                }
494

495
                builder.endDocument();
1✔
496

497
                return docElement;
1✔
498
            } finally {
499
                // close record set
500
                if (rs != null) {
1!
501
                    try {
502
                        rs.close();
1✔
503
                    } catch (final SQLException se) {
×
504
                        LOG.warn("Unable to close JDBC RecordSet: {}", se.getMessage(), se);
×
505
                    }
1✔
506
                }
507
            }
508
        } finally {
509
            context.popDocumentContext();
1✔
510
        }
511
    }
512

513
    private ElementImpl sqlExceptionAsElement(final SQLException sqle, final String sql,
514
            @Nullable final Element parametersElement) {
515
        context.pushDocumentContext();
1✔
516
        try {
517
            final MemTreeBuilder builder = context.getDocumentBuilder();
1✔
518

519
            builder.startDocument();
1✔
520
            builder.startElement(new QName("exception", NAMESPACE_URI, PREFIX), null);
1✔
521

522
            final boolean recoverable = sqle instanceof SQLRecoverableException;
1✔
523
            builder.addAttribute(new QName("recoverable", null, null), String.valueOf(recoverable));
1✔
524

525
            builder.startElement(new QName("state", NAMESPACE_URI, PREFIX), null);
1✔
526
            builder.characters(sqle.getSQLState());
1✔
527
            builder.endElement();
1✔
528

529
            builder.startElement(new QName("message", NAMESPACE_URI, PREFIX), null);
1✔
530
            final String state = sqle.getMessage();
1✔
531
            if (state != null) {
1!
532
                builder.characters(state);
1✔
533
            }
534
            builder.endElement();
1✔
535

536
            builder.startElement(new QName("stack-trace", NAMESPACE_URI, PREFIX), null);
1✔
537
            try (final UnsynchronizedByteArrayOutputStream bufStackTrace = new UnsynchronizedByteArrayOutputStream();
1✔
538
                 final PrintStream ps = new PrintStream(bufStackTrace)) {
1✔
539
                sqle.printStackTrace(ps);
1✔
540
                builder.characters(bufStackTrace.toString(UTF_8));
1✔
541
            } catch (final IOException e) {
×
542
                LOG.warn("Unable to get stack-trace of JDBC SQLException: {}", e.getMessage(), e);
×
543
            }
1✔
544
            builder.endElement();
1✔
545

546
            builder.startElement(new QName("sql", NAMESPACE_URI, PREFIX), null);
1✔
547
            builder.characters(sql);
1✔
548
            builder.endElement();
1✔
549

550
            if (parametersElement != null) {
1✔
551
                final String ns = parametersElement.getNamespaceURI();
1✔
552
                if (ns != null && ns.equals(NAMESPACE_URI) && parametersElement.getLocalName().equals(PARAMETERS_ELEMENT_NAME)) {
1!
553
                    final NodeList paramElements = parametersElement.getElementsByTagNameNS(NAMESPACE_URI, PARAM_ELEMENT_NAME);
1✔
554

555
                    builder.startElement(new QName(PARAMETERS_ELEMENT_NAME, NAMESPACE_URI, PREFIX), null);
1✔
556

557
                    for (int i = 0; i < paramElements.getLength(); i++) {
1✔
558
                        final Element param = ((Element) paramElements.item(i));
1✔
559
                        final Node valueNode = param.getFirstChild();
1✔
560
                        final String value = valueNode != null ? valueNode.getNodeValue() : null;
1!
561
                        final String type = param.getAttributeNS(NAMESPACE_URI, TYPE_ATTRIBUTE_NAME);
1✔
562

563
                        builder.startElement(new QName(PARAM_ELEMENT_NAME, NAMESPACE_URI, PREFIX), null);
1✔
564
                        builder.addAttribute(new QName(TYPE_ATTRIBUTE_NAME, NAMESPACE_URI, PREFIX), type);
1✔
565
                        builder.characters(value);
1✔
566
                        builder.endElement();
1✔
567
                    }
568

569
                    builder.endElement();
1✔
570
                }
571
            }
572

573
            builder.startElement(new QName("xquery", NAMESPACE_URI, PREFIX), null);
1✔
574
            builder.addAttribute(new QName("line", null, null), String.valueOf(getLine()));
1✔
575
            builder.addAttribute(new QName("column", null, null), String.valueOf(getColumn()));
1✔
576
            builder.endElement();
1✔
577

578
            builder.endElement();
1✔
579
            builder.endDocument();
1✔
580

581
            return (ElementImpl) builder.getDocument().getDocumentElement();
1✔
582
        } finally {
583
            context.popDocumentContext();
1✔
584
        }
585
    }
586

587
    private static FunctionSignature[] functionSignatures(final String name, final String description, final FunctionReturnSequenceType returnType, final FunctionParameterSequenceType[][] variableParamTypes) {
588
        return FunctionDSL.functionSignatures(new QName(name, NAMESPACE_URI, PREFIX), description, returnType, variableParamTypes);
1✔
589
    }
590
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc