package com.revolsys.geopackage; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.annotation.PostConstruct; import javax.sql.DataSource; import org.apache.commons.dbcp2.DelegatingConnection; import org.sqlite.core.CoreConnection; import org.sqlite.core.DB; import com.revolsys.collection.map.Maps; import com.revolsys.datatype.DataTypes; import com.revolsys.identifier.Identifier; import com.revolsys.io.PathName; import com.revolsys.jdbc.JdbcConnection; import com.revolsys.jdbc.io.AbstractJdbcRecordStore; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.record.schema.RecordDefinitionImpl; import com.revolsys.record.schema.RecordStoreSchema; import com.revolsys.record.schema.RecordStoreSchemaElement; public class GeoPackageRecordStore extends AbstractJdbcRecordStore { private boolean initialized; public GeoPackageRecordStore(final DataSource dataSource) { super(dataSource); } public GeoPackageRecordStore(final GeoPackage geoPackage, final Map<String, ? extends Object> connectionProperties) { super(geoPackage, connectionProperties); } @Override protected Set<String> getDatabaseSchemaNames() { return Collections.emptySet(); } @Override public Identifier getNextPrimaryKey(final String typePath) { return null; } @Override public String getRecordStoreType() { return "GeoPackage"; } @Override public String getSequenceName(final RecordDefinition recordDefinition) { return null; } @Override @PostConstruct public void initialize() { setUsesSchema(false); final String filter = "WHERE NOT (NAME LIKE 'GPKG%' OR NAME LIKE 'RTREE%' OR NAME LIKE 'SQLITE%')"; setSchemaTablePermissionsSql( "select '/' \"SCHEMA_NAME\", name \"TABLE_NAME\", 'ALL' \"PRIVILEGE\", '' \"REMARKS\" from sqlite_master " + filter + " union all " + "select '/' \"SCHEMA_NAME\", name \"TABLE_NAME\", 'ALL' \"PRIVILEGE\", '' \"REMARKS\" from sqlite_temp_master " + filter); addFieldAdder("BOOLEAN", DataTypes.BOOLEAN); addFieldAdder("TINYINT", DataTypes.BYTE); addFieldAdder("SMALLINT", DataTypes.SHORT); addFieldAdder("MEDIUMINT", DataTypes.INTEGER); addFieldAdder("INT", DataTypes.LONG); addFieldAdder("INTEGER", DataTypes.LONG); addFieldAdder("FLOAT", DataTypes.FLOAT); addFieldAdder("DOUBLE", DataTypes.DOUBLE); addFieldAdder("REAL", DataTypes.DOUBLE); addFieldAdder("TEXT", DataTypes.STRING); addFieldAdder("BLOB", DataTypes.BLOB); addFieldAdder("DATE", DataTypes.SQL_DATE); addFieldAdder("DATETIME", DataTypes.DATE_TIME); final GeoPackageGeometryFieldAdder geometryAdder = new GeoPackageGeometryFieldAdder(); addFieldAdder("GEOMETRY", geometryAdder); addFieldAdder("POINT", geometryAdder); addFieldAdder("LINESTRING", geometryAdder); addFieldAdder("POLYGON", geometryAdder); addFieldAdder("GEOMETRYCOLLECTION", geometryAdder); addFieldAdder("MULTIPOINT", geometryAdder); addFieldAdder("MULTILINESTRING", geometryAdder); addFieldAdder("MULTIPOLYGON", geometryAdder); super.initialize(); try ( JdbcConnection connection = getJdbcConnection(true)) { final CoreConnection sqliteConnection = (CoreConnection)((DelegatingConnection<?>)connection .getConnection()).getInnermostDelegate(); final DB db = sqliteConnection.db(); db.enable_load_extension(true); try { db._exec("select load_extension('libgpkg')"); } finally { db.enable_load_extension(false); } } catch (final SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (!this.initialized) { this.initialized = true; } } @Override public boolean isSchemaExcluded(final String schemaName) { return false; } @Override protected Map<PathName, ? extends RecordStoreSchemaElement> refreshSchemaElementsDo( final RecordStoreSchema schema, final PathName schemaPath) { final String schemaName = schema.getPath(); final Map<String, String> tableDescriptionMap = new HashMap<>(); final Map<String, List<String>> tablePermissionsMap = new TreeMap<>(); loadSchemaTablePermissions("", tablePermissionsMap, tableDescriptionMap); final Map<PathName, RecordStoreSchemaElement> elementsByPath = new TreeMap<>(); final Map<PathName, RecordDefinitionImpl> recordDefinitionMap = new TreeMap<>(); try { try ( final Connection connection = getJdbcConnection()) { final Set<String> tableNames = tablePermissionsMap.keySet(); for (final String dbTableName : tableNames) { final String tableName = dbTableName.toUpperCase(); final PathName typePath = schemaPath.newChild(tableName); setDbSchemaAndTableName(typePath, null, dbTableName); final RecordDefinitionImpl recordDefinition = newRecordDefinition(schema, typePath); final String description = tableDescriptionMap.get(dbTableName); recordDefinition.setDescription(description); final List<String> permissions = Maps.get(tablePermissionsMap, dbTableName, DEFAULT_PERMISSIONS); recordDefinition.setProperty("permissions", permissions); recordDefinitionMap.put(typePath, recordDefinition); elementsByPath.put(typePath, recordDefinition); } for (final RecordDefinitionImpl recordDefinition : recordDefinitionMap.values()) { final PathName pathName = recordDefinition.getPathName(); final String tableName = getDatabaseTableName(pathName); final List<String> idFieldNames = new ArrayList<>(); try ( PreparedStatement columnStatement = connection .prepareStatement("PRAGMA table_info(" + tableName + ")")) { try ( final ResultSet columnsRs = columnStatement.executeQuery()) { while (columnsRs.next()) { final String dbColumnName = columnsRs.getString("name"); final String fieldName = dbColumnName.toUpperCase(); final int sqlType = Types.OTHER; String dataType = columnsRs.getString("type"); int length = -1; final int scale = -1; if (dataType.startsWith("TEXT(")) { length = Integer.parseInt(dataType.substring(5, dataType.length() - 1)); dataType = "TEXT"; } final boolean required = columnsRs.getString("notnull").equals("1"); final boolean primaryKey = columnsRs.getString("pk").equals("1"); if (primaryKey) { idFieldNames.add(fieldName); } final Object defaultValue = columnsRs.getString("dflt_value"); final FieldDefinition field = addField(recordDefinition, dbColumnName, fieldName, dataType, sqlType, length, scale, required, null); field.setDefaultValue(defaultValue); } } } recordDefinition.setIdFieldNames(idFieldNames); } } } catch (final Throwable e) { throw new IllegalArgumentException("Unable to load metadata for schema " + schemaName, e); } return elementsByPath; } }