• 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

47.34
/extensions/indexes/spatial/src/main/java/org/exist/indexing/spatial/GMLHSQLIndex.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.indexing.spatial;
50

51
import java.io.IOException;
52
import java.io.OutputStream;
53
import java.nio.file.Files;
54
import java.nio.file.Path;
55
import java.sql.Connection;
56
import java.sql.DriverManager;
57
import java.sql.ResultSet;
58
import java.sql.SQLException;
59
import java.sql.Statement;
60
import java.util.List;
61

62
import org.apache.logging.log4j.LogManager;
63
import org.apache.logging.log4j.Logger;
64
import org.exist.backup.RawDataBackup;
65
import org.exist.indexing.IndexWorker;
66
import org.exist.indexing.RawBackupSupport;
67
import org.exist.storage.BrokerPool;
68
import org.exist.storage.DBBroker;
69
import org.exist.storage.btree.DBException;
70
import org.exist.util.DatabaseConfigurationException;
71
import org.exist.util.FileUtils;
72
import org.w3c.dom.Element;
73

74
/**
75
 * @author <a href="mailto:pierrick.brihaye@free.fr">Pierrick Brihaye</a>
76
 */
77
public class GMLHSQLIndex extends AbstractGMLJDBCIndex implements RawBackupSupport {
1✔
78

79
    private final static Logger LOG = LogManager.getLogger(GMLHSQLIndex.class);
1✔
80

81
    public static String db_file_name_prefix = "spatial_index";
1✔
82
    //Keep this upper case ;-)
83
    public static String TABLE_NAME = "SPATIAL_INDEX_V1";
1✔
84
    private DBBroker connectionOwner = null;
1✔
85
    private long connectionTimeout = 100000L;
1✔
86
    
87
    @Override
88
    public void configure(BrokerPool pool, Path dataDir, Element config) throws DatabaseConfigurationException {
89
        super.configure(pool, dataDir, config);
1✔
90
        String param = config.getAttribute("connectionTimeout");
1✔
91
        if (!param.isEmpty()) {
1!
92
            try {
93
                connectionTimeout = Long.parseLong(param);
1✔
94
            } catch (NumberFormatException e) {
×
95
                LOG.error("Invalid value for 'connectionTimeout'", e);
×
96
            }
1✔
97
        }
98

99
        param = config.getAttribute("max_docs_in_context_to_refine_query");
1✔
100
        if (!param.isEmpty()) {
1!
101
            try {
102
                max_docs_in_context_to_refine_query = Integer.parseInt(param);
×
103
            } catch (NumberFormatException e) {
×
104
                LOG.error("Invalid value for 'max_docs_in_context_to_refine_query', using default:{}", max_docs_in_context_to_refine_query, e);
×
105
            }
×
106
        }
107

108
        if (LOG.isDebugEnabled())
1!
109
            LOG.debug("max_docs_in_context_to_refine_query = {}", max_docs_in_context_to_refine_query);
×
110
    }
1✔
111

112
    @Override
113
    public IndexWorker getWorker(DBBroker broker) {
114
        AbstractGMLJDBCIndexWorker worker = workers.get(broker);
1✔
115
        if (worker == null) {
1!
116
            worker = new GMLHSQLIndexWorker(this, broker);
1✔
117
            workers.put(broker, worker);
1✔
118
        }
119
        return worker;
1✔
120
    }
121

122
    @Override
123
    protected void checkDatabase() throws ClassNotFoundException, SQLException {
124
        //Test to see if we have a HSQL driver in the classpath
125
        Class.forName("org.hsqldb.jdbcDriver");
1✔
126
    }
1✔
127

128
    @Override
129
    protected void shutdownDatabase() throws DBException {
130
        try {
131
            //No need to shutdown if we have opened something
132
            if (conn != null) {
1!
133
                Statement stmt = conn.createStatement();
1✔
134
                stmt.executeQuery("SHUTDOWN");
1✔
135
                stmt.close();
1✔
136
                conn.close();
1✔
137
                if (LOG.isDebugEnabled())
1!
138
                    LOG.debug("GML index: {}/{} closed", getDataDir(), db_file_name_prefix);
×
139
            }
140
        } catch (SQLException e) {
×
141
            throw new DBException(e.getMessage());
×
142
        } finally {
143
            conn = null;
1✔
144
        }
145
    }
1✔
146
    
147
    @Override
148
    protected void deleteDatabase() throws DBException {
149
        final Path directory = getDataDir();
×
150
        try {
151
            final List<Path> files = FileUtils.list(directory, path -> FileUtils.fileName(path).startsWith(db_file_name_prefix));
×
152
            boolean deleted = true;
×
153
            for (final Path file : files) {
×
154
                deleted &= FileUtils.deleteQuietly(file);
×
155
            }
×
156
        } catch(final IOException e) {
×
157
            LOG.error(e);
×
158
            throw new DBException(e.getMessage());
×
159
        }
×
160
        //TODO : raise an error if deleted == false ?
161
    }
×
162

163
    @Override
164
    protected void removeIndexContent() throws DBException {
165
        try {
166
            //Let's be lazy here : we only delete the index content if we have a connection
167
            //deleteDatabase() should be far more efficient ;-)
168
            if (conn != null) {
×
169
                Statement stmt = conn.createStatement(); 
×
170
                int nodeCount = stmt.executeUpdate("DELETE FROM " + GMLHSQLIndex.TABLE_NAME + ";");
×
171
                stmt.close();
×
172
                if (LOG.isDebugEnabled())
×
173
                    LOG.debug("GML index: {}/{}. {} nodes removed", getDataDir(), db_file_name_prefix, nodeCount);
×
174
            }
175
        } catch (SQLException e) {
×
176
            throw new DBException(e.getMessage());
×
177
        }
×
178
    }
×
179

180
    @Override
181
    protected Connection acquireConnection(DBBroker broker) throws SQLException {
182
        //Horrible "locking" mechanism
183
        synchronized (this) {
1✔
184
            if (connectionOwner == null) {
1!
185
                connectionOwner = broker;
1✔
186
                if (conn == null)
1✔
187
                    initializeConnection();
1✔
188
                return conn;
1✔
189
            }
190
            long timeOut_ = connectionTimeout;
×
191
            long waitTime = timeOut_;
×
192
            long start = System.currentTimeMillis();
×
193
            try {
194
                for (;;) {
195
                    wait(waitTime);
×
196
                    if (connectionOwner == null) {
×
197
                        connectionOwner = broker;
×
198
                        if (conn == null)
×
199
                            //We should never get there since the connection should have been initialized
200
                            //by the first request from a worker
201
                            initializeConnection();
×
202
                        return conn;
×
203
                    }
204
                    waitTime = timeOut_ - (System.currentTimeMillis() - start);
×
205
                    if (waitTime <= 0) {
×
206
                        LOG.error("Time out while trying to get connection");
×
207
                        return null;
×
208
                    }
209
                }
210
            } catch (InterruptedException ex) {
×
211
                notify();
×
212
                throw new RuntimeException("interrupted while waiting for lock");
×
213
            }
214
        }
215
    }
216

217
    @Override
218
    protected synchronized void releaseConnection(DBBroker broker) throws SQLException {   
219
        if (connectionOwner == null)
1!
220
            throw new SQLException("Attempted to release a connection that wasn't acquired");
×
221
        connectionOwner = null;
1✔
222
    }
1✔
223

224
    private void initializeConnection() throws SQLException {
225
        System.setProperty("hsqldb.cache_scale", "11");
1✔
226
        System.setProperty("hsqldb.cache_size_scale", "12");
1✔
227
        System.setProperty("hsqldb.default_table_type", "cached");
1✔
228
        //Get a connection to the DB... and keep it
229
        this.conn = DriverManager.getConnection("jdbc:hsqldb:" + getDataDir() + "/" + db_file_name_prefix + ";sql.enforce_size=false" /* + ";shutdown=true" */, "sa", "");
1✔
230
        if (LOG.isDebugEnabled())
1!
231
            LOG.debug("Connected to GML index: {}/{}", getDataDir(), db_file_name_prefix);
×
232
        try (ResultSet rs = this.conn.getMetaData().getTables(null, null, TABLE_NAME, new String[]{"TABLE"})) {
1✔
233
            rs.last();
1✔
234
            if (rs.getRow() == 1) {
1!
235
                if (LOG.isDebugEnabled())
×
236
                    LOG.debug("Opened GML index: {}/{}", getDataDir(), db_file_name_prefix);
×
237
                //Create the data structure if it doesn't exist
238
            } else if (rs.getRow() == 0) {
1!
239
                Statement stmt = conn.createStatement();
1✔
240
                stmt.executeUpdate("CREATE TABLE " + TABLE_NAME + "(" +
1✔
241
                        /*1*/ "DOCUMENT_URI VARCHAR, " +
242
                        /*2*/ "NODE_ID_UNITS INTEGER, " +
243
                        /*3*/ "NODE_ID BINARY, " +
244
                        /*4*/ "GEOMETRY_TYPE VARCHAR, " +
245
                        /*5*/ "SRS_NAME VARCHAR, " +
246
                        /*6*/ "WKT VARCHAR(500000), " +
247
                        /*7*/ "WKB BINARY, " +
248
                        /*8*/ "MINX DOUBLE, " +
249
                        /*9*/ "MAXX DOUBLE, " +
250
                        /*10*/ "MINY DOUBLE, " +
251
                        /*11*/ "MAXY DOUBLE, " +
252
                        /*12*/ "CENTROID_X DOUBLE, " +
253
                        /*13*/ "CENTROID_Y DOUBLE, " +
254
                        /*14*/ "AREA DOUBLE, " +
255
                        //Boundary ?
256
                        /*15*/ "EPSG4326_WKT VARCHAR(500000), " +
257
                        /*16*/ "EPSG4326_WKB BINARY, " +
258
                        /*17*/ "EPSG4326_MINX DOUBLE, " +
259
                        /*18*/ "EPSG4326_MAXX DOUBLE, " +
260
                        /*19*/ "EPSG4326_MINY DOUBLE, " +
261
                        /*20*/ "EPSG4326_MAXY DOUBLE, " +
262
                        /*21*/ "EPSG4326_CENTROID_X DOUBLE, " +
263
                        /*22*/ "EPSG4326_CENTROID_Y DOUBLE, " +
264
                        /*23*/ "EPSG4326_AREA DOUBLE, " +
265
                        //Boundary ?
266
                        /*24*/ "IS_CLOSED BOOLEAN, " +
267
                        /*25*/ "IS_SIMPLE BOOLEAN, " +
268
                        /*26*/ "IS_VALID BOOLEAN, " +
269
                        //Enforce uniqueness
270
                        "UNIQUE (" +
271
                        "DOCUMENT_URI, NODE_ID_UNITS, NODE_ID" +
272
                        ")" +
273
                        ")");
274
                stmt.executeUpdate("CREATE INDEX DOCUMENT_URI ON " + TABLE_NAME + " (DOCUMENT_URI);");
1✔
275
                stmt.executeUpdate("CREATE INDEX NODE_ID ON " + TABLE_NAME + " (NODE_ID);");
1✔
276
                stmt.executeUpdate("CREATE INDEX GEOMETRY_TYPE ON " + TABLE_NAME + " (GEOMETRY_TYPE);");
1✔
277
                stmt.executeUpdate("CREATE INDEX SRS_NAME ON " + TABLE_NAME + " (SRS_NAME);");
1✔
278
                stmt.executeUpdate("CREATE INDEX WKB ON " + TABLE_NAME + " (WKB);");
1✔
279
                stmt.executeUpdate("CREATE INDEX EPSG4326_WKB ON " + TABLE_NAME + " (EPSG4326_WKB);");
1✔
280
                stmt.executeUpdate("CREATE INDEX EPSG4326_MINX ON " + TABLE_NAME + " (EPSG4326_MINX);");
1✔
281
                stmt.executeUpdate("CREATE INDEX EPSG4326_MAXX ON " + TABLE_NAME + " (EPSG4326_MAXX);");
1✔
282
                stmt.executeUpdate("CREATE INDEX EPSG4326_MINY ON " + TABLE_NAME + " (EPSG4326_MINY);");
1✔
283
                stmt.executeUpdate("CREATE INDEX EPSG4326_MAXY ON " + TABLE_NAME + " (EPSG4326_MAXY);");
1✔
284
                stmt.executeUpdate("CREATE INDEX EPSG4326_CENTROID_X ON " + TABLE_NAME + " (EPSG4326_CENTROID_X);");
1✔
285
                stmt.executeUpdate("CREATE INDEX EPSG4326_CENTROID_Y ON " + TABLE_NAME + " (EPSG4326_CENTROID_Y);");
1✔
286
                //AREA ?
287
                stmt.close();
1✔
288
                if (LOG.isDebugEnabled())
1!
289
                    LOG.debug("Created GML index: {}/{}", getDataDir(), db_file_name_prefix);
×
290
            } else {
1✔
291
                throw new SQLException("2 tables with the same name ?");
×
292
            }
293
        }
294
    }
1✔
295

296
        @Override
297
        public void backupToArchive(RawDataBackup backup) throws IOException {
298
        final Path directory = getDataDir();
×
299
        final List<Path> files = FileUtils.list(directory, path -> FileUtils.fileName(path).startsWith(db_file_name_prefix));
×
300
        for (final Path file : files) {
×
301
                        try(final OutputStream os = backup.newEntry(FileUtils.fileName(file))) {
×
302
                Files.copy(file, os);
×
303
            } finally {
304
                backup.closeEntry();
×
305
            }
306
        }
×
307
        }
×
308
        
309
}
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