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

com.mongodb.jdbc.MongoResultSetMetaData Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/*
 * Copyright 2022-present MongoDB, Inc.
 *
 * 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.mongodb.jdbc;

import com.mongodb.jdbc.logging.AutoLoggable;
import com.mongodb.jdbc.logging.MongoLogger;
import java.math.BigDecimal;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.bson.BsonType;
import org.bson.BsonValue;

@AutoLoggable
public class MongoResultSetMetaData implements ResultSetMetaData {

    private static class NameSpace {
        String datasource;
        String columnLabel;

        NameSpace(String datasource, String columnLabel) {
            this.datasource = datasource;
            this.columnLabel = columnLabel;
        }
    }

    private static class DatasourceAndIndex {
        String datasource;
        int index;

        DatasourceAndIndex(String datasource, int index) {
            this.datasource = datasource;
            this.index = index;
        }
    }

    // A mapping from columnLabel name to datasource name and index.
    private Map> columnLabels;
    // A mapping from index position to NameSpace (datasource, columnLabel).
    private List columnIndices;
    // A mapping from index position to ColumnTypeInfo.
    private List columnInfo;
    protected final int UNKNOWN_LENGTH = 0;
    protected MongoLogger logger;

    /**
     * Constructor.
     *
     * @param schema The resultset schema.
     * @param sortFieldsAlphabetically Flag to set the fields sort order. True if fields must be
     *     sorted alphabetically. False otherwise.
     * @param parentLogger The parent connection logger.
     * @param statementId The statement id for the logger or null if this resultset is not tied to a
     *     statement.
     */
    public MongoResultSetMetaData(
            MongoJsonSchema schema,
            List> selectOrder,
            boolean sortFieldsAlphabetically,
            MongoLogger parentLogger,
            Integer statementId)
            throws SQLException {
        this.logger =
                (statementId == null)
                        ? new MongoLogger(this.getClass().getCanonicalName(), parentLogger)
                        : new MongoLogger(
                                this.getClass().getCanonicalName(), parentLogger, statementId);

        assertDatasourceSchema(schema);

        columnLabels = new HashMap>();
        columnIndices = new ArrayList();
        columnInfo = new ArrayList();

        if (selectOrder == null) {
            String[] datasources = schema.properties.keySet().toArray(new String[0]);
            Arrays.sort(datasources);

            for (String datasource : datasources) {
                processDataSource(schema, datasource, sortFieldsAlphabetically);
            }
        } else {
            processSelectOrder(selectOrder, schema);
        }
    }

    private void assertDatasourceSchema(MongoJsonSchema schema) throws SQLException {
        // A Datasource Schema must be an Object Schema, and unlike Object Schemata in general,
        // the properties field cannot be null.
        if (!schema.isObject() || schema.properties == null) {
            throw new SQLException("ResultSetMetaData json schema must be object with properties");
        }
    }

    private void processDataSource(
            MongoJsonSchema schema, String datasource, boolean sortFieldsAlphabetically)
            throws SQLException {
        MongoJsonSchema datasourceSchema = schema.properties.get(datasource);
        assertDatasourceSchema(datasourceSchema);

        List fields = null;
        if (sortFieldsAlphabetically) {
            fields =
                    datasourceSchema
                            .properties
                            .keySet()
                            .stream()
                            .sorted()
                            .collect(Collectors.toList());
        } else {
            fields = datasourceSchema.properties.keySet().stream().collect(Collectors.toList());
        }

        for (String field : fields) {
            processColumnInfo(datasource, field, datasourceSchema);
        }
    };

    private void processSelectOrder(List> selectOrder, MongoJsonSchema schema)
            throws SQLException {

        // reset columnIndices and columnInfo to empty lists and populate in select order
        columnIndices = new ArrayList();
        columnInfo = new ArrayList();
        for (List column : selectOrder) {
            String datasource = column.get(0);
            String field = column.get(1);
            MongoJsonSchema datasourceSchema = schema.properties.get(datasource);
            assertDatasourceSchema(datasourceSchema);
            processColumnInfo(datasource, field, datasourceSchema);
        }
    }

    private void processColumnInfo(
            String datasource, String field, MongoJsonSchema datasourceSchema) throws SQLException {
        MongoJsonSchema columnSchema = datasourceSchema.properties.get(field);
        BsonTypeInfo columnBsonTypeInfo = columnSchema.getBsonTypeInfo();
        int nullability = datasourceSchema.getColumnNullability(field);
        columnIndices.add(new NameSpace(datasource, field));
        columnInfo.add(new MongoColumnInfo(datasource, field, columnBsonTypeInfo, nullability));
        if (!columnLabels.containsKey(field)) {
            List datasourceAndIndexList = new ArrayList<>();
            datasourceAndIndexList.add(
                    new DatasourceAndIndex(datasource, columnIndices.size() - 1));
            columnLabels.put(field, datasourceAndIndexList);
        } else {
            columnLabels
                    .get(field)
                    .add(new DatasourceAndIndex(datasource, columnIndices.size() - 1));
        }
    }

    // This gets the datasource for a given columnLabel.
    // It is only used in our unit tests.
    protected String getDatasource(String columnLabel) throws Exception {
        List columnsForLabel = columnLabels.get(columnLabel);
        if (columnsForLabel.size() > 1) {
            throw new Exception(
                    String.format(
                            "Multiple columns with the label '%s' exist. Use indexes to avoid ambiguity.",
                            columnLabel));
        } else {
            return columnsForLabel.get(0).datasource;
        }
    }

    protected void checkBounds(int i) throws SQLException {
        if (i > getColumnCount()) {
            throw new SQLException("Index out of bounds: '" + i + "'.");
        }
    }

    public MongoColumnInfo getColumnInfo(int column) throws SQLException {
        checkBounds(column);
        return columnInfo.get(column - 1);
    }

    @Override
    public boolean isAutoIncrement(int column) throws SQLException {
        checkBounds(column);
        return false;
    }

    @Override
    public boolean isSearchable(int column) throws SQLException {
        checkBounds(column);
        return true;
    }

    @Override
    public boolean isCurrency(int column) throws SQLException {
        checkBounds(column);
        return false;
    }

    @Override
    public String getSchemaName(int column) throws SQLException {
        checkBounds(column);
        return "";
    }

    @Override
    public int getColumnCount() throws SQLException {
        return columnIndices.size();
    }

    public boolean hasColumnWithLabel(String label) {
        return columnLabels.containsKey(label);
    }

    public int getColumnPositionFromLabel(String label) throws Exception {
        List columnsForLabel = columnLabels.get(label);
        if (columnsForLabel.size() > 1) {
            throw new Exception(
                    "Multiple columns with the label '"
                            + label
                            + "' exist. Use indexes to avoid ambiguity.");
        } else {
            return columnsForLabel.get(0).index;
        }
    }

    @Override
    public boolean isReadOnly(int column) throws SQLException {
        checkBounds(column);
        return true;
    }

    @Override
    public boolean isWritable(int column) throws SQLException {
        checkBounds(column);
        return false;
    }

    @Override
    public boolean isDefinitelyWritable(int column) throws SQLException {
        checkBounds(column);
        return false;
    }

    @Override
    public int isNullable(int column) throws SQLException {
        return getColumnInfo(column).getNullability();
    }

    @Override
    public String getColumnLabel(int column) throws SQLException {
        return getColumnInfo(column).getColumnAlias();
    }

    @Override
    public String getColumnName(int column) throws SQLException {
        return getColumnInfo(column).getColumnName();
    }

    @Override
    public String getTableName(int column) throws SQLException {
        return getColumnInfo(column).getTableAlias();
    }

    @Override
    public String getCatalogName(int column) throws SQLException {
        return getColumnInfo(column).getDatabase();
    }

    @Override
    public int getColumnType(int column) throws SQLException {
        return getColumnInfo(column).getJDBCType();
    }

    @Override
    public String getColumnTypeName(int column) throws SQLException {
        return getColumnInfo(column).getBsonTypeName();
    }

    // java.sql.Wrapper impl
    @Override
    public boolean isWrapperFor(Class iface) throws SQLException {
        return iface.isInstance(this);
    }

    @SuppressWarnings("unchecked")
    @Override
    public  T unwrap(Class iface) throws SQLException {
        return (T) this;
    }

    @Override
    public boolean isCaseSensitive(int column) throws SQLException {
        MongoColumnInfo ci = getColumnInfo(column);
        if (ci.isPolymorphic()) {
            return true;
        }
        BsonType t = ci.getBsonTypeEnum();
        switch (t) {
            case ARRAY:
            case BINARY:
            case BOOLEAN:
            case DATE_TIME:
            case DB_POINTER:
            case DECIMAL128:
            case DOCUMENT:
            case DOUBLE:
            case INT32:
            case INT64:
            case MAX_KEY:
            case MIN_KEY:
            case NULL:
            case OBJECT_ID:
            case TIMESTAMP:
            case UNDEFINED:
                return false;
            case JAVASCRIPT:
            case JAVASCRIPT_WITH_SCOPE:
            case REGULAR_EXPRESSION:
            case STRING:
            case SYMBOL:
                return true;
        }
        return false;
    }

    @Override
    public boolean isSigned(int column) throws SQLException {
        MongoColumnInfo ci = getColumnInfo(column);
        if (ci.isPolymorphic()) {
            return true;
        }
        BsonType t = ci.getBsonTypeEnum();
        switch (t) {
            case DOUBLE:
            case DECIMAL128:
            case INT32:
            case INT64:
                return true;
            case ARRAY:
            case BINARY:
            case BOOLEAN:
            case DATE_TIME:
            case DB_POINTER:
            case DOCUMENT:
            case MAX_KEY:
            case MIN_KEY:
            case NULL:
            case OBJECT_ID:
            case TIMESTAMP:
            case UNDEFINED:
            case JAVASCRIPT:
            case JAVASCRIPT_WITH_SCOPE:
            case REGULAR_EXPRESSION:
            case STRING:
            case SYMBOL:
                return false;
        }
        return false;
    }

    @Override
    public int getColumnDisplaySize(int column) throws SQLException {
        MongoColumnInfo ci = getColumnInfo(column);
        if (ci.isPolymorphic()) {
            return UNKNOWN_LENGTH;
        }
        BsonType t = ci.getBsonTypeEnum();
        switch (t) {
            case ARRAY:
                return UNKNOWN_LENGTH;
            case BINARY:
                return UNKNOWN_LENGTH;
            case BOOLEAN:
                return 1;
            case DATE_TIME:
                //24 characters to display.
                return 24;
            case DB_POINTER:
                return 0;
            case DECIMAL128:
                return 34;
            case DOCUMENT:
                return UNKNOWN_LENGTH;
            case DOUBLE:
                return 15;
            case INT32:
                return 10;
            case INT64:
                return 19;
            case JAVASCRIPT:
                return UNKNOWN_LENGTH;
            case JAVASCRIPT_WITH_SCOPE:
                return UNKNOWN_LENGTH;
            case MAX_KEY:
                return 0;
            case MIN_KEY:
                return 0;
            case NULL:
                return 0;
            case OBJECT_ID:
                return 24;
            case REGULAR_EXPRESSION:
                return UNKNOWN_LENGTH;
            case STRING:
                return UNKNOWN_LENGTH;
            case SYMBOL:
                return UNKNOWN_LENGTH;
            case TIMESTAMP:
                return UNKNOWN_LENGTH;
            case UNDEFINED:
                return 0;
        }
        throw new SQLException("unknown bson type: " + t);
    }

    @Override
    public int getPrecision(int column) throws SQLException {
        MongoColumnInfo ci = getColumnInfo(column);
        if (ci.isPolymorphic()) {
            return UNKNOWN_LENGTH;
        }
        BsonType t = ci.getBsonTypeEnum();
        switch (t) {
            case ARRAY:
                return UNKNOWN_LENGTH;
            case BINARY:
                return UNKNOWN_LENGTH;
            case BOOLEAN:
                return 1;
            case DATE_TIME:
                return 24;
            case DB_POINTER:
                return 0;
            case DECIMAL128:
                return 34;
            case DOCUMENT:
                return UNKNOWN_LENGTH;
            case DOUBLE:
                return 15;
            case INT32:
                return 10;
            case INT64:
                return 19;
            case JAVASCRIPT:
                return UNKNOWN_LENGTH;
            case JAVASCRIPT_WITH_SCOPE:
                return UNKNOWN_LENGTH;
            case MAX_KEY:
                return 0;
            case MIN_KEY:
                return 0;
            case NULL:
                return 0;
            case OBJECT_ID:
                return 24;
            case REGULAR_EXPRESSION:
                return UNKNOWN_LENGTH;
            case STRING:
                return UNKNOWN_LENGTH;
            case SYMBOL:
                return UNKNOWN_LENGTH;
            case TIMESTAMP:
                return 0;
            case UNDEFINED:
                return 0;
        }
        throw new SQLException("unknown bson type: " + t);
    }

    @Override
    public int getScale(int column) throws SQLException {
        MongoColumnInfo ci = getColumnInfo(column);
        if (ci.isPolymorphic()) {
            return UNKNOWN_LENGTH;
        }
        BsonType t = ci.getBsonTypeEnum();
        switch (t) {
            case ARRAY:
            case BINARY:
            case BOOLEAN:
            case DATE_TIME:
            case DB_POINTER:
            case DOCUMENT:
            case INT32:
            case INT64:
            case JAVASCRIPT:
            case JAVASCRIPT_WITH_SCOPE:
            case MAX_KEY:
            case MIN_KEY:
            case NULL:
            case OBJECT_ID:
            case REGULAR_EXPRESSION:
            case STRING:
            case SYMBOL:
            case TIMESTAMP:
            case UNDEFINED:
                return 0;
            case DECIMAL128:
                return 34;
            case DOUBLE:
                return 15;
        }
        throw new SQLException("unknown bson type: " + t);
    }

    // --------------------------JDBC 2.0-----------------------------------
    @Override
    public String getColumnClassName(int column) throws SQLException {
        String intClassName = int.class.getName();
        String booleanClassName = boolean.class.getName();
        String stringClassName = String.class.getName();
        String floatClassName = float.class.getName();
        String doubleClassName = double.class.getName();
        String bigDecimalClassName = BigDecimal.class.getName();
        String timestampClassName = Timestamp.class.getName();
        String bsonClassName = BsonValue.class.getName();

        int columnType = getColumnType(column);
        switch (columnType) {
            case Types.ARRAY:
                // not supported
                break;
            case Types.BIGINT:
                return intClassName;
            case Types.BINARY:
                // not supported
                break;
            case Types.BIT:
                return booleanClassName;
            case Types.BLOB:
                // not supported
                break;
            case Types.BOOLEAN:
                return booleanClassName;
            case Types.CHAR:
                // not supported
                break;
            case Types.CLOB:
                // not supported
                break;
            case Types.DATALINK:
                // not supported
                break;
            case Types.DATE:
                // not supported
                break;
            case Types.DECIMAL:
                return bigDecimalClassName;
            case Types.DISTINCT:
                // not supported
                break;
            case Types.DOUBLE:
                return doubleClassName;
            case Types.FLOAT:
                return floatClassName;
            case Types.INTEGER:
                return intClassName;
            case Types.JAVA_OBJECT:
                // not supported
                break;
            case Types.LONGNVARCHAR:
                return stringClassName;
            case Types.LONGVARBINARY:
                // not supported
                break;
            case Types.LONGVARCHAR:
                return stringClassName;
            case Types.NCHAR:
                return stringClassName;
            case Types.NCLOB:
                // not supported
                break;
            case Types.NULL:
                return null;
            case Types.NUMERIC:
                return doubleClassName;
            case Types.NVARCHAR:
                return stringClassName;
            case Types.OTHER:
                return bsonClassName;
            case Types.REAL:
                // not supported
                break;
            case Types.REF:
                // not supported
                break;
            case Types.REF_CURSOR:
                // not supported
                break;
            case Types.ROWID:
                // not supported
                break;
            case Types.SMALLINT:
                return intClassName;
            case Types.SQLXML:
                // not supported
                break;
            case Types.STRUCT:
                // not supported
                break;
            case Types.TIME:
                // not supported
                break;
            case Types.TIME_WITH_TIMEZONE:
                // not supported
                break;
            case Types.TIMESTAMP:
                return timestampClassName;
            case Types.TIMESTAMP_WITH_TIMEZONE:
                // not supported
                break;
            case Types.TINYINT:
                return intClassName;
            case Types.VARBINARY:
                // not supported
                break;
            case Types.VARCHAR:
                return stringClassName;
        }
        throw new SQLException("getObject not supported for column type " + columnType);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy