All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.cloud.spanner.jdbc.JdbcDatabaseMetaData Maven / Gradle / Ivy

There is a newer version: 2.25.2
Show newest version
/*
 * Copyright 2019 Google LLC
 *
 * Licensed 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 com.google.cloud.spanner.jdbc;

import com.google.auth.Credentials;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.oauth2.UserCredentials;
import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.ResultSets;
import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.Type;
import com.google.cloud.spanner.Type.StructField;
import com.google.cloud.spanner.connection.Connection.InternalMetadataQuery;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.RowIdLifetime;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;
import java.util.Scanner;

/** {@link DatabaseMetaData} implementation for Cloud Spanner */
class JdbcDatabaseMetaData extends AbstractJdbcWrapper implements DatabaseMetaData {
  private static final int JDBC_MAJOR_VERSION = 4;
  private static final int JDBC_MINOR_VERSION = 1;
  private static final int DATABASE_MAJOR_VERSION = 1;
  private static final int DATABASE_MINOR_VERSION = 0;
  private static final String PRODUCT_NAME = "Google Cloud Spanner";

  @VisibleForTesting
  static String readSqlFromFile(String filename, Dialect dialect) {
    InputStream in;
    switch (dialect) {
      case POSTGRESQL:
        in = JdbcDatabaseMetaData.class.getResourceAsStream("postgresql/" + filename);
        break;
      case GOOGLE_STANDARD_SQL:
      default:
        in = JdbcDatabaseMetaData.class.getResourceAsStream(filename);
    }
    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
    StringBuilder builder = new StringBuilder();
    try (Scanner scanner = new Scanner(reader)) {
      while (scanner.hasNextLine()) {
        String line = scanner.nextLine();
        builder.append(line).append("\n");
      }
    }
    return builder.toString();
  }

  private final JdbcConnection connection;

  JdbcDatabaseMetaData(JdbcConnection connection) {
    this.connection = connection;
  }

  @Override
  public boolean isClosed() {
    return false;
  }

  @Override
  public boolean allProceduresAreCallable() {
    return true;
  }

  @Override
  public boolean allTablesAreSelectable() {
    return true;
  }

  @Override
  public String getURL() {
    return connection.getConnectionUrl();
  }

  @Override
  public String getUserName() {
    Credentials credentials = connection.getConnectionOptions().getCredentials();
    if (credentials != null) {
      if (credentials instanceof ServiceAccountSigner) {
        return ((ServiceAccountSigner) credentials).getAccount();
      } else if (credentials instanceof UserCredentials) {
        return ((UserCredentials) credentials).getClientId();
      }
    }
    return "";
  }

  @Override
  public boolean isReadOnly() {
    return false;
  }

  @Override
  public boolean nullsAreSortedHigh() {
    return false;
  }

  @Override
  public boolean nullsAreSortedLow() {
    return true;
  }

  @Override
  public boolean nullsAreSortedAtStart() {
    return false;
  }

  @Override
  public boolean nullsAreSortedAtEnd() {
    return false;
  }

  @Override
  public String getDatabaseProductName() {
    return PRODUCT_NAME;
  }

  @Override
  public String getDatabaseProductVersion() {
    return getDatabaseMajorVersion() + "." + getDatabaseMinorVersion();
  }

  @Override
  public String getDriverName() {
    return JdbcDriver.class.getName();
  }

  @Override
  public String getDriverVersion() {
    return getDriverMajorVersion() + "." + getDriverMinorVersion();
  }

  @Override
  public int getDriverMajorVersion() {
    return JdbcDriver.MAJOR_VERSION;
  }

  @Override
  public int getDriverMinorVersion() {
    return JdbcDriver.MINOR_VERSION;
  }

  @Override
  public boolean usesLocalFiles() {
    return false;
  }

  @Override
  public boolean usesLocalFilePerTable() {
    return false;
  }

  @Override
  public boolean supportsMixedCaseIdentifiers() {
    return false;
  }

  @Override
  public boolean storesUpperCaseIdentifiers() {
    return false;
  }

  @Override
  public boolean storesLowerCaseIdentifiers() {
    return false;
  }

  @Override
  public boolean storesMixedCaseIdentifiers() {
    return true;
  }

  @Override
  public boolean supportsMixedCaseQuotedIdentifiers() {
    return false;
  }

  @Override
  public boolean storesUpperCaseQuotedIdentifiers() {
    return false;
  }

  @Override
  public boolean storesLowerCaseQuotedIdentifiers() {
    return false;
  }

  @Override
  public boolean storesMixedCaseQuotedIdentifiers() {
    return true;
  }

  @Override
  public String getIdentifierQuoteString() {
    return "`";
  }

  @Override
  public String getSQLKeywords() {
    return "ASSERT_ROWS_MODIFIED,ENUM,GROUPS,HASH,IGNORE,LOOKUP,PROTO,RESPECT,STRUCT,WINDOW";
  }

  @Override
  public String getNumericFunctions() {
    return "ABS,SIGN,IS_INF,IS_NAN,IEEE_DIVIDE,SQRT,POW,POWER,EXP,LN,LOG,LOG10,GREATEST,LEAST,DIV,MOD,ROUND,TRUNC,CEIL,CEILING,FLOOR,COS,COSH,ACOS,ACOSH,SIN,SINH,ASIN,ASINH,TAN,TANH,ATAN,ATANH,ATAN2,FARM_FINGERPRINT,SHA1,SHA256,SHA512";
  }

  @Override
  public String getStringFunctions() {
    return "BYTE_LENGTH,CHAR_LENGTH,CHARACTER_LENGTH,CODE_POINTS_TO_BYTES,CODE_POINTS_TO_STRING,CONCAT,ENDS_WITH,FORMAT,FROM_BASE64,FROM_HEX,LENGTH,LPAD,LOWER,LTRIM,REGEXP_CONTAINS,REGEXP_EXTRACT,REGEXP_EXTRACT_ALL,REGEXP_REPLACE,REPLACE,REPEAT,REVERSE,RPAD,RTRIM,SAFE_CONVERT_BYTES_TO_STRING,SPLIT,STARTS_WITH,STRPOS,SUBSTR,TO_BASE64,TO_CODE_POINTS,TO_HEX,TRIM,UPPER,JSON_QUERY,JSON_VALUE";
  }

  @Override
  public String getSystemFunctions() {
    return "";
  }

  @Override
  public String getTimeDateFunctions() {
    return "CURRENT_DATE,EXTRACT,DATE,DATE_ADD,DATE_SUB,DATE_DIFF,DATE_TRUNC,DATE_FROM_UNIX_DATE,FORMAT_DATE,PARSE_DATE,UNIX_DATE,CURRENT_TIMESTAMP,STRING,TIMESTAMP,TIMESTAMP_ADD,TIMESTAMP_SUB,TIMESTAMP_DIFF,TIMESTAMP_TRUNC,FORMAT_TIMESTAMP,PARSE_TIMESTAMP,TIMESTAMP_SECONDS,TIMESTAMP_MILLIS,TIMESTAMP_MICROS,UNIX_SECONDS,UNIX_MILLIS,UNIX_MICROS";
  }

  @Override
  public String getSearchStringEscape() {
    return "\\";
  }

  @Override
  public String getExtraNameCharacters() {
    return "";
  }

  @Override
  public boolean supportsAlterTableWithAddColumn() {
    return true;
  }

  @Override
  public boolean supportsAlterTableWithDropColumn() {
    return true;
  }

  @Override
  public boolean supportsColumnAliasing() {
    return true;
  }

  @Override
  public boolean nullPlusNonNullIsNull() {
    return true;
  }

  @Override
  public boolean supportsConvert() {
    return false;
  }

  @Override
  public boolean supportsConvert(int fromType, int toType) {
    return false;
  }

  @Override
  public boolean supportsTableCorrelationNames() {
    return true;
  }

  @Override
  public boolean supportsDifferentTableCorrelationNames() {
    return false;
  }

  @Override
  public boolean supportsExpressionsInOrderBy() {
    return true;
  }

  @Override
  public boolean supportsOrderByUnrelated() {
    return true;
  }

  @Override
  public boolean supportsGroupBy() {
    return true;
  }

  @Override
  public boolean supportsGroupByUnrelated() {
    return true;
  }

  @Override
  public boolean supportsGroupByBeyondSelect() {
    return true;
  }

  @Override
  public boolean supportsLikeEscapeClause() {
    return true;
  }

  @Override
  public boolean supportsMultipleResultSets() {
    return true;
  }

  @Override
  public boolean supportsMultipleTransactions() {
    return true;
  }

  @Override
  public boolean supportsNonNullableColumns() {
    return true;
  }

  @Override
  public boolean supportsMinimumSQLGrammar() {
    return false;
  }

  @Override
  public boolean supportsCoreSQLGrammar() {
    return false;
  }

  @Override
  public boolean supportsExtendedSQLGrammar() {
    return false;
  }

  @Override
  public boolean supportsANSI92EntryLevelSQL() {
    return false;
  }

  @Override
  public boolean supportsANSI92IntermediateSQL() {
    return false;
  }

  @Override
  public boolean supportsANSI92FullSQL() {
    return false;
  }

  @Override
  public boolean supportsIntegrityEnhancementFacility() {
    return false;
  }

  @Override
  public boolean supportsOuterJoins() {
    return true;
  }

  @Override
  public boolean supportsFullOuterJoins() {
    return true;
  }

  @Override
  public boolean supportsLimitedOuterJoins() {
    return true;
  }

  @Override
  public String getSchemaTerm() {
    return "SCHEMA";
  }

  @Override
  public String getProcedureTerm() {
    return "PROCEDURE";
  }

  @Override
  public String getCatalogTerm() {
    // Spanner does not support catalogs, but the term is included for compatibility with the SQL
    // standard
    return "CATALOG";
  }

  @Override
  public boolean isCatalogAtStart() {
    return false;
  }

  @Override
  public String getCatalogSeparator() {
    return ".";
  }

  @Override
  public boolean supportsSchemasInDataManipulation() {
    return true;
  }

  @Override
  public boolean supportsSchemasInProcedureCalls() {
    return true;
  }

  @Override
  public boolean supportsSchemasInTableDefinitions() {
    return true;
  }

  @Override
  public boolean supportsSchemasInIndexDefinitions() {
    return true;
  }

  @Override
  public boolean supportsSchemasInPrivilegeDefinitions() {
    return true;
  }

  @Override
  public boolean supportsCatalogsInDataManipulation() {
    return false;
  }

  @Override
  public boolean supportsCatalogsInProcedureCalls() {
    return false;
  }

  @Override
  public boolean supportsCatalogsInTableDefinitions() {
    return false;
  }

  @Override
  public boolean supportsCatalogsInIndexDefinitions() {
    return false;
  }

  @Override
  public boolean supportsCatalogsInPrivilegeDefinitions() {
    return false;
  }

  @Override
  public boolean supportsPositionedDelete() {
    return false;
  }

  @Override
  public boolean supportsPositionedUpdate() {
    return false;
  }

  @Override
  public boolean supportsSelectForUpdate() {
    return false;
  }

  @Override
  public boolean supportsStoredProcedures() {
    return false;
  }

  @Override
  public boolean supportsSubqueriesInComparisons() {
    return true;
  }

  @Override
  public boolean supportsSubqueriesInExists() {
    return true;
  }

  @Override
  public boolean supportsSubqueriesInIns() {
    return true;
  }

  @Override
  public boolean supportsSubqueriesInQuantifieds() {
    return true;
  }

  @Override
  public boolean supportsCorrelatedSubqueries() {
    return true;
  }

  @Override
  public boolean supportsUnion() {
    // Note that Cloud Spanner requires the user to specify 'UNION DISTINCT' or 'UNION ALL' in a
    // query. 'UNION DISTINCT' is equal to the SQL operation 'UNION'.
    return true;
  }

  @Override
  public boolean supportsUnionAll() {
    return true;
  }

  @Override
  public boolean supportsOpenCursorsAcrossCommit() {
    return false;
  }

  @Override
  public boolean supportsOpenCursorsAcrossRollback() {
    return false;
  }

  @Override
  public boolean supportsOpenStatementsAcrossCommit() {
    return true;
  }

  @Override
  public boolean supportsOpenStatementsAcrossRollback() {
    return true;
  }

  @Override
  public int getMaxBinaryLiteralLength() {
    return 0;
  }

  @Override
  public int getMaxCharLiteralLength() {
    return 0;
  }

  @Override
  public int getMaxColumnNameLength() {
    return 128;
  }

  @Override
  public int getMaxColumnsInGroupBy() {
    return 1000;
  }

  @Override
  public int getMaxColumnsInIndex() {
    return 16;
  }

  @Override
  public int getMaxColumnsInOrderBy() {
    return 0;
  }

  @Override
  public int getMaxColumnsInSelect() {
    return 0;
  }

  @Override
  public int getMaxColumnsInTable() {
    return 1024;
  }

  @Override
  public int getMaxConnections() {
    // there is a max number of sessions, but that is not the same as the max number of connections
    return 0;
  }

  @Override
  public int getMaxCursorNameLength() {
    return 0;
  }

  @Override
  public int getMaxIndexLength() {
    return 8000;
  }

  @Override
  public int getMaxSchemaNameLength() {
    return 128;
  }

  @Override
  public int getMaxProcedureNameLength() {
    return 0;
  }

  @Override
  public int getMaxCatalogNameLength() {
    return 0;
  }

  @Override
  public int getMaxRowSize() {
    // The limit is 1024 columns per table * 10MB per column, which is more than fits in an int.
    // We therefore return 0 to indicate no limit (or an unknown limit).
    return 0;
  }

  @Override
  public boolean doesMaxRowSizeIncludeBlobs() {
    return true;
  }

  @Override
  public int getMaxStatementLength() {
    return 1000000;
  }

  @Override
  public int getMaxStatements() {
    return 0;
  }

  @Override
  public int getMaxTableNameLength() {
    return 128;
  }

  @Override
  public int getMaxTablesInSelect() {
    return 0;
  }

  @Override
  public int getMaxUserNameLength() {
    return 0;
  }

  @Override
  public int getDefaultTransactionIsolation() {
    return Connection.TRANSACTION_SERIALIZABLE;
  }

  @Override
  public boolean supportsTransactions() {
    return true;
  }

  @Override
  public boolean supportsTransactionIsolationLevel(int level) {
    return Connection.TRANSACTION_SERIALIZABLE == level;
  }

  @Override
  public boolean supportsDataDefinitionAndDataManipulationTransactions() {
    return false;
  }

  @Override
  public boolean supportsDataManipulationTransactionsOnly() {
    return true;
  }

  @Override
  public boolean dataDefinitionCausesTransactionCommit() {
    return false;
  }

  @Override
  public boolean dataDefinitionIgnoredInTransactions() {
    return false;
  }

  @Override
  public ResultSet getProcedures(
      String catalog, String schemaPattern, String procedureNamePattern) {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("PROCEDURE_CAT", Type.string()),
                StructField.of("PROCEDURE_SCHEM", Type.string()),
                StructField.of("PROCEDURE_NAME", Type.string()),
                StructField.of("reserved1", Type.string()),
                StructField.of("reserved2", Type.string()),
                StructField.of("reserved3", Type.string()),
                StructField.of("REMARKS", Type.string()),
                StructField.of("PROCEDURE_TYPE", Type.int64()),
                StructField.of("SPECIFIC_NAME", Type.string())),
            Collections.emptyList()));
  }

  @Override
  public ResultSet getProcedureColumns(
      String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("PROCEDURE_CAT", Type.string()),
                StructField.of("PROCEDURE_SCHEM", Type.string()),
                StructField.of("PROCEDURE_NAME", Type.string()),
                StructField.of("COLUMN_NAME", Type.string()),
                StructField.of("COLUMN_TYPE", Type.int64()),
                StructField.of("DATA_TYPE", Type.int64()),
                StructField.of("TYPE_NAME", Type.string()),
                StructField.of("PRECISION", Type.string()),
                StructField.of("LENGTH", Type.int64()),
                StructField.of("SCALE", Type.int64()),
                StructField.of("RADIX", Type.int64()),
                StructField.of("NULLABLE", Type.int64()),
                StructField.of("REMARKS", Type.string()),
                StructField.of("COLUMN_DEF", Type.string()),
                StructField.of("SQL_DATA_TYPE", Type.int64()),
                StructField.of("SQL_DATETIME_SUB", Type.int64()),
                StructField.of("CHAR_OCTET_LENGTH", Type.int64()),
                StructField.of("ORDINAL_POSITION", Type.int64()),
                StructField.of("IS_NULLABLE", Type.string()),
                StructField.of("SPECIFIC_NAME", Type.string())),
            Collections.emptyList()));
  }

  private JdbcPreparedStatement prepareStatementReplaceNullWithAnyString(
      String sql, String... params) throws SQLException {
    JdbcPreparedStatement statement = connection.prepareStatement(sql);
    int paramIndex = 1;
    for (String param : params) {
      if (param == null) {
        statement.setString(paramIndex, "%");
      } else {
        statement.setString(paramIndex, param.toUpperCase());
      }
      paramIndex++;
    }
    return statement;
  }

  @Override
  public ResultSet getTables(
      String catalog, String schemaPattern, String tableNamePattern, String[] types)
      throws SQLException {
    String sql = readSqlFromFile("DatabaseMetaData_GetTables.sql", connection.getDialect());
    String type1;
    String type2;
    if (types == null || types.length == 0) {
      type1 = "TABLE";
      type2 = "VIEW";
    } else if (types.length == 1) {
      type1 = types[0];
      type2 = "NON_EXISTENT_TYPE";
    } else {
      type1 = types[0];
      type2 = types[1];
    }
    JdbcPreparedStatement statement =
        prepareStatementReplaceNullWithAnyString(
            sql, catalog, schemaPattern, tableNamePattern, type1, type2);
    return statement.executeQueryWithOptions(InternalMetadataQuery.INSTANCE);
  }

  @Override
  public ResultSet getSchemas() throws SQLException {
    return getSchemas(null, null);
  }

  @Override
  public ResultSet getCatalogs() throws SQLException {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(StructField.of("TABLE_CAT", Type.string())),
            Collections.singletonList(
                Struct.newBuilder().set("TABLE_CAT").to(getConnection().getCatalog()).build())));
  }

  @Override
  public ResultSet getTableTypes() {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(StructField.of("TABLE_TYPE", Type.string())),
            Arrays.asList(
                Struct.newBuilder().set("TABLE_TYPE").to("TABLE").build(),
                Struct.newBuilder().set("TABLE_TYPE").to("VIEW").build())));
  }

  @Override
  public ResultSet getColumns(
      String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)
      throws SQLException {
    String sql = readSqlFromFile("DatabaseMetaData_GetColumns.sql", connection.getDialect());
    JdbcPreparedStatement statement =
        prepareStatementReplaceNullWithAnyString(
            sql, catalog, schemaPattern, tableNamePattern, columnNamePattern);
    return statement.executeQueryWithOptions(InternalMetadataQuery.INSTANCE);
  }

  @Override
  public ResultSet getColumnPrivileges(
      String catalog, String schema, String table, String columnNamePattern) {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("TABLE_CAT", Type.string()),
                StructField.of("TABLE_SCHEM", Type.string()),
                StructField.of("TABLE_NAME", Type.string()),
                StructField.of("COLUMN_NAME", Type.string()),
                StructField.of("GRANTOR", Type.string()),
                StructField.of("GRANTEE", Type.string()),
                StructField.of("PRIVILEGE", Type.string()),
                StructField.of("IS_GRANTABLE", Type.string())),
            Collections.emptyList()));
  }

  @Override
  public ResultSet getTablePrivileges(
      String catalog, String schemaPattern, String tableNamePattern) {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("TABLE_CAT", Type.string()),
                StructField.of("TABLE_SCHEM", Type.string()),
                StructField.of("TABLE_NAME", Type.string()),
                StructField.of("GRANTOR", Type.string()),
                StructField.of("GRANTEE", Type.string()),
                StructField.of("PRIVILEGE", Type.string()),
                StructField.of("IS_GRANTABLE", Type.string())),
            Collections.emptyList()));
  }

  @Override
  public ResultSet getBestRowIdentifier(
      String catalog, String schema, String table, int scope, boolean nullable) {
    return getEmptyColumnsResultSet();
  }

  @Override
  public ResultSet getVersionColumns(String catalog, String schema, String table) {
    return getEmptyColumnsResultSet();
  }

  private ResultSet getEmptyColumnsResultSet() {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("SCOPE", Type.int64()),
                StructField.of("COLUMN_NAME", Type.string()),
                StructField.of("DATA_TYPE", Type.int64()),
                StructField.of("TYPE_NAME", Type.string()),
                StructField.of("COLUMN_SIZE", Type.int64()),
                StructField.of("BUFFER_LENGTH", Type.int64()),
                StructField.of("DECIMAL_DIGITS", Type.int64()),
                StructField.of("PSEUDO_COLUMN", Type.int64())),
            Collections.emptyList()));
  }

  @Override
  public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
    JdbcPreconditions.checkArgument(table != null, "table may not be null");
    String sql = readSqlFromFile("DatabaseMetaData_GetPrimaryKeys.sql", connection.getDialect());
    JdbcPreparedStatement statement =
        prepareStatementReplaceNullWithAnyString(sql, catalog, schema, table);
    return statement.executeQueryWithOptions(InternalMetadataQuery.INSTANCE);
  }

  @Override
  public ResultSet getImportedKeys(String catalog, String schema, String table)
      throws SQLException {
    JdbcPreconditions.checkArgument(table != null, "table may not be null");
    String sql = readSqlFromFile("DatabaseMetaData_GetImportedKeys.sql", connection.getDialect());
    JdbcPreparedStatement statement =
        prepareStatementReplaceNullWithAnyString(sql, catalog, schema, table);
    return statement.executeQueryWithOptions(InternalMetadataQuery.INSTANCE);
  }

  @Override
  public ResultSet getExportedKeys(String catalog, String schema, String table)
      throws SQLException {
    JdbcPreconditions.checkArgument(table != null, "table may not be null");
    String sql = readSqlFromFile("DatabaseMetaData_GetExportedKeys.sql", connection.getDialect());
    JdbcPreparedStatement statement =
        prepareStatementReplaceNullWithAnyString(sql, catalog, schema, table);
    return statement.executeQueryWithOptions(InternalMetadataQuery.INSTANCE);
  }

  @Override
  public ResultSet getCrossReference(
      String parentCatalog,
      String parentSchema,
      String parentTable,
      String foreignCatalog,
      String foreignSchema,
      String foreignTable)
      throws SQLException {
    String sql =
        readSqlFromFile("DatabaseMetaData_GetCrossReferences.sql", connection.getDialect());
    JdbcPreparedStatement statement =
        prepareStatementReplaceNullWithAnyString(
            sql,
            parentCatalog,
            parentSchema,
            parentTable,
            foreignCatalog,
            foreignSchema,
            foreignTable);
    return statement.executeQueryWithOptions(InternalMetadataQuery.INSTANCE);
  }

  @Override
  public ResultSet getTypeInfo() {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("TYPE_NAME", Type.string()),
                StructField.of("DATA_TYPE", Type.int64()),
                StructField.of("PRECISION", Type.int64()),
                StructField.of("LITERAL_PREFIX", Type.string()),
                StructField.of("LITERAL_SUFFIX", Type.string()),
                StructField.of("CREATE_PARAMS", Type.string()),
                StructField.of("NULLABLE", Type.int64()),
                StructField.of("CASE_SENSITIVE", Type.bool()),
                StructField.of("SEARCHABLE", Type.int64()),
                StructField.of("UNSIGNED_ATTRIBUTE", Type.bool()),
                StructField.of("FIXED_PREC_SCALE", Type.bool()),
                StructField.of("AUTO_INCREMENT", Type.bool()),
                StructField.of("LOCAL_TYPE_NAME", Type.string()),
                StructField.of("MINIMUM_SCALE", Type.int64()),
                StructField.of("MAXIMUM_SCALE", Type.int64()),
                StructField.of("SQL_DATA_TYPE", Type.int64()),
                StructField.of("SQL_DATETIME_SUB", Type.int64()),
                StructField.of("NUM_PREC_RADIX", Type.int64())),
            Arrays.asList(
                // TODO(#925): Make these dialect-dependent (i.e. 'timestamptz' for PostgreSQL.
                Struct.newBuilder()
                    .set("TYPE_NAME")
                    .to("STRING")
                    .set("DATA_TYPE")
                    .to(Types.NVARCHAR) // -9
                    .set("PRECISION")
                    .to(2621440L)
                    .set("LITERAL_PREFIX")
                    .to((String) null)
                    .set("LITERAL_SUFFIX")
                    .to((String) null)
                    .set("CREATE_PARAMS")
                    .to("(length)")
                    .set("NULLABLE")
                    .to(DatabaseMetaData.typeNullable)
                    .set("CASE_SENSITIVE")
                    .to(true)
                    .set("SEARCHABLE")
                    .to(DatabaseMetaData.typeSearchable)
                    .set("UNSIGNED_ATTRIBUTE")
                    .to(true)
                    .set("FIXED_PREC_SCALE")
                    .to(false)
                    .set("AUTO_INCREMENT")
                    .to(false)
                    .set("LOCAL_TYPE_NAME")
                    .to("STRING")
                    .set("MINIMUM_SCALE")
                    .to(0)
                    .set("MAXIMUM_SCALE")
                    .to(0)
                    .set("SQL_DATA_TYPE")
                    .to((Long) null)
                    .set("SQL_DATETIME_SUB")
                    .to((Long) null)
                    .set("NUM_PREC_RADIX")
                    .to((Long) null)
                    .build(),
                Struct.newBuilder()
                    .set("TYPE_NAME")
                    .to("INT64")
                    .set("DATA_TYPE")
                    .to(Types.BIGINT) // -5
                    .set("PRECISION")
                    .to(19L)
                    .set("LITERAL_PREFIX")
                    .to((String) null)
                    .set("LITERAL_SUFFIX")
                    .to((String) null)
                    .set("CREATE_PARAMS")
                    .to((String) null)
                    .set("NULLABLE")
                    .to(DatabaseMetaData.typeNullable)
                    .set("CASE_SENSITIVE")
                    .to(false)
                    .set("SEARCHABLE")
                    .to(DatabaseMetaData.typePredBasic)
                    .set("UNSIGNED_ATTRIBUTE")
                    .to(false)
                    .set("FIXED_PREC_SCALE")
                    .to(false)
                    .set("AUTO_INCREMENT")
                    .to(false)
                    .set("LOCAL_TYPE_NAME")
                    .to("INT64")
                    .set("MINIMUM_SCALE")
                    .to(0)
                    .set("MAXIMUM_SCALE")
                    .to(0)
                    .set("SQL_DATA_TYPE")
                    .to((Long) null)
                    .set("SQL_DATETIME_SUB")
                    .to((Long) null)
                    .set("NUM_PREC_RADIX")
                    .to(10)
                    .build(),
                Struct.newBuilder()
                    .set("TYPE_NAME")
                    .to("BYTES")
                    .set("DATA_TYPE")
                    .to(Types.BINARY) // -2
                    .set("PRECISION")
                    .to(10485760L)
                    .set("LITERAL_PREFIX")
                    .to((String) null)
                    .set("LITERAL_SUFFIX")
                    .to((String) null)
                    .set("CREATE_PARAMS")
                    .to("(length)")
                    .set("NULLABLE")
                    .to(DatabaseMetaData.typeNullable)
                    .set("CASE_SENSITIVE")
                    .to(false)
                    .set("SEARCHABLE")
                    .to(DatabaseMetaData.typePredBasic)
                    .set("UNSIGNED_ATTRIBUTE")
                    .to(true)
                    .set("FIXED_PREC_SCALE")
                    .to(false)
                    .set("AUTO_INCREMENT")
                    .to(false)
                    .set("LOCAL_TYPE_NAME")
                    .to("BYTES")
                    .set("MINIMUM_SCALE")
                    .to(0)
                    .set("MAXIMUM_SCALE")
                    .to(0)
                    .set("SQL_DATA_TYPE")
                    .to((Long) null)
                    .set("SQL_DATETIME_SUB")
                    .to((Long) null)
                    .set("NUM_PREC_RADIX")
                    .to((Long) null)
                    .build(),
                Struct.newBuilder()
                    .set("TYPE_NAME")
                    .to("FLOAT32")
                    .set("DATA_TYPE")
                    .to(Types.REAL) // 8
                    .set("PRECISION")
                    .to(7L)
                    .set("LITERAL_PREFIX")
                    .to((String) null)
                    .set("LITERAL_SUFFIX")
                    .to((String) null)
                    .set("CREATE_PARAMS")
                    .to((String) null)
                    .set("NULLABLE")
                    .to(DatabaseMetaData.typeNullable)
                    .set("CASE_SENSITIVE")
                    .to(false)
                    .set("SEARCHABLE")
                    .to(DatabaseMetaData.typePredBasic)
                    .set("UNSIGNED_ATTRIBUTE")
                    .to(false)
                    .set("FIXED_PREC_SCALE")
                    .to(false)
                    .set("AUTO_INCREMENT")
                    .to(false)
                    .set("LOCAL_TYPE_NAME")
                    .to("FLOAT32")
                    .set("MINIMUM_SCALE")
                    .to(0)
                    .set("MAXIMUM_SCALE")
                    .to(0)
                    .set("SQL_DATA_TYPE")
                    .to((Long) null)
                    .set("SQL_DATETIME_SUB")
                    .to((Long) null)
                    .set("NUM_PREC_RADIX")
                    .to(2)
                    .build(),
                Struct.newBuilder()
                    .set("TYPE_NAME")
                    .to("FLOAT64")
                    .set("DATA_TYPE")
                    .to(Types.DOUBLE) // 8
                    .set("PRECISION")
                    .to(15L)
                    .set("LITERAL_PREFIX")
                    .to((String) null)
                    .set("LITERAL_SUFFIX")
                    .to((String) null)
                    .set("CREATE_PARAMS")
                    .to((String) null)
                    .set("NULLABLE")
                    .to(DatabaseMetaData.typeNullable)
                    .set("CASE_SENSITIVE")
                    .to(false)
                    .set("SEARCHABLE")
                    .to(DatabaseMetaData.typePredBasic)
                    .set("UNSIGNED_ATTRIBUTE")
                    .to(false)
                    .set("FIXED_PREC_SCALE")
                    .to(false)
                    .set("AUTO_INCREMENT")
                    .to(false)
                    .set("LOCAL_TYPE_NAME")
                    .to("FLOAT64")
                    .set("MINIMUM_SCALE")
                    .to(0)
                    .set("MAXIMUM_SCALE")
                    .to(0)
                    .set("SQL_DATA_TYPE")
                    .to((Long) null)
                    .set("SQL_DATETIME_SUB")
                    .to((Long) null)
                    .set("NUM_PREC_RADIX")
                    .to(2)
                    .build(),
                Struct.newBuilder()
                    .set("TYPE_NAME")
                    .to("BOOL")
                    .set("DATA_TYPE")
                    .to(Types.BOOLEAN) // 16
                    .set("PRECISION")
                    .to((Long) null)
                    .set("LITERAL_PREFIX")
                    .to((String) null)
                    .set("LITERAL_SUFFIX")
                    .to((String) null)
                    .set("CREATE_PARAMS")
                    .to((String) null)
                    .set("NULLABLE")
                    .to(DatabaseMetaData.typeNullable)
                    .set("CASE_SENSITIVE")
                    .to(false)
                    .set("SEARCHABLE")
                    .to(DatabaseMetaData.typePredBasic)
                    .set("UNSIGNED_ATTRIBUTE")
                    .to(true)
                    .set("FIXED_PREC_SCALE")
                    .to(false)
                    .set("AUTO_INCREMENT")
                    .to(false)
                    .set("LOCAL_TYPE_NAME")
                    .to("BOOL")
                    .set("MINIMUM_SCALE")
                    .to(0)
                    .set("MAXIMUM_SCALE")
                    .to(0)
                    .set("SQL_DATA_TYPE")
                    .to((Long) null)
                    .set("SQL_DATETIME_SUB")
                    .to((Long) null)
                    .set("NUM_PREC_RADIX")
                    .to((Long) null)
                    .build(),
                Struct.newBuilder()
                    .set("TYPE_NAME")
                    .to("DATE")
                    .set("DATA_TYPE")
                    .to(Types.DATE) // 91
                    .set("PRECISION")
                    .to(10L)
                    .set("LITERAL_PREFIX")
                    .to("DATE ")
                    .set("LITERAL_SUFFIX")
                    .to((String) null)
                    .set("CREATE_PARAMS")
                    .to((String) null)
                    .set("NULLABLE")
                    .to(DatabaseMetaData.typeNullable)
                    .set("CASE_SENSITIVE")
                    .to(false)
                    .set("SEARCHABLE")
                    .to(DatabaseMetaData.typePredBasic)
                    .set("UNSIGNED_ATTRIBUTE")
                    .to(true)
                    .set("FIXED_PREC_SCALE")
                    .to(false)
                    .set("AUTO_INCREMENT")
                    .to(false)
                    .set("LOCAL_TYPE_NAME")
                    .to("DATE")
                    .set("MINIMUM_SCALE")
                    .to(0)
                    .set("MAXIMUM_SCALE")
                    .to(0)
                    .set("SQL_DATA_TYPE")
                    .to((Long) null)
                    .set("SQL_DATETIME_SUB")
                    .to((Long) null)
                    .set("NUM_PREC_RADIX")
                    .to((Long) null)
                    .build(),
                Struct.newBuilder()
                    .set("TYPE_NAME")
                    .to("TIMESTAMP")
                    .set("DATA_TYPE")
                    .to(Types.TIMESTAMP) // 93
                    .set("PRECISION")
                    .to(35L)
                    .set("LITERAL_PREFIX")
                    .to("TIMESTAMP ")
                    .set("LITERAL_SUFFIX")
                    .to((String) null)
                    .set("CREATE_PARAMS")
                    .to((String) null)
                    .set("NULLABLE")
                    .to(DatabaseMetaData.typeNullable)
                    .set("CASE_SENSITIVE")
                    .to(false)
                    .set("SEARCHABLE")
                    .to(DatabaseMetaData.typePredBasic)
                    .set("UNSIGNED_ATTRIBUTE")
                    .to(true)
                    .set("FIXED_PREC_SCALE")
                    .to(false)
                    .set("AUTO_INCREMENT")
                    .to(false)
                    .set("LOCAL_TYPE_NAME")
                    .to("TIMESTAMP")
                    .set("MINIMUM_SCALE")
                    .to(0)
                    .set("MAXIMUM_SCALE")
                    .to(0)
                    .set("SQL_DATA_TYPE")
                    .to((Long) null)
                    .set("SQL_DATETIME_SUB")
                    .to((Long) null)
                    .set("NUM_PREC_RADIX")
                    .to((Long) null)
                    .build(),
                Struct.newBuilder()
                    .set("TYPE_NAME")
                    .to("NUMERIC")
                    .set("DATA_TYPE")
                    .to(Types.NUMERIC) // 2
                    .set("PRECISION")
                    .to(2621440L)
                    .set("LITERAL_PREFIX")
                    .to((String) null)
                    .set("LITERAL_SUFFIX")
                    .to((String) null)
                    .set("CREATE_PARAMS")
                    .to((String) null)
                    .set("NULLABLE")
                    .to(DatabaseMetaData.typeNullable)
                    .set("CASE_SENSITIVE")
                    .to(false)
                    .set("SEARCHABLE")
                    .to(DatabaseMetaData.typePredBasic)
                    .set("UNSIGNED_ATTRIBUTE")
                    .to(false)
                    .set("FIXED_PREC_SCALE")
                    .to(false)
                    .set("AUTO_INCREMENT")
                    .to(false)
                    .set("LOCAL_TYPE_NAME")
                    .to("NUMERIC")
                    .set("MINIMUM_SCALE")
                    .to(0)
                    .set("MAXIMUM_SCALE")
                    .to(0)
                    .set("SQL_DATA_TYPE")
                    .to((Long) null)
                    .set("SQL_DATETIME_SUB")
                    .to((Long) null)
                    .set("NUM_PREC_RADIX")
                    .to(10)
                    .build(),
                getJsonType(connection.getDialect()))),
        // Allow column 2 to be cast to short without any range checks.
        ImmutableSet.of(2));
  }

  private Struct getJsonType(Dialect dialect) {
    return Struct.newBuilder()
        .set("TYPE_NAME")
        .to(dialect == Dialect.POSTGRESQL ? "JSONB" : "JSON")
        .set("DATA_TYPE")
        .to(
            dialect == Dialect.POSTGRESQL
                ? PgJsonbType.VENDOR_TYPE_NUMBER
                : JsonType.VENDOR_TYPE_NUMBER)
        .set("PRECISION")
        .to(2621440L)
        .set("LITERAL_PREFIX")
        .to((String) null)
        .set("LITERAL_SUFFIX")
        .to((String) null)
        .set("CREATE_PARAMS")
        .to((String) null)
        .set("NULLABLE")
        .to(DatabaseMetaData.typeNullable)
        .set("CASE_SENSITIVE")
        .to(true)
        .set("SEARCHABLE")
        .to(DatabaseMetaData.typeSearchable)
        .set("UNSIGNED_ATTRIBUTE")
        .to(true)
        .set("FIXED_PREC_SCALE")
        .to(false)
        .set("AUTO_INCREMENT")
        .to(false)
        .set("LOCAL_TYPE_NAME")
        .to(dialect == Dialect.POSTGRESQL ? "JSONB" : "JSON")
        .set("MINIMUM_SCALE")
        .to(0)
        .set("MAXIMUM_SCALE")
        .to(0)
        .set("SQL_DATA_TYPE")
        .to((Long) null)
        .set("SQL_DATETIME_SUB")
        .to((Long) null)
        .set("NUM_PREC_RADIX")
        .to((Long) null)
        .build();
  }

  @Override
  public ResultSet getIndexInfo(
      String catalog, String schema, String table, boolean unique, boolean approximate)
      throws SQLException {
    return getIndexInfo(catalog, schema, table, null, unique);
  }

  public ResultSet getIndexInfo(String catalog, String schema, String indexName)
      throws SQLException {
    return getIndexInfo(catalog, schema, null, indexName, false);
  }

  private ResultSet getIndexInfo(
      String catalog, String schema, String table, String indexName, boolean unique)
      throws SQLException {
    String sql = readSqlFromFile("DatabaseMetaData_GetIndexInfo.sql", connection.getDialect());
    JdbcPreparedStatement statement =
        prepareStatementReplaceNullWithAnyString(
            sql, catalog, schema, table, indexName, unique ? "YES" : "%");
    return statement.executeQueryWithOptions(InternalMetadataQuery.INSTANCE);
  }

  @Override
  public boolean supportsResultSetType(int type) {
    return type == ResultSet.TYPE_FORWARD_ONLY;
  }

  @Override
  public boolean supportsResultSetConcurrency(int type, int concurrency) {
    return type == ResultSet.TYPE_FORWARD_ONLY && concurrency == ResultSet.CONCUR_READ_ONLY;
  }

  @Override
  public boolean ownUpdatesAreVisible(int type) {
    return false;
  }

  @Override
  public boolean ownDeletesAreVisible(int type) {
    return false;
  }

  @Override
  public boolean ownInsertsAreVisible(int type) {
    return false;
  }

  @Override
  public boolean othersUpdatesAreVisible(int type) {
    return false;
  }

  @Override
  public boolean othersDeletesAreVisible(int type) {
    return false;
  }

  @Override
  public boolean othersInsertsAreVisible(int type) {
    return false;
  }

  @Override
  public boolean updatesAreDetected(int type) {
    return false;
  }

  @Override
  public boolean deletesAreDetected(int type) {
    return false;
  }

  @Override
  public boolean insertsAreDetected(int type) {
    return false;
  }

  @Override
  public boolean supportsBatchUpdates() {
    return true;
  }

  @Override
  public ResultSet getUDTs(
      String catalog, String schemaPattern, String typeNamePattern, int[] types) {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("TYPE_CAT", Type.string()),
                StructField.of("TYPE_SCHEM", Type.string()),
                StructField.of("TYPE_NAME", Type.string()),
                StructField.of("CLASS_NAME", Type.string()),
                StructField.of("DATA_TYPE", Type.int64()),
                StructField.of("REMARKS", Type.string()),
                StructField.of("BASE_TYPE", Type.int64())),
            Collections.emptyList()));
  }

  @Override
  public Connection getConnection() {
    return connection;
  }

  @Override
  public boolean supportsSavepoints() {
    return false;
  }

  @Override
  public boolean supportsNamedParameters() {
    return false;
  }

  @Override
  public boolean supportsMultipleOpenResults() {
    return true;
  }

  @Override
  public boolean supportsGetGeneratedKeys() {
    return false;
  }

  @Override
  public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("TYPE_CAT", Type.string()),
                StructField.of("TYPE_SCHEM", Type.string()),
                StructField.of("TYPE_NAME", Type.string()),
                StructField.of("SUPERTYPE_CAT", Type.string()),
                StructField.of("SUPERTYPE_SCHEM", Type.string()),
                StructField.of("SUPERTYPE_NAME", Type.string())),
            Collections.emptyList()));
  }

  @Override
  public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("TABLE_CAT", Type.string()),
                StructField.of("TABLE_SCHEM", Type.string()),
                StructField.of("TABLE_NAME", Type.string()),
                StructField.of("SUPERTABLE_NAME", Type.string())),
            Collections.emptyList()));
  }

  @Override
  public ResultSet getAttributes(
      String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("TYPE_CAT", Type.string()),
                StructField.of("TYPE_SCHEM", Type.string()),
                StructField.of("TYPE_NAME", Type.string()),
                StructField.of("ATTR_NAME", Type.string()),
                StructField.of("DATA_TYPE", Type.int64()),
                StructField.of("ATTR_TYPE_NAME", Type.string()),
                StructField.of("ATTR_SIZE", Type.int64()),
                StructField.of("DECIMAL_DIGITS", Type.int64()),
                StructField.of("NUM_PREC_RADIX", Type.int64()),
                StructField.of("NULLABLE", Type.int64()),
                StructField.of("REMARKS", Type.string()),
                StructField.of("ATTR_DEF", Type.string()),
                StructField.of("SQL_DATA_TYPE", Type.int64()),
                StructField.of("SQL_DATETIME_SUB", Type.int64()),
                StructField.of("CHAR_OCTET_LENGTH", Type.int64()),
                StructField.of("ORDINAL_POSITION", Type.int64()),
                StructField.of("IS_NULLABLE", Type.string()),
                StructField.of("SCOPE_CATALOG", Type.string()),
                StructField.of("SCOPE_SCHEMA", Type.string()),
                StructField.of("SCOPE_TABLE", Type.string()),
                StructField.of("SOURCE_DATA_TYPE", Type.int64())),
            Collections.emptyList()));
  }

  @Override
  public boolean supportsResultSetHoldability(int holdability) {
    return holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT;
  }

  @Override
  public int getResultSetHoldability() {
    return ResultSet.CLOSE_CURSORS_AT_COMMIT;
  }

  @Override
  public int getDatabaseMajorVersion() {
    return DATABASE_MAJOR_VERSION;
  }

  @Override
  public int getDatabaseMinorVersion() {
    return DATABASE_MINOR_VERSION;
  }

  @Override
  public int getJDBCMajorVersion() {
    return JDBC_MAJOR_VERSION;
  }

  @Override
  public int getJDBCMinorVersion() {
    return JDBC_MINOR_VERSION;
  }

  @Override
  public int getSQLStateType() {
    return sqlStateSQL;
  }

  @Override
  public boolean locatorsUpdateCopy() {
    return true;
  }

  @Override
  public boolean supportsStatementPooling() {
    return false;
  }

  @Override
  public RowIdLifetime getRowIdLifetime() {
    return RowIdLifetime.ROWID_UNSUPPORTED;
  }

  @Override
  public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
    String sql = readSqlFromFile("DatabaseMetaData_GetSchemas.sql", connection.getDialect());
    try (JdbcPreparedStatement statement =
        prepareStatementReplaceNullWithAnyString(sql, catalog, schemaPattern)) {
      return statement.executeQueryWithOptions(InternalMetadataQuery.INSTANCE);
    }
  }

  @Override
  public boolean supportsStoredFunctionsUsingCallSyntax() {
    return false;
  }

  @Override
  public boolean autoCommitFailureClosesAllResultSets() {
    return false;
  }

  /**
   * The max length for client info values is 63 to make them fit in Cloud Spanner session labels.
   */
  static final int MAX_CLIENT_INFO_VALUE_LENGTH = 63;

  static Properties getDefaultClientInfoProperties() throws SQLException {
    Properties info = new Properties();
    try (ResultSet rs = getDefaultClientInfo()) {
      while (rs.next()) {
        info.put(rs.getString("NAME"), rs.getString("DEFAULT_VALUE"));
      }
    }
    return info;
  }

  private static ResultSet getDefaultClientInfo() {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("NAME", Type.string()),
                StructField.of("MAX_LEN", Type.int64()),
                StructField.of("DEFAULT_VALUE", Type.string()),
                StructField.of("DESCRIPTION", Type.string())),
            Arrays.asList(
                Struct.newBuilder()
                    .set("NAME")
                    .to("APPLICATIONNAME")
                    .set("MAX_LEN")
                    .to(MAX_CLIENT_INFO_VALUE_LENGTH)
                    .set("DEFAULT_VALUE")
                    .to("")
                    .set("DESCRIPTION")
                    .to("The name of the application currently utilizing the connection.")
                    .build(),
                Struct.newBuilder()
                    .set("NAME")
                    .to("CLIENTHOSTNAME")
                    .set("MAX_LEN")
                    .to(MAX_CLIENT_INFO_VALUE_LENGTH)
                    .set("DEFAULT_VALUE")
                    .to("")
                    .set("DESCRIPTION")
                    .to(
                        "The hostname of the computer the application using the connection is running on.")
                    .build(),
                Struct.newBuilder()
                    .set("NAME")
                    .to("CLIENTUSER")
                    .set("MAX_LEN")
                    .to(MAX_CLIENT_INFO_VALUE_LENGTH)
                    .set("DEFAULT_VALUE")
                    .to("")
                    .set("DESCRIPTION")
                    .to(
                        "The name of the user that the application using the connection is performing work for. "
                            + "This may not be the same as the user name that was used in establishing the connection.")
                    .build())));
  }

  @Override
  public ResultSet getClientInfoProperties() {
    return getDefaultClientInfo();
  }

  @Override
  public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) {
    // TODO: return system functions
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("FUNCTION_CAT", Type.string()),
                StructField.of("FUNCTION_SCHEM", Type.string()),
                StructField.of("FUNCTION_NAME", Type.string()),
                StructField.of("REMARKS", Type.string()),
                StructField.of("FUNCTION_TYPE", Type.int64()),
                StructField.of("SPECIFIC_NAME", Type.string())),
            Collections.emptyList()));
  }

  @Override
  public ResultSet getFunctionColumns(
      String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) {
    // TODO: return system functions
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("FUNCTION_CAT", Type.string()),
                StructField.of("FUNCTION_SCHEM", Type.string()),
                StructField.of("FUNCTION_NAME", Type.string()),
                StructField.of("COLUMN_NAME", Type.string()),
                StructField.of("COLUMN_TYPE", Type.int64()),
                StructField.of("DATA_TYPE", Type.int64()),
                StructField.of("TYPE_NAME", Type.string()),
                StructField.of("PRECISION", Type.int64()),
                StructField.of("LENGTH", Type.int64()),
                StructField.of("SCALE", Type.int64()),
                StructField.of("RADIX", Type.int64()),
                StructField.of("NULLABLE", Type.int64()),
                StructField.of("REMARKS", Type.string()),
                StructField.of("CHAR_OCTET_LENGTH", Type.int64()),
                StructField.of("ORDINAL_POSITION", Type.int64()),
                StructField.of("IS_NULLABLE", Type.string()),
                StructField.of("SPECIFIC_NAME", Type.string())),
            Collections.emptyList()));
  }

  @Override
  public ResultSet getPseudoColumns(
      String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) {
    return JdbcResultSet.of(
        ResultSets.forRows(
            Type.struct(
                StructField.of("TABLE_CAT", Type.string()),
                StructField.of("TABLE_SCHEM", Type.string()),
                StructField.of("TABLE_NAME", Type.string()),
                StructField.of("COLUMN_NAME", Type.string()),
                StructField.of("DATA_TYPE", Type.int64()),
                StructField.of("COLUMN_SIZE", Type.int64()),
                StructField.of("DECIMAL_DIGITS", Type.int64()),
                StructField.of("NUM_PREC_RADIX", Type.int64()),
                StructField.of("COLUMN_USAGE", Type.string()),
                StructField.of("REMARKS", Type.string()),
                StructField.of("CHAR_OCTET_LENGTH", Type.int64()),
                StructField.of("IS_NULLABLE", Type.string())),
            Collections.emptyList()));
  }

  @Override
  public boolean generatedKeyAlwaysReturned() {
    return false;
  }

  @Override
  public long getMaxLogicalLobSize() {
    // BYTES(MAX)
    return 10485760L;
  }

  @Override
  public boolean supportsRefCursors() {
    return false;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy