• 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

14.43
/extensions/modules/sql/src/main/java/org/exist/xquery/modules/sql/SQLModule.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
package org.exist.xquery.modules.sql;
25

26
import com.zaxxer.hikari.HikariConfig;
27
import com.zaxxer.hikari.HikariDataSource;
28
import org.apache.logging.log4j.LogManager;
29
import org.apache.logging.log4j.Logger;
30

31
import org.exist.xquery.*;
32

33
import java.sql.Connection;
34
import java.sql.SQLException;
35

36
import java.util.List;
37
import java.util.Map;
38
import java.util.Map.Entry;
39
import java.util.Properties;
40
import java.util.concurrent.ConcurrentHashMap;
41
import java.util.regex.Matcher;
42
import java.util.regex.Pattern;
43

44
import org.exist.xquery.modules.ModuleUtils;
45
import org.exist.xquery.modules.ModuleUtils.ContextMapEntryModifier;
46

47
import javax.annotation.Nullable;
48

49
import static org.exist.xquery.FunctionDSL.functionDefs;
50

51
/**
52
 * SQL Module Extension for XQuery.
53
 *
54
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
55
 */
56
public class SQLModule extends AbstractInternalModule {
57

58
    protected final static Logger LOG = LogManager.getLogger(SQLModule.class);
1✔
59
    public final static String NAMESPACE_URI = "http://exist-db.org/xquery/sql";
60
    public final static String PREFIX = "sql";
61
    public final static String INCLUSION_DATE = "2006-09-25";
62
    public final static String RELEASED_IN_VERSION = "eXist-1.2";
63

64
    public static final FunctionDef[] functions = functionDefs(
1✔
65
            functionDefs(GetConnectionFunction.class, GetConnectionFunction.FS_GET_CONNECTION),
1✔
66
            functionDefs(GetConnectionFunction.class, GetConnectionFunction.FS_GET_CONNECTION_FROM_POOL),
1✔
67
            functionDefs(CloseConnectionFunction.class, CloseConnectionFunction.FS_CLOSE_CONNECTION),
1✔
68
            functionDefs(GetJNDIConnectionFunction.class, GetJNDIConnectionFunction.signatures),
1✔
69
            functionDefs(ExecuteFunction.class, ExecuteFunction.FS_EXECUTE),
1✔
70
            functionDefs(PrepareFunction.class, PrepareFunction.signatures)
1✔
71
    );
72

73
    public final static String CONNECTIONS_CONTEXTVAR = "_eXist_sql_connections";
74
    public final static String PREPARED_STATEMENTS_CONTEXTVAR = "_eXist_sql_prepared_statements";
75

76
    private static final Map<String, HikariDataSource> CONNECTION_POOLS = new ConcurrentHashMap<>();
1✔
77
    private static final Pattern POOL_NAME_PATTERN = Pattern.compile("(pool\\.[0-9]+)\\.name");
1✔
78

79
    public SQLModule(final Map<String, List<?>> parameters) {
80
        super(functions, parameters);
×
81

82
        // create any connection pools that are not yet created
83
        Matcher poolNameMatcher = null;
×
84
        for (final Map.Entry<String, List<?>> parameter : parameters.entrySet()) {
×
85
            if (poolNameMatcher == null) {
×
86
                poolNameMatcher = POOL_NAME_PATTERN.matcher(parameter.getKey());
×
87
            }  else {
88
                 poolNameMatcher.reset(parameter.getKey());
×
89
            }
90

91
            if (poolNameMatcher.matches()) {
×
92
                if (parameter.getValue() != null && parameter.getValue().size() == 1) {
×
93
                    final String poolId = poolNameMatcher.group(1);
×
94
                    final String poolName = parameter.getValue().get(0).toString();
×
95
                    if (poolName != null && !poolName.isEmpty()) {
×
96
                        if (!CONNECTION_POOLS.containsKey(poolName)) {
×
97

98
                            final Properties poolProperties = new Properties();;
×
99
                            poolProperties.setProperty("poolName", poolName);
×
100

101
                            final String poolPropertiesPrefix = poolId + ".properties.";
×
102
                            for (final Map.Entry<String, List<?>> poolParameter : parameters.entrySet()) {
×
103
                                if (poolParameter.getKey().startsWith(poolPropertiesPrefix)) {
×
104
                                    if (poolParameter.getValue() != null && poolParameter.getValue().size() == 1) {
×
105
                                        final String propertyName = poolParameter.getKey().replace(poolPropertiesPrefix, "");
×
106
                                        final String propertyValue = poolParameter.getValue().get(0).toString();
×
107
                                        poolProperties.setProperty(propertyName, propertyValue);
×
108
                                    }
109
                                }
110
                            }
×
111

112
                            final HikariConfig hikariConfig = new HikariConfig(poolProperties);
×
113
                            final HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
×
114
                            CONNECTION_POOLS.put(poolName, hikariDataSource);
×
115
                        }
116
                    }
117
                }
118
            }
119
        }
×
120
    }
×
121

122
    @Override
123
    public String getNamespaceURI() {
124
        return NAMESPACE_URI;
×
125
    }
126

127
    @Override
128
    public String getDefaultPrefix() {
129
        return PREFIX;
×
130
    }
131

132
    @Override
133
    public String getDescription() {
134
        return "A module for performing SQL queries against Databases, returning XML representations of the result sets.";
×
135
    }
136

137
    @Override
138
    public String getReleaseVersion() {
139
        return RELEASED_IN_VERSION;
×
140
    }
141

142
    /**
143
     * Gets a Connection Pool.
144
     *
145
     * @param poolName the name of the connection pool.
146
     *
147
     * @return the connection pool, or null if there is no such pool
148
     */
149
    static @Nullable HikariDataSource getPool(final String poolName) {
150
        return CONNECTION_POOLS.get(poolName);
×
151
    }
152

153
    /**
154
     * Retrieves a previously stored Connection from the Context of an XQuery.
155
     *
156
     * @param context The Context of the XQuery containing the Connection
157
     * @param connectionUID The UID of the Connection to retrieve from the Context of the XQuery
158
     * @return the database connection for the UID, or null if there is no such connection.
159
     */
160
    public static @Nullable Connection retrieveConnection(final XQueryContext context, final long connectionUID) {
161
        return ModuleUtils.retrieveObjectFromContextMap(context, SQLModule.CONNECTIONS_CONTEXTVAR, connectionUID);
1✔
162
    }
163

164
    /**
165
     * Stores a Connection in the Context of an XQuery.
166
     *
167
     * @param context The Context of the XQuery to store the Connection in
168
     * @param con The connection to store
169
     * @return A unique ID representing the connection
170
     */
171
    public static long storeConnection(final XQueryContext context, final Connection con) {
172
        return ModuleUtils.storeObjectInContextMap(context, SQLModule.CONNECTIONS_CONTEXTVAR, con);
1✔
173
    }
174

175
    /**
176
     * Removes a Connection from the Context of an XQuery.
177
     *
178
     * @param context The Context of the XQuery to remove the Connection from
179
     * @param connectionUID The UID of the Connection to remove from the Context of the XQuery
180
     * @return the database connection for the UID, or null if there is no such connection.
181
     */
182
    public static @Nullable Connection removeConnection(final XQueryContext context, final long connectionUID) {
183
        return ModuleUtils.removeObjectFromContextMap(context, SQLModule.CONNECTIONS_CONTEXTVAR, connectionUID);
×
184
    }
185

186
    /**
187
     * Retrieves a previously stored PreparedStatement from the Context of an XQuery.
188
     *
189
     * @param context The Context of the XQuery containing the PreparedStatement
190
     * @param preparedStatementUID The UID of the PreparedStatement to retrieve from the Context of the XQuery
191
     * @return the prepared statement for the UID, or null if there is no such prepared statement.
192
     */
193
    public static PreparedStatementWithSQL retrievePreparedStatement(final XQueryContext context, final long preparedStatementUID) {
194
        return ModuleUtils.retrieveObjectFromContextMap(context, SQLModule.PREPARED_STATEMENTS_CONTEXTVAR, preparedStatementUID);
1✔
195
    }
196

197
    /**
198
     * Stores a PreparedStatement in the Context of an XQuery.
199
     *
200
     * @param context The Context of the XQuery to store the PreparedStatement in
201
     * @param stmt preparedStatement The PreparedStatement to store
202
     * @return A unique ID representing the PreparedStatement
203
     */
204
    public static long storePreparedStatement(final XQueryContext context, final PreparedStatementWithSQL stmt) {
205
        return ModuleUtils.storeObjectInContextMap(context, SQLModule.PREPARED_STATEMENTS_CONTEXTVAR, stmt);
1✔
206
    }
207

208
    /**
209
     * Resets the Module Context and closes any DB connections for the XQueryContext.
210
     *
211
     * @param xqueryContext The XQueryContext
212
     */
213
    @Override
214
    public void reset(final XQueryContext xqueryContext, final boolean keepGlobals) {
215
        // reset the module context
216
        super.reset(xqueryContext, keepGlobals);
×
217

218
        // close any open PreparedStatements
219
        closeAllPreparedStatements(xqueryContext);
×
220

221
        // close any open Connections
222
        closeAllConnections(xqueryContext);
×
223
    }
×
224

225
    /**
226
     * Closes all the open DB Connections for the specified XQueryContext.
227
     *
228
     * @param xqueryContext The context to close JDBC Connections for
229
     */
230
    private static void closeAllConnections(final XQueryContext xqueryContext) {
231
        ModuleUtils.modifyContextMap(xqueryContext, SQLModule.CONNECTIONS_CONTEXTVAR, new ContextMapEntryModifier<Connection>() {
×
232

233
            @Override
234
            public void modifyWithoutResult(final Map<Long, Connection> map) {
235
                super.modifyWithoutResult(map);
×
236

237
                // empty the map
238
                map.clear();
×
239
            }
×
240

241
            @Override
242
            public void modifyEntry(final Entry<Long, Connection> entry) {
243
                final Connection con = entry.getValue();
×
244
                try {
245
                    // close the Connection
246
                    con.close();
×
247
                } catch (final SQLException se) {
×
248
                    LOG.warn("Unable to close JDBC Connection: {}", se.getMessage(), se);
×
249
                }
×
250
            }
×
251
        });
252
    }
×
253

254
    /**
255
     * Closes all the open DB PreparedStatements for the specified XQueryContext.
256
     *
257
     * @param xqueryContext The context to close JDBC PreparedStatements for
258
     */
259
    private static void closeAllPreparedStatements(final XQueryContext xqueryContext) {
260
        ModuleUtils.modifyContextMap(xqueryContext, SQLModule.PREPARED_STATEMENTS_CONTEXTVAR, new ContextMapEntryModifier<PreparedStatementWithSQL>() {
×
261

262
            @Override
263
            public void modifyWithoutResult(final Map<Long, PreparedStatementWithSQL> map) {
264
                super.modifyWithoutResult(map);
×
265

266
                // empty the map
267
                map.clear();
×
268
            }
×
269

270
            @Override
271
            public void modifyEntry(final Entry<Long, PreparedStatementWithSQL> entry) {
272
                final PreparedStatementWithSQL stmt = entry.getValue();
×
273
                try {
274
                    // close the PreparedStatement
275
                    stmt.getStmt().close();
×
276
                } catch (SQLException se) {
×
277
                    LOG.warn("Unable to close JDBC PreparedStatement: {}", se.getMessage(), se);
×
278
                }
×
279
            }
×
280
        });
281
    }
×
282
}
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