com.rgi.geopackage.utility.DatabaseUtility 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.utility;
import com.rgi.common.util.jdbc.JdbcUtility;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/**
* @author Luke Lambert
*
*/
public class DatabaseUtility
{
/**
* @param sqliteFile
* the GeoPackag file
* @return the String representation of the sqliteVersion
* @throws IOException
* throws when the FileDoes not exist or unable to seek in the
* File to read the SQLite Version
*/
public static DatabaseVersion getSqliteVersion(final File sqliteFile) throws IOException
{
if(sqliteFile == null)
{
throw new IllegalArgumentException("File cannot be null");
}
if(!sqliteFile.exists())
{
throw new FileNotFoundException("File does not exist: " + sqliteFile.getPath());
}
if(sqliteFile.length() < 100)
{
throw new IllegalArgumentException("File must be at least 100 bytes to be an SQLite file.");
}
try(RandomAccessFile randomAccessFile = new RandomAccessFile(sqliteFile, "r"))
{
// https://www.sqlite.org/fileformat2.html
// Bytes 96 -> 100 are an int representing the sqlite version
randomAccessFile.seek(96);
final int version = randomAccessFile.readInt();
// Major/minor/revision, https://www.sqlite.org/fileformat2.html
final int major = version/1000000;
final int minor = (version - (major*1000000))/1000;
final int revision = version - ((major*1000000) + (minor*1000));
return new DatabaseVersion(major, minor, revision);
}
}
/**
* @param connection
* the connection to the database
* @param name
* the name of the table
* @return true if the table or view exists in the database; otherwise
* returns false
* @throws SQLException
* throws if unable to connect to the database or other various
* SQLExceptions
*/
public static boolean tableOrViewExists(final Connection connection, final String name) throws SQLException
{
DatabaseUtility.verify(connection);
if(name == null || name.isEmpty())
{
throw new IllegalArgumentException("Table/view name cannot be null or empty");
}
return JdbcUtility.selectOne(connection,
"SELECT COUNT(*) FROM sqlite_master WHERE (type = 'table' OR type = 'view') AND name = ? LIMIT 1;",
preparedStatement -> preparedStatement.setString(1, name),
resultSet -> resultSet.getInt(1)) > 0;
}
/**
* @param connection
* the connection to the database
* @param names
* the names of the tables
* @return true if All the tables or views exists in the database; otherwise
* returns false
* @throws SQLException
* throws if unable to connect to the database or other various
* SQLExceptions
*/
public static boolean tablesOrViewsExists(final Connection connection, final String... names) throws SQLException
{
DatabaseUtility.verify(connection);
final Set uniqueNames = new HashSet<>(Arrays.asList(names));
return JdbcUtility.selectOne(connection,
String.format("SELECT COUNT(*) AS count FROM sqlite_master WHERE (type = 'table' OR type = 'view') AND name IN (%s);",
String.join(", ", Collections.nCopies(uniqueNames.size(), "?"))),
preparedStatement -> { int index = 1;
for(final String name : uniqueNames)
{
preparedStatement.setString(index++, name);
}
},
resultSet -> resultSet.getInt("count")) == uniqueNames.size();
}
/**
* @param connection
* connection to the database
* @param applicationId
* the int Application ID to be set
* @throws SQLException
* throws if various SQLExceptions occur
*/
public static void setApplicationId(final Connection connection, final int applicationId) throws SQLException
{
DatabaseUtility.verify(connection);
JdbcUtility.update(connection, String.format("PRAGMA application_id = %d;", applicationId));
}
/**
* @param connection
* connection to the database
* @return the application Id of the database
* @throws SQLException
* throws if various SQLExceptions occur
*/
public static int getApplicationId(final Connection connection) throws SQLException
{
DatabaseUtility.verify(connection);
return JdbcUtility.selectOne(connection,
"PRAGMA application_id;",
null,
resultSet -> resultSet.getInt("application_id"));
}
/**
* @param connection
* connection to the database
* @param state
* true or false whether you want foreign_keys to be set on or
* off
* @throws SQLException
* throws if various SQLExceptions occur
*/
public static void setPragmaForeignKeys(final Connection connection, final boolean state) throws SQLException
{
DatabaseUtility.verify(connection);
JdbcUtility.update(connection, String.format("PRAGMA foreign_keys = %d;", (state ? 1 : 0)));
}
/**
* @param connection
* connection to the database
* @throws SQLException
* throws if various SQLExceptions occur
*/
public static void setPragmaJournalModeMemory(final Connection connection) throws SQLException
{
DatabaseUtility.verify(connection);
JdbcUtility.update(connection, "PRAGMA journal_mode = MEMORY;");
}
/**
* @param connection
* connection to the database
* @throws SQLException
* throws if various SQLExceptions occur
*/
public static void setPragmaSynchronousOff(final Connection connection) throws SQLException
{
DatabaseUtility.verify(connection);
JdbcUtility.update(connection, "PRAGMA synchronous = OFF;");
}
/**
* Get the smallest value for a table and column that does not yet exist
*
*
* @param connection
* connection to the database
* @param tableName
* table name
* @param columnName
* column name
* @return the smallest value for a table and column that does not yet exist
* @throws SQLException
* if there's a database error
*/
@SuppressWarnings("unchecked")
public static T nextValue(final Connection connection, final String tableName, final String columnName) throws SQLException
{
final String smallestNonexistentValue = String.format("SELECT (table1.%1$s + 1) " +
"FROM %2$s AS table1 LEFT JOIN %2$s table2 on table2.%1$s = (table1.%1$s + 1) " +
"WHERE table2.%1$s IS NULL " +
"ORDER BY table1.%1$s " +
"LIMIT 1",
columnName,
tableName);
return JdbcUtility.selectOne(connection,
smallestNonexistentValue,
null,
resultSet -> (T)resultSet.getObject(1));
}
public static List getColumnNames(final Connection connection, final String tableOrViewName) throws SQLException
{
verify(connection);
if(tableOrViewName == null || tableOrViewName.isEmpty())
{
throw new IllegalArgumentException("Table or view name may not be null or empty");
}
return JdbcUtility.select(connection,
String.format("PRAGMA table_info('%s')", tableOrViewName),
null,
resultSet -> resultSet.getString("name"));
}
public static void validateTableName(final String tableName)
{
if(tableName == null || tableName.isEmpty())
{
throw new IllegalArgumentException("The table name may not be null or empty");
}
if(sqlKeywords.stream()
.anyMatch(keyword -> keyword.equalsIgnoreCase(tableName)))
{
throw new IllegalArgumentException(String.format("The table name may not be an SQL keyword: %s", String.join(", ", sqlKeywords)));
}
if(!identifierPattern.matcher(tableName).matches())
{
throw new IllegalArgumentException("The table name must begin with a letter (A..Z, a..z) or an underscore (_) and may only be followed by letters, underscores, or numbers");
}
if(tableName.startsWith("gpkg_"))
{
throw new IllegalArgumentException("The table name may not start with the reserved prefix 'gpkg_'");
}
}
public static void validateColumnName(final String columnName)
{
if(columnName == null || columnName.isEmpty())
{
throw new IllegalArgumentException("The column name may not be null or empty");
}
if(sqlKeywords.stream()
.anyMatch(keyword -> keyword.equalsIgnoreCase(columnName)))
{
throw new IllegalArgumentException(String.format("The column name may not be an SQL keyword: %s", String.join(", ", sqlKeywords)));
}
// TODO
// By wrapping column names in double quotes, you can get away with
// a variety of strange names. This is *strongly* discouraged, but I'm
// not sure it's correct to explicitly forbid it.
if(!identifierPattern.matcher(columnName).matches())
{
// This is just a best guess. SQLite doesn't actually have an EBNF diagram for "column-name"
throw new IllegalArgumentException(String.format("The column '%s' must begin with a letter (A..Z, a..z) or an underscore (_) and may only be followed by letters, underscores, or numbers.",
columnName));
}
}
private static void verify(final Connection connection) throws SQLException
{
if(connection == null || connection.isClosed())
{
throw new IllegalArgumentException("The connection cannot be null or closed.");
}
}
private static final Pattern identifierPattern = Pattern.compile("^[_a-zA-Z]\\w*"); // This is just a best guess. SQLite doesn't actually have an EBNF diagram for "column-name"
private static final Collection sqlKeywords = Arrays.asList("ABORT", "ACTION", "ADD", "AFTER", "ALL",
"ALTER", "ANALYZE", "AND", "AS", "ASC",
"ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN",
"BY", "CASCADE", "CASE", "CAST", "CHECK",
"COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT",
"CREATE", "CROSS", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP",
"DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE",
"DESC", "DETACH", "DISTINCT", "DROP", "EACH",
"ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE",
"EXISTS", "EXPLAIN", "FAIL", "FOR", "FOREIGN",
"FROM", "FULL", "GLOB", "GROUP", "HAVING",
"IF", "IGNORE", "IMMEDIATE", "IN", "INDEX",
"INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD",
"INTERSECT", "INTO", "IS", "ISNULL", "JOIN",
"KEY", "LEFT", "LIKE", "LIMIT", "MATCH",
"NATURAL", "NO", "NOT", "NOTNULL", "NULL",
"OF", "OFFSET", "ON", "OR", "ORDER",
"OUTER", "PLAN", "PRAGMA", "PRIMARY", "QUERY",
"RAISE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX",
"RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT",
"ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET",
"TABLE", "TEMP", "TEMPORARY", "THEN", "TO",
"TRANSACTION", "TRIGGER", "UNION", "UNIQUE", "UPDATE",
"USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL",
"WHEN", "WHERE", "WITH", "WITHOUT");
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy