org.apache.ddlutils.platform.postgresql.PostgreSqlPlatform.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ddlutils.platform.postgresql.PostgreSqlPlatform.java

Source

package org.apache.ddlutils.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 java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.beanutils.DynaBean;
import org.apache.ddlutils.DatabaseOperationException;
import org.apache.ddlutils.PlatformInfo;
import org.apache.ddlutils.alteration.AddColumnChange;
import org.apache.ddlutils.alteration.ModelComparator;
import org.apache.ddlutils.alteration.RemoveColumnChange;
import org.apache.ddlutils.alteration.TableChange;
import org.apache.ddlutils.alteration.TableDefinitionChangesPredicate;
import org.apache.ddlutils.dynabean.SqlDynaProperty;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.Table;
import org.apache.ddlutils.platform.CreationParameters;
import org.apache.ddlutils.platform.DefaultTableDefinitionChangesPredicate;
import org.apache.ddlutils.platform.PlatformImplBase;

/**
 * The platform implementation for PostgresSql.
 * 
 * @version $Revision: 231306 $
 */
public class PostgreSqlPlatform extends PlatformImplBase {
    /** Database name of this platform. */
    public static final String DATABASENAME = "PostgreSql";
    /** The standard PostgreSQL jdbc driver. */
    public static final String JDBC_DRIVER = "org.postgresql.Driver";
    /** The subprotocol used by the standard PostgreSQL driver. */
    public static final String JDBC_SUBPROTOCOL = "postgresql";

    /**
     * Creates a new platform instance.
     */
    public PostgreSqlPlatform() {
        PlatformInfo info = getPlatformInfo();

        info.setPrimaryKeyColumnAutomaticallyRequired(true);
        // this is the default length though it might be changed when building PostgreSQL
        // in file src/include/postgres_ext.h
        info.setMaxIdentifierLength(31);

        info.addNativeTypeMapping(Types.ARRAY, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.BINARY, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.BIT, "BOOLEAN");
        info.addNativeTypeMapping(Types.BLOB, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.BOOLEAN, "BOOLEAN", Types.BIT);
        info.addNativeTypeMapping(Types.CLOB, "TEXT", Types.LONGVARCHAR);
        info.addNativeTypeMapping(Types.DATALINK, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.DECIMAL, "NUMERIC", Types.NUMERIC);
        info.addNativeTypeMapping(Types.DISTINCT, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.DOUBLE, "DOUBLE PRECISION");
        info.addNativeTypeMapping(Types.FLOAT, "DOUBLE PRECISION", Types.DOUBLE);
        info.addNativeTypeMapping(Types.JAVA_OBJECT, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.LONGVARBINARY, "BYTEA");
        info.addNativeTypeMapping(Types.LONGVARCHAR, "TEXT", Types.LONGVARCHAR);
        info.addNativeTypeMapping(Types.NULL, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.OTHER, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.REF, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.STRUCT, "BYTEA", Types.LONGVARBINARY);
        info.addNativeTypeMapping(Types.TINYINT, "SMALLINT", Types.SMALLINT);
        info.addNativeTypeMapping(Types.VARBINARY, "BYTEA", Types.LONGVARBINARY);

        info.setDefaultSize(Types.CHAR, 254);
        info.setDefaultSize(Types.VARCHAR, 254);

        // no support for specifying the size for these types (because they are mapped
        // to BYTEA which back-maps to BLOB)
        info.setHasSize(Types.BINARY, false);
        info.setHasSize(Types.VARBINARY, false);

        setSqlBuilder(new PostgreSqlBuilder(this));
        setModelReader(new PostgreSqlModelReader(this));
    }

    /**
     * {@inheritDoc}
     */
    public String getName() {
        return DATABASENAME;
    }

    /**
     * Creates or drops the database referenced by the given connection url.
     * 
     * @param jdbcDriverClassName The jdbc driver class name
     * @param connectionUrl       The url to connect to the database if it were already created
     * @param username            The username for creating the database
     * @param password            The password for creating the database
     * @param parameters          Additional parameters for the operation
     * @param createDb            Whether to create or drop the database
     */
    private void createOrDropDatabase(String jdbcDriverClassName, String connectionUrl, String username,
            String password, Map parameters, boolean createDb)
            throws DatabaseOperationException, UnsupportedOperationException {
        if (JDBC_DRIVER.equals(jdbcDriverClassName)) {
            int slashPos = connectionUrl.lastIndexOf('/');

            if (slashPos < 0) {
                throw new DatabaseOperationException("Cannot parse the given connection url " + connectionUrl);
            }

            int paramPos = connectionUrl.lastIndexOf('?');
            String baseDb = connectionUrl.substring(0, slashPos + 1) + "template1";
            String dbName = (paramPos > slashPos ? connectionUrl.substring(slashPos + 1, paramPos)
                    : connectionUrl.substring(slashPos + 1));
            Connection connection = null;
            Statement stmt = null;
            StringBuffer sql = new StringBuffer();

            sql.append(createDb ? "CREATE" : "DROP");
            sql.append(" DATABASE ");
            sql.append(dbName);
            if ((parameters != null) && !parameters.isEmpty()) {
                for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) {
                    Map.Entry entry = (Map.Entry) it.next();

                    sql.append(" ");
                    sql.append(entry.getKey().toString());
                    if (entry.getValue() != null) {
                        sql.append(" ");
                        sql.append(entry.getValue().toString());
                    }
                }
            }
            if (getLog().isDebugEnabled()) {
                getLog().debug("About to create database via " + baseDb + " using this SQL: " + sql.toString());
            }
            try {
                Class.forName(jdbcDriverClassName);

                connection = DriverManager.getConnection(baseDb, username, password);
                stmt = connection.createStatement();
                stmt.execute(sql.toString());
                logWarnings(connection);
            } catch (Exception ex) {
                throw new DatabaseOperationException("Error while trying to " + (createDb ? "create" : "drop")
                        + " a database: " + ex.getLocalizedMessage(), ex);
            } finally {
                if (stmt != null) {
                    try {
                        stmt.close();
                    } catch (SQLException ex) {
                    }
                }
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException ex) {
                    }
                }
            }
        } else {
            throw new UnsupportedOperationException("Unable to " + (createDb ? "create" : "drop")
                    + " a PostgreSQL database via the driver " + jdbcDriverClassName);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void createDatabase(String jdbcDriverClassName, String connectionUrl, String username, String password,
            Map parameters) throws DatabaseOperationException, UnsupportedOperationException {
        // With PostgreSQL, you create a database by executing "CREATE DATABASE" in an existing database (usually 
        // the template1 database because it usually exists)
        createOrDropDatabase(jdbcDriverClassName, connectionUrl, username, password, parameters, true);
    }

    /**
     * {@inheritDoc}
     */
    public void dropDatabase(String jdbcDriverClassName, String connectionUrl, String username, String password)
            throws DatabaseOperationException, UnsupportedOperationException {
        // With PostgreSQL, you create a database by executing "DROP DATABASE" in an existing database (usually 
        // the template1 database because it usually exists)
        createOrDropDatabase(jdbcDriverClassName, connectionUrl, username, password, null, false);
    }

    /**
     * {@inheritDoc}
     */
    protected void setObject(PreparedStatement statement, int sqlIndex, DynaBean dynaBean, SqlDynaProperty property)
            throws SQLException {
        int typeCode = property.getColumn().getTypeCode();
        Object value = dynaBean.get(property.getName());

        // PostgreSQL doesn't like setNull for BYTEA columns
        if (value == null) {
            switch (typeCode) {
            case Types.BINARY:
            case Types.VARBINARY:
            case Types.LONGVARBINARY:
            case Types.BLOB:
                statement.setBytes(sqlIndex, null);
                break;
            default:
                statement.setNull(sqlIndex, typeCode);
                break;
            }
        } else {
            super.setObject(statement, sqlIndex, dynaBean, property);
        }
    }

    /**
     * {@inheritDoc}
     */
    protected ModelComparator getModelComparator() {
        ModelComparator comparator = super.getModelComparator();

        comparator.setCanDropPrimaryKeyColumns(false);
        return comparator;
    }

    /**
     * {@inheritDoc}
     */
    protected TableDefinitionChangesPredicate getTableDefinitionChangesPredicate() {
        return new DefaultTableDefinitionChangesPredicate() {
            protected boolean isSupported(Table intermediateTable, TableChange change) {
                if (change instanceof RemoveColumnChange) {
                    return true;
                } else if (change instanceof AddColumnChange) {
                    AddColumnChange addColumnChange = (AddColumnChange) change;

                    // We can only handle this if
                    // * the column is not set to NOT NULL (the constraint would be applied immediately
                    //   which will not work if there is already data in the table)
                    // * the column has no default value (it would be applied after the change which
                    //   means that PostgreSQL would behave differently from other databases where the
                    //   default is applied to every column)
                    // * the column is added at the end of the table (PostgreSQL does not support
                    //   insertion of a column)
                    return !addColumnChange.getNewColumn().isRequired()
                            && (addColumnChange.getNewColumn().getDefaultValue() == null)
                            && (addColumnChange.getNextColumn() == null);
                } else {
                    // TODO: PK changes ?
                    return false;
                }
            }
        };
    }

    /**
     * Processes the removal of a column from a table.
     * 
     * @param currentModel The current database schema
     * @param params       The parameters used in the creation of new tables. Note that for existing
     *                     tables, the parameters won't be applied
     * @param change       The change object
     */
    public void processChange(Database currentModel, CreationParameters params, RemoveColumnChange change)
            throws IOException {
        Table changedTable = findChangedTable(currentModel, change);
        Column removedColumn = changedTable.findColumn(change.getChangedColumn(), isDelimitedIdentifierModeOn());

        ((PostgreSqlBuilder) getSqlBuilder()).dropColumn(changedTable, removedColumn);
        change.apply(currentModel, isDelimitedIdentifierModeOn());
    }
}