Punching screen controller created - migrated initview and addcase endpoints
This commit is contained in:
39
cygnus-onprem-db/README.md
Normal file
39
cygnus-onprem-db/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Cygnus On-Premises Database Core
|
||||
|
||||
This module executes cloud-supplied, parameterized query definitions against the
|
||||
on-premises JDBC `DataSource` without creating delimiter strings or `String[][]`
|
||||
result buffers.
|
||||
|
||||
## Query format
|
||||
|
||||
The query catalog value retains the operation prefix during incremental migration:
|
||||
|
||||
```text
|
||||
select!C0L!select id, name from operation where operation_type = ? and operated_by = ?
|
||||
```
|
||||
|
||||
Legacy `val0`, `val1`, ... placeholders are rejected by the modern executor. They
|
||||
remain available through the existing `DBFunctions.FetchRunQuery` path until each
|
||||
query is migrated.
|
||||
|
||||
## Usage
|
||||
|
||||
```java
|
||||
List<Operation> rows = dbFunctions.query(
|
||||
600,
|
||||
new Object[] { operationType, operatedBy },
|
||||
Operation.class);
|
||||
```
|
||||
|
||||
For maximum throughput, use an explicit mapper:
|
||||
|
||||
```java
|
||||
List<Operation> rows = dbFunctions.query(
|
||||
600,
|
||||
parameters,
|
||||
result -> new Operation(result.getLong("id"), result.getString("name")));
|
||||
```
|
||||
|
||||
Large reports can use `stream(...)` so rows are consumed without retaining the
|
||||
whole result in memory. PostgreSQL cursor fetching is enabled by running reads with
|
||||
auto-commit disabled and the configured fetch size.
|
||||
38
cygnus-onprem-db/pom.xml
Normal file
38
cygnus-onprem-db/pom.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.cygnus</groupId>
|
||||
<artifactId>cygnus-parent</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>cygnus-onprem-db</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Cygnus On-Premises Database Core</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>2.3.232</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.5.4</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CygnusDbExecutor {
|
||||
<T> DbResult<T> execute(int queryId, Object[] parameters, Class<T> responseType);
|
||||
DbResult<RowView> execute(int queryId, Object[] parameters);
|
||||
<T> List<T> query(int queryId, Object[] parameters, Class<T> responseType);
|
||||
<T> List<T> query(int queryId, Object[] parameters, RowMapper<T> mapper);
|
||||
List<RowView> query(int queryId, Object[] parameters);
|
||||
<T> Optional<T> queryOne(int queryId, Object[] parameters, Class<T> responseType);
|
||||
int insert(int queryId, Object[] parameters);
|
||||
<K> K insert(int queryId, Object[] parameters, Class<K> generatedKeyType);
|
||||
int update(int queryId, Object[] parameters);
|
||||
int delete(int queryId, Object[] parameters);
|
||||
<T> T procedure(int queryId, Object[] parameters, Class<T> responseType);
|
||||
<T> List<T> procedureRows(int queryId, Object[] parameters, Class<T> responseType);
|
||||
<T> void stream(int queryId, Object[] parameters, RowMapper<T> mapper, RowConsumer<T> consumer);
|
||||
<T> T transaction(TransactionCallback<T> callback);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
public final class DatabaseExecutionException extends RuntimeException {
|
||||
private final int queryId;
|
||||
|
||||
public DatabaseExecutionException(int queryId, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.queryId = queryId;
|
||||
}
|
||||
|
||||
public int queryId() { return queryId; }
|
||||
}
|
||||
20
cygnus-onprem-db/src/main/java/com/cygnus/db/DbResult.java
Normal file
20
cygnus-onprem-db/src/main/java/com/cygnus/db/DbResult.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public record DbResult<T>(QueryType type, List<T> rows, int affectedRows, T value) {
|
||||
public DbResult {
|
||||
rows = rows == null ? List.of() : List.copyOf(rows);
|
||||
}
|
||||
|
||||
public Optional<T> optionalValue() { return Optional.ofNullable(value); }
|
||||
|
||||
public static <T> DbResult<T> rows(QueryType type, List<T> rows) {
|
||||
return new DbResult<>(type, rows, 0, rows.isEmpty() ? null : rows.getFirst());
|
||||
}
|
||||
|
||||
public static <T> DbResult<T> affected(QueryType type, int count) {
|
||||
return new DbResult<>(type, List.of(), count, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
public record ExecutorOptions(int fetchSize, int queryTimeoutSeconds) {
|
||||
public static final ExecutorOptions DEFAULT = new ExecutorOptions(250, 60);
|
||||
|
||||
public ExecutorOptions {
|
||||
if (fetchSize < 0) throw new IllegalArgumentException("fetchSize cannot be negative");
|
||||
if (queryTimeoutSeconds < 0) throw new IllegalArgumentException("queryTimeoutSeconds cannot be negative");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
final class ImmutableRowView implements RowView {
|
||||
private final Map<String, Object> values;
|
||||
private final Map<String, Object> normalized;
|
||||
|
||||
ImmutableRowView(Map<String, Object> values) {
|
||||
this.values = Collections.unmodifiableMap(new LinkedHashMap<>(values));
|
||||
Map<String, Object> index = new LinkedHashMap<>();
|
||||
values.forEach((name, value) -> index.put(name.toLowerCase(Locale.ROOT), value));
|
||||
this.normalized = Map.copyOf(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String column) {
|
||||
Object value = values.get(column);
|
||||
return value != null || values.containsKey(column)
|
||||
? value : normalized.get(column.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(String column, Class<T> type) {
|
||||
return ValueConverter.convert(get(column), type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> asMap() { return values; }
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public final class JdbcCygnusDbExecutor implements CygnusDbExecutor {
|
||||
private final DataSource dataSource;
|
||||
private final QueryDefinitionProvider queryProvider;
|
||||
private final ExecutorOptions options;
|
||||
private final ParameterBinder binder = new ParameterBinder();
|
||||
private final ObjectRowMapperFactory mapperFactory = new ObjectRowMapperFactory();
|
||||
|
||||
public JdbcCygnusDbExecutor(DataSource dataSource, QueryDefinitionProvider queryProvider) {
|
||||
this(dataSource, queryProvider, ExecutorOptions.DEFAULT);
|
||||
}
|
||||
|
||||
public JdbcCygnusDbExecutor(
|
||||
DataSource dataSource, QueryDefinitionProvider queryProvider, ExecutorOptions options) {
|
||||
this.dataSource = java.util.Objects.requireNonNull(dataSource, "dataSource");
|
||||
this.queryProvider = java.util.Objects.requireNonNull(queryProvider, "queryProvider");
|
||||
this.options = java.util.Objects.requireNonNull(options, "options");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DbResult<T> execute(int queryId, Object[] parameters, Class<T> responseType) {
|
||||
QueryDefinition definition = queryProvider.get(queryId);
|
||||
if (definition.type() == QueryType.SELECT) {
|
||||
return withReadConnection(queryId, connection ->
|
||||
executeRowsOrCount(connection, definition, parameters, responseType));
|
||||
}
|
||||
if (definition.type() == QueryType.PROCEDURE) {
|
||||
return executeRowsOrCount(definition, parameters, responseType);
|
||||
}
|
||||
return DbResult.affected(definition.type(), executeUpdate(definition, parameters));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbResult<RowView> execute(int queryId, Object[] parameters) {
|
||||
return execute(queryId, parameters, RowView.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> query(int queryId, Object[] parameters, Class<T> responseType) {
|
||||
return withReadConnection(queryId, connection -> query(
|
||||
connection, require(queryId, QueryType.SELECT), parameters, responseType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> query(int queryId, Object[] parameters, RowMapper<T> mapper) {
|
||||
return withReadConnection(queryId, connection -> query(
|
||||
connection, require(queryId, QueryType.SELECT), parameters, mapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RowView> query(int queryId, Object[] parameters) {
|
||||
return query(queryId, parameters, RowView.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> queryOne(int queryId, Object[] parameters, Class<T> responseType) {
|
||||
List<T> rows = query(queryId, parameters, responseType);
|
||||
if (rows.size() > 1) throw new DatabaseExecutionException(
|
||||
queryId, "Expected one row but received " + rows.size(), null);
|
||||
return rows.stream().findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insert(int queryId, Object[] parameters) {
|
||||
return executeUpdate(require(queryId, QueryType.INSERT), parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K> K insert(int queryId, Object[] parameters, Class<K> generatedKeyType) {
|
||||
QueryDefinition definition = require(queryId, QueryType.INSERT);
|
||||
return withConnection(queryId, connection -> {
|
||||
try (PreparedStatement statement = prepare(
|
||||
connection, definition, Statement.RETURN_GENERATED_KEYS)) {
|
||||
binder.bind(statement, parameters);
|
||||
statement.executeUpdate();
|
||||
try (ResultSet keys = statement.getGeneratedKeys()) {
|
||||
if (!keys.next()) throw new DatabaseExecutionException(
|
||||
queryId, "Insert did not return a generated key", null);
|
||||
return ValueConverter.convert(keys.getObject(1), generatedKeyType);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(int queryId, Object[] parameters) {
|
||||
return executeUpdate(require(queryId, QueryType.UPDATE), parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(int queryId, Object[] parameters) {
|
||||
return executeUpdate(require(queryId, QueryType.DELETE), parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T procedure(int queryId, Object[] parameters, Class<T> responseType) {
|
||||
List<T> rows = procedureRows(queryId, parameters, responseType);
|
||||
return rows.isEmpty() ? null : rows.getFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> procedureRows(int queryId, Object[] parameters, Class<T> responseType) {
|
||||
DbResult<T> result = executeRowsOrCount(
|
||||
require(queryId, QueryType.PROCEDURE), parameters, responseType);
|
||||
return result.rows();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void stream(
|
||||
int queryId, Object[] parameters, RowMapper<T> mapper, RowConsumer<T> consumer) {
|
||||
QueryDefinition definition = require(queryId, QueryType.SELECT);
|
||||
withReadConnection(queryId, connection -> {
|
||||
try (PreparedStatement statement = prepare(connection, definition);
|
||||
ResultSet result = executeQuery(statement, parameters)) {
|
||||
while (result.next()) consumer.accept(mapper.map(result));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T transaction(TransactionCallback<T> callback) {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
boolean autoCommit = connection.getAutoCommit();
|
||||
connection.setAutoCommit(false);
|
||||
try {
|
||||
T result = callback.execute(new TransactionOperationsImpl(connection));
|
||||
connection.commit();
|
||||
return result;
|
||||
} catch (Exception exception) {
|
||||
try { connection.rollback(); } catch (SQLException rollback) { exception.addSuppressed(rollback); }
|
||||
if (exception instanceof RuntimeException runtime) throw runtime;
|
||||
throw new DatabaseExecutionException(0, "Transaction failed", exception);
|
||||
} finally {
|
||||
connection.setAutoCommit(autoCommit);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
throw new DatabaseExecutionException(0, "Unable to manage transaction", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> DbResult<T> executeRowsOrCount(
|
||||
QueryDefinition definition, Object[] parameters, Class<T> responseType) {
|
||||
return withConnection(definition.queryId(), connection ->
|
||||
executeRowsOrCount(connection, definition, parameters, responseType));
|
||||
}
|
||||
|
||||
private <T> DbResult<T> executeRowsOrCount(
|
||||
Connection connection,
|
||||
QueryDefinition definition,
|
||||
Object[] parameters,
|
||||
Class<T> responseType) throws SQLException {
|
||||
try (PreparedStatement statement = prepare(connection, definition)) {
|
||||
binder.bind(statement, parameters);
|
||||
boolean rows = statement.execute();
|
||||
if (!rows) return DbResult.affected(definition.type(), statement.getUpdateCount());
|
||||
try (ResultSet result = statement.getResultSet()) {
|
||||
return DbResult.rows(definition.type(), map(result, responseType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int executeUpdate(QueryDefinition definition, Object[] parameters) {
|
||||
return withConnection(definition.queryId(), connection ->
|
||||
executeUpdate(connection, definition, parameters));
|
||||
}
|
||||
|
||||
private int executeUpdate(
|
||||
Connection connection, QueryDefinition definition, Object[] parameters) throws SQLException {
|
||||
try (PreparedStatement statement = prepare(connection, definition)) {
|
||||
binder.bind(statement, parameters);
|
||||
return statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private <T> List<T> query(
|
||||
Connection connection,
|
||||
QueryDefinition definition,
|
||||
Object[] parameters,
|
||||
Class<T> responseType) throws SQLException {
|
||||
try (PreparedStatement statement = prepare(connection, definition);
|
||||
ResultSet result = executeQuery(statement, parameters)) {
|
||||
return map(result, responseType);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> List<T> query(
|
||||
Connection connection,
|
||||
QueryDefinition definition,
|
||||
Object[] parameters,
|
||||
RowMapper<T> mapper) throws SQLException {
|
||||
try (PreparedStatement statement = prepare(connection, definition);
|
||||
ResultSet result = executeQuery(statement, parameters)) {
|
||||
List<T> rows = new ArrayList<>();
|
||||
while (result.next()) rows.add(mapper.map(result));
|
||||
return List.copyOf(rows);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> List<T> map(ResultSet result, Class<T> responseType) throws SQLException {
|
||||
RowMapper<T> mapper = mapperFactory.create(responseType, result.getMetaData());
|
||||
List<T> rows = new ArrayList<>();
|
||||
while (result.next()) rows.add(mapper.map(result));
|
||||
return List.copyOf(rows);
|
||||
}
|
||||
|
||||
private ResultSet executeQuery(PreparedStatement statement, Object[] parameters) throws SQLException {
|
||||
binder.bind(statement, parameters);
|
||||
return statement.executeQuery();
|
||||
}
|
||||
|
||||
private PreparedStatement prepare(Connection connection, QueryDefinition definition) throws SQLException {
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
definition.sql(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
|
||||
configure(statement);
|
||||
return statement;
|
||||
}
|
||||
|
||||
private PreparedStatement prepare(
|
||||
Connection connection, QueryDefinition definition, int generatedKeys) throws SQLException {
|
||||
PreparedStatement statement = connection.prepareStatement(definition.sql(), generatedKeys);
|
||||
configure(statement);
|
||||
return statement;
|
||||
}
|
||||
|
||||
private void configure(PreparedStatement statement) throws SQLException {
|
||||
statement.setFetchSize(options.fetchSize());
|
||||
statement.setQueryTimeout(options.queryTimeoutSeconds());
|
||||
}
|
||||
|
||||
private QueryDefinition require(int queryId, QueryType expected) {
|
||||
QueryDefinition definition = queryProvider.get(queryId);
|
||||
if (definition.type() != expected) throw new QueryDefinitionException(
|
||||
"Query " + queryId + " is " + definition.type() + ", not " + expected);
|
||||
return definition;
|
||||
}
|
||||
|
||||
private <T> T withConnection(int queryId, SqlWork<T> work) {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
return work.execute(connection);
|
||||
} catch (DatabaseExecutionException exception) {
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
throw new DatabaseExecutionException(queryId, "Database operation failed for query " + queryId, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T withReadConnection(int queryId, SqlWork<T> work) {
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
boolean autoCommit = connection.getAutoCommit();
|
||||
connection.setAutoCommit(false); // PostgreSQL requires this for cursor-based fetchSize.
|
||||
try {
|
||||
T value = work.execute(connection);
|
||||
connection.commit();
|
||||
return value;
|
||||
} catch (Exception exception) {
|
||||
try { connection.rollback(); } catch (SQLException rollback) { exception.addSuppressed(rollback); }
|
||||
if (exception instanceof RuntimeException runtime) throw runtime;
|
||||
throw new DatabaseExecutionException(queryId,
|
||||
"Database read failed for query " + queryId, exception);
|
||||
} finally {
|
||||
connection.setAutoCommit(autoCommit);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
throw new DatabaseExecutionException(queryId,
|
||||
"Unable to manage database read for query " + queryId, exception);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface SqlWork<T> { T execute(Connection connection) throws Exception; }
|
||||
|
||||
private final class TransactionOperationsImpl implements TransactionOperations {
|
||||
private final Connection connection;
|
||||
private TransactionOperationsImpl(Connection connection) { this.connection = connection; }
|
||||
|
||||
@Override
|
||||
public <T> List<T> query(int queryId, Object[] parameters, Class<T> type) {
|
||||
try { return JdbcCygnusDbExecutor.this.query(
|
||||
connection, require(queryId, QueryType.SELECT), parameters, type); }
|
||||
catch (SQLException exception) { throw failure(queryId, exception); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> queryOne(int queryId, Object[] parameters, Class<T> type) {
|
||||
List<T> rows = query(queryId, parameters, type);
|
||||
if (rows.size() > 1) throw new DatabaseExecutionException(
|
||||
queryId, "Expected one row but received " + rows.size(), null);
|
||||
return rows.stream().findFirst();
|
||||
}
|
||||
|
||||
@Override public int insert(int id, Object[] values) { return updateType(id, values, QueryType.INSERT); }
|
||||
@Override public int update(int id, Object[] values) { return updateType(id, values, QueryType.UPDATE); }
|
||||
@Override public int delete(int id, Object[] values) { return updateType(id, values, QueryType.DELETE); }
|
||||
|
||||
private int updateType(int id, Object[] values, QueryType type) {
|
||||
try { return executeUpdate(connection, require(id, type), values); }
|
||||
catch (SQLException exception) { throw failure(id, exception); }
|
||||
}
|
||||
|
||||
private DatabaseExecutionException failure(int id, SQLException exception) {
|
||||
return new DatabaseExecutionException(id, "Transaction operation failed for query " + id, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
final class ObjectRowMapperFactory {
|
||||
private final ConcurrentMap<Class<?>, TypeMetadata> metadataCache = new ConcurrentHashMap<>();
|
||||
|
||||
<T> RowMapper<T> create(Class<T> type, ResultSetMetaData resultMetadata) throws SQLException {
|
||||
if (type == RowView.class) return result -> type.cast(rowView(result, resultMetadata));
|
||||
if (isScalar(type)) return result -> ValueConverter.convert(result.getObject(1), type);
|
||||
TypeMetadata typeMetadata = metadataCache.computeIfAbsent(type, this::inspect);
|
||||
Map<String, Integer> columns = columns(resultMetadata);
|
||||
return typeMetadata.mapper(type, columns);
|
||||
}
|
||||
|
||||
private RowView rowView(java.sql.ResultSet result, ResultSetMetaData metadata) throws SQLException {
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
for (int column = 1; column <= metadata.getColumnCount(); column++) {
|
||||
values.put(metadata.getColumnLabel(column), result.getObject(column));
|
||||
}
|
||||
return new ImmutableRowView(values);
|
||||
}
|
||||
|
||||
private TypeMetadata inspect(Class<?> type) {
|
||||
try {
|
||||
if (type.isRecord()) {
|
||||
RecordComponent[] components = type.getRecordComponents();
|
||||
Class<?>[] parameterTypes = new Class<?>[components.length];
|
||||
String[] names = new String[components.length];
|
||||
for (int index = 0; index < components.length; index++) {
|
||||
parameterTypes[index] = components[index].getType();
|
||||
names[index] = normalize(components[index].getName());
|
||||
}
|
||||
Constructor<?> constructor = type.getDeclaredConstructor(parameterTypes);
|
||||
constructor.setAccessible(true);
|
||||
return new RecordMetadata(constructor, parameterTypes, names);
|
||||
}
|
||||
Constructor<?> constructor = type.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
List<Field> fields = new ArrayList<>();
|
||||
for (Class<?> current = type; current != null && current != Object.class;
|
||||
current = current.getSuperclass()) {
|
||||
for (Field field : current.getDeclaredFields()) {
|
||||
if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
|
||||
field.setAccessible(true);
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new BeanMetadata(constructor, List.copyOf(fields));
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
throw new IllegalArgumentException(
|
||||
"Response type must be a record or have a no-argument constructor: " + type.getName(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Integer> columns(ResultSetMetaData metadata) throws SQLException {
|
||||
Map<String, Integer> result = new LinkedHashMap<>();
|
||||
for (int column = 1; column <= metadata.getColumnCount(); column++) {
|
||||
result.putIfAbsent(normalize(metadata.getColumnLabel(column)), column);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String normalize(String value) {
|
||||
return value.replace("_", "").toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static boolean isScalar(Class<?> type) {
|
||||
return type.isPrimitive() || type.isEnum()
|
||||
|| Number.class.isAssignableFrom(type)
|
||||
|| type == String.class || type == Boolean.class || type == Character.class
|
||||
|| type == java.util.UUID.class
|
||||
|| type.getPackageName().equals("java.time");
|
||||
}
|
||||
|
||||
private sealed interface TypeMetadata permits RecordMetadata, BeanMetadata {
|
||||
<T> RowMapper<T> mapper(Class<T> type, Map<String, Integer> columns);
|
||||
}
|
||||
|
||||
private record RecordMetadata(
|
||||
Constructor<?> constructor, Class<?>[] parameterTypes, String[] names) implements TypeMetadata {
|
||||
@Override
|
||||
public <T> RowMapper<T> mapper(Class<T> type, Map<String, Integer> columns) {
|
||||
int[] indexes = new int[names.length];
|
||||
for (int index = 0; index < names.length; index++) {
|
||||
Integer column = columns.get(names[index]);
|
||||
if (column == null) throw new IllegalArgumentException(
|
||||
"Result does not contain record component: " + names[index]);
|
||||
indexes[index] = column;
|
||||
}
|
||||
return result -> {
|
||||
try {
|
||||
Object[] values = new Object[indexes.length];
|
||||
for (int index = 0; index < indexes.length; index++) {
|
||||
values[index] = ValueConverter.convert(
|
||||
result.getObject(indexes[index]), parameterTypes[index]);
|
||||
}
|
||||
return type.cast(constructor.newInstance(values));
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
throw new DatabaseExecutionException(0, "Unable to map record " + type.getName(), exception);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private record BeanMetadata(Constructor<?> constructor, List<Field> fields) implements TypeMetadata {
|
||||
@Override
|
||||
public <T> RowMapper<T> mapper(Class<T> type, Map<String, Integer> columns) {
|
||||
List<FieldBinding> bindings = fields.stream()
|
||||
.map(field -> new FieldBinding(field, columns.get(normalize(field.getName()))))
|
||||
.filter(binding -> binding.column() != null)
|
||||
.toList();
|
||||
return result -> {
|
||||
try {
|
||||
T instance = type.cast(constructor.newInstance());
|
||||
for (FieldBinding binding : bindings) {
|
||||
binding.field().set(instance, ValueConverter.convert(
|
||||
result.getObject(binding.column()), binding.field().getType()));
|
||||
}
|
||||
return instance;
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
throw new DatabaseExecutionException(0, "Unable to map bean " + type.getName(), exception);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private record FieldBinding(Field field, Integer column) {}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
final class ParameterBinder {
|
||||
void bind(PreparedStatement statement, Object[] parameters) throws SQLException {
|
||||
Object[] values = parameters == null ? new Object[0] : parameters;
|
||||
for (int index = 0; index < values.length; index++) {
|
||||
bind(statement, index + 1, values[index]);
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(PreparedStatement statement, int index, Object value) throws SQLException {
|
||||
if (value instanceof SqlParameter parameter) {
|
||||
if (parameter.value() == null) statement.setNull(index, parameter.sqlType());
|
||||
else statement.setObject(index, parameter.value(), parameter.sqlType());
|
||||
} else if (value == null) {
|
||||
statement.setObject(index, null);
|
||||
} else if (value instanceof Instant instant) {
|
||||
statement.setTimestamp(index, Timestamp.from(instant));
|
||||
} else if (value instanceof LocalDate date) {
|
||||
statement.setObject(index, date);
|
||||
} else if (value instanceof LocalDateTime dateTime) {
|
||||
statement.setObject(index, dateTime);
|
||||
} else if (value instanceof OffsetDateTime dateTime) {
|
||||
statement.setObject(index, dateTime);
|
||||
} else if (value instanceof UUID uuid) {
|
||||
statement.setObject(index, uuid);
|
||||
} else if (value instanceof Enum<?> enumValue) {
|
||||
statement.setString(index, enumValue.name());
|
||||
} else {
|
||||
statement.setObject(index, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public record QueryDefinition(int queryId, QueryType type, String sql) {
|
||||
private static final String DELIMITER = "!C0L!";
|
||||
private static final Pattern LEGACY_PARAMETER = Pattern.compile("\\bval\\d+\\b");
|
||||
|
||||
public QueryDefinition {
|
||||
if (queryId <= 0) throw new QueryDefinitionException("Query ID must be positive");
|
||||
Objects.requireNonNull(type, "type");
|
||||
if (sql == null || sql.isBlank()) throw new QueryDefinitionException("Query SQL is empty: " + queryId);
|
||||
if (LEGACY_PARAMETER.matcher(sql).find()) {
|
||||
throw new QueryDefinitionException(
|
||||
"Query " + queryId + " has not been migrated to parameterized SQL");
|
||||
}
|
||||
}
|
||||
|
||||
public static QueryDefinition parse(int queryId, String storedValue) {
|
||||
if (storedValue == null) throw new QueryDefinitionException("Query was not found: " + queryId);
|
||||
int delimiter = storedValue.indexOf(DELIMITER);
|
||||
if (delimiter <= 0) throw new QueryDefinitionException("Invalid query definition: " + queryId);
|
||||
return new QueryDefinition(
|
||||
queryId,
|
||||
QueryType.parse(storedValue.substring(0, delimiter)),
|
||||
storedValue.substring(delimiter + DELIMITER.length()).trim());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
public final class QueryDefinitionException extends RuntimeException {
|
||||
public QueryDefinitionException(String message) { super(message); }
|
||||
public QueryDefinitionException(String message, Throwable cause) { super(message, cause); }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface QueryDefinitionProvider {
|
||||
QueryDefinition get(int queryId);
|
||||
}
|
||||
15
cygnus-onprem-db/src/main/java/com/cygnus/db/QueryType.java
Normal file
15
cygnus-onprem-db/src/main/java/com/cygnus/db/QueryType.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum QueryType {
|
||||
SELECT, INSERT, UPDATE, DELETE, PROCEDURE;
|
||||
|
||||
public static QueryType parse(String value) {
|
||||
try {
|
||||
return valueOf(value.trim().toUpperCase(Locale.ROOT));
|
||||
} catch (RuntimeException exception) {
|
||||
throw new QueryDefinitionException("Unsupported query type: " + value, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RowConsumer<T> {
|
||||
void accept(T row) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RowMapper<T> {
|
||||
T map(ResultSet resultSet) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface RowView {
|
||||
Object get(String column);
|
||||
<T> T get(String column, Class<T> type);
|
||||
Map<String, Object> asMap();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
public record SqlParameter(int sqlType, Object value) {
|
||||
public static SqlParameter of(int sqlType, Object value) { return new SqlParameter(sqlType, value); }
|
||||
public static SqlParameter nullValue(int sqlType) { return new SqlParameter(sqlType, null); }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TransactionCallback<T> {
|
||||
T execute(TransactionOperations operations) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface TransactionOperations {
|
||||
<T> List<T> query(int queryId, Object[] parameters, Class<T> responseType);
|
||||
<T> Optional<T> queryOne(int queryId, Object[] parameters, Class<T> responseType);
|
||||
int insert(int queryId, Object[] parameters);
|
||||
int update(int queryId, Object[] parameters);
|
||||
int delete(int queryId, Object[] parameters);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Date;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
final class ValueConverter {
|
||||
private ValueConverter() {}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
static <T> T convert(Object value, Class<T> target) {
|
||||
if (value == null) return target.isPrimitive() ? (T) primitiveDefault(target) : null;
|
||||
Class<?> boxed = box(target);
|
||||
if (boxed.isInstance(value)) return (T) value;
|
||||
if (boxed == String.class) return (T) value.toString();
|
||||
if (Number.class.isAssignableFrom(boxed) && value instanceof Number number) {
|
||||
return (T) number(number, boxed);
|
||||
}
|
||||
if (boxed == Boolean.class) {
|
||||
if (value instanceof Number number) return (T) Boolean.valueOf(number.intValue() != 0);
|
||||
return (T) Boolean.valueOf(value.toString());
|
||||
}
|
||||
if (boxed == Character.class) {
|
||||
String text = value.toString();
|
||||
if (text.isEmpty()) throw new IllegalArgumentException("Cannot convert an empty value to char");
|
||||
return (T) Character.valueOf(text.charAt(0));
|
||||
}
|
||||
if (boxed == UUID.class) return (T) UUID.fromString(value.toString());
|
||||
if (boxed == LocalDate.class && value instanceof Date date) return (T) date.toLocalDate();
|
||||
if (boxed == LocalDateTime.class && value instanceof Timestamp timestamp) return (T) timestamp.toLocalDateTime();
|
||||
if (boxed == Instant.class && value instanceof Timestamp timestamp) return (T) timestamp.toInstant();
|
||||
if (boxed == OffsetDateTime.class && value instanceof OffsetDateTime dateTime) return (T) dateTime;
|
||||
if (boxed.isEnum()) return (T) Enum.valueOf((Class<Enum>) boxed, value.toString());
|
||||
throw new DatabaseExecutionException(0,
|
||||
"Cannot convert " + value.getClass().getName() + " to " + target.getName(), null);
|
||||
}
|
||||
|
||||
private static Object number(Number value, Class<?> target) {
|
||||
if (target == Integer.class) return value.intValue();
|
||||
if (target == Long.class) return value.longValue();
|
||||
if (target == Double.class) return value.doubleValue();
|
||||
if (target == Float.class) return value.floatValue();
|
||||
if (target == Short.class) return value.shortValue();
|
||||
if (target == Byte.class) return value.byteValue();
|
||||
if (target == BigDecimal.class) return value instanceof BigDecimal decimal
|
||||
? decimal : new BigDecimal(value.toString());
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Class<?> box(Class<?> type) {
|
||||
if (!type.isPrimitive()) return type;
|
||||
if (type == int.class) return Integer.class;
|
||||
if (type == long.class) return Long.class;
|
||||
if (type == double.class) return Double.class;
|
||||
if (type == float.class) return Float.class;
|
||||
if (type == short.class) return Short.class;
|
||||
if (type == byte.class) return Byte.class;
|
||||
if (type == boolean.class) return Boolean.class;
|
||||
if (type == char.class) return Character.class;
|
||||
return type;
|
||||
}
|
||||
|
||||
private static Object primitiveDefault(Class<?> type) {
|
||||
if (type == boolean.class) return false;
|
||||
if (type == char.class) return '\0';
|
||||
if (type == byte.class) return (byte) 0;
|
||||
if (type == short.class) return (short) 0;
|
||||
if (type == int.class) return 0;
|
||||
if (type == long.class) return 0L;
|
||||
if (type == float.class) return 0F;
|
||||
if (type == double.class) return 0D;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.cygnus.db;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.Statement;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class JdbcCygnusDbExecutorTest {
|
||||
private JdbcCygnusDbExecutor executor;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
JdbcDataSource dataSource = new JdbcDataSource();
|
||||
dataSource.setURL("jdbc:h2:mem:cygnus;DB_CLOSE_DELAY=-1");
|
||||
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
|
||||
statement.execute("DROP TABLE IF EXISTS person");
|
||||
statement.execute("CREATE TABLE person (id bigint generated by default as identity primary key, name varchar(100), active boolean)");
|
||||
statement.execute("INSERT INTO person(name, active) VALUES ('Asha', true), ('Ravi', false)");
|
||||
}
|
||||
Map<Integer, QueryDefinition> queries = Map.of(
|
||||
1, new QueryDefinition(1, QueryType.SELECT,
|
||||
"select id, name from person where active = ? order by id"),
|
||||
2, new QueryDefinition(2, QueryType.INSERT,
|
||||
"insert into person(name, active) values (?, ?)"),
|
||||
3, new QueryDefinition(3, QueryType.UPDATE,
|
||||
"update person set active = ? where name = ?"),
|
||||
4, new QueryDefinition(4, QueryType.DELETE,
|
||||
"delete from person where name = ?"),
|
||||
5, new QueryDefinition(5, QueryType.PROCEDURE, "call abs(?)"));
|
||||
executor = new JdbcCygnusDbExecutor(dataSource, queries::get, new ExecutorOptions(10, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapsSelectDirectlyToRecordsAndDynamicRows() {
|
||||
var people = executor.query(1, new Object[]{true}, Person.class);
|
||||
assertEquals(java.util.List.of(new Person(1L, "Asha")), people);
|
||||
|
||||
RowView row = executor.query(1, new Object[]{true}).getFirst();
|
||||
assertEquals("Asha", row.get("NAME", String.class));
|
||||
assertEquals(1L, row.get("id", Long.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesInsertUpdateDeleteAndGeneratedKeys() {
|
||||
Long id = executor.insert(2, new Object[]{"Mira", true}, Long.class);
|
||||
assertTrue(id > 0);
|
||||
assertEquals(1, executor.update(3, new Object[]{false, "Mira"}));
|
||||
assertEquals(1, executor.delete(4, new Object[]{"Mira"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void executesProcedureAndStreamsWithoutMaterializingRows() {
|
||||
assertEquals(7, executor.procedure(5, new Object[]{-7}, Integer.class));
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
executor.stream(1, new Object[]{true},
|
||||
result -> new Person(result.getLong("id"), result.getString("name")),
|
||||
row -> count.incrementAndGet());
|
||||
assertEquals(1, count.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rollsBackFailedTransactions() {
|
||||
assertThrows(IllegalStateException.class, () -> executor.transaction(tx -> {
|
||||
tx.insert(2, new Object[]{"Rollback", true});
|
||||
throw new IllegalStateException("stop");
|
||||
}));
|
||||
assertFalse(executor.query(1, new Object[]{true}, Person.class).stream()
|
||||
.anyMatch(person -> person.name().equals("Rollback")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsLegacyStringReplacementQueries() {
|
||||
assertThrows(QueryDefinitionException.class, () ->
|
||||
QueryDefinition.parse(513, "select!C0L!select * from person where id=val0"));
|
||||
}
|
||||
|
||||
record Person(long id, String name) {}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/DbResult.class
Normal file
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/DbResult.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/QueryType.class
Normal file
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/QueryType.class
Normal file
Binary file not shown.
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/RowConsumer.class
Normal file
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/RowConsumer.class
Normal file
Binary file not shown.
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/RowMapper.class
Normal file
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/RowMapper.class
Normal file
Binary file not shown.
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/RowView.class
Normal file
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/RowView.class
Normal file
Binary file not shown.
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/SqlParameter.class
Normal file
BIN
cygnus-onprem-db/target/classes/com/cygnus/db/SqlParameter.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
cygnus-onprem-db/target/cygnus-onprem-db-1.0.0-SNAPSHOT.jar
Normal file
BIN
cygnus-onprem-db/target/cygnus-onprem-db-1.0.0-SNAPSHOT.jar
Normal file
Binary file not shown.
3
cygnus-onprem-db/target/maven-archiver/pom.properties
Normal file
3
cygnus-onprem-db/target/maven-archiver/pom.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
artifactId=cygnus-onprem-db
|
||||
groupId=com.cygnus
|
||||
version=1.0.0-SNAPSHOT
|
||||
@@ -0,0 +1,25 @@
|
||||
com/cygnus/db/RowConsumer.class
|
||||
com/cygnus/db/RowView.class
|
||||
com/cygnus/db/QueryDefinitionException.class
|
||||
com/cygnus/db/QueryDefinitionProvider.class
|
||||
com/cygnus/db/CygnusDbExecutor.class
|
||||
com/cygnus/db/SqlParameter.class
|
||||
com/cygnus/db/ObjectRowMapperFactory$RecordMetadata.class
|
||||
com/cygnus/db/JdbcCygnusDbExecutor.class
|
||||
com/cygnus/db/JdbcCygnusDbExecutor$SqlWork.class
|
||||
com/cygnus/db/QueryDefinition.class
|
||||
com/cygnus/db/QueryType.class
|
||||
com/cygnus/db/ObjectRowMapperFactory$TypeMetadata.class
|
||||
com/cygnus/db/TransactionCallback.class
|
||||
com/cygnus/db/RowMapper.class
|
||||
com/cygnus/db/ObjectRowMapperFactory.class
|
||||
com/cygnus/db/DatabaseExecutionException.class
|
||||
com/cygnus/db/ObjectRowMapperFactory$FieldBinding.class
|
||||
com/cygnus/db/TransactionOperations.class
|
||||
com/cygnus/db/ValueConverter.class
|
||||
com/cygnus/db/ExecutorOptions.class
|
||||
com/cygnus/db/DbResult.class
|
||||
com/cygnus/db/ParameterBinder.class
|
||||
com/cygnus/db/ObjectRowMapperFactory$BeanMetadata.class
|
||||
com/cygnus/db/JdbcCygnusDbExecutor$TransactionOperationsImpl.class
|
||||
com/cygnus/db/ImmutableRowView.class
|
||||
@@ -0,0 +1,19 @@
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/CygnusDbExecutor.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/DatabaseExecutionException.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/DbResult.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ExecutorOptions.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ImmutableRowView.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/JdbcCygnusDbExecutor.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ObjectRowMapperFactory.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ParameterBinder.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/QueryDefinition.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/QueryDefinitionException.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/QueryDefinitionProvider.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/QueryType.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/RowConsumer.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/RowMapper.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/RowView.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/SqlParameter.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/TransactionCallback.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/TransactionOperations.java
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ValueConverter.java
|
||||
@@ -0,0 +1,2 @@
|
||||
com/cygnus/db/JdbcCygnusDbExecutorTest.class
|
||||
com/cygnus/db/JdbcCygnusDbExecutorTest$Person.class
|
||||
@@ -0,0 +1 @@
|
||||
/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/test/java/com/cygnus/db/JdbcCygnusDbExecutorTest.java
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user