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

org.jooq.impl.Val Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2009-2014, Data Geekery GmbH (http://www.datageekery.com)
 * All rights reserved.
 *
 * This work is dual-licensed
 * - under the Apache Software License 2.0 (the "ASL")
 * - under the jOOQ License and Maintenance Agreement (the "jOOQ License")
 * =============================================================================
 * You may choose which license applies to you:
 *
 * - If you're using this work with Open Source databases, you may choose
 *   either ASL or jOOQ License.
 * - If you're using this work with at least one commercial database, you must
 *   choose jOOQ License
 *
 * For more information, please visit http://www.jooq.org/licenses
 *
 * Apache Software License 2.0:
 * -----------------------------------------------------------------------------
 * 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.
 *
 * jOOQ License and Maintenance Agreement:
 * -----------------------------------------------------------------------------
 * Data Geekery grants the Customer the non-exclusive, timely limited and
 * non-transferable license to install and use the Software under the terms of
 * the jOOQ License and Maintenance Agreement.
 *
 * This library is distributed with a LIMITED WARRANTY. See the jOOQ License
 * and Maintenance Agreement for more details: http://www.jooq.org/licensing
 */
package org.jooq.impl;

import static java.lang.Boolean.TRUE;
import static java.lang.Integer.toOctalString;
import static java.util.Arrays.asList;
// ...
// ...
import static org.jooq.SQLDialect.CUBRID;
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
// ...
import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.MYSQL;
// ...
import static org.jooq.SQLDialect.POSTGRES;
import static org.jooq.SQLDialect.SQLITE;
// ...
// ...
import static org.jooq.conf.ParamType.NAMED;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.using;
import static org.jooq.tools.StringUtils.leftPad;

import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;

// ...
import org.jooq.BindContext;
import org.jooq.Converter;
import org.jooq.DataType;
import org.jooq.EnumType;
import org.jooq.RenderContext;
import org.jooq.SQLDialect;
import org.jooq.Schema;
import org.jooq.UDTRecord;
import org.jooq.tools.StringUtils;
import org.jooq.types.Interval;

/**
 * @author Lukas Eder
 */
class Val extends AbstractParam {

    private static final long   serialVersionUID = 6807729087019209084L;
    private static final char[] HEX              = "0123456789abcdef".toCharArray();

    Val(T value, DataType type) {
        super(value, type);
    }

    Val(T value, DataType type, String paramName) {
        super(value, type, paramName);
    }

    // ------------------------------------------------------------------------
    // XXX: Field API
    // ------------------------------------------------------------------------

    @Override
    public final void toSQL(RenderContext context) {

        // Casting can be enforced or prevented
        switch (context.castMode()) {
            case NEVER:
                toSQL(context, value, getType());
                return;

            case ALWAYS:
                toSQLCast(context);
                return;

            case SOME:

                // This dialect must cast
                if (context.cast()) {
                    toSQLCast(context);
                }

                // In some cases, we should still cast
                else if (shouldCast(context)) {
                    toSQLCast(context);
                }
                else {
                    toSQL(context, value, getType());
                }

                return;
        }

        // See if we "should" cast, to stay on the safe side
        if (shouldCast(context)) {
            toSQLCast(context);
        }

        // Most RDBMS can infer types for bind values
        else {
            toSQL(context, value, getType());
        }
    }

    private final boolean shouldCast(RenderContext context) {

        // In default mode, casting is only done when parameters are NOT inlined
        if (!isInline(context)) {

            // Generated enums should not be cast...
            if (!(value instanceof EnumType)) {
                switch (context.configuration().dialect().family()) {

                    // These dialects can hardly detect the type of a bound constant.
                    /* [pro] xx
                    xxxx xxxx
                    xx [/pro] */
                    case DERBY:
                    case FIREBIRD:

                    // These dialects have some trouble, when they mostly get it right.
                    case H2:
                    case HSQLDB:

                    // [#1261] There are only a few corner-cases, where this is
                    // really needed. Check back on related CUBRID bugs
                    case CUBRID:

                    // [#1029] Postgres and [#632] Sybase need explicit casting
                    // in very rare cases.
                    /* [pro] xx
                    xxxx xxxxxxx
                    xx [/pro] */
                    case POSTGRES: {
                        return true;
                    }
                }
            }
        }

        // [#566] JDBC doesn't explicitly support interval data types. To be on
        // the safe side, always cast these types in those dialects that support
        // them
        if (getDataType().isInterval()) {
            switch (context.configuration().dialect().family()) {
                /* [pro] xx
                xxxx xxxxxxx
                xx [/pro] */
                case POSTGRES:
                    return true;
            }
        }

        return false;
    }

    /**
     * Render the bind variable including a cast, if necessary
     */
    private final void toSQLCast(RenderContext context) {
        DataType dataType = getDataType(context.configuration());
        DataType type = dataType.getSQLDataType();
        SQLDialect family = context.configuration().dialect().family();

        // [#822] Some RDBMS need precision / scale information on BigDecimals
        if (value != null && getType() == BigDecimal.class && asList(CUBRID, DERBY, FIREBIRD, HSQLDB).contains(family)) {

            // Add precision / scale on BigDecimals
            int scale = ((BigDecimal) value).scale();
            int precision = scale + ((BigDecimal) value).precision();

            // Firebird's max precision is 18
            if (family == FIREBIRD) {
                precision = Math.min(precision, 18);
            }

            toSQLCast(context, dataType, 0, precision, scale);
        }

        // [#1028] Most databases don't know an OTHER type (except H2, HSQLDB).
        else if (SQLDataType.OTHER == type) {

            // If the bind value is set, it can be used to derive the cast type
            if (value != null) {
                toSQLCast(context, DefaultDataType.getDataType(family, value.getClass()), 0, 0, 0);
            }

            // [#632] [#722] Current integration tests show that Ingres and
            // Sybase can do without casting in most cases.
            else if (asList().contains(family)) {
                context.sql(getBindVariable(context));
            }

            // Derby and DB2 must have a type associated with NULL. Use VARCHAR
            // as a workaround. That's probably not correct in all cases, though
            else {
                toSQLCast(context, DefaultDataType.getDataType(family, String.class), 0, 0, 0);
            }
        }

        // [#1029] Postgres generally doesn't need the casting. Only in the
        // above case where the type is OTHER
        // [#1125] Also with temporal data types, casting is needed some times
        // [#1130] TODO type can be null for ARRAY types, etc.
        else if (family == POSTGRES && (type == null || !type.isTemporal())) {
            toSQL(context, value, getType());
        }

        // [#1727] VARCHAR types should be cast to their actual lengths in some
        // dialects
        else if ((type == SQLDataType.VARCHAR || type == SQLDataType.CHAR) && asList(FIREBIRD).contains(family)) {
            toSQLCast(context, dataType, getValueLength(), 0, 0);
        }

        /* [pro] xx
        xx xxxxxxx xxxx xxxx xxxxx xxxxxx xxx xx xxxx xx xxx xxxxxx xx xxxx xxxxxxxx
        xxxx xx xxxxx xx xxxxxxxxxxxxxxxxxxxxxxx xx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x
            xxxxxxxxxxxxxxxxxx xxxxxxxxx xx xx xxx
        x
        xx [/pro] */

        // In all other cases, the bind variable can be cast normally
        else {
            toSQLCast(context, dataType, dataType.length(), dataType.precision(), dataType.scale());
        }
    }

    private final int getValueLength() {
        String string = (String) value;
        if (string == null) {
            return 1;
        }

        else {
            int length = string.length();

            // If non 7-bit ASCII characters are present, multiply the length by
            // 4 to be sure that even UTF-32 collations will fit. But don't use
            // larger numbers than Derby's upper limit 32672
            for (int i = 0; i < length; i++) {
                if (string.charAt(i) > 127) {
                    return Math.min(32672, 4 * length);
                }
            }

            return Math.min(32672, length);
        }
    }

    private final void toSQLCast(RenderContext context, DataType type, int length, int precision, int scale) {
        context.keyword("cast").sql("(");
        toSQL(context, value, getType());
        context.sql(" ").keyword("as").sql(" ")
               .sql(type.length(length).precision(precision, scale).getCastTypeName(context.configuration()))
               .sql(")");
    }

    /**
     * Get a bind variable, depending on value of
     * {@link RenderContext#namedParams()}
     */
    private final String getBindVariable(RenderContext context) {
        if (context.paramType() == NAMED) {
            int index = context.nextIndex();

            if (StringUtils.isBlank(getParamName())) {
                return ":" + index;
            }
            else {
                return ":" + getName();
            }
        }
        else {
            return "?";
        }
    }

    /**
     * Inlining abstraction
     */
    private final void toSQL(RenderContext context, Object val) {
        if (val == null) {
            toSQL(context, val, Object.class);
        }
        else {
            toSQL(context, val, val.getClass());
        }
    }

    /**
     * Inlining abstraction
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private final void toSQL(RenderContext context, Object val, Class type) {
        SQLDialect family = context.configuration().dialect().family();

        // [#650] Check first, if we have a converter for the supplied type
        Converter converter = DataTypes.converter(type);
        if (converter != null) {
            val = ((Converter) converter).to(val);
            type = converter.fromType();
        }

        if (isInline(context)) {
            // [#2223] Some type-casts in this section may seem unnecessary, e.g.
            // ((Boolean) val).toString(). They have been put in place to avoid
            // accidental type confusions where type != val.getClass(), and thus
            // SQL injection may occur

            if (val == null) {
                context.keyword("null");
            }
            else if (type == Boolean.class) {

                // [#1153] Some dialects don't support boolean literals
                // TRUE and FALSE
                if (asList(FIREBIRD, SQLITE).contains(family)) {
                    context.sql(((Boolean) val) ? "1" : "0");
                }
                else {
                    context.keyword(((Boolean) val).toString());
                }
            }

            // [#1154] Binary data cannot always be inlined
            else if (type == byte[].class) {
                byte[] binary = (byte[]) val;

                if (asList().contains(family)) {
                    context.sql("0x")
                           .sql(convertBytesToHex(binary));
                }
                /* [pro] xx
                xxxx xx xxxxxxx xx xxxx x
                    xxxxxxxxxxxxxxxxxxxxxxx
                           xxxxxxxxxxx
                           xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                           xxxxxxxxxxx
                x
                xx [/pro] */
                else if (asList(DERBY, H2, HSQLDB, MARIADB, MYSQL, SQLITE).contains(family)) {
                    context.sql("X'")
                           .sql(convertBytesToHex(binary))
                           .sql("'");
                }
                else if (asList().contains(family)) {
                    context.keyword("hextoraw('")
                           .sql(convertBytesToHex(binary))
                           .sql("')");
                }
                else if (family == POSTGRES) {
                    context.sql("E'")
                           .sql(convertBytesToPostgresOctal(binary))
                           .keyword("'::bytea");
                }

                // This default behaviour is used in debug logging for dialects
                // that do not support inlining binary data
                else {
                    context.sql("X'")
                           .sql(convertBytesToHex(binary))
                           .sql("'");
                }
            }

            // Interval extends Number, so let Interval come first!
            else if (Interval.class.isAssignableFrom(type)) {
                context.sql("'")
                       .sql(escape(val))
                       .sql("'");
            }

            else if (Number.class.isAssignableFrom(type)) {
                context.sql(((Number) val).toString());
            }

            // [#1156] Date/Time data types should be inlined using JDBC
            // escape syntax
            else if (type == Date.class) {

                // The SQLite JDBC driver does not implement the escape syntax
                // [#1253] SQL Server and Sybase do not implement date literals
                if (asList(SQLITE).contains(family)) {
                    context.sql("'").sql(escape(val)).sql("'");
                }

                /* [pro] xx
                xxxx xx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x
                    xxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxx
                x
                xx [/pro] */

                // [#1253] Derby doesn't support the standard literal
                else if (family == DERBY) {
                    context.keyword("date('").sql(escape(val)).sql("')");
                }

                // Most dialects implement SQL standard date literals
                else {
                    context.keyword("date '").sql(escape(val)).sql("'");
                }
            }
            else if (type == Timestamp.class) {

                // The SQLite JDBC driver does not implement the escape syntax
                // [#1253] SQL Server and Sybase do not implement timestamp literals
                if (asList(SQLITE).contains(family)) {
                    context.sql("'").sql(escape(val)).sql("'");
                }

                /* [pro] xx
                xxxx xx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x
                    xxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxx
                x
                xx [/pro] */

                // [#1253] Derby doesn't support the standard literal
                else if (family == DERBY) {
                    context.keyword("timestamp('").sql(escape(val)).sql("')");
                }

                // CUBRID timestamps have no fractional seconds
                else if (family == CUBRID) {
                    context.keyword("datetime '").sql(escape(val)).sql("'");
                }

                // Most dialects implement SQL standard timestamp literals
                else {
                    context.keyword("timestamp '").sql(escape(val)).sql("'");
                }
            }
            else if (type == Time.class) {

                // The SQLite JDBC driver does not implement the escape syntax
                // [#1253] SQL Server and Sybase do not implement time literals
                if (asList(SQLITE).contains(family)) {
                    context.sql("'").sql(new SimpleDateFormat("HH:mm:ss").format((Time) val)).sql("'");
                }

                /* [pro] xx
                xxxx xx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x
                    xxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                x
                xx [/pro] */

                // [#1253] Derby doesn't support the standard literal
                else if (family == DERBY) {
                    context.keyword("time").sql("('").sql(escape(val)).sql("')");
                }

                /* [pro] xx
                xx xxxxxxx xxxxxx xxxxxxx xxxx xxxx xxxxxxxx
                xxxx xx xxxxxxx xx xxxxxxx x
                    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                x

                xx [/pro] */
                // Most dialects implement SQL standard time literals
                else {
                    context.keyword("time").sql(" '").sql(escape(val)).sql("'");
                }
            }
            else if (type.isArray()) {
                String separator = "";

                // H2 renders arrays as rows
                if (family == H2) {
                    context.sql("(");

                    for (Object o : ((Object[]) val)) {
                        context.sql(separator);
                        toSQL(context, o, type.getComponentType());
                        separator = ", ";
                    }

                    context.sql(")");
                }

                // By default, render HSQLDB / POSTGRES syntax
                else {
                    context.keyword("ARRAY");
                    context.sql("[");

                    for (Object o : ((Object[]) val)) {
                        context.sql(separator);
                        toSQL(context, o, type.getComponentType());
                        separator = ", ";
                    }

                    context.sql("]");
                }
            }
            /* [pro] xx
            xxxx xx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx x
                xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxx
            x
            xx [/pro] */
            else if (EnumType.class.isAssignableFrom(type)) {
                toSQL(context, ((EnumType) val).getLiteral());
            }
            else if (UDTRecord.class.isAssignableFrom(type)) {
                context.sql("[UDT]");
            }

            // Known fall-through types:
            // - Blob, Clob (both not supported by jOOQ)
            // - String
            // - UUID
            else {
                context.sql("'")
                       .sql(escape(val), true)
                       .sql("'");
            }
        }

        // In Postgres, some additional casting must be done in some cases...
        // TODO: Improve this implementation with [#215] (cast support)
        else if (family == SQLDialect.POSTGRES) {

            // Postgres needs explicit casting for array types
            if (type.isArray() && byte[].class != type) {
                context.sql(getBindVariable(context));
                context.sql("::");
                context.keyword(DefaultDataType.getDataType(family, type).getCastTypeName(context.configuration()));
            }

            // ... and also for enum types
            else if (EnumType.class.isAssignableFrom(type)) {
                context.sql(getBindVariable(context));

                // [#968] Don't cast "synthetic" enum types (note, val can be null!)
                EnumType e = (EnumType) type.getEnumConstants()[0];
                Schema schema = e.getSchema();

                if (schema != null) {
                    context.sql("::");

                    schema = using(context.configuration()).map(schema);
                    if (schema != null && TRUE.equals(context.configuration().settings().isRenderSchema())) {
                        context.visit(schema);
                        context.sql(".");
                    }

                    context.visit(name(e.getName()));
                }
            }

            else {
                context.sql(getBindVariable(context));
            }
        }

        else {
            context.sql(getBindVariable(context));
        }
    }

    /**
     * Escape a string literal by replacing ' by ''
     */
    private final String escape(Object val) {
        return val.toString().replace("'", "''");
    }

    @Override
    public final void bind(BindContext context) {

        // [#1302] Bind value only if it was not explicitly forced to be inlined
        if (!isInline()) {
            context.bindValue(value, getType());
        }
    }

    // ------------------------------------------------------------------------
    // XXX: Param API
    // ------------------------------------------------------------------------

    /**
     * Convert a byte array to a hex encoded string.
     *
     * @param value the byte array
     * @return the hex encoded string
     */
    private static final String convertBytesToHex(byte[] value) {
        return convertBytesToHex(value, value.length);
    }

    /**
     * Convert a byte array to a hex encoded string.
     *
     * @param value the byte array
     * @param len the number of bytes to encode
     * @return the hex encoded string
     */
    private static final String convertBytesToHex(byte[] value, int len) {
        char[] buff = new char[len + len];
        char[] hex = HEX;
        for (int i = 0; i < len; i++) {
            int c = value[i] & 0xff;
            buff[i + i] = hex[c >> 4];
            buff[i + i + 1] = hex[c & 0xf];
        }
        return new String(buff);
    }

    /**
     * Postgres uses octals instead of hex encoding
     */
    private static final String convertBytesToPostgresOctal(byte[] binary) {
        StringBuilder sb = new StringBuilder();

        for (byte b : binary) {
            sb.append("\\\\");
            sb.append(leftPad(toOctalString(b), 3, '0'));
        }

        return sb.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy