Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.speedment.runtime.connector.sqlite.internal.SqliteMetadataHandler Maven / Gradle / Ivy
Go to download
A Speedment bundle that shades all dependencies into one jar. This is
useful when deploying an application on a server.
/*
*
* Copyright (c) 2006-2019, Speedment, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); You may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.speedment.runtime.connector.sqlite.internal;
import com.speedment.common.injector.Injector;
import com.speedment.common.injector.State;
import com.speedment.common.injector.annotation.ExecuteBefore;
import com.speedment.common.injector.annotation.Inject;
import com.speedment.common.logger.Logger;
import com.speedment.common.logger.LoggerManager;
import com.speedment.runtime.config.*;
import com.speedment.runtime.config.mutator.ForeignKeyColumnMutator;
import com.speedment.runtime.config.mutator.IndexMutator;
import com.speedment.runtime.config.trait.HasId;
import com.speedment.runtime.config.util.DocumentDbUtil;
import com.speedment.runtime.config.util.DocumentUtil;
import com.speedment.runtime.connector.sqlite.internal.types.SqlTypeMappingHelper;
import com.speedment.runtime.connector.sqlite.internal.util.MetaDataUtil;
import com.speedment.runtime.core.component.ProjectComponent;
import com.speedment.runtime.core.component.connectionpool.ConnectionPoolComponent;
import com.speedment.runtime.core.db.DatabaseNamingConvention;
import com.speedment.runtime.core.db.DbmsMetadataHandler;
import com.speedment.runtime.core.db.JavaTypeMap;
import com.speedment.runtime.core.db.SqlFunction;
import com.speedment.runtime.core.db.SqlPredicate;
import com.speedment.runtime.core.db.SqlSupplier;
import com.speedment.runtime.core.db.metadata.ColumnMetaData;
import com.speedment.runtime.core.exception.SpeedmentException;
import com.speedment.runtime.core.util.ProgressMeasure;
import com.speedment.runtime.typemapper.TypeMapper;
import com.speedment.runtime.typemapper.primitive.PrimitiveTypeMapper;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static com.speedment.common.invariant.NullUtil.requireNonNulls;
import static com.speedment.runtime.connector.sqlite.internal.util.LoggingUtil.describe;
import static com.speedment.runtime.connector.sqlite.internal.util.MetaDataUtil.getOrderType;
import static com.speedment.runtime.connector.sqlite.internal.util.MetaDataUtil.isAutoIncrement;
import static com.speedment.runtime.connector.sqlite.internal.util.MetaDataUtil.isWrapper;
import static com.speedment.runtime.core.internal.db.AbstractDbmsOperationHandler.SHOW_METADATA;
import static java.lang.String.format;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
/**
* Implementation of {@link DbmsMetadataHandler} for SQLite databases.
*
* @author Emil Forslund
* @since 3.1.10
*/
public final class SqliteMetadataHandler implements DbmsMetadataHandler {
private final static Logger LOGGER = LoggerManager.getLogger(SqliteMetadataHandler.class);
private final static String[] TABLES_AND_VIEWS = {"TABLE", "VIEW"};
private final static String ORIGINAL_TYPE = "originalDatabaseType";
private final static Pattern BINARY_TYPES = Pattern.compile(
"^(?:(?:TINY|MEDIUM|LONG)?\\s?BLOB|(?:VAR)?BINARY)(?:\\(\\d+\\))?$");
private final static Pattern INTEGER_BOOLEAN_TYPE = Pattern.compile(
"^(?:BIT|INT|INTEGER|TINYINT)\\(1\\)$");
private final static Pattern BIT_TYPES = Pattern.compile(
"^BIT\\((\\d+)\\)$");
private final static Pattern SHORT_TYPES = Pattern.compile(
"^(?:UNSIGNED\\s+TINY\\s?INT|SHORT(?:\\s?INT)?)(?:\\((\\d+)\\))?$");
private final static Pattern INT_TYPES = Pattern.compile(
"^(?:UNSIGNED (?:SHORT|MEDIUM\\s?INT)|INT(?:EGER)?|MEDIUM\\s?INT)(?:\\((\\d+)\\))?$");
private final static Pattern LONG_TYPES = Pattern.compile(
"^(?:UNSIGNED(?: INT(?:EGER)?)|(?:UNSIGNED\\s*)?(?:BIG\\s?INT|LONG))(?:\\((\\d+)\\))?$");
/**
* Fix #566: Some connectors throw an exception if getIndexInfo() is invoked
* for a database VIEW.
*/
private final static boolean IGNORE_VIEW_INDEXES = false;
/**
* 'true' below might speed up metadata retrieval since approximations can
* be used. See https://github.com/speedment/speedment-enterprise/issues/168
*/
private final static boolean APPROXIMATE_INDEX = true;
private @Inject ConnectionPoolComponent connectionPool;
private @Inject ProjectComponent projects;
private @Inject SqliteDbmsType dbmsType;
private JavaTypeMap javaTypeMap;
private SqlTypeMappingHelper typeMappingHelper;
@ExecuteBefore(State.INITIALIZED)
void initJavaTypeMap() {
javaTypeMap = JavaTypeMap.create();
javaTypeMap.addRule((mappings, md) ->
md.getTypeName().toUpperCase().startsWith("NUMERIC(")
? Optional.of(Double.class) : Optional.empty()
);
javaTypeMap.addRule((mappings, md) ->
md.getTypeName().toUpperCase().startsWith("DECIMAL(")
? Optional.of(Double.class) : Optional.empty()
);
javaTypeMap.addRule((mappings, md) ->
Optional.of(md.getTypeName())
.map(String::toUpperCase)
.filter(str -> INTEGER_BOOLEAN_TYPE.matcher(str).find())
.map(str -> Boolean.class)
);
javaTypeMap.addRule((mappings, md) -> patternMapper(SHORT_TYPES, md, Short.class));
javaTypeMap.addRule((mappings, md) -> patternMapper(LONG_TYPES, md, Long.class));
javaTypeMap.addRule((mappings, md) -> patternMapper(INT_TYPES, md, Integer.class));
javaTypeMap.addRule((mappings, md) ->
Optional.of(md.getTypeName())
.map(String::toUpperCase)
.map(BIT_TYPES::matcher)
.filter(Matcher::find)
.map(match -> match.group(1))
.map(Integer::parseInt)
.map(bits -> {
if (bits > Integer.SIZE) return Long.class;
else if (bits > Short.SIZE) return Integer.class;
else if (bits > Byte.SIZE) return Short.class;
else return Byte.class;
})
);
javaTypeMap.addRule((mappings, md) ->
Optional.of(md.getTypeName())
.map(String::toUpperCase)
.filter(str -> BINARY_TYPES.matcher(str).find())
.map(str -> byte[].class)
);
}
private static Optional> patternMapper(Pattern pattern, ColumnMetaData md, Class expected) {
return Optional.of(md.getTypeName())
.map(String::toUpperCase)
.map(pattern::matcher)
.filter(Matcher::find)
.map(match -> {
final String group = match.group(1);
if (group == null) {
return expected;
} else {
final int digits = Integer.parseInt(group);
if (digits > 11) return Long.class;
else if (digits > 4) return Integer.class;
else if (digits > 2) return Short.class;
else return Byte.class;
}
});
}
@ExecuteBefore(State.RESOLVED)
void initSqlTypeMappingHelper(Injector injector) {
typeMappingHelper = SqlTypeMappingHelper.create(injector, javaTypeMap);
}
@Override
public String getDbmsInfoString(Dbms dbms) throws SQLException {
try (final Connection conn = connectionPool.getConnection(dbms)) {
final DatabaseMetaData md = conn.getMetaData();
return md.getDatabaseProductName() + ", " +
md.getDatabaseProductVersion() + ", " +
md.getDriverName() + " " +
md.getDriverVersion() + ", JDBC version " +
md.getJDBCMajorVersion() + "." +
md.getJDBCMinorVersion();
}
}
@Override
public CompletableFuture readSchemaMetadata(
Dbms dbms, ProgressMeasure progress,
Predicate filterCriteria) {
// Create a deep copy of the project document.
final Project projectCopy = projects.getProject().deepCopy();
// Make sure there are not multiple dbmses with the same id
final Set ids = new HashSet<>();
if (!projectCopy.dbmses().map(Dbms::getId).allMatch(ids::add)) {
final Set duplicates = new HashSet<>();
ids.clear();
projectCopy.dbmses()
.map(Dbms::getId)
.forEach(s -> {
if (!ids.add(s)) {
duplicates.add(s);
}
});
throw new SpeedmentException(
"The following dbmses have duplicates in the config document: "
+ duplicates
);
}
// Locate the dbms in the copy.
final Dbms dbmsCopy = projectCopy.dbmses()
.filter(d -> d.getId().equals(dbms.getId()))
.findAny().orElseThrow(() -> new SpeedmentException(
"Could not find Dbms document in copy."
));
return readSchemaMetadata(
projectCopy, dbmsCopy, progress
).whenCompleteAsync((project, ex) -> {
progress.setProgress(ProgressMeasure.DONE);
if (ex != null) {
progress.setCurrentAction("Error!");
throw new SpeedmentException("Unable to read schema metadata.", ex);
} else {
progress.setCurrentAction("Done!");
}
});
}
private CompletableFuture readSchemaMetadata(
Project project, Dbms dbms, ProgressMeasure progress) {
//final DbmsType dbmsType = dbmsTypeOf(dbmsHandlerComponent, dbms);
progress.setCurrentAction(describe(dbms));
LOGGER.info(describe(dbms));
final CompletableFuture>> typeMappingTask
= typeMappingHelper.loadFor(dbms);
final Schema schema = dbms.mutator().addNewSchema();
schema.mutator().setId("schema");
schema.mutator().setName("schema");
return readTableMetadata(schema, typeMappingTask, progress)
.thenApplyAsync($ -> project);
}
private CompletableFuture readTableMetadata(
Schema schema,
CompletableFuture>> typeMappingTask,
ProgressMeasure progress) {
final CompletableFuture tablesTask = CompletableFuture.runAsync(() -> {
final Dbms dbms = schema.getParentOrThrow();
try (final Connection connection = connectionPool.getConnection(dbms)) {
try (final ResultSet rsTable = connection.getMetaData().getTables(null, null, null, TABLES_AND_VIEWS)) {
if (SHOW_METADATA) {
final ResultSetMetaData rsmd = rsTable.getMetaData();
final int numberOfColumns = rsmd.getColumnCount();
for (int x = 1; x <= numberOfColumns; x++) {
LOGGER.debug(new StringJoiner(", ")
.add(rsmd.getColumnName(x))
.add(rsmd.getColumnClassName(x))
.add(Integer.toString(rsmd.getColumnType(x)))
.toString()
);
}
}
while (rsTable.next()) {
if (SHOW_METADATA) {
final ResultSetMetaData rsmd = rsTable.getMetaData();
final int numberOfColumns = rsmd.getColumnCount();
for (int x = 1; x <= numberOfColumns; x++) {
LOGGER.debug(rsmd.getColumnName(x) +
":'" + rsTable.getObject(x) + "'");
}
}
final Table table = schema.mutator().addNewTable();
final String tableName = rsTable.getString("TABLE_NAME");
final String tableType = rsTable.getString("TABLE_TYPE");
table.mutator().setId(tableName);
table.mutator().setName(tableName);
table.mutator().setView("VIEW".equals(tableType));
}
} catch (final SQLException ex) {
throw new SpeedmentException(format(
"Error reading results from SQLite-database '%s'.",
DocumentUtil.toStringHelper(dbms)
), ex);
}
} catch (final SQLException ex) {
throw new SpeedmentException(format(
"Error getting connection to SQLite-database '%s'.",
DocumentUtil.toStringHelper(dbms)
), ex);
}
});
return tablesTask.thenComposeAsync($ -> typeMappingTask)
.thenComposeAsync(sqlTypeMappings -> {
final Dbms dbms = schema.getParentOrThrow();
final AtomicInteger cnt = new AtomicInteger();
final double noTables = schema.tables().count();
return CompletableFuture.allOf(
schema.tables().map(table -> CompletableFuture.runAsync(() -> {
try (final Connection conn = connectionPool.getConnection(dbms)) {
progress.setCurrentAction(describe(table));
columns(conn, sqlTypeMappings, table, progress);
indexes(conn, table, progress);
foreignKeys(conn, table, progress);
primaryKeyColumns(conn, table, progress);
if (!table.isView()) {
// If no INTEGER PRIMARY KEY exists, a rowId should be created.
if (table.columns()
.filter(col -> col.getAsString(ORIGINAL_TYPE).filter("INTEGER"::equalsIgnoreCase).isPresent())
.noneMatch(col -> table.primaryKeyColumns().anyMatch(pkc -> DocumentDbUtil.isSame(pkc.findColumnOrThrow(), col)))
&& table.columns().map(Column::getId).noneMatch("rowid"::equalsIgnoreCase)) {
final Column column = table.mutator().addNewColumn();
column.mutator().setId("rowid");
column.mutator().setName("rowid");
column.mutator().setOrdinalPosition(0);
column.mutator().setDatabaseType(Long.class);
column.mutator().setAutoIncrement(true);
column.mutator().setNullable(false);
column.mutator().setTypeMapper(PrimitiveTypeMapper.class);
// When we introduce a new primary key, we need to
// add a UNIQUE index that represents the same set
// of columns as the old primary key.
if (table.primaryKeyColumns().anyMatch(pkc -> true)) {
final Set oldPks = table.primaryKeyColumns()
.map(PrimaryKeyColumn::getId)
.collect(toCollection(LinkedHashSet::new));
// Make sure such an index doesn't already exist
if (table.indexes()
.filter(Index::isUnique)
.noneMatch(idx -> oldPks.equals(idx.indexColumns()
.map(IndexColumn::getId)
.collect(toCollection(LinkedHashSet::new))
))) {
final Index pkReplacement = table.mutator().addNewIndex();
final String idxName = md5(oldPks.toString());
final IndexMutator mutator = pkReplacement.mutator();
mutator.setId(idxName);
mutator.setName(idxName);
mutator.setUnique(true);
table.primaryKeyColumns().forEachOrdered(pkc -> {
final int ordNo = 1 + (int) pkReplacement.indexColumns().count();
final IndexColumn idxCol = mutator.addNewIndexColumn();
idxCol.mutator().setId(pkc.getId());
idxCol.mutator().setName(pkc.getName());
idxCol.mutator().setOrdinalPosition(ordNo);
});
}
// Remove the existing primary key since the rowid is
// the only value that should be considered part of
// the primary key
table.getData().remove(TableUtil.PRIMARY_KEY_COLUMNS);
}
final PrimaryKeyColumn pkc = table.mutator().addNewPrimaryKeyColumn();
pkc.mutator().setId("rowid");
pkc.mutator().setName("rowid");
pkc.mutator().setOrdinalPosition(1);
} else {
table.columns()
.filter(col -> col.getAsString(ORIGINAL_TYPE).filter("INTEGER"::equalsIgnoreCase).isPresent())
.filter(col -> table.primaryKeyColumns().anyMatch(pkc -> DocumentDbUtil.isSame(pkc.findColumnOrThrow(), col)))
.forEach(col -> col.mutator().setAutoIncrement(true));
}
}
table.columns().forEach(col -> {
col.getData().remove(ORIGINAL_TYPE);
});
progress.setProgress(cnt.incrementAndGet() / noTables);
} catch (final SQLException ex) {
throw new SpeedmentException(ex);
}
})).toArray(CompletableFuture[]::new)
).thenApplyAsync(v -> schema);
});
}
private void columns(Connection conn, Map> sqlTypeMapping, Table table, ProgressMeasure progress) {
requireNonNulls(conn, sqlTypeMapping, table, progress);
final SqlSupplier supplier = () ->
conn.getMetaData().getColumns(null, null, table.getId(), null);
final TableChildMutator mutator = (column, rs) -> {
final ColumnMetaData md = ColumnMetaData.of(rs);
final String columnName = md.getColumnName();
column.getData().put(ORIGINAL_TYPE, md.getTypeName());
column.mutator().setId(columnName);
column.mutator().setName(columnName);
column.mutator().setOrdinalPosition(md.getOrdinalPosition());
column.mutator().setNullable(MetaDataUtil.isNullable(md));
column.mutator().setDatabaseType(
typeMappingHelper.findFor(sqlTypeMapping, md)
.orElseGet(() -> {
LOGGER.warn(format(
"Unable to determine mapping for table %s, " +
"column %s. Type name %s, data type %d, decimal " +
"digits %d. Fallback to JDBC-type %s",
table.getId(),
column.getId(),
md.getTypeName(),
md.getDataType(),
md.getDecimalDigits(),
Object.class.getName()
));
return Object.class;
})
);
if (!md.isDecimalDigitsNull() && md.getDecimalDigits() != 10) {
column.mutator().setDecimalDigits(md.getDecimalDigits());
}
if (!md.isColumnSizeNull() && md.getColumnSize() != 2_000_000_000) {
column.mutator().setColumnSize(md.getColumnSize());
}
if (isAutoIncrement(md)) {
column.mutator().setAutoIncrement(true);
}
if (!column.isNullable() && isWrapper(column.findDatabaseType())) {
column.mutator().setTypeMapper(TypeMapper.primitive().getClass());
}
if ("ENUM".equalsIgnoreCase(md.getTypeName())) {
final Dbms dbms = DocumentUtil.ancestor(table, Dbms.class).get();
final List constants = enumConstantsOf(dbms, table, columnName);
column.mutator().setEnumConstants(constants.stream().collect(joining(",")));
}
progress.setCurrentAction(describe(column));
};
tableChilds(
table,
Column.class,
table.mutator()::addNewColumn,
supplier,
rsChild -> ColumnMetaData.of(rsChild).getColumnName(),
mutator
);
}
private void primaryKeyColumns(Connection conn, Table table, ProgressMeasure progress) {
requireNonNulls(conn, table, progress);
final SqlSupplier supplier = () ->
conn.getMetaData().getPrimaryKeys(null, null, table.getId());
final TableChildMutator mutator = (pkc, rs) -> {
final String columnName = rs.getString("COLUMN_NAME");
pkc.mutator().setId(columnName);
pkc.mutator().setName(columnName);
pkc.mutator().setOrdinalPosition(rs.getInt("KEY_SEQ"));
};
tableChilds(
table,
PrimaryKeyColumn.class,
table.mutator()::addNewPrimaryKeyColumn,
supplier,
rsChild -> rsChild.getString("COLUMN_NAME"),
mutator
);
if (!table.isView() && table.primaryKeyColumns().noneMatch(pk -> true)) {
LOGGER.warn(format("Table '%s' does not have any primary key.",
table.getId()));
}
}
private void indexes(Connection conn, Table table, ProgressMeasure progress) {
requireNonNulls(conn, table, progress);
if (table.isView() && IGNORE_VIEW_INDEXES) return;
final SqlSupplier supplier = () ->
conn.getMetaData().getIndexInfo(null, null, table.getId(), false,
APPROXIMATE_INDEX);
final TableChildMutator mutator = (index, rs) -> {
final String indexName = rs.getString("INDEX_NAME");
index.mutator().setId(indexName);
index.mutator().setName(indexName);
index.mutator().setUnique(!rs.getBoolean("NON_UNIQUE"));
final IndexColumn indexColumn = index.mutator().addNewIndexColumn();
final String columnName = rs.getString("COLUMN_NAME");
indexColumn.mutator().setId(columnName);
indexColumn.mutator().setName(columnName);
indexColumn.mutator().setOrdinalPosition(rs.getInt("ORDINAL_POSITION"));
indexColumn.mutator().setOrderType(getOrderType(rs));
};
final SqlPredicate filter = rs -> rs.getString("INDEX_NAME") != null;
tableChilds(
table,
Index.class,
table.mutator()::addNewIndex,
supplier,
rsChild -> rsChild.getString("INDEX_NAME"),
mutator,
filter
);
}
private void foreignKeys(Connection conn, Table table, ProgressMeasure progress) {
requireNonNulls(conn, table);
final Schema schema = table.getParentOrThrow();
final SqlSupplier supplier = () ->
conn.getMetaData().getImportedKeys(null, null, table.getId());
final Set fksThatNeedNewNames = new LinkedHashSet<>();
final TableChildMutator mutator = (foreignKey, rs) -> {
final String foreignKeyName = rs.getString("FK_NAME");
if (foreignKeyName == null || foreignKeyName.trim().isEmpty()) {
if (rs.getInt("KEY_SEQ") == 1) {
final String uniqueName = UUID.randomUUID().toString();
foreignKey.mutator().setId(uniqueName);
foreignKey.mutator().setName(uniqueName);
fksThatNeedNewNames.add(uniqueName);
}
} else {
foreignKey.mutator().setId(foreignKeyName);
foreignKey.mutator().setName(foreignKeyName);
}
final ForeignKeyColumn foreignKeyColumn = foreignKey.mutator().addNewForeignKeyColumn();
final ForeignKeyColumnMutator fkcMutator = foreignKeyColumn.mutator();
final String fkColumnName = rs.getString("FKCOLUMN_NAME");
fkcMutator.setId(fkColumnName);
fkcMutator.setName(fkColumnName);
fkcMutator.setOrdinalPosition(rs.getInt("KEY_SEQ"));
fkcMutator.setForeignTableName(rs.getString("PKTABLE_NAME"));
fkcMutator.setForeignColumnName(rs.getString("PKCOLUMN_NAME"));
// FKs always point to the same DBMS but can
// be changed to another one using the config
fkcMutator.setForeignDatabaseName(schema.getParentOrThrow().getId());
fkcMutator.setForeignSchemaName("schema");
};
tableChilds(
table,
ForeignKey.class,
table.mutator()::addNewForeignKey,
supplier,
rsChild -> rsChild.getInt("KEY_SEQ") == 1 ? ""
: fksThatNeedNewNames.stream()
.skip(fksThatNeedNewNames.size() - 1L)
.findFirst().orElseThrow(IllegalStateException::new),
mutator
);
// Fix foreign keys without any id by finding an index with the same
// set of columns.
table.foreignKeys()
.filter(fk -> fksThatNeedNewNames.contains(fk.getId()))
.forEach(fk -> {
final Set thisSet = fk.foreignKeyColumns()
.map(ForeignKeyColumn::getId)
.collect(toSet());
final Optional found = table.indexes()
.filter(idx -> idx.indexColumns()
.map(IndexColumn::getId)
.collect(toSet())
.equals(thisSet)
).findFirst();
if (found.isPresent()) {
fk.mutator().setId(found.get().getId());
fk.mutator().setName(found.get().getName());
} else {
final String randName = md5(thisSet.toString());
fk.mutator().setId(randName);
fk.mutator().setName(randName);
LOGGER.error(format(
"Found a foreign key in table '%s' with no name. " +
"Assigning it a random name '%s'",
table.getId(), randName));
}
});
}
private static String md5(String str) {
try {
final MessageDigest md = MessageDigest.getInstance("MD5");
final byte[] mdbytes = md.digest(str.getBytes(StandardCharsets.UTF_8));
final StringBuilder sb = new StringBuilder();
for (byte mdbyte : mdbytes) {
sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException("MD5 algorithm not supported.", ex);
}
}
private void tableChilds(
final Table table,
final Class childType,
final Supplier childSupplier,
final SqlSupplier resultSetSupplier,
final SqlFunction childIdGetter,
final TableChildMutator resultSetMutator) {
tableChilds(table, childType, childSupplier, resultSetSupplier,
childIdGetter, resultSetMutator, rs -> true);
}
private void tableChilds(
final Table table,
final Class childType,
final Supplier childSupplier,
final SqlSupplier resultSetSupplier,
final SqlFunction childIdGetter,
final TableChildMutator resultSetMutator,
final SqlPredicate filter) {
requireNonNulls(childSupplier, resultSetSupplier, resultSetMutator);
try (final ResultSet rsChild = resultSetSupplier.get()) {
if (SHOW_METADATA) {
final ResultSetMetaData rsmd = rsChild.getMetaData();
final int numberOfColumns = rsmd.getColumnCount();
for (int x = 1; x <= numberOfColumns; x++) {
final int columnType = rsmd.getColumnType(x);
LOGGER.info(x + ":" + rsmd.getColumnName(x) + ", " +
rsmd.getColumnClassName(x) + ", " + columnType);
}
}
while (rsChild.next()) {
if (SHOW_METADATA) {
final ResultSetMetaData rsmd = rsChild.getMetaData();
final int numberOfColumns = rsmd.getColumnCount();
for (int x = 1; x <= numberOfColumns; x++) {
final Object val = rsChild.getObject(x);
LOGGER.info(x + ":" + rsmd.getColumnName(x) + ":'" +
val + "'");
}
}
if (filter.test(rsChild)) {
final String id = childIdGetter.apply(rsChild);
final Optional existing = DocumentDbUtil.typedChildrenOf(table)
.filter(childType::isInstance)
.map(childType::cast)
.filter(idx -> id.equals(idx.getId()))
.findAny();
final T child = existing.orElseGet(childSupplier);
resultSetMutator.mutate(child, rsChild);
} else {
LOGGER.info("Skipped due to RS filtering. This is normal " +
"for some DBMS types.");
}
}
} catch (final SQLException sqle) {
LOGGER.error(sqle, "Unable to read table child.");
throw new SpeedmentException(sqle);
}
}
/**
* Queries the database for a list of ENUM constants belonging to the specified table and
* column.
*
* @param dbms the dbms
* @param table the table
* @param columnName the column name
* @return list of enum constants.
* @throws SQLException if an error occured
*/
private List enumConstantsOf(Dbms dbms, Table table, String columnName) throws SQLException {
final DatabaseNamingConvention naming = dbmsType.getDatabaseNamingConvention();
final String sql = String.format(
"show columns from %s where field=%s;",
naming.fullNameOf(table),
naming.quoteField(columnName)
);
try (final Connection conn = connectionPool.getConnection(dbms);
final PreparedStatement ps = conn.prepareStatement(sql);
final ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
final String fullResult = rs.getString(2);
if (fullResult.startsWith("enum('")
&& fullResult.endsWith("')")) {
final String middle = fullResult.substring(5, fullResult.length() - 1);
return Stream.of(middle.split(","))
.map(s -> s.substring(1, s.length() - 1))
.filter(s -> !s.isEmpty())
.collect(toList());
} else {
throw new SpeedmentException("Unexpected response (" + fullResult + ").");
}
} else {
throw new SpeedmentException("Expected an result.");
}
}
}
@FunctionalInterface
protected interface TableChildMutator {
void mutate(T t, ResultSet u) throws SQLException;
}
}