808 lines
23 KiB
Java
808 lines
23 KiB
Java
/*
|
|
* 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.
|
|
*
|
|
* Other licenses:
|
|
* -----------------------------------------------------------------------------
|
|
* Commercial licenses for this work are available. These replace the above
|
|
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
|
|
* database integrations.
|
|
*
|
|
* For more information, please visit: http://www.jooq.org/licenses
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
package org.jooq.impl;
|
|
|
|
import static org.jooq.DatePart.MONTH;
|
|
import static org.jooq.DatePart.SECOND;
|
|
// ...
|
|
// ...
|
|
// ...
|
|
import static org.jooq.SQLDialect.CUBRID;
|
|
// ...
|
|
import static org.jooq.SQLDialect.FIREBIRD;
|
|
import static org.jooq.SQLDialect.H2;
|
|
import static org.jooq.SQLDialect.HSQLDB;
|
|
// ...
|
|
// ...
|
|
// ...
|
|
import static org.jooq.SQLDialect.POSTGRES;
|
|
// ...
|
|
// ...
|
|
import static org.jooq.SQLDialect.SQLITE;
|
|
// ...
|
|
// ...
|
|
// ...
|
|
// ...
|
|
import static org.jooq.impl.DSL.function;
|
|
import static org.jooq.impl.DSL.inline;
|
|
import static org.jooq.impl.DSL.keyword;
|
|
import static org.jooq.impl.DSL.two;
|
|
import static org.jooq.impl.DSL.val;
|
|
import static org.jooq.impl.ExpressionOperator.ADD;
|
|
import static org.jooq.impl.ExpressionOperator.BIT_AND;
|
|
import static org.jooq.impl.ExpressionOperator.BIT_NAND;
|
|
import static org.jooq.impl.ExpressionOperator.BIT_NOR;
|
|
import static org.jooq.impl.ExpressionOperator.BIT_OR;
|
|
import static org.jooq.impl.ExpressionOperator.BIT_XNOR;
|
|
import static org.jooq.impl.ExpressionOperator.BIT_XOR;
|
|
import static org.jooq.impl.ExpressionOperator.SHL;
|
|
import static org.jooq.impl.ExpressionOperator.SHR;
|
|
import static org.jooq.impl.ExpressionOperator.SUBTRACT;
|
|
import static org.jooq.impl.Keywords.F_DATEADD;
|
|
import static org.jooq.impl.Keywords.F_DATE_ADD;
|
|
import static org.jooq.impl.Keywords.F_STRFTIME;
|
|
import static org.jooq.impl.Keywords.F_TIMESTAMPADD;
|
|
import static org.jooq.impl.Keywords.K_AS;
|
|
import static org.jooq.impl.Keywords.K_CAST;
|
|
import static org.jooq.impl.Keywords.K_DAY;
|
|
import static org.jooq.impl.Keywords.K_DAY_MICROSECOND;
|
|
import static org.jooq.impl.Keywords.K_DAY_MILLISECOND;
|
|
import static org.jooq.impl.Keywords.K_DAY_TO_SECOND;
|
|
import static org.jooq.impl.Keywords.K_INTERVAL;
|
|
import static org.jooq.impl.Keywords.K_MILLISECOND;
|
|
import static org.jooq.impl.Keywords.K_MONTH;
|
|
import static org.jooq.impl.Keywords.K_YEAR_MONTH;
|
|
import static org.jooq.impl.Keywords.K_YEAR_TO_MONTH;
|
|
import static org.jooq.impl.Tools.castIfNeeded;
|
|
|
|
import java.sql.Timestamp;
|
|
import java.util.Arrays;
|
|
import java.util.Set;
|
|
import java.util.regex.Pattern;
|
|
|
|
import org.jooq.Context;
|
|
import org.jooq.DataType;
|
|
import org.jooq.DatePart;
|
|
import org.jooq.Field;
|
|
import org.jooq.Param;
|
|
import org.jooq.SQLDialect;
|
|
import org.jooq.exception.DataTypeException;
|
|
import org.jooq.exception.SQLDialectNotSupportedException;
|
|
import org.jooq.types.DayToSecond;
|
|
import org.jooq.types.Interval;
|
|
import org.jooq.types.YearToMonth;
|
|
import org.jooq.types.YearToSecond;
|
|
|
|
final class Expression<T> extends AbstractField<T> {
|
|
|
|
/**
|
|
* Generated UID
|
|
*/
|
|
private static final long serialVersionUID = -5522799070693019771L;
|
|
private static final Set<SQLDialect> SUPPORT_BIT_AND = SQLDialect.supportedBy(H2, HSQLDB);
|
|
private static final Set<SQLDialect> SUPPORT_BIT_OR_XOR = SQLDialect.supportedBy(H2, HSQLDB);
|
|
private static final Set<SQLDialect> EMULATE_BIT_XOR = SQLDialect.supportedBy(SQLITE);
|
|
private static final Set<SQLDialect> EMULATE_SHR_SHL = SQLDialect.supportedBy(HSQLDB);
|
|
private static final Set<SQLDialect> HASH_OP_FOR_BIT_XOR = SQLDialect.supportedBy(POSTGRES);
|
|
|
|
private final Field<T> lhs;
|
|
private final QueryPartList<Field<?>> rhs;
|
|
private final Field<?>[] arguments;
|
|
private final ExpressionOperator operator;
|
|
|
|
Expression(ExpressionOperator operator, Field<T> lhs, Field<?>... rhs) {
|
|
super(DSL.name(operator.toSQL()), lhs.getDataType());
|
|
|
|
this.operator = operator;
|
|
this.lhs = lhs;
|
|
this.rhs = new QueryPartList<>(rhs);
|
|
this.arguments = Tools.combine(lhs, rhs);
|
|
}
|
|
|
|
@Override
|
|
public final Field<T> add(Field<?> value) {
|
|
if (operator == ExpressionOperator.ADD && getDataType().isNumeric()) {
|
|
rhs.add(value);
|
|
return this;
|
|
}
|
|
|
|
return super.add(value);
|
|
}
|
|
|
|
@Override
|
|
public final Field<T> mul(Field<? extends Number> value) {
|
|
if (operator == ExpressionOperator.MULTIPLY && getDataType().isNumeric()) {
|
|
rhs.add(value);
|
|
return this;
|
|
}
|
|
|
|
return super.mul(value);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public final void accept(Context<?> ctx) {
|
|
SQLDialect family = ctx.family();
|
|
|
|
// ---------------------------------------------------------------------
|
|
// XXX: Bitwise operators
|
|
// ---------------------------------------------------------------------
|
|
|
|
// DB2, H2 and HSQLDB know functions, instead of operators
|
|
if (BIT_AND == operator && SUPPORT_BIT_AND.contains(family))
|
|
ctx.visit(function("bitand", getDataType(), arguments));
|
|
else if (BIT_AND == operator && FIREBIRD == family)
|
|
ctx.visit(function("bin_and", getDataType(), arguments));
|
|
else if (BIT_XOR == operator && SUPPORT_BIT_OR_XOR.contains(family))
|
|
ctx.visit(function("bitxor", getDataType(), arguments));
|
|
else if (BIT_XOR == operator && FIREBIRD == family)
|
|
ctx.visit(function("bin_xor", getDataType(), arguments));
|
|
else if (BIT_OR == operator && SUPPORT_BIT_OR_XOR.contains(family))
|
|
ctx.visit(function("bitor", getDataType(), arguments));
|
|
else if (BIT_OR == operator && FIREBIRD == family)
|
|
ctx.visit(function("bin_or", getDataType(), arguments));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ~(a & b) & (a | b)
|
|
else if (BIT_XOR == operator && EMULATE_BIT_XOR.contains(family))
|
|
ctx.visit(DSL.bitAnd(
|
|
DSL.bitNot(DSL.bitAnd(lhsAsNumber(), rhsAsNumber())),
|
|
DSL.bitOr(lhsAsNumber(), rhsAsNumber())));
|
|
|
|
else if (operator == SHL || operator == SHR) {
|
|
if (family == H2)
|
|
ctx.visit(function(SHL == operator ? "lshift" : "rshift", getDataType(), arguments));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Some dialects support shifts as functions
|
|
else if (FIREBIRD == family)
|
|
ctx.visit(function(SHL == operator ? "bin_shl" : "bin_shr", getDataType(), arguments));
|
|
|
|
// Many dialects don't support shifts. Use multiplication/division instead
|
|
else if (SHL == operator && EMULATE_SHR_SHL.contains(family))
|
|
ctx.visit(lhs.mul((Field<? extends Number>) castIfNeeded(DSL.power(two(), rhsAsNumber()), lhs)));
|
|
|
|
// [#3962] This emulation is expensive. If this is emulated, BitCount should
|
|
// use division instead of SHR directly
|
|
else if (SHR == operator && EMULATE_SHR_SHL.contains(family))
|
|
ctx.visit(lhs.div((Field<? extends Number>) castIfNeeded(DSL.power(two(), rhsAsNumber()), lhs)));
|
|
|
|
// Use the default operator expression for all other cases
|
|
else
|
|
ctx.visit(new DefaultExpression<>(lhs, operator, rhs));
|
|
}
|
|
|
|
// These operators are not supported in any dialect
|
|
else if (BIT_NAND == operator)
|
|
ctx.visit(DSL.bitNot(DSL.bitAnd(lhsAsNumber(), rhsAsNumber())));
|
|
else if (BIT_NOR == operator)
|
|
ctx.visit(DSL.bitNot(DSL.bitOr(lhsAsNumber(), rhsAsNumber())));
|
|
else if (BIT_XNOR == operator)
|
|
ctx.visit(DSL.bitNot(DSL.bitXor(lhsAsNumber(), rhsAsNumber())));
|
|
|
|
// ---------------------------------------------------------------------
|
|
// XXX: Date time arithmetic operators
|
|
// ---------------------------------------------------------------------
|
|
|
|
// [#585] Date time arithmetic for numeric or interval RHS
|
|
else if ((ADD == operator || SUBTRACT == operator) &&
|
|
lhs.getDataType().isDateTime() &&
|
|
(rhs.get(0).getDataType().isNumeric() ||
|
|
rhs.get(0).getDataType().isInterval()))
|
|
ctx.visit(new DateExpression<>(lhs, operator, rhs.get(0)));
|
|
|
|
// ---------------------------------------------------------------------
|
|
// XXX: Other operators
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Use the default operator expression for all other cases
|
|
else
|
|
ctx.visit(new DefaultExpression<>(lhs, operator, rhs));
|
|
}
|
|
|
|
/**
|
|
* In some expressions, the lhs can be safely assumed to be a single number
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
private final Field<Number> lhsAsNumber() {
|
|
return (Field<Number>) lhs;
|
|
}
|
|
|
|
/**
|
|
* In some expressions, the rhs can be safely assumed to be a single number
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
private final Field<Number> rhsAsNumber() {
|
|
return (Field<Number>) rhs.get(0);
|
|
}
|
|
|
|
// E.g. +2 00:00:00.000000000
|
|
private static final Pattern TRUNC_TO_MICROS = Pattern.compile("([^.]*\\.\\d{0,6})\\d{0,3}");
|
|
|
|
/**
|
|
* Return the expression to be rendered when the RHS is an interval type
|
|
*/
|
|
private static class DateExpression<T> extends AbstractField<T> {
|
|
|
|
/**
|
|
* Generated UID
|
|
*/
|
|
private static final long serialVersionUID = 3160679741902222262L;
|
|
|
|
private final Field<T> lhs;
|
|
private final ExpressionOperator operator;
|
|
private final Field<?> rhs;
|
|
|
|
DateExpression(Field<T> lhs, ExpressionOperator operator, Field<?> rhs) {
|
|
super(DSL.name(operator.toSQL()), lhs.getDataType());
|
|
|
|
this.lhs = lhs;
|
|
this.operator = operator;
|
|
this.rhs = rhs;
|
|
}
|
|
|
|
private final <U> Field<U> p(U u) {
|
|
Param<U> result = val(u);
|
|
|
|
if (((Param<?>) rhs).isInline())
|
|
result.setInline(true);
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public final void accept(Context<?> ctx) {
|
|
if (rhs.getDataType().isInterval())
|
|
acceptIntervalExpression(ctx);
|
|
else
|
|
acceptNumberExpression(ctx);
|
|
}
|
|
|
|
private final Field<T> getYTSExpression() {
|
|
YearToSecond yts = rhsAsYTS();
|
|
|
|
return new DateExpression<>(
|
|
new DateExpression<>(lhs, operator, p(yts.getYearToMonth())),
|
|
operator,
|
|
p(yts.getDayToSecond())
|
|
);
|
|
}
|
|
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
private final void acceptIntervalExpression(Context<?> ctx) {
|
|
SQLDialect family = ctx.family();
|
|
|
|
int sign = (operator == ADD) ? 1 : -1;
|
|
switch (family) {
|
|
|
|
|
|
|
|
|
|
case CUBRID:
|
|
case MARIADB:
|
|
case MYSQL: {
|
|
if (rhs.getType() == YearToSecond.class) {
|
|
ctx.visit(getYTSExpression());
|
|
break;
|
|
}
|
|
|
|
Interval interval = rhsAsInterval();
|
|
|
|
if (operator == SUBTRACT)
|
|
interval = interval.neg();
|
|
|
|
if (rhs.getType() == YearToMonth.class)
|
|
ctx.visit(F_DATE_ADD).sql('(').visit(lhs).sql(", ").visit(K_INTERVAL).sql(' ')
|
|
.visit(Tools.field(interval, SQLDataType.VARCHAR)).sql(' ').visit(K_YEAR_MONTH).sql(')');
|
|
else if (family == CUBRID)
|
|
ctx.visit(F_DATE_ADD).sql('(').visit(lhs).sql(", ").visit(K_INTERVAL).sql(' ')
|
|
.visit(Tools.field(interval, SQLDataType.VARCHAR)).sql(' ').visit(K_DAY_MILLISECOND).sql(')');
|
|
|
|
// [#6820] Workaround for bugs:
|
|
// https://bugs.mysql.com/bug.php?id=88573
|
|
// https://jira.mariadb.org/browse/MDEV-14452
|
|
else
|
|
ctx.visit(F_DATE_ADD).sql('(').visit(lhs).sql(", ").visit(K_INTERVAL).sql(' ')
|
|
.visit(Tools.field(TRUNC_TO_MICROS.matcher("" + interval).replaceAll("$1"), SQLDataType.VARCHAR)).sql(' ').visit(K_DAY_MICROSECOND).sql(')');
|
|
break;
|
|
}
|
|
|
|
case DERBY:
|
|
case HSQLDB: {
|
|
if (rhs.getType() == YearToSecond.class) {
|
|
ctx.visit(getYTSExpression());
|
|
break;
|
|
}
|
|
|
|
boolean needsCast = getDataType().getType() != Timestamp.class;
|
|
if (needsCast)
|
|
ctx.visit(K_CAST).sql('(');
|
|
|
|
if (rhs.getType() == YearToMonth.class)
|
|
ctx.sql("{fn ").visit(F_TIMESTAMPADD).sql('(').visit(keyword("sql_tsi_month")).sql(", ")
|
|
.visit(p(sign * rhsAsYTM().intValue())).sql(", ").visit(lhs).sql(") }");
|
|
else
|
|
ctx.sql("{fn ").visit(F_TIMESTAMPADD).sql('(').visit(keyword("sql_tsi_second")).sql(", ")
|
|
.visit(p(sign * (long) rhsAsDTS().getTotalSeconds())).sql(", {fn ")
|
|
.visit(F_TIMESTAMPADD).sql('(').visit(keyword("sql_tsi_milli_second")).sql(", ")
|
|
.visit(p(sign * (long) rhsAsDTS().getMilli())).sql(", ").visit(lhs).sql(") }) }");
|
|
|
|
// [#1883] TIMESTAMPADD returns TIMESTAMP columns. If this
|
|
// is a DATE column, cast it to DATE
|
|
if (needsCast)
|
|
ctx.sql(' ').visit(K_AS).sql(' ').visit(keyword(getDataType().getCastTypeName(ctx.configuration()))).sql(')');
|
|
|
|
break;
|
|
}
|
|
|
|
case FIREBIRD: {
|
|
if (rhs.getType() == YearToSecond.class)
|
|
ctx.visit(getYTSExpression());
|
|
else if (rhs.getType() == YearToMonth.class)
|
|
ctx.visit(F_DATEADD).sql('(').visit(K_MONTH).sql(", ").visit(p(sign * rhsAsYTM().intValue())).sql(", ").visit(lhs).sql(')');
|
|
else
|
|
ctx.visit(F_DATEADD).sql('(').visit(K_MILLISECOND).sql(", ").visit(p(sign * (long) rhsAsDTS().getTotalMilli())).sql(", ").visit(lhs).sql(')');
|
|
break;
|
|
}
|
|
|
|
case H2: {
|
|
if (rhs.getType() == YearToSecond.class)
|
|
ctx.visit(getYTSExpression());
|
|
else if (rhs.getType() == YearToMonth.class)
|
|
ctx.visit(F_DATEADD).sql("('month', ").visit(p(sign * rhsAsYTM().intValue())).sql(", ").visit(lhs).sql(')');
|
|
else
|
|
ctx.visit(F_DATEADD).sql("('ms', ").visit(p(sign * (long) rhsAsDTS().getTotalMilli())).sql(", ").visit(lhs).sql(')');
|
|
break;
|
|
}
|
|
|
|
case SQLITE: {
|
|
if (rhs.getType() == YearToSecond.class) {
|
|
ctx.visit(getYTSExpression());
|
|
break;
|
|
}
|
|
|
|
boolean ytm = rhs.getType() == YearToMonth.class;
|
|
Field<?> interval = p(ytm ? rhsAsYTM().intValue() : rhsAsDTS().getTotalSeconds());
|
|
|
|
if (sign < 0)
|
|
interval = interval.neg();
|
|
|
|
interval = interval.concat(inline(ytm ? " months" : " seconds"));
|
|
ctx.visit(F_STRFTIME).sql("('%Y-%m-%d %H:%M:%f', ").visit(lhs).sql(", ").visit(interval).sql(')');
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case POSTGRES:
|
|
default:
|
|
ctx.visit(new DefaultExpression<>(lhs, operator, new QueryPartList<>(Arrays.asList(rhs))));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the expression to be rendered when the RHS is a number type
|
|
*/
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
private final void acceptNumberExpression(Context<?> ctx) {
|
|
switch (ctx.family()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case FIREBIRD: {
|
|
if (operator == ADD)
|
|
ctx.visit(F_DATEADD).sql('(').visit(K_DAY).sql(", ").visit(rhsAsNumber()).sql(", ").visit(lhs).sql(')');
|
|
else
|
|
ctx.visit(F_DATEADD).sql('(').visit(K_DAY).sql(", ").visit(rhsAsNumber().neg()).sql(", ").visit(lhs).sql(')');
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
|
|
case HSQLDB: {
|
|
if (operator == ADD)
|
|
ctx.visit(lhs.add(DSL.field("{0} day", rhsAsNumber())));
|
|
else
|
|
ctx.visit(lhs.sub(DSL.field("{0} day", rhsAsNumber())));
|
|
break;
|
|
}
|
|
|
|
case DERBY: {
|
|
boolean needsCast = getDataType().getType() != Timestamp.class;
|
|
if (needsCast)
|
|
ctx.visit(K_CAST).sql('(');
|
|
|
|
if (operator == ADD)
|
|
ctx.sql("{fn ").visit(F_TIMESTAMPADD).sql('(').visit(keyword("sql_tsi_day")).sql(", ").visit(rhsAsNumber()).sql(", ").visit(lhs).sql(") }");
|
|
else
|
|
ctx.sql("{fn ").visit(F_TIMESTAMPADD).sql('(').visit(keyword("sql_tsi_day")).sql(", ").visit(rhsAsNumber().neg()).sql(", ").visit(lhs).sql(") }");
|
|
|
|
// [#1883] TIMESTAMPADD returns TIMESTAMP columns. If this
|
|
// is a DATE column, cast it to DATE
|
|
if (needsCast)
|
|
ctx.sql(' ').visit(K_AS).sql(' ').visit(keyword(getDataType().getCastTypeName(ctx.configuration()))).sql(')');
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case CUBRID:
|
|
case MARIADB:
|
|
case MYSQL: {
|
|
if (operator == ADD)
|
|
ctx.visit(F_DATE_ADD).sql('(').visit(lhs).sql(", ").visit(K_INTERVAL).sql(' ').visit(rhsAsNumber()).sql(' ').visit(K_DAY).sql(')');
|
|
else
|
|
ctx.visit(F_DATE_ADD).sql('(').visit(lhs).sql(", ").visit(K_INTERVAL).sql(' ').visit(rhsAsNumber().neg()).sql(' ').visit(K_DAY).sql(')');
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case POSTGRES: {
|
|
|
|
// This seems to be the most reliable way to avoid issues
|
|
// with incompatible data types and timezones
|
|
// ? + CAST (? || ' days' as interval)
|
|
if (operator == ADD)
|
|
ctx.visit(new DateAdd(lhs, rhsAsNumber(), DatePart.DAY));
|
|
else
|
|
ctx.visit(new DateAdd(lhs, rhsAsNumber().neg(), DatePart.DAY));
|
|
break;
|
|
}
|
|
|
|
case SQLITE:
|
|
if (operator == ADD)
|
|
ctx.visit(F_STRFTIME).sql("('%Y-%m-%d %H:%M:%f', ").visit(lhs).sql(", ").visit(rhsAsNumber().concat(inline(" day"))).sql(')');
|
|
else
|
|
ctx.visit(F_STRFTIME).sql("('%Y-%m-%d %H:%M:%f', ").visit(lhs).sql(", ").visit(rhsAsNumber().neg().concat(inline(" day"))).sql(')');
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case H2:
|
|
default:
|
|
ctx.visit(new DefaultExpression<>(lhs, operator, new QueryPartList<>(Arrays.asList(rhs))));
|
|
break;
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private final YearToSecond rhsAsYTS() {
|
|
try {
|
|
return ((Param<YearToSecond>) rhs).getValue();
|
|
}
|
|
catch (ClassCastException e) {
|
|
throw new DataTypeException("Cannot perform datetime arithmetic with a non-numeric, non-interval data type on the right hand side of the expression: " + rhs);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private final YearToMonth rhsAsYTM() {
|
|
try {
|
|
return ((Param<YearToMonth>) rhs).getValue();
|
|
}
|
|
catch (ClassCastException e) {
|
|
throw new DataTypeException("Cannot perform datetime arithmetic with a non-numeric, non-interval data type on the right hand side of the expression: " + rhs);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private final DayToSecond rhsAsDTS() {
|
|
try {
|
|
return ((Param<DayToSecond>) rhs).getValue();
|
|
}
|
|
catch (ClassCastException e) {
|
|
throw new DataTypeException("Cannot perform datetime arithmetic with a non-numeric, non-interval data type on the right hand side of the expression: " + rhs);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private final Interval rhsAsInterval() {
|
|
try {
|
|
return ((Param<Interval>) rhs).getValue();
|
|
}
|
|
catch (ClassCastException e) {
|
|
throw new DataTypeException("Cannot perform datetime arithmetic with a non-numeric, non-interval data type on the right hand side of the expression: " + rhs);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* In some expressions, the rhs can be safely assumed to be a single number
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
private final Field<Number> rhsAsNumber() {
|
|
return (Field<Number>) rhs;
|
|
}
|
|
}
|
|
|
|
private static class DefaultExpression<T> extends AbstractField<T> {
|
|
|
|
/**
|
|
* Generated UID
|
|
*/
|
|
private static final long serialVersionUID = -5105004317793995419L;
|
|
|
|
private final Field<T> lhs;
|
|
private final ExpressionOperator operator;
|
|
private final QueryPartList<Field<?>> rhs;
|
|
|
|
DefaultExpression(Field<T> lhs, ExpressionOperator operator, QueryPartList<Field<?>> rhs) {
|
|
super(operator.toName(), lhs.getDataType());
|
|
|
|
this.lhs = lhs;
|
|
this.operator = operator;
|
|
this.rhs = rhs;
|
|
}
|
|
|
|
@Override
|
|
public final void accept(Context<?> ctx) {
|
|
String op = operator.toSQL();
|
|
|
|
if (operator == BIT_XOR && HASH_OP_FOR_BIT_XOR.contains(ctx.family()))
|
|
op = "#";
|
|
|
|
ctx.sql('(');
|
|
ctx.visit(lhs);
|
|
|
|
for (Field<?> field : rhs)
|
|
ctx.sql(' ')
|
|
.sql(op)
|
|
.sql(' ')
|
|
.visit(field);
|
|
|
|
ctx.sql(')');
|
|
}
|
|
}
|
|
}
|