[jOOQ/jOOQ#12028] Add MULTISET_AGG aggregate and window function support

This commit is contained in:
Lukas Eder 2021-06-21 11:32:33 +02:00
parent 1526cab8ee
commit 887518ee24
9 changed files with 1466 additions and 95 deletions

View File

@ -244,13 +244,7 @@ public class Settings
protected Boolean emulateOnDuplicateKeyUpdateOnPrimaryKeyOnly = false;
@XmlElement(defaultValue = "DEFAULT")
@XmlSchemaType(name = "string")
protected NestedCollectionEmulation emulateList = NestedCollectionEmulation.DEFAULT;
@XmlElement(defaultValue = "DEFAULT")
@XmlSchemaType(name = "string")
protected NestedCollectionEmulation emulateMultiset = NestedCollectionEmulation.DEFAULT;
@XmlElement(defaultValue = "DEFAULT")
@XmlSchemaType(name = "string")
protected NestedCollectionEmulation emulateSet = NestedCollectionEmulation.DEFAULT;
@XmlElement(defaultValue = "LOG_DEBUG")
@XmlSchemaType(name = "string")
protected ExecuteWithoutWhere executeUpdateWithoutWhere = ExecuteWithoutWhere.LOG_DEBUG;
@ -2281,23 +2275,7 @@ public class Settings
}
/**
* [#3884] Whether <code>LIST</code> support should be emulated.
*
*/
public NestedCollectionEmulation getEmulateList() {
return emulateList;
}
/**
* [#3884] Whether <code>LIST</code> support should be emulated.
*
*/
public void setEmulateList(NestedCollectionEmulation value) {
this.emulateList = value;
}
/**
* [#3884] Whether <code>MULTISET</code> support should be emulated.
* [#3884] How <code>MULTISET</code> support should be emulated.
*
*/
public NestedCollectionEmulation getEmulateMultiset() {
@ -2305,29 +2283,13 @@ public class Settings
}
/**
* [#3884] Whether <code>MULTISET</code> support should be emulated.
* [#3884] How <code>MULTISET</code> support should be emulated.
*
*/
public void setEmulateMultiset(NestedCollectionEmulation value) {
this.emulateMultiset = value;
}
/**
* [#3884] Whether <code>SET</code> support should be emulated.
*
*/
public NestedCollectionEmulation getEmulateSet() {
return emulateSet;
}
/**
* [#3884] Whether <code>SET</code> support should be emulated.
*
*/
public void setEmulateSet(NestedCollectionEmulation value) {
this.emulateSet = value;
}
/**
* [#6771] Specifies whether UPDATE statements are allowed to be executed lacking a WHERE clause. This has no effect on rendering the statements SQL string.
*
@ -3676,16 +3638,7 @@ public class Settings
}
/**
* [#3884] Whether <code>LIST</code> support should be emulated.
*
*/
public Settings withEmulateList(NestedCollectionEmulation value) {
setEmulateList(value);
return this;
}
/**
* [#3884] Whether <code>MULTISET</code> support should be emulated.
* [#3884] How <code>MULTISET</code> support should be emulated.
*
*/
public Settings withEmulateMultiset(NestedCollectionEmulation value) {
@ -3693,15 +3646,6 @@ public class Settings
return this;
}
/**
* [#3884] Whether <code>SET</code> support should be emulated.
*
*/
public Settings withEmulateSet(NestedCollectionEmulation value) {
setEmulateSet(value);
return this;
}
/**
* [#6771] Specifies whether UPDATE statements are allowed to be executed lacking a WHERE clause. This has no effect on rendering the statements SQL string.
*
@ -4090,9 +4034,7 @@ public class Settings
builder.append("inListPadBase", inListPadBase);
builder.append("delimiter", delimiter);
builder.append("emulateOnDuplicateKeyUpdateOnPrimaryKeyOnly", emulateOnDuplicateKeyUpdateOnPrimaryKeyOnly);
builder.append("emulateList", emulateList);
builder.append("emulateMultiset", emulateMultiset);
builder.append("emulateSet", emulateSet);
builder.append("executeUpdateWithoutWhere", executeUpdateWithoutWhere);
builder.append("executeDeleteWithoutWhere", executeDeleteWithoutWhere);
builder.append("interpreterDialect", interpreterDialect);
@ -4921,15 +4863,6 @@ public class Settings
return false;
}
}
if (emulateList == null) {
if (other.emulateList!= null) {
return false;
}
} else {
if (!emulateList.equals(other.emulateList)) {
return false;
}
}
if (emulateMultiset == null) {
if (other.emulateMultiset!= null) {
return false;
@ -4939,15 +4872,6 @@ public class Settings
return false;
}
}
if (emulateSet == null) {
if (other.emulateSet!= null) {
return false;
}
} else {
if (!emulateSet.equals(other.emulateSet)) {
return false;
}
}
if (executeUpdateWithoutWhere == null) {
if (other.executeUpdateWithoutWhere!= null) {
return false;
@ -5338,9 +5262,7 @@ public class Settings
result = ((prime*result)+((inListPadBase == null)? 0 :inListPadBase.hashCode()));
result = ((prime*result)+((delimiter == null)? 0 :delimiter.hashCode()));
result = ((prime*result)+((emulateOnDuplicateKeyUpdateOnPrimaryKeyOnly == null)? 0 :emulateOnDuplicateKeyUpdateOnPrimaryKeyOnly.hashCode()));
result = ((prime*result)+((emulateList == null)? 0 :emulateList.hashCode()));
result = ((prime*result)+((emulateMultiset == null)? 0 :emulateMultiset.hashCode()));
result = ((prime*result)+((emulateSet == null)? 0 :emulateSet.hashCode()));
result = ((prime*result)+((executeUpdateWithoutWhere == null)? 0 :executeUpdateWithoutWhere.hashCode()));
result = ((prime*result)+((executeDeleteWithoutWhere == null)? 0 :executeDeleteWithoutWhere.hashCode()));
result = ((prime*result)+((interpreterDialect == null)? 0 :interpreterDialect.hashCode()));

View File

@ -60,6 +60,7 @@ import static org.jooq.impl.QueryPartCollectionView.wrap;
import static org.jooq.impl.SQLDataType.DOUBLE;
import static org.jooq.impl.SQLDataType.NUMERIC;
import static org.jooq.impl.Tools.camelCase;
import static org.jooq.impl.Tools.isEmpty;
import java.util.Arrays;
import java.util.Collection;
@ -369,6 +370,15 @@ implements
return DSL.nullif(fo(function), (Field<U>) zero());
}
/**
* Apply this aggregate function's <code>ORDER BY</code>,
* <code>FILTER</code> and <code>OVER</code> clauses to an argument
* aggregate function.
*/
final <U> Field<U> ofo(AbstractAggregateFunction<U> function) {
return fo(isEmpty(withinGroupOrderBy) ? function : function.orderBy(withinGroupOrderBy));
}
/**
* Apply this aggregate function's <code>FILTER</code> and <code>OVER</code>
* clauses to an argument aggregate function.

View File

@ -37,7 +37,6 @@
*/
package org.jooq.impl;
// ...
import static org.jooq.impl.Names.N_ARRAY_AGG;
import org.jooq.Context;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,128 @@
/*
* 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.impl.DSL.jsonArray;
import static org.jooq.impl.DSL.jsonArrayAgg;
import static org.jooq.impl.DSL.jsonObject;
import static org.jooq.impl.DSL.jsonbArray;
import static org.jooq.impl.DSL.jsonbArrayAgg;
import static org.jooq.impl.DSL.jsonbObject;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.DSL.selectFrom;
import static org.jooq.impl.DSL.xmlagg;
import static org.jooq.impl.DSL.xmlelement;
import static org.jooq.impl.Keywords.K_MULTISET;
import static org.jooq.impl.Names.N_ARRAY_AGG;
import static org.jooq.impl.Names.N_MULTISET_AGG;
import static org.jooq.impl.Names.N_RECORD;
import static org.jooq.impl.Names.N_RESULT;
import static org.jooq.impl.Tools.emulateMultiset;
import static org.jooq.impl.Tools.map;
import static org.jooq.impl.Tools.visitSubquery;
import org.jooq.AggregateFunction;
import org.jooq.Context;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.Row;
import org.jooq.SelectField;
import org.jooq.Table;
/**
* @author Lukas Eder
*/
final class MultisetAgg<R extends Record> extends DefaultAggregateFunction<Result<R>> {
private final AbstractRow<R> row;
MultisetAgg(boolean distinct, SelectField<R> row) {
super(distinct, N_MULTISET_AGG, new MultisetDataType<>((AbstractRow<R>) row, null), (((AbstractRow<R>) row).fields()));
this.row = (AbstractRow<R>) row;
}
@Override
public final void accept(Context<?> ctx) {
switch (emulateMultiset(ctx.configuration())) {
case JSON: {
ctx.visit(ofo((AbstractAggregateFunction<?>) jsonArrayAgg(jsonObject(row.fields()))));
break;
}
case JSONB: {
ctx.visit(ofo((AbstractAggregateFunction<?>) jsonbArrayAgg(jsonObject(row.fields()))));
break;
}
case XML: {
ctx.visit(xmlelement(N_RESULT,
ofo((AbstractAggregateFunction<?>)
xmlagg(xmlelement(N_RECORD,
map(row.fields(), f -> xmlelement(f.getUnqualifiedName(), f))
))
)
));
break;
}
case NATIVE:
ctx.visit(N_MULTISET_AGG).sql('(');
acceptArguments1(ctx, new QueryPartListView<>(arguments.get(0)));
acceptOrderBy(ctx);
ctx.sql(')');
acceptFilterClause(ctx);
acceptOverClause(ctx);
break;
}
}
}

View File

@ -39,8 +39,6 @@ package org.jooq.impl;
import static org.jooq.impl.Tools.CTX;
import static org.jooq.impl.Tools.newRecord;
import static org.jooq.impl.Tools.recordType;
import static org.jooq.impl.Tools.row0;
import java.util.List;
@ -51,8 +49,6 @@ import org.jooq.Nullability;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.Row;
import org.jooq.Select;
import org.jooq.impl.AbstractRecord.TransferRecordState;
/**
* A wrapper for anonymous multiset data types.

View File

@ -248,6 +248,7 @@ final class Names {
static final Name N_MODE = unquotedName("mode");
static final Name N_MUL = unquotedName("mul");
static final Name N_MULTISET = unquotedName("multiset");
static final Name N_MULTISET_AGG = unquotedName("multiset_agg");
static final Name N_NANO100_BETWEEN = unquotedName("nano100_between");
static final Name N_NEWID = unquotedName("newid");
static final Name N_NEXTVAL = unquotedName("nextval");

View File

@ -10422,6 +10422,8 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
over = filter = parseOrderedSetFunctionIf();
if (filter == null && !basic)
over = filter = parseArrayAggFunctionIf();
if (filter == null && !basic)
over = filter = parseMultisetAggFunctionIf();
if (filter == null && !basic)
over = filter = parseXMLAggFunctionIf();
if (filter == null && !basic)
@ -10847,6 +10849,24 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
return null;
}
private final AggregateFilterStep<?> parseMultisetAggFunctionIf() {
if (parseKeywordIf("MULTISET_AGG")) {
parse('(');
List<Field<?>> fields = parseList(',', ParseContext::parseField);
List<SortField<?>> sort = null;
if (parseKeywordIf("ORDER BY"))
sort = parseList(',', ParseContext::parseSortField);
parse(')');
ArrayAggOrderByStep<?> s1 = multisetAgg(fields);
return sort == null ? s1 : s1.orderBy(sort);
}
return null;
}
private final List<SortField<?>> parseWithinGroupN() {
return parseWithinGroupN(false);
}

View File

@ -552,16 +552,8 @@ jOOQ queries, for which no specific fetchSize value was specified.]]></jxb:javad
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[[#6462] Use only the primary key to emulate MySQL's INSERT .. ON DUPLICATE KEY UPDATE statement. In MySQL, the statement considers all unique keys for duplicates to apply an update rather than an insert. Earlier versions of jOOQ considered only the PRIMARY KEY. This flag can be turned on to maintain backwards compatibility.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="emulateList" type="jooq-runtime:NestedCollectionEmulation" minOccurs="0" maxOccurs="1" default="DEFAULT">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[[#3884] Whether <code>LIST</code> support should be emulated.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="emulateMultiset" type="jooq-runtime:NestedCollectionEmulation" minOccurs="0" maxOccurs="1" default="DEFAULT">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[[#3884] Whether <code>MULTISET</code> support should be emulated.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="emulateSet" type="jooq-runtime:NestedCollectionEmulation" minOccurs="0" maxOccurs="1" default="DEFAULT">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[[#3884] Whether <code>SET</code> support should be emulated.]]></jxb:javadoc></jxb:property></appinfo></annotation>
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[[#3884] How <code>MULTISET</code> support should be emulated.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="executeUpdateWithoutWhere" type="jooq-runtime:ExecuteWithoutWhere" minOccurs="0" maxOccurs="1" default="LOG_DEBUG">