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

org.firebirdsql.jdbc.FBResultSetMetaData Maven / Gradle / Ivy

There is a newer version: 6.0.0-beta-1
Show newest version
/*
 * Firebird Open Source JavaEE Connector - JDBC Driver
 *
 * Distributable under LGPL license.
 * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * LGPL License for more details.
 *
 * This file was created by members of the firebird development team.
 * All individual contributions remain the Copyright (C) of those
 * individuals.  Contributors to this file are either listed here or
 * can be obtained from a source control history command.
 *
 * All rights reserved.
 */
package org.firebirdsql.jdbc;

import org.firebirdsql.gds.ISCConstants;
import org.firebirdsql.gds.impl.GDSHelper;
import org.firebirdsql.gds.ng.fields.FieldDescriptor;
import org.firebirdsql.gds.ng.fields.RowDescriptor;
import org.firebirdsql.jdbc.field.JdbcTypeConverter;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;

import static org.firebirdsql.util.StringUtils.isNullOrEmpty;

/**
 * An object that can be used to get information about the types and properties of the columns in
 * a ResultSet object.
 *
 * @author David Jencks
 * @author Mark Rotteveel
 */
@SuppressWarnings("RedundantThrows")
public class FBResultSetMetaData extends AbstractFieldMetaData implements FirebirdResultSetMetaData {

    private final ColumnStrategy columnStrategy;

    /**
     * Creates a new FBResultSetMetaData instance.
     *
     * @param rowDescriptor
     *         a row descriptor
     * @param connection
     *         a FBConnection value
     * @throws SQLException
     *         if an error occurs
     *         

* TODO Need another constructor for metadata from constructed * result set, where we supply the ext field info. */ protected FBResultSetMetaData(RowDescriptor rowDescriptor, FBConnection connection) throws SQLException { super(rowDescriptor, connection); // Decide how to handle column names and column labels if (isColumnLabelForName(connection)) { columnStrategy = ColumnStrategy.COLUMN_LABEL_FOR_NAME; } else { columnStrategy = ColumnStrategy.DEFAULT; } } private static boolean isColumnLabelForName(FBConnection connection) throws SQLException { if (connection == null) { return false; } GDSHelper gdsHelper = connection.getGDSHelper(); return gdsHelper != null && gdsHelper.getConnectionProperties().isColumnLabelForName(); } @Override public int getColumnCount() throws SQLException { return getFieldCount(); } /** * {@inheritDoc} */ @Override public boolean isAutoIncrement(int column) throws SQLException { switch (getColumnType(column)) { case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: ExtendedFieldInfo extFieldInfo = getExtFieldInfo(column); return extFieldInfo != null && extFieldInfo.autoIncrement; default: return false; } } /** * {@inheritDoc} *

* The current implementation always returns true. *

*/ @Override public boolean isCaseSensitive(int column) throws SQLException { return true; } @Override public boolean isSearchable(int column) throws SQLException { final int sqlType = getFieldDescriptor(column).getType() & ~1; return !((sqlType == ISCConstants.SQL_ARRAY) || (sqlType == ISCConstants.SQL_BLOB)); } @Override public boolean isCurrency(int column) throws SQLException { return false; } @Override public int isNullable(int column) throws SQLException { return (getFieldDescriptor(column).getType() & 1) == 1 ? ResultSetMetaData.columnNullable : ResultSetMetaData.columnNoNulls; } @Override public boolean isSigned(int column) throws SQLException { return isSignedInternal(column); } @Override public int getColumnDisplaySize(int column) throws SQLException { final int colType = getColumnType(column); final int precision = getPrecision(column); switch (colType) { case Types.DECIMAL: case Types.NUMERIC: return precision + 2; // sign + decimal separator case Types.FLOAT: return 7 + 6; // 7: number of decimal digits, 6: sign + decimal separator + E + sign exp + exp (2 pos) case Types.DOUBLE: return 15 + 7; // 15: number of decimal digits, 7: sign + decimal separator + E + sign exp + exp (3 pos) case Types.INTEGER: case Types.BIGINT: case Types.SMALLINT: return precision + 1; // sign case JaybirdTypeCodes.DECFLOAT: if (precision == 16) { return 16 + 7; // 7: sign + decimal separator + E + sign exp + exp (3 pos) } return 34 + 8; // 8: sign + decimal separator + E + sign exp + exp (4 pos) case Types.BOOLEAN: return 5; // assuming displaying true/false default: return precision; } } @Override public String getColumnLabel(int column) throws SQLException { return columnStrategy.getColumnLabel(getFieldDescriptor(column)); } @Override public String getColumnName(int column) throws SQLException { return columnStrategy.getColumnName(getFieldDescriptor(column)); } /** * {@inheritDoc} * * @return Always {@code ""} as schemas are not supported. */ @Override public String getSchemaName(int column) throws SQLException { return ""; } /** * {@inheritDoc} *

* NOTE For NUMERIC and DECIMAL we attempt to retrieve the exact precision from the * metadata, if this is not possible (eg the column is dynamically defined in the query), the reported precision is * the maximum precision allowed by the underlying storage data type. *

*/ @Override public int getPrecision(int column) throws SQLException { return getPrecisionInternal(column); } @Override public int getScale(int column) throws SQLException { return getScaleInternal(column); } @Override public String getTableName(int column) throws SQLException { String result = getFieldDescriptor(column).getOriginalTableName(); if (result == null) result = ""; return result; } @Override public String getTableAlias(int column) throws SQLException { String result = getFieldDescriptor(column).getTableAlias(); if (result == null) result = getTableName(column); return result; } /** * {@inheritDoc} * * @return Always {@code ""} as catalogs are not supported */ @Override public String getCatalogName(int column) throws SQLException { return ""; } @Override public int getColumnType(int column) throws SQLException { return getFieldType(column); } @Override public String getColumnTypeName(int column) throws SQLException { return getFieldTypeName(column); } /** * {@inheritDoc} *

* The current implementation always returns false, except for a DB_KEY column. *

*/ @Override public boolean isReadOnly(int column) throws SQLException { // TODO Need to consider privileges!! return getFieldDescriptor(column).isDbKey(); } /** * {@inheritDoc} *

* The current implementation always returns true, except for a DB_KEY column. *

*/ @Override public boolean isWritable(int column) throws SQLException { // TODO Needs privileges? return !getFieldDescriptor(column).isDbKey(); } @Override public boolean isDefinitelyWritable(int column) throws SQLException { return isWritable(column); } @Override public String getColumnClassName(int column) throws SQLException { return getFieldClassName(column); } private static final int FIELD_INFO_RELATION_NAME = 1; private static final int FIELD_INFO_FIELD_NAME = 2; private static final int FIELD_INFO_FIELD_PRECISION = 3; private static final int FIELD_INFO_FIELD_AUTO_INC = 4; //@formatter:off private static final String GET_FIELD_INFO_25 = "select\n" + " RF.RDB$RELATION_NAME as RELATION_NAME,\n" + " RF.RDB$FIELD_NAME as FIELD_NAME,\n" + " F.RDB$FIELD_PRECISION as FIELD_PRECISION,\n" + " 'F' as FIELD_AUTO_INC\n" + "from RDB$RELATION_FIELDS RF inner join RDB$FIELDS F\n" + " on RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME\n" + "where RF.RDB$FIELD_NAME = ? and RF.RDB$RELATION_NAME = ?";; private static final String GET_FIELD_INFO_30 = "select\n" + " RF.RDB$RELATION_NAME as RELATION_NAME,\n" + " RF.RDB$FIELD_NAME as FIELD_NAME,\n" + " F.RDB$FIELD_PRECISION as FIELD_PRECISION,\n" + " RF.RDB$IDENTITY_TYPE is not null as FIELD_AUTO_INC\n" + "from RDB$RELATION_FIELDS RF inner join RDB$FIELDS F\n" + " on RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME\n" + "where RF.RDB$FIELD_NAME = ? and RF.RDB$RELATION_NAME = ?"; //@formatter:on // Apparently there is a limit in the UNION. It is necessary to split in several queries. Although the problem // reported with 93 UNION, use only 70. private static final int MAX_FIELD_INFO_UNIONS = 70; @Override protected Map getExtendedFieldInfo(FBConnection connection) throws SQLException { if (connection == null || !connection.isExtendedMetadata()) return Collections.emptyMap(); final int fieldCount = getFieldCount(); int currentColumn = 1; Map result = new HashMap<>(); FBDatabaseMetaData metaData = (FBDatabaseMetaData) connection.getMetaData(); List params = new ArrayList<>(); StringBuilder sb = new StringBuilder(); boolean fb3OrHigher = metaData.getDatabaseMajorVersion() >= 3; String getFieldInfoQuery = fb3OrHigher ? GET_FIELD_INFO_30 : GET_FIELD_INFO_25; while (currentColumn <= fieldCount) { params.clear(); sb.setLength(0); for (int unionCount = 0; currentColumn <= fieldCount && unionCount < MAX_FIELD_INFO_UNIONS; currentColumn++) { FieldDescriptor fieldDescriptor = getFieldDescriptor(currentColumn); if (!needsExtendedFieldInfo(fieldDescriptor, fb3OrHigher)) continue; String relationName = fieldDescriptor.getOriginalTableName(); String fieldName = fieldDescriptor.getOriginalName(); if (isNullOrEmpty(relationName) || isNullOrEmpty(fieldName)) continue; if (unionCount != 0) { sb.append("\nunion all\n"); } sb.append(getFieldInfoQuery); params.add(fieldName); params.add(relationName); unionCount++; } if (sb.length() == 0) continue; try (ResultSet rs = metaData.doQuery(sb.toString(), params, true)) { while (rs.next()) { ExtendedFieldInfo fieldInfo = extractExtendedFieldInfo(rs); result.put(fieldInfo.fieldKey, fieldInfo); } } } return result; } private static ExtendedFieldInfo extractExtendedFieldInfo(ResultSet rs) throws SQLException { return new ExtendedFieldInfo(rs.getString(FIELD_INFO_RELATION_NAME), rs.getString(FIELD_INFO_FIELD_NAME), rs.getInt(FIELD_INFO_FIELD_PRECISION), rs.getBoolean(FIELD_INFO_FIELD_AUTO_INC)); } /** * @return {@code true} when the field descriptor needs extended field info (currently only NUMERIC and DECIMAL, * and - when {@code fb3OrHigher == true} - INTEGER, BIGINT and SMALLINT) */ private static boolean needsExtendedFieldInfo(FieldDescriptor fieldDescriptor, boolean fb3OrHigher) { switch (JdbcTypeConverter.toJdbcType(fieldDescriptor)) { case Types.NUMERIC: case Types.DECIMAL: return true; case Types.INTEGER: case Types.BIGINT: case Types.SMALLINT: return fb3OrHigher; default: return false; } } /** * Strategy for retrieving column labels and column names */ private enum ColumnStrategy { /** * Default, JDBC-compliant, strategy for column naming. *

* columnLabel is the AS clause (xsqlvar.aliasname) if specified, * otherwise xsqlvar.sqlname. *

*

* columnName is xsqlvar.sqlname if specified, otherwise xsqlvar.aliasname (TODO: change this?) *

*/ DEFAULT { @Override String getColumnName(FieldDescriptor fieldDescriptor) { return fieldDescriptor.getOriginalName() != null ? fieldDescriptor.getOriginalName() : getColumnLabel(fieldDescriptor); } }, /** * Alternative strategy for column naming (related to columnLabelForName connection property) *

* This strategy is not JDBC-compliant, but is provided as a workaround for use with com.sun.rowset.CachedRowSetImpl and * for people expecting the old behavior. *

* columnLabel is the AS clause (xsqlvar.aliasname) if specified, * otherwise xsqlvar.sqlname. *

*

* columnName is identical to columnLabel. *

*/ COLUMN_LABEL_FOR_NAME { @Override String getColumnName(FieldDescriptor fieldDescriptor) { return getColumnLabel(fieldDescriptor); } }; /** * Retrieve the columnName for the specified column. * * @param fieldDescriptor * Column descriptor * @return value for the columnName */ abstract String getColumnName(FieldDescriptor fieldDescriptor); /** * Retrieve the columnLabel for the specified column. * * @param fieldDescriptor * Column descriptor * @return value for the columnLabel */ String getColumnLabel(FieldDescriptor fieldDescriptor) { if (fieldDescriptor.getFieldName() != null) { return fieldDescriptor.getFieldName(); } else if (fieldDescriptor.getOriginalName() != null) { return fieldDescriptor.getOriginalName(); } else { return ""; } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy