/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2011, Open Source Geospatial Foundation (OSGeo)
*
* This library 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 2.1 of the License.
*
* This library 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.
*/
package org.geotools.data.teradata;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
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.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.geotools.filter.function.EnvFunction;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.jdbc.JDBCDataStore;
import org.geotools.jdbc.NullPrimaryKey;
import org.geotools.jdbc.PreparedFilterToSQL;
import org.geotools.jdbc.PreparedStatementSQLDialect;
import org.geotools.jdbc.PrimaryKey;
import org.geotools.jdbc.PrimaryKeyColumn;
import org.geotools.referencing.CRS;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.io.WKTWriter;
import java.text.MessageFormat;
public class TeradataDialect extends PreparedStatementSQLDialect {
/**
* sysspatial schema
*/
final static String SYSSPATIAL = "sysspatial";
/**
* tessellation information table
*/
final static String TESSELLATION = "tessellation";
/**
* geometry columns table
*/
final static String GEOMETRY_COLUMNS = "geometry_columns";
/**
* spatial ref sys
*/
final static String SPATIAL_REF_SYS = "spatial_ref_sys";
/**
* key for spatial index table
*/
final static String SPATIAL_INDEX = "org.geotools.data.teradata.spatialIndex";
final static Map<String, Class<?>> TYPE_TO_CLASS = new HashMap<String, Class<?>>() {
{
put("GEOMETRY", Geometry.class);
put("POINT", Point.class);
put("LINESTRING", LineString.class);
put("POLYGON", Polygon.class);
put("MULTIPOINT", MultiPoint.class);
put("MULTILINESTRING", MultiLineString.class);
put("MULTIPOLYGON", MultiPolygon.class);
put("GEOMETRYCOLLECTION", GeometryCollection.class);
put("GEOSEQUENCE", Geometry.class);
}
};
final static Map<Class<?>, String> CLASS_TO_TYPE = new HashMap<Class<?>, String>() {
{
put(Geometry.class, "GEOMETRY");
put(Point.class, "POINT");
put(LineString.class, "LINESTRING");
put(Polygon.class, "POLYGON");
put(MultiPoint.class, "MULTIPOINT");
put(MultiLineString.class, "MULTILINESTRING");
put(MultiPolygon.class, "MULTIPOLYGON");
put(GeometryCollection.class, "GEOMETRYCOLLECTION");
}
};
/** loose bbox flag */
boolean looseBBOXEnabled = false;
/** estimated bounds */
boolean estimatedBounds = false;
/** ApplicationName query band */
String application;
/** teradata version */
int tdVersion = -1;
/** support LOB workaround */
private boolean lobWorkaroundEnabled;
public TeradataDialect(JDBCDataStore store) {
super(store);
}
public boolean isLobWorkaroundEnabled() {
return lobWorkaroundEnabled;
}
public void setLobWorkaroundEnabled(boolean lobWorkaroundEnabled) {
this.lobWorkaroundEnabled = lobWorkaroundEnabled;
}
public void setLooseBBOXEnabled(boolean looseBBOXEnabled) {
this.looseBBOXEnabled = looseBBOXEnabled;
}
public boolean isLooseBBOXEnabled() {
return looseBBOXEnabled;
}
public void setEstimatedBounds(boolean estimatedBounds) {
this.estimatedBounds = estimatedBounds;
}
public boolean isEstimatedBounds() {
return estimatedBounds;
}
public int getTdVersion() {
return tdVersion;
}
public void setApplication(String application) {
this.application = application;
}
@Override
public void initializeConnection(Connection cx) throws SQLException {
if (tdVersion == -1) {
tdVersion = cx.getMetaData().getDatabaseMajorVersion();
}
//JD: for some reason this does not work if we use a prepared statement
String sql = String.format("SET QUERY_BAND='%s;' FOR SESSION", QueryBand.APPLICATION + "="
+ (application != null ? application : TeradataDataStoreFactory.APPLICATION.sample));
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(sql);
}
Statement st = cx.createStatement();
try {
st.execute(sql);
}
finally {
st.close();
}
// String sql = "SET QUERY_BAND=? FOR SESSION";
// String qb = QueryBand.APPLICATION + "=GeoTools;";
// if (LOGGER.isLoggable(Level.FINE)) {
// LOGGER.fine(String.format("%s;1=%s", sql, qb));
// }
// PreparedStatement ps = cx.prepareStatement(sql);
// try {
// ps.setString(1, qb);
// ps.execute();
// }
// finally {
// dataStore.closeSafe(ps);
// }
}
@Override
public boolean includeTable(String schemaName, String tableName, Connection cx)
throws SQLException {
if (tableName.equalsIgnoreCase("geometry_columns")) {
return false;
} else if (tableName.toLowerCase().startsWith("spatial_ref_sys")) {
return false;
} else if (tableName.equalsIgnoreCase("geography_columns")) {
return false;
} else if (tableName.equalsIgnoreCase("tessellation")) {
return false;
} else if (tableName.endsWith("_idx")) {
return false;
}
// others?
return dataStore.getDatabaseSchema() == null
|| dataStore.getDatabaseSchema().equals(schemaName);
}
@Override
public void setGeometryValue(Geometry g, int srid, Class binding,
PreparedStatement ps, int column) throws SQLException {
if (g != null) {
if (g instanceof LinearRing ) {
//teradata does not handle linear rings, convert to just a line string
g = g.getFactory().createLineString(((LinearRing) g).getCoordinateSequence());
}
//TODO: use WKB instead of WKT
String wkt = new WKTWriter().write(g);
if (wkt.length() > 64000) {
ByteArrayInputStream bin = new ByteArrayInputStream(wkt.getBytes());
ps.setAsciiStream(column, bin, bin.available());
}
else {
ps.setString(column, wkt);
}
}
else {
ps.setNull(column, Types.OTHER, "Geometry");
}
}
@Override
public Geometry decodeGeometryValue(GeometryDescriptor descriptor, ResultSet rs, String column,
GeometryFactory factory, Connection cx) throws IOException, SQLException {
try {
// first check for the inline geometry value, applied in td 13 and above
String wkt = null;
try {
wkt = rs.getString(column + "_inline");
}
catch(SQLException e) {}
if (wkt != null) {
return new WKTReader(factory).read(wkt);
}
//in "locator" form the geometry comes across as text
//figure out index so we can inspect type
int index = -1;
for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) {
if (column.equals(rs.getMetaData().getColumnName(i+1))) {
index = i+1;
break;
}
}
if ("java.lang.String".equals(rs.getMetaData().getColumnClassName(index))) {
wkt = rs.getString(index);
if (wkt == null) {
return null;
}
return new WKTReader(factory).read(wkt);
}
//assume its a clob
Clob clob = rs.getClob(column);
if (clob == null) {
return null;
}
InputStream in = clob.getAsciiStream();
try {
return new WKTReader(factory).read(new InputStreamReader(in));
}
finally {
if (in != null) in.close();
}
}
catch (ParseException e) {
throw (IOException) new IOException("Error parsing geometry").initCause(e);
}
}
WKBAttributeIO getWkbReader(GeometryFactory factory) {
factory = factory != null ? factory : dataStore.getGeometryFactory();
return new WKBAttributeIO(factory);
}
@Override
public Envelope decodeGeometryEnvelope(ResultSet rs, int column,
Connection cx) throws SQLException, IOException {
Geometry envelope = getWkbReader(null).read(rs, column);
if (envelope != null) {
return envelope.getEnvelopeInternal();
} else {
// empty one
return new Envelope();
}
}
@Override
public void encodeColumnType(String sqlTypeName, StringBuffer sql) {
if ("DECIMAL".equals(sqlTypeName)) {
sql.append(sqlTypeName).append("(10,2)");
}
else {
super.encodeColumnType(sqlTypeName, sql);
}
}
@Override
public void encodeGeometryEnvelope(String tableName, String geometryColumn,
StringBuffer sql) {
encodeColumnName(geometryColumn, sql);
sql.append(".ST_Envelope().ST_AsBinary()");
}
@Override
public void encodePostSelect(SimpleFeatureType featureType, StringBuffer sql) {
//Since geometries are stored as LOB's in teradata reading a geometry value in TD requires
// as second trip to the database. Since this is expensive we attempt to cast the geometry
// into a 64K byte string which is the limit of what TD will include inline. For large
// geometries we will have to check and fall back onto readin the Blob, but for smaller
// geometries this will save us the second trip
// see decodeGeometryValue()
// CASE WHEN CHARACTERS("the_geom") > 16000 THEN NULL ELSE CAST("the_geom" AS VARCHAR(16000)) END as "the_geom_inline
// Note: this only applies to TD 13 and up
if (tdVersion > 12 && lobWorkaroundEnabled) {
for (AttributeDescriptor att : featureType.getAttributeDescriptors()) {
if (att instanceof GeometryDescriptor) {
sql.append(", CASE WHEN CHARACTERS(cast(");
encodeColumnName(att.getLocalName(), sql);
sql.append(" as clob)) > 30000 THEN NULL ELSE CAST (");
encodeColumnName(att.getLocalName(), sql);
// works but not ideal, assumes rest of attributes consume < 2000 characters in result set
sql.append(" as VARCHAR(30000)) END");
encodeColumnAlias(att.getLocalName() + "_inline", sql);
}
}
}
}
@Override
public List<ReferencedEnvelope> getOptimizedBounds(String schema,
SimpleFeatureType featureType, Connection cx) throws SQLException, IOException {
if (!estimatedBounds) {
return null;
}
String tableName = featureType.getTypeName();
if (tdVersion > 12) {
//first check the geometry_columns
StringBuffer sql = new StringBuffer();
sql.append("SELECT gc.UxMin, gc.UyMin, gc.UxMax, gc.UyMax, srs.AUTH_SRID FROM ");
encodeTableName(SYSSPATIAL, GEOMETRY_COLUMNS, sql);
encodeTableAlias("gc", sql);
sql.append(", ");
encodeTableName(SYSSPATIAL, SPATIAL_REF_SYS, sql);
encodeTableAlias("srs", sql);
sql.append(" WHERE ");
sql.append("gc.");
encodeColumnName("SRID", sql);
sql.append(" = ");
sql.append("srs.");
encodeColumnName("SRID", sql);
sql.append(" AND gc.");
encodeColumnName("F_TABLE_SCHEMA", sql);
sql.append(" = ?").append(" AND ");
sql.append("gc.");
encodeColumnName("F_TABLE_NAME", sql);
sql.append(" = ? ");
//AND gc.UxMin IS NOT NULL AND gc.UyMin IS NOT NULL AND UxMax IS NOT NULL")
// .append(" AND ymax is NOT NULL");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(String.format("%s;1=%s;2=%s", sql.toString(), schema, tableName));
}
PreparedStatement ps = cx.prepareStatement(sql.toString());
try {
ps.setString(1, schema);
ps.setString(2, tableName);
ResultSet rs = null;
try {
rs = ps.executeQuery();
List<ReferencedEnvelope> envs = new ArrayList();
if (rs.next()) {
int srid = rs.getInt(5);
ReferencedEnvelope env = new ReferencedEnvelope(rs.getDouble(1),
rs.getDouble(3), rs.getDouble(2), rs.getDouble(4), CRS.decode("EPSG:"+srid));
//check for "empty" envelope, means values were not set in geometry_columns
// table, fall out
if (env.isEmpty() || env.isNull() ||
(env.getWidth() == 0 && env.getMinX() == 0)) {
throw new Exception("Empty universe in geometry columns");
}
envs.add(env);
}
return envs;
}
catch(Exception e) {
//ignore and fall through
LOGGER.log(Level.FINER, "Error query geometry_columns", e);
}
finally {
dataStore.closeSafe(rs);
}
}
finally {
dataStore.closeSafe(ps);
}
}
//fall back on tessellation entry
List<TessellationInfo> tinfos =
lookupTessellationInfos(cx, schema, featureType.getTypeName(), null);
if (tinfos.isEmpty()) {
return null;
}
List<ReferencedEnvelope> envs = new ArrayList();
for (TessellationInfo tinfo : tinfos) {
GeometryDescriptor gatt =
(GeometryDescriptor) featureType.getDescriptor(tinfo.getColumName());
ReferencedEnvelope env = new ReferencedEnvelope(gatt.getCoordinateReferenceSystem());
env.init(tinfo.getUBounds());
envs.add(env);
}
return envs;
}
/**
* Prevent deadlock - old behavior in JDBCFeatureSource was to disable
* autocommit in getReaderInternal to work around a postgres bug. This caused
* database deadlock many times as the 'read' transaction would never complete
* and subsequent writes would lock.
* @return true
*/
@Override
public boolean isAutoCommitQuery() {
return true;
}
public void encodePrimaryKey(String column, StringBuffer sql) {
encodeColumnName(column, sql);
// sql.append(" PRIMARY KEY not null generated always as identity (start with 0) integer");
sql.append(" PRIMARY KEY not null integer");
}
public Integer getGeometrySRID(String schemaName, String tableName,
String columnName, Connection cx) throws SQLException {
StringBuffer sql = new StringBuffer("SELECT ref.AUTH_SRID FROM ");
encodeTableName(SYSSPATIAL, SPATIAL_REF_SYS, sql);
sql.append(" as ref, ");
encodeTableName(SYSSPATIAL, GEOMETRY_COLUMNS, sql);
sql.append(" as col ");
sql.append(" WHERE col.F_TABLE_SCHEMA = ?");
sql.append(" AND col.F_TABLE_NAME = ? ");
sql.append(" AND col.F_GEOMETRY_COLUMN = ? ");
sql.append(" AND col.SRID = ref.SRID");
LOGGER.log(Level.FINE, String.format("%s; 1=%s, 2=%s, 3=%s", sql.toString(), schemaName,
tableName, columnName));
PreparedStatement ps = cx.prepareStatement(sql.toString());
try {
ps.setString(1, schemaName);
ps.setString(2, tableName);
ps.setString(3, columnName);
ResultSet rs = ps.executeQuery();
try {
if (rs.next()) {
return rs.getInt(1);
}
else {
LOGGER.warning(String.format("No SRID entry for %s, %s, %s", schemaName,
tableName, columnName));
}
}
finally {
dataStore.closeSafe(rs);
}
}
finally {
dataStore.closeSafe(ps);
}
return null;
}
public String getGeometryTypeName(Integer type) {
return "ST_Geometry";
}
public Class<?> getMapping(ResultSet columnMetaData, Connection cx)
throws SQLException {
String typeName = columnMetaData.getString("TYPE_NAME");
String gType = null;
if ("SYSUDTLIB.ST_GEOMETRY".equalsIgnoreCase(typeName)) {
gType = lookupGeometryType(columnMetaData, cx);
}
else {
return null;
}
// decode the type into
if (gType == null) {
// it's either a generic geography or geometry not registered in the
// medatata tables
return Geometry.class;
} else {
Class<?> geometryClass = TYPE_TO_CLASS.get(gType.toUpperCase());
if (geometryClass == null) {
geometryClass = Geometry.class;
}
return geometryClass;
}
}
@Override
public boolean lookupGeneratedValuesPostInsert() {
return true;
}
@Override
public Object getLastAutoGeneratedValue(String schemaName, String tableName, String columnName,
Connection cx) throws SQLException {
StringBuffer sql = new StringBuffer("SELECT TOP 1 ");
encodeColumnName(columnName, sql);
sql.append(" FROM ");
encodeTableName(schemaName, tableName, sql);
sql.append(" ORDER BY ");
encodeColumnName(columnName, sql);
sql.append(" DESC");
LOGGER.fine(sql.toString());
PreparedStatement ps = cx.prepareStatement(sql.toString());
try {
ResultSet rs = ps.executeQuery();
try {
if (rs.next()) {
return rs.getInt(1);
}
}
finally {
dataStore.closeSafe(rs);
}
}
finally {
dataStore.closeSafe(ps);
}
return null;
}
/**
* Looks up geometry type of schema/table/column.
*/
String lookupGeometryType(ResultSet columnMetaData, Connection cx) throws SQLException {
// grab the information we need to proceed
String schemaName = columnMetaData.getString("TABLE_SCHEM");
String tableName = columnMetaData.getString("TABLE_NAME");
String columnName = columnMetaData.getString("COLUMN_NAME");
StringBuffer sql = new StringBuffer("SELECT GEOM_TYPE");
sql.append(" FROM ");
encodeTableName(SYSSPATIAL, GEOMETRY_COLUMNS, sql);
sql.append("WHERE F_TABLE_SCHEMA = ? AND F_TABLE_NAME = ? AND F_GEOMETRY_COLUMN = ?");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(String.format("%s; 1=%s, 2=%s, 3=%s", sql.toString(), schemaName,
tableName, columnName));
}
PreparedStatement ps = cx.prepareStatement(sql.toString());
try {
ps.setString(1, schemaName);
ps.setString(2, tableName);
ps.setString(3, columnName);
ResultSet rs = ps.executeQuery();
try {
if (rs.next()) {
return rs.getString(1);
}
}
finally {
dataStore.closeSafe(rs);
}
}
finally {
dataStore.closeSafe(ps);
}
return null;
}
/**
* Looks up tessellation info for the schema/table/geometry.
*/
TessellationInfo lookupTessellationInfo(Connection cx, String schemaName, String tableName,
String columnName) throws SQLException {
if (columnName == null) {
throw new IllegalArgumentException("Column name must not be null");
}
List<TessellationInfo> tinfos = lookupTessellationInfos(cx, schemaName, tableName, columnName);
return !tinfos.isEmpty() ? tinfos.get(0) : null;
}
/**
* Looks up tessellation info for the schema/table.
* <p>
* The schema of the tessellation table is:
* <pre>
* F_TABLE_SCHEMA VARCHAR(128) CHARACTER SET UNICODE NOT CASESPECIFIC NOT NULL,
* F_TABLE_NAME VARCHAR(128) CHARACTER SET UNICODE NOT CASESPECIFIC NOT NULL,
* F_GEOMETRY_COLUMN VARCHAR(128) CHARACTER SET UNICODE NOT CASESPECIFIC NOT NULL,
* U_XMIN FLOAT,
* U_YMIN FLOAT,
* U_XMAX FLOAT,
* U_YMAX FLOAT,
* G_NX INTEGER,
* G_NY INTEGER,
* LEVELS INTEGER,
* SCALE FLOAT,
* SHIFT INTEGER)
* </pre>
* </p>
*/
List<TessellationInfo> lookupTessellationInfos(Connection cx, String schemaName, String tableName,
String columnName) throws SQLException {
ResultSet tables = cx.getMetaData().getTables(
null, "sysspatial", TESSELLATION, new String[]{"TABLE"});
try {
if (!tables.next()) {
LOGGER.warning("sysspatial." + TESSELLATION + " does not exist. Unable to " +
" perform spatially index queries.");
return Collections.EMPTY_LIST;
}
}
finally {
dataStore.closeSafe(tables);
}
List<TessellationInfo> tinfos = new ArrayList();
StringBuffer sql = new StringBuffer();
sql.append("SELECT * FROM ");
encodeTableName(SYSSPATIAL, TESSELLATION, sql);
sql.append(" WHERE ");
encodeColumnName("F_TABLE_SCHEMA", sql);
sql.append(" = ?").append(" AND ");
encodeColumnName("F_TABLE_NAME", sql);
sql.append(" = ?");
if (columnName != null) {
sql.append(" AND ");
encodeColumnName("F_GEOMETRY_COLUMN", sql);
sql.append(" = ?");
}
LOGGER.fine(sql.toString());
PreparedStatement ps = cx.prepareStatement(sql.toString());
try {
ps.setString(1, schemaName);
ps.setString(2, tableName);
if (columnName != null) {
ps.setString(3, columnName);
}
ResultSet rs = ps.executeQuery();
try {
if (rs.next()) {
TessellationInfo tinfo = new TessellationInfo();
tinfo.setUBounds(new Envelope(rs.getDouble("U_XMIN"), rs.getDouble("U_XMAX"),
rs.getDouble("U_YMIN"), rs.getDouble("U_YMAX")));
tinfo.setNx(rs.getInt("G_NX"));
tinfo.setNy(rs.getInt("G_NY"));
tinfo.setLevels(rs.getInt("LEVELS"));
tinfo.setScale(rs.getDouble("SCALE"));
tinfo.setShift(rs.getInt("SHIFT"));
tinfo.setColumName(rs.getString("F_GEOMETRY_COLUMN"));
tinfo.setSchemaName(schemaName);
tinfo.setTableName(tableName);
//look up the spatial index table
tables = cx.getMetaData().getTables(
null, schemaName, tableName+"_"+columnName+"_idx", new String[]{"TABLE"});
try {
if (tables.next()) {
tinfo.setIndexTableName(tables.getString("TABLE_NAME"));
}
}
finally {
dataStore.closeSafe(tables);
}
tinfos.add(tinfo);
}
}
finally {
dataStore.closeSafe(rs);
}
}
finally {
dataStore.closeSafe(ps);
}
return tinfos;
}
/**
* Returns the database typename of the column by inspecting the metadata.
*/
public String lookupSqlTypeName(Connection cx, String schemaName, String tableName,
String columnName) throws SQLException {
ResultSet columns = null;
try {
columns = cx.getMetaData().getColumns(null, schemaName, tableName, columnName);
columns.next();
return columns.getString("TYPE_NAME");
}
finally {
columns.close();
}
}
void encodeTableName(String schemaName, String tableName, StringBuffer sql) {
if (schemaName != null && !"".equals(schemaName.trim())) {
encodeSchemaName(schemaName, sql);
sql.append(".");
}
encodeTableName(tableName, sql);
}
@Override
public void postCreateAttribute(AttributeDescriptor att, String tableName, String schemaName,
Connection cx) throws SQLException {
if (att instanceof GeometryDescriptor) {
//look up tessellation info
TessellationInfo tinfo =
lookupTessellationInfo(cx, schemaName, tableName, att.getLocalName());
if (tinfo != null) {
att.getUserData().put(TessellationInfo.KEY, tinfo);
}
else {
LOGGER.fine(String.format("%s.%s.(%s) does not have tessellation entry.", schemaName,
tableName, att.getLocalName()));
}
}
}
public void postCreateTable(String schemaName, SimpleFeatureType featureType, Connection cx)
throws SQLException {
String tableName = featureType.getName().getLocalPart();
// register all geometry columns in the database
for (AttributeDescriptor att : featureType.getAttributeDescriptors()) {
if (att instanceof GeometryDescriptor) {
GeometryDescriptor gd = (GeometryDescriptor) att;
//figure out the native db srid
int srid = 0;
Integer epsg = null;
if (gd.getCoordinateReferenceSystem() != null) {
try {
epsg = CRS.lookupEpsgCode(gd.getCoordinateReferenceSystem(), true);
}
catch(Exception e) {
LOGGER.log(Level.WARNING, "Error looking up epsg code", e);
}
}
if (epsg != null) {
String sql = "SELECT SRID FROM SYSSPATIAL.spatial_ref_sys"
+ " WHERE AUTH_SRID = ?";
LOGGER.log(Level.FINE, sql + ";{0}", epsg);
PreparedStatement ps = cx.prepareStatement(sql);
try {
ps.setInt(1, epsg);
ResultSet rs = ps.executeQuery();
try {
if (rs.next()) {
srid = rs.getInt("SRID");
}
else {
LOGGER.warning("EPSG Code " + epsg + " does not map to SRID");
}
}
finally {
dataStore.closeSafe(ps);
}
}
finally {
dataStore.closeSafe(ps);
}
}
// grab the geometry type
String geomType = CLASS_TO_TYPE.get(gd.getType().getBinding());
geomType = geomType != null ? geomType : "GEOMETRY";
//insert into geometry columns table
String sql =
"INSERT INTO SYSSPATIAL.GEOMETRY_COLUMNS (F_TABLE_CATALOG, F_TABLE_SCHEMA, " +
"F_TABLE_NAME, F_GEOMETRY_COLUMN, COORD_DIMENSION, SRID, GEOM_TYPE) " +
"VALUES (?, ?, ?, ?, 2, ?, ?)";
LOGGER.log(Level.FINE, sql + ";{0},{1},{2},{3},{4},{5}",
new Object[]{"", schemaName, tableName, gd.getLocalName(), srid, geomType});
PreparedStatement ps = cx.prepareStatement(sql);
try {
ps.setString(1, "");
ps.setString(2, schemaName);
ps.setString(3, tableName);
ps.setString(4, gd.getLocalName());
ps.setInt(5, srid);
ps.setString(6, geomType);
ps.execute();
}
finally {
dataStore.closeSafe(ps);
}
//create the spatial index table
PrimaryKey pkey = dataStore.getPrimaryKeyFinder()
.getPrimaryKey(dataStore, schemaName, tableName, cx);
if (!(pkey instanceof NullPrimaryKey)) {
String indexTableName = tableName + "_" + gd.getLocalName() + "_idx";
String hashIndex = indexTableName + "_idx";
// drop index hash index if exists
StringBuffer sb = new StringBuffer("DROP HASH INDEX ");
encodeTableName(schemaName, hashIndex, sb);
sql = sb.toString();
LOGGER.fine(sql);
try {
ps = cx.prepareStatement(sql);
ps.execute();
}
catch(SQLException e) {
//ignore
}
finally {
dataStore.closeSafe(ps);
}
// drop index table if exists
sb = new StringBuffer("DROP TABLE ");
encodeTableName(schemaName, indexTableName, sb);
sql = sb.toString();
LOGGER.fine(sql);
try {
ps = cx.prepareStatement(sql);
ps.execute();
}
catch(SQLException e) {
//ignore
}
finally {
dataStore.closeSafe(ps);
}
// create index table
sb = new StringBuffer("CREATE MULTISET TABLE ");
encodeTableName(schemaName, indexTableName, sb);
sb.append("( ");
for (PrimaryKeyColumn col : pkey.getColumns()) {
encodeColumnName(col.getName(), sb);
String typeName = lookupSqlTypeName(cx, schemaName, tableName, col.getName());
sb.append(" ").append(typeName).append(" NOT NULL, ");
}
if (!pkey.getColumns().isEmpty()) {
// @todo only looking at first primary key
// more multiply keyed tables, this at least ensures some speed
sb.append("cellid INTEGER NOT NULL)");
sb.append("PRIMARY INDEX (");
encodeColumnName(pkey.getColumns().get(0).getName(), sb);
sb.append(")");
}
sql = sb.toString();
LOGGER.fine(sql);
try {
ps = cx.prepareStatement(sql);
ps.execute();
} finally {
dataStore.closeSafe(ps);
}
// create hash
sb = new StringBuffer("CREATE HASH INDEX " + hashIndex + " (cellid) ON " + indexTableName + " ORDER BY (cellid)");
sql = sb.toString();
LOGGER.fine(sql);
try {
ps = cx.prepareStatement(sql);
ps.execute();
} finally {
dataStore.closeSafe(ps);
}
installTriggers(cx,tableName,gd.getLocalName(),indexTableName,pkey.getColumns());
}
else {
LOGGER.warning("No primary key for " + schemaName + "." + tableName + ". Unable"
+ " to create spatial index.");
}
}
//cx.commit();
}
}
public void registerClassToSqlMappings(Map<Class<?>, Integer> mappings) {
super.registerClassToSqlMappings(mappings);
// jdbc metadata for geom columns reports DATA_TYPE=1111=Types.OTHER
mappings.put(Geometry.class, Types.OTHER);
}
public void registerSqlTypeNameToClassMappings(
Map<String, Class<?>> mappings) {
super.registerSqlTypeNameToClassMappings(mappings);
mappings.put("ST_GEOMETRY", Geometry.class);
mappings.put("SYSUDTLIB.ST_GEOMETRY", Geometry.class);
}
@Override
public void registerSqlTypeToSqlTypeNameOverrides(
Map<Integer, String> overrides) {
overrides.put(Types.VARCHAR, "VARCHAR");
overrides.put(Types.DOUBLE, "FLOAT");
overrides.put(Types.NUMERIC, "DECIMAL");
}
@Override
public boolean isLimitOffsetSupported() {
//JD: Currently we don't have the ability to specify either limit or
// offset, and currently we actually only do limit. Change this method
// to return false if you want to pass the test suite
return true;
}
static Pattern ORDER_BY_QUERY =
Pattern.compile(".*(ORDER BY (?:,? *\"?[\\w]+\"?(?: (?:ASC)|(:?DESC))?)+)");
static Pattern ORDER_BY =
Pattern.compile("ORDER BY (?:,? *\"?[\\w]+\"?(?: (?:ASC)|(:?DESC))?)+");
@Override
public void applyLimitOffset(StringBuffer sql, int limit, int offset) {
if (offset == 0) {
//use TOP N
int i = sql.indexOf("SELECT");
sql.insert(i+6, " TOP " + limit);
}
else {
//use ROW_NUMBER() function
//TODO: this actually doesn't work when an ORDER BY is present because the row_number()
// gets sorted as well
//this is a hack but subqueries can't have ORDER BY clause... so strip it off and
// append it to the end
Matcher m = ORDER_BY_QUERY.matcher(sql.toString());
String orderBy = null;
if (m.matches()) {
orderBy = m.group(1);
//strip it out
m = ORDER_BY.matcher(sql.toString());
String s = m.replaceAll("");
sql.setLength(0);
sql.append(s);
}
sql.insert(0, "SELECT t.*, ROW_NUMBER() OVER (ORDER BY 'foo') AS row_num FROM (");
sql.append(") AS t ");
if (orderBy != null) {
sql.append(orderBy).append(" ");
}
long max = (limit == Integer.MAX_VALUE ? Long.MAX_VALUE : limit + offset);
sql.append("QUALIFY row_num > ").append(offset).append(" AND row_num <= ")
.append(max);
}
}
@Override
public PreparedFilterToSQL createPreparedFilterToSQL() {
return new TeradataFilterToSQL(this);
}
@Override
public void onSelect(PreparedStatement select, Connection cx, SimpleFeatureType featureType)
throws SQLException {
setQueryBand(cx, "SELECT");
}
@Override
public void onDelete(PreparedStatement delete, Connection cx, SimpleFeatureType featureType)
throws SQLException {
setQueryBand(cx, "DELETE");
}
@Override
public void onInsert(PreparedStatement insert, Connection cx, SimpleFeatureType featureType)
throws SQLException {
setQueryBand(cx, "INSERT");
}
@Override
public void onUpdate(PreparedStatement update, Connection cx, SimpleFeatureType featureType)
throws SQLException {
setQueryBand(cx, "UPDATE");
}
void setQueryBand(Connection cx, String process) throws SQLException {
String sql = "SET QUERY_BAND=? FOR TRANSACTION";
StringBuffer qb = new StringBuffer();
for (Map.Entry<String, String> e : QueryBand.local().entrySet()) {
qb.append(e.getKey()).append("=").append(e.getValue()).append(";");
}
qb.append(QueryBand.PROCESS).append("=").append(process).append(";");
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(String.format("%s;1=%s", sql, qb.toString()));
}
PreparedStatement ps = cx.prepareStatement(sql);
try {
ps.setString(1, qb.toString());
ps.execute();
}
finally {
dataStore.closeSafe(ps);
}
}
private void encodeWhereStatement(StringBuffer buf,List<PrimaryKeyColumn> ids,String table) {
buf.append(" WHERE ");
for (int i = 0; i < ids.size(); i++) {
encodeColumnName(ids.get(i).getName(), buf);
buf.append('=');
buf.append(table).append('.');
encodeColumnName(ids.get(i).getName(), buf);
if (i + 1 < ids.size()) {
buf.append(" AND ");
}
}
buf.append(" ");
}
private void installInsertTrigger(Connection cx,String tableName, String geomName, String indexTableName, List<PrimaryKeyColumn> primaryKeys) throws SQLException {
String referencing = "REFERENCING NEW TABLE AS nt";
String triggerAction = "INSERT";
String triggerStmt = createTriggerInsert(indexTableName,geomName,primaryKeys);
installTrigger(cx,tableName,geomName,triggerAction,referencing,triggerStmt);
}
private void installUpdateTrigger(Connection cx,String tableName, String geomName, String indexTableName, List<PrimaryKeyColumn> primaryKeys) throws SQLException {
String referencing = "REFERENCING NEW TABLE AS nt";
String triggerAction = "UPDATE";
StringBuffer buf = new StringBuffer("DELETE FROM " + indexTableName);
encodeWhereStatement(buf, primaryKeys, "nt");
buf.append(';');
buf.append(createTriggerInsert(indexTableName, geomName, primaryKeys));
installTrigger(cx,tableName,geomName,triggerAction,referencing,buf.toString());
}
private void installDeleteTrigger(Connection cx, String tableName, String geomName,String indexTableName, List<PrimaryKeyColumn> primaryKeys) throws SQLException {
String referencing = "REFERENCING OLD TABLE AS ot";
String triggerAction = "DELETE";
StringBuffer buf = new StringBuffer("DELETE FROM " + indexTableName);
encodeWhereStatement(buf, primaryKeys, "ot");
buf.append(';');
installTrigger(cx,tableName,geomName,triggerAction,referencing,buf.toString());
}
private String createTriggerInsert(String indexTable,String geometryName,List<PrimaryKeyColumn> primaryKeys) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < primaryKeys.size(); i++) {
encodeColumnName(primaryKeys.get(i).getName(), buf);
if (i + 1 < primaryKeys.size()) {
buf.append(',');
}
}
String tinsert = "INSERT INTO {0} SELECT " + buf.toString() + ","
+ " sysspatial.tessellate_index("
+ " \"{1}\".ST_MBR().Xmin(), "
+ " \"{1}\".ST_MBR().Ymin(), "
+ " \"{1}\".ST_MBR().Xmax(), "
+ " \"{1}\".ST_MBR().Ymax(), "
+ " {2,number,0.0#}, {3,number,0.0#}, {4,number,0.0#}, {5,number,0.0#}, "
+ " {6,number,0}, {7,number,0}, {8,number,0}, {9,number,0.0#}, {10,number,0})"
+ " from nt WHERE {1} IS NOT NULL;";
int west = -180;
int south = -90;
int east = 180;
int north = 90;
int nx = 1000;
int ny = 1000;
int level = 3;
double scale = .01;
int shift = 0;
return MessageFormat.format(tinsert,
indexTable,geometryName,
west,south,east,north,
nx,ny,level,scale,shift
);
}
private void installTrigger(Connection cx, String tableName,String geomName, String triggerAction, String referencing, String triggerStmt) throws SQLException {
String triggerName = tableName + "_" + geomName + "_m" + triggerAction.substring(0, 1).toLowerCase();
String sql = "CREATE TRIGGER " + triggerName + " AFTER " + triggerAction + " ON " + tableName + "\n";
sql = sql + referencing + "\n";
sql = sql + "FOR EACH STATEMENT BEGIN ATOMIC (\n";
sql = sql + triggerStmt + "\n) END;";
Statement s = cx.createStatement();
LOGGER.fine("trigger SQL : " + sql);
try {
s.execute(sql);
} finally {
s.close();
}
}
void installTriggers(Connection cx, String tableName, String geomName, String indexTableName, List<PrimaryKeyColumn> primaryKeys) throws SQLException {
/*
"CREATE TRIGGER \"{0}_{1}_mi\" AFTER INSERT ON {12}"
+ " REFERENCING NEW TABLE AS nt"
+ " FOR EACH STATEMENT"
+ " BEGIN ATOMIC"
+ " ("
+ " INSERT INTO {13} SELECT \"{2}\","
+ " sysspatial.tessellate_index("
+ " \"{1}\".ST_Envelope().ST_ExteriorRing().ST_PointN(1).ST_X(), "
+ " \"{1}\".ST_Envelope().ST_ExteriorRing().ST_PointN(1).ST_Y(), "
+ " \"{1}\".ST_Envelope().ST_ExteriorRing().ST_PointN(3).ST_X(), "
+ " \"{1}\".ST_Envelope().ST_ExteriorRing().ST_PointN(3).ST_Y(), "
+ " {3,number,0.0#}, {4,number,0.0#}, {5,number,0.0#}, {6,number,0.0#}, "
+ " {7,number,0}, {8,number,0}, {9,number,0}, {10,number,0.0#}, {11,number,0})"
// + ")"
+ " FROM nt WHERE \"{1}\" IS NOT NULL;"
+ " ) " + "END"
*/
/*"CREATE TRIGGER \"{0}_{1}_mu\" AFTER UPDATE OF \"{1}\" ON {12}"
+ " REFERENCING NEW AS nt"
+ " FOR EACH STATEMENT"
+ " BEGIN ATOMIC"
+ " ("
+ " DELETE FROM {13} WHERE id in (SELECT \"{2}\" from nt); "
+ " INSERT INTO {13} SELECT \"{2}\","
+ " sysspatial.tessellate_index("
+ " \"{1}\".ST_Envelope().ST_ExteriorRing().ST_PointN(1).ST_X(), "
+ " \"{1}\".ST_Envelope().ST_ExteriorRing().ST_PointN(1).ST_Y(), "
+ " \"{1}\".ST_Envelope().ST_ExteriorRing().ST_PointN(3).ST_X(), "
+ " \"{1}\".ST_Envelope().ST_ExteriorRing().ST_PointN(3).ST_Y(), "
+ " {3,number,0.0#}, {4,number,0.0#}, {5,number,0.0#}, {6,number,0.0#}, "
+ " {7,number,0}, {8,number,0}, {9,number,0}, {10,number,0.0#}, {11,number,0})"
+ " FROM nt WHERE \"{1}\" IS NOT NULL;" + " ) "*/
/*"CREATE TRIGGER \"{0}_{1}_md\" AFTER DELETE ON {2}"
+ " REFERENCING OLD TABLE AS ot"
+ " FOR EACH STATEMENT"
+ " BEGIN ATOMIC"
+ " ("
+ " DELETE FROM \"{0}_{1}_idx\" WHERE ID IN (SELECT \"{1}\" from ot);"
+ " )" + "END"*/
installInsertTrigger(cx, tableName, geomName, indexTableName, primaryKeys);
installUpdateTrigger(cx, tableName, geomName, indexTableName, primaryKeys);
installDeleteTrigger(cx, tableName, geomName, indexTableName, primaryKeys);
LOGGER.info("Installed triggers on " + tableName + " to update " + indexTableName);
}
}