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

SpiNNakerManchester / JavaSpiNNaker / 13068443665

31 Jan 2025 07:38AM UTC coverage: 37.377% (+0.08%) from 37.299%
13068443665

push

github

rowleya
Check that a job is possible before entering the queue

45 of 62 new or added lines in 4 files covered. (72.58%)

15 existing lines in 4 files now uncovered.

8873 of 23739 relevant lines covered (37.38%)

1.12 hits per line

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

94.33
/SpiNNaker-allocserv/src/main/java/uk/ac/manchester/spinnaker/alloc/db/DatabaseEngineJDBCImpl.java
1
/*
2
 * Copyright (c) 2022 The University of Manchester
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package uk.ac.manchester.spinnaker.alloc.db;
17

18
import static java.nio.charset.StandardCharsets.UTF_8;
19
import static java.sql.Statement.RETURN_GENERATED_KEYS;
20
import static java.util.stream.Collectors.toUnmodifiableList;
21
import static org.slf4j.LoggerFactory.getLogger;
22
import static org.springframework.jdbc.core.namedparam.NamedParameterUtils.buildSqlParameterList;
23
import static org.springframework.jdbc.core.namedparam.NamedParameterUtils.parseSqlStatement;
24
import static org.springframework.jdbc.core.namedparam.NamedParameterUtils.parseSqlStatementIntoString;
25
import static uk.ac.manchester.spinnaker.alloc.IOUtils.serialize;
26

27
import java.io.IOException;
28
import java.io.Serializable;
29
import java.time.Duration;
30
import java.time.Instant;
31
import java.util.ArrayList;
32
import java.util.List;
33
import java.util.Optional;
34

35
import javax.annotation.PostConstruct;
36

37
import org.apache.commons.io.IOUtils;
38
import org.slf4j.Logger;
39
import org.springframework.beans.factory.annotation.Autowired;
40
import org.springframework.beans.factory.annotation.Qualifier;
41
import org.springframework.beans.factory.annotation.Value;
42
import org.springframework.context.annotation.Primary;
43
import org.springframework.core.io.Resource;
44
import org.springframework.jdbc.core.JdbcTemplate;
45
import org.springframework.jdbc.core.PreparedStatementCallback;
46
import org.springframework.jdbc.core.SqlParameter;
47
import org.springframework.jdbc.core.namedparam.EmptySqlParameterSource;
48
import org.springframework.jdbc.datasource.init.UncategorizedScriptException;
49
import org.springframework.jdbc.support.GeneratedKeyHolder;
50
import org.springframework.stereotype.Service;
51
import org.springframework.transaction.PlatformTransactionManager;
52
import org.springframework.transaction.support.TransactionTemplate;
53

54
/**
55
 * Implementation of the {@link DatabaseAPI} that uses JDBC to talk to MySQL.
56
 */
57
@Service
58
@Primary
59
public class DatabaseEngineJDBCImpl implements DatabaseAPI {
60
        private static final Logger log = getLogger(DatabaseEngineJDBCImpl.class);
3✔
61

62
        @Value("classpath:/spalloc-mysql.sql")
63
        private Resource sqlDDLFile;
64

65
        @Value("classpath:/spalloc-tombstone-mysql.sql")
66
        private Resource tombstoneDDLFile;
67

68
        private final JdbcTemplate jdbcTemplate;
69

70
        private final JdbcTemplate tombstoneJdbcTemplate;
71

72
        private final TransactionTemplate transactionTemplate;
73

74
        /**
75
         * Create a new JDBC Database API.
76
         *
77
         * @param jdbcTemplate
78
         *            The connection to the main database.
79
         * @param tombstoneJdbcTemplate
80
         *            The connection to the historical database.
81
         * @param transactionManager
82
         *            The transaction manager.
83
         */
84
        @Autowired
85
        public DatabaseEngineJDBCImpl(
86
                        @Qualifier("mainDatabase") JdbcTemplate jdbcTemplate,
87
                        @Qualifier("historicalDatabase") JdbcTemplate tombstoneJdbcTemplate,
88
                        @Qualifier("mainTransactionManager")
89
                                PlatformTransactionManager transactionManager) {
3✔
90
                this.jdbcTemplate = jdbcTemplate;
3✔
91
                this.tombstoneJdbcTemplate = tombstoneJdbcTemplate;
3✔
92
                this.transactionTemplate = new TransactionTemplate(transactionManager);
3✔
93
        }
3✔
94

95
        @PostConstruct
96
        private void setup() {
97
                log.info("Running Database setup...");
3✔
98
                runSQLFile(sqlDDLFile, jdbcTemplate);
3✔
99
                if (tombstoneJdbcTemplate != null) {
3✔
100
                        log.info("Running tombstone setup");
3✔
101
                        runSQLFile(tombstoneDDLFile, tombstoneJdbcTemplate);
3✔
102
                }
103
        }
3✔
104

105
        private void runSQLFile(Resource sqlFile, JdbcTemplate template) {
106
                var sql = readSQL(sqlFile);
3✔
107
                var lines = sql.split("\n");
3✔
108
                int i = 0;
3✔
109
                while (i < lines.length) {
3✔
110
                        // Find the next statement start
111
                        while (i < lines.length && !lines[i].startsWith("-- STMT")) {
3✔
112
                                log.trace("DDL non-statement line {}", lines[i]);
3✔
113
                                i++;
3✔
114
                        }
115
                        // Skip the STMT line
116
                        i++;
3✔
117

118
                        // Build a statement until it ends
119
                        var stmt = new StringBuilder();
3✔
120
                        while (i < lines.length && !lines[i].startsWith("-- STMT")
3✔
121
                                        && !lines[i].startsWith("-- IGNORE")) {
3✔
122
                                var line = lines[i].trim();
3✔
123
                                if (!line.startsWith("--") && !line.isEmpty()) {
3✔
124
                                        log.trace("DDL statement line {}", line);
3✔
125
                                        stmt.append(line);
3✔
126
                                        stmt.append('\n');
3✔
127
                                }
128
                                i++;
3✔
129
                        }
3✔
130
                        if (stmt.length() != 0) {
3✔
131
                                var statement = stmt.toString();
3✔
132
                                log.debug("Executing DDL Statement: {}", statement);
3✔
133
                                template.execute(statement);
3✔
134
                        }
135
                }
3✔
136
        }
3✔
137

138
        private final class ConnectionImpl implements Connection {
139
                /** Whether a rollback has been requested on a transaction. */
140
                private boolean doRollback = false;
3✔
141

142
                /** The JdbcTemplate to use. */
143
                private final JdbcTemplate connectionJdbcTemplate;
144

145
                ConnectionImpl(JdbcTemplate connectionJdbcTemplate) {
3✔
146
                        this.connectionJdbcTemplate = connectionJdbcTemplate;
3✔
147
                }
3✔
148

149
                @Override
150
                public void close() {
151
                        // Does nothing
152
                }
3✔
153

154
                @Override
155
                public Query query(String sql) {
156
                        return new QueryImpl(sql, connectionJdbcTemplate);
3✔
157
                }
158

159
                @Override
160
                public Query query(Resource sqlResource) {
161
                        return new QueryImpl(readSQL(sqlResource), connectionJdbcTemplate);
3✔
162
                }
163

164
                @Override
165
                public Query query(SQL sql) {
166
                        return new QueryImpl(sql.getSQL(), connectionJdbcTemplate);
3✔
167
                }
168

169
                @Override
170
                public Update update(String sql) {
171
                        return new UpdateImpl(sql, connectionJdbcTemplate);
3✔
172
                }
173

174
                @Override
175
                public Update update(SQL sql) {
NEW
176
                        return new UpdateImpl(sql.getSQL(), connectionJdbcTemplate);
×
177
                }
178

179
                @Override
180
                public Update update(Resource sqlResource) {
181
                        return new UpdateImpl(readSQL(sqlResource), connectionJdbcTemplate);
3✔
182
                }
183

184
                @Override
185
                public void transaction(boolean lockForWriting, Transacted operation) {
186
                        // Use the other method, ignoring the result value
187
                        transaction(lockForWriting, () -> {
3✔
188
                                operation.act();
3✔
189
                                return this;
3✔
190
                        });
191
                }
3✔
192

193
                @Override
194
                public void transaction(Transacted operation) {
195
                        transaction(true, operation);
3✔
196
                }
3✔
197

198
                @Override
199
                public <T> T transaction(TransactedWithResult<T> operation) {
200
                        return transaction(true, operation);
3✔
201
                }
202

203
                @Override
204
                public synchronized <T> T transaction(boolean lockForWriting,
205
                                TransactedWithResult<T> operation) {
206
                        return transactionTemplate.execute(status -> {
3✔
207
                                try {
208
                                        return operation.act();
3✔
209
                                } finally {
210
                                        if (doRollback) {
3✔
211
                                                status.setRollbackOnly();
3✔
212
                                                doRollback = false;
3✔
213
                                        }
214
                                }
215
                        });
216
                }
217

218
                @Override
219
                public boolean isReadOnly() {
220
                        return false;
3✔
221
                }
222

223
                @Override
224
                public void rollback() {
225
                        doRollback = true;
3✔
226
                }
3✔
227

228
                @Override
229
                public Query query(String sql, boolean lockType) {
230
                        return query(sql);
×
231
                }
232

233
                @Override
234
                public Query query(Resource sqlResource, boolean lockType) {
235
                        return query(sqlResource);
×
236
                }
237

238
                @Override
239
                public Query query(SQL sql, boolean lockType) {
NEW
240
                        return query(sql);
×
241
                }
242
        }
243

244
        private abstract class StatementImpl implements StatementCommon {
245
                private final String originalSql;
246

247
                final String sql;
248

249
                final JdbcTemplate jdbcTemplate;
250

251
                StatementImpl(String sql, JdbcTemplate jdbcTemplate) {
3✔
252
                        this.originalSql = sql;
3✔
253
                        this.sql = parseSqlStatementIntoString(sql);
3✔
254
                        this.jdbcTemplate = jdbcTemplate;
3✔
255
                }
3✔
256

257
                @Override
258
                public void close() {
259
                        // Does Nothing
260
                }
3✔
261

262
                @Override
263
                public final List<String> getParameters() {
264
                        return buildSqlParameterList(parseSqlStatement(originalSql),
3✔
265
                                        new EmptySqlParameterSource()).stream()
3✔
266
                                        .map(SqlParameter::getName).collect(toUnmodifiableList());
3✔
267
                }
268

269
                final Object[] resolveArguments(Object[] arguments) {
270
                        var resolved = new Object[arguments.length];
3✔
271
                        for (int i = 0; i < arguments.length; i++) {
3✔
272
                                var arg = arguments[i];
3✔
273
                                // The classes we augment the DB driver with
274
                                if (arg instanceof Optional) {
3✔
275
                                        // Unpack one layer of Optional only; absent = NULL
276
                                        arg = ((Optional<?>) arg).orElse(null);
3✔
277
                                }
278
                                if (arg instanceof Instant) {
3✔
279
                                        arg = ((Instant) arg).getEpochSecond();
3✔
280
                                } else if (arg instanceof Duration) {
3✔
281
                                        arg = ((Duration) arg).getSeconds();
3✔
282
                                } else if (arg instanceof Enum) {
3✔
283
                                        arg = ((Enum<?>) arg).ordinal();
3✔
284
                                } else if (arg != null && arg instanceof Serializable
3✔
285
                                                && !(arg instanceof String || arg instanceof Number
286
                                                                || arg instanceof Boolean
287
                                                                || arg instanceof byte[])) {
288
                                        try {
289
                                                arg = serialize(arg);
3✔
290
                                        } catch (IOException e) {
×
291
                                                arg = null;
×
292
                                        }
3✔
293
                                }
294
                                resolved[i] = arg;
3✔
295
                        }
296
                        return resolved;
3✔
297
                }
298
        }
299

300
        private final class QueryImpl extends StatementImpl implements Query {
301
                QueryImpl(String sql, JdbcTemplate queryJdbcTemplate) {
3✔
302
                        super(sql, queryJdbcTemplate);
3✔
303
                }
3✔
304

305
                @Override
306
                public <T> List<T> call(RowMapper<T> mapper, Object... arguments) {
307
                        var resolved = resolveArguments(arguments);
3✔
308
                        return jdbcTemplate.query(sql, (results) -> {
3✔
309
                                var values = new ArrayList<T>();
3✔
310
                                while (results.next()) {
3✔
311
                                        values.add(mapper.mapRow(new Row(results)));
3✔
312
                                }
313
                                return values;
3✔
314
                        }, resolved);
315
                }
316

317
                @Override
318
                public <T> Optional<T> call1(RowMapper<T> mapper, Object... arguments) {
319
                        var resolved = resolveArguments(arguments);
3✔
320
                        return jdbcTemplate.query(sql, (results) -> {
3✔
321
                                if (results.next()) {
3✔
322
                                        // Allow nullable if there is a value
323
                                        return Optional.ofNullable(mapper.mapRow(new Row(results)));
3✔
324
                                }
325
                                return Optional.empty();
3✔
326
                        }, resolved);
327
                }
328

329
                @Override
330
                public List<String> getColumns() {
331
                        return jdbcTemplate.execute(sql,
3✔
332
                                        (PreparedStatementCallback<List<String>>) ps -> {
333
                                                var md = ps.getMetaData();
3✔
334
                                                var names = new ArrayList<String>();
3✔
335
                                                for (int i = 1; i <= md.getColumnCount(); i++) {
3✔
336
                                                        names.add(md.getColumnLabel(i));
3✔
337
                                                }
338
                                                return names;
3✔
339
                                        });
340
                }
341
        }
342

343
        private final class UpdateImpl extends StatementImpl implements Update {
344
                UpdateImpl(String sql, JdbcTemplate updateJdbcTemplate) {
3✔
345
                        super(sql, updateJdbcTemplate);
3✔
346
                }
3✔
347

348
                @Override
349
                public int call(Object... arguments) {
350
                        var resolved = resolveArguments(arguments);
3✔
351
                        return jdbcTemplate.update(sql, resolved);
3✔
352
                }
353

354
                @Override
355
                public Optional<Integer> key(Object... arguments) {
356
                        var resolved = resolveArguments(arguments);
3✔
357
                        var keyHolder = new GeneratedKeyHolder();
3✔
358
                        jdbcTemplate.update(con -> {
3✔
359
                                var stmt = con.prepareStatement(sql, RETURN_GENERATED_KEYS);
3✔
360
                                for (int i = 0; i < resolved.length; i++) {
3✔
361
                                        stmt.setObject(i + 1, resolved[i]);
3✔
362
                                }
363
                                return stmt;
3✔
364
                        }, keyHolder);
365
                        return Optional.ofNullable(keyHolder.getKey())
3✔
366
                                        .map(Number::intValue);
3✔
367
                }
368
        }
369

370
        /**
371
         * Simple reader that loads a complex SQL query from a file.
372
         *
373
         * @param resource
374
         *                        The resource to load from
375
         * @return The content of the resource
376
         * @throws UncategorizedScriptException
377
         *                         If the resource can't be loaded.
378
         */
379
        private String readSQL(Resource resource) {
380
                try (var is = resource.getInputStream()) {
3✔
381
                        var s = IOUtils.toString(is, UTF_8);
3✔
382
                        return s;
3✔
383
                } catch (IOException e) {
×
384
                        throw new UncategorizedScriptException(
×
385
                                        "could not load SQL file from " + resource, e);
386
                }
387
        }
388

389
        @Override
390
        public Connection getConnection() {
391
                return new ConnectionImpl(jdbcTemplate);
3✔
392
        }
393

394
        @Override
395
        public boolean isHistoricalDBAvailable() {
396
                return tombstoneJdbcTemplate != null;
3✔
397
        }
398

399
        @Override
400
        public Connection getHistoricalConnection() {
401
                return new ConnectionImpl(tombstoneJdbcTemplate);
3✔
402
        }
403

404
        @Override
405
        public void executeVoid(boolean lockForWriting, Connected operation) {
406
                try (var conn = getConnection()) {
3✔
407
                        conn.transaction(lockForWriting, () -> {
3✔
408
                                operation.act(conn);
3✔
409
                                return this;
3✔
410
                        });
411
                }
412
        }
3✔
413

414
        @Override
415
        public <T> T execute(boolean lockForWriting,
416
                        ConnectedWithResult<T> operation) {
417
                try (var conn = getConnection()) {
3✔
418
                        return conn.transaction(lockForWriting, () -> operation.act(conn));
3✔
419
                }
420
        }
421
}
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

© 2026 Coveralls, Inc