Skip to content

Commit 1b0601a

Browse files
committed
[feature] Add sql:close-connection#1 function to allow a user to explicitly close a SQL connection earlier to save resources
1 parent 7473d65 commit 1b0601a

File tree

4 files changed

+148
-0
lines changed

4 files changed

+148
-0
lines changed

extensions/modules/sql/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
<header>${project.parent.relativePath}/LGPL-21-license.template.txt</header>
139139
<excludes>
140140
<exclude>src/main/java/org/exist/xquery/modules/sql/GetConnectionFunction.java</exclude>
141+
<exclude>src/main/java/org/exist/xquery/modules/sql/CloseConnectionFunction.java</exclude>
141142
<exclude>src/main/java/org/exist/xquery/modules/sql/SQLModule.java</exclude>
142143
<exclude>src/test/resources/jndi.properties</exclude>
143144
<exclude>src/test/java/org/exist/xquery/modules/sql/ConnectionIT.java</exclude>
@@ -153,6 +154,7 @@
153154
<header>${project.parent.relativePath}/FDB-backport-LGPL-21-ONLY-license.template.txt</header>
154155
<includes>
155156
<include>src/main/java/org/exist/xquery/modules/sql/GetConnectionFunction.java</include>
157+
<include>src/main/java/org/exist/xquery/modules/sql/CloseConnectionFunction.java</include>
156158
<include>src/main/java/org/exist/xquery/modules/sql/SQLModule.java</include>
157159
<include>src/test/resources/jndi.properties</include>
158160
<include>src/test/java/org/exist/xquery/modules/sql/ConnectionIT.java</include>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright (C) 2014, Evolved Binary Ltd
3+
*
4+
* This file was originally ported from FusionDB to eXist-db by
5+
* Evolved Binary, for the benefit of the eXist-db Open Source community.
6+
* Only the ported code as it appears in this file, at the time that
7+
* it was contributed to eXist-db, was re-licensed under The GNU
8+
* Lesser General Public License v2.1 only for use in eXist-db.
9+
*
10+
* This license grant applies only to a snapshot of the code as it
11+
* appeared when ported, it does not offer or infer any rights to either
12+
* updates of this source code or access to the original source code.
13+
*
14+
* The GNU Lesser General Public License v2.1 only license follows.
15+
*
16+
* ---------------------------------------------------------------------
17+
*
18+
* Copyright (C) 2014, Evolved Binary Ltd
19+
*
20+
* This library is free software; you can redistribute it and/or
21+
* modify it under the terms of the GNU Lesser General Public
22+
* License as published by the Free Software Foundation; version 2.1.
23+
*
24+
* This library is distributed in the hope that it will be useful,
25+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
26+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27+
* Lesser General Public License for more details.
28+
*
29+
* You should have received a copy of the GNU Lesser General Public
30+
* License along with this library; if not, write to the Free Software
31+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
32+
*/
33+
package org.exist.xquery.modules.sql;
34+
35+
import org.apache.logging.log4j.LogManager;
36+
import org.apache.logging.log4j.Logger;
37+
38+
import org.exist.xquery.BasicFunction;
39+
import org.exist.xquery.FunctionSignature;
40+
import org.exist.xquery.XPathException;
41+
import org.exist.xquery.XQueryContext;
42+
import org.exist.xquery.value.*;
43+
44+
import java.sql.Connection;
45+
import java.sql.SQLException;
46+
47+
import static org.exist.xquery.FunctionDSL.*;
48+
import static org.exist.xquery.modules.sql.SQLModule.functionSignature;
49+
50+
51+
/**
52+
* SQL Module Extension function for XQuery to explicitly close a connection.
53+
*
54+
* @author <a href="mailto:[email protected]">Adam Retter</a>
55+
*/
56+
public class CloseConnectionFunction extends BasicFunction {
57+
58+
private static final Logger LOGGER = LogManager.getLogger(GetConnectionFunction.class);
59+
60+
private static final String FN_CLOSE_CONNECTION = "close-connection";
61+
public static final FunctionSignature FS_CLOSE_CONNECTION = functionSignature(
62+
FN_CLOSE_CONNECTION,
63+
"Closes a connection to a SQL Database, or if the connection was taken from a connection pool then it is returned to the pool",
64+
returns(Type.BOOLEAN, "true if the connection was closed, false if there was no such connection"),
65+
param("connection-handle", Type.LONG, "an xs:long representing the connection handle")
66+
);
67+
68+
public CloseConnectionFunction(final XQueryContext context, final FunctionSignature signature) {
69+
super(context, signature);
70+
}
71+
72+
/**
73+
* Evaluate the call to the xquery close-connection().
74+
*
75+
* @param args arguments from the close-connection() function call
76+
* @param contextSequence the Context Sequence to operate on (not used here internally!)
77+
*
78+
* @return An empty sequence
79+
*
80+
* @throws XPathException if an error occurs.
81+
*/
82+
@Override
83+
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
84+
final IntegerValue connectionHandle = (IntegerValue) args[0].itemAt(0);
85+
final long connectionUid = connectionHandle.toJavaObject(long.class);
86+
87+
final Connection connection = SQLModule.removeConnection(context, connectionUid);
88+
if (connection == null) {
89+
return BooleanValue.FALSE;
90+
}
91+
92+
try {
93+
if (connection.isClosed()) {
94+
LOGGER.warn("sql:close-connection() Cannot close connection with handle: {}, as it is already closed!", connectionUid);
95+
return BooleanValue.FALSE;
96+
}
97+
98+
connection.close();
99+
return BooleanValue.TRUE;
100+
101+
} catch (final SQLException e) {
102+
throw new XPathException(this, "Unable to close connection with handle: " + connectionUid + ". " + e.getMessage());
103+
}
104+
}
105+
}

extensions/modules/sql/src/main/java/org/exist/xquery/modules/sql/SQLModule.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public class SQLModule extends AbstractInternalModule {
7676
public static final FunctionDef[] functions = functionDefs(
7777
functionDefs(GetConnectionFunction.class, GetConnectionFunction.FS_GET_CONNECTION),
7878
functionDefs(GetConnectionFunction.class, GetConnectionFunction.FS_GET_CONNECTION_FROM_POOL),
79+
functionDefs(CloseConnectionFunction.class, CloseConnectionFunction.FS_CLOSE_CONNECTION),
7980
functionDefs(GetJNDIConnectionFunction.class, GetJNDIConnectionFunction.signatures),
8081
functionDefs(ExecuteFunction.class, ExecuteFunction.FS_EXECUTE),
8182
functionDefs(PrepareFunction.class, PrepareFunction.signatures)
@@ -191,6 +192,17 @@ public static long storeConnection(final XQueryContext context, final Connection
191192
return ModuleUtils.storeObjectInContextMap(context, SQLModule.CONNECTIONS_CONTEXTVAR, con);
192193
}
193194

195+
/**
196+
* Removes a Connection from the Context of an XQuery.
197+
*
198+
* @param context The Context of the XQuery to remove the Connection from
199+
* @param connectionUID The UID of the Connection to remove from the Context of the XQuery
200+
* @return the database connection for the UID, or null if there is no such connection.
201+
*/
202+
public static @Nullable Connection removeConnection(final XQueryContext context, final long connectionUID) {
203+
return ModuleUtils.removeObjectFromContextMap(context, SQLModule.CONNECTIONS_CONTEXTVAR, connectionUID);
204+
}
205+
194206
/**
195207
* Retrieves a previously stored PreparedStatement from the Context of an XQuery.
196208
*

extensions/modules/sql/src/test/java/org/exist/xquery/modules/sql/ConnectionIT.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,35 @@ public void getConnectionFromPoolIsAutomaticallyClosed() throws EXistException,
181181
}
182182
}
183183

184+
@Test
185+
public void getConnectionCanBeExplicitlyClosed() throws EXistException, XPathException, PermissionDeniedException, IOException {
186+
final String query =
187+
"import module namespace sql = \"http://exist-db.org/xquery/sql\";\n" +
188+
"let $conn := sql:get-connection(\"" + h2Database.getDriverClass().getName() + "\", \"" + h2Database.getUrl() + "\", \"" + h2Database.getUser() + "\", \"" + h2Database.getPassword() + "\")\n" +
189+
"return sql:close-connection($conn)";
190+
191+
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
192+
final Source source = new StringSource(query);
193+
try (final DBBroker broker = pool.getBroker();
194+
final Txn transaction = pool.getTransactionManager().beginTransaction()) {
195+
196+
// execute query
197+
final Tuple2<XQueryContext, Boolean> contextAndResult = withCompiledQuery(broker, source, compiledXQuery -> {
198+
final Sequence result = executeQuery(broker, compiledXQuery);
199+
return Tuple(compiledXQuery.getContext(), result.itemAt(0).toJavaObject(boolean.class));
200+
});
201+
202+
// check that the handle for the sql connection was closed
203+
assertTrue(contextAndResult._2);
204+
205+
// check the connections were closed
206+
final int connectionsCount = ModuleUtils.readContextMap(contextAndResult._1, SQLModule.CONNECTIONS_CONTEXTVAR, Map::size);
207+
assertEquals(0, connectionsCount);
208+
209+
transaction.commit();
210+
}
211+
}
212+
184213
private Sequence executeQuery(final DBBroker broker, final CompiledXQuery compiledXQuery) throws PermissionDeniedException, XPathException {
185214
final BrokerPool pool = broker.getBrokerPool();
186215
final XQuery xqueryService = pool.getXQueryService();

0 commit comments

Comments
 (0)