org.h2gis.utilities.JDBCUtilities Maven / Gradle / Ivy
/*
* H2GIS is a library that brings spatial support to the H2 Database Engine
* http://www.h2database.com. H2GIS is developed by CNRS
* http://www.cnrs.fr/.
*
* This code is part of the H2GIS project. H2GIS is free software; you can
* redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation; version 3.0 of
* the License.
*
* H2GIS 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 Lesser General Public License for more
* details .
*
*
* For more information, please consult: http://www.h2gis.org/
* or contact directly: info_at_h2gis.org
*/
package org.h2gis.utilities;
import org.h2gis.api.ProgressVisitor;
import org.h2gis.utilities.dbtypes.DBTypes;
import org.h2gis.utilities.dbtypes.DBUtils;
import org.h2gis.utilities.wrapper.ConnectionWrapper;
import org.h2gis.utilities.wrapper.DataSourceWrapper;
import javax.sql.DataSource;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;
import static org.h2gis.utilities.dbtypes.DBTypes.*;
import static org.h2gis.utilities.dbtypes.DBUtils.getDBType;
/**
* DBMS should follow standard but it is not always the case, this class do some
* common operations. Compatible with H2 and PostgreSQL.
*
* @author Nicolas Fortin
* @author Erwan Bocher
* @author Adam Gouge
* @author Sylvain PALOMINOS (UBS chaire GEOTERA 2020)
*/
public class JDBCUtilities {
public static final String H2_DRIVER_PACKAGE_NAME = "org.h2.jdbc";
public static final int POSTGRES_MAX_VARCHAR = 10485760;
public enum TABLE_TYPE {
TABLE, VIEW, FOREIGN_TABLE, TEMPORARY, TABLE_LINK, UNKOWN;
/**
* Build a new {@code TABLE_TYPE} from a {@code String table_type_name}.
*
* @param table_type_name
* @return A {@code TABLE_TYPE} value.
*/
public static TABLE_TYPE fromString(String table_type_name) {
String token = table_type_name == null ? "" : table_type_name;
if (token.contains("BASE TABLE")) {
return TABLE;
} else if (token.equals("TABLE")) {
return TABLE;
} else if (token.contains("SYSTEM TABLE")) {
return TABLE;
} else if (token.contains("VIEW")) {
return VIEW;
} else if (token.contains("FOREIGN TABLE")) {
return FOREIGN_TABLE;
} else if (token.contains("TEMPORARY")) {
return TEMPORARY;
} else if (token.contains("TABLE LINK")) {
return TABLE_LINK;
} else {
return UNKOWN;
}
}
}
public enum FUNCTION_TYPE {
ALL, BUILT_IN, ALIAS
}
public static final String H2_DRIVER_NAME = "H2 JDBC Driver";
private JDBCUtilities() {
}
private static ResultSet getTablesView(Connection connection, String catalog, String schema, String table) throws SQLException {
Integer catalogIndex = null;
Integer schemaIndex = null;
Integer tableIndex = 1;
StringBuilder sb = new StringBuilder("SELECT * from INFORMATION_SCHEMA.TABLES where ");
if (!catalog.isEmpty()) {
sb.append("UPPER(table_catalog) = ? AND ");
catalogIndex = 1;
tableIndex++;
}
if (!schema.isEmpty()) {
sb.append("UPPER(table_schema) = ? AND ");
schemaIndex = tableIndex;
tableIndex++;
}
sb.append("UPPER(table_name) = ? ");
PreparedStatement geomStatement = connection.prepareStatement(sb.toString());
if (catalogIndex != null) {
geomStatement.setString(catalogIndex, catalog.toUpperCase());
}
if (schemaIndex != null) {
geomStatement.setString(schemaIndex, schema.toUpperCase());
}
geomStatement.setString(tableIndex, table.toUpperCase());
return geomStatement.executeQuery();
}
/**
* Return true if table table contains field fieldName.
*
* @param connection Connection
* @param table a TableLocation
* @param fieldName Field name
* @return True if the table contains the field
* @throws SQLException
*/
public static boolean hasField(Connection connection, TableLocation table, String fieldName) throws SQLException {
return hasField(connection, table.toString(), fieldName);
}
/**
* Return true if table tableName contains field fieldName.
*
* @param connection Connection
* @param tableName a table name in the form CATALOG.SCHEMA.TABLE
* @param fieldName Field name
* @return True if the table contains the field
* @throws SQLException
*/
public static boolean hasField(Connection connection, String tableName, String fieldName) throws SQLException {
final Statement statement = connection.createStatement();
try {
final ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + tableName + " LIMIT 0;");
try {
return hasField(resultSet.getMetaData(), fieldName);
} finally {
resultSet.close();
}
} catch (SQLException ex) {
return false;
} finally {
statement.close();
}
}
private static boolean hasField(ResultSetMetaData resultSetMetaData, String fieldName) throws SQLException {
return getFieldIndex(resultSetMetaData, fieldName) != -1;
}
/**
* Fetch the metadata, and check field name
*
* @param resultSetMetaData Active result set meta data.
* @param fieldName Field name, ignore case
* @return The field index [1-n]; -1 if the field is not found
* @throws SQLException
*/
public static int getFieldIndex(ResultSetMetaData resultSetMetaData, String fieldName) throws SQLException {
int columnCount = resultSetMetaData.getColumnCount();
for (int columnId = 1; columnId <= columnCount; columnId++) {
if (fieldName.equalsIgnoreCase(resultSetMetaData.getColumnName(columnId))) {
return columnId;
}
}
return -1;
}
/**
* Check column name from its index
*
* @param resultSetMetaData Active result set meta data.
* @param columnIndex Column index
* @return The column name
* @throws SQLException
*/
public static String getColumnName(ResultSetMetaData resultSetMetaData, Integer columnIndex) throws SQLException {
int columnCount = resultSetMetaData.getColumnCount();
for (int columnId = 1; columnId <= columnCount; columnId++) {
if (columnId == columnIndex) {
return resultSetMetaData.getColumnName(columnId);
}
}
return null;
}
/**
* @param connection Active connection to the database
* @param table a TableLocation
* @param columnIndex Field ordinal position [1-n]
* @return The field name, empty if the field position or table is not found
* @throws SQLException If jdbc throws an error
*/
public static String getColumnName(Connection connection, TableLocation table, int columnIndex) throws SQLException {
return getColumnName(connection, table.toString(), columnIndex);
}
/**
* @param connection Active connection to the database
* @param tableName a table name in the form CATALOG.SCHEMA.TABLE
* @param columnIndex Field ordinal position [1-n]
* @return The field name, empty if the field position or table is not found
* @throws SQLException If jdbc throws an error
*/
public static String getColumnName(Connection connection, String tableName, int columnIndex) throws SQLException {
final Statement statement = connection.createStatement();
try {
final ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + tableName + " LIMIT 0;");
try {
return getColumnName(resultSet.getMetaData(), columnIndex);
} finally {
resultSet.close();
}
} finally {
statement.close();
}
}
/**
* Returns the list of all the column names of a table.
*
* @param connection Active connection to the database
* @param table a TableLocation
* @return The list of field name.
* @throws SQLException If jdbc throws an error
*/
public static List getColumnNames(Connection connection, TableLocation table) throws SQLException {
return getColumnNames(connection, table.toString());
}
/**
* Returns the list of all the column names of a table.
*
* @param connection Active connection to the database
* @param tableName a table name in the form CATALOG.SCHEMA.TABLE
* @return The list of field name.
* @throws SQLException If jdbc throws an error
*/
public static List getColumnNames(Connection connection, String tableName) throws SQLException {
List fieldNameList = new ArrayList<>();
final Statement statement = connection.createStatement();
try {
final ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + tableName + " LIMIT 0;");
try {
ResultSetMetaData metadata = resultSet.getMetaData();
int columnCount = metadata.getColumnCount();
for (int columnId = 1; columnId <= columnCount; columnId++) {
fieldNameList.add(metadata.getColumnName(columnId));
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
return fieldNameList;
}
/**
* Returns the list of all the column names and indexes of a table.
*
* @param connection Active connection to the database
* @param table a TableLocation
* @return The list of field name.
* @throws SQLException If jdbc throws an error
*/
public static List> getColumnNamesAndIndexes(Connection connection, TableLocation table) throws SQLException {
return getColumnNamesAndIndexes(connection, table.toString());
}
/**
* Returns the list of all the column names and indexes of a table.
*
* @param connection Active connection to the database
* @param tableName a table name in the form CATALOG.SCHEMA.TABLE
* @return The list of field name.
* @throws SQLException If jdbc throws an error
*/
public static List> getColumnNamesAndIndexes(Connection connection, String tableName) throws SQLException {
List> fieldNameList = new ArrayList<>();
final Statement statement = connection.createStatement();
try {
final ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + tableName + " LIMIT 0;");
try {
ResultSetMetaData metadata = resultSet.getMetaData();
int columnCount = metadata.getColumnCount();
for (int columnId = 1; columnId <= columnCount; columnId++) {
fieldNameList.add(new Tuple<>(metadata.getColumnName(columnId), columnId));
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
return fieldNameList;
}
/**
* Fetch the row count of a table.
*
* @param connection Active connection.
* @param table a TableLocation
* @return Row count
* @throws SQLException If the table does not exists, or sql request fail.
*/
public static int getRowCount(Connection connection, TableLocation table) throws SQLException {
return getRowCount(connection, table.toString());
}
/**
* Fetch the row count of a table.
*
* @param connection Active connection.
* @param tableName a table name in the form CATALOG.SCHEMA.TABLE
* @return Row count
* @throws SQLException If the table does not exists, or sql request fail.
*/
public static int getRowCount(Connection connection, String tableName) throws SQLException {
Statement st = connection.createStatement();
int rowCount = 0;
try {
ResultSet rs = st.executeQuery(String.format("select count(*) rowcount from %s", tableName));
try {
if (rs.next()) {
rowCount = rs.getInt(1);
}
} finally {
rs.close();
}
} finally {
st.close();
}
return rowCount;
}
/**
* Read INFORMATION_SCHEMA.TABLES in order to see if the provided table
* reference is a temporary table.
*
* @param connection Active connection not closed by this method
* @param tableLocation Table reference
* @return True if the provided table is temporary.
* @throws SQLException If the table does not exists.
*/
public static boolean isTemporaryTable(Connection connection, TableLocation tableLocation) throws SQLException {
ResultSet rs = getTablesView(connection, tableLocation.getCatalog(), tableLocation.getSchema(), tableLocation.getTable());
boolean isTemporary = false;
try {
if (rs.next()) {
String tableType;
if (hasField(rs.getMetaData(), "STORAGE_TYPE")) {
// H2
tableType = rs.getString("STORAGE_TYPE");
} else {
// Standard SQL
tableType = rs.getString("TABLE_TYPE");
}
isTemporary = tableType.contains("TEMPORARY");
} else {
throw new SQLException("The table " + tableLocation.toString() + " does not exists");
}
} finally {
rs.close();
}
return isTemporary;
}
/**
* Read INFORMATION_SCHEMA.TABLES in order to see if the provided table
* reference is a linked table.
*
* @param connection Active connection not closed by this method
* @param table TableLocation
* @return True if the provided table is linked.
* @throws SQLException If the table does not exists.
*/
public static boolean isLinkedTable(Connection connection, TableLocation table) throws SQLException {
return isLinkedTable(connection, table.toString());
}
/**
* Read INFORMATION_SCHEMA.TABLES in order to see if the provided table
* reference is a linked table.
*
* @param connection Active connection not closed by this method
* @param tableReference Table reference
* @return True if the provided table is linked.
* @throws SQLException If the table does not exists.
*/
public static boolean isLinkedTable(Connection connection, String tableReference) throws SQLException {
String[] location = TableLocation.split(tableReference);
ResultSet rs = getTablesView(connection, location[0], location[1], location[2]);
boolean isLinked;
try {
if (rs.next()) {
String tableType = rs.getString("STORAGE_TYPE");
isLinked = tableType.contains("TABLE LINK");
} else {
throw new SQLException("The table " + tableReference + " does not exists");
}
} finally {
rs.close();
}
return isLinked;
}
/**
* @param connection to the
* @return True if the provided metadata is a h2 database connection.
* @throws SQLException
*/
public static boolean isH2DataBase(Connection connection) throws SQLException {
if (connection.getClass().getName().startsWith(H2_DRIVER_PACKAGE_NAME)
|| connection.getClass().equals(ConnectionWrapper.class)) {
return true;
} else {
return connection.getMetaData().getDriverName().equals("H2 JDBC Driver");
}
}
/**
* @param connection Connection
* @param tableLocation table identifier
* @return The integer primary key used for edition[1-n]; 0 if the source is
* closed or if the table has no primary key or more than one column as
* primary key
* @throws java.sql.SQLException
*/
public static int getIntegerPrimaryKey(Connection connection, TableLocation tableLocation) throws SQLException {
if (!tableExists(connection, tableLocation)) {
throw new SQLException("Table " + tableLocation + " not found.");
}
final DatabaseMetaData meta = connection.getMetaData();
String columnNamePK = null;
ResultSet rs = meta.getPrimaryKeys(tableLocation.getCatalog(null), tableLocation.getSchema(null),
tableLocation.getTable());
try {
while (rs.next()) {
// If the schema is not specified, public must be the schema
if (!tableLocation.getSchema().isEmpty() || "public".equalsIgnoreCase(rs.getString("TABLE_SCHEM"))) {
if (columnNamePK == null) {
columnNamePK = rs.getString("COLUMN_NAME");
} else {
// Multi-column PK is not supported
columnNamePK = null;
break;
}
}
}
} finally {
rs.close();
}
if (columnNamePK != null) {
rs = meta.getColumns(tableLocation.getCatalog(null), tableLocation.getSchema(null),
tableLocation.getTable(), columnNamePK);
try {
while (rs.next()) {
if (!tableLocation.getSchema().isEmpty() || "public".equalsIgnoreCase(rs.getString("TABLE_SCHEM"))) {
int dataType = rs.getInt("DATA_TYPE");
if (dataType == Types.BIGINT || dataType == Types.INTEGER || dataType == Types.ROWID) {
return rs.getInt("ORDINAL_POSITION");
}
}
}
} finally {
rs.close();
}
}
return 0;
}
/**
* Method to fetch an integer primary key (name + index). Return null
* otherwise
*
* @param connection Connection
* @param tableLocation table identifier
* @return The name and the index of an integer primary key used for
* edition[1-n]; 0 if the source is closed or if the table has no primary
* key or more than one column as primary key
* @throws java.sql.SQLException
*/
public static Tuple getIntegerPrimaryKeyNameAndIndex(Connection connection, TableLocation tableLocation) throws SQLException {
if (!tableExists(connection, tableLocation)) {
throw new SQLException("Table " + tableLocation + " not found.");
}
final DatabaseMetaData meta = connection.getMetaData();
String columnNamePK = null;
ResultSet rs = meta.getPrimaryKeys(tableLocation.getCatalog(null), tableLocation.getSchema(null),
tableLocation.getTable());
try {
while (rs.next()) {
// If the schema is not specified, public must be the schema
if (!tableLocation.getSchema().isEmpty() || "public".equalsIgnoreCase(rs.getString("TABLE_SCHEM"))) {
if (columnNamePK == null) {
columnNamePK = rs.getString("COLUMN_NAME");
} else {
// Multi-column PK is not supported
columnNamePK = null;
break;
}
}
}
} finally {
rs.close();
}
if (columnNamePK != null) {
rs = meta.getColumns(tableLocation.getCatalog(null), tableLocation.getSchema(null),
tableLocation.getTable(), columnNamePK);
try {
while (rs.next()) {
if (!tableLocation.getSchema().isEmpty() || "public".equalsIgnoreCase(rs.getString("TABLE_SCHEM"))) {
int dataType = rs.getInt("DATA_TYPE");
if (dataType == Types.BIGINT || dataType == Types.INTEGER || dataType == Types.ROWID) {
return new Tuple<>(columnNamePK, rs.getInt("ORDINAL_POSITION"));
}
}
}
} finally {
rs.close();
}
}
return null;
}
/**
* Return true if the table exists.
*
* @param connection Connection
* @param tableLocation Table name
* @return true if the table exists
* @throws java.sql.SQLException
*/
public static boolean tableExists(Connection connection, TableLocation tableLocation) throws SQLException {
List tableNames = JDBCUtilities.getTableNames(connection,
tableLocation.getCatalog().isEmpty() ? null : tableLocation.getCatalog(),
tableLocation.getSchema().isEmpty() ? null : tableLocation.getSchema(),
null,
new String[]{"TABLE", "VIEW", "SYSTEM TABLE"});
for (String matchTableName : tableNames) {
TableLocation matchTableNameLocation = TableLocation.parse(matchTableName, tableLocation.getDbTypes());
if (tableLocation.getTable().equals(matchTableNameLocation.getTable()) &&
((tableLocation.getSchema().isEmpty() &&
matchTableNameLocation.getSchema().equalsIgnoreCase("public")) ||
tableLocation.getSchema().equals(matchTableNameLocation.getSchema()))) {
return true;
}
}
return false;
}
/**
* Return true if the table exists.
*
* @param connection Connection
* @param tableName Table name
* @return true if the table exists
* @throws java.sql.SQLException
*/
public static boolean tableExists(Connection connection, String tableName) throws SQLException {
DBTypes dbTypes = DBUtils.getDBType(connection);
return tableExists(connection, TableLocation.parse(tableName, dbTypes));
}
/**
* Returns the list of table names.
*
* @param connection Active connection to the database
* @param tableLocation Table name
* @param types A list of table types, which must be from the list of table
* types returned from getTableTypes(), to include. null returns all types
* @return The integer primary key used for edition[1-n]; 0 if the source is
* closed or if the table has no primary key or more than one column as
* primary key
* @throws java.sql.SQLException
*/
public static List getTableNames(Connection connection, TableLocation tableLocation, String[] types) throws SQLException {
List tableList = new ArrayList<>();
ResultSet rs = connection.getMetaData().getTables(tableLocation.getCatalog(),
tableLocation.getSchema("PUBLIC"),
tableLocation.getTable().replace(TableLocation.QUOTE_CHAR, ""), types);
try {
while (rs.next()) {
tableList.add(new TableLocation(rs).toString(tableLocation.getDbTypes()));
}
} finally {
rs.close();
}
return tableList;
}
/**
* Returns the list of table names.
*
* @param connection Active connection to the database
* @param catalog A catalog name. Must match the catalog name as it is
* stored in the database. "" retrieves those without a catalog; null means
* that the catalog name should not be used to narrow the search
* @param schemaPattern A schema name pattern. Must match the schema name as
* it is stored in the database. "" retrieves those without a schema. null
* means that the schema name should not be used to narrow the search
* @param tableNamePattern A table name pattern. Must match the table name
* as it is stored in the database
* @param types A list of table types, which must be from the list of table
* types returned from getTableTypes(), to include. null returns all types
* @return The integer primary key used for edition[1-n]; 0 if the source is
* closed or if the table has no primary key or more than one column as
* primary key
* @throws java.sql.SQLException
*/
public static List getTableNames(Connection connection, String catalog, String schemaPattern,
String tableNamePattern, String[] types) throws SQLException {
List tableList = new ArrayList<>();
ResultSet rs = connection.getMetaData().getTables(catalog, schemaPattern, tableNamePattern, types);
final DBTypes dbType = getDBType(connection);
try {
while (rs.next()) {
tableList.add(new TableLocation(rs).toString(dbType));
}
} finally {
rs.close();
}
return tableList;
}
/**
* Returns the list of distinct values contained by a field from a table
* from the database
*
* @param connection Connection
* @param table Name of the table containing the field.
* @param fieldName Name of the field containing the values.
* @return The list of distinct values of the field.
* @throws java.sql.SQLException
*/
public static List getUniqueFieldValues(Connection connection, TableLocation table, String fieldName) throws SQLException {
return getUniqueFieldValues(connection, table.toString(), fieldName);
}
/**
* Returns the list of distinct values contained by a field from a table
* from the database
*
* @param connection Connection
* @param tableName Name of the table containing the field.
* @param fieldName Name of the field containing the values.
* @return The list of distinct values of the field.
* @throws java.sql.SQLException
*/
public static List getUniqueFieldValues(Connection connection, String tableName, String fieldName) throws SQLException {
final DBTypes dbType = getDBType(connection);
final Statement statement = connection.createStatement();
List fieldValues = new ArrayList<>();
try {
ResultSet result = statement.executeQuery("SELECT DISTINCT " + TableLocation.quoteIdentifier(fieldName) + " FROM " + TableLocation.parse(tableName).toString(dbType));
try {
while (result.next()) {
fieldValues.add(result.getString(1));
}
} finally {
result.close();
}
} finally {
statement.close();
}
return fieldValues;
}
/**
* A method to create an empty table (no columns)
*
* @param connection Connection
* @param table Table name
* @throws java.sql.SQLException
*/
public static void createEmptyTable(Connection connection, TableLocation table) throws SQLException {
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE " + table.toString() + " ()");
}
}
/**
* A method to create an empty table (no columns)
*
* @param connection Connection
* @param tableReference Table name
* @throws java.sql.SQLException
*/
public static void createEmptyTable(Connection connection, String tableReference) throws SQLException {
try (Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE " + tableReference + " ()");
}
}
/**
* Fetch the name of columns
*
* @param resultSetMetaData Active result set meta data.
* @return An array with all column names
* @throws SQLException
*/
public static List getColumnNames(ResultSetMetaData resultSetMetaData) throws SQLException {
List columnNames = new ArrayList<>();
int cols = resultSetMetaData.getColumnCount();
for (int i = 1; i <= cols; i++) {
columnNames.add(resultSetMetaData.getColumnName(i));
}
return columnNames;
}
/**
* In order to be able to use {@link ResultSet#unwrap(Class)} and
* {@link java.sql.ResultSetMetaData#unwrap(Class)} to get
* {@link SpatialResultSet} and {@link SpatialResultSetMetaData} this method
* wrap the provided dataSource.
*
* @param dataSource H2 or PostGIS DataSource
* @return Wrapped DataSource, with spatial methods
*/
public static DataSource wrapSpatialDataSource(DataSource dataSource) {
try {
if (dataSource.isWrapperFor(DataSourceWrapper.class)) {
return dataSource;
} else {
return new DataSourceWrapper(dataSource);
}
} catch (SQLException ex) {
return new DataSourceWrapper(dataSource);
}
}
/**
* Use this only if DataSource is not available. In order to be able to use
* {@link ResultSet#unwrap(Class)} and
* {@link java.sql.ResultSetMetaData#unwrap(Class)} to get
* {@link SpatialResultSet} and {@link SpatialResultSetMetaData} this method
* wrap the provided connection.
*
* @param connection H2 or PostGIS Connection
* @return Wrapped DataSource, with spatial methods
*/
public static Connection wrapConnection(Connection connection) {
try {
if (connection.isWrapperFor(ConnectionWrapper.class)) {
return connection;
} else {
return new ConnectionWrapper(connection);
}
} catch (SQLException ex) {
return new ConnectionWrapper(connection);
}
}
/**
* @param st Statement to cancel
* @param progressVisitor Progress to link with
* @return call
* {@link org.h2gis.api.ProgressVisitor#removePropertyChangeListener(java.beans.PropertyChangeListener)}
* with this object as argument
*/
public static PropertyChangeListener attachCancelResultSet(Statement st, ProgressVisitor progressVisitor) {
PropertyChangeListener propertyChangeListener = new CancelResultSet(st);
progressVisitor.addPropertyChangeListener(ProgressVisitor.PROPERTY_CANCELED, propertyChangeListener);
return propertyChangeListener;
}
/**
* Call cancel of statement
*/
private static final class CancelResultSet implements PropertyChangeListener {
private final Statement st;
private CancelResultSet(Statement st) {
this.st = st;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
try {
st.cancel();
} catch (SQLException ex) {
// Ignore
}
}
}
/**
* Return the type of the table using an Enum
*
* @param connection
* @param location
* @return
* @throws SQLException
*/
public static TABLE_TYPE getTableType(Connection connection, TableLocation location) throws SQLException {
DBTypes dbType = location.getDbTypes();
try (ResultSet rs = getTablesView(connection, location.getCatalog(), location.getSchema(), location.getTable())) {
if (rs.next()) {
if (dbType == H2) {
// H2
String storage = rs.getString("STORAGE_TYPE");
if (storage.contains("TEMPORARY")) {
return TABLE_TYPE.TEMPORARY;
} else if (storage.equals("TABLE LINK")) {
return TABLE_TYPE.TABLE_LINK;
} else {
return TABLE_TYPE.fromString(rs.getString("TABLE_TYPE"));
}
} else {
// Standard SQL
return TABLE_TYPE.fromString(rs.getString("TABLE_TYPE"));
}
} else {
throw new SQLException("The table " + location + " does not exists");
}
}
}
/**
* A simple method to generate a DDL create table command from a table name
*
*
* Takes into account only data types
*
* @param connection
* @param sourceTable
* @param targetTable
* @return a create table ddl command
* @throws SQLException
*/
public static String createTableDDL(Connection connection, TableLocation sourceTable, TableLocation targetTable) throws SQLException {
if (sourceTable == null) {
throw new SQLException("The source table name cannot be null or empty");
}
if (targetTable == null) {
throw new SQLException("The target table name cannot be null or empty");
}
if (JDBCUtilities.tableExists(connection, sourceTable)) {
final StringBuilder builder = new StringBuilder(256);
LinkedHashMap geomMetadatas = GeometryTableUtilities.getMetaData(connection, sourceTable);
builder.append("CREATE TABLE ").append(targetTable);
final Statement statement = connection.createStatement();
try {
final ResultSet resultSet = statement.executeQuery("SELECT * FROM " + sourceTable.toString() + " LIMIT 0;");
try {
ResultSetMetaData metadata = resultSet.getMetaData();
int columnCount = metadata.getColumnCount();
if (columnCount > 0) {
builder.append(" (");
}
for (int i = 1; i <= columnCount; i++) {
if (i > 1) {
builder.append(",");
}
String columnName = metadata.getColumnName(i);
String columnTypeName = metadata.getColumnTypeName(i);
int columnType = metadata.getColumnType(i);
if (columnType == Types.VARCHAR || columnType == Types.LONGVARCHAR || columnType == Types.NVARCHAR || columnType == Types.LONGNVARCHAR) {
int precision = metadata.getPrecision(i);
//POSTGRESQL VARCHAR MAX SIZE
if (precision > POSTGRES_MAX_VARCHAR) {
builder.append(columnName).append(" ").append(columnTypeName);
} else {
builder.append(columnName).append(" ").append(columnTypeName);
builder.append("(").append(precision).append(")");
}
} else {
if (columnType == Types.CHAR) {
builder.append(columnName).append(" ").append(columnTypeName);
builder.append("(").append(metadata.getColumnDisplaySize(i)).append(")");
} else if (columnType == Types.DOUBLE) {
builder.append(columnName).append(" ").append("DOUBLE PRECISION");
} else if (columnTypeName.toLowerCase().startsWith("geometry")) {
if (geomMetadatas.isEmpty()) {
builder.append(columnName).append(" ").append(columnTypeName);
} else {
GeometryMetaData geomMetadata = geomMetadatas.get(columnName);
if (geomMetadata.getGeometryTypeCode() == GeometryTypeCodes.GEOMETRY && geomMetadata.getSRID() == 0) {
builder.append(columnName).append(" ").append(columnTypeName);
} else {
builder.append(columnName).append(" ").append("GEOMETRY")
.append("(").append(geomMetadata.getGeometryType()).append(",").append(geomMetadata.getSRID()).append(")");
}
}
} else if (columnTypeName.equalsIgnoreCase("decfloat")) {
builder.append(columnName).append(" FLOAT");
} else {
builder.append(columnName).append(" ").append(columnTypeName);
}
}
}
if (columnCount > 0) {
builder.append(")");
}
return builder.toString();
} finally {
resultSet.close();
}
} finally {
statement.close();
}
} else {
throw new SQLException("The table " + sourceTable + " doesn't exist");
}
}
/**
* A simple method to generate a DDL create table command from a table name
*
* Takes into account only data types
*
* @param connection
* @param location
* @return a create table ddl command
* @throws SQLException
*/
public static String createTableDDL(Connection connection, TableLocation location) throws SQLException {
return createTableDDL(connection, location, location);
}
/**
* A simple method to generate a DDL create table command from a query
*
* Takes into account only data types
*
* @param outputTableName
* @param resultSet
* @return a create table ddl command
* @throws SQLException
*/
public static String createTableDDL(ResultSet resultSet, String outputTableName) throws SQLException {
return createTableDDL(resultSet.getMetaData(), outputTableName);
}
/**
* A simple method to generate a DDL create table command from
* ResultSetMetaData
*
* Takes into account only data types
*
* @param outputTableName
* @param metadata
* @return a create table ddl command
* @throws SQLException
*/
public static String createTableDDL(ResultSetMetaData metadata, String outputTableName) throws SQLException {
if (outputTableName == null || outputTableName.isEmpty()) {
throw new SQLException("The target table name cannot be null or empty");
}
final StringBuilder builder = new StringBuilder(256);
builder.append("CREATE TABLE ").append(outputTableName);
int columnCount = metadata.getColumnCount();
if (columnCount > 0) {
builder.append(" (");
}
for (int i = 1; i <= columnCount; i++) {
if (i > 1) {
builder.append(",");
}
String columnName = metadata.getColumnName(i);
String columnTypeName = metadata.getColumnTypeName(i);
int columnType = metadata.getColumnType(i);
if (columnType == Types.VARCHAR || columnType == Types.LONGVARCHAR || columnType == Types.NVARCHAR || columnType == Types.LONGNVARCHAR) {
int precision = metadata.getPrecision(i);
//POSTGRESQL VARCHAR MAX SIZE
if (precision > POSTGRES_MAX_VARCHAR) {
builder.append(columnName).append(" ").append(columnTypeName);
} else {
builder.append(columnName).append(" ").append(columnTypeName);
builder.append("(").append(precision).append(")");
}
} else {
if (columnType == Types.CHAR) {
builder.append(columnName).append(" ").append(columnTypeName);
builder.append("(").append(metadata.getColumnDisplaySize(i)).append(")");
} else if (columnType == Types.DOUBLE) {
builder.append(columnName).append(" ").append("DOUBLE PRECISION");
} else if (columnTypeName.toLowerCase().startsWith("geometry")) {
builder.append(columnName).append(" ").append(columnTypeName);
} else if (columnTypeName.equalsIgnoreCase("decfloat")) {
builder.append(columnName).append(" FLOAT");
} else {
builder.append(columnName).append(" ").append(columnTypeName);
}
}
}
if (columnCount > 0) {
builder.append(")");
}
return builder.toString();
}
/**
* Returns true if the given column name from the given table is indexed,
* return false otherwise.
*
* @param connection {@link Connection} containing the table to check.
* @param tableName Name of the table to check.
* @param columnName Name of the column to check.
* @return True if the given column is indexed, false otherwise.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static boolean isIndexed(Connection connection, String tableName, String columnName) throws SQLException {
return isIndexed(connection, TableLocation.parse(tableName, getDBType(connection)), columnName);
}
/**
* Returns true if the given column name from the given table has an
* indexed, return false otherwise.
*
* @param connection {@link Connection} containing the table to check.
* @param table {@link TableLocation} of the table to check.
* @param columnName Name of the column to check.
* @return True if the given column is indexed, false otherwise.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static boolean isIndexed(Connection connection, TableLocation table, String columnName) throws SQLException {
if (connection == null || table == null) {
throw new SQLException("Unable to get the index names");
}
DBTypes dbTypes = table.getDbTypes();
columnName = TableLocation.capsIdentifier(columnName, dbTypes);
DatabaseMetaData md = connection.getMetaData();
ResultSet indexInfo = md.getIndexInfo(connection.getCatalog(), table.getSchema(), table.getTable(), false, true);
while (indexInfo.next()) {
if (columnName.equals(indexInfo.getString("COLUMN_NAME"))) {
return true;
}
}
return false;
}
/**
* Returns true if the given column name from the given table is indexed,
* return false otherwise.
*
* @param connection {@link Connection} containing the table to check.
* @param tableName Name of the table to check.
* @param columnName Name of the column to check.
* @return True if the given column is indexed, false otherwise.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static boolean isSpatialIndexed(Connection connection, String tableName, String columnName) throws SQLException {
return isSpatialIndexed(connection, TableLocation.parse(tableName, getDBType(connection)), columnName);
}
/**
* Returns true if the given column name from the given table is indexed,
* return false otherwise.
*
* @param connection {@link Connection} containing the table to check.
* @param table {@link TableLocation} of the table to check.
* @param columnName Name of the column to check.
* @return True if the given column is indexed, false otherwise.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static boolean isSpatialIndexed(Connection connection, TableLocation table, String columnName) throws SQLException {
if (connection == null || columnName == null || table == null) {
throw new SQLException("Unable to find an index");
}
DBTypes dbType = table.getDbTypes();
if (dbType == H2 || dbType == H2GIS) {
PreparedStatement ps = connection.prepareStatement("SELECT INDEX_NAME FROM INFORMATION_SCHEMA.INDEX_COLUMNS "
+ "WHERE INFORMATION_SCHEMA.INDEX_COLUMNS.TABLE_NAME=? "
+ "AND INFORMATION_SCHEMA.INDEX_COLUMNS.TABLE_SCHEMA=? "
+ "AND INFORMATION_SCHEMA.INDEX_COLUMNS.COLUMN_NAME=?"
+ "AND INFORMATION_SCHEMA.INDEX_COLUMNS.INDEX_NAME "
+ "IN (SELECT INDEX_NAME FROM INFORMATION_SCHEMA.INDEXES WHERE "
+ "INFORMATION_SCHEMA.INDEXES.TABLE_SCHEMA=? "
+ "AND INFORMATION_SCHEMA.INDEXES.TABLE_NAME= ? "
+ " AND INFORMATION_SCHEMA.INDEXES.INDEX_TYPE_NAME='SPATIAL INDEX')");
String tableName = table.getTable();
String schemaName = table.getSchema("PUBLIC");
ps.setObject(1, tableName);
ps.setObject(2, schemaName);
ps.setObject(3, TableLocation.capsIdentifier(columnName, dbType));
ps.setObject(4, schemaName);
ps.setObject(5, tableName);
ResultSet rs = ps.executeQuery();
return rs.next();
} else if (dbType == POSTGIS || dbType == POSTGRESQL) {
String query = "SELECT cls.relname, am.amname "
+ "FROM pg_class cls "
+ "JOIN pg_am am ON am.oid=cls.relam where cls.oid "
+ " in(select attrelid as pg_class_oid from pg_catalog.pg_attribute "
+ " where attname = ? and attrelid in "
+ "(select b.oid from pg_catalog.pg_indexes a, pg_catalog.pg_class b where a.schemaname =? and a.tablename =? "
+ "and a.indexname = b.relname)) and am.amname = 'gist' ;";
PreparedStatement ps = connection.prepareStatement(query);
ps.setObject(1, columnName);
ps.setObject(2, table.getSchema("public"));
ps.setObject(3, table.getTable());
ResultSet rs = ps.executeQuery();
return rs.next();
} else {
throw new SQLException("Database not supported");
}
}
/**
* Create an index on the given column of the given table on the given
* connection.
*
* @param connection Connection to access to the desired table.
* @param table Table containing the column to index.
* @param columnName Name of the column to index.
* @return True if the column have been indexed, false otherwise.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static boolean createIndex(Connection connection, TableLocation table, String columnName) throws SQLException {
if (connection == null || table == null || columnName == null) {
throw new SQLException("Unable to create an index");
}
final DBTypes dbType = table.getDbTypes();
final String tableName = table.toString();
connection.createStatement().execute("CREATE INDEX IF NOT EXISTS " + tableName + "_" + columnName
+ " ON " + tableName + " (" + TableLocation.capsIdentifier(columnName, dbType) + ")");
return true;
}
/**
* Create an index on the given column of the given table on the given
* connection.
*
* @param connection Connection to access to the desired table.
* @param table Name of the table containing the column to index.
* @param columnName Name of the column to index.
* @return True if the column have been indexed, false otherwise.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static boolean createIndex(Connection connection, String table, String columnName) throws SQLException {
return createIndex(connection, TableLocation.parse(table, getDBType(connection)), columnName);
}
/**
* Create a spatial index on the given column of the given table on the
* given connection.
*
* @param connection Connection to access to the desired table.
* @param table Table containing the column to index.
* @param columnName Name of the column to index.
* @return True if the column have been indexed, false otherwise.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static boolean createSpatialIndex(Connection connection, TableLocation table, String columnName) throws SQLException {
if (connection == null || table == null || columnName == null) {
throw new SQLException("Unable to create a spatial index");
}
DBTypes dbTypes = table.getDbTypes();
if (dbTypes == H2GIS || dbTypes == POSTGIS || dbTypes == H2 || dbTypes == POSTGRESQL) {
if (dbTypes == H2 || dbTypes == H2GIS) {
connection.createStatement().execute("CREATE SPATIAL INDEX IF NOT EXISTS " + table.toString() + "_" + columnName
+ " ON " + table.toString() + " (" + TableLocation.capsIdentifier(columnName, dbTypes) + ")");
} else {
connection.createStatement().execute("CREATE INDEX IF NOT EXISTS " + table.toString() + "_" + columnName
+ " ON " + table.toString() + " USING GIST (" + TableLocation.capsIdentifier(columnName, dbTypes) + ")");
}
return true;
}
throw new SQLException("DataBase not supported");
}
/**
* Create a spatial index on the given column of the given table on the
* given connection.
*
* @param connection Connection to access to the desired table.
* @param table Name of the table containing the column to index.
* @param columnName Name of the column to index.
* @return True if the column have been indexed, false otherwise.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static boolean createSpatialIndex(Connection connection, String table, String columnName) throws SQLException {
return createSpatialIndex(connection, TableLocation.parse(table, getDBType(connection)), columnName);
}
/**
* Drop the index of the given column of the given table on yhe given
* connection.
*
* @param connection Connection to access to the desired table.
* @param table Table containing the column to drop index.
* @param columnName Name of the column to drop index.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static void dropIndex(Connection connection, TableLocation table, String columnName) throws SQLException {
List indexes = getIndexNames(connection, table, columnName);
String query = indexes.stream()
.map(key -> "DROP INDEX " + key)
.collect(Collectors.joining(";"));
connection.createStatement().execute(query);
}
/**
* Return the name of all indexes for a given table
*
* @param connection Connection to access to the desired table.
* @param table Table containing the column to get the index names.
* @return a map with the index name and its column name
* @throws SQLException
*/
public static Map getIndexNames(Connection connection, String table) throws SQLException {
return getIndexNames(connection, TableLocation.parse(table, getDBType(connection)));
}
/**
* Return the name of all indexes for a given table
*
* @param connection Connection to access to the desired table.
* @param table Table containing the column to get the index names.
* @return a map with the index name and its column name
* @throws SQLException
*/
public static Map getIndexNames(Connection connection, TableLocation table) throws SQLException {
if (connection == null || table == null) {
throw new SQLException("Unable to get the index names");
}
DatabaseMetaData md = connection.getMetaData();
Map indexes = new HashMap<>();
ResultSet indexInfo = md.getIndexInfo(connection.getCatalog(), table.getSchema(), table.getTable(), false, true);
while (indexInfo.next()) {
String indexName = indexInfo.getString("INDEX_NAME");
String columnName = indexInfo.getString("COLUMN_NAME");
indexes.put(indexName, columnName);
}
return indexes;
}
/**
* Return the name of indexes for a given table and column
*
* @param connection Connection to access to the desired table.
* @param table Table containing the column to get the index names.
* @param columnName Name of the column.
* @return
* @throws SQLException
*/
public static List getIndexNames(Connection connection, String table, String columnName) throws SQLException {
return getIndexNames(connection, TableLocation.parse(table, getDBType(connection)), columnName);
}
/**
* Return the name of indexes for a given table and column
*
* @param connection Connection to access to the desired table.
* @param table Table containing the column to get the index names.
* @param columnName Name of the column.
* @return
* @throws SQLException
*/
public static List getIndexNames(Connection connection, TableLocation table, String columnName) throws SQLException {
if (connection == null || table == null) {
throw new SQLException("Unable to get the index names");
}
DBTypes dbTypes = table.getDbTypes();
columnName = TableLocation.capsIdentifier(columnName, dbTypes);
DatabaseMetaData md = connection.getMetaData();
ArrayList indexes = new ArrayList<>();
ResultSet indexInfo = md.getIndexInfo(connection.getCatalog(), table.getSchema(), table.getTable(), false, true);
while (indexInfo.next()) {
if (columnName.equals(indexInfo.getString("COLUMN_NAME"))) {
indexes.add(indexInfo.getString("INDEX_NAME"));
}
}
return indexes;
}
/**
* Drop the index of the given column of the given table on yhe given
* connection.
*
* @param connection Connection to access to the desired table.
* @param table Name of the table containing the column to drop index.
* @param columnName Name of the column to drop index.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static void dropIndex(Connection connection, String table, String columnName) throws SQLException {
dropIndex(connection, TableLocation.parse(table, getDBType(connection)), columnName);
}
/**
* Drop the all the indexes of the given table on the given connection.
*
* @param connection Connection to access to the desired table.
* @param table Table containing the column to drop index.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static void dropIndex(Connection connection, TableLocation table) throws SQLException {
if (connection == null || table == null) {
throw new SQLException("Unable to drop index");
}
Map indexes = getIndexNames(connection, table);
String query = indexes.keySet().stream()
.map(key -> "DROP INDEX " + key)
.collect(Collectors.joining(";"));
connection.createStatement().execute(query);
}
/**
* Drop the all the indexes of the given table on yhe given connection.
*
* @param connection Connection to access to the desired table.
* @param table Name of the table containing the column to drop index.
* @throws SQLException Exception thrown on SQL execution error.
*/
public static void dropIndex(Connection connection, String table) throws SQLException {
dropIndex(connection, TableLocation.parse(table, getDBType(connection)));
}
/**
* Return a list of numeric column names
*
* @param resultSetMetaData the metadata of the table
* @return a list
* @throws SQLException
*/
public static List getNumericColumns(ResultSetMetaData resultSetMetaData) throws SQLException {
List fieldNameList = new ArrayList<>();
int columnCount = resultSetMetaData.getColumnCount();
for (int columnId = 1; columnId <= columnCount; columnId++) {
if (isNumeric(resultSetMetaData.getColumnType(columnId))) {
fieldNameList.add(resultSetMetaData.getColumnName(columnId));
}
}
return fieldNameList;
}
/**
* Returns the list of all the numeric column names of a table.
*
* @param connection Active connection to the database
* @param table a TableLocation
* @return The list of field name.
* @throws SQLException If jdbc throws an error
*/
public static List getNumericColumns(Connection connection, TableLocation table) throws SQLException {
return getNumericColumns(connection, table.toString());
}
/**
* Returns the list of all the numeric column names of a table.
*
* @param connection Active connection to the database
* @param tableName a table name in the form CATALOG.SCHEMA.TABLE
* @return The list of field names.
* @throws SQLException If jdbc throws an error
*/
public static List getNumericColumns(Connection connection, String tableName) throws SQLException {
List fieldNameList = new ArrayList<>();
final Statement statement = connection.createStatement();
try {
final ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + tableName + " LIMIT 0;");
try {
ResultSetMetaData metadata = resultSet.getMetaData();
int columnCount = metadata.getColumnCount();
for (int columnId = 1; columnId <= columnCount; columnId++) {
if (isNumeric(metadata.getColumnType(columnId))) {
fieldNameList.add(metadata.getColumnName(columnId));
}
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
return fieldNameList;
}
/**
* Return the first numeric column otherwise null
*
* @param resultSetMetaData the metadata of the table
* @return the name of the column
* @throws SQLException
*/
public static String getFirstNumericColumn(ResultSetMetaData resultSetMetaData) throws SQLException {
int columnCount = resultSetMetaData.getColumnCount();
for (int columnId = 1; columnId <= columnCount; columnId++) {
if (isNumeric(resultSetMetaData.getColumnType(columnId))) {
return resultSetMetaData.getColumnName(columnId);
}
}
return null;
}
/**
* Returns the first numeric column name of a table.
*
* @param connection Active connection to the database
* @param table a TableLocation
* @return The first numeric column name .
* @throws SQLException If jdbc throws an error
*/
public static String getFirstNumericColumn(Connection connection, TableLocation table) throws SQLException {
return getFirstNumericColumn(connection, table.toString());
}
/**
* Returns the first numeric column name of a table.
*
* @param connection Active connection to the database
* @param tableName a table name in the form CATALOG.SCHEMA.TABLE
* @return The first numeric column name .
* @throws SQLException If jdbc throws an error
*/
public static String getFirstNumericColumn(Connection connection, String tableName) throws SQLException {
final Statement statement = connection.createStatement();
try {
final ResultSet resultSet = statement.executeQuery(
"SELECT * FROM " + tableName + " LIMIT 0;");
try {
return getFirstNumericColumn(resultSet.getMetaData());
} finally {
resultSet.close();
}
} finally {
statement.close();
}
}
/**
* Return true is the SQL type is a numeric data type
*
* @param sqlType SQL type from {@link java.sql.Types}
* @return True if the type is numeric
*/
public static boolean isNumeric(int sqlType) {
switch (sqlType) {
case Types.NUMERIC:
case Types.DECIMAL:
case Types.BIGINT:
case Types.SMALLINT:
case Types.TINYINT:
case Types.INTEGER:
case Types.REAL:
case Types.DOUBLE:
case Types.FLOAT:
return true;
default:
return false;
}
}
}