Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.phoenix.jdbc; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.RowIdLifetime; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import org.apache.commons.lang.StringEscapeUtils; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.compile.ColumnProjector; import org.apache.phoenix.compile.ExpressionProjector; import org.apache.phoenix.compile.RowProjector; import org.apache.phoenix.coprocessor.MetaDataProtocol; import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.exception.SQLExceptionInfo; import org.apache.phoenix.expression.RowKeyColumnExpression; import org.apache.phoenix.expression.function.ExternalSqlTypeIdFunction; import org.apache.phoenix.expression.function.IndexStateNameFunction; import org.apache.phoenix.expression.function.SQLIndexTypeFunction; import org.apache.phoenix.expression.function.SQLTableTypeFunction; import org.apache.phoenix.expression.function.SQLViewTypeFunction; import org.apache.phoenix.expression.function.SqlTypeNameFunction; import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr; import org.apache.phoenix.hbase.index.util.VersionUtil; import org.apache.phoenix.iterate.DelegateResultIterator; import org.apache.phoenix.iterate.MaterializedResultIterator; import org.apache.phoenix.iterate.ResultIterator; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.schema.types.PDataType; import org.apache.phoenix.schema.PDatum; import org.apache.phoenix.schema.PName; import org.apache.phoenix.schema.PTable.LinkType; import org.apache.phoenix.schema.PTableType; import org.apache.phoenix.schema.types.PVarchar; import org.apache.phoenix.schema.RowKeyValueAccessor; import org.apache.phoenix.schema.SortOrder; import org.apache.phoenix.schema.tuple.ResultTuple; import org.apache.phoenix.schema.tuple.SingleKeyValueTuple; import org.apache.phoenix.schema.tuple.Tuple; import org.apache.phoenix.util.ByteUtil; import org.apache.phoenix.util.KeyValueUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.StringUtil; import com.google.common.collect.Lists; /** * * JDBC DatabaseMetaData implementation of Phoenix reflecting read-only nature of driver. * Supported metadata methods include: * {@link #getTables(String, String, String, String[])} * {@link #getColumns(String, String, String, String)} * {@link #getTableTypes()} * {@link #getPrimaryKeys(String, String, String)} * {@link #getIndexInfo(String, String, String, boolean, boolean)} * {@link #getSchemas()} * {@link #getSchemas(String, String)} * {@link #getDatabaseMajorVersion()} * {@link #getDatabaseMinorVersion()} * {@link #getClientInfoProperties()} * {@link #getConnection()} * {@link #getDatabaseProductName()} * {@link #getDatabaseProductVersion()} * {@link #getDefaultTransactionIsolation()} * {@link #getDriverName()} * {@link #getDriverVersion()} * {@link #getSuperTables(String, String, String)} * {@link #getCatalogs()} * Other ResultSet methods return an empty result set. * * * @since 0.1 */ public class PhoenixDatabaseMetaData implements DatabaseMetaData, org.apache.phoenix.jdbc.Jdbc7Shim.DatabaseMetaData { public static final int INDEX_NAME_INDEX = 4; // Shared with FAMILY_NAME_INDEX public static final int FAMILY_NAME_INDEX = 4; public static final int COLUMN_NAME_INDEX = 3; public static final int TABLE_NAME_INDEX = 2; public static final int SCHEMA_NAME_INDEX = 1; public static final int TENANT_ID_INDEX = 0; public static final String SYSTEM_CATALOG_SCHEMA = QueryConstants.SYSTEM_SCHEMA_NAME; public static final byte[] SYSTEM_CATALOG_SCHEMA_BYTES = QueryConstants.SYSTEM_SCHEMA_NAME_BYTES; public static final String SYSTEM_CATALOG_TABLE = "CATALOG"; public static final byte[] SYSTEM_CATALOG_TABLE_BYTES = Bytes.toBytes(SYSTEM_CATALOG_TABLE); public static final String SYSTEM_CATALOG = SYSTEM_CATALOG_SCHEMA + ".\"" + SYSTEM_CATALOG_TABLE + "\""; public static final String SYSTEM_CATALOG_NAME = SchemaUtil.getTableName(SYSTEM_CATALOG_SCHEMA, SYSTEM_CATALOG_TABLE); public static final byte[] SYSTEM_CATALOG_NAME_BYTES = Bytes.toBytes(SYSTEM_CATALOG_NAME); public static final String SYSTEM_STATS_TABLE = "STATS"; public static final String SYSTEM_STATS_NAME = SchemaUtil.getTableName(SYSTEM_CATALOG_SCHEMA, SYSTEM_STATS_TABLE); public static final byte[] SYSTEM_STATS_NAME_BYTES = Bytes.toBytes(SYSTEM_STATS_NAME); public static final String SYSTEM_CATALOG_ALIAS = "\"SYSTEM.TABLE\""; public static final String TABLE_NAME = "TABLE_NAME"; public static final byte[] TABLE_NAME_BYTES = Bytes.toBytes(TABLE_NAME); public static final String TABLE_TYPE = "TABLE_TYPE"; public static final byte[] TABLE_TYPE_BYTES = Bytes.toBytes(TABLE_TYPE); public static final String PHYSICAL_NAME = "PHYSICAL_NAME"; public static final byte[] PHYSICAL_NAME_BYTES = Bytes.toBytes(PHYSICAL_NAME); public static final String COLUMN_FAMILY = "COLUMN_FAMILY"; public static final String TABLE_CAT = "TABLE_CAT"; public static final String TABLE_CATALOG = "TABLE_CATALOG"; public static final String TABLE_SCHEM = "TABLE_SCHEM"; public static final String REMARKS = "REMARKS"; public static final String TYPE_SCHEM = "TYPE_SCHEM"; public static final String SELF_REFERENCING_COL_NAME = "SELF_REFERENCING_COL_NAME"; public static final String REF_GENERATION = "REF_GENERATION"; public static final String PK_NAME = "PK_NAME"; public static final byte[] PK_NAME_BYTES = Bytes.toBytes(PK_NAME); public static final String TABLE_SEQ_NUM = "TABLE_SEQ_NUM"; public static final byte[] TABLE_SEQ_NUM_BYTES = Bytes.toBytes(TABLE_SEQ_NUM); public static final String COLUMN_COUNT = "COLUMN_COUNT"; public static final byte[] COLUMN_COUNT_BYTES = Bytes.toBytes(COLUMN_COUNT); public static final String SALT_BUCKETS = "SALT_BUCKETS"; public static final byte[] SALT_BUCKETS_BYTES = Bytes.toBytes(SALT_BUCKETS); public static final String STORE_NULLS = "STORE_NULLS"; public static final byte[] STORE_NULLS_BYTES = Bytes.toBytes(STORE_NULLS); public static final String DATA_TABLE_NAME = "DATA_TABLE_NAME"; public static final byte[] DATA_TABLE_NAME_BYTES = Bytes.toBytes(DATA_TABLE_NAME); public static final String INDEX_STATE = "INDEX_STATE"; public static final byte[] INDEX_STATE_BYTES = Bytes.toBytes(INDEX_STATE); public static final String TENANT_ID = "TENANT_ID"; public static final byte[] TENANT_ID_BYTES = Bytes.toBytes(TENANT_ID); public static final String COLUMN_NAME = "COLUMN_NAME"; public static final String DATA_TYPE = "DATA_TYPE"; public static final byte[] DATA_TYPE_BYTES = Bytes.toBytes(DATA_TYPE); public static final String TYPE_NAME = "TYPE_NAME"; public static final String COLUMN_SIZE = "COLUMN_SIZE"; public static final byte[] COLUMN_SIZE_BYTES = Bytes.toBytes(COLUMN_SIZE); public static final String BUFFER_LENGTH = "BUFFER_LENGTH"; public static final String DECIMAL_DIGITS = "DECIMAL_DIGITS"; public static final byte[] DECIMAL_DIGITS_BYTES = Bytes.toBytes(DECIMAL_DIGITS); public static final String NUM_PREC_RADIX = "NUM_PREC_RADIX"; public static final String NULLABLE = "NULLABLE"; public static final byte[] NULLABLE_BYTES = Bytes.toBytes(NULLABLE); public static final String COLUMN_DEF = "COLUMN_DEF"; public static final String SQL_DATA_TYPE = "SQL_DATA_TYPE"; public static final String SQL_DATETIME_SUB = "SQL_DATETIME_SUB"; public static final String CHAR_OCTET_LENGTH = "CHAR_OCTET_LENGTH"; public static final String ORDINAL_POSITION = "ORDINAL_POSITION"; public static final byte[] ORDINAL_POSITION_BYTES = Bytes.toBytes(ORDINAL_POSITION); public static final String IS_NULLABLE = "IS_NULLABLE"; public static final String SCOPE_CATALOG = "SCOPE_CATALOG"; public static final String SCOPE_SCHEMA = "SCOPE_SCHEMA"; public static final String SCOPE_TABLE = "SCOPE_TABLE"; public static final String SOURCE_DATA_TYPE = "SOURCE_DATA_TYPE"; public static final String IS_AUTOINCREMENT = "IS_AUTOINCREMENT"; public static final String SORT_ORDER = "SORT_ORDER"; public static final byte[] SORT_ORDER_BYTES = Bytes.toBytes(SORT_ORDER); public static final String IMMUTABLE_ROWS = "IMMUTABLE_ROWS"; public static final byte[] IMMUTABLE_ROWS_BYTES = Bytes.toBytes(IMMUTABLE_ROWS); public static final String DEFAULT_COLUMN_FAMILY_NAME = "DEFAULT_COLUMN_FAMILY"; public static final byte[] DEFAULT_COLUMN_FAMILY_NAME_BYTES = Bytes.toBytes(DEFAULT_COLUMN_FAMILY_NAME); public static final String VIEW_STATEMENT = "VIEW_STATEMENT"; public static final byte[] VIEW_STATEMENT_BYTES = Bytes.toBytes(VIEW_STATEMENT); public static final String DISABLE_WAL = "DISABLE_WAL"; public static final byte[] DISABLE_WAL_BYTES = Bytes.toBytes(DISABLE_WAL); public static final String MULTI_TENANT = "MULTI_TENANT"; public static final byte[] MULTI_TENANT_BYTES = Bytes.toBytes(MULTI_TENANT); public static final String VIEW_TYPE = "VIEW_TYPE"; public static final byte[] VIEW_TYPE_BYTES = Bytes.toBytes(VIEW_TYPE); public static final String INDEX_TYPE = "INDEX_TYPE"; public static final byte[] INDEX_TYPE_BYTES = Bytes.toBytes(INDEX_TYPE); public static final String LINK_TYPE = "LINK_TYPE"; public static final byte[] LINK_TYPE_BYTES = Bytes.toBytes(LINK_TYPE); public static final String ARRAY_SIZE = "ARRAY_SIZE"; public static final byte[] ARRAY_SIZE_BYTES = Bytes.toBytes(ARRAY_SIZE); public static final String VIEW_CONSTANT = "VIEW_CONSTANT"; public static final byte[] VIEW_CONSTANT_BYTES = Bytes.toBytes(VIEW_CONSTANT); public static final String IS_VIEW_REFERENCED = "IS_VIEW_REFERENCED"; public static final byte[] IS_VIEW_REFERENCED_BYTES = Bytes.toBytes(IS_VIEW_REFERENCED); public static final String VIEW_INDEX_ID = "VIEW_INDEX_ID"; public static final byte[] VIEW_INDEX_ID_BYTES = Bytes.toBytes(VIEW_INDEX_ID); public static final String TABLE_FAMILY = QueryConstants.DEFAULT_COLUMN_FAMILY; public static final byte[] TABLE_FAMILY_BYTES = QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES; public static final String TYPE_SEQUENCE = "SEQUENCE"; public static final byte[] SEQUENCE_FAMILY_BYTES = QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES; public static final String SEQUENCE_SCHEMA_NAME = SYSTEM_CATALOG_SCHEMA; public static final byte[] SEQUENCE_SCHEMA_NAME_BYTES = Bytes.toBytes(SEQUENCE_SCHEMA_NAME); public static final String SEQUENCE_TABLE_NAME = TYPE_SEQUENCE; public static final byte[] SEQUENCE_TABLE_NAME_BYTES = Bytes.toBytes(SEQUENCE_TABLE_NAME); public static final String SEQUENCE_FULLNAME_ESCAPED = SYSTEM_CATALOG_SCHEMA + ".\"" + TYPE_SEQUENCE + "\""; public static final String SEQUENCE_FULLNAME = SchemaUtil.getTableName(SEQUENCE_SCHEMA_NAME, SEQUENCE_TABLE_NAME); public static final byte[] SEQUENCE_FULLNAME_BYTES = Bytes.toBytes(SEQUENCE_FULLNAME); public static final String SEQUENCE_SCHEMA = "SEQUENCE_SCHEMA"; public static final String SEQUENCE_NAME = "SEQUENCE_NAME"; public static final String CURRENT_VALUE = "CURRENT_VALUE"; public static final byte[] CURRENT_VALUE_BYTES = Bytes.toBytes(CURRENT_VALUE); public static final String START_WITH = "START_WITH"; public static final byte[] START_WITH_BYTES = Bytes.toBytes(START_WITH); // MIN_VALUE, MAX_VALUE, CYCLE_FLAG and LIMIT_FLAG were added in 3.1/4.1 public static final String MIN_VALUE = "MIN_VALUE"; public static final byte[] MIN_VALUE_BYTES = Bytes.toBytes(MIN_VALUE); public static final String MAX_VALUE = "MAX_VALUE"; public static final byte[] MAX_VALUE_BYTES = Bytes.toBytes(MAX_VALUE); public static final String INCREMENT_BY = "INCREMENT_BY"; public static final byte[] INCREMENT_BY_BYTES = Bytes.toBytes(INCREMENT_BY); public static final String CACHE_SIZE = "CACHE_SIZE"; public static final byte[] CACHE_SIZE_BYTES = Bytes.toBytes(CACHE_SIZE); public static final String CYCLE_FLAG = "CYCLE_FLAG"; public static final byte[] CYCLE_FLAG_BYTES = Bytes.toBytes(CYCLE_FLAG); public static final String LIMIT_REACHED_FLAG = "LIMIT_REACHED_FLAG"; public static final byte[] LIMIT_REACHED_FLAG_BYTES = Bytes.toBytes(LIMIT_REACHED_FLAG); public static final String KEY_SEQ = "KEY_SEQ"; public static final byte[] KEY_SEQ_BYTES = Bytes.toBytes(KEY_SEQ); public static final String SUPERTABLE_NAME = "SUPERTABLE_NAME"; public static final String TYPE_ID = "TYPE_ID"; public static final String INDEX_DISABLE_TIMESTAMP = "INDEX_DISABLE_TIMESTAMP"; public static final byte[] INDEX_DISABLE_TIMESTAMP_BYTES = Bytes.toBytes(INDEX_DISABLE_TIMESTAMP); public static final String REGION_NAME = "REGION_NAME"; public static final byte[] REGION_NAME_BYTES = Bytes.toBytes(REGION_NAME); public static final String GUIDE_POSTS = "GUIDE_POSTS"; public static final byte[] GUIDE_POSTS_BYTES = Bytes.toBytes(GUIDE_POSTS); public static final String GUIDE_POSTS_COUNT = "GUIDE_POSTS_COUNT"; public static final byte[] GUIDE_POSTS_COUNT_BYTES = Bytes.toBytes(GUIDE_POSTS_COUNT); public static final String GUIDE_POSTS_WIDTH = "GUIDE_POSTS_WIDTH"; public static final byte[] GUIDE_POSTS_WIDTH_BYTES = Bytes.toBytes(GUIDE_POSTS_WIDTH); public static final String GUIDE_POSTS_ROW_COUNT = "GUIDE_POSTS_ROW_COUNT"; public static final byte[] GUIDE_POSTS_ROW_COUNT_BYTES = Bytes.toBytes(GUIDE_POSTS_ROW_COUNT); public static final String MIN_KEY = "MIN_KEY"; public static final byte[] MIN_KEY_BYTES = Bytes.toBytes(MIN_KEY); public static final String MAX_KEY = "MAX_KEY"; public static final byte[] MAX_KEY_BYTES = Bytes.toBytes(MAX_KEY); public static final String LAST_STATS_UPDATE_TIME = "LAST_STATS_UPDATE_TIME"; public static final byte[] LAST_STATS_UPDATE_TIME_BYTES = Bytes.toBytes(LAST_STATS_UPDATE_TIME); public static final String PARENT_TENANT_ID = "PARENT_TENANT_ID"; public static final byte[] PARENT_TENANT_ID_BYTES = Bytes.toBytes(PARENT_TENANT_ID); private static final String TENANT_POS_SHIFT = "TENANT_POS_SHIFT"; private static final byte[] TENANT_POS_SHIFT_BYTES = Bytes.toBytes(TENANT_POS_SHIFT); private final PhoenixConnection connection; private final ResultSet emptyResultSet; // Version below which we should turn off essential column family. public static final int ESSENTIAL_FAMILY_VERSION_THRESHOLD = VersionUtil.encodeVersion("0", "94", "7"); // Version below which we should disallow usage of mutable secondary indexing. public static final int MUTABLE_SI_VERSION_THRESHOLD = VersionUtil.encodeVersion("0", "94", "10"); /** Version below which we fall back on the generic KeyValueBuilder */ public static final int CLIENT_KEY_VALUE_BUILDER_THRESHOLD = VersionUtil.encodeVersion("0", "94", "14"); PhoenixDatabaseMetaData(PhoenixConnection connection) throws SQLException { this.emptyResultSet = new PhoenixResultSet(ResultIterator.EMPTY_ITERATOR, RowProjector.EMPTY_PROJECTOR, new PhoenixStatement(connection)); this.connection = connection; } @Override public boolean allProceduresAreCallable() throws SQLException { return false; } @Override public boolean allTablesAreSelectable() throws SQLException { return true; } @Override public boolean autoCommitFailureClosesAllResultSets() throws SQLException { return false; } @Override public boolean dataDefinitionCausesTransactionCommit() throws SQLException { return false; } @Override public boolean dataDefinitionIgnoredInTransactions() throws SQLException { return false; } @Override public boolean deletesAreDetected(int type) throws SQLException { return false; } @Override public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { return false; } @Override public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException { return emptyResultSet; } @Override public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException { return emptyResultSet; } @Override public String getCatalogSeparator() throws SQLException { return "."; } @Override public String getCatalogTerm() throws SQLException { return "Tenant"; } @Override public ResultSet getCatalogs() throws SQLException { StringBuilder buf = new StringBuilder("select \n" + " DISTINCT " + TENANT_ID + " " + TABLE_CAT + " from " + SYSTEM_CATALOG + " " + SYSTEM_CATALOG_ALIAS + " where " + COLUMN_NAME + " is null" + " and " + COLUMN_FAMILY + " is null" + " and " + TENANT_ID + " is not null"); addTenantIdFilter(buf, null); buf.append(" order by " + TENANT_ID); Statement stmt = connection.createStatement(); return stmt.executeQuery(buf.toString()); } @Override public ResultSet getClientInfoProperties() throws SQLException { return emptyResultSet; } @Override public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException { return emptyResultSet; } private static String escapePattern(String pattern) { return StringEscapeUtils.escapeSql(pattern); // Need to escape double quotes } public static final String GLOBAL_TENANANTS_ONLY = "null"; private void addTenantIdFilter(StringBuilder buf, String tenantIdPattern) { PName tenantId = connection.getTenantId(); if (tenantIdPattern == null) { if (tenantId != null) { appendConjunction(buf); buf.append(" (" + TENANT_ID + " IS NULL " + " OR " + TENANT_ID + " = '" + escapePattern(tenantId.getString()) + "') "); } } else if (tenantIdPattern.length() == 0) { appendConjunction(buf); buf.append(TENANT_ID + " IS NULL "); } else { appendConjunction(buf); buf.append(" TENANT_ID LIKE '" + escapePattern(tenantIdPattern) + "' "); if (tenantId != null) { buf.append(" and TENANT_ID + = '" + escapePattern(tenantId.getString()) + "' "); } } } private static void appendConjunction(StringBuilder buf) { buf.append(buf.length() == 0 ? "" : " and "); } @Override public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { StringBuilder buf = new StringBuilder("select \n " + TENANT_ID + " " + TABLE_CAT + "," + // use this for tenant id TABLE_SCHEM + "," + TABLE_NAME + " ," + COLUMN_NAME + "," + ExternalSqlTypeIdFunction.NAME + "(" + DATA_TYPE + ") AS " + DATA_TYPE + "," + SqlTypeNameFunction.NAME + "(" + DATA_TYPE + ") AS " + TYPE_NAME + "," + COLUMN_SIZE + "," + BUFFER_LENGTH + "," + DECIMAL_DIGITS + "," + NUM_PREC_RADIX + "," + NULLABLE + "," + COLUMN_DEF + "," + SQL_DATA_TYPE + "," + SQL_DATETIME_SUB + "," + CHAR_OCTET_LENGTH + "," + "CASE WHEN " + TENANT_POS_SHIFT + " THEN " + ORDINAL_POSITION + "-1 ELSE " + ORDINAL_POSITION + " END AS " + ORDINAL_POSITION + "," + "CASE " + NULLABLE + " WHEN " + DatabaseMetaData.attributeNoNulls + " THEN '" + Boolean.FALSE.toString() + "' WHEN " + DatabaseMetaData.attributeNullable + " THEN '" + Boolean.TRUE.toString() + "' END AS " + IS_NULLABLE + "," + SCOPE_CATALOG + "," + SCOPE_SCHEMA + "," + SCOPE_TABLE + "," + SOURCE_DATA_TYPE + "," + IS_AUTOINCREMENT + "," + ARRAY_SIZE + "," + COLUMN_FAMILY + "," + DATA_TYPE + " " + TYPE_ID + "," + // raw type id for potential internal consumption VIEW_CONSTANT + "," + MULTI_TENANT + "," + "CASE WHEN " + TENANT_POS_SHIFT + " THEN " + KEY_SEQ + "-1 ELSE " + KEY_SEQ + " END AS " + KEY_SEQ + " from " + SYSTEM_CATALOG + " " + SYSTEM_CATALOG_ALIAS + "(" + TENANT_POS_SHIFT + " BOOLEAN)"); StringBuilder where = new StringBuilder(); addTenantIdFilter(where, catalog); if (schemaPattern != null) { appendConjunction(where); where.append(TABLE_SCHEM + (schemaPattern.length() == 0 ? " is null" : " like '" + escapePattern(schemaPattern) + "'")); } if (tableNamePattern != null && tableNamePattern.length() > 0) { appendConjunction(where); where.append(TABLE_NAME + " like '" + escapePattern(tableNamePattern) + "'"); } // Allow a "." in columnNamePattern for column family match String colPattern = null; if (columnNamePattern != null && columnNamePattern.length() > 0) { String cfPattern = null; int index = columnNamePattern.indexOf('.'); if (index <= 0) { colPattern = columnNamePattern; } else { cfPattern = columnNamePattern.substring(0, index); if (columnNamePattern.length() > index + 1) { colPattern = columnNamePattern.substring(index + 1); } } if (cfPattern != null && cfPattern.length() > 0) { // if null or empty, will pick up all columns // Will pick up only KV columns appendConjunction(where); where.append(COLUMN_FAMILY + " like '" + escapePattern(cfPattern) + "'"); } if (colPattern != null && colPattern.length() > 0) { appendConjunction(where); where.append(COLUMN_NAME + " like '" + escapePattern(colPattern) + "'"); } } if (colPattern == null) { appendConjunction(where); where.append(COLUMN_NAME + " is not null"); } boolean isTenantSpecificConnection = connection.getTenantId() != null; if (isTenantSpecificConnection) { buf.append(" where (" + where + ") OR (" + COLUMN_FAMILY + " is null AND " + COLUMN_NAME + " is null)"); } else { buf.append(" where " + where); } buf.append(" order by " + TENANT_ID + "," + TABLE_SCHEM + "," + TABLE_NAME + "," + SYSTEM_CATALOG_ALIAS + "." + ORDINAL_POSITION); Statement stmt; if (isTenantSpecificConnection) { stmt = connection.createStatement(new PhoenixStatementFactory() { @Override public PhoenixStatement newStatement(PhoenixConnection connection) { return new PhoenixStatement(connection) { @Override protected PhoenixResultSet newResultSet(ResultIterator iterator, RowProjector projector) throws SQLException { return new PhoenixResultSet(new TenantColumnFilteringIterator(iterator, projector), projector, this); } }; } }); } else { stmt = connection.createStatement(); } return stmt.executeQuery(buf.toString()); } /** * Filters the tenant id column out of a column metadata result set (thus, where each row is a column definition). * The tenant id is by definition the first column of the primary key, but the primary key does not necessarily * start at the first column. Assumes columns are sorted on ordinal position. */ private static class TenantColumnFilteringIterator extends DelegateResultIterator { private final RowProjector rowProjector; private final int columnFamilyIndex; private final int columnNameIndex; private final int multiTenantIndex; private final int keySeqIndex; private boolean inMultiTenantTable; private boolean tenantColumnSkipped; private TenantColumnFilteringIterator(ResultIterator delegate, RowProjector rowProjector) throws SQLException { super(delegate); this.rowProjector = rowProjector; this.columnFamilyIndex = rowProjector.getColumnIndex(COLUMN_FAMILY); this.columnNameIndex = rowProjector.getColumnIndex(COLUMN_NAME); this.multiTenantIndex = rowProjector.getColumnIndex(MULTI_TENANT); this.keySeqIndex = rowProjector.getColumnIndex(KEY_SEQ); } @Override public Tuple next() throws SQLException { Tuple tuple = super.next(); while (tuple != null && getColumn(tuple, columnFamilyIndex) == null && getColumn(tuple, columnNameIndex) == null) { // new table, check if it is multitenant inMultiTenantTable = getColumn(tuple, multiTenantIndex) == Boolean.TRUE; tenantColumnSkipped = false; // skip row representing table tuple = super.next(); } if (tuple != null && inMultiTenantTable && !tenantColumnSkipped) { Object value = getColumn(tuple, keySeqIndex); if (value != null && ((Number) value).longValue() == 1L) { tenantColumnSkipped = true; // skip tenant id primary key column return next(); } } if (tuple != null && tenantColumnSkipped) { ResultTuple resultTuple = (ResultTuple) tuple; List<Cell> cells = resultTuple.getResult().listCells(); KeyValue kv = new KeyValue(resultTuple.getResult().getRow(), TABLE_FAMILY_BYTES, TENANT_POS_SHIFT_BYTES, PDataType.TRUE_BYTES); List<Cell> newCells = Lists.newArrayListWithCapacity(cells.size() + 1); newCells.addAll(cells); newCells.add(kv); Collections.sort(newCells, KeyValue.COMPARATOR); resultTuple.setResult(Result.create(newCells)); } return tuple; } private Object getColumn(Tuple tuple, int index) throws SQLException { ColumnProjector projector = this.rowProjector.getColumnProjector(index); PDataType type = projector.getExpression().getDataType(); return projector.getValue(tuple, type, new ImmutableBytesPtr()); } } @Override public Connection getConnection() throws SQLException { return connection; } @Override public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException { return emptyResultSet; } @Override public int getDatabaseMajorVersion() throws SQLException { return MetaDataProtocol.PHOENIX_MAJOR_VERSION; } @Override public int getDatabaseMinorVersion() throws SQLException { return MetaDataProtocol.PHOENIX_MINOR_VERSION; } @Override public String getDatabaseProductName() throws SQLException { return "Phoenix"; } @Override public String getDatabaseProductVersion() throws SQLException { return Integer.toString(getDatabaseMajorVersion()) + "." + Integer.toString(getDatabaseMinorVersion()); } @Override public int getDefaultTransactionIsolation() throws SQLException { return connection.getTransactionIsolation(); } @Override public int getDriverMajorVersion() { return Integer.parseInt(connection.getClientInfo(PhoenixEmbeddedDriver.MAJOR_VERSION_PROP)); } @Override public int getDriverMinorVersion() { return Integer.parseInt(connection.getClientInfo(PhoenixEmbeddedDriver.MINOR_VERSION_PROP)); } @Override public String getDriverName() throws SQLException { return connection.getClientInfo(PhoenixEmbeddedDriver.DRIVER_NAME_PROP); } @Override public String getDriverVersion() throws SQLException { return connection.getClientInfo(PhoenixEmbeddedDriver.MAJOR_VERSION_PROP) + "." + connection.getClientInfo(PhoenixEmbeddedDriver.MINOR_VERSION_PROP); } @Override public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { return emptyResultSet; } @Override public String getExtraNameCharacters() throws SQLException { return ""; } @Override public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException { return emptyResultSet; } @Override public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException { return emptyResultSet; } @Override public String getIdentifierQuoteString() throws SQLException { return SchemaUtil.ESCAPE_CHARACTER; } @Override public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { return emptyResultSet; } @Override public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException { if (unique) { // No unique indexes return emptyResultSet; } StringBuilder buf = new StringBuilder("select \n" + TENANT_ID + " " + TABLE_CAT + ",\n" + // use this column for column family name TABLE_SCHEM + ",\n" + DATA_TABLE_NAME + " " + TABLE_NAME + ",\n" + "true NON_UNIQUE,\n" + "null INDEX_QUALIFIER,\n" + TABLE_NAME + " INDEX_NAME,\n" + DatabaseMetaData.tableIndexOther + " TYPE,\n" + ORDINAL_POSITION + ",\n" + COLUMN_NAME + ",\n" + "CASE WHEN " + COLUMN_FAMILY + " IS NOT NULL THEN null WHEN " + SORT_ORDER + " = " + (SortOrder.DESC.getSystemValue()) + " THEN 'D' ELSE 'A' END ASC_OR_DESC,\n" + "null CARDINALITY,\n" + "null PAGES,\n" + "null FILTER_CONDITION,\n" + // Include data type info, though not in spec ExternalSqlTypeIdFunction.NAME + "(" + DATA_TYPE + ") AS " + DATA_TYPE + ",\n" + SqlTypeNameFunction.NAME + "(" + DATA_TYPE + ") AS " + TYPE_NAME + ",\n" + DATA_TYPE + " " + TYPE_ID + ",\n" + COLUMN_FAMILY + ",\n" + COLUMN_SIZE + ",\n" + ARRAY_SIZE + "\nfrom " + SYSTEM_CATALOG + "\nwhere "); buf.append(TABLE_SCHEM + (schema == null || schema.length() == 0 ? " is null" : " = '" + escapePattern(schema) + "'")); buf.append("\nand " + DATA_TABLE_NAME + " = '" + escapePattern(table) + "'"); buf.append("\nand " + COLUMN_NAME + " is not null"); addTenantIdFilter(buf, catalog); buf.append("\norder by INDEX_NAME," + ORDINAL_POSITION); Statement stmt = connection.createStatement(); return stmt.executeQuery(buf.toString()); } @Override public int getJDBCMajorVersion() throws SQLException { return 1; } @Override public int getJDBCMinorVersion() throws SQLException { return 0; } @Override public int getMaxBinaryLiteralLength() throws SQLException { return 0; } @Override public int getMaxCatalogNameLength() throws SQLException { return 0; } @Override public int getMaxCharLiteralLength() throws SQLException { return 4000; } @Override public int getMaxColumnNameLength() throws SQLException { return 200; } @Override public int getMaxColumnsInGroupBy() throws SQLException { return 1; } @Override public int getMaxColumnsInIndex() throws SQLException { return 0; } @Override public int getMaxColumnsInOrderBy() throws SQLException { return 0; } @Override public int getMaxColumnsInSelect() throws SQLException { return 0; } @Override public int getMaxColumnsInTable() throws SQLException { return 0; } @Override public int getMaxConnections() throws SQLException { return 0; } @Override public int getMaxCursorNameLength() throws SQLException { return 0; } @Override public int getMaxIndexLength() throws SQLException { return 0; } @Override public int getMaxProcedureNameLength() throws SQLException { return 0; } @Override public int getMaxRowSize() throws SQLException { return 0; } @Override public int getMaxSchemaNameLength() throws SQLException { return 0; } @Override public int getMaxStatementLength() throws SQLException { return 0; } @Override public int getMaxStatements() throws SQLException { return 0; } @Override public int getMaxTableNameLength() throws SQLException { return 0; } @Override public int getMaxTablesInSelect() throws SQLException { return 1; } @Override public int getMaxUserNameLength() throws SQLException { return 0; } @Override public String getNumericFunctions() throws SQLException { return ""; } @Override public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { if (table == null || table.length() == 0) { return emptyResultSet; } StringBuilder buf = new StringBuilder("select \n" + TENANT_ID + " " + TABLE_CAT + "," + // use catalog for tenant_id TABLE_SCHEM + "," + TABLE_NAME + " ," + COLUMN_NAME + "," + KEY_SEQ + "," + PK_NAME + "," + "CASE WHEN " + SORT_ORDER + " = " + (SortOrder.DESC.getSystemValue()) + " THEN 'D' ELSE 'A' END ASC_OR_DESC," + ExternalSqlTypeIdFunction.NAME + "(" + DATA_TYPE + ") AS " + DATA_TYPE + "," + SqlTypeNameFunction.NAME + "(" + DATA_TYPE + ") AS " + TYPE_NAME + "," + COLUMN_SIZE + "," + DATA_TYPE + " " + TYPE_ID + "," + // raw type id VIEW_CONSTANT + " from " + SYSTEM_CATALOG + " " + SYSTEM_CATALOG_ALIAS + " where "); buf.append(TABLE_SCHEM + (schema == null || schema.length() == 0 ? " is null" : " = '" + escapePattern(schema) + "'")); buf.append(" and " + TABLE_NAME + " = '" + escapePattern(table) + "'"); buf.append(" and " + COLUMN_NAME + " is not null"); buf.append(" and " + COLUMN_FAMILY + " is null"); addTenantIdFilter(buf, catalog); buf.append(" order by " + TENANT_ID + "," + TABLE_SCHEM + "," + TABLE_NAME + " ," + COLUMN_NAME); ResultSet rs = connection.createStatement().executeQuery(buf.toString()); return rs; } @Override public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException { return emptyResultSet; } @Override public String getProcedureTerm() throws SQLException { return "procedure"; } @Override public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException { return emptyResultSet; } @Override public int getResultSetHoldability() throws SQLException { return connection.getHoldability(); } @Override public RowIdLifetime getRowIdLifetime() throws SQLException { return RowIdLifetime.ROWID_UNSUPPORTED; } @Override public String getSQLKeywords() throws SQLException { return ""; } @Override public int getSQLStateType() throws SQLException { return DatabaseMetaData.sqlStateSQL99; } @Override public String getSchemaTerm() throws SQLException { return "schema"; } @Override public ResultSet getSchemas() throws SQLException { return getSchemas(null, null); } @Override public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { StringBuilder buf = new StringBuilder( "select distinct \n" + TABLE_SCHEM + "," + TENANT_ID + " " + TABLE_CATALOG + " from " + SYSTEM_CATALOG + " " + SYSTEM_CATALOG_ALIAS + " where " + COLUMN_NAME + " is null"); this.addTenantIdFilter(buf, catalog); if (schemaPattern != null) { buf.append(" and " + TABLE_SCHEM + " like '" + escapePattern(schemaPattern) + "'"); } Statement stmt = connection.createStatement(); return stmt.executeQuery(buf.toString()); } @Override public String getSearchStringEscape() throws SQLException { return "\\"; } @Override public String getStringFunctions() throws SQLException { return ""; } @Override public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { StringBuilder buf = new StringBuilder("select \n" + TENANT_ID + " " + TABLE_CAT + "," + // Use tenantId for catalog TABLE_SCHEM + "," + TABLE_NAME + "," + COLUMN_FAMILY + " " + SUPERTABLE_NAME + " from " + SYSTEM_CATALOG + " " + SYSTEM_CATALOG_ALIAS + " where " + COLUMN_NAME + " is null" + " and " + LINK_TYPE + " = " + LinkType.PHYSICAL_TABLE.getSerializedValue()); addTenantIdFilter(buf, catalog); if (schemaPattern != null) { buf.append(" and " + TABLE_SCHEM + (schemaPattern.length() == 0 ? " is null" : " like '" + escapePattern(schemaPattern) + "'")); } if (tableNamePattern != null) { buf.append(" and " + TABLE_NAME + " like '" + escapePattern(tableNamePattern) + "'"); } buf.append(" order by " + TENANT_ID + "," + TABLE_SCHEM + "," + TABLE_NAME + "," + SUPERTABLE_NAME); Statement stmt = connection.createStatement(); return stmt.executeQuery(buf.toString()); } @Override public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { return emptyResultSet; } @Override public String getSystemFunctions() throws SQLException { return ""; } @Override public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { return emptyResultSet; } private static final PDatum TABLE_TYPE_DATUM = new PDatum() { @Override public boolean isNullable() { return false; } @Override public PDataType getDataType() { return PVarchar.INSTANCE; } @Override public Integer getMaxLength() { return null; } @Override public Integer getScale() { return null; } @Override public SortOrder getSortOrder() { return SortOrder.getDefault(); } }; private static final RowProjector TABLE_TYPE_ROW_PROJECTOR = new RowProjector( Arrays.<ColumnProjector>asList(new ExpressionProjector(TABLE_TYPE, SYSTEM_CATALOG, new RowKeyColumnExpression(TABLE_TYPE_DATUM, new RowKeyValueAccessor(Collections.<PDatum>singletonList(TABLE_TYPE_DATUM), 0)), false)), 0, true); private static final Collection<Tuple> TABLE_TYPE_TUPLES = Lists .newArrayListWithExpectedSize(PTableType.values().length); static { for (PTableType tableType : PTableType.values()) { TABLE_TYPE_TUPLES.add(new SingleKeyValueTuple( KeyValueUtil.newKeyValue(tableType.getValue().getBytes(), TABLE_FAMILY_BYTES, TABLE_TYPE_BYTES, MetaDataProtocol.MIN_TABLE_TIMESTAMP, ByteUtil.EMPTY_BYTE_ARRAY))); } } @Override public ResultSet getTableTypes() throws SQLException { return new PhoenixResultSet(new MaterializedResultIterator(TABLE_TYPE_TUPLES), TABLE_TYPE_ROW_PROJECTOR, new PhoenixStatement(connection)); } /** * We support either: * 1) A non null tableNamePattern to find an exactly match with a table name, in which case either a single * row would be returned in the ResultSet (if found) or no rows would be returned (if not * found). * 2) A null tableNamePattern, in which case the ResultSet returned would have one row per * table. * Note that catalog and schemaPattern must be null or an empty string and types must be null * or "TABLE". Otherwise, no rows will be returned. */ @Override public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) throws SQLException { StringBuilder buf = new StringBuilder("select \n" + TENANT_ID + " " + TABLE_CAT + "," + // tenant_id is the catalog TABLE_SCHEM + "," + TABLE_NAME + " ," + SQLTableTypeFunction.NAME + "(" + TABLE_TYPE + ") AS " + TABLE_TYPE + "," + REMARKS + " ," + TYPE_NAME + "," + SELF_REFERENCING_COL_NAME + "," + REF_GENERATION + "," + IndexStateNameFunction.NAME + "(" + INDEX_STATE + ") AS " + INDEX_STATE + "," + IMMUTABLE_ROWS + "," + SALT_BUCKETS + "," + MULTI_TENANT + "," + VIEW_STATEMENT + "," + SQLViewTypeFunction.NAME + "(" + VIEW_TYPE + ") AS " + VIEW_TYPE + "," + SQLIndexTypeFunction.NAME + "(" + INDEX_TYPE + ") AS " + INDEX_TYPE + " from " + SYSTEM_CATALOG + " " + SYSTEM_CATALOG_ALIAS + " where " + COLUMN_NAME + " is null" + " and " + COLUMN_FAMILY + " is null"); addTenantIdFilter(buf, catalog); if (schemaPattern != null) { buf.append(" and " + TABLE_SCHEM + (schemaPattern.length() == 0 ? " is null" : " like '" + escapePattern(schemaPattern) + "'")); } if (tableNamePattern != null) { buf.append(" and " + TABLE_NAME + " like '" + escapePattern(tableNamePattern) + "'"); } if (types != null && types.length > 0) { buf.append(" and " + TABLE_TYPE + " IN ("); for (String type : types) { buf.append('\''); buf.append(PTableType.fromValue(type).getSerializedValue()); buf.append('\''); buf.append(','); } buf.setCharAt(buf.length() - 1, ')'); } buf.append(" order by " + SYSTEM_CATALOG_ALIAS + "." + TABLE_TYPE + "," + TENANT_ID + "," + TABLE_SCHEM + "," + TABLE_NAME); Statement stmt = connection.createStatement(); return stmt.executeQuery(buf.toString()); } @Override public String getTimeDateFunctions() throws SQLException { return ""; } @Override public ResultSet getTypeInfo() throws SQLException { return emptyResultSet; } @Override public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException { return emptyResultSet; } @Override public String getURL() throws SQLException { return connection.getURL(); } @Override public String getUserName() throws SQLException { String userName = connection.getQueryServices().getUserName(); return userName == null ? StringUtil.EMPTY_STRING : userName; } @Override public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { return emptyResultSet; } @Override public boolean insertsAreDetected(int type) throws SQLException { return false; } @Override public boolean isCatalogAtStart() throws SQLException { return false; } @Override public boolean isReadOnly() throws SQLException { return false; } @Override public boolean locatorsUpdateCopy() throws SQLException { return false; } @Override public boolean nullPlusNonNullIsNull() throws SQLException { return true; } @Override public boolean nullsAreSortedAtEnd() throws SQLException { return false; } @Override public boolean nullsAreSortedAtStart() throws SQLException { return true; } @Override public boolean nullsAreSortedHigh() throws SQLException { return false; } @Override public boolean nullsAreSortedLow() throws SQLException { return true; } @Override public boolean othersDeletesAreVisible(int type) throws SQLException { return true; } @Override public boolean othersInsertsAreVisible(int type) throws SQLException { return true; } @Override public boolean othersUpdatesAreVisible(int type) throws SQLException { return true; } @Override public boolean ownDeletesAreVisible(int type) throws SQLException { return false; } @Override public boolean ownInsertsAreVisible(int type) throws SQLException { return false; } @Override public boolean ownUpdatesAreVisible(int type) throws SQLException { return false; } @Override public boolean storesLowerCaseIdentifiers() throws SQLException { return false; } @Override public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { return false; } @Override public boolean storesMixedCaseIdentifiers() throws SQLException { return false; } @Override public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { return true; } @Override public boolean storesUpperCaseIdentifiers() throws SQLException { return true; } @Override public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { return true; } @Override public boolean supportsANSI92EntryLevelSQL() throws SQLException { return false; } @Override public boolean supportsANSI92FullSQL() throws SQLException { return false; } @Override public boolean supportsANSI92IntermediateSQL() throws SQLException { return false; } @Override public boolean supportsAlterTableWithAddColumn() throws SQLException { return true; } @Override public boolean supportsAlterTableWithDropColumn() throws SQLException { return true; } @Override public boolean supportsBatchUpdates() throws SQLException { return true; } @Override public boolean supportsCatalogsInDataManipulation() throws SQLException { return false; } @Override public boolean supportsCatalogsInIndexDefinitions() throws SQLException { return false; } @Override public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { return false; } @Override public boolean supportsCatalogsInProcedureCalls() throws SQLException { return false; } @Override public boolean supportsCatalogsInTableDefinitions() throws SQLException { return false; } @Override public boolean supportsColumnAliasing() throws SQLException { return true; } @Override public boolean supportsConvert() throws SQLException { return true; } @Override public boolean supportsConvert(int fromType, int toType) throws SQLException { // TODO return false; } @Override public boolean supportsCoreSQLGrammar() throws SQLException { return false; } @Override public boolean supportsCorrelatedSubqueries() throws SQLException { return false; } @Override public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException { return true; } @Override public boolean supportsDataManipulationTransactionsOnly() throws SQLException { return false; } @Override public boolean supportsDifferentTableCorrelationNames() throws SQLException { return false; } @Override public boolean supportsExpressionsInOrderBy() throws SQLException { return true; } @Override public boolean supportsExtendedSQLGrammar() throws SQLException { return false; } @Override public boolean supportsFullOuterJoins() throws SQLException { return false; } @Override public boolean supportsGetGeneratedKeys() throws SQLException { return false; } @Override public boolean supportsGroupBy() throws SQLException { return true; } @Override public boolean supportsGroupByBeyondSelect() throws SQLException { return false; } @Override public boolean supportsGroupByUnrelated() throws SQLException { return false; } @Override public boolean supportsIntegrityEnhancementFacility() throws SQLException { return false; } @Override public boolean supportsLikeEscapeClause() throws SQLException { return true; } @Override public boolean supportsLimitedOuterJoins() throws SQLException { return false; } @Override public boolean supportsMinimumSQLGrammar() throws SQLException { return false; } @Override public boolean supportsMixedCaseIdentifiers() throws SQLException { return false; } @Override public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { return true; } @Override public boolean supportsMultipleOpenResults() throws SQLException { return true; } @Override public boolean supportsMultipleResultSets() throws SQLException { return true; } @Override public boolean supportsMultipleTransactions() throws SQLException { return false; } @Override public boolean supportsNamedParameters() throws SQLException { return false; } @Override public boolean supportsNonNullableColumns() throws SQLException { return true; } @Override public boolean supportsOpenCursorsAcrossCommit() throws SQLException { return false; } @Override public boolean supportsOpenCursorsAcrossRollback() throws SQLException { return false; } @Override public boolean supportsOpenStatementsAcrossCommit() throws SQLException { return false; } @Override public boolean supportsOpenStatementsAcrossRollback() throws SQLException { return false; } @Override public boolean supportsOrderByUnrelated() throws SQLException { return false; } @Override public boolean supportsOuterJoins() throws SQLException { return true; } @Override public boolean supportsPositionedDelete() throws SQLException { return false; } @Override public boolean supportsPositionedUpdate() throws SQLException { return false; } @Override public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException { // TODO: review return type == ResultSet.TYPE_FORWARD_ONLY && concurrency == Connection.TRANSACTION_READ_COMMITTED; } @Override public boolean supportsResultSetHoldability(int holdability) throws SQLException { // TODO return holdability == connection.getHoldability(); } @Override public boolean supportsResultSetType(int type) throws SQLException { return type == ResultSet.TYPE_FORWARD_ONLY; } @Override public boolean supportsSavepoints() throws SQLException { return false; } @Override public boolean supportsSchemasInDataManipulation() throws SQLException { return false; } @Override public boolean supportsSchemasInIndexDefinitions() throws SQLException { return false; } @Override public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { return false; } @Override public boolean supportsSchemasInProcedureCalls() throws SQLException { return false; } @Override public boolean supportsSchemasInTableDefinitions() throws SQLException { return false; } @Override public boolean supportsSelectForUpdate() throws SQLException { return false; } @Override public boolean supportsStatementPooling() throws SQLException { return false; } @Override public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { return false; } @Override public boolean supportsStoredProcedures() throws SQLException { return false; } @Override public boolean supportsSubqueriesInComparisons() throws SQLException { return false; } @Override public boolean supportsSubqueriesInExists() throws SQLException { return false; } @Override public boolean supportsSubqueriesInIns() throws SQLException { return false; } @Override public boolean supportsSubqueriesInQuantifieds() throws SQLException { return false; } @Override public boolean supportsTableCorrelationNames() throws SQLException { return false; } @Override public boolean supportsTransactionIsolationLevel(int level) throws SQLException { return level == connection.getTransactionIsolation(); } @Override public boolean supportsTransactions() throws SQLException { return false; } @Override public boolean supportsUnion() throws SQLException { return false; } @Override public boolean supportsUnionAll() throws SQLException { return false; } @Override public boolean updatesAreDetected(int type) throws SQLException { return false; } @Override public boolean usesLocalFilePerTable() throws SQLException { return false; } @Override public boolean usesLocalFiles() throws SQLException { return false; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return iface.isInstance(this); } @SuppressWarnings("unchecked") @Override public <T> T unwrap(Class<T> iface) throws SQLException { if (!iface.isInstance(this)) { throw new SQLExceptionInfo.Builder(SQLExceptionCode.CLASS_NOT_UNWRAPPABLE) .setMessage(this.getClass().getName() + " not unwrappable from " + iface.getName()).build() .buildException(); } return (T) this; } @Override public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { return this.emptyResultSet; } @Override public boolean generatedKeyAlwaysReturned() throws SQLException { return false; } }