Skip to content

Commit d26adcc

Browse files
committed
[refactor] Split XQueryUtil into compile and execute phases
1 parent da53b9a commit d26adcc

9 files changed

Lines changed: 335 additions & 287 deletions

File tree

exist-core/src/main/java/org/exist/http/RESTServer.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,7 +1389,7 @@ protected void search(final DBBroker broker, final Txn transaction, final String
13891389
try (final XQueryUtil.QueryResult queryResult = XQueryUtil.query(broker, source, true, contextSequence, outputProperties, setupXqueryContextPreCompilation, setupXqueryContextPreExecution, setupXqueryContextPostExecution)) {
13901390

13911391
// special header to indicate that the query is not returned from cache
1392-
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.QueryResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
1392+
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.CompilationResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
13931393

13941394
if (LOG.isDebugEnabled()) {
13951395
LOG.debug("Found {} in {}ms.", queryResult.result.getItemCount(), queryResult.executionTime);
@@ -1604,7 +1604,7 @@ private void executeXQuery(final DBBroker broker, final Txn transaction, final D
16041604
try (final XQueryUtil.QueryResult queryResult = XQueryUtil.query(broker, source, true, null, outputProperties, setupXqueryContextPreCompilation, setupXqueryContextPreExecution, setupXqueryContextPostExecution)) {
16051605

16061606
// Special header to indicate whether the compiled query is returned from the cache
1607-
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.QueryResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
1607+
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.CompilationResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
16081608

16091609
final boolean wrap = "yes".equals(outputProperties.getProperty("_wrap"));
16101610
writeResults(response, broker, transaction, queryResult, -1, 1, false, outputProperties, wrap);
@@ -1663,7 +1663,7 @@ private void executeXProc(final DBBroker broker, final Txn transaction, final Do
16631663
try (final XQueryUtil.QueryResult queryResult = XQueryUtil.query(broker, source, true, null, null, setupXqueryContextPreCompilation, setupXqueryContextPreExecution, setupXqueryContextPostExecution)) {
16641664

16651665
// special header to indicate that the query is not returned from cache
1666-
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.QueryResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
1666+
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.CompilationResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
16671667

16681668
writeResults(response, broker, transaction, queryResult, -1, 1, false, outputProperties, false);
16691669
} catch (final IOException e) {

exist-core/src/main/java/org/exist/http/servlets/RedirectorServlet.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ private XQueryUtil.QueryResult executeQuery(final Source source, final RequestWr
302302
final XQueryUtil.QueryResult queryResult = XQueryUtil.query(broker, source, true, null, outputProperties, setupXqueryContextPreCompilation, null, null);
303303

304304
// special header to indicate that the query is not returned from cache
305-
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.QueryResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
305+
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.CompilationResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
306306

307307
return queryResult;
308308
}

exist-core/src/main/java/org/exist/http/servlets/XQueryServlet.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ protected void process(HttpServletRequest request, HttpServletResponse response)
508508
try (final XQueryUtil.QueryResult queryResult = XQueryUtil.query(broker, source, true, null, outputProperties, null, setupXqueryContextPreExecution, setupXqueryContextPostExecution)) {
509509

510510
// special header to indicate that the query is not returned from cache
511-
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.QueryResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
511+
response.setHeader(XQUERY_CACHED_RESPONSE_HEADER, queryResult.compilationTime == XQueryUtil.CompilationResult.RETRIEVED_CACHED_COMPILED_QUERY ? "true" : "false");
512512

513513
final String mediaType = outputProperties.getProperty(OutputKeys.MEDIA_TYPE);
514514
if (mediaType != null) {

exist-core/src/main/java/org/exist/xquery/XQueryUtil.java

Lines changed: 128 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
import com.evolvedbinary.j8fu.function.BiConsumerE;
2424
import com.evolvedbinary.j8fu.function.ConsumerE;
25-
import com.evolvedbinary.j8fu.function.Function2E;
2625
import org.exist.security.PermissionDeniedException;
2726
import org.exist.source.Source;
2827
import org.exist.storage.BrokerPool;
@@ -34,10 +33,17 @@
3433
import java.io.IOException;
3534
import java.util.Properties;
3635

36+
/**
37+
* Utility to make compiling and executing XQuery simpler
38+
* and safer by ensuring cleanup when the returned object is closed.
39+
*
40+
* @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
41+
*/
3742
public class XQueryUtil {
3843

3944
/**
4045
* Execute an XPath or XQuery.
46+
* Performs both the compile and execute steps.
4147
*
4248
* @param broker the database broker.
4349
* @param source the source of the query.
@@ -60,6 +66,7 @@ public static XQueryUtil.QueryResult query(final DBBroker broker, final Source s
6066

6167
/**
6268
* Execute an XPath or XQuery.
69+
* Performs both the compile and execute steps.
6370
*
6471
* @param broker the database broker.
6572
* @param source the source of the query.
@@ -78,6 +85,26 @@ public static XQueryUtil.QueryResult query(final DBBroker broker, final Source s
7885
* @throws XPathException if the query raises an error.
7986
*/
8087
public static XQueryUtil.QueryResult query(final DBBroker broker, final Source source, final boolean isXPointer, final boolean cacheQuery, @Nullable final Sequence contextSequence, @Nullable final Properties outputProperties, @Nullable final ConsumerE<XQueryContext, XPathException> preCompilationContext, @Nullable final ConsumerE<XQueryContext, XPathException> preExecutionContext, @Nullable final BiConsumerE<XQueryContext, QueryResult, XPathException> postExecutionContext) throws XPathException, PermissionDeniedException, IOException {
88+
final CompilationResult compilationResult = compile(broker, source, isXPointer, cacheQuery, preCompilationContext);
89+
return execute(broker, compilationResult, contextSequence, outputProperties, preExecutionContext, postExecutionContext);
90+
}
91+
92+
/**
93+
* Compile an XPath or XQuery.
94+
*
95+
* @param broker the database broker.
96+
* @param source the source of the query.
97+
* @param isXPointer true if the query is an XPointer, false otherwise.
98+
* @param cacheQuery true if the compiled query should be cached, false otherwise.
99+
* @param preCompilationContext access to the XQuery Context pre-compilation, or null if access is not required.
100+
*
101+
* @return the result of the compilation and the compilation time.
102+
*
103+
* @throws IOException if the source of the query cannot be read.
104+
* @throws PermissionDeniedException if the calling user does not have permission to execute the query.
105+
* @throws XPathException if the query raises an error.
106+
*/
107+
public static CompilationResult compile(final DBBroker broker, final Source source, final boolean isXPointer, final boolean cacheQuery, @Nullable final ConsumerE<XQueryContext, XPathException> preCompilationContext) throws XPathException, PermissionDeniedException, IOException {
81108
final BrokerPool brokerPool = broker.getBrokerPool();
82109
final XQuery xquery = brokerPool.getXQueryService();
83110

@@ -104,7 +131,7 @@ public static XQueryUtil.QueryResult query(final DBBroker broker, final Source s
104131
compiledXquery.getContext().updateContext(xqueryContext);
105132
xqueryContext.getWatchDog().reset();
106133

107-
compilationTime = XQueryUtil.QueryResult.RETRIEVED_CACHED_COMPILED_QUERY;
134+
compilationTime = XQueryUtil.CompilationResult.RETRIEVED_CACHED_COMPILED_QUERY;
108135

109136
} else {
110137
xqueryContext = new XQueryContext(brokerPool);
@@ -118,146 +145,162 @@ public static XQueryUtil.QueryResult query(final DBBroker broker, final Source s
118145
compilationTime = System.currentTimeMillis() - compilationStart;
119146
}
120147

148+
return new CompilationResult(source, xqueryPool, compiledXquery, xqueryContext, compilationTime);
149+
150+
} catch (final XPathException | PermissionDeniedException | IOException e) {
151+
// Make sure to clean-up in case of an exception!
152+
if (xqueryContext != null) {
153+
xqueryContext.runCleanupTasks();
154+
}
155+
156+
if (xqueryPool != null && compiledXquery != null) {
157+
xqueryPool.returnCompiledXQuery(source, compiledXquery);
158+
}
159+
160+
throw e;
161+
}
162+
}
163+
164+
/**
165+
* Execute a compiled XPath or XQuery.
166+
*
167+
* @param broker the database broker.
168+
* @param compilationResult the compiled query.
169+
* @param contextSequence the context sequence to use when executing the query, or null if there is no context sequence.
170+
* @param outputProperties any output properties, or null.
171+
* @param preExecutionContext access to the XQuery Context pre-execution, or null if access is not required.
172+
* @param postExecutionContext access to the XQuery Context post-execution, or null if access is not required.
173+
*
174+
* @return the result of the query and compilation and execution times.
175+
*
176+
* @throws PermissionDeniedException if the calling user does not have permission to execute the query.
177+
* @throws XPathException if the query raises an error.
178+
*/
179+
public static QueryResult execute(final DBBroker broker, final CompilationResult compilationResult, @Nullable final Sequence contextSequence, @Nullable final Properties outputProperties, @Nullable final ConsumerE<XQueryContext, XPathException> preExecutionContext, @Nullable final BiConsumerE<XQueryContext, QueryResult, XPathException> postExecutionContext) throws XPathException, PermissionDeniedException {
180+
final BrokerPool brokerPool = broker.getBrokerPool();
181+
final XQuery xquery = brokerPool.getXQueryService();
182+
183+
try {
121184
if (preExecutionContext != null) {
122-
preExecutionContext.accept(xqueryContext);
185+
preExecutionContext.accept(compilationResult.xqueryContext);
123186
}
124187

125188
final long executionStart = System.currentTimeMillis();
126-
final Sequence result = xquery.execute(broker, compiledXquery, null, contextSequence, outputProperties, true);
189+
final Sequence result = xquery.execute(broker, compilationResult.compiledXquery, null, contextSequence, outputProperties, true);
127190
final long executionTime = System.currentTimeMillis() - executionStart;
128191

129-
final QueryResult queryResult = new XQueryUtil.QueryResult(source, xqueryPool, compiledXquery, xqueryContext, compilationTime, executionTime, result);
192+
final QueryResult queryResult = new XQueryUtil.QueryResult(compilationResult, executionTime, result);
130193

131194
if (postExecutionContext != null) {
132-
postExecutionContext.accept(xqueryContext, queryResult);
195+
postExecutionContext.accept(compilationResult.xqueryContext, queryResult);
133196
}
134197

135198
return queryResult;
136199

137-
} catch (final XPathException | PermissionDeniedException | IOException e) {
200+
} catch (final XPathException | PermissionDeniedException e) {
138201
// Make sure to clean-up in case of an exception!
139-
if (xqueryContext != null) {
140-
xqueryContext.runCleanupTasks();
141-
}
202+
compilationResult.xqueryContext.runCleanupTasks();
142203

143-
if (xqueryPool != null && compiledXquery != null) {
144-
xqueryPool.returnCompiledXQuery(source, compiledXquery);
204+
if (compilationResult.xqueryPool != null) {
205+
compilationResult.xqueryPool.returnCompiledXQuery(compilationResult.source, compilationResult.compiledXquery);
145206
}
146207

147208
throw e;
148209
}
149210
}
150211

151-
public static class QueryResult implements AutoCloseable {
212+
public static class CompilationResult implements AutoCloseable {
152213
/**
153214
* Indicates that the query did not need to be compiled as a cached version was available.
154215
*/
155216
public static final int RETRIEVED_CACHED_COMPILED_QUERY = -1;
156217

157218
private final Source source;
158219
private @Nullable final XQueryPool xqueryPool;
159-
private @Nullable final CompiledXQuery compiledXquery;
220+
private final CompiledXQuery compiledXquery;
160221
private final XQueryContext xqueryContext;
222+
private boolean closed = false;
161223

162224
public final long compilationTime;
163-
public final long executionTime;
164-
public final Sequence result;
165225

166-
public QueryResult(final Source source, @Nullable final XQueryPool xqueryPool, @Nullable final CompiledXQuery compiledXquery, final XQueryContext xqueryContext, final long compilationTime, final long executionTime, final Sequence result) {
226+
public CompilationResult(final Source source, @Nullable final XQueryPool xqueryPool, final CompiledXQuery compiledXquery, final XQueryContext xqueryContext, final long compilationTime) {
167227
this.source = source;
168228
this.xqueryPool = xqueryPool;
169229
this.compiledXquery = compiledXquery;
170230
this.xqueryContext = xqueryContext;
171-
172231
this.compilationTime = compilationTime;
173-
this.executionTime = executionTime;
174-
this.result = result;
175232
}
176233

177234
@Override
178235
public void close() {
236+
if (closed) {
237+
return;
238+
}
239+
179240
// NOTE(AR) Only when the user has finished with the Query Result i.e. {@link #result}, can we then clean-up any associated resources.
180241
xqueryContext.runCleanupTasks();
181242

182243
// Once we have cleaned-up if the query should be cached we return it to the query pool.
183244
if (xqueryPool != null && compiledXquery != null) {
184245
xqueryPool.returnCompiledXQuery(source, compiledXquery);
185246
}
247+
248+
closed = true;
186249
}
250+
187251
}
188252

189-
/**
190-
* Compile a query and perform an operation with the compiled query.
191-
*
192-
* @param <T> the type of the result of the operation.
193-
*
194-
* @param broker the database broker.
195-
* @param source the source of the query.
196-
*
197-
* @param op the operation to perform with the compiled query.
198-
*
199-
* @return the result of the operation.
200-
*/
201-
public static <T> T withCompiledQuery(final DBBroker broker, final Source source, final Function2E<CompiledXQuery, T, XPathException, PermissionDeniedException> op) throws XPathException, PermissionDeniedException, IOException {
202-
final BrokerPool pool = broker.getBrokerPool();
203-
final XQuery xqueryService = pool.getXQueryService();
204-
final XQueryPool xqueryPool = pool.getXQueryPool();
205-
final CompiledXQuery compiledQuery = compileQuery(broker, xqueryService, xqueryPool, source);
206-
try {
207-
return op.apply(compiledQuery);
208-
} finally {
209-
if (compiledQuery != null) {
210-
if (compiledQuery.getContext() != null) {
211-
compiledQuery.getContext().runCleanupTasks();
212-
}
213-
xqueryPool.returnCompiledXQuery(source, compiledQuery);
214-
}
253+
public static class QueryResult implements AutoCloseable {
254+
private final Source source;
255+
private @Nullable final XQueryPool xqueryPool;
256+
private @Nullable final CompiledXQuery compiledXquery;
257+
private final XQueryContext xqueryContext;
258+
private boolean closed = false;
259+
260+
public final long compilationTime;
261+
public final long executionTime;
262+
public final Sequence result;
263+
264+
public QueryResult(final Source source, @Nullable final XQueryPool xqueryPool, @Nullable final CompiledXQuery compiledXquery, final XQueryContext xqueryContext, final long compilationTime, final long executionTime, final Sequence result) {
265+
this.source = source;
266+
this.xqueryPool = xqueryPool;
267+
this.compiledXquery = compiledXquery;
268+
this.xqueryContext = xqueryContext;
269+
270+
this.compilationTime = compilationTime;
271+
this.executionTime = executionTime;
272+
this.result = result;
215273
}
216-
}
217274

218-
private static CompiledXQuery compileQuery(final DBBroker broker, final XQuery xqueryService, final XQueryPool xqueryPool, final Source query) throws PermissionDeniedException, XPathException, IOException {
219-
@Nullable CompiledXQuery compiled = null;
220-
@Nullable XQueryContext context = null;
221-
try {
222-
compiled = xqueryPool.borrowCompiledXQuery(broker, query);
223-
if (compiled == null) {
224-
context = new XQueryContext(broker.getBrokerPool());
225-
} else {
226-
context = compiled.getContext();
227-
context.prepareForReuse();
228-
}
275+
private QueryResult(final CompilationResult compilationResult, final long executionTime, final Sequence result) {
276+
this.source = compilationResult.source;
277+
this.xqueryPool = compilationResult.xqueryPool;
278+
this.compiledXquery = compilationResult.compiledXquery;
279+
this.xqueryContext = compilationResult.xqueryContext;
229280

230-
if (compiled == null) {
231-
compiled = xqueryService.compile(context, query);
232-
} else {
233-
compiled.getContext().updateContext(context);
234-
context.getWatchDog().reset();
235-
}
281+
this.compilationTime = compilationResult.compilationTime;
282+
this.executionTime = executionTime;
283+
this.result = result;
236284

237-
return compiled;
285+
// transfer ownership of the resources owned by compilationResult to this class
286+
compilationResult.closed = true;
287+
}
238288

239-
} catch (final PermissionDeniedException | XPathException | IOException e) {
240-
if (context != null) {
241-
context.runCleanupTasks();
289+
@Override
290+
public void close() {
291+
if (closed) {
292+
return;
242293
}
243-
if (compiled != null) {
244-
xqueryPool.returnCompiledXQuery(query, compiled);
294+
295+
// NOTE(AR) Only when the user has finished with the Query Result i.e. {@link #result}, can we then clean-up any associated resources.
296+
xqueryContext.runCleanupTasks();
297+
298+
// Once we have cleaned-up if the query should be cached we return it to the query pool.
299+
if (xqueryPool != null && compiledXquery != null) {
300+
xqueryPool.returnCompiledXQuery(source, compiledXquery);
245301
}
246-
throw e;
247-
}
248-
}
249302

250-
/**
251-
* Execute a compiled query.
252-
*
253-
* @param broker the database broker.
254-
* @param compiledXQuery the compiled query.
255-
*
256-
* @return the result sequence.
257-
*/
258-
public static Sequence executeQuery(final DBBroker broker, final CompiledXQuery compiledXQuery) throws PermissionDeniedException, XPathException {
259-
final BrokerPool pool = broker.getBrokerPool();
260-
final XQuery xqueryService = pool.getXQueryService();
261-
return xqueryService.execute(broker, compiledXQuery, null, null, null, true);
303+
closed = true;
304+
}
262305
}
263306
}

0 commit comments

Comments
 (0)