[jOOQ/jOOQ#10644] Add Settings.transformUnneededArithmeticExpressions to optimise arithmetic prior to SQL generation (WIP)

This commit is contained in:
Lukas Eder 2020-09-22 10:28:35 +02:00
parent 74c06bf825
commit e3d0cc483d
12 changed files with 227 additions and 23 deletions

View File

@ -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.
* <p>
* 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.
* <p>
* (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.
* <p>
* This flag has not been implemented yet!
* <p>
* 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.
* <p>
* 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.
* <p>
* This feature is available in the commercial distribution only.
*
*/
public TransformUnneededArithmeticExpressions getTransformUnneededArithmeticExpressions() {
return transformUnneededArithmeticExpressions;
}
/**
* Transform arithmetic expressions on literals and bind variables.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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()));

View File

@ -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.
* <p>

View File

@ -0,0 +1,40 @@
package org.jooq.conf;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for TransformUnneededArithmeticExpressions.
*
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* &lt;simpleType name="TransformUnneededArithmeticExpressions"&gt;
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}string"&gt;
* &lt;enumeration value="NEVER"/&gt;
* &lt;enumeration value="INTERNAL"/&gt;
* &lt;enumeration value="ALWAYS"/&gt;
* &lt;/restriction&gt;
* &lt;/simpleType&gt;
* </pre>
*
*/
@XmlType(name = "TransformUnneededArithmeticExpressions")
@XmlEnum
public enum TransformUnneededArithmeticExpressions {
NEVER,
INTERNAL,
ALWAYS;
public String value() {
return name();
}
public static TransformUnneededArithmeticExpressions fromValue(String v) {
return valueOf(v);
}
}

View File

@ -353,7 +353,7 @@ abstract class AbstractField<T> extends AbstractTypedNamed<T> implements Field<T
*/
@Override
public Field<T> 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<T> extends AbstractTypedNamed<T> implements Field<T
@Override
public final Field<T> 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<T> extends AbstractTypedNamed<T> implements Field<T
*/
@Override
public Field<T> mul(Field<? extends Number> 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<T> extends AbstractTypedNamed<T> implements Field<T
@Override
public final Field<T> div(Field<? extends Number> value) {
return new Expression<>(DIVIDE, this, nullSafe(value, getDataType()));
return new Expression<>(DIVIDE, false, this, nullSafe(value, getDataType()));
}
@Override

View File

@ -106,7 +106,7 @@ final class Concat extends AbstractField<String> {
default:
ctx.visit(new Expression<>(CONCAT, first, others));
ctx.visit(new Expression<>(CONCAT, false, first, others));
break;
}
}

View File

@ -18906,7 +18906,7 @@ public class DSL {
@NotNull
@Support({ CUBRID, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE })
public static <T extends Number> Field<T> bitAnd(Field<T> field1, Field<T> 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 <T extends Number> Field<T> bitNand(Field<T> field1, Field<T> 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 <T extends Number> Field<T> bitOr(Field<T> field1, Field<T> 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 <T extends Number> Field<T> bitNor(Field<T> field1, Field<T> 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 <T extends Number> Field<T> bitXor(Field<T> field1, Field<T> 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 <T extends Number> Field<T> bitXNor(Field<T> field1, Field<T> 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 <T extends Number> Field<T> shl(Field<T> field1, Field<? extends Number> 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 <T extends Number> Field<T> shr(Field<T> field1, Field<? extends Number> field2) {
return new Expression<>(ExpressionOperator.SHR, nullSafe(field1), nullSafe(field2));
return new Expression<>(ExpressionOperator.SHR, false, nullSafe(field1), nullSafe(field2));
}
// ------------------------------------------------------------------------

View File

@ -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<T> extends AbstractField<T> {
private static final Set<SQLDialect> HASH_OP_FOR_BIT_XOR = SQLDialect.supportedBy(POSTGRES);
private static final Set<SQLDialect> SUPPORT_YEAR_TO_SECOND = SQLDialect.supportedBy(POSTGRES);
private final ExpressionOperator operator;
private final boolean internal;
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) {
Expression(ExpressionOperator operator, boolean internal, Field<T> 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<T> extends AbstractField<T> {
@SuppressWarnings("unchecked")
@Override
public final void accept(Context<?> ctx) {
SQLDialect family = ctx.family();
// ---------------------------------------------------------------------
@ -253,6 +271,36 @@ final class Expression<T> extends AbstractField<T> {
ctx.visit(new DefaultExpression<>(lhs, operator, rhs));
}
/**
* In some expressions, the lhs can be safely assumed to be a single number
*/

View File

@ -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;

View File

@ -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 <R extends Record, ER extends EmbeddableRecord<ER>> TableField<R, ?>[] fields(TableField<R, ER> embeddableField) {
return ((EmbeddableTableField<R, ER>) embeddableField).fields;
}
@Support
static final <T> Field<T> add(Field<T> lhs, Field<T> rhs) {
return new Expression<>(ADD, true, lhs, nullSafe(rhs, lhs.getDataType()));
}
@Support
static final <T> Field<T> sub(Field<T> lhs, Field<T> rhs) {
return new Expression<>(SUBTRACT, true, lhs, nullSafe(rhs, lhs.getDataType()));
}
@Support
static final <T> Field<T> mul(Field<T> lhs, Field<T> rhs) {
return new Expression<>(MULTIPLY, true, lhs, nullSafe(rhs, lhs.getDataType()));
}
@Support
static final <T> Field<T> div(Field<T> lhs, Field<T> rhs) {
return new Expression<>(DIVIDE, true, lhs, nullSafe(rhs, lhs.getDataType()));
}
}

View File

@ -79,7 +79,7 @@ final class Mod<T> extends AbstractField<T> {
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(')');

View File

@ -654,6 +654,12 @@ final class Tools {

View File

@ -190,7 +190,7 @@ For details, see <a href="https://github.com/jOOQ/jOOQ/issues/4498">https://gith
</element>
<element name="transformAnsiJoinToTableLists" type="boolean" minOccurs="0" maxOccurs="1" default="false">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Transform ANSI join to table lists if possible
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Transform ANSI join to table lists if possible.
<p>
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.]]></jxb:javadoc><
</element>
<element name="transformTableListsToAnsiJoin" type="boolean" minOccurs="0" maxOccurs="1" default="false">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Transform table lists to ANSI join if possible
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Transform table lists to ANSI join if possible.
<p>
(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 <code>(+)</code> (Oracle, DB2) or <code>*=</code> (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.
<p>
This flag has not been implemented yet!
This feature is available in the commercial distribution only.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="transformUnneededArithmeticExpressions" type="jooq-runtime:TransformUnneededArithmeticExpressions" minOccurs="0" maxOccurs="1" default="NEVER">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Transform arithmetic expressions on literals and bind variables.
<p>
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.
<p>
This feature is available in the commercial distribution only.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
@ -1091,4 +1098,18 @@ Either &lt;input/&gt; or &lt;inputExpression/&gt; must be provided]]></jxb:javad
<enumeration value="SET_NON_PRIMARY_KEY_TO_RECORD_VALUES"/>
</restriction>
</simpleType>
<simpleType name="TransformUnneededArithmeticExpressions">
<restriction base="string">
<!-- Never transform unneeded arithmetic expressions -->
<enumeration value="NEVER"/>
<!-- Transform arithmetic expressions arising from jOOQ's internals -->
<enumeration value="INTERNAL"/>
<!-- Transform all arithmetic expressions -->
<enumeration value="ALWAYS"/>
</restriction>
</simpleType>
</schema>