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

org.jumpmind.db.platform.postgresql.PostgreSqlDdlReader Maven / Gradle / Ivy

The newest version!
package org.jumpmind.db.platform.postgresql;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import static org.jumpmind.db.model.ColumnTypes.MAPPED_TIMESTAMPTZ;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;

import org.jumpmind.db.model.Column;
import org.jumpmind.db.model.ForeignKey;
import org.jumpmind.db.model.IIndex;
import org.jumpmind.db.model.PlatformColumn;
import org.jumpmind.db.model.Table;
import org.jumpmind.db.model.TypeMap;
import org.jumpmind.db.platform.AbstractJdbcDdlReader;
import org.jumpmind.db.platform.DatabaseMetaDataWrapper;
import org.jumpmind.db.platform.IDatabasePlatform;
import org.jumpmind.db.sql.JdbcSqlTemplate;

/*
 * Reads a database model from a PostgreSql database.
 */
public class PostgreSqlDdlReader extends AbstractJdbcDdlReader {

    public PostgreSqlDdlReader(IDatabasePlatform platform) {
        super(platform);
        setDefaultCatalogPattern(null);
        setDefaultSchemaPattern(null);
        setDefaultTablePattern(null);
    }

    @Override
    protected Table readTable(Connection connection, DatabaseMetaDataWrapper metaData,
            Map values) throws SQLException {
        Table table = super.readTable(connection, metaData, values);

        if (table != null) {
            // PostgreSQL also returns unique indices for non-pk auto-increment
            // columns which are of the form "[table]_[column]_key"
            HashMap uniquesByName = new HashMap();

            for (int indexIdx = 0; indexIdx < table.getIndexCount(); indexIdx++) {
                IIndex index = table.getIndex(indexIdx);

                if (index.isUnique() && (index.getName() != null)) {
                    uniquesByName.put(index.getName(), index);
                }
            }
            for (int columnIdx = 0; columnIdx < table.getColumnCount(); columnIdx++) {
                Column column = table.getColumn(columnIdx);
                if (column.isAutoIncrement() && !column.isPrimaryKey()) {
                    String indexName = table.getName() + "_" + column.getName() + "_key";
                    if (uniquesByName.containsKey(indexName)) {
                        table.removeIndex((IIndex) uniquesByName.get(indexName));
                        uniquesByName.remove(indexName);
                    }
                }
            }
        }
        setPrimaryKeyConstraintName(connection, table);
        return table;
    }
    
    protected void setPrimaryKeyConstraintName(Connection connection, Table table) throws SQLException {
        String sql = "select conname from pg_constraint where conrelid in (select oid from pg_class where relname=? and relnamespace in (select oid from pg_namespace where nspname=?)) and contype='p'";
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            pstmt = connection.prepareStatement(sql);
            pstmt.setString(1, table.getName());
            pstmt.setString(2, table.getSchema());
            rs = pstmt.executeQuery();
            if (rs.next()) {
                table.setPrimaryKeyConstraintName(rs.getString(1).trim());
            }            
        } finally {
            JdbcSqlTemplate.close(rs);
            JdbcSqlTemplate.close(pstmt);
        }
    }

    @Override
    protected Integer mapUnknownJdbcTypeForColumn(Map values) {
        String typeName = (String) values.get("TYPE_NAME");
        if (typeName != null && typeName.equalsIgnoreCase("ABSTIME")) {
            return Types.TIMESTAMP;
        } else if (typeName != null && typeName.equalsIgnoreCase("TIMESTAMPTZ")) {
            // lets use the same type code that oracle uses
            return MAPPED_TIMESTAMPTZ;            
        } else if (PostgreSqlDatabasePlatform.isBlobStoredByReference(typeName)) {
            return Types.BLOB;
        } else {
            return super.mapUnknownJdbcTypeForColumn(values);
        }
    }

    @Override
    protected Column readColumn(DatabaseMetaDataWrapper metaData, Map values) throws SQLException {
        Column column = super.readColumn(metaData, values);
        
        PlatformColumn platformColumn = column.findPlatformColumn(platform.getName());
        if (platformColumn != null && "serial".equals(platformColumn.getType()) ||
                "serial4".equals(platformColumn.getType())) {
            platformColumn.setType("int4");
        } else if (platformColumn != null && "bigserial".equals(platformColumn.getType()) ||
                "serial8".equals(platformColumn.getType())) {
            platformColumn.setType("int8");            
        }

        if (column.getSize() != null) {
            if (column.getSizeAsInt() <= 0) {
                column.setSize(null);
                // PostgreSQL reports BYTEA and TEXT as BINARY(-1) and
                // VARCHAR(-1) respectively
                // Since we cannot currently use the Blob/Clob interface with
                // BYTEA, we instead
                // map them to LONGVARBINARY/LONGVARCHAR
                if (column.getMappedTypeCode() == Types.BINARY) {
                    column.setMappedTypeCode(Types.LONGVARBINARY);
                } else if (column.getMappedTypeCode() == Types.VARCHAR) {
                    column.setMappedTypeCode(Types.LONGVARCHAR);
                }
            }
            // fix issue DDLUTILS-165 as postgresql-8.2-504-jdbc3.jar seems to
            // return Integer.MAX_VALUE
            // on columns defined as TEXT.
            else if (column.getSizeAsInt() == Integer.MAX_VALUE) {
                column.setSize(null);
                if (column.getMappedTypeCode() == Types.VARCHAR) {
                    column.setMappedTypeCode(Types.LONGVARCHAR);
                } else if (column.getMappedTypeCode() == Types.BINARY) {
                    column.setMappedTypeCode(Types.LONGVARBINARY);
                }
            } else if (column.getSizeAsInt() == 131089 && column.getJdbcTypeCode() == Types.NUMERIC) {                
                column.setSizeAndScale(0, 0);
                column.setMappedTypeCode(Types.DECIMAL);
                if (platformColumn != null) {
                    platformColumn.setSize(-1);
                    platformColumn.setDecimalDigits(-1);
                }
            }
            
        }

        String defaultValue = column.getDefaultValue();

        if ((defaultValue != null) && (defaultValue.length() > 0)) {
            // If the default value looks like
            // "nextval('ROUNDTRIP_VALUE_seq'::text)"
            // then it is an auto-increment column
            if (defaultValue.startsWith("nextval(") || 
                    (PostgreSqlDdlBuilder.isUsePseudoSequence() && defaultValue.endsWith("seq()"))) {
                column.setAutoIncrement(true);
                defaultValue = null;
            } else {
                // PostgreSQL returns default values in the forms
                // "-9000000000000000000::bigint" or
                // "'some value'::character varying" or "'2000-01-01'::date"
                switch (column.getMappedTypeCode()) {
                case Types.INTEGER:
                case Types.BIGINT:
                case Types.DECIMAL:
                case Types.NUMERIC:
                    defaultValue = extractUndelimitedDefaultValue(defaultValue);
                    break;
                case Types.CHAR:
                case Types.VARCHAR:
                case Types.LONGVARCHAR:
                case Types.DATE:
                case Types.TIME:
                case Types.TIMESTAMP:
                    defaultValue = extractDelimitedDefaultValue(defaultValue);
                    break;
                }
                if (TypeMap.isTextType(column.getMappedTypeCode())) {
                    // We assume escaping via double quote (see also the
                    // backslash_quote setting:
                    // http://www.postgresql.org/docs/7.4/interactive/runtime-config.html#RUNTIME-CONFIG-COMPATIBLE)
                    defaultValue = unescape(defaultValue, "'", "''");
                }
            }
            column.setDefaultValue(defaultValue);
        }
        return column;
    }

    /*
     * Extractes the default value from a default value spec of the form
     * "'some value'::character varying" or "'2000-01-01'::date".
     * 
     * @param defaultValue The default value spec
     * 
     * @return The default value
     */
    private String extractDelimitedDefaultValue(String defaultValue) {
        if (defaultValue.startsWith("'")) {
            int valueEnd = defaultValue.indexOf("'::");

            if (valueEnd > 0) {
                return defaultValue.substring("'".length(), valueEnd);
            }
        }
        return defaultValue;
    }

    /*
     * Extractes the default value from a default value spec of the form
     * "-9000000000000000000::bigint".
     * 
     * @param defaultValue The default value spec
     * 
     * @return The default value
     */
    private String extractUndelimitedDefaultValue(String defaultValue) {
        int valueEnd = defaultValue.indexOf("::");

        if (valueEnd > 0) {
            defaultValue = defaultValue.substring(0, valueEnd);
        } else {
            if (defaultValue.startsWith("(") && defaultValue.endsWith(")")) {
                defaultValue = defaultValue.substring(1, defaultValue.length() - 1);
            }
        }
        return defaultValue;
    }

    @Override
    protected boolean isInternalForeignKeyIndex(Connection connection,
            DatabaseMetaDataWrapper metaData, Table table, ForeignKey fk, IIndex index) {
        // PostgreSQL does not return an index for a foreign key
        return false;
    }

    @Override
    protected boolean isInternalPrimaryKeyIndex(Connection connection,
            DatabaseMetaDataWrapper metaData, Table table, IIndex index) {
        return table.doesIndexContainOnlyPrimaryKeyColumns(index);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy