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

evolvedbinary / elemental / 932

28 Apr 2025 01:10AM UTC coverage: 56.402% (-0.01%) from 56.413%
932

push

circleci

adamretter
[bugfix] Correct release process instructions

28446 of 55846 branches covered (50.94%)

Branch coverage included in aggregate %.

77456 of 131918 relevant lines covered (58.72%)

0.59 hits per line

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

88.75
/exist-core/src/main/java/org/exist/storage/XQueryPool.java
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.storage;
34

35
import java.text.NumberFormat;
36
import java.util.ArrayDeque;
37
import java.util.Deque;
38

39
import com.github.benmanes.caffeine.cache.Cache;
40
import com.github.benmanes.caffeine.cache.Caffeine;
41
import net.jcip.annotations.ThreadSafe;
42
import org.apache.logging.log4j.LogManager;
43
import org.apache.logging.log4j.Logger;
44
import org.exist.security.Permission;
45
import org.exist.security.PermissionDeniedException;
46
import org.exist.source.DBSource;
47
import org.exist.source.Source;
48
import org.exist.util.Configuration;
49
import org.exist.util.Holder;
50
import org.exist.xquery.*;
51

52
/**
53
 * Global pool for compiled XQuery expressions.
54
 *
55
 * Expressions are stored and retrieved from the pool by comparing the
56
 * {@link org.exist.source.Source} objects from which they were created.
57
 *
58
 * For each XQuery, a maximum of {@link #DEFAULT_MAX_QUERY_STACK_SIZE} compiled
59
 * expressions are kept in the pool.
60
 *
61
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
62
 */
63
@ThreadSafe
64
public class XQueryPool implements BrokerPoolService {
1✔
65

66
    private static final Logger LOG = LogManager.getLogger(XQueryPool.class);
1✔
67

68
    public static final String CONFIGURATION_ELEMENT_NAME = "query-pool";
69
    public static final String MAX_STACK_SIZE_ATTRIBUTE = "max-stack-size";
70
    public static final String POOL_SIZE_ATTTRIBUTE = "size";
71

72
    public static final String PROPERTY_MAX_STACK_SIZE = "db-connection.query-pool.max-stack-size";
73
    public static final String PROPERTY_POOL_SIZE = "db-connection.query-pool.size";
74

75
    private static final int DEFAULT_MAX_POOL_SIZE = 128;
76
    private static final int DEFAULT_MAX_QUERY_STACK_SIZE = 64;
1✔
77

78
    private int maxPoolSize = DEFAULT_MAX_POOL_SIZE;
1✔
79
    private int maxQueryStackSize = DEFAULT_MAX_QUERY_STACK_SIZE;
1✔
80

81
    /**
82
     * Source -> Deque of compiled Queries
83
     */
84
    private Cache<Source, Deque<CompiledXQuery>> cache;
85

86
    @Override
87
    public void configure(final Configuration configuration) {
88
        final Integer maxStSz = (Integer) configuration.getProperty(PROPERTY_MAX_STACK_SIZE);
1✔
89
        final Integer maxPoolSz = (Integer) configuration.getProperty(PROPERTY_POOL_SIZE);
1✔
90
        final NumberFormat nf = NumberFormat.getNumberInstance();
1✔
91

92
        if (maxPoolSz != null) {
1✔
93
            this.maxPoolSize = maxPoolSz;
1✔
94
        } else {
1✔
95
            this.maxPoolSize = DEFAULT_MAX_POOL_SIZE;
1✔
96
        }
97

98
        if (maxStSz != null) {
1✔
99
            this.maxQueryStackSize = maxStSz;
1✔
100
        } else {
1✔
101
            this.maxQueryStackSize = DEFAULT_MAX_QUERY_STACK_SIZE;
1✔
102
        }
103

104
        this.cache = Caffeine.newBuilder()
1✔
105
                .maximumSize(maxPoolSize)
1✔
106
                .build();
1✔
107

108
        LOG.info("QueryPool: size = {}; maxQueryStackSize = {}", nf.format(maxPoolSize), nf.format(maxQueryStackSize));
1✔
109
    }
1✔
110

111
    /**
112
     * Returns a compiled XQuery to the XQuery pool.
113
     *
114
     * @param source The source of the compiled XQuery.
115
     * @param compiledXQuery The compiled XQuery to add to the XQuery pool.
116
     */
117
    public void returnCompiledXQuery(final Source source, final CompiledXQuery compiledXQuery) {
118
        if (compiledXQuery == null) {
1!
119
            return;
×
120
        }
121

122
        cache.asMap().compute(source, (key, value) -> {
1✔
123
            final Deque<CompiledXQuery> deque;
124
            if (value != null) {
1✔
125
                deque = value;
1✔
126
            } else {
1✔
127
                deque = new ArrayDeque<>(maxQueryStackSize);
1✔
128
            }
129

130
            deque.offerFirst(compiledXQuery);
1✔
131

132
            return deque;
1✔
133
        });
134
    }
1✔
135

136
    /**
137
     * Borrows a compiled XQuery from the XQuery pool.
138
     *
139
     * @param broker A database broker.
140
     * @param source The source identifying the XQuery to borrow.
141
     *
142
     * @return The compiled XQuery identified by the source, or null if
143
     *     there is no valid compiled representation in the XQuery pool.
144
     *
145
     * @throws PermissionDeniedException if the caller does not have execute
146
     *     permission for the compiled XQuery.
147
     */
148
    public CompiledXQuery borrowCompiledXQuery(final DBBroker broker, final Source source)
149
            throws PermissionDeniedException {
150
        if (broker == null || source == null) {
1!
151
            return null;
×
152
        }
153

154
        // this will be set to non-null if we can borrow a query... allows us to escape the lamba, see https://github.com/ben-manes/caffeine/issues/192#issuecomment-337365618
155
        final Holder<CompiledXQuery> borrowedCompiledQuery = new Holder<>();
1✔
156

157
        // get (compute by checking validity) the stack of compiled XQuerys for the source
158
        final Deque<CompiledXQuery> deque = cache.asMap().computeIfPresent(source, (key, value) -> {
1✔
159
            final CompiledXQuery firstCompiledXQuery = value.pollFirst();
1✔
160
            if (firstCompiledXQuery == null) {
1!
161
                // deque is empty, returning null will remove the entry from the cache
162
                return null;
×
163
            }
164

165
            if (!isCompiledQueryValid(firstCompiledXQuery)) {
1✔
166
                if (LOG.isDebugEnabled()) {
1!
167
                    LOG.debug("{} is invalid, removing from XQuery Pool...", source.pathOrShortIdentifier());
×
168
                }
169

170
                // query is invalid, returning null will remove the entry from the cache
171
                return null;
1✔
172
            }
173

174
            // escape the result from the lambda
175
            borrowedCompiledQuery.value = firstCompiledXQuery;
1✔
176

177
            // query is ok, preserve the tail of the deque
178
            return value;
1✔
179
        });
180

181
        if (deque == null) {
1✔
182
            return null;
1✔
183
        }
184

185
        //check execution permission
186
        if (source instanceof DBSource) {
1✔
187
            ((DBSource) source).validate(Permission.EXECUTE);
1✔
188
        }
189

190
        return borrowedCompiledQuery.value;
1✔
191
    }
192

193
    /**
194
     * Determines if a compiled XQuery is still valid.
195
     *
196
     * @param broker the database broker
197
     * @param source the source of the query
198
     * @param compiledXQuery the compiled query
199
     *
200
     * @return true if the compiled query is still valid, false otherwise.
201
     */
202
    private static boolean isCompiledQueryValid(final CompiledXQuery compiledXQuery) {
203
        final Source cachedSource = compiledXQuery.getSource();
1✔
204
        final Source.Validity validity = cachedSource.isValid();
1✔
205

206
        if (validity == Source.Validity.INVALID) {
1✔
207
            return false;    // returning false will remove the entry from the cache
1✔
208
        }
209

210
        // the compiled query is no longer valid if one of the imported
211
        // modules may have changed
212
        return compiledXQuery.isValid();
1✔
213
    }
214

215
    /**
216
     * Removes all entries from the XQuery Pool.
217
     */
218
    public void clear() {
219
        cache.invalidateAll();
1✔
220
    }
1✔
221
}
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