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

SpiNNakerManchester / JavaSpiNNaker / 6233274834

19 Sep 2023 08:46AM UTC coverage: 36.409% (-0.6%) from 36.982%
6233274834

Pull #658

github

dkfellows
Merge branch 'master' into java-17
Pull Request #658: Update Java version to 17

1656 of 1656 new or added lines in 260 files covered. (100.0%)

8373 of 22997 relevant lines covered (36.41%)

0.36 hits per line

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

94.89
/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);
1✔
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) {
1✔
90
                this.jdbcTemplate = jdbcTemplate;
1✔
91
                this.tombstoneJdbcTemplate = tombstoneJdbcTemplate;
1✔
92
                this.transactionTemplate = new TransactionTemplate(transactionManager);
1✔
93
        }
1✔
94

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

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

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

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

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

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

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

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

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

164
                @Override
165
                public Update update(String sql) {
166
                        return new UpdateImpl(sql, connectionJdbcTemplate);
1✔
167
                }
168

169
                @Override
170
                public Update update(Resource sqlResource) {
171
                        return new UpdateImpl(readSQL(sqlResource), connectionJdbcTemplate);
×
172
                }
173

174
                @Override
175
                public void transaction(boolean lockForWriting, Transacted operation) {
176
                        // Use the other method, ignoring the result value
177
                        transaction(lockForWriting, () -> {
1✔
178
                                operation.act();
1✔
179
                                return this;
1✔
180
                        });
181
                }
1✔
182

183
                @Override
184
                public void transaction(Transacted operation) {
185
                        transaction(true, operation);
1✔
186
                }
1✔
187

188
                @Override
189
                public <T> T transaction(TransactedWithResult<T> operation) {
190
                        return transaction(true, operation);
1✔
191
                }
192

193
                @Override
194
                public synchronized <T> T transaction(boolean lockForWriting,
195
                                TransactedWithResult<T> operation) {
196
                        return transactionTemplate.execute(status -> {
1✔
197
                                try {
198
                                        return operation.act();
1✔
199
                                } finally {
200
                                        if (doRollback) {
1✔
201
                                                status.setRollbackOnly();
1✔
202
                                                doRollback = false;
1✔
203
                                        }
204
                                }
205
                        });
206
                }
207

208
                @Override
209
                public boolean isReadOnly() {
210
                        return false;
1✔
211
                }
212

213
                @Override
214
                public void rollback() {
215
                        doRollback = true;
1✔
216
                }
1✔
217

218
                @Override
219
                public Query query(String sql, boolean lockType) {
220
                        return query(sql);
×
221
                }
222

223
                @Override
224
                public Query query(Resource sqlResource, boolean lockType) {
225
                        return query(sqlResource);
×
226
                }
227
        }
228

229
        private abstract class StatementImpl implements StatementCommon {
230
                private final String originalSql;
231

232
                final String sql;
233

234
                final JdbcTemplate jdbcTemplate;
235

236
                StatementImpl(String sql, JdbcTemplate jdbcTemplate) {
1✔
237
                        this.originalSql = sql;
1✔
238
                        this.sql = parseSqlStatementIntoString(sql);
1✔
239
                        this.jdbcTemplate = jdbcTemplate;
1✔
240
                }
1✔
241

242
                @Override
243
                public void close() {
244
                        // Does Nothing
245
                }
1✔
246

247
                @Override
248
                public final List<String> getParameters() {
249
                        return buildSqlParameterList(parseSqlStatement(originalSql),
1✔
250
                                        new EmptySqlParameterSource()).stream()
1✔
251
                                        .map(SqlParameter::getName).collect(toUnmodifiableList());
1✔
252
                }
253

254
                final Object[] resolveArguments(Object[] arguments) {
255
                        var resolved = new Object[arguments.length];
1✔
256
                        for (int i = 0; i < arguments.length; i++) {
1✔
257
                                var arg = arguments[i];
1✔
258
                                // The classes we augment the DB driver with
259
                                if (arg instanceof Optional<?> opt) {
1✔
260
                                        // Unpack one layer of Optional only; absent = NULL
261
                                        arg = opt.orElse(null);
1✔
262
                                }
263
                                if (arg instanceof Instant inst) {
1✔
264
                                        arg = inst.getEpochSecond();
1✔
265
                                } else if (arg instanceof Duration d) {
1✔
266
                                        arg = d.getSeconds();
1✔
267
                                } else if (arg instanceof Enum<?> e) {
1✔
268
                                        arg = e.ordinal();
1✔
269
                                } else if (arg != null && arg instanceof Serializable
1✔
270
                                                && !(arg instanceof String || arg instanceof Number
271
                                                                || arg instanceof Boolean
272
                                                                || arg instanceof byte[])) {
273
                                        try {
274
                                                arg = serialize(arg);
1✔
275
                                        } catch (IOException e) {
×
276
                                                arg = null;
×
277
                                        }
1✔
278
                                }
279
                                resolved[i] = arg;
1✔
280
                        }
281
                        return resolved;
1✔
282
                }
283
        }
284

285
        private final class QueryImpl extends StatementImpl implements Query {
286
                QueryImpl(String sql, JdbcTemplate queryJdbcTemplate) {
1✔
287
                        super(sql, queryJdbcTemplate);
1✔
288
                }
1✔
289

290
                @Override
291
                public <T> List<T> call(RowMapper<T> mapper, Object... arguments) {
292
                        var resolved = resolveArguments(arguments);
1✔
293
                        return jdbcTemplate.query(sql, (results) -> {
1✔
294
                                var values = new ArrayList<T>();
1✔
295
                                while (results.next()) {
1✔
296
                                        values.add(mapper.mapRow(new Row(results)));
1✔
297
                                }
298
                                return values;
1✔
299
                        }, resolved);
300
                }
301

302
                @Override
303
                public <T> Optional<T> call1(RowMapper<T> mapper, Object... arguments) {
304
                        var resolved = resolveArguments(arguments);
1✔
305
                        return jdbcTemplate.query(sql, (results) -> {
1✔
306
                                if (results.next()) {
1✔
307
                                        // Allow nullable if there is a value
308
                                        return Optional.ofNullable(mapper.mapRow(new Row(results)));
1✔
309
                                }
310
                                return Optional.empty();
1✔
311
                        }, resolved);
312
                }
313

314
                @Override
315
                public List<String> getColumns() {
316
                        return jdbcTemplate.execute(sql,
1✔
317
                                        (PreparedStatementCallback<List<String>>) ps -> {
318
                                                var md = ps.getMetaData();
1✔
319
                                                var names = new ArrayList<String>();
1✔
320
                                                for (int i = 1; i <= md.getColumnCount(); i++) {
1✔
321
                                                        names.add(md.getColumnLabel(i));
1✔
322
                                                }
323
                                                return names;
1✔
324
                                        });
325
                }
326
        }
327

328
        private final class UpdateImpl extends StatementImpl implements Update {
329
                UpdateImpl(String sql, JdbcTemplate updateJdbcTemplate) {
1✔
330
                        super(sql, updateJdbcTemplate);
1✔
331
                }
1✔
332

333
                @Override
334
                public int call(Object... arguments) {
335
                        var resolved = resolveArguments(arguments);
1✔
336
                        return jdbcTemplate.update(sql, resolved);
1✔
337
                }
338

339
                @Override
340
                public Optional<Integer> key(Object... arguments) {
341
                        var resolved = resolveArguments(arguments);
1✔
342
                        var keyHolder = new GeneratedKeyHolder();
1✔
343
                        jdbcTemplate.update(con -> {
1✔
344
                                var stmt = con.prepareStatement(sql, RETURN_GENERATED_KEYS);
1✔
345
                                for (int i = 0; i < resolved.length; i++) {
1✔
346
                                        stmt.setObject(i + 1, resolved[i]);
1✔
347
                                }
348
                                return stmt;
1✔
349
                        }, keyHolder);
350
                        return Optional.ofNullable(keyHolder.getKey())
1✔
351
                                        .map(Number::intValue);
1✔
352
                }
353
        }
354

355
        /**
356
         * Simple reader that loads a complex SQL query from a file.
357
         *
358
         * @param resource
359
         *                        The resource to load from
360
         * @return The content of the resource
361
         * @throws UncategorizedScriptException
362
         *                         If the resource can't be loaded.
363
         */
364
        private String readSQL(Resource resource) {
365
                try (var is = resource.getInputStream()) {
1✔
366
                        return IOUtils.toString(is, UTF_8);
1✔
367
                } catch (IOException e) {
×
368
                        throw new UncategorizedScriptException(
×
369
                                        "could not load SQL file from " + resource, e);
370
                }
371
        }
372

373
        @Override
374
        public Connection getConnection() {
375
                return new ConnectionImpl(jdbcTemplate);
1✔
376
        }
377

378
        @Override
379
        public boolean isHistoricalDBAvailable() {
380
                return tombstoneJdbcTemplate != null;
1✔
381
        }
382

383
        @Override
384
        public Connection getHistoricalConnection() {
385
                return new ConnectionImpl(tombstoneJdbcTemplate);
1✔
386
        }
387

388
        @Override
389
        public void executeVoid(boolean lockForWriting, Connected operation) {
390
                try (var conn = getConnection()) {
1✔
391
                        conn.transaction(lockForWriting, () -> {
1✔
392
                                operation.act(conn);
1✔
393
                                return this;
1✔
394
                        });
395
                }
396
        }
1✔
397

398
        @Override
399
        public <T> T execute(boolean lockForWriting,
400
                        ConnectedWithResult<T> operation) {
401
                try (var conn = getConnection()) {
1✔
402
                        return conn.transaction(lockForWriting, () -> operation.act(conn));
1✔
403
                }
404
        }
405
}
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