/*
 * 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.
 */
package org.apache.openjpa.jdbc.sql;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Locale;

import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.Table;

/**
 * Dictionary for Empress using ODBC server combined with their
 * type 2 driver. This dictionary may issues with other
 * driver/topology configurations.
 *  Empress does not allow multiple connections to read rows
 * read in a transaction, effectively forcing pessimistic transactions
 * regardless of the Optimistic setting. To allow users to use
 * optimistic transactions in a multi-connection evironment, you
 * must set the AllowConcurrentRead setting to true in addition
 * to standard options.
 *  Empress has the following additional limitations:
 * <ul>
 * <li>Foreign keys are quite limited in Empress and it is recommended
 * that these be created by hand.</li>
 * <li>Batching can be unreliable. Using BatchLimit=0 is strongly
 * recommended.</li>
 * <li>Using AllowConcurrentRead should be accompanied by
 * SimulateLocking=true</li>
 * <li>Connections should be rolled back on return to ensure locks
 * are released (see OpenJPA default DataSource documentation.</li>
 * <li>Certain outer joins requiring parameters in a subselect is not
 * supported by Empress and may under certain configurations cause
 * size () calls on query results and LRS fields to throw an exception.</li>
 * <li>Certain aggregate functions are not supported.</li>
 * </ul>
 */
public class EmpressDictionary
    extends DBDictionary {

    /**
     * This setting inserts "BYPASS" after every "SELECT". This
     * allows for multiple transactional reads of the same row
     * from different connections at the expense of loss of
     * pessimistic locking. Defaults to false.
     */
    public boolean allowConcurrentRead = false;

    public EmpressDictionary() {
        platform = "Empress";

        validationSQL = "SELECT DISTINCT today FROM sys_tables";
        joinSyntax = SYNTAX_TRADITIONAL;
        toUpperCaseFunction = "TOUPPER({0})";
        toLowerCaseFunction = "TOLOWER({0})";

        supportsDeferredConstraints = false;
        requiresAliasForSubselect = true;
        maxTableNameLength = 28;
        maxColumnNameLength = 28;
        maxIndexNameLength = 28;
        maxConstraintNameLength = 28;
        schemaCase = SCHEMA_CASE_PRESERVE;

        useGetBytesForBlobs = true;
        useSetBytesForBlobs = true;
        useGetStringForClobs = true;
        useSetStringForClobs = true;

        clobTypeName = "TEXT";
        blobTypeName = "BULK";
        realTypeName = "FLOAT(8)";
        bigintTypeName = "DECIMAL(38)";
        timestampTypeName = "DATE";
        varcharTypeName = "CHARACTER";
        tinyintTypeName = "DOUBLE PRECISION";
        doubleTypeName = "SMALLINT";
        bitTypeName = "SMALLINT";

        fixedSizeTypeNameSet.addAll(Arrays.asList(new String[]{
            "TEXT", "BULK", "LONGFLOAT", "INTEGER64", "SHORTINTEGER",
            "LONGINTEGER",
        }));
    }

    @Override
    public boolean isSystemIndex(String name, Table table) {
        return name.toUpperCase(Locale.ENGLISH).startsWith("SYS_");
    }

    @Override
    public SQLBuffer toSelect(SQLBuffer selects, JDBCFetchConfiguration fetch,
        SQLBuffer from, SQLBuffer where, SQLBuffer group,
        SQLBuffer having, SQLBuffer order,
        boolean distinct, boolean forUpdate, long startIdx, long endIdx) {
        if (!allowConcurrentRead)
            return super.toSelect(selects, fetch, from, where, group,
                having, order, distinct, forUpdate, startIdx, endIdx);

        // override to allow a "BYPASS" to be inserted post-"select"
        // depending on allowConcurrentRead setting
        SQLBuffer buf = new SQLBuffer(this);
        buf.append("SELECT BYPASS ");
        if (distinct)
            buf.append("DISTINCT ");
        buf.append(selects).append(" FROM ").append(from);

        if (where != null && !where.isEmpty())
            buf.append(" WHERE ").append(where);
        if (group != null && !group.isEmpty())
            buf.append(" GROUP BY ").append(group);
        if (having != null && !having.isEmpty())
            buf.append(" HAVING ").append(having);
        if (order != null && !order.isEmpty())
            buf.append(" ORDER BY ").append(order);
        return buf;
    }

    @Override
    public String[] getDropColumnSQL(Column column) {
        // empress wants dropped columns in the form: ALTER TABLE foo
        // DELETE columnToDrop
        return new String[]{ "ALTER TABLE "
            + getFullName(column.getTable(), false) + " DELETE " +
            getColumnDBName(column) };
    }

    @Override
    public void setFloat(PreparedStatement stmnt, int idx, float val,
        Column col)
        throws SQLException {
        // empress seems to allow INFINITY to be stored, but not retrieved,
        // which can prove to be difficult to handle
        if (val == Float.POSITIVE_INFINITY) {
            val = Float.MAX_VALUE;
            storageWarning(Float.POSITIVE_INFINITY,
                    val);
        } else if (val == Float.NEGATIVE_INFINITY) {
            val = Float.MIN_VALUE + 1;
            storageWarning(Float.NEGATIVE_INFINITY,
                    val);
        }
        super.setFloat(stmnt, idx, val, col);
    }

    @Override
    public void setDouble(PreparedStatement stmnt, int idx, double val,
        Column col)
        throws SQLException {
        // empress seems to allow INFINITY to be stored, but not retrieved,
        // which can prove to be difficult to handle
        if (val == Double.POSITIVE_INFINITY) {
            val = Double.MAX_VALUE;
            storageWarning(Double.POSITIVE_INFINITY,
                    val);
        } else if (val == Double.NEGATIVE_INFINITY) {
            val = Double.MIN_VALUE + 1;
            storageWarning(Double.NEGATIVE_INFINITY,
                    val);
        }
        super.setDouble(stmnt, idx, val, col);
    }
}
