ru.curs.celesta.dbutils.adaptors.DBAdaptor Maven / Gradle / Ivy
The newest version!
/*
(с) 2013 ООО "КУРС-ИТ"
Этот файл — часть КУРС:Celesta.
КУРС:Celesta — свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Стандартной общественной лицензии GNU в том виде, в каком
она была опубликована Фондом свободного программного обеспечения; либо
версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Стандартной
общественной лицензии GNU.
Вы должны были получить копию Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см. http://www.gnu.org/licenses/.
Copyright 2013, COURSE-IT Ltd.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
*/
package ru.curs.celesta.dbutils.adaptors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.curs.celesta.CelestaException;
import ru.curs.celesta.ConnectionPool;
import ru.curs.celesta.DBType;
import ru.curs.celesta.dbutils.QueryBuildingHelper;
import ru.curs.celesta.dbutils.adaptors.column.ColumnDefiner;
import ru.curs.celesta.dbutils.adaptors.column.ColumnDefinerFactory;
import ru.curs.celesta.dbutils.adaptors.ddl.DdlAdaptor;
import ru.curs.celesta.dbutils.adaptors.ddl.DdlConsumer;
import ru.curs.celesta.dbutils.adaptors.ddl.DdlGenerator;
import ru.curs.celesta.dbutils.meta.DbColumnInfo;
import ru.curs.celesta.dbutils.meta.DbFkInfo;
import ru.curs.celesta.dbutils.meta.DbIndexInfo;
import ru.curs.celesta.dbutils.meta.DbPkInfo;
import ru.curs.celesta.dbutils.meta.DbSequenceInfo;
import ru.curs.celesta.dbutils.query.FromClause;
import ru.curs.celesta.dbutils.stmt.ParameterSetter;
import ru.curs.celesta.event.TriggerQuery;
import ru.curs.celesta.score.BasicTable;
import ru.curs.celesta.score.BinaryColumn;
import ru.curs.celesta.score.BooleanColumn;
import ru.curs.celesta.score.Column;
import ru.curs.celesta.score.ColumnMeta;
import ru.curs.celesta.score.DataGrainElement;
import ru.curs.celesta.score.DateTimeColumn;
import ru.curs.celesta.score.DecimalColumn;
import ru.curs.celesta.score.FKRule;
import ru.curs.celesta.score.FloatingColumn;
import ru.curs.celesta.score.ForeignKey;
import ru.curs.celesta.score.Grain;
import ru.curs.celesta.score.Index;
import ru.curs.celesta.score.IntegerColumn;
import ru.curs.celesta.score.MaterializedView;
import ru.curs.celesta.score.ParameterizedView;
import ru.curs.celesta.score.ParseException;
import ru.curs.celesta.score.SQLGenerator;
import ru.curs.celesta.score.SequenceElement;
import ru.curs.celesta.score.StringColumn;
import ru.curs.celesta.score.Table;
import ru.curs.celesta.score.TableElement;
import ru.curs.celesta.score.VersionedElement;
import ru.curs.celesta.score.View;
import ru.curs.celesta.score.ZonedDateTimeColumn;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
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.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static ru.curs.celesta.dbutils.adaptors.function.CommonFunctions.getFieldList;
import static ru.curs.celesta.dbutils.adaptors.function.CommonFunctions.padComma;
import static ru.curs.celesta.dbutils.jdbc.SqlUtils.executeQuery;
import static ru.curs.celesta.dbutils.jdbc.SqlUtils.executeUpdate;
/**
* Adaptor for connection to the database.
*/
public abstract class DBAdaptor implements QueryBuildingHelper, StaticDataAdaptor {
/*
* N.B. for contributors. This class is big, so to avoid chaos,
* here is the order of (except constructors and factory methods):
* first of all -- public final methods,
* then -- package-private static methods,
* then -- package-private final methods,
* then -- package-private methods,
* then -- package-private abstract methods,
* then -- public static methods,
* then -- public final methods,
* then -- public methods,
* then -- public abstract methods,
* then -- private methods
*/
static final List>> COLUMN_CLASSES = Arrays.asList(
IntegerColumn.class,
StringColumn.class,
BooleanColumn.class,
FloatingColumn.class,
DecimalColumn.class,
BinaryColumn.class,
DateTimeColumn.class,
ZonedDateTimeColumn.class);
static final String COLUMN_NAME = "COLUMN_NAME";
private static final Logger LOGGER = LoggerFactory.getLogger(DBAdaptor.class);
protected final ConnectionPool connectionPool;
DdlAdaptor ddlAdaptor;
protected DBAdaptor(ConnectionPool connectionPool, DdlConsumer ddlConsumer) {
this.connectionPool = connectionPool;
this.ddlAdaptor = new DdlAdaptor(getDdlGenerator(), ddlConsumer);
connectionPool.setDbAdaptor(this);
}
abstract DdlGenerator getDdlGenerator();
// =========> PACKAGE-PRIVATE STATIC METHODS <=========
/**
* Creates a PreparedStatement object.
*
* @param conn Connection to use.
* @param sql SQL statement.
* @return new default PreparedStatement object.
*/
static PreparedStatement prepareStatement(Connection conn, String sql) {
try {
return conn.prepareStatement(sql);
} catch (SQLException e) {
throw new CelestaException(e.getMessage(), e);
}
}
/**
* Transforms {@link Iterable} of field names into comma separated {@link String} field names.
* Binary fields are excluded from result.
*
* @param t the {@link DataGrainElement} type, that's owner of fields.
* @param fields {@link Iterable} fields to transform.
* @return Comma separated {@link String} field names.
*/
static String getTableFieldsListExceptBlobs(DataGrainElement t, Set fields) {
final List flds;
Predicate> notBinary = c -> !BinaryColumn.CELESTA_TYPE.equals(c.getCelestaType());
if (fields.isEmpty()) {
flds = t.getColumns().entrySet().stream()
.filter(e -> notBinary.test(e.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
} else {
flds = fields.stream()
.filter(f -> notBinary.test(t.getColumns().get(f)))
.collect(Collectors.toList());
}
// To the list of fields of the versioned tables we necessarily add "recversion"
if (t instanceof Table && ((Table) t).isVersioned()) {
flds.add(VersionedElement.REC_VERSION);
}
return getFieldList(flds);
}
/**
* Returns {@link FKRule} by input string rule.
* The method is case-insensitive for rule param.
*
* @param rule input string.
* @return Returns one of the values of {@link FKRule} or null in case of invalid input.
*/
static FKRule getFKRule(String rule) {
if ("NO ACTION".equalsIgnoreCase(rule) || "RECTRICT".equalsIgnoreCase(rule)) {
return FKRule.NO_ACTION;
}
if ("SET NULL".equalsIgnoreCase(rule)) {
return FKRule.SET_NULL;
}
if ("CASCADE".equalsIgnoreCase(rule)) {
return FKRule.CASCADE;
}
return null;
}
/**
* Executes sql query and then adds a column values with index 1 to {@link Set} to return.
*
* @param conn Connection to use.
* @param sql Sql query to execute.
* @return {@link Set} with values of column with index 1,
* which were received as a result of the sql query.
*/
static Set sqlToStringSet(Connection conn, String sql) {
Set result = new HashSet<>();
executeQuery(conn, sql, rs -> {
while (rs.next()) {
result.add(rs.getString(1));
}
});
return result;
}
// =========> END PACKAGE-PRIVATE STATIC METHODS <=========
// =========> PACKAGE-PRIVATE FINAL METHODS <=========
/**
* Return String representation of sql query to select data with "ORDER BY" expression.
*
* @param from FROM metadata.
* @param whereClause WHERE clause to use in resulting query.
* @param orderBy ORDER BY clause to use in resulting query.
* @param fields fields for select by a resulting query.
* @return Return String representation of sql query to select data with "ORDER BY" expression.
*/
final String getSelectFromOrderBy(
FromClause from, String whereClause, String orderBy, Set fields
) {
final String fieldList = getTableFieldsListExceptBlobs(from.getGe(), fields);
String sqlfrom = String.format("select %s from %s", fieldList,
from.getExpression());
String sqlwhere = "".equals(whereClause) ? "" : " where " + whereClause;
return sqlfrom + sqlwhere + " order by " + orderBy;
}
// =========> END PACKAGE-PRIVATE FINAL METHODS <=========
// =========> PACKAGE-PRIVATE METHODS <=========
/**
* Returns FROM clause for selection of a constant in SQL.
*/
String constantFromSql() {
return "";
}
/**
* Database specific preparation of column for select static method.
*
* @param value to be selected
* @param colName name of the column
* @param maxStringLength maximum length
*/
String prepareRowColumnForSelectStaticStrings(String value, String colName, int maxStringLength) {
return "? as " + colName;
}
final ColumnDefiner getColumnDefiner(Class extends Column>> c) {
return ColumnDefinerFactory.getColumnDefiner(getType(), c);
}
// =========> END PACKAGE-PRIVATE METHODS <=========
// =========> PACKAGE-PRIVATE ABSTRACT METHODS <=========
/**
* Builds SELECT expression that selects restricted amount of records starting
* from an offset.
*
* @param from from clause of the SQL
* @param whereClause where clause of the SQL
* @param orderBy order by clause of the SQL
* @param offset offset
* @param rowCount row count to remove
* @param fields fields to select
*/
abstract String getLimitedSQL(
FromClause from, String whereClause, String orderBy, long offset, long rowCount, Set fields
);
/**
* Returns trigger body for the given query.
*
* @param query query
*/
abstract String getSelectTriggerBodySql(TriggerQuery query);
/**
* Whether user defined tables exist in the DB.
*
* @param conn DB connection
*/
abstract boolean userTablesExist(Connection conn) throws SQLException;
/**
* Creates DB schema if it is absent.
*
* @param conn DB connection
* @param name schema name
*/
abstract void createSchemaIfNotExists(Connection conn, String name);
// =========> END PACKAGE-PRIVATE ABSTRACT METHODS <=========
// =========> PUBLIC STATIC METHODS <=========
// =========> END PUBLIC STATIC METHODS <=========
// =========> PUBLIC FINAL METHODS <=========
/**
* Deletes table from RDBMS.
*
* @param conn Connection to use.
* @param t TableElement metadata of deleting table provided by Celesta.
*/
public final void dropTable(Connection conn, TableElement t) {
this.ddlAdaptor.dropTable(conn, t);
}
/**
* Returns {@code true} in that and only that case if DB contains user tables
* (i.e. DB is not empty).
*/
public final boolean userTablesExist() {
try (Connection conn = connectionPool.get()) {
return userTablesExist(conn);
} catch (SQLException e) {
throw new CelestaException(e.getMessage(), e);
}
}
/**
* Creates DB schema with the specified name if such didn't exist before.
*
* @param name schema name.
*/
public final void createSchemaIfNotExists(String name) {
try (Connection conn = connectionPool.get()) {
createSchemaIfNotExists(conn, name);
conn.commit();
} catch (SQLException e) {
throw new CelestaException("Cannot create schema. " + e.getMessage(), e);
}
}
/**
* Adds a new column to the table.
*
* @param conn DB connection
* @param c column
*/
public final void createColumn(Connection conn, Column> c) {
this.ddlAdaptor.createColumn(conn, c);
}
// CHECKSTYLE:OFF 6 parameters
/**
* Builds prepared statement for records UPDATE.
*
* {@code equalsMask[columnIndex]} should contain {@code true} for the column with
* index equal to {@code columnIndex} to take part in the evaluation.
* If {@code nullsMask[columnIndex]} contains {@code true} {@code IS NULL} check
* has a priority above {@code program[columnIndex]} check - {@code column = ?}.
*
* @param conn DB connection
* @param t updatable table
* @param equalsMask equals mask
* @param nullsMask nulls mask
* @param program collects parameter predicates
* @param where WHERE clause
*/
public final PreparedStatement getUpdateRecordStatement(
Connection conn, BasicTable t, boolean[] equalsMask,
boolean[] nullsMask, List program, String where) {
// CHECKSTYLE:ON
StringBuilder setClause = new StringBuilder();
if (t instanceof Table && ((Table) t).isVersioned()) {
setClause.append(String.format("\"%s\" = ?", VersionedElement.REC_VERSION));
program.add(ParameterSetter.createForRecversion(this));
}
int i = 0;
for (String c : t.getColumns().keySet()) {
// Пропускаем ключевые поля и поля, не изменившие своего значения
if (!(equalsMask[i] || t.getPrimaryKey().containsKey(c))) {
padComma(setClause);
if (nullsMask[i]) {
setClause.append(String.format("\"%s\" = NULL", c));
} else {
setClause.append(String.format("\"%s\" = ?", c));
program.add(ParameterSetter.create(t.getColumnIndex(c), this));
}
}
i++;
}
String sql = String.format("update " + tableString(t.getGrain().getName(), t.getName()) + " set %s where %s",
setClause, where);
LOGGER.trace(sql);
return prepareStatement(conn, sql);
}
/**
* Creates a table index in the DB.
*
* @param conn DB connection
* @param index table index
*/
public final void createIndex(Connection conn, Index index) {
this.ddlAdaptor.createIndex(conn, index);
}
/**
* Creates a foreign key in the DB.
*
* @param conn DB connection
* @param fk foreign key
*/
public final void createFK(Connection conn, ForeignKey fk) {
this.ddlAdaptor.createFk(conn, fk);
}
/**
* Removes table index in the grain.
*
* @param g Grain
* @param dBIndexInfo Information on index
*/
public final void dropIndex(Grain g, DbIndexInfo dBIndexInfo) {
try (Connection conn = connectionPool.get()) { //TODO: Why there is a new Connection instance
ddlAdaptor.dropIndex(conn, g, dBIndexInfo);
conn.commit();
} catch (CelestaException | SQLException e) {
throw new CelestaException("Cannot drop index '%s': %s ", dBIndexInfo.getIndexName(), e.getMessage());
}
}
/**
* Returns {@link PreparedStatement} containing a filtered set of entries.
*
* @param conn Connection
* @param from Object for forming FROM part of the query
* @param whereClause Where clause
* @param orderBy Sort order
* @param offset Number of entries to skip
* @param rowCount Number of entries to return (limit filter)
* @param fields Requested columns. If none are provided all columns are requested
*/
// CHECKSTYLE:OFF 7 parameters
public final PreparedStatement getRecordSetStatement(
Connection conn, FromClause from, String whereClause,
String orderBy, long offset, long rowCount, Set fields
) {
// CHECKSTYLE:ON
String sql;
if (offset == 0 && rowCount == 0) {
// The query is not limited -- it is same for all DBMS
// Joining all received components into a standard query
// SELECT..FROM..WHERE..ORDER BY
sql = getSelectFromOrderBy(from, whereClause, orderBy, fields);
} else {
sql = getLimitedSQL(from, whereClause, orderBy, offset, rowCount, fields);
LOGGER.trace(sql);
}
try {
PreparedStatement result = conn.prepareStatement(sql);
return result;
} catch (SQLException e) {
throw new CelestaException(e.getMessage(), e);
}
}
/**
* Builds a SELECT COUNT statement.
*
* @param conn Connection
* @param from From clause
* @param whereClause Where clause
*/
public final PreparedStatement getSetCountStatement(Connection conn, FromClause from, String whereClause) {
String sql = "select count(*) from " + from.getExpression()
+ ("".equals(whereClause) ? "" : " where " + whereClause);
PreparedStatement result = prepareStatement(conn, sql);
return result;
}
/**
* Drops a trigger from DB.
*
* @param conn Connection
* @param query Trigger query
*/
public final void dropTrigger(Connection conn, TriggerQuery query) {
ddlAdaptor.dropTrigger(conn, query);
}
public final void updateVersioningTrigger(Connection conn, TableElement t) {
ddlAdaptor.updateVersioningTrigger(conn, t);
}
/**
* Creates primary key on a table.
*
* @param conn DB connection
* @param t table
*/
public final void createPK(Connection conn, TableElement t) {
this.ddlAdaptor.createPk(conn, t);
}
public final SQLGenerator getViewSQLGenerator() {
return this.ddlAdaptor.getViewSQLGenerator();
}
/**
* Creates a view in the DB based on metadata.
*
* @param conn DB connection
* @param v View
*/
public final void createView(Connection conn, View v) {
this.ddlAdaptor.createView(conn, v);
}
public final void createParameterizedView(Connection conn, ParameterizedView pv) {
this.ddlAdaptor.createParameterizedView(conn, pv);
}
public final void dropTableTriggersForMaterializedViews(Connection conn, BasicTable t) {
this.ddlAdaptor.dropTableTriggersForMaterializedViews(conn, t);
}
public final void createTableTriggersForMaterializedViews(Connection conn, BasicTable t) {
this.ddlAdaptor.createTableTriggersForMaterializedViews(conn, t);
}
public final void executeNative(Connection conn, String sql) {
this.ddlAdaptor.executeNative(conn, sql);
}
// =========> END PUBLIC FINAL METHODS <=========
// =========> PUBLIC METHODS <=========
/**
* Checking for connection validity.
*
* @param conn connection
* @param timeout time-out
* @return {@code true} if connection is valid, otherwise {@code false}
*/
public boolean isValidConnection(Connection conn, int timeout) {
try {
return conn.isValid(timeout);
} catch (SQLException e) {
throw new CelestaException(e.getMessage(), e);
}
}
/**
* Returns template by table name.
*
* @param schemaName schema name
* @param tableName table name
*/
public String tableString(String schemaName, String tableName) {
return getSchemaDotNameQuotedTemplate(schemaName, tableName);
}
private String getSchemaDotNameQuotedTemplate(String schemaName, String name) {
StringBuilder sb = new StringBuilder();
if (schemaName.startsWith("\"")) {
sb.append(schemaName);
} else {
sb.append("\"").append(schemaName).append("\"");
}
sb.append(".");
if (name.startsWith("\"")) {
sb.append(name);
} else {
sb.append("\"").append(name).append("\"");
}
return sb.toString();
}
/**
* Returns template by sequence name.
*
* @param schemaName schema name
* @param sequenceName sequence name
*/
public String sequenceString(String schemaName, String sequenceName) {
return getSchemaDotNameQuotedTemplate(schemaName, sequenceName);
}
/**
* Returns DB specific PK constraint name for a table element.
*
* @param tableElement table element
*/
public String pkConstraintString(TableElement tableElement) {
return tableElement.getPkConstraintName();
}
/**
* Creates a table "from scratch" in the database.
*
* @param conn Connection
* @param te Table for creation.
* tables also in case if such table exists.
*/
public void createTable(Connection conn, TableElement te) {
ddlAdaptor.createTable(conn, te);
}
/**
* Returns a set of column names for a specific table.
*
* @param conn DB connection
* @param t Table to look the columns in.
*/
public Set getColumns(Connection conn, TableElement t) {
Set result = new LinkedHashSet<>();
try (
ResultSet rs = conn.getMetaData()
.getColumns(null, t.getGrain().getName(), t.getName(), null)
) {
while (rs.next()) {
String rColumnName = rs.getString(COLUMN_NAME);
result.add(rColumnName);
}
} catch (SQLException e) {
throw new CelestaException(e);
}
return result;
}
/**
* Drops a foreign key from the database.
*
* @param conn DB connection
* @param schemaName schema name
* @param tableName table possessing the foreign key
* @param fkName name of foreign key
*/
public void dropFK(Connection conn, String schemaName, String tableName, String fkName) {
try {
this.ddlAdaptor.dropFK(conn, schemaName, tableName, fkName);
} catch (CelestaException e) {
throw new CelestaException("Cannot drop foreign key '%s': %s", fkName, e.getMessage());
}
}
/**
* Drops parameterized view from the database.
*
* @param conn DB connection
* @param schemaName schema name
* @param viewName view name
*/
public void dropParameterizedView(Connection conn, String schemaName, String viewName) {
this.ddlAdaptor.dropParameterizedView(conn, schemaName, viewName);
}
/**
* Returns list of view names in the grain.
*
* @param conn DB connection
* @param g Grain for which the list of view names have to be returned.
*/
public List getViewList(Connection conn, Grain g) {
String sql = String.format("select table_name from information_schema.views where table_schema = '%s'",
g.getName());
List result = new ArrayList<>();
executeQuery(conn, sql, rs -> {
while (rs.next()) {
result.add(rs.getString(1));
}
}, "Cannot get views list");
return result;
}
/**
* Prepares a function call expression.
*
* @param pv current parameterized view
*/
public String getCallFunctionSql(ParameterizedView pv) {
return String.format(
tableString(pv.getGrain().getName(), pv.getName()) + "(%s)",
pv.getParameters().keySet().stream()
.map(p -> "?")
.collect(Collectors.joining(", "))
);
}
/**
* Creates a sequence in the database.
*
* @param conn DB connection
* @param s sequence element
*/
public void createSequence(Connection conn, SequenceElement s) {
ddlAdaptor.createSequence(conn, s);
}
/**
* Alters sequence in the database.
*
* @param conn DB connection
* @param s sequence element
*/
public void alterSequence(Connection conn, SequenceElement s) {
ddlAdaptor.alterSequence(conn, s);
}
/**
* Drops sequence from the database.
*
* @param conn DB connection
* @param s sequence element
*/
public void dropSequence(Connection conn, SequenceElement s) {
String sql = String.format("DROP SEQUENCE " + sequenceString(s.getGrain().getName(), s.getName()));
executeUpdate(conn, sql);
}
/**
* Drops view.
*
* @param conn DB connection
* @param schemaName grain name
* @param viewName view name
*/
public void dropView(Connection conn, String schemaName, String viewName) {
ddlAdaptor.dropView(conn, schemaName, viewName);
}
/**
* Creates or recreates other system objects (stored procedures, functions)
* needed for Celesta functioning on current RDBMS.
*
* @param conn DB connection
* @param sysSchemaName system schema name
*/
public void createSysObjects(Connection conn, String sysSchemaName) {
}
/**
* Translates Celesta date literal to the one from specific database.
*
* @param date Date literal
*/
public String translateDate(String date) {
try {
DateTimeColumn.parseISODate(date);
} catch (ParseException e) {
throw new CelestaException(e.getMessage());
}
return date;
}
/**
* Returns body of the trigger existing in the database.
*
* @param conn Connection
* @param query Trigger query
* @return Trigger body or empty optional if not exists.
*/
public Optional getTriggerBody(Connection conn, TriggerQuery query) {
String sql = getSelectTriggerBodySql(query);
return executeQuery(conn, sql, rs -> {
Optional result;
if (rs.next()) {
result = Optional.ofNullable(rs.getString(1));
} else {
result = Optional.empty();
}
return result;
}, String.format("Failed to select body of trigger %s", query.getName()));
}
/**
* Initializes data for a newly crated materialized view.
*
* @param conn connection
* @param mv current materialized view
*/
public void initDataForMaterializedView(Connection conn, MaterializedView mv) {
this.ddlAdaptor.initDataForMaterializedView(conn, mv);
}
/**
* Returned strings ordered by the database according to the current collation. This method is required
* for operations that depend on collation rules.
*
* @param data List of String to select
* @param columnName name of result column
* @param orderByDirection expression to concatenate after "ORDER BY" (ASC or DESC)
* @return list of strings sorted according to the current collation rule.
*/
@Override
public List selectStaticStrings(
List data, String columnName, String orderByDirection) {
int maxStringLength = data.stream().mapToInt(String::length).max().getAsInt();
//prepare sql
String sql = data.stream().map(
str -> {
String rowStr = prepareRowColumnForSelectStaticStrings(str, columnName, maxStringLength);
return String.format("SELECT %s %s", rowStr, constantFromSql());
})
.collect(Collectors.joining(" UNION ALL "));
if (orderByDirection != null && !orderByDirection.isEmpty()) {
sql = sql + " " + this.orderByForSelectStaticStrings(columnName, orderByDirection);
}
try (Connection conn = connectionPool.get();
PreparedStatement ps = conn.prepareStatement(sql)
) {
//fill preparedStatement
AtomicInteger paramCounter = new AtomicInteger(1);
data.forEach(
str -> {
try {
ps.setString(paramCounter.getAndIncrement(), str);
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
//execute query and parse result
try (ResultSet rs = ps.executeQuery()) {
List result = new ArrayList<>();
while (rs.next()) {
String str = rs.getString(1);
result.add(str);
}
return result;
}
} catch (SQLException e) {
throw new CelestaException("Can't select static data", e);
}
}
/**
* Generates ORDER BY clause for auxiliary strings selector.
*
* @param columnName selected column
* @param orderByDirection direction of ORDER BY
*/
String orderByForSelectStaticStrings(String columnName, String orderByDirection) {
return String.format("ORDER BY %s %s", columnName, orderByDirection);
}
/**
* Compare strings using database according to current collation rules.
*
* @param left left string
* @param right right string
* @return result of comparison
*/
@Override
public int compareStrings(String left, String right) {
List comparisons = Arrays.asList("<", "=", ">");
int maxStringLength = Math.max(left.length(), right.length());
String sql = comparisons.stream()
.map(comparison ->
"SELECT COUNT(*) "
+ " FROM ( SELECT " + prepareRowColumnForSelectStaticStrings("?", "a", maxStringLength)
+ " " + constantFromSql() + ") r "
+ " WHERE a " + comparison + " ?"
)
.collect(Collectors.joining(" UNION ALL "));
try (Connection conn = connectionPool.get();
PreparedStatement ps = conn.prepareStatement(sql)
) {
for (int i = 1; i < comparisons.size() * 2; i += 2) {
ps.setString(i, left);
ps.setString(i + 1, right);
}
try (ResultSet rs = ps.executeQuery()) {
int result = -1;
while (rs.next()) {
boolean compareResult = rs.getBoolean(1);
if (compareResult) {
break;
}
++result;
}
return result;
}
} catch (Exception e) {
throw new CelestaException("Can't compare strings", e);
}
}
/**
* Whether DB supports cortege comparing.
*/
@Override
public boolean supportsCortegeComparing() {
return false;
}
/**
* Drops primary key from the table by using known name of the primary key.
*
* @param conn DB connection
* @param t Table
* @param pkName name of the primary key
*/
public void dropPk(Connection conn, TableElement t, String pkName) {
ddlAdaptor.dropPk(conn, t, pkName);
}
/**
* Updates a table column.
*
* @param conn DB connection
* @param c Column to update
* @param actual Actual column info
*/
public void updateColumn(Connection conn, Column> c, DbColumnInfo actual) {
ddlAdaptor.updateColumn(conn, c, actual);
}
@Override
public ZonedDateTime prepareZonedDateTimeForParameterSetter(Connection conn, ZonedDateTime z) {
return z;
}
// =========> END PUBLIC METHODS <=========
// =========> PUBLIC ABSTRACT METHODS <=========
/**
* Returns navigable PreparedStatement by a filtered set of records.
*
* @param conn Connection
* @param from From clause
* @param orderBy Sorting order (ascending or descending)
* @param navigationWhereClause Navigable set condition (from current record)
* @param fields Fields of selection
* @param offset First record offset
*/
public abstract PreparedStatement getNavigationStatement(
Connection conn, FromClause from, String orderBy,
String navigationWhereClause, Set fields, long offset
);
/**
* Checks if table exists in the DB.
*
* @param conn DB connection
* @param schema schema name
* @param name table name
*/
public abstract boolean tableExists(Connection conn, String schema, String name);
/**
* Checks if trigger exists in the DB.
*
* @param conn DB connection.
* @param query trigger query parameters
* @throws SQLException thrown if resulting query fails
*/
public abstract boolean triggerExists(Connection conn, TriggerQuery query) throws SQLException;
/**
* Creates a PreparedStatement object for a SELECT statement containing at most one record.
*
* @param conn DB connection
* @param t table
* @param where WHERE condition
* @param fields fields of selection
*/
public abstract PreparedStatement getOneRecordStatement(Connection conn, TableElement t,
String where, Set fields);
/**
* Creates a PreparedStatement object for a SELECT statement of a single column containing
* at most one record.
*
* @param conn DB connection
* @param c column to select
* @param where WHERE condition
*/
public abstract PreparedStatement getOneFieldStatement(Connection conn, Column> c, String where);
/**
* Creates a PreparedStatement object for a DELETE statement for deleting a set of records that
* satisfy a condition.
*
* @param conn DB connection
* @param t table
* @param where condition
*/
public abstract PreparedStatement deleteRecordSetStatement(Connection conn, TableElement t, String where);
/**
* Creates a PreparedStatement object for an INSERT statement to insert a record into a table.
*
* @param conn DB connection
* @param t table
* @param nullsMask null-flags (if set the corresponding field at n-th position becomes {@code null})
* @param program collects parameters that can be set with the query
*/
public abstract PreparedStatement getInsertRecordStatement(Connection conn, BasicTable t, boolean[] nullsMask,
List program);
/**
* Returns current identity value for the table.
*
* @param conn DB connection
* @param t table
*/
public abstract int getCurrentIdent(Connection conn, BasicTable t);
/**
* Creates a PreparedStatement object for a DELETE statement for deleting a set of records that
* satisfy a condition.
*
* @param conn DB connection
* @param t table
* @param where condition (can be {@code null})
*/
public abstract PreparedStatement getDeleteRecordStatement(Connection conn, TableElement t, String where);
/**
* Returns information on a column.
*
* @param conn DB connection
* @param c column
*/
public abstract DbColumnInfo getColumnInfo(Connection conn, Column> c);
/**
* Returns information on the primary key of a table.
*
* @param conn DB connection
* @param t Table that the information on the primary key has to be returned from
*/
public abstract DbPkInfo getPKInfo(Connection conn, TableElement t);
/**
* Returns information on the foreign keys from grain.
*
* @param conn DB connection
* @param g grain name
* @return list where each item contain information on a separate foreign key
*/
public abstract List getFKInfo(Connection conn, Grain g);
/**
* Returns a set of indices referring to tables specified in the indicated grain.
*
* @param conn DB connection
* @param g Grain the tables of which have to be traversed for the indices.
*/
public abstract Map getIndices(Connection conn, Grain g);
/**
* Get names of existing parameterized views.
*
* @param conn connection
* @param g current grain
*/
public abstract List getParameterizedViewList(Connection conn, Grain g);
/**
* Returns process id of current database connection.
*
* @param conn DB connection
*/
public abstract int getDBPid(Connection conn);
/**
* Returns current database type. E.g. H2, POSTGRESQL etc.
*/
public abstract DBType getType();
/**
* Retrieves next value from the sequence.
*
* @param conn DB connection
* @param s sequence
*/
public abstract long nextSequenceValue(Connection conn, SequenceElement s);
/**
* Checks if sequence exists in the DB.
*
* @param conn DB connection
* @param schema schema name
* @param name sequence name
*/
public abstract boolean sequenceExists(Connection conn, String schema, String name);
/**
* Returns information on a sequence.
*
* @param conn DB connection
* @param s sequence
*/
public abstract DbSequenceInfo getSequenceInfo(Connection conn, SequenceElement s);
// =========> END PUBLIC ABSTRACT METHODS <=========
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy