Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.impossibl.postgres.jdbc.PGDatabaseMetaData Maven / Gradle / Ivy
/**
* Copyright (c) 2013, impossibl.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of impossibl.com nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.impossibl.postgres.jdbc;
import com.impossibl.postgres.data.ACLItem;
import com.impossibl.postgres.protocol.ResultField;
import com.impossibl.postgres.protocol.ResultField.Format;
import com.impossibl.postgres.types.CompositeType;
import com.impossibl.postgres.types.DomainType;
import com.impossibl.postgres.types.Registry;
import com.impossibl.postgres.types.Type;
import com.impossibl.postgres.utils.guava.Joiner;
import static com.impossibl.postgres.jdbc.Exceptions.NOT_IMPLEMENTED;
import static com.impossibl.postgres.jdbc.Exceptions.SERVER_VERSION_NOT_SUPPORTED;
import static com.impossibl.postgres.jdbc.Exceptions.UNWRAP_ERROR;
import static com.impossibl.postgres.system.Settings.CREDENTIALS_USERNAME;
import static com.impossibl.postgres.system.Settings.DATABASE_URL;
import static com.impossibl.postgres.utils.guava.Strings.isNullOrEmpty;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PseudoColumnUsage;
import java.sql.ResultSet;
import java.sql.RowIdLifetime;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static java.util.Arrays.asList;
class PGDatabaseMetaData implements DatabaseMetaData {
private static final String EXTRA_KEYWORDS =
"abort,acl,add,aggregate,append,archive," +
"arch_store,backward,binary,boolean,change,cluster," +
"copy,database,delimiter,delimiters,do,extend," +
"explain,forward,heavy,index,inherits,isnull," +
"light,listen,load,merge,nothing,notify," +
"notnull,oids,purge,rename,replace,retrieve," +
"returns,rule,recipe,setof,stdin,stdout,store," +
"vacuum,verbose,version";
PGConnectionImpl connection;
int maxNameLength;
int maxIndexKeys;
PGDatabaseMetaData(PGConnectionImpl connection) {
this.connection = connection;
}
private int execForInteger(String query) throws SQLException {
String res = connection.executeForString(query, false);
if (res == null) {
throw SERVER_VERSION_NOT_SUPPORTED;
}
try {
return Integer.parseInt(res);
}
catch (NumberFormatException e) {
throw SERVER_VERSION_NOT_SUPPORTED;
}
}
private PGResultSet execForResultSet(String sql, Object... params) throws SQLException {
return execForResultSet(sql, Arrays.asList(params));
}
private PGResultSet execForResultSet(String sql, List params) throws SQLException {
PGPreparedStatement ps = connection.prepareStatement(sql);
ps.closeOnCompletion();
for (int c = 0; c < params.size(); ++c) {
ps.setObject(c + 1, params.get(c));
}
return ps.executeQuery();
}
private PGResultSet createResultSet(List resultFields, List results) throws SQLException {
PGStatement stmt = connection.createStatement();
stmt.closeOnCompletion();
return stmt.createResultSet(resultFields, results);
}
private int getMaxNameLength() throws SQLException {
if (maxNameLength == 0) {
maxNameLength =
execForInteger("SELECT t.typlen FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n WHERE t.typnamespace=n.oid AND t.typname='name' AND n.nspname='pg_catalog'");
}
return maxNameLength;
}
protected int getMaxIndexKeys() throws SQLException {
if (maxIndexKeys == 0) {
maxIndexKeys = execForInteger("SELECT setting FROM pg_catalog.pg_settings WHERE name='max_index_keys'");
}
return maxIndexKeys;
}
@Override
public T unwrap(Class iface) throws SQLException {
if (!iface.isAssignableFrom(getClass())) {
throw UNWRAP_ERROR;
}
return iface.cast(this);
}
@Override
public boolean isWrapperFor (Class> iface) throws SQLException {
return iface.isAssignableFrom(getClass());
}
@Override
public boolean allProceduresAreCallable() throws SQLException {
return true;
}
@Override
public boolean allTablesAreSelectable() throws SQLException {
return true;
}
@Override
public String getURL() throws SQLException {
Object val = connection.getSetting(DATABASE_URL);
if (val == null)
throw new SQLException("invalid connection");
return val.toString();
}
@Override
public String getUserName() throws SQLException {
Object val = connection.getSetting(CREDENTIALS_USERNAME);
if (val == null)
throw new SQLException("invalid connection");
return val.toString();
}
@Override
public boolean isReadOnly() throws SQLException {
return connection.isReadOnly();
}
@Override
public boolean nullsAreSortedHigh() throws SQLException {
return true;
}
@Override
public boolean nullsAreSortedLow() throws SQLException {
return false;
}
@Override
public boolean nullsAreSortedAtStart() throws SQLException {
return false;
}
@Override
public boolean nullsAreSortedAtEnd() throws SQLException {
return false;
}
@Override
public String getDatabaseProductName() throws SQLException {
return "PostgreSQL";
}
@Override
public String getDatabaseProductVersion() throws SQLException {
return connection.getServerVersion().toString();
}
@Override
public String getDriverName() throws SQLException {
return "PostgreSQL JDBC - NG";
}
@Override
public String getDriverVersion() throws SQLException {
return PGDriver.VERSION.toString();
}
@Override
public int getDriverMajorVersion() {
return PGDriver.VERSION.getMajor();
}
@Override
public int getDriverMinorVersion() {
return PGDriver.VERSION.getMinor();
}
@Override
public boolean usesLocalFiles() throws SQLException {
return false;
}
@Override
public boolean usesLocalFilePerTable() throws SQLException {
return false;
}
@Override
public boolean supportsMixedCaseIdentifiers() throws SQLException {
return false;
}
@Override
public boolean storesUpperCaseIdentifiers() throws SQLException {
return false;
}
@Override
public boolean storesLowerCaseIdentifiers() throws SQLException {
return true;
}
@Override
public boolean storesMixedCaseIdentifiers() throws SQLException {
return false;
}
@Override
public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException {
return true;
}
@Override
public boolean storesUpperCaseQuotedIdentifiers() throws SQLException {
return false;
}
@Override
public boolean storesLowerCaseQuotedIdentifiers() throws SQLException {
return false;
}
@Override
public boolean storesMixedCaseQuotedIdentifiers() throws SQLException {
return false;
}
@Override
public String getIdentifierQuoteString() throws SQLException {
return "\"";
}
@Override
public String getSQLKeywords() throws SQLException {
return EXTRA_KEYWORDS;
}
@Override
public String getNumericFunctions() throws SQLException {
return Joiner.on(',').join(SQLTextEscapeFunctions.ALL_NUMERIC);
}
@Override
public String getStringFunctions() throws SQLException {
return Joiner.on(',').join(SQLTextEscapeFunctions.ALL_STRING);
}
@Override
public String getSystemFunctions() throws SQLException {
return Joiner.on(',').join(SQLTextEscapeFunctions.ALL_SYSTEM);
}
@Override
public String getTimeDateFunctions() throws SQLException {
return Joiner.on(',').join(SQLTextEscapeFunctions.ALL_DATE_TIME);
}
@Override
public String getSearchStringEscape() throws SQLException {
return "\\";
}
@Override
public String getExtraNameCharacters() throws SQLException {
return "";
}
@Override
public boolean supportsAlterTableWithAddColumn() throws SQLException {
return true;
}
@Override
public boolean supportsAlterTableWithDropColumn() throws SQLException {
return true;
}
@Override
public boolean supportsColumnAliasing() throws SQLException {
return true;
}
@Override
public boolean nullPlusNonNullIsNull() throws SQLException {
return true;
}
@Override
public boolean supportsConvert() throws SQLException {
return false;
}
@Override
public boolean supportsConvert(int fromType, int toType) throws SQLException {
return false;
}
@Override
public boolean supportsTableCorrelationNames() throws SQLException {
return true;
}
@Override
public boolean supportsDifferentTableCorrelationNames() throws SQLException {
return false;
}
@Override
public boolean supportsExpressionsInOrderBy() throws SQLException {
return true;
}
@Override
public boolean supportsOrderByUnrelated() throws SQLException {
return true;
}
@Override
public boolean supportsGroupBy() throws SQLException {
return true;
}
@Override
public boolean supportsGroupByUnrelated() throws SQLException {
return true;
}
@Override
public boolean supportsGroupByBeyondSelect() throws SQLException {
return true;
}
@Override
public boolean supportsLikeEscapeClause() throws SQLException {
return true;
}
@Override
public boolean supportsMultipleResultSets() throws SQLException {
return true;
}
@Override
public boolean supportsMultipleTransactions() throws SQLException {
return true;
}
@Override
public boolean supportsNonNullableColumns() throws SQLException {
return true;
}
@Override
public boolean supportsMinimumSQLGrammar() throws SQLException {
return true;
}
@Override
public boolean supportsCoreSQLGrammar() throws SQLException {
return true;
}
@Override
public boolean supportsExtendedSQLGrammar() throws SQLException {
return false;
}
@Override
public boolean supportsANSI92EntryLevelSQL() throws SQLException {
return true;
}
@Override
public boolean supportsANSI92IntermediateSQL() throws SQLException {
return false;
}
@Override
public boolean supportsANSI92FullSQL() throws SQLException {
return false;
}
@Override
public boolean supportsIntegrityEnhancementFacility() throws SQLException {
return true;
}
@Override
public boolean supportsOuterJoins() throws SQLException {
return true;
}
@Override
public boolean supportsFullOuterJoins() throws SQLException {
return true;
}
@Override
public boolean supportsLimitedOuterJoins() throws SQLException {
return true;
}
@Override
public String getSchemaTerm() throws SQLException {
return "schema";
}
@Override
public String getProcedureTerm() throws SQLException {
return "function";
}
@Override
public String getCatalogTerm() throws SQLException {
return "database";
}
@Override
public boolean isCatalogAtStart() throws SQLException {
return true;
}
@Override
public String getCatalogSeparator() throws SQLException {
return ".";
}
@Override
public boolean supportsSchemasInDataManipulation() throws SQLException {
return true;
}
@Override
public boolean supportsSchemasInProcedureCalls() throws SQLException {
return true;
}
@Override
public boolean supportsSchemasInTableDefinitions() throws SQLException {
return true;
}
@Override
public boolean supportsSchemasInIndexDefinitions() throws SQLException {
return true;
}
@Override
public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException {
return true;
}
@Override
public boolean supportsCatalogsInDataManipulation() throws SQLException {
return false;
}
@Override
public boolean supportsCatalogsInProcedureCalls() throws SQLException {
return false;
}
@Override
public boolean supportsCatalogsInTableDefinitions() throws SQLException {
return false;
}
@Override
public boolean supportsCatalogsInIndexDefinitions() throws SQLException {
return false;
}
@Override
public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException {
return false;
}
@Override
public boolean supportsPositionedDelete() throws SQLException {
return true;
}
@Override
public boolean supportsPositionedUpdate() throws SQLException {
return true;
}
@Override
public boolean supportsSelectForUpdate() throws SQLException {
return true;
}
@Override
public boolean supportsStoredProcedures() throws SQLException {
return true;
}
@Override
public boolean supportsSubqueriesInComparisons() throws SQLException {
return true;
}
@Override
public boolean supportsSubqueriesInExists() throws SQLException {
return true;
}
@Override
public boolean supportsSubqueriesInIns() throws SQLException {
return true;
}
@Override
public boolean supportsSubqueriesInQuantifieds() throws SQLException {
return true;
}
@Override
public boolean supportsCorrelatedSubqueries() throws SQLException {
return true;
}
@Override
public boolean supportsUnion() throws SQLException {
return true;
}
@Override
public boolean supportsUnionAll() throws SQLException {
return true;
}
@Override
public boolean supportsOpenCursorsAcrossCommit() throws SQLException {
return true;
}
@Override
public boolean supportsOpenCursorsAcrossRollback() throws SQLException {
return true;
}
@Override
public boolean supportsOpenStatementsAcrossCommit() throws SQLException {
return true;
}
@Override
public boolean supportsOpenStatementsAcrossRollback() throws SQLException {
return true;
}
@Override
public int getMaxBinaryLiteralLength() throws SQLException {
return 0;
}
@Override
public int getMaxCharLiteralLength() throws SQLException {
return 0;
}
@Override
public int getMaxColumnNameLength() throws SQLException {
return getMaxNameLength();
}
@Override
public int getMaxColumnsInGroupBy() throws SQLException {
return 0;
}
@Override
public int getMaxColumnsInIndex() throws SQLException {
return getMaxIndexKeys();
}
@Override
public int getMaxColumnsInOrderBy() throws SQLException {
return 0;
}
@Override
public int getMaxColumnsInSelect() throws SQLException {
return 0;
}
@Override
public int getMaxColumnsInTable() throws SQLException {
return 1600;
}
@Override
public int getMaxConnections() throws SQLException {
return 0;
}
@Override
public int getMaxCursorNameLength() throws SQLException {
return getMaxNameLength();
}
@Override
public int getMaxIndexLength() throws SQLException {
return 0;
}
@Override
public int getMaxSchemaNameLength() throws SQLException {
return getMaxNameLength();
}
@Override
public int getMaxProcedureNameLength() throws SQLException {
return getMaxNameLength();
}
@Override
public int getMaxCatalogNameLength() throws SQLException {
return getMaxNameLength();
}
@Override
public int getMaxRowSize() throws SQLException {
return 0; //Larger than int (1.6 TB)
}
@Override
public boolean doesMaxRowSizeIncludeBlobs() throws SQLException {
return false;
}
@Override
public int getMaxStatementLength() throws SQLException {
return 0;
}
@Override
public int getMaxStatements() throws SQLException {
return 0;
}
@Override
public int getMaxTableNameLength() throws SQLException {
return getMaxNameLength();
}
@Override
public int getMaxTablesInSelect() throws SQLException {
return 0;
}
@Override
public int getMaxUserNameLength() throws SQLException {
return getMaxNameLength();
}
@Override
public int getDefaultTransactionIsolation() throws SQLException {
return Connection.TRANSACTION_READ_COMMITTED;
}
@Override
public boolean supportsTransactions() throws SQLException {
return true;
}
@Override
public boolean supportsTransactionIsolationLevel(int level) throws SQLException {
switch(level) {
case Connection.TRANSACTION_NONE:
case Connection.TRANSACTION_READ_UNCOMMITTED:
case Connection.TRANSACTION_READ_COMMITTED:
case Connection.TRANSACTION_REPEATABLE_READ:
case Connection.TRANSACTION_SERIALIZABLE:
return true;
default:
return false;
}
}
@Override
public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException {
return true;
}
@Override
public boolean supportsDataManipulationTransactionsOnly() throws SQLException {
return true;
}
@Override
public boolean dataDefinitionCausesTransactionCommit() throws SQLException {
return false;
}
@Override
public boolean dataDefinitionIgnoredInTransactions() throws SQLException {
return false;
}
@Override
public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException {
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT NULL AS PROCEDURE_CAT, n.nspname AS PROCEDURE_SCHEM, p.proname AS PROCEDURE_NAME, NULL, NULL, NULL, " +
" d.description AS REMARKS, " + java.sql.DatabaseMetaData.procedureReturnsResult + " AS PROCEDURE_TYPE, p.proname || '_' || p.oid AS SPECIFIC_NAME " +
" FROM pg_catalog.pg_namespace n, pg_catalog.pg_proc p " +
" LEFT JOIN pg_catalog.pg_description d ON (p.oid=d.objoid) " +
" LEFT JOIN pg_catalog.pg_class c ON (d.classoid=c.oid AND c.relname='pg_proc') " +
" LEFT JOIN pg_catalog.pg_namespace pn ON (c.relnamespace=pn.oid AND pn.nspname='pg_catalog') " +
" WHERE p.pronamespace=n.oid");
if (!isNullOrEmpty(schemaPattern)) {
sql.append(" AND n.nspname LIKE ?");
params.add(schemaPattern);
}
if (!isNullOrEmpty(procedureNamePattern)) {
sql.append(" AND p.proname LIKE ?");
params.add(procedureNamePattern);
}
sql.append(" ORDER BY PROCEDURE_SCHEM, PROCEDURE_NAME, SPECIFIC_NAME");
return execForResultSet(sql.toString(), params);
}
@Override
public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException {
Registry reg = connection.getRegistry();
ResultField[] resultFields = new ResultField[20];
List results = new ArrayList<>();
resultFields[0] = new ResultField("PROCEDURE_CAT", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[1] = new ResultField("PROCEDURE_SCHEM", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[2] = new ResultField("PROCEDURE_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[3] = new ResultField("COLUMN_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[4] = new ResultField("COLUMN_TYPE", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[5] = new ResultField("DATA_TYPE", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[6] = new ResultField("TYPE_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[7] = new ResultField("PRECISION", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[8] = new ResultField("LENGTH", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[9] = new ResultField("SCALE", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[10] = new ResultField("RADIX", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[11] = new ResultField("NULLABLE", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[12] = new ResultField("REMARKS", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[13] = new ResultField("COLUMN_DEF", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[14] = new ResultField("SQL_DATA_TYPE", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[15] = new ResultField("SQL_DATETIME_SUB", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[16] = new ResultField("CHAR_OCTECT_LENGTH", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[17] = new ResultField("ORDINAL_POSITION", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[18] = new ResultField("IS_NULLABLE", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[19] = new ResultField("SPECIFIC_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT n.nspname,p.proname,p.prorettype,p.proargtypes, t.typtype,t.typrelid, p.proargnames, " +
" p.proargmodes, p.proallargtypes, p.oid " +
" FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_type t " +
" WHERE p.pronamespace=n.oid AND p.prorettype=t.oid ");
if (!isNullOrEmpty(schemaPattern)) {
sql.append(" AND n.nspname LIKE ?");
params.add(schemaPattern);
}
if (!isNullOrEmpty(procedureNamePattern)) {
sql.append(" AND p.proname LIKE ?");
params.add(procedureNamePattern);
}
sql.append(" ORDER BY n.nspname, p.proname, p.oid::text ");
try (PGResultSet rs = execForResultSet(sql.toString(), params)) {
while (rs.next()) {
String schema = rs.getString("nspname");
String procedureName = rs.getString("proname");
String specificName = rs.getString("proname") + "_" + rs.getString("oid");
Type returnType = reg.loadType(rs.getInt("prorettype"));
String returnTypeType = rs.getString("typtype");
int returnTypeRelId = rs.getInt("typrelid");
Integer[] argTypeIds = rs.getObject("proargtypes", Integer[].class);
String[] argNames = rs.getObject("proargnames", String[].class);
String[] argModes = rs.getObject("proargmodes", String[].class);
Integer[] allArgTypeIds = rs.getObject("proallargtypes", Integer[].class);
int numArgs = allArgTypeIds != null ? allArgTypeIds.length : argTypeIds.length;
// decide if we are returning a single column result.
if (returnTypeType.equals("b") ||
returnTypeType.equals("d") ||
(returnTypeType.equals("p") && argModes == null)) {
Object[] row = new Object[resultFields.length];
row[0] = null;
row[1] = schema;
row[2] = procedureName;
row[3] = "returnValue";
row[4] = DatabaseMetaData.procedureColumnReturn;
row[5] = SQLTypeMetaData.getSQLType(returnType);
row[6] = SQLTypeMetaData.getTypeName(returnType, null, 0);
row[7] = null;
row[8] = null;
row[9] = null;
row[10] = null;
row[11] = DatabaseMetaData.procedureNullableUnknown;
row[12] = null;
row[17] = 0;
row[18] = "";
row[19] = specificName;
results.add(row);
}
// Add a row for each argument.
for (int i = 0; i < numArgs; i++) {
Object[] row = new Object[resultFields.length];
row[0] = null;
row[1] = schema;
row[2] = procedureName;
if (argNames != null) {
row[3] = argNames[i];
}
else {
row[3] = "$" + (i + 1);
}
int columnMode = DatabaseMetaData.procedureColumnIn;
if (argModes != null) {
if (argModes[i].equals("o")) {
columnMode = DatabaseMetaData.procedureColumnOut;
}
else if (argModes[i].equals("b")) {
columnMode = DatabaseMetaData.procedureColumnInOut;
}
}
row[4] = columnMode;
Type argType;
if (allArgTypeIds != null) {
argType = reg.loadType(allArgTypeIds[i].intValue());
}
else {
argType = reg.loadType(argTypeIds[i].intValue());
}
row[5] = SQLTypeMetaData.getSQLType(argType);
row[6] = argType.getJavaType(argType.getPreferredFormat(), connection.getTypeMap()).getName();
row[7] = null;
row[8] = null;
row[9] = null;
row[10] = null;
row[11] = DatabaseMetaData.procedureNullableUnknown;
row[12] = null;
row[17] = i + 1;
row[18] = "";
row[19] = specificName;
results.add(row);
}
// if we are returning a multi-column result.
if (returnTypeType.equals("c") ||
(returnTypeType.equals("p") && argModes != null && returnTypeRelId != 0)) {
String columnsql = "SELECT a.attname,a.atttypid FROM pg_catalog.pg_attribute a WHERE a.attrelid = " + returnTypeRelId + " AND a.attnum > 0 ORDER BY a.attnum ";
try (ResultSet columnrs = connection.createStatement().executeQuery(columnsql)) {
while (columnrs.next()) {
Type columnType = reg.loadType(columnrs.getInt("atttypid"));
Object[] row = new Object[resultFields.length];
row[0] = null;
row[1] = schema;
row[2] = procedureName;
row[3] = columnrs.getString("attname");
row[4] = DatabaseMetaData.procedureColumnResult;
row[5] = SQLTypeMetaData.getSQLType(columnType);
row[6] = columnType.getJavaType(columnType.getPreferredFormat(), connection.getTypeMap()).getName();
row[7] = null;
row[8] = null;
row[9] = null;
row[10] = null;
row[11] = DatabaseMetaData.procedureNullableUnknown;
row[12] = null;
row[17] = 0;
row[18] = "";
row[19] = specificName;
results.add(row);
}
}
}
}
}
return createResultSet(Arrays.asList(resultFields), results);
}
@Override
public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) throws SQLException {
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, c.relname AS TABLE_NAME, " +
" CASE n.nspname ~ '^pg_' OR n.nspname = 'information_schema' " +
" WHEN true THEN CASE " +
" WHEN n.nspname = 'pg_catalog' OR n.nspname = 'information_schema' THEN CASE c.relkind " +
" WHEN 'r' THEN 'SYSTEM TABLE' " +
" WHEN 'v' THEN 'SYSTEM VIEW' " +
" WHEN 'i' THEN 'SYSTEM INDEX' " +
" ELSE NULL " +
" END " +
" WHEN n.nspname = 'pg_toast' THEN CASE c.relkind " +
" WHEN 'r' THEN 'SYSTEM TOAST TABLE' " +
" WHEN 'i' THEN 'SYSTEM TOAST INDEX' " +
" ELSE NULL " +
" END " +
" ELSE CASE c.relkind " +
" WHEN 'r' THEN 'TEMPORARY TABLE' " +
" WHEN 'i' THEN 'TEMPORARY INDEX' " +
" WHEN 'S' THEN 'TEMPORARY SEQUENCE' " +
" WHEN 'v' THEN 'TEMPORARY VIEW' " +
" ELSE NULL " +
" END " +
" END " +
" WHEN false THEN CASE c.relkind " +
" WHEN 'r' THEN 'TABLE' " +
" WHEN 'i' THEN 'INDEX' " +
" WHEN 'S' THEN 'SEQUENCE' " +
" WHEN 'v' THEN 'VIEW' " +
" WHEN 'c' THEN 'TYPE' " +
" WHEN 'f' THEN 'FOREIGN TABLE' " +
" ELSE NULL " +
" END " +
" ELSE NULL " +
" END " +
" AS TABLE_TYPE, d.description AS REMARKS " +
" FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c " +
" LEFT JOIN pg_catalog.pg_description d ON (c.oid = d.objoid AND d.objsubid = 0) " +
" LEFT JOIN pg_catalog.pg_class dc ON (d.classoid=dc.oid AND dc.relname='pg_class') " +
" LEFT JOIN pg_catalog.pg_namespace dn ON (dn.oid=dc.relnamespace AND dn.nspname='pg_catalog') " +
" WHERE c.relnamespace = n.oid ");
if (!isNullOrEmpty(schemaPattern)) {
sql.append(" AND n.nspname LIKE ?");
params.add(schemaPattern);
}
if (!isNullOrEmpty(tableNamePattern)) {
sql.append(" AND c.relname LIKE ?");
params.add(tableNamePattern);
}
if (types != null) {
sql.append(" AND (false ");
for (int i = 0; i < types.length; i++) {
String clause = tableTypeClauses.get(types[i]);
if (clause != null) {
sql.append(" OR ( " + clause + " ) ");
}
}
sql.append(") ");
}
sql.append(" ORDER BY TABLE_TYPE,TABLE_SCHEM,TABLE_NAME ");
return execForResultSet(sql.toString(), params);
}
private static final Map tableTypeClauses;
static {
tableTypeClauses = new HashMap<>();
tableTypeClauses.put("TABLE", "c.relkind = 'r' AND n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'");
tableTypeClauses.put("VIEW", "c.relkind = 'v' AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema'");
tableTypeClauses.put("INDEX", "c.relkind = 'i' AND n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'");
tableTypeClauses.put("SEQUENCE", "c.relkind = 'S'");
tableTypeClauses.put("TYPE", "c.relkind = 'c' AND n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'");
tableTypeClauses.put("SYSTEM TABLE", "c.relkind = 'r' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema')");
tableTypeClauses.put("SYSTEM TOAST TABLE", "c.relkind = 'r' AND n.nspname = 'pg_toast'");
tableTypeClauses.put("SYSTEM TOAST INDEX", "c.relkind = 'i' AND n.nspname = 'pg_toast'");
tableTypeClauses.put("SYSTEM VIEW", "c.relkind = 'v' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema') ");
tableTypeClauses.put("SYSTEM INDEX", "c.relkind = 'i' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema') ");
tableTypeClauses.put("TEMPORARY TABLE", "c.relkind = 'r' AND n.nspname ~ '^pg_temp_' ");
tableTypeClauses.put("TEMPORARY INDEX", "c.relkind = 'i' AND n.nspname ~ '^pg_temp_' ");
tableTypeClauses.put("TEMPORARY VIEW", "c.relkind = 'v' AND n.nspname ~ '^pg_temp_' ");
tableTypeClauses.put("TEMPORARY SEQUENCE", "c.relkind = 'S' AND n.nspname ~ '^pg_temp_' ");
tableTypeClauses.put("FOREIGN TABLE", "c.relkind = 'f'");
}
@Override
public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
//Select all schemas the current user has access to including temp and toast schemas
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT nspname AS TABLE_SCHEM, NULL AS TABLE_CATALOG" +
" FROM pg_catalog.pg_namespace " +
" WHERE " +
" (" +
" nspname <> 'pg_toast'" +
" AND" +
" (nspname !~ '^pg_temp_\\d+' OR nspname = (pg_catalog.current_schemas(true))[1])" +
" AND" +
" (nspname !~ '^pg_toast_temp_' OR nspname = replace((pg_catalog.current_schemas(true))[1], 'pg_temp_', 'pg_toast_temp_'))" +
" )");
if (!isNullOrEmpty(schemaPattern)) {
sql.append(" AND nspname LIKE ?");
params.add(schemaPattern);
}
sql.append(" ORDER BY TABLE_SCHEM");
return execForResultSet(sql.toString(), params);
}
@Override
public ResultSet getSchemas() throws SQLException {
return getSchemas(null, null);
}
@Override
public ResultSet getCatalogs() throws SQLException {
ResultField[] resultFields = new ResultField[1];
resultFields[0] = new ResultField("TABLE_CAT", 0, (short)0, connection.getRegistry().loadType("text"), (short)0, 0, Format.Binary);
List results = new ArrayList<>();
results.add(new Object[] {connection.getCatalog()});
return createResultSet(Arrays.asList(resultFields), results);
}
@Override
public ResultSet getTableTypes() throws SQLException {
ResultField[] resultFields = new ResultField[1];
resultFields[0] = new ResultField("TABLE_TYPE", 0, (short)0, connection.getRegistry().loadType("text"), (short)0, 0, Format.Binary);
List results = new ArrayList<>();
for (String tableType : tableTypeClauses.keySet()) {
results.add(new Object[] {tableType});
}
return createResultSet(Arrays.asList(resultFields), results);
}
static class ColumnData {
String tableSchemaName;
String tableName;
CompositeType relationType;
int relationAttrNum;
String columnName;
Type type;
int typeModifier;
int typeLength;
Boolean nullable;
String defaultValue;
String description;
Type baseType;
}
@Override
public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException {
Registry registry = connection.getRegistry();
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT * FROM (" +
" SELECT n.nspname,c.relname,a.attname,a.atttypid,a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull,a.atttypmod,a.attlen,a.attrelid," +
" row_number() OVER (PARTITION BY a.attrelid ORDER BY a.attnum) AS attnum, pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS adsrc,dsc.description,t.typbasetype,t.typtype " +
" FROM pg_catalog.pg_namespace n " +
" JOIN pg_catalog.pg_class c ON (c.relnamespace = n.oid) " +
" JOIN pg_catalog.pg_attribute a ON (a.attrelid=c.oid) " +
" JOIN pg_catalog.pg_type t ON (a.atttypid = t.oid) " +
" LEFT JOIN pg_catalog.pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) " +
" LEFT JOIN pg_catalog.pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid) " +
" LEFT JOIN pg_catalog.pg_class dc ON (dc.oid=dsc.classoid AND dc.relname='pg_class') " +
" LEFT JOIN pg_catalog.pg_namespace dn ON (dc.relnamespace=dn.oid AND dn.nspname='pg_catalog') " +
" WHERE a.attnum > 0 AND NOT a.attisdropped ");
if (!isNullOrEmpty(schemaPattern)) {
sql.append(" AND n.nspname LIKE ?");
params.add(schemaPattern);
}
if (!isNullOrEmpty(tableNamePattern)) {
sql.append(" AND c.relname LIKE ?");
params.add(tableNamePattern);
}
sql.append(") c");
if (!isNullOrEmpty(columnNamePattern)) {
sql.append(" WHERE attname LIKE ?");
params.add(columnNamePattern);
}
sql.append(" ORDER BY nspname,c.relname,attnum ");
//Build list of column fields and data
List columnsData = new ArrayList<>();
try (ResultSet rs = execForResultSet(sql.toString(), params)) {
while (rs.next()) {
ColumnData columnData = new ColumnData();
columnData.tableSchemaName = rs.getString("nspname");
columnData.tableName = rs.getString("relname");
columnData.relationType = registry.loadRelationType(rs.getInt("attrelid"));
columnData.relationAttrNum = rs.getInt("attnum");
columnData.columnName = rs.getString("attname");
columnData.type = registry.loadType(rs.getInt("atttypid"));
columnData.typeModifier = rs.getInt("atttypmod");
columnData.typeLength = rs.getInt("attlen");
columnData.nullable = !rs.getBoolean("attnotnull");
columnData.defaultValue = rs.getString("adsrc");
columnData.description = rs.getString("description");
columnData.baseType = registry.loadType(rs.getInt("typbasetype"));
columnsData.add(columnData);
}
}
//Build result set (manually)
ResultField[] resultFields = new ResultField[24];
resultFields[0] = new ResultField("TABLE_CAT", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[1] = new ResultField("TABLE_SCHEM", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[2] = new ResultField("TABLE_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[3] = new ResultField("COLUMN_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[4] = new ResultField("DATA_TYPE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[5] = new ResultField("TYPE_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[6] = new ResultField("COLUMN_SIZE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[7] = new ResultField("BUFFER_LENGTH", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[8] = new ResultField("DECIMAL_DIGITS", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[9] = new ResultField("NUM_PREC_RADIX", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[10] = new ResultField("NULLABLE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[11] = new ResultField("REMARKS", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[12] = new ResultField("COLUMN_DEF", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[13] = new ResultField("SQL_DATA_TYPE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[14] = new ResultField("SQL_DATETIME_SUB", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[15] = new ResultField("CHAR_OCTET_LENGTH", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[16] = new ResultField("ORDINAL_POSITION", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[17] = new ResultField("IS_NULLABLE", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[18] = new ResultField("SCOPE_CATLOG", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[19] = new ResultField("SCOPE_SCHEMA", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[20] = new ResultField("SCOPE_TABLE", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[21] = new ResultField("SOURCE_DATA_TYPE", 0, (short)0, registry.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[22] = new ResultField("IS_AUTOINCREMENT", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[23] = new ResultField("IS_GENERATEDCOLUMN", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
List results = new ArrayList<>();
for (int c = 0; c < columnsData.size(); ++c) {
ColumnData columnData = columnsData.get(c);
Object[] row = new Object[resultFields.length];
row[0] = null;
row[1] = columnData.tableSchemaName;
row[2] = columnData.tableName;
row[3] = columnData.columnName;
row[4] = SQLTypeMetaData.getSQLType(columnData.type);
row[5] = SQLTypeMetaData.getTypeName(columnData.type, columnData.relationType, columnData.relationAttrNum);
int size = SQLTypeMetaData.getPrecision(columnData.type, columnData.typeLength, columnData.typeModifier);
if (size == 0) {
size = SQLTypeMetaData.getDisplaySize(columnData.type, columnData.typeLength, columnData.typeModifier);
}
row[6] = size;
row[7] = null;
row[8] = SQLTypeMetaData.getScale(columnData.type, columnData.typeLength, columnData.typeModifier);
row[9] = SQLTypeMetaData.getPrecisionRadix(columnData.type);
row[10] = SQLTypeMetaData.isNullable(columnData.type, columnData.relationType, columnData.relationAttrNum);
row[11] = columnData.description;
row[12] = columnData.defaultValue;
row[13] = null;
row[14] = null;
row[15] = columnData.typeLength;
row[16] = columnData.relationAttrNum;
String nullable = null;
switch((int)row[10]) {
case columnNoNulls:
nullable = "NO";
break;
case columnNullable:
nullable = "YES";
break;
default:
nullable = "";
break;
}
row[17] = nullable;
row[18] = null;
row[19] = null;
row[20] = null;
row[21] = columnData.baseType != null ? SQLTypeMetaData.getSQLType(columnData.baseType) : null;
row[22] = SQLTypeMetaData.isAutoIncrement(columnData.type, columnData.relationType, columnData.relationAttrNum) ? "YES" : "NO";
row[23] = columnData.relationType != null ? "YES" : "NO";
results.add(row);
}
return createResultSet(Arrays.asList(resultFields), results);
}
@Override
public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException {
if (table == null) {
table = "%";
}
if (columnNamePattern == null) {
columnNamePattern = "%";
}
Registry reg = connection.getRegistry();
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT" +
" n.nspname,c.relname,r.rolname,c.relacl,a.attacl,a.attname " +
"FROM" +
" pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_roles r, pg_catalog.pg_attribute a " +
"WHERE " +
" c.relnamespace = n.oid AND c.relowner = r.oid AND c.oid = a.attrelid AND c.relkind = 'r' AND a.attnum > 0 AND NOT a.attisdropped ");
if (!isNullOrEmpty(schema)) {
sql.append(" AND n.nspname = ?");
params.add(schema);
}
sql.append(" AND c.relname = ?");
params.add(table);
if (!isNullOrEmpty(columnNamePattern)) {
sql.append(" AND a.attname LIKE ?");
params.add(columnNamePattern);
}
sql.append(" ORDER BY attname");
ResultField[] fields = new ResultField[8];
fields[0] = new ResultField("TABLE_CAT", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[1] = new ResultField("TABLE_SCHEM", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[2] = new ResultField("TABLE_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[3] = new ResultField("COLUMN_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[4] = new ResultField("GRANTOR", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[5] = new ResultField("GRANTEE", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[6] = new ResultField("PRIVILEGE", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[7] = new ResultField("IS_GRANTABLE", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
List results = new ArrayList<>();
try (ResultSet rs = execForResultSet(sql.toString(), params)) {
while (rs.next()) {
String schemaName = rs.getString("nspname");
String tableName = rs.getString("relname");
String column = rs.getString("attname");
String owner = rs.getString("rolname");
Map>> privileges = new HashMap<>();
mapACLPrivileges(owner, rs.getObject("relacl", ACLItem[].class), privileges);
mapACLPrivileges(owner, rs.getObject("attacl", ACLItem[].class), privileges);
//Gather list privelege names
String[] privNames = new String[privileges.size()];
Iterator e = privileges.keySet().iterator();
int i = 0;
while (e.hasNext()) {
privNames[i++] = e.next();
}
Arrays.sort(privNames);
for (String privName : privNames) {
Map> grantees = privileges.get(privName);
//Gather list of grantee users
String[] granteeUsers = new String[grantees.size()];
Iterator g = grantees.keySet().iterator();
int k = 0;
while (g.hasNext()) {
granteeUsers[k++] = g.next();
}
//Add row for grantee
for (int j = 0; j < grantees.size(); j++) {
List grantors = grantees.get(granteeUsers[j]);
String grantee = granteeUsers[j];
for (int l = 0; l < grantors.size(); l++) {
String[] grants = grantors.get(l);
String grantor = isNullOrEmpty(grants[0]) ? owner : grants[0];
String grantable = owner.equals(grantee) ? "YES" : grants[1];
Object[] row = new Object[8];
row[0] = null;
row[1] = schemaName;
row[2] = tableName;
row[3] = column;
row[4] = grantor;
row[5] = grantee;
row[6] = privName;
row[7] = grantable;
results.add(row);
}
}
}
}
}
return createResultSet(Arrays.asList(fields), results);
}
@Override
public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
Registry reg = connection.getRegistry();
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT" +
" n.nspname,c.relname,r.rolname,c.relacl::text[] " +
"FROM" +
" pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_roles r " +
"WHERE" +
" c.relnamespace = n.oid AND c.relowner = r.oid AND c.relkind = 'r' ");
if (!isNullOrEmpty(schemaPattern)) {
sql.append(" AND n.nspname LIKE ?");
params.add(schemaPattern);
}
if (!isNullOrEmpty(tableNamePattern)) {
sql.append(" AND c.relname LIKE ?");
params.add(tableNamePattern);
}
sql.append(" ORDER BY nspname, relname ");
ResultField[] fields = new ResultField[7];
fields[0] = new ResultField("TABLE_CAT", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[1] = new ResultField("TABLE_SCHEM", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[2] = new ResultField("TABLE_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[3] = new ResultField("GRANTOR", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[4] = new ResultField("GRANTEE", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[5] = new ResultField("PRIVILEGE", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
fields[6] = new ResultField("IS_GRANTABLE", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
List results = new ArrayList<>();
try (ResultSet rs = execForResultSet(sql.toString(), params)) {
while (rs.next()) {
String schema = rs.getString("nspname");
String table = rs.getString("relname");
String owner = rs.getString("rolname");
Map>> privileges = new HashMap<>();
mapACLPrivileges(owner, rs.getObject("relacl", ACLItem[].class), privileges);
String[] privNames = new String[privileges.size()];
Iterator e = privileges.keySet().iterator();
int i = 0;
while (e.hasNext()) {
privNames[i++] = e.next();
}
Arrays.sort(privNames);
for (String privName : privNames) {
Map> grantees = privileges.get(privName);
//Gather list of grantee users
String[] granteeUsers = new String[grantees.size()];
Iterator g = grantees.keySet().iterator();
int k = 0;
while (g.hasNext()) {
granteeUsers[k++] = g.next();
}
for (int j = 0; j < granteeUsers.length; j++) {
List grantors = grantees.get(granteeUsers[j]);
String grantee = granteeUsers[j];
for (int l = 0; l < grantors.size(); l++) {
String[] grants = grantors.get(l);
String grantor = isNullOrEmpty(grants[0]) ? owner : grants[0];
String grantable = owner.equals(grantee) ? "YES" : grants[1];
Object[] row = new Object[7];
row[0] = null;
row[1] = schema;
row[2] = table;
row[3] = grantor;
row[4] = grantee;
row[5] = privName;
row[6] = grantable;
results.add(row);
}
}
}
}
}
return createResultSet(Arrays.asList(fields), results);
}
private void mapACLPrivileges(String owner, ACLItem[] aclItems, Map>> privileges) {
if (aclItems == null) {
//Null is shortcut for owner having full privileges
ACLItem fullPrivs = new ACLItem(owner, "arwdDxt", owner);
aclItems = new ACLItem[] {fullPrivs};
}
for (ACLItem aclItem : aclItems) {
for (int i = 0; i < aclItem.privileges.length(); i++) {
char c = aclItem.privileges.charAt(i);
if (c != '*') {
String sqlpriv;
String grantable;
if (i < aclItem.privileges.length() - 1 && aclItem.privileges.charAt(i + 1) == '*') {
grantable = "YES";
}
else {
grantable = "NO";
}
switch(c) {
case 'a':
sqlpriv = "INSERT";
break;
case 'r':
sqlpriv = "SELECT";
break;
case 'w':
sqlpriv = "UPDATE";
break;
case 'd':
sqlpriv = "DELETE";
break;
case 'D':
sqlpriv = "TRUNCATE";
break;
case 'R':
sqlpriv = "RULE";
break;
case 'x':
sqlpriv = "REFERENCES";
break;
case 't':
sqlpriv = "TRIGGER";
break;
case 'X':
sqlpriv = "EXECUTE";
break;
case 'U':
sqlpriv = "USAGE";
break;
case 'C':
sqlpriv = "CREATE";
break;
case 'T':
sqlpriv = "CREATE TEMP";
break;
default:
sqlpriv = "UNKNOWN";
}
Map> usersWithPermission = privileges.get(sqlpriv);
if (usersWithPermission == null) {
usersWithPermission = new HashMap<>();
privileges.put(sqlpriv, usersWithPermission);
}
List permissionByGrantor = usersWithPermission.get(aclItem.user);
if (permissionByGrantor == null) {
permissionByGrantor = new ArrayList<>();
usersWithPermission.put(aclItem.user, permissionByGrantor);
}
permissionByGrantor.add(new String[] {aclItem.grantor, grantable});
}
}
}
}
@Override
public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException {
Registry reg = connection.getRegistry();
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT a.attname, a.atttypid, attlen, atttypmod " +
"FROM pg_catalog.pg_class ct " +
" JOIN pg_catalog.pg_attribute a ON (ct.oid = a.attrelid) " +
" JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid) " +
" JOIN (SELECT i.indexrelid, i.indrelid, i.indisprimary, " +
" information_schema._pg_expandarray(i.indkey) AS keys " +
" FROM pg_catalog.pg_index i) i " +
" ON (a.attnum = (i.keys).x AND a.attrelid = i.indrelid) ");
if (!isNullOrEmpty(schema)) {
sql.append(" WHERE n.nspname = ?");
params.add(schema);
}
ResultField[] resultFields = new ResultField[8];
List results = new ArrayList<>();
resultFields[0] = new ResultField("SCOPE", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[1] = new ResultField("COLUMN_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[2] = new ResultField("DATA_TYPE", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[3] = new ResultField("TYPE_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[4] = new ResultField("COLUMN_SIZE", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[5] = new ResultField("BUFFER_LENGTH", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[6] = new ResultField("DECIMAL_DIGITS", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[7] = new ResultField("PSEUDO_COLUMN", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
try (ResultSet rs = execForResultSet(sql.toString(), params)) {
while (rs.next()) {
Object[] row = new Object[8];
Type type = reg.loadType(rs.getInt("atttypid"));
int typeLen = rs.getInt("attlen");
int typeMod = rs.getInt("atttypmod");
int decimalDigits = SQLTypeMetaData.getScale(type, typeLen, typeMod);
int columnSize = SQLTypeMetaData.getPrecision(type, typeLen, typeMod);
if (columnSize == 0) {
columnSize = SQLTypeMetaData.getDisplaySize(type, typeLen, typeMod);
}
row[0] = scope;
row[1] = rs.getString("attname");
row[2] = SQLTypeMetaData.getSQLType(type);
row[3] = SQLTypeMetaData.getTypeName(type, null, 0);
row[4] = columnSize;
row[5] = null; // unused
row[6] = decimalDigits;
row[7] = DatabaseMetaData.bestRowNotPseudo;
results.add(row);
}
}
return createResultSet(Arrays.asList(resultFields), results);
}
@Override
public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException {
Registry reg = connection.getRegistry();
ResultField[] resultFields = new ResultField[8];
List results = new ArrayList<>();
resultFields[0] = new ResultField("SCOPE", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[1] = new ResultField("COLUMN_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[2] = new ResultField("DATA_TYPE", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[3] = new ResultField("TYPE_NAME", 0, (short)0, reg.loadType("text"), (short)0, 0, Format.Binary);
resultFields[4] = new ResultField("COLUMN_SIZE", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[5] = new ResultField("BUFFER_LENGTH", 0, (short)0, reg.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[6] = new ResultField("DECIMAL_DIGITS", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[7] = new ResultField("PSEUDO_COLUMN", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
Object[] row = new Object[8];
/* Postgresql does not have any column types that are
* automatically updated like some databases' timestamp type.
* We can't tell what rules or triggers might be doing, so we
* are left with the system columns that change on an update.
* An update may change all of the following system columns:
* ctid, xmax, xmin, cmax, and cmin. Depending on if we are
* in a transaction and wether we roll it back or not the
* only guaranteed change is to ctid. -KJ
*/
Type type = reg.loadType("tid");
row[0] = null;
row[1] = "ctid";
row[2] = SQLTypeMetaData.getSQLType(type);
row[3] = SQLTypeMetaData.getTypeName(type, null, 0);
row[4] = null;
row[5] = null;
row[6] = null;
row[7] = DatabaseMetaData.versionColumnPseudo;
results.add(row);
return createResultSet(Arrays.asList(resultFields), results);
}
@Override
public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, " +
" ct.relname AS TABLE_NAME, a.attname AS COLUMN_NAME, " +
" (i.keys).n AS KEY_SEQ, ci.relname AS PK_NAME " +
"FROM pg_catalog.pg_class ct " +
" JOIN pg_catalog.pg_attribute a ON (ct.oid = a.attrelid) " +
" JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid) " +
" JOIN (SELECT i.indexrelid, i.indrelid, i.indisprimary, " +
" information_schema._pg_expandarray(i.indkey) AS keys " +
" FROM pg_catalog.pg_index i) i " +
" ON (a.attnum = (i.keys).x AND a.attrelid = i.indrelid) " +
" JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid) ");
if (!isNullOrEmpty(schema)) {
sql.append(" WHERE n.nspname = ?");
params.add(schema);
}
return execForResultSet(sql.toString(), params);
}
protected ResultSet getImportedExportedKeys(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException
{
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT NULL::text AS PKTABLE_CAT, pkn.nspname AS PKTABLE_SCHEM, pkc.relname AS PKTABLE_NAME, pka.attname AS PKCOLUMN_NAME, " +
"NULL::text AS FKTABLE_CAT, fkn.nspname AS FKTABLE_SCHEM, fkc.relname AS FKTABLE_NAME, fka.attname AS FKCOLUMN_NAME, " +
"pos.n AS KEY_SEQ, " +
"CASE con.confupdtype " +
" WHEN 'c' THEN " + DatabaseMetaData.importedKeyCascade +
" WHEN 'd' THEN " + DatabaseMetaData.importedKeySetDefault +
" WHEN 'n' THEN " + DatabaseMetaData.importedKeySetNull +
" WHEN 'r' THEN " + DatabaseMetaData.importedKeyRestrict +
" WHEN 'a' THEN " + DatabaseMetaData.importedKeyNoAction +
" ELSE NULL END AS UPDATE_RULE, " +
"CASE con.confdeltype " +
" WHEN 'c' THEN " + DatabaseMetaData.importedKeyCascade +
" WHEN 'n' THEN " + DatabaseMetaData.importedKeySetNull +
" WHEN 'd' THEN " + DatabaseMetaData.importedKeySetDefault +
" WHEN 'r' THEN " + DatabaseMetaData.importedKeyRestrict +
" WHEN 'a' THEN " + DatabaseMetaData.importedKeyNoAction +
" ELSE NULL END AS DELETE_RULE, " +
"con.conname AS FK_NAME, pkic.relname AS PK_NAME, " +
"CASE " +
" WHEN con.condeferrable AND con.condeferred THEN " + DatabaseMetaData.importedKeyInitiallyDeferred +
" WHEN con.condeferrable THEN " + DatabaseMetaData.importedKeyInitiallyImmediate +
" ELSE " + DatabaseMetaData.importedKeyNotDeferrable +
" END AS DEFERRABILITY " +
" FROM " +
" pg_catalog.pg_namespace pkn, pg_catalog.pg_class pkc, pg_catalog.pg_attribute pka, " +
" pg_catalog.pg_namespace fkn, pg_catalog.pg_class fkc, pg_catalog.pg_attribute fka, " +
" pg_catalog.pg_constraint con, " +
" pg_catalog.generate_series(1, " + getMaxIndexKeys() + ") pos(n), " +
" pg_catalog.pg_depend dep, pg_catalog.pg_class pkic " +
" WHERE pkn.oid = pkc.relnamespace AND pkc.oid = pka.attrelid AND pka.attnum = con.confkey[pos.n] AND con.confrelid = pkc.oid " +
" AND fkn.oid = fkc.relnamespace AND fkc.oid = fka.attrelid AND fka.attnum = con.conkey[pos.n] AND con.conrelid = fkc.oid " +
" AND con.contype = 'f' AND con.oid = dep.objid AND pkic.oid = dep.refobjid AND pkic.relkind = 'i' AND dep.classid = 'pg_constraint'::regclass::oid AND dep.refclassid = 'pg_class'::regclass::oid ");
if (!isNullOrEmpty(primarySchema)) {
sql.append(" AND pkn.nspname = ?");
params.add(primarySchema);
}
if (!isNullOrEmpty(foreignSchema)) {
sql.append(" AND fkn.nspname = ?");
params.add(foreignSchema);
}
if (!isNullOrEmpty(primaryTable)) {
sql.append(" AND pkc.relname = ?");
params.add(primaryTable);
}
if (!isNullOrEmpty(foreignTable)) {
sql.append(" AND fkc.relname = ?");
params.add(foreignTable);
}
if (primaryTable != null) {
sql.append(" ORDER BY fkn.nspname,fkc.relname,pos.n");
}
else {
sql.append(" ORDER BY pkn.nspname,pkc.relname,pos.n");
}
return execForResultSet(sql.toString(), params);
}
@Override
public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
return getImportedExportedKeys(null, null, null, catalog, schema, table);
}
@Override
public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
return getImportedExportedKeys(catalog, schema, table, null, null, null);
}
@Override
public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException {
return getImportedExportedKeys(parentCatalog, parentSchema, parentTable, foreignCatalog, foreignSchema, foreignTable);
}
@Override
public ResultSet getTypeInfo() throws SQLException {
Registry registry = connection.getRegistry();
ResultField[] resultFields = new ResultField[18];
List results = new ArrayList<>();
resultFields[0] = new ResultField("TYPE_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[1] = new ResultField("DATA_TYPE", 0, (short)0, registry.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[2] = new ResultField("PRECISION", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[3] = new ResultField("LITERAL_PREFIX", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[4] = new ResultField("LITERAL_SUFFIX", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[5] = new ResultField("CREATE_PARAMS", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[6] = new ResultField("NULLABLE", 0, (short)0, registry.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[7] = new ResultField("CASE_SENSITIVE", 0, (short)0, registry.loadType("bool"), (short)0, 0, Format.Binary);
resultFields[8] = new ResultField("SEARCHABLE", 0, (short)0, registry.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[9] = new ResultField("UNSIGNED_ATTRIBUTE", 0, (short)0, registry.loadType("bool"), (short)0, 0, Format.Binary);
resultFields[10] = new ResultField("FIXED_PREC_SCALE", 0, (short)0, registry.loadType("bool"), (short)0, 0, Format.Binary);
resultFields[11] = new ResultField("AUTO_INCREMENT", 0, (short)0, registry.loadType("bool"), (short)0, 0, Format.Binary);
resultFields[12] = new ResultField("LOCAL_TYPE_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[13] = new ResultField("MINIMUM_SCALE", 0, (short)0, registry.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[14] = new ResultField("MAXIMUM_SCALE", 0, (short)0, registry.loadType("int2"), (short)0, 0, Format.Binary);
resultFields[15] = new ResultField("SQL_DATA_TYPE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[16] = new ResultField("SQL_DATETIME_SUB", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[17] = new ResultField("NUM_PREC_RADIX", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
String sql =
"SELECT t.typname,t.oid FROM pg_catalog.pg_type t" +
" JOIN pg_catalog.pg_namespace n ON (t.typnamespace = n.oid) " +
" WHERE n.nspname != 'pg_toast'";
ResultSet rs = execForResultSet(sql);
while (rs.next()) {
Object[] row = new Object[18];
int typeOid = rs.getInt(2);
Type type = registry.loadType(typeOid);
row[0] = SQLTypeMetaData.getTypeName(type, null, 0);
row[1] = SQLTypeMetaData.getSQLType(type);
row[2] = SQLTypeMetaData.getMaxPrecision(type);
if (SQLTypeMetaData.requiresQuoting(type)) {
row[3] = "\'";
row[4] = "\'";
}
row[6] = SQLTypeMetaData.isNullable(type, null, 0);
row[7] = SQLTypeMetaData.isCaseSensitive(type);
row[8] = true;
row[9] = !SQLTypeMetaData.isSigned(type);
row[10] = SQLTypeMetaData.isCurrency(type);
row[11] = SQLTypeMetaData.isAutoIncrement(type, null, 0);
row[13] = SQLTypeMetaData.getMinScale(type);
row[14] = SQLTypeMetaData.getMaxScale(type);
row[15] = null; //Unused
row[16] = null; //Unused
row[17] = SQLTypeMetaData.getPrecisionRadix(type);
results.add(row);
}
rs.close();
return createResultSet(Arrays.asList(resultFields), results);
}
@Override
public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException {
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, " +
" ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, " +
" NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME, " +
" CASE i.indisclustered " +
" WHEN true THEN " + java.sql.DatabaseMetaData.tableIndexClustered +
" ELSE CASE am.amname " +
" WHEN 'hash' THEN " + java.sql.DatabaseMetaData.tableIndexHashed +
" ELSE " + java.sql.DatabaseMetaData.tableIndexOther +
" END " +
" END AS TYPE, " +
" (i.keys).n AS ORDINAL_POSITION, " +
" pg_catalog.pg_get_indexdef(ci.oid, (i.keys).n, false) AS COLUMN_NAME, " +
" CASE am.amcanorder " +
" WHEN true THEN CASE i.indoption[(i.keys).n - 1] & 1 " +
" WHEN 1 THEN 'D' " +
" ELSE 'A' " +
" END " +
" ELSE NULL " +
" END AS ASC_OR_DESC, " +
" ci.reltuples AS CARDINALITY, " +
" ci.relpages AS PAGES, " +
" pg_catalog.pg_get_expr(i.indpred, i.indrelid) AS FILTER_CONDITION " +
"FROM pg_catalog.pg_class ct " +
" JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid) " +
" JOIN (SELECT i.indexrelid, i.indrelid, i.indoption, " +
" i.indisunique, i.indisclustered, i.indpred, " +
" i.indexprs, " +
" information_schema._pg_expandarray(i.indkey) AS keys " +
" FROM pg_catalog.pg_index i) i " +
" ON (ct.oid = i.indrelid) " +
" JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid) " +
" JOIN pg_catalog.pg_am am ON (ci.relam = am.oid) " +
"WHERE true ");
if (!isNullOrEmpty(schema)) {
sql.append(" AND n.nspname = ?");
params.add(schema);
}
sql.append(" AND ct.relname = ?");
params.add(table);
if (unique) {
sql.append(" AND i.indisunique ");
}
sql.append(" ORDER BY NON_UNIQUE, TYPE, INDEX_NAME, ORDINAL_POSITION ");
return execForResultSet(sql.toString(), params);
}
@Override
public boolean supportsResultSetType(int type) throws SQLException {
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return true;
default:
return false;
}
}
@Override
public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException {
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
//Support all types
return true;
default:
return false;
}
}
@Override
public boolean ownUpdatesAreVisible(int type) throws SQLException {
//TODO this depends of TX state & isolation level
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return false;
default:
return false;
}
}
@Override
public boolean ownDeletesAreVisible(int type) throws SQLException {
//TODO this depends of TX state & isolation level
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return false;
default:
return false;
}
}
@Override
public boolean ownInsertsAreVisible(int type) throws SQLException {
//TODO this depends of TX state & isolation level
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return false;
default:
return false;
}
}
@Override
public boolean othersUpdatesAreVisible(int type) throws SQLException {
//TODO this depends of TX state & isolation level
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return false;
default:
return false;
}
}
@Override
public boolean othersDeletesAreVisible(int type) throws SQLException {
//TODO this depends of TX state & isolation level
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return false;
default:
return false;
}
}
@Override
public boolean othersInsertsAreVisible(int type) throws SQLException {
//TODO this depends of TX state & isolation level
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return false;
default:
return false;
}
}
@Override
public boolean updatesAreDetected(int type) throws SQLException {
//TODO this depends of TX state & isolation level
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return false;
default:
return false;
}
}
@Override
public boolean deletesAreDetected(int type) throws SQLException {
//TODO this depends of TX state & isolation level
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return false;
default:
return false;
}
}
@Override
public boolean insertsAreDetected(int type) throws SQLException {
//TODO this depends of TX state & isolation level
switch(type) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
return false;
default:
return false;
}
}
@Override
public boolean supportsBatchUpdates() throws SQLException {
return true;
}
@Override
public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException {
Registry reg = connection.getRegistry();
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT " +
" NULL AS TYPE_CAT, n.nspname AS TYPE_SCHEM, t.typname AS TYPE_NAME, NULL AS CLASS_NAME, " +
" CASE WHEN t.typtype='c' THEN " + java.sql.Types.STRUCT + " ELSE " + java.sql.Types.DISTINCT + " END AS DATA_TYPE, " +
" pg_catalog.obj_description(t.oid, 'pg_type') AS REMARKS, " +
" typbasetype as BASE_TYPE_ID " +
"FROM" +
" pg_catalog.pg_type t, pg_catalog.pg_namespace n " +
"WHERE" +
" t.typnamespace = n.oid and n.nspname != 'pg_catalog' and n.nspname != 'pg_toast'");
if (types != null) {
sql.append(" AND (false ");
for (int i = 0; i < types.length; i++) {
switch(types[i]) {
case java.sql.Types.STRUCT:
sql.append(" or t.typtype = 'c'");
break;
case java.sql.Types.DISTINCT:
sql.append(" or t.typtype = 'd'");
break;
default:
break;
}
}
sql.append(" ) ");
}
else {
sql.append(" and t.typtype IN ('c','d') ");
}
// spec says that if typeNamePattern is a fully qualified name
// then the schema and catalog are ignored
if (typeNamePattern != null) {
// search for qualifier
int firstQualifier = typeNamePattern.indexOf('.');
int secondQualifier = typeNamePattern.lastIndexOf('.');
if (firstQualifier != -1) {
// if one of them is -1 they both will be
if (firstQualifier != secondQualifier) {
// we have a catalog.schema.typename, ignore catalog
schemaPattern = typeNamePattern.substring(firstQualifier + 1, secondQualifier);
}
else {
// we just have a schema.typename
schemaPattern = typeNamePattern.substring(0, firstQualifier);
}
// strip out just the typeName
typeNamePattern = typeNamePattern.substring(secondQualifier + 1);
}
sql.append(" AND t.typname LIKE ?");
params.add(typeNamePattern);
}
// schemaPattern may have been modified above
if (schemaPattern != null) {
sql.append(" AND n.nspname LIKE ?");
params.add(schemaPattern);
}
sql.append(" ORDER BY data_type, type_schem, type_name");
PGResultSet rs = execForResultSet(sql.toString(), params);
ResultField[] fields = new ResultField[7];
fields[0] = rs.getResultFields().get(0);
fields[1] = rs.getResultFields().get(1);
fields[2] = rs.getResultFields().get(2);
fields[3] = rs.getResultFields().get(3);
fields[4] = rs.getResultFields().get(4);
fields[5] = rs.getResultFields().get(5);
fields[6] = new ResultField("BASE_TYPE", 0, (short)0, reg.loadType("int2"), (short)0, 0, Format.Binary);
List results = new ArrayList<>();
while (rs.next()) {
Object[] row = new Object[7];
row[0] = rs.get(1);
row[1] = rs.get(2);
row[2] = rs.get(3);
row[3] = rs.get(4);
row[4] = rs.get(5);
row[5] = rs.get(6);
Type type = reg.loadType(rs.getInt(7));
if (type != null) {
row[6] = SQLTypeMetaData.getSQLType(type);
}
else {
row[6] = null;
}
results.add(row);
}
return createResultSet(Arrays.asList(fields), results);
}
static class AttributeData {
String typeSchemaName;
String typeName;
CompositeType relationType;
int relationAttrNum;
String attributeName;
Type type;
int typeModifier;
int typeLength;
Boolean nullable;
String description;
}
@Override
public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException {
Registry registry = connection.getRegistry();
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT * FROM (" +
" SELECT n.nspname,t.typname,a.attname,a.atttypid,a.attrelid,a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull,a.atttypmod,a.attlen," +
" row_number() OVER (PARTITION BY a.attrelid ORDER BY a.attnum) AS attnum, dsc.description" +
" FROM pg_catalog.pg_namespace n " +
" JOIN pg_catalog.pg_type t ON (t.typnamespace=n.oid) " +
" JOIN pg_catalog.pg_attribute a ON (a.attrelid=t.typrelid) " +
" LEFT JOIN pg_catalog.pg_description dsc ON (t.typrelid=dsc.objoid AND a.attnum = dsc.objsubid) " +
" LEFT JOIN pg_catalog.pg_class dc ON (dc.oid=dsc.classoid AND dc.relname='pg_class') " +
" LEFT JOIN pg_catalog.pg_namespace dn ON (dc.relnamespace=dn.oid AND dn.nspname='pg_catalog') " +
" WHERE a.attnum > 0 AND NOT a.attisdropped AND t.typtype='c'");
if (!isNullOrEmpty(schemaPattern)) {
sql.append(" AND n.nspname LIKE ?");
params.add(schemaPattern);
}
if (!isNullOrEmpty(typeNamePattern)) {
sql.append(" AND t.typname LIKE ?");
params.add(typeNamePattern);
}
sql.append(") c");
if (!isNullOrEmpty(attributeNamePattern)) {
sql.append(" WHERE attname LIKE ?");
params.add(attributeNamePattern);
}
sql.append(" ORDER BY nspname,c.typname,attnum ");
//Build list of column fields and data
List attrsData = new ArrayList<>();
try (ResultSet rs = execForResultSet(sql.toString(), params)) {
while (rs.next()) {
AttributeData attrData = new AttributeData();
attrData.typeSchemaName = rs.getString("nspname");
attrData.typeName = rs.getString("typname");
attrData.relationType = registry.loadRelationType(rs.getInt("attrelid"));
attrData.relationAttrNum = rs.getInt("attnum");
attrData.attributeName = rs.getString("attname");
attrData.type = registry.loadType(rs.getInt("atttypid"));
attrData.typeModifier = rs.getInt("atttypmod");
attrData.typeLength = rs.getInt("attlen");
attrData.nullable = !rs.getBoolean("attnotnull");
attrData.description = rs.getString("description");
attrsData.add(attrData);
}
}
//Build result set (manually)
ResultField[] resultFields = new ResultField[21];
resultFields[0] = new ResultField("TYPE_CAT", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[1] = new ResultField("TYPE_SCHEM", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[2] = new ResultField("TYPE_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[3] = new ResultField("ATTR_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[4] = new ResultField("DATA_TYPE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[5] = new ResultField("ATTR_TYPE_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[6] = new ResultField("ATTR_SIZE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[7] = new ResultField("DECIMAL_DIGITS", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[8] = new ResultField("NUM_PREC_RADIX", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[9] = new ResultField("NULLABLE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[10] = new ResultField("REMARKS", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[11] = new ResultField("ATTR_DEF", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[12] = new ResultField("SQL_DATA_TYPE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[13] = new ResultField("SQL_DATETIME_SUB", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[14] = new ResultField("CHAR_OCTET_LENGTH", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[15] = new ResultField("ORDINAL_POSITION", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[16] = new ResultField("IS_NULLABLE", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[17] = new ResultField("SCOPE_CATLOG", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[18] = new ResultField("SCOPE_SCHEMA", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[19] = new ResultField("SCOPE_TABLE", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[20] = new ResultField("SOURCE_DATA_TYPE", 0, (short)0, registry.loadType("int2"), (short)0, 0, Format.Binary);
List results = new ArrayList<>();
for (int c = 0; c < attrsData.size(); ++c) {
AttributeData attrData = attrsData.get(c);
Object[] row = new Object[resultFields.length];
row[0] = null;
row[1] = attrData.typeSchemaName;
row[2] = attrData.typeName;
row[3] = attrData.attributeName;
row[4] = SQLTypeMetaData.getSQLType(attrData.type);
row[5] = SQLTypeMetaData.getTypeName(attrData.type, attrData.relationType, attrData.relationAttrNum);
int size = SQLTypeMetaData.getPrecision(attrData.type, attrData.typeLength, attrData.typeModifier);
if (size == 0) {
size = SQLTypeMetaData.getDisplaySize(attrData.type, attrData.typeLength, attrData.typeModifier);
}
row[6] = size;
row[7] = SQLTypeMetaData.getScale(attrData.type, attrData.typeLength, attrData.typeModifier);
row[8] = SQLTypeMetaData.getPrecisionRadix(attrData.type);
row[9] = SQLTypeMetaData.isNullable(attrData.type, attrData.relationType, attrData.relationAttrNum);
row[10] = attrData.description;
row[11] = null;
row[12] = null;
row[13] = null;
row[14] = attrData.typeLength;
row[15] = attrData.relationAttrNum;
String nullable = null;
switch((int)row[9]) {
case attributeNoNulls:
nullable = "NO";
break;
case attributeNullable:
nullable = "YES";
break;
default:
nullable = "";
break;
}
row[16] = nullable;
row[17] = null;
row[18] = null;
row[19] = null;
row[20] = attrData.type instanceof DomainType ? SQLTypeMetaData.getSQLType(attrData.type.unwrap()) : null;
results.add(row);
}
return createResultSet(Arrays.asList(resultFields), results);
}
@Override
public Connection getConnection() throws SQLException {
return connection;
}
@Override
public boolean supportsSavepoints() throws SQLException {
return true;
}
@Override
public boolean supportsNamedParameters() throws SQLException {
return false;
}
@Override
public boolean supportsMultipleOpenResults() throws SQLException {
return true;
}
@Override
public boolean supportsGetGeneratedKeys() throws SQLException {
return true;
}
@Override
public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException {
throw NOT_IMPLEMENTED;
}
@Override
public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
throw NOT_IMPLEMENTED;
}
@Override
public boolean supportsResultSetHoldability(int holdability) throws SQLException {
switch(holdability) {
case ResultSet.CLOSE_CURSORS_AT_COMMIT:
case ResultSet.HOLD_CURSORS_OVER_COMMIT:
return true;
default:
return false;
}
}
@Override
public int getResultSetHoldability() throws SQLException {
return ResultSet.CLOSE_CURSORS_AT_COMMIT;
}
@Override
public int getDatabaseMajorVersion() throws SQLException {
return connection.getServerVersion().getMajor();
}
@Override
public int getDatabaseMinorVersion() throws SQLException {
return connection.getServerVersion().getMinor();
}
@Override
public int getJDBCMajorVersion() throws SQLException {
return 4;
}
@Override
public int getJDBCMinorVersion() throws SQLException {
return 1;
}
@Override
public int getSQLStateType() throws SQLException {
return DatabaseMetaData.sqlStateSQL;
}
@Override
public boolean locatorsUpdateCopy() throws SQLException {
return false;
}
@Override
public boolean supportsStatementPooling() throws SQLException {
// TODO Not for now...
return false;
}
@Override
public RowIdLifetime getRowIdLifetime() throws SQLException {
return RowIdLifetime.ROWID_VALID_SESSION;
}
@Override
public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {
return true;
}
@Override
public boolean autoCommitFailureClosesAllResultSets() throws SQLException {
return false;
}
@Override
public ResultSet getClientInfoProperties() throws SQLException {
Registry registry = connection.getRegistry();
ResultField[] resultFields = new ResultField[18];
List results = new ArrayList<>();
resultFields[0] = new ResultField("NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[1] = new ResultField("MAX_LEN", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[2] = new ResultField("DEFAULT_VALUE", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[3] = new ResultField("DESCRIPTION", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
Object[] row = new Object[4];
//ApplicationName
row[0] = "ApplicationName";
row[1] = -1;
row[2] = "pgjdbc app";
row[3] = "Name of application using the connection";
results.add(row);
return createResultSet(asList(resultFields), results);
}
@Override
public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException {
throw NOT_IMPLEMENTED;
}
@Override
public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException {
throw NOT_IMPLEMENTED;
}
@Override
public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException {
Registry registry = connection.getRegistry();
StringBuilder sql = new StringBuilder();
List params = new ArrayList<>();
sql.append(
"SELECT * FROM (" +
" SELECT n.nspname,c.relname,a.attname,a.atttypid,a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull,a.atttypmod,a.attlen,a.attrelid," +
" row_number() OVER (PARTITION BY a.attrelid ORDER BY a.attnum) AS attnum, pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS adsrc,dsc.description,t.typbasetype,t.typtype " +
" FROM pg_catalog.pg_namespace n " +
" JOIN pg_catalog.pg_class c ON (c.relnamespace = n.oid) " +
" JOIN pg_catalog.pg_attribute a ON (a.attrelid=c.oid) " +
" JOIN pg_catalog.pg_type t ON (a.atttypid = t.oid) " +
" LEFT JOIN pg_catalog.pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) " +
" LEFT JOIN pg_catalog.pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid) " +
" LEFT JOIN pg_catalog.pg_class dc ON (dc.oid=dsc.classoid AND dc.relname='pg_class') " +
" LEFT JOIN pg_catalog.pg_namespace dn ON (dc.relnamespace=dn.oid AND dn.nspname='pg_catalog') " +
" WHERE a.attnum < 1 AND NOT a.attisdropped ");
if (!isNullOrEmpty(schemaPattern)) {
sql.append(" AND n.nspname LIKE ?");
params.add(schemaPattern);
}
if (!isNullOrEmpty(tableNamePattern)) {
sql.append(" AND c.relname LIKE ?");
params.add(tableNamePattern);
}
sql.append(") c");
if (!isNullOrEmpty(columnNamePattern)) {
sql.append(" WHERE attname LIKE ?");
params.add(columnNamePattern);
}
sql.append(" ORDER BY nspname,c.relname,attnum ");
//Build list of column fields and data
List columnsData = new ArrayList<>();
try (ResultSet rs = execForResultSet(sql.toString(), params)) {
while (rs.next()) {
ColumnData columnData = new ColumnData();
columnData.tableSchemaName = rs.getString("nspname");
columnData.tableName = rs.getString("relname");
columnData.relationType = registry.loadRelationType(rs.getInt("attrelid"));
columnData.relationAttrNum = rs.getInt("attnum");
columnData.columnName = rs.getString("attname");
columnData.type = registry.loadType(rs.getInt("atttypid"));
columnData.typeModifier = rs.getInt("atttypmod");
columnData.typeLength = rs.getInt("attlen");
columnData.nullable = !rs.getBoolean("attnotnull");
columnData.defaultValue = rs.getString("adsrc");
columnData.description = rs.getString("description");
columnData.baseType = registry.loadType(rs.getInt("typbasetype"));
columnsData.add(columnData);
}
}
//Build result set (manually)
ResultField[] resultFields = new ResultField[24];
resultFields[0] = new ResultField("TABLE_CAT", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[1] = new ResultField("TABLE_SCHEM", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[2] = new ResultField("TABLE_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[3] = new ResultField("COLUMN_NAME", 0, (short)0, registry.loadType("text"), (short)0, 0, Format.Binary);
resultFields[4] = new ResultField("DATA_TYPE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[5] = new ResultField("COLUMN_SIZE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[6] = new ResultField("DECIMAL_DIGITS", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[7] = new ResultField("NUM_PREC_RADIX", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[8] = new ResultField("COLUMN_USAGE", 0, (short)0, registry.loadType("int4"), (short)0, 0, Format.Binary);
resultFields[9] = new ResultField("REMARKS", 0, (short) 0, registry.loadType("text"), (short) 0, 0, Format.Binary);
resultFields[10] = new ResultField("CHAR_OCTET_LENGTH", 0, (short) 0, registry.loadType("int4"), (short) 0, 0, Format.Binary);
resultFields[11] = new ResultField("IS_NULLABLE", 0, (short) 0, registry.loadType("text"), (short) 0, 0, Format.Binary);
List results = new ArrayList<>();
for (int c = 0; c < columnsData.size(); ++c) {
ColumnData columnData = columnsData.get(c);
Object[] row = new Object[resultFields.length];
row[0] = null;
row[1] = columnData.tableSchemaName;
row[2] = columnData.tableName;
row[3] = columnData.columnName;
row[4] = SQLTypeMetaData.getSQLType(columnData.type);
int size = SQLTypeMetaData.getPrecision(columnData.type, columnData.typeLength, columnData.typeModifier);
if (size == 0) {
size = SQLTypeMetaData.getDisplaySize(columnData.type, columnData.typeLength, columnData.typeModifier);
}
row[5] = size;
row[6] = SQLTypeMetaData.getScale(columnData.type, columnData.typeLength, columnData.typeModifier);
row[7] = SQLTypeMetaData.getPrecisionRadix(columnData.type);
row[8] = PseudoColumnUsage.NO_USAGE_RESTRICTIONS.name();
row[9] = columnData.description;
row[10] = columnData.typeLength;
String nullable = null;
int isNullable = SQLTypeMetaData.isNullable(columnData.type, columnData.relationType, columnData.relationAttrNum);
switch (isNullable) {
case columnNoNulls:
nullable = "NO";
break;
case columnNullable:
nullable = "YES";
break;
default:
nullable = "";
break;
}
row[11] = nullable;
results.add(row);
}
return createResultSet(Arrays.asList(resultFields), results);
}
@Override
public boolean generatedKeyAlwaysReturned() throws SQLException {
return false;
}
}