com.rgi.geopackage.schema.GeoPackageSchema Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swagd Show documentation
Show all versions of swagd Show documentation
SWAGD: Software to Aggregate Geospatial Data
The newest version!
/* The MIT License (MIT)
*
* Copyright (c) 2015 Reinventing Geospatial, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.rgi.geopackage.schema;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.activation.MimeType;
import com.rgi.common.util.jdbc.ResultSetStream;
import com.rgi.geopackage.core.Content;
import com.rgi.geopackage.utility.DatabaseUtility;
import com.rgi.geopackage.utility.SelectBuilder;
import com.rgi.geopackage.verification.VerificationIssue;
import com.rgi.geopackage.verification.VerificationLevel;
/**
* @author Luke Lambert
*
*/
public class GeoPackageSchema
{
/**
* Constructor
*
* @param databaseConnection
* The open connection to the database that contains a GeoPackage
*/
public GeoPackageSchema(final Connection databaseConnection)
{
this.databaseConnection = databaseConnection;
}
/**
* Schema requirements this GeoPackage failed to meet
*
* @param verificationLevel
* Controls the level of verification testing performed
* @return The schema GeoPackage requirements this GeoPackage fails to conform to
* @throws SQLException throws if the method {@link SchemaVerifier#SchemaVerifier} throws an SQLException
*/
public Collection getVerificationIssues(final VerificationLevel verificationLevel) throws SQLException
{
return new SchemaVerifier(this.databaseConnection, verificationLevel).getVerificationIssues();
}
/**
* Add an entry to the geopackage_data_columns table
*
* @param table
* Content table
* @param columnName
* Name of the table column
* @param name
* A human-readable identifier (e.g. short name) for the
* columnName content
* @param title
* A human-readable formal title for the columnName content
* @param description
* A human-readable description for the tableName content
* @param mimeType
* MIME type of columnName if BLOB type, or NULL for other
* types
* @param constraintName
* Case sensitive column value constraint name specified by
* reference to gpkg_data_column_constraints.constraint name
* @return Returns the newly added {@link DataColumn} object
* @throws SQLException
* throws if unable to create the Data Columns Table and unable
* to insert values into the Data Columns Table along with other
* various SQLExceptions
*/
public DataColumn addDataColumn(final Content table,
final String columnName,
final String name,
final String title,
final String description,
final MimeType mimeType,
final String constraintName) throws SQLException
{
if(table == null)
{
throw new IllegalArgumentException("Table may not be null");
}
if(columnName == null || columnName.isEmpty())
{
throw new IllegalArgumentException("Column name may not be null or empty");
}
// TODO check to make sure the table exists and that the column belongs to that table
final DataColumn existingDataColumn = this.getDataColumn(table, columnName);
if(existingDataColumn != null)
{
return existingDataColumn;
}
try
{
this.createDataColumnsTableNoCommit(); // Create the data columns table if it doesn't exist
final String insertDataColumnSql = String.format("INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s) VALUES (?, ?, ?, ?, ?, ?, ?)",
GeoPackageSchema.DataColumnsTableName,
"table_name",
"column_name",
"name",
"title",
"description",
"mime_type",
"constraint_name");
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(insertDataColumnSql))
{
preparedStatement.setString(1, table.getTableName());
preparedStatement.setString(2, columnName);
preparedStatement.setString(3, name);
preparedStatement.setString(4, title);
preparedStatement.setString(5, description);
preparedStatement.setObject(6, mimeType.toString());
preparedStatement.setObject(7, constraintName);
preparedStatement.executeUpdate();
}
final DataColumn dataColumn = new DataColumn(table.getTableName(),
columnName,
name,
title,
description,
mimeType.toString(),
constraintName);
this.databaseConnection.commit();
return dataColumn;
}
catch(final Exception ex)
{
this.databaseConnection.rollback();
throw ex;
}
}
/**
* Adds an entry to the GeoPackage's data column constraints table
*
* @param constraintName
* Case sensitive name of constraint
* @param constraintType
* Constraint type
* @param value
* Specified case sensitive value for enum or glob or
* null
for range constraintType
* @param minimum
* Minimum value for "range" or null
for "enum" or
* "glob" constraintType
* @param minimumIsInclusive
* false
if minimum value is exclusive,
* true
if minimum value is inclusive, or
* null
for "enum" or "glob" constraintType
* @param maximum
* Maximum value for "range" or null
for "enum" or
* "glob" constraintType
* @param maximumIsInclusive
* false
if maximum value is exclusive,
* true
if maximum value is inclusive, or
* null
for "enum" or "glob" constraintType
* @param description
* For ranges and globs, describes the constraint; for enums,
* describes the enum value.
* @return Returns the newly added {@link DataColumnConstraint} object
* @throws SQLException
* throws if unable to create the Data Columns Constraints Table
* and unable to insert values into the Data Columns Constraints
* Table along with other various SQLExceptions
*/
public DataColumnConstraint addDataColumnConstraint(final String constraintName,
final Type constraintType,
final String value,
final Number minimum,
final Boolean minimumIsInclusive,
final Number maximum,
final Boolean maximumIsInclusive,
final String description) throws SQLException
{
if(constraintName == null || constraintName.isEmpty())
{
throw new IllegalArgumentException("Constraint name may not be null or empty");
}
if(constraintType == null)
{
throw new IllegalArgumentException("Constraint type may not be null");
}
if(constraintType == Type.Range && value != null)
{
throw new IllegalArgumentException("Value must be null for range constraints types"); // Requirement 63
}
if(constraintType == Type.Range &&
(minimum == null ||
maximum == null ||
minimum.doubleValue() >= maximum.doubleValue()))
{
throw new IllegalArgumentException("Minimum and maximum may not be null, and minimum must be strictly less than maximum for range constraints types"); // Requirement 64
}
if(constraintType == Type.Range &&
(minimumIsInclusive == null ||
maximumIsInclusive == null))
{
throw new IllegalArgumentException("Inclusivity parameters may not be null, for range constraints types"); // Requirement 65
}
if((constraintType == Type.Enum ||
constraintType == Type.Glob) &&
(minimum != null ||
maximum != null ||
minimumIsInclusive != null ||
maximumIsInclusive != null))
{
throw new IllegalArgumentException("Minimum, maximum and inclusivity parameters must be null for enum and glob constraint types"); // Requirement 66
}
if((constraintType == Type.Enum ||
constraintType == Type.Glob) &&
value == null)
{
throw new IllegalArgumentException("Value may not be null for enum and glob constraint types"); // Requirement 67
}
final DataColumnConstraint existingDataColumnConstraint = this.getDataColumnConstraint(constraintName,
constraintType,
value);
if(existingDataColumnConstraint != null)
{
return existingDataColumnConstraint;
}
try
{
this.createDataColumnConstraintsTableNoCommit(); // Create the data column constraints table if it doesn't exist
final String insertDataColumnSql = String.format("INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s, %s) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
GeoPackageSchema.DataColumnConstraintsTableName,
"constraint_name",
"constraint_type",
"value",
"min",
"minIsInclusive",
"max",
"maxIsInclusive",
"description");
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(insertDataColumnSql))
{
preparedStatement.setString (1, constraintName);
preparedStatement.setString (2, constraintType.toString());
preparedStatement.setString (3, value);
preparedStatement.setObject (4, minimum);
preparedStatement.setObject (5, minimumIsInclusive);
preparedStatement.setObject (6, maximum);
preparedStatement.setObject (7, maximumIsInclusive);
preparedStatement.setObject (8, description);
preparedStatement.executeUpdate();
}
final DataColumnConstraint dataColumnConstraint = new DataColumnConstraint(constraintName,
constraintType.toString(),
value,
minimum,
minimumIsInclusive,
maximum,
maximumIsInclusive,
description);
this.databaseConnection.commit();
return dataColumnConstraint;
}
catch(final Exception ex)
{
this.databaseConnection.rollback();
throw ex;
}
}
/**
* Gets an entry in the GeoPackage data column table that matches the
* supplied criteria
*
* @param table
* Content table
* @param columnName
* Name of the table column
* @return Returns the a {@link DataColumn} that matches the supplied
* criteria, or null if there isn't a match
* @throws SQLException
* throws if the method
* {@link DatabaseUtility#tableOrViewExists(Connection, String)}
* or if other various SQLExceptions occur
*/
public DataColumn getDataColumn(final Content table, final String columnName) throws SQLException
{
if(table == null)
{
throw new IllegalArgumentException("Content table may not be null");
}
if(columnName == null || columnName.isEmpty())
{
throw new IllegalArgumentException("Column name may not be null or empty");
}
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageSchema.DataColumnsTableName))
{
return null;
}
final String dataColumnQuerySql = String.format("SELECT %s, %s, %s, %s, %s FROM %s WHERE %s = ? AND %s = ? LIMIT 1;",
"name",
"title",
"description",
"mime_type",
"constraint_name",
GeoPackageSchema.DataColumnsTableName,
"table_name",
"column_name");
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(dataColumnQuerySql))
{
preparedStatement.setString(1, table.getTableName());
preparedStatement.setString(2, columnName);
try(ResultSet result = preparedStatement.executeQuery())
{
if(result.isBeforeFirst())
{
return new DataColumn(table.getTableName(),
columnName,
result.getString(1), // name
result.getString(2), // title
result.getString(3), // description
result.getString(4), // mime type
result.getString(5)); // constraint name
}
}
return null;
}
}
/**
* Gets an entry in the GeoPackage data column constraints table that
* matches the supplied criteria
*
* @param constraintName
* Case sensitive name of constraint
* @param constraintType
* Constraint type
* @param value
* Specified case sensitive value for enum or glob or
* null
for range constraintType
* @return Returns the a {@link DataColumnConstraint} that matches the
* supplied criteria, or null if there isn't a match
* @throws SQLException
* throws if the method
* {@link DatabaseUtility#tableOrViewExists(Connection, String)}
* or if other various SQLExceptions occur
*/
public DataColumnConstraint getDataColumnConstraint(final String constraintName,
final Type constraintType,
final String value) throws SQLException
{
if(constraintName == null || constraintName.isEmpty())
{
throw new IllegalArgumentException("Constraint name may not be null or empty");
}
if(constraintType == null)
{
throw new IllegalArgumentException("Constraint type may not be null");
}
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageSchema.DataColumnConstraintsTableName))
{
return null;
}
try(final SelectBuilder selectStatement = new SelectBuilder(this.databaseConnection,
GeoPackageSchema.DataColumnConstraintsTableName,
Arrays.asList("min",
"minIsInclusive",
"max",
"maxIsInclusive",
"description"),
Arrays.asList(new AbstractMap.SimpleImmutableEntry<>("constraint_name", constraintName),
new AbstractMap.SimpleImmutableEntry<>("constraint_type", constraintType),
new AbstractMap.SimpleImmutableEntry<>("value", value)));
final ResultSet result = selectStatement.executeQuery())
{
if(result.isBeforeFirst())
{
return new DataColumnConstraint(constraintName,
constraintType.toString(),
value,
(Number) result.getObject(1), // minimum
result.getBoolean(2), // minimum is inclusive
(Number) result.getObject(3), // maximum
result.getBoolean(4), // maximum is inclusive
result.getString(5)); // description
}
}
return null;
}
/**
* Gets every entry in the GeoPackage data columns table
*
* @return Returns a collection of {@link DataColumn} objects that
* represents all of the entries in the GeoPackage data columns
* table
* @throws SQLException
* throws if the method
* {@link DatabaseUtility#tableOrViewExists(Connection, String)}
* or if other various SQLExceptions occur
*/
public Collection getDataColumns() throws SQLException
{
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageSchema.DataColumnsTableName))
{
return Collections.emptyList();
}
final String dataColumnQuerySql = String.format("SELECT %s, %s, %s, %s, %s, %s, %s FROM %s;",
"table_name",
"column_name",
"name",
"title",
"description",
"mime_type",
"constraint_name",
GeoPackageSchema.DataColumnsTableName);
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(dataColumnQuerySql);
ResultSet results = preparedStatement.executeQuery())
{
return ResultSetStream.getStream(results)
.map(result -> { try
{
return new DataColumn(result.getString(1), // table name
result.getString(2), // column name
result.getString(3), // name
result.getString(4), // title
result.getString(5), // description
result.getString(6), // mime type
result.getString(7)); // constraint name
}
catch(final SQLException ex)
{
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
/**
* Gets every entry in the GeoPackage data column constraints table
*
* @return Returns a collection of {@link DataColumnConstraint} objects that
* represents all of the entries in the GeoPackage data column
* constraints table
* @throws SQLException
* throws if the method
* {@link DatabaseUtility#tableOrViewExists(Connection, String)}
* or if other various SQLExceptions occur
*/
public Collection getDataColumnConstraints() throws SQLException
{
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageSchema.DataColumnConstraintsTableName))
{
return Collections.emptyList();
}
final String dataColumnConstraintsQuerySql = String.format("SELECT %s, %s, %s, %s, %s, %s, %s, %s FROM %s;",
"constraint_name",
"constraint_type",
"value",
"min",
"minIsInclusive",
"max",
"maxIsInclusive",
"description",
GeoPackageSchema.DataColumnConstraintsTableName);
try(PreparedStatement preparedStatement = this.databaseConnection.prepareStatement(dataColumnConstraintsQuerySql);
ResultSet results = preparedStatement.executeQuery())
{
return ResultSetStream.getStream(results)
.map(result -> { try
{
return new DataColumnConstraint( result.getString(1), // constraint name
result.getString(2), // constraint type
result.getString(3), // value
(Number) result.getObject(4), // minimum
cast((Integer)result.getObject(5)), // minimum is inclusive
(Number) result.getObject(6), // maximum
cast((Integer)result.getObject(7)), // maximum is inclusive
result.getString(8)); // description
}
catch(final SQLException ex)
{
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
private static Boolean cast(final Integer integer)
{
return integer == null ? null : integer != 0;
}
/**
* Creates the GeoPackage data columns table
*
*
* **WARNING** this does not do a database commit. It is expected
* that this transaction will always be paired with others that need to be
* committed or rollback as a single transaction.
*
* @throws SQLException
*/
protected void createDataColumnsTableNoCommit() throws SQLException
{
// Create the data columns table
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageSchema.DataColumnsTableName))
{
try(Statement statement = this.databaseConnection.createStatement())
{
statement.executeUpdate(this.getDataColumnsTableCreationSql());
}
}
}
/**
* Creates the GeoPackage data column constraints table
*
*
* **WARNING** this does not do a database commit. It is expected
* that this transaction will always be paired with others that need to be
* committed or rollback as a single transaction.
*
* @throws SQLException
*/
protected void createDataColumnConstraintsTableNoCommit() throws SQLException
{
// Create the data column constraints table
if(!DatabaseUtility.tableOrViewExists(this.databaseConnection, GeoPackageSchema.DataColumnConstraintsTableName))
{
try(Statement statement = this.databaseConnection.createStatement())
{
statement.executeUpdate(this.getDataColumnConstraintsTableCreationSql());
}
}
}
@SuppressWarnings("static-method")
protected String getDataColumnsTableCreationSql()
{
// http://www.geopackage.org/spec/#gpkg_data_columns_cols
// http://www.geopackage.org/spec/#gpkg_data_columns_sql
return "CREATE TABLE " + GeoPackageSchema.DataColumnsTableName + "\n" +
"(table_name TEXT NOT NULL, -- Name of the tiles or feature table\n" +
" column_name TEXT NOT NULL, -- Name of the table column\n" +
" name TEXT, -- A human-readable identifier (e.g. short name) for the columnName content\n" +
" title TEXT, -- A human-readable formal title for the columnName content\n" +
" description TEXT, -- A human-readable description for the tableName content\n" +
" mime_type TEXT, -- MIME type of columnName if BLOB type, or NULL for other types\n" +
" constraint_name TEXT, -- Case sensitive column value constraint name specified by reference to gpkg_data_column_constraints.constraint name\n" +
" CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),\n" +
" CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES gpkg_contents(table_name));";
}
@SuppressWarnings("static-method")
protected String getDataColumnConstraintsTableCreationSql()
{
// http://www.geopackage.org/spec/#gpkg_data_column_constraints_cols
// http://www.geopackage.org/spec/#gpkg_data_column_constraints_sql
return "CREATE TABLE " + GeoPackageSchema.DataColumnConstraintsTableName + "\n" +
"(constraint_name TEXT NOT NULL, -- Case sensitive name of constraint\n" +
" constraint_type TEXT NOT NULL, -- Lowercase type name of constraint: 'range', 'enum', or 'glob'\n" +
" value TEXT, -- Specified case sensitive value for enum or glob or null for range constraintType\n" +
" min NUMERIC, -- Minimum value for 'range' or null for 'enum' or 'glob' constraintType\n" +
" minIsInclusive BOOLEAN, -- false if minimum value is exclusive, or true if minimum value is inclusive\n" +
" max NUMERIC, -- Maximum value for 'range' or null for 'enum' or 'glob' constraintType\n" +
" maxIsInclusive BOOLEAN, -- false if maximum value is exclusive, or true if maximum value is inclusive\n" +
" description TEXT, -- For ranges and globs, describes the constraint; for enums, describes the enum value.\n" +
" CONSTRAINT gdcc_ntv UNIQUE (constraint_name, constraint_type, value));";
}
private final Connection databaseConnection;
/**
* The name of the Data Columns Table "gpkg_data_columns"
* http://www.geopackage.org/spec/#_data_columns
*/
public final static String DataColumnsTableName = "gpkg_data_columns";
/**
* The name of the Data Column Constraints Table "gpkg_data_column_constraints"
* http://www.geopackage.org/spec/#_data_columns_constraints
*/
public final static String DataColumnConstraintsTableName = "gpkg_data_column_constraints";
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy