From e3d0cc483d2e09ba0ba7cd9a952dda4325d01d86 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 22 Sep 2020 10:28:35 +0200 Subject: [PATCH] [jOOQ/jOOQ#10644] Add Settings.transformUnneededArithmeticExpressions to optimise arithmetic prior to SQL generation (WIP) --- .../src/main/java/org/jooq/conf/Settings.java | 60 +++++++++++++++++-- .../java/org/jooq/conf/SettingsTools.java | 9 +++ ...ransformUnneededArithmeticExpressions.java | 40 +++++++++++++ .../java/org/jooq/impl/AbstractField.java | 8 +-- jOOQ/src/main/java/org/jooq/impl/Concat.java | 2 +- jOOQ/src/main/java/org/jooq/impl/DSL.java | 16 ++--- .../main/java/org/jooq/impl/Expression.java | 52 +++++++++++++++- .../java/org/jooq/impl/GenerateSeries.java | 3 + .../src/main/java/org/jooq/impl/Internal.java | 25 ++++++++ jOOQ/src/main/java/org/jooq/impl/Mod.java | 2 +- jOOQ/src/main/java/org/jooq/impl/Tools.java | 6 ++ .../resources/xsd/jooq-runtime-3.14.0.xsd | 27 ++++++++- 12 files changed, 227 insertions(+), 23 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/conf/TransformUnneededArithmeticExpressions.java diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index d4d66fbf7f..8cff54b40b 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -94,6 +94,9 @@ public class Settings protected Boolean transformAnsiJoinToTableLists = false; @XmlElement(defaultValue = "false") protected Boolean transformTableListsToAnsiJoin = false; + @XmlElement(defaultValue = "NEVER") + @XmlSchemaType(name = "string") + protected TransformUnneededArithmeticExpressions transformUnneededArithmeticExpressions = TransformUnneededArithmeticExpressions.NEVER; @XmlElement(defaultValue = "DEFAULT") @XmlSchemaType(name = "string") protected BackslashEscaping backslashEscaping = BackslashEscaping.DEFAULT; @@ -778,7 +781,7 @@ public class Settings } /** - * Transform ANSI join to table lists if possible + * Transform ANSI join to table lists if possible. *

* Historically, prior to ANSI join syntax, joins were implemented by listing tables in * the FROM clause and providing join predicates in the WHERE clause, possibly using vendor specific @@ -813,7 +816,7 @@ public class Settings } /** - * Transform table lists to ANSI join if possible + * Transform table lists to ANSI join if possible. *

* (Very) historically, prior to ANSI join syntax, joins were implemented by listing tables in * the FROM clause and providing join predicates in the WHERE clause, possibly using vendor specific @@ -821,8 +824,6 @@ public class Settings * support. Migrating such join syntax is tedious. The jOOQ parser can parse the old syntax and * this flag enables the transformation to ANSI join syntax. *

- * This flag has not been implemented yet! - *

* This feature is available in the commercial distribution only. * * @return @@ -846,6 +847,32 @@ public class Settings this.transformTableListsToAnsiJoin = value; } + /** + * Transform arithmetic expressions on literals and bind variables. + *

+ * Arithmetic expressions may be implemented by the user, or arise from emulations from within jOOQ. + * Expressions on literals and bind variables could be evaluated in the client prior to generating SQL. + *

+ * This feature is available in the commercial distribution only. + * + */ + public TransformUnneededArithmeticExpressions getTransformUnneededArithmeticExpressions() { + return transformUnneededArithmeticExpressions; + } + + /** + * Transform arithmetic expressions on literals and bind variables. + *

+ * Arithmetic expressions may be implemented by the user, or arise from emulations from within jOOQ. + * Expressions on literals and bind variables could be evaluated in the client prior to generating SQL. + *

+ * This feature is available in the commercial distribution only. + * + */ + public void setTransformUnneededArithmeticExpressions(TransformUnneededArithmeticExpressions value) { + this.transformUnneededArithmeticExpressions = value; + } + /** * Whether string literals should be escaped with backslash. * @@ -2405,6 +2432,20 @@ public class Settings return this; } + /** + * Transform arithmetic expressions on literals and bind variables. + *

+ * Arithmetic expressions may be implemented by the user, or arise from emulations from within jOOQ. + * Expressions on literals and bind variables could be evaluated in the client prior to generating SQL. + *

+ * This feature is available in the commercial distribution only. + * + */ + public Settings withTransformUnneededArithmeticExpressions(TransformUnneededArithmeticExpressions value) { + setTransformUnneededArithmeticExpressions(value); + return this; + } + /** * Whether string literals should be escaped with backslash. * @@ -2977,6 +3018,7 @@ public class Settings builder.append("fetchTriggerValuesAfterSQLServerOutput", fetchTriggerValuesAfterSQLServerOutput); builder.append("transformAnsiJoinToTableLists", transformAnsiJoinToTableLists); builder.append("transformTableListsToAnsiJoin", transformTableListsToAnsiJoin); + builder.append("transformUnneededArithmeticExpressions", transformUnneededArithmeticExpressions); builder.append("backslashEscaping", backslashEscaping); builder.append("paramType", paramType); builder.append("paramCastMode", paramCastMode); @@ -3282,6 +3324,15 @@ public class Settings return false; } } + if (transformUnneededArithmeticExpressions == null) { + if (other.transformUnneededArithmeticExpressions!= null) { + return false; + } + } else { + if (!transformUnneededArithmeticExpressions.equals(other.transformUnneededArithmeticExpressions)) { + return false; + } + } if (backslashEscaping == null) { if (other.backslashEscaping!= null) { return false; @@ -3925,6 +3976,7 @@ public class Settings result = ((prime*result)+((fetchTriggerValuesAfterSQLServerOutput == null)? 0 :fetchTriggerValuesAfterSQLServerOutput.hashCode())); result = ((prime*result)+((transformAnsiJoinToTableLists == null)? 0 :transformAnsiJoinToTableLists.hashCode())); result = ((prime*result)+((transformTableListsToAnsiJoin == null)? 0 :transformTableListsToAnsiJoin.hashCode())); + result = ((prime*result)+((transformUnneededArithmeticExpressions == null)? 0 :transformUnneededArithmeticExpressions.hashCode())); result = ((prime*result)+((backslashEscaping == null)? 0 :backslashEscaping.hashCode())); result = ((prime*result)+((paramType == null)? 0 :paramType.hashCode())); result = ((prime*result)+((paramCastMode == null)? 0 :paramCastMode.hashCode())); diff --git a/jOOQ/src/main/java/org/jooq/conf/SettingsTools.java b/jOOQ/src/main/java/org/jooq/conf/SettingsTools.java index ab05600b39..6da319ed4e 100644 --- a/jOOQ/src/main/java/org/jooq/conf/SettingsTools.java +++ b/jOOQ/src/main/java/org/jooq/conf/SettingsTools.java @@ -296,6 +296,15 @@ public final class SettingsTools { return result == null ? ExecuteWithoutWhere.LOG_DEBUG : result; } + /** + * Lazy access to {@link Settings#getTransformUnneededArithmeticExpressions()}. + */ + public static final TransformUnneededArithmeticExpressions getTransformUnneededArithmeticExpressions(Settings settings) { + return settings.getTransformUnneededArithmeticExpressions() != null + ? settings.getTransformUnneededArithmeticExpressions() + : TransformUnneededArithmeticExpressions.NEVER; + } + /** * Retrieve the configured default settings. *

diff --git a/jOOQ/src/main/java/org/jooq/conf/TransformUnneededArithmeticExpressions.java b/jOOQ/src/main/java/org/jooq/conf/TransformUnneededArithmeticExpressions.java new file mode 100644 index 0000000000..d85b86e0ce --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/conf/TransformUnneededArithmeticExpressions.java @@ -0,0 +1,40 @@ + +package org.jooq.conf; + +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for TransformUnneededArithmeticExpressions. + * + *

The following schema fragment specifies the expected content contained within this class. + *

+ *

+ * <simpleType name="TransformUnneededArithmeticExpressions">
+ *   <restriction base="{http://www.w3.org/2001/XMLSchema}string">
+ *     <enumeration value="NEVER"/>
+ *     <enumeration value="INTERNAL"/>
+ *     <enumeration value="ALWAYS"/>
+ *   </restriction>
+ * </simpleType>
+ * 
+ * + */ +@XmlType(name = "TransformUnneededArithmeticExpressions") +@XmlEnum +public enum TransformUnneededArithmeticExpressions { + + NEVER, + INTERNAL, + ALWAYS; + + public String value() { + return name(); + } + + public static TransformUnneededArithmeticExpressions fromValue(String v) { + return valueOf(v); + } + +} diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java index 91069b1b36..44f98eca36 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java @@ -353,7 +353,7 @@ abstract class AbstractField extends AbstractTypedNamed implements Field add(Field value) { - return new Expression<>(ADD, this, nullSafe(value, getDataType())); + return new Expression<>(ADD, false, this, nullSafe(value, getDataType())); } @Override @@ -363,7 +363,7 @@ abstract class AbstractField extends AbstractTypedNamed implements Field sub(Field value) { - return new Expression<>(SUBTRACT, this, nullSafe(value, getDataType())); + return new Expression<>(SUBTRACT, false, this, nullSafe(value, getDataType())); } @Override @@ -377,7 +377,7 @@ abstract class AbstractField extends AbstractTypedNamed implements Field mul(Field value) { - return new Expression<>(MULTIPLY, this, nullSafe(value, getDataType())); + return new Expression<>(MULTIPLY, false, this, nullSafe(value, getDataType())); } @Override @@ -387,7 +387,7 @@ abstract class AbstractField extends AbstractTypedNamed implements Field div(Field value) { - return new Expression<>(DIVIDE, this, nullSafe(value, getDataType())); + return new Expression<>(DIVIDE, false, this, nullSafe(value, getDataType())); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/Concat.java b/jOOQ/src/main/java/org/jooq/impl/Concat.java index e91bca39c1..b2704d5ff7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Concat.java +++ b/jOOQ/src/main/java/org/jooq/impl/Concat.java @@ -106,7 +106,7 @@ final class Concat extends AbstractField { default: - ctx.visit(new Expression<>(CONCAT, first, others)); + ctx.visit(new Expression<>(CONCAT, false, first, others)); break; } } diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index 8da1b690e7..26c5d3902f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -18906,7 +18906,7 @@ public class DSL { @NotNull @Support({ CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE }) public static Field bitAnd(Field field1, Field field2) { - return new Expression<>(ExpressionOperator.BIT_AND, nullSafe(field1), nullSafe(field2)); + return new Expression<>(ExpressionOperator.BIT_AND, false, nullSafe(field1), nullSafe(field2)); } /** @@ -18960,7 +18960,7 @@ public class DSL { @NotNull @Support({ CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE }) public static Field bitNand(Field field1, Field field2) { - return new Expression<>(ExpressionOperator.BIT_NAND, nullSafe(field1), nullSafe(field2)); + return new Expression<>(ExpressionOperator.BIT_NAND, false, nullSafe(field1), nullSafe(field2)); } /** @@ -19009,7 +19009,7 @@ public class DSL { @NotNull @Support({ CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE }) public static Field bitOr(Field field1, Field field2) { - return new Expression<>(ExpressionOperator.BIT_OR, nullSafe(field1), nullSafe(field2)); + return new Expression<>(ExpressionOperator.BIT_OR, false, nullSafe(field1), nullSafe(field2)); } /** @@ -19061,7 +19061,7 @@ public class DSL { @NotNull @Support({ CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE }) public static Field bitNor(Field field1, Field field2) { - return new Expression<>(ExpressionOperator.BIT_NOR, nullSafe(field1), nullSafe(field2)); + return new Expression<>(ExpressionOperator.BIT_NOR, false, nullSafe(field1), nullSafe(field2)); } /** @@ -19110,7 +19110,7 @@ public class DSL { @NotNull @Support({ CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE }) public static Field bitXor(Field field1, Field field2) { - return new Expression<>(ExpressionOperator.BIT_XOR, nullSafe(field1), nullSafe(field2)); + return new Expression<>(ExpressionOperator.BIT_XOR, false, nullSafe(field1), nullSafe(field2)); } /** @@ -19162,7 +19162,7 @@ public class DSL { @NotNull @Support({ CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE }) public static Field bitXNor(Field field1, Field field2) { - return new Expression<>(ExpressionOperator.BIT_XNOR, nullSafe(field1), nullSafe(field2)); + return new Expression<>(ExpressionOperator.BIT_XNOR, false, nullSafe(field1), nullSafe(field2)); } /** @@ -19213,7 +19213,7 @@ public class DSL { @NotNull @Support({ CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE }) public static Field shl(Field field1, Field field2) { - return new Expression<>(ExpressionOperator.SHL, nullSafe(field1), nullSafe(field2)); + return new Expression<>(ExpressionOperator.SHL, false, nullSafe(field1), nullSafe(field2)); } /** @@ -19264,7 +19264,7 @@ public class DSL { @NotNull @Support({ CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE }) public static Field shr(Field field1, Field field2) { - return new Expression<>(ExpressionOperator.SHR, nullSafe(field1), nullSafe(field2)); + return new Expression<>(ExpressionOperator.SHR, false, nullSafe(field1), nullSafe(field2)); } // ------------------------------------------------------------------------ diff --git a/jOOQ/src/main/java/org/jooq/impl/Expression.java b/jOOQ/src/main/java/org/jooq/impl/Expression.java index d4da86bfb0..3f89a15115 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Expression.java +++ b/jOOQ/src/main/java/org/jooq/impl/Expression.java @@ -59,6 +59,7 @@ import static org.jooq.SQLDialect.SQLITE; // ... // ... // ... +import static org.jooq.conf.SettingsTools.getTransformUnneededArithmeticExpressions; import static org.jooq.impl.DSL.function; import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.keyword; @@ -98,6 +99,7 @@ import static org.jooq.impl.Names.N_SQL_TSI_SECOND; import static org.jooq.impl.Names.N_STRFTIME; import static org.jooq.impl.Names.N_TIMESTAMPADD; import static org.jooq.impl.QueryPartListView.wrap; +import static org.jooq.impl.Tools.EMPTY_FIELD; import static org.jooq.impl.Tools.castIfNeeded; import java.sql.Timestamp; @@ -109,8 +111,11 @@ import org.jooq.DataType; import org.jooq.DatePart; import org.jooq.Field; import org.jooq.Param; +// ... import org.jooq.SQLDialect; +import org.jooq.conf.TransformUnneededArithmeticExpressions; import org.jooq.exception.DataTypeException; +import org.jooq.impl.Tools.DataExtendedKey; import org.jooq.types.DayToSecond; import org.jooq.types.Interval; import org.jooq.types.YearToMonth; @@ -129,15 +134,17 @@ final class Expression extends AbstractField { private static final Set HASH_OP_FOR_BIT_XOR = SQLDialect.supportedBy(POSTGRES); private static final Set SUPPORT_YEAR_TO_SECOND = SQLDialect.supportedBy(POSTGRES); + private final ExpressionOperator operator; + private final boolean internal; private final Field lhs; private final QueryPartList> rhs; private final Field[] arguments; - private final ExpressionOperator operator; - Expression(ExpressionOperator operator, Field lhs, Field... rhs) { + Expression(ExpressionOperator operator, boolean internal, Field lhs, Field... rhs) { super(DSL.name(operator.toSQL()), lhs.getDataType()); this.operator = operator; + this.internal = internal; this.lhs = lhs; this.rhs = new QueryPartList<>(rhs); this.arguments = Tools.combine(lhs, rhs); @@ -166,6 +173,17 @@ final class Expression extends AbstractField { @SuppressWarnings("unchecked") @Override public final void accept(Context ctx) { + + + + + + + + + + + SQLDialect family = ctx.family(); // --------------------------------------------------------------------- @@ -253,6 +271,36 @@ final class Expression extends AbstractField { ctx.visit(new DefaultExpression<>(lhs, operator, rhs)); } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /** * In some expressions, the lhs can be safely assumed to be a single number */ diff --git a/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java b/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java index 39364e7302..0b4fbcf6a9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java +++ b/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java @@ -43,6 +43,9 @@ import static org.jooq.impl.DSL.one; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.unquotedName; import static org.jooq.impl.DSL.withRecursive; +import static org.jooq.impl.Internal.add; +import static org.jooq.impl.Internal.mul; +import static org.jooq.impl.Internal.sub; import static org.jooq.impl.Names.N_GENERATE_SERIES; import static org.jooq.impl.Names.N_SYSTEM_RANGE; import static org.jooq.impl.SQLDataType.INTEGER; diff --git a/jOOQ/src/main/java/org/jooq/impl/Internal.java b/jOOQ/src/main/java/org/jooq/impl/Internal.java index 1b0f1f31ec..302a411aab 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Internal.java +++ b/jOOQ/src/main/java/org/jooq/impl/Internal.java @@ -37,12 +37,16 @@ */ package org.jooq.impl; +import static org.jooq.impl.DSL.nullSafe; +import static org.jooq.impl.ExpressionOperator.*; + import org.jooq.Binding; import org.jooq.Check; import org.jooq.Converter; import org.jooq.DataType; import org.jooq.Domain; import org.jooq.EmbeddableRecord; +import org.jooq.Field; import org.jooq.ForeignKey; import org.jooq.Identity; import org.jooq.Index; @@ -53,6 +57,7 @@ import org.jooq.Parameter; import org.jooq.Record; import org.jooq.Schema; import org.jooq.Sequence; +import org.jooq.Support; import org.jooq.Table; import org.jooq.TableField; import org.jooq.UDT; @@ -357,4 +362,24 @@ public final class Internal { public static final > TableField[] fields(TableField embeddableField) { return ((EmbeddableTableField) embeddableField).fields; } + + @Support + static final Field add(Field lhs, Field rhs) { + return new Expression<>(ADD, true, lhs, nullSafe(rhs, lhs.getDataType())); + } + + @Support + static final Field sub(Field lhs, Field rhs) { + return new Expression<>(SUBTRACT, true, lhs, nullSafe(rhs, lhs.getDataType())); + } + + @Support + static final Field mul(Field lhs, Field rhs) { + return new Expression<>(MULTIPLY, true, lhs, nullSafe(rhs, lhs.getDataType())); + } + + @Support + static final Field div(Field lhs, Field rhs) { + return new Expression<>(DIVIDE, true, lhs, nullSafe(rhs, lhs.getDataType())); + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Mod.java b/jOOQ/src/main/java/org/jooq/impl/Mod.java index d7222f33d7..9ec8c994e1 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Mod.java +++ b/jOOQ/src/main/java/org/jooq/impl/Mod.java @@ -79,7 +79,7 @@ final class Mod extends AbstractField { case SQLITE: - ctx.visit(new Expression<>(MODULO, arg1, arg2)); + ctx.visit(new Expression<>(MODULO, false, arg1, arg2)); break; default: ctx.visit(K_MOD).sql('(').visit(arg1).sql(", ").visit(arg2).sql(')'); diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 87e26ba4a9..8e718ec23d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -654,6 +654,12 @@ final class Tools { + + + + + + diff --git a/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd b/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd index f6bdcbe795..1ddb7e8bd3 100644 --- a/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd +++ b/jOOQ/src/main/resources/xsd/jooq-runtime-3.14.0.xsd @@ -190,7 +190,7 @@ For details, see https://gith - Historically, prior to ANSI join syntax, joins were implemented by listing tables in the FROM clause and providing join predicates in the WHERE clause, possibly using vendor specific @@ -205,7 +205,7 @@ This feature is available in the commercial distribution only.]]>< - (Very) historically, prior to ANSI join syntax, joins were implemented by listing tables in the FROM clause and providing join predicates in the WHERE clause, possibly using vendor specific @@ -213,7 +213,14 @@ operators like (+) (Oracle, DB2) or *= (SQL Server) fo support. Migrating such join syntax is tedious. The jOOQ parser can parse the old syntax and this flag enables the transformation to ANSI join syntax.

-This flag has not been implemented yet! +This feature is available in the commercial distribution only.]]> + + + + +Arithmetic expressions may be implemented by the user, or arise from emulations from within jOOQ. +Expressions on literals and bind variables could be evaluated in the client prior to generating SQL.

This feature is available in the commercial distribution only.]]> @@ -1091,4 +1098,18 @@ Either <input/> or <inputExpression/> must be provided]]> + + + + + + + + + + + + + + \ No newline at end of file