[jOOQ/jOOQ#9879] Add support for client side GENERATED ALWAYS AS - WIP

This commit is contained in:
Lukas Eder 2022-03-23 10:03:44 +01:00
parent 11dbdbb75e
commit 02df3dced5
26 changed files with 704 additions and 127 deletions

View File

@ -548,7 +548,7 @@ class GenerationUtil {
}
static ExpressionType expressionType(String expression) {
if (TYPE_REFERENCE_PATTERN.matcher(expression).matches())
if (!"null".equals(expression) && TYPE_REFERENCE_PATTERN.matcher(expression).matches())
return CONSTRUCTOR_REFERENCE;
else
return EXPRESSION;

View File

@ -5796,6 +5796,20 @@ public class JavaGenerator extends AbstractGenerator {
final String columnName = column.getName();
final List<String> converter = out.ref(list(column.getType(resolver(out)).getConverter()));
final List<String> binding = out.ref(list(column.getType(resolver(out)).getBinding()));
final List<String> generator = new ArrayList<>();
final String columnVisibility =
@ -5807,19 +5821,19 @@ public class JavaGenerator extends AbstractGenerator {
out.javadoc("The column <code>%s</code>.[[before= ][%s]]", column.getQualifiedOutputName(), list(escapeEntities(comment(column))));
if (scala) {
out.println("%sval %s: %s[%s, %s] = createField(%s.name(\"%s\"), %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ")",
columnVisibility, scalaWhitespaceSuffix(columnId), TableField.class, recordType, columnType, DSL.class, columnName, columnTypeRef, escapeString(comment(column)), converter, binding);
out.println("%sval %s: %s[%s, %s] = createField(%s.name(\"%s\"), %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + converterTemplate(generator) + ")",
columnVisibility, scalaWhitespaceSuffix(columnId), TableField.class, recordType, columnType, DSL.class, columnName, columnTypeRef, escapeString(comment(column)), converter, binding, generator);
}
else if (kotlin) {
out.println("%sval %s: %s<%s, %s?> = createField(%s.name(\"%s\"), %s, this, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ")",
columnVisibility, columnId, TableField.class, recordType, columnType, DSL.class, columnName, columnTypeRef, escapeString(comment(column)), converter, binding);
out.println("%sval %s: %s<%s, %s?> = createField(%s.name(\"%s\"), %s, this, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + converterTemplate(generator) + ")",
columnVisibility, columnId, TableField.class, recordType, columnType, DSL.class, columnName, columnTypeRef, escapeString(comment(column)), converter, binding, generator);
}
else {
String isStatic = generateInstanceFields() ? "" : "static ";
String tableRef = generateInstanceFields() ? "this" : out.ref(getStrategy().getJavaIdentifier(table), 2);
out.println("%s%sfinal %s<%s, %s> %s = createField(%s.name(\"%s\"), %s, %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ");",
columnVisibility, isStatic, TableField.class, recordType, columnType, columnId, DSL.class, columnName, columnTypeRef, tableRef, escapeString(comment(column)), converter, binding);
out.println("%s%sfinal %s<%s, %s> %s = createField(%s.name(\"%s\"), %s, %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + converterTemplate(generator) + ");",
columnVisibility, isStatic, TableField.class, recordType, columnType, columnId, DSL.class, columnName, columnTypeRef, tableRef, escapeString(comment(column)), converter, binding, generator);
}
}

View File

@ -1403,8 +1403,8 @@ public abstract class AbstractDatabase implements Database {
}
if (StringUtils.isBlank(type.getName())) {
if (StringUtils.isBlank(type.getUserType())) {
log.warn("Bad configuration for <forcedType/>. Either <name/> or <userType/> is required: " + type);
if (StringUtils.isBlank(type.getUserType()) && StringUtils.isBlank(type.getGenerator())) {
log.warn("Bad configuration for <forcedType/>. Either <name/>, <userType/>, or <generator/> is required: " + type);
it2.remove();
continue;
@ -1412,9 +1412,10 @@ public abstract class AbstractDatabase implements Database {
if (StringUtils.isBlank(type.getBinding()) &&
StringUtils.isBlank(type.getConverter()) &&
StringUtils.isBlank(type.getGenerator()) &&
!Boolean.TRUE.equals(type.isEnumConverter()) &&
type.getLambdaConverter() == null) {
log.warn("Bad configuration for <forcedType/>. Either <binding/> or <converter/> or <enumConverter/> or <lambdaConverter/> is required: " + type);
log.warn("Bad configuration for <forcedType/>. Either <binding/>, <converter/>, <enumConverter/>, <lambdaConverter/>, or <generator/> is required: " + type);
it2.remove();
continue;

View File

@ -210,6 +210,7 @@ public abstract class AbstractTypedElementDefinition<T extends Definition>
ForcedType forcedType = db.getConfiguredForcedType(child, definedType);
if (forcedType != null) {
String uType = forcedType.getName();
String generator = forcedType.getGenerator();
String converter = null;
String binding = result.getBinding();
@ -219,6 +220,9 @@ public abstract class AbstractTypedElementDefinition<T extends Definition>
? customType.getType()
: customType.getName();
if (generator == null)
generator = customType.getGenerator();
// [#5877] [#6567] EnumConverters profit from simplified configuration
if (Boolean.TRUE.equals(customType.isEnumConverter()) ||
EnumConverter.class.getName().equals(customType.getConverter())) {
@ -277,7 +281,7 @@ public abstract class AbstractTypedElementDefinition<T extends Definition>
if (customType != null)
log.warn("Custom type conflict", child + " has custom type " + customType + " forced by " + forcedType + " but a data type rewrite applies");
result = new DefaultDataTypeDefinition(db, child.getSchema(), uType, l, p, s, n, r, g, d, i, (Name) null, converter, binding, null);
result = new DefaultDataTypeDefinition(db, child.getSchema(), uType, l, p, s, n, r, g, d, i, (Name) null, generator, converter, binding, null);
}
// Other forced types are UDT's, enums, etc.
@ -287,7 +291,7 @@ public abstract class AbstractTypedElementDefinition<T extends Definition>
s = result.getScale();
String t = result.getType();
Name u = result.getQualifiedUserType();
result = new DefaultDataTypeDefinition(db, definedType.getSchema(), t, l, p, s, n, r, g, d, i, u, converter, binding, uType);
result = new DefaultDataTypeDefinition(db, definedType.getSchema(), t, l, p, s, n, r, g, d, i, u, generator, converter, binding, uType);
}
// [#4597] If we don't have a type-rewrite (forcedDataType) or a
@ -302,6 +306,9 @@ public abstract class AbstractTypedElementDefinition<T extends Definition>
log.warn("Bad configuration for <forcedType/> " + forcedType.getName() + ". No matching <customType/> found, and no matching SQLDataType found: " + forcedType);
}
}
if (generator != null)
((DefaultDataTypeDefinition) result).generator(generator);
}
return result;
@ -341,6 +348,7 @@ public abstract class AbstractTypedElementDefinition<T extends Definition>
.withBinding(forcedType.getBinding())
.withEnumConverter(forcedType.isEnumConverter())
.withLambdaConverter(forcedType.getLambdaConverter())
.withGenerator(forcedType.getGenerator())
.withConverter(forcedType.getConverter())
.withName(name)
.withType(forcedType.getUserType());

View File

@ -41,9 +41,12 @@ import java.util.List;
// ...
import org.jooq.Name;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.meta.jaxb.ForcedType;
import org.jetbrains.annotations.Nullable;
/**
* A definition for a data type object.
*
@ -56,16 +59,25 @@ public interface DataTypeDefinition {
*/
String getType();
/**
* The generator type that is applied to this data type, or
* <code>null</code>, if no such generator type is configured.
*/
@Nullable
String getGenerator();
/**
* The converter type that is applied to this data type, or
* <code>null</code>, if no such converter type is configured.
*/
@Nullable
String getConverter();
/**
* The binding type that is applied to this data type, or
* <code>null</code>, if no such binding type is configured.
*/
@Nullable
String getBinding();
/**
@ -131,6 +143,11 @@ public interface DataTypeDefinition {
*/
GenerationOption getGenerationOption();
/**
* The computed column generation location.
*/
GenerationLocation getGenerationLocation();
/**
* Whether this data type is an identity.
*/

View File

@ -51,6 +51,7 @@ import java.util.Set;
import org.jooq.Name;
import org.jooq.SQLDialect;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
@ -71,6 +72,7 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
private final String type;
private final Name userType;
private final String javaType;
private String generator;
private final String converter;
private final String binding;
private final boolean nullable;
@ -196,6 +198,10 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
}
public DefaultDataTypeDefinition(Database database, SchemaDefinition schema, String typeName, Number length, Number precision, Number scale, Boolean nullable, boolean readonly, String generatedAlwaysAs, String defaultValue, boolean identity, Name userType, String converter, String binding, String javaType) {
this(database, schema, typeName, length, precision, scale, nullable, readonly, generatedAlwaysAs, defaultValue, identity, userType, null, converter, binding, javaType);
}
public DefaultDataTypeDefinition(Database database, SchemaDefinition schema, String typeName, Number length, Number precision, Number scale, Boolean nullable, boolean readonly, String generatedAlwaysAs, String defaultValue, boolean identity, Name userType, String generator, String converter, String binding, String javaType) {
this.database = database;
this.schema = schema;
@ -203,6 +209,7 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
this.type = typeName == null ? "OTHER" : typeName;
this.userType = userType;
this.javaType = javaType;
this.generator = generator;
this.converter = converter;
this.binding = binding;
@ -228,6 +235,9 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
this.generatedAlwaysAs = generatedAlwaysAs;
this.defaultValue = defaultValue;
this.identity = identity;
if (generator != null)
database.create().configuration().commercial(() -> "Client side computed columns are a commercial only feature. Please consider upgrading to the jOOQ Professional Edition or jOOQ Enterprise Edition.");
}
@Override
@ -284,6 +294,21 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
return generationOption;
}
@Override
public GenerationLocation getGenerationLocation() {
return generator == null ? GenerationLocation.SERVER : GenerationLocation.CLIENT;
}
private static final JooqLogger logGenerator = JooqLogger.getLogger(DefaultDataTypeDefinition.class, "logGenerator", 1);
public final DefaultDataTypeDefinition generator(String g) {
if (g != null && !database.create().configuration().commercial())
logGenerator.info("Computed columns", "Client side computed columns are a commercial only jOOQ feature. If you wish to profit from this feature, please upgrade to the jOOQ Professional Edition");
this.generator = g;
return this;
}
public final DefaultDataTypeDefinition generationOption(GenerationOption go) {
this.generationOption = go;
return this;
@ -335,6 +360,11 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
return type;
}
@Override
public final String getGenerator() {
return generator;
}
@Override
public final String getConverter() {
return converter;

View File

@ -36,6 +36,8 @@ public class CustomType implements Serializable, XMLAppendable
@XmlJavaTypeAdapter(StringAdapter.class)
protected String type;
@XmlJavaTypeAdapter(StringAdapter.class)
protected String generator;
@XmlJavaTypeAdapter(StringAdapter.class)
protected String converter;
protected Boolean enumConverter;
protected LambdaConverter lambdaConverter;
@ -78,6 +80,24 @@ public class CustomType implements Serializable, XMLAppendable
this.type = value;
}
/**
* @deprecated Use ForcedType only
*
*/
@Deprecated
public String getGenerator() {
return generator;
}
/**
* @deprecated Use ForcedType only
*
*/
@Deprecated
public void setGenerator(String value) {
this.generator = value;
}
/**
* @deprecated Use ForcedType only
*
@ -178,6 +198,16 @@ public class CustomType implements Serializable, XMLAppendable
return this;
}
/**
* @deprecated Use ForcedType only
*
*/
@Deprecated
public CustomType withGenerator(String value) {
setGenerator(value);
return this;
}
/**
* @deprecated Use ForcedType only
*
@ -217,6 +247,7 @@ public class CustomType implements Serializable, XMLAppendable
public final void appendTo(XMLBuilder builder) {
builder.append("name", name);
builder.append("type", type);
builder.append("generator", generator);
builder.append("converter", converter);
builder.append("enumConverter", enumConverter);
builder.append("lambdaConverter", lambdaConverter);
@ -260,6 +291,15 @@ public class CustomType implements Serializable, XMLAppendable
return false;
}
}
if (generator == null) {
if (other.generator!= null) {
return false;
}
} else {
if (!generator.equals(other.generator)) {
return false;
}
}
if (converter == null) {
if (other.converter!= null) {
return false;
@ -305,6 +345,7 @@ public class CustomType implements Serializable, XMLAppendable
int result = 1;
result = ((prime*result)+((name == null)? 0 :name.hashCode()));
result = ((prime*result)+((type == null)? 0 :type.hashCode()));
result = ((prime*result)+((generator == null)? 0 :generator.hashCode()));
result = ((prime*result)+((converter == null)? 0 :converter.hashCode()));
result = ((prime*result)+((enumConverter == null)? 0 :enumConverter.hashCode()));
result = ((prime*result)+((lambdaConverter == null)? 0 :lambdaConverter.hashCode()));

View File

@ -37,6 +37,8 @@ public class ForcedType implements Serializable, XMLAppendable
@XmlJavaTypeAdapter(StringAdapter.class)
protected String userType;
@XmlJavaTypeAdapter(StringAdapter.class)
protected String generator;
@XmlJavaTypeAdapter(StringAdapter.class)
protected String converter;
protected Boolean enumConverter;
protected LambdaConverter lambdaConverter;
@ -119,6 +121,26 @@ public class ForcedType implements Serializable, XMLAppendable
this.userType = value;
}
/**
* A {@link org.jooq.Generator} implementation used for client-side computed columns.
* <p>
* This feature is available in the commercial distribution only.
*
*/
public String getGenerator() {
return generator;
}
/**
* A {@link org.jooq.Generator} implementation used for client-side computed columns.
* <p>
* This feature is available in the commercial distribution only.
*
*/
public void setGenerator(String value) {
this.generator = value;
}
/**
* A converter implementation for the {@link #getUserType()}.
*
@ -395,6 +417,17 @@ public class ForcedType implements Serializable, XMLAppendable
return this;
}
/**
* A {@link org.jooq.Generator} implementation used for client-side computed columns.
* <p>
* This feature is available in the commercial distribution only.
*
*/
public ForcedType withGenerator(String value) {
setGenerator(value);
return this;
}
/**
* A converter implementation for the {@link #getUserType()}.
*
@ -529,6 +562,7 @@ public class ForcedType implements Serializable, XMLAppendable
builder.append("priority", priority);
builder.append("name", name);
builder.append("userType", userType);
builder.append("generator", generator);
builder.append("converter", converter);
builder.append("enumConverter", enumConverter);
builder.append("lambdaConverter", lambdaConverter);
@ -591,6 +625,15 @@ public class ForcedType implements Serializable, XMLAppendable
return false;
}
}
if (generator == null) {
if (other.generator!= null) {
return false;
}
} else {
if (!generator.equals(other.generator)) {
return false;
}
}
if (converter == null) {
if (other.converter!= null) {
return false;
@ -727,6 +770,7 @@ public class ForcedType implements Serializable, XMLAppendable
result = ((prime*result)+((priority == null)? 0 :priority.hashCode()));
result = ((prime*result)+((name == null)? 0 :name.hashCode()));
result = ((prime*result)+((userType == null)? 0 :userType.hashCode()));
result = ((prime*result)+((generator == null)? 0 :generator.hashCode()));
result = ((prime*result)+((converter == null)? 0 :converter.hashCode()));
result = ((prime*result)+((enumConverter == null)? 0 :enumConverter.hashCode()));
result = ((prime*result)+((lambdaConverter == null)? 0 :lambdaConverter.hashCode()));

View File

@ -1339,6 +1339,18 @@ This feature is available in the commercial distribution only.]]></jxb:javadoc><
</annotation>
</element>
<element name="generator" type="string" minOccurs="0" maxOccurs="1">
<annotation>
<appinfo>
<jxb:property>
<jxb:javadoc><![CDATA[@deprecated Use ForcedType only]]></jxb:javadoc>
</jxb:property>
<annox:annotate target="getter">@java.lang.Deprecated</annox:annotate>
<annox:annotate target="setter">@java.lang.Deprecated</annox:annotate>
</appinfo>
</annotation>
</element>
<element name="converter" type="string" minOccurs="0" maxOccurs="1">
<annotation>
<appinfo>
@ -1473,6 +1485,12 @@ This feature is available in the commercial distribution only.]]></jxb:javadoc><
If provided, {@link #getName()} will be ignored, and either {@link #getConverter()}
or {@link #getBinding()} is required]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="generator" type="string" minOccurs="0" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[A {@link org.jooq.Generator} implementation used for client-side computed columns.
<p>
This feature is available in the commercial distribution only.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="converter" type="string" minOccurs="0" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[A converter implementation for the {@link #getUserType()}.]]></jxb:javadoc></jxb:property></appinfo></annotation>

View File

@ -77,9 +77,9 @@ import java.util.List;
import java.util.function.Function;
import org.jooq.Converters.UnknownType;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataTypeException;
import org.jooq.impl.DSL;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.impl.SQLDataType;
import org.jooq.types.DayToSecond;
@ -467,6 +467,32 @@ public interface DataType<T> extends Named {
*/
boolean readonly();
/**
* Get the readonly attribute of this data type, combined with other flags
* that influence readonly behaviour.
* <p>
* A column may be marked as {@link #readonly()} for various reasons,
* including:
* <ul>
* <li>When it is marked as readonly explicitly by the code generator.</li>
* <li>When it is marked as readonly implicitly because it's a computed
* column with {@link #generationLocation()} being
* {@link GenerationLocation#SERVER}.</li>
* </ul>
* <p>
* Some columns are readonly for users, meaning users of the jOOQ API cannot
* write to them, but jOOQ, internally, may still write to those columns.
* Such columns may include:
* <ul>
* <li>Columns that are computed with {@link #generationLocation()} being
* {@link GenerationLocation#CLIENT}</li>
* <li>Columns used for optimistic locking</li>
* </ul>
* <p>
* This feature is implemented in commercial distributions only.
*/
boolean readonlyInternal();
/**
* Whether this column is computed.
* <p>
@ -475,7 +501,17 @@ public interface DataType<T> extends Named {
boolean computed();
/**
* Set the computed column expression of this data type.
* Whether this column is computed on the client.
* <p>
* This is true only if {@link #computed()} and
* {@link #generationLocation()} == {@link GenerationLocation#CLIENT}.
* <p>
* This feature is implemented in commercial distributions only.
*/
boolean computedOnClient();
/**
* Set the computed column expression of this data type to a constant value.
* <p>
* This implicitly sets {@link #readonly()} to <code>true</code>.
* <p>
@ -486,7 +522,8 @@ public interface DataType<T> extends Named {
DataType<T> generatedAlwaysAs(T generatedAlwaysAsValue);
/**
* Set the computed column expression of this data type.
* Set the computed column expression of this data type to a constant
* expression.
* <p>
* This implicitly sets {@link #readonly()} to <code>true</code>.
* <p>
@ -496,14 +533,50 @@ public interface DataType<T> extends Named {
@Support({ DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
DataType<T> generatedAlwaysAs(Field<T> generatedAlwaysAsValue);
/**
* Set the computed column expression of this data type to a dynamic
* expression.
* <p>
* Unlike {@link #generatedAlwaysAs(Object)} and
* {@link #generatedAlwaysAs(Field)}, which produce a constant value or
* expression, this allows for generating a dynamic expression if used along
* with {@link #generationLocation()} and {@link GenerationLocation#CLIENT},
* in order to implement client side computed columns.
* <p>
* If {@link #generationLocation()} is {@link GenerationLocation#SERVER},
* then this does not affect generated DML statements, and will be evaluated
* only in DDL statements, when creating the table.
* <p>
* This implicitly sets {@link #readonly()} to <code>true</code>.
* <p>
* This feature is implemented in commercial distributions only.
*/
@NotNull
DataType<T> generatedAlwaysAs(Generator<T> generatedAlwaysAsValue);
/**
* Get the computed column expression of this data type, if any.
* <p>
* This eagerly evaluates the {@link #generatedAlwaysAsGenerator()}
* generator, which may not produce the same expression upon execution of a
* query, in case {@link #generationLocation()} is
* {@link GenerationLocation#CLIENT}. The behaviour of
* {@link GenerationLocation#SERVER} is not affected. The method has been
* left unmodified for backwards compatibility with jOOQ 3.16.
* <p>
* This feature is implemented in commercial distributions only.
*/
@Nullable
Field<T> generatedAlwaysAs();
/**
* Get the computed column expression of this data type, if any.
* <p>
* This feature is implemented in commercial distributions only.
*/
@Nullable
Generator<T> generatedAlwaysAsGenerator();
/**
* Set the {@link #generationOption()} of the computed column expression to
* {@link GenerationOption#STORED}.
@ -549,6 +622,46 @@ public interface DataType<T> extends Named {
@Support
GenerationOption generationOption();
/**
* Set the {@link #generationLocation()} of the computed column expression.
* <p>
* Specifies whether the {@link #generatedAlwaysAs()} expression is computed
* on the {@link GenerationLocation#SERVER} (by default) or in the
* {@link GenerationLocation#CLIENT}. The latter is supported in all
* dialects, the former only in relevant dialects.
* <p>
* The computation happens in {@link Insert}, {@link Update}, or
* {@link Merge} statements in case {@link #generationOption()} is
* {@link GenerationOption#STORED}, or in {@link Select} in case the
* {@link #generationOption()} is {@link GenerationOption#VIRTUAL}.
* <p>
* This feature is implemented in commercial distributions only.
*/
@NotNull
@Support
DataType<T> generationLocation(GenerationLocation generationOption);
/**
* Get the {@link GenerationLocation} of the computed column expression of
* this data type, if any.
* <p>
* Specifies whether the {@link #generatedAlwaysAs()} expression is computed
* on the {@link GenerationLocation#SERVER} (by default) or in the
* {@link GenerationLocation#CLIENT}. The latter is supported in all
* dialects, the former only in relevant dialects.
* <p>
* The computation happens in {@link Insert}, {@link Update}, or
* {@link Merge} statements in case {@link #generationOption()} is
* {@link GenerationOption#STORED}, or in {@link Select} in case the
* {@link #generationOption()} is {@link GenerationOption#VIRTUAL}.
* <p>
* <p>
* This feature is implemented in commercial distributions only.
*/
@NotNull
@Support
GenerationLocation generationLocation();
/**
* Synonym for {@link #nullable(boolean)}, passing <code>true</code> as an
* argument.

View File

@ -0,0 +1,48 @@
/*
* 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;
import java.io.Serializable;
import java.util.function.Supplier;
/**
* A generator can be used with {@link DataType#generatedAlwaysAs(Generator)} to
* implement dynamic, client side computed columns.
*/
@FunctionalInterface
public interface Generator<T> extends Supplier<Field<T>>, Serializable {}

View File

@ -195,8 +195,8 @@ abstract class AbstractDMLQuery<R extends Record> extends AbstractRowCountQuery
private final WithImpl with;
private final Table<R> table;
final WithImpl with;
final Table<R> table;
final SelectFieldList<SelectFieldOrAsterisk> returning;
final List<Field<?>> returningResolvedAsterisks;
Result<Record> returnedResult;

View File

@ -70,6 +70,7 @@ import java.time.OffsetTime;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
// ...
// ...
@ -85,6 +86,7 @@ import org.jooq.Domain;
import org.jooq.EmbeddableRecord;
import org.jooq.EnumType;
import org.jooq.Field;
import org.jooq.Generator;
import org.jooq.Geography;
import org.jooq.Geometry;
import org.jooq.JSON;
@ -98,11 +100,13 @@ import org.jooq.Result;
import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.XML;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.impl.QOM.UEmpty;
import org.jooq.types.Interval;
import org.jooq.types.UNumber;
import jakarta.persistence.GenerationType;
// ...
/**
@ -149,12 +153,22 @@ implements
@Override
public abstract boolean readonly();
@Override
public final boolean readonlyInternal() {
return readonly() && !computedOnClient();
}
@Override
public abstract DataType<T> readonly(boolean r);
@Override
public final boolean computed() {
return generatedAlwaysAs() != null;
return generatedAlwaysAsGenerator() != null;
}
@Override
public final boolean computedOnClient() {
return computed() && generationLocation() == GenerationLocation.CLIENT;
}
@Override
@ -163,10 +177,21 @@ implements
}
@Override
public abstract DataType<T> generatedAlwaysAs(Field<T> generatedAlwaysAsValue);
public final DataType<T> generatedAlwaysAs(Field<T> generatedAlwaysAsValue) {
return generatedAlwaysAs(() -> generatedAlwaysAsValue);
}
@Override
public abstract Field<T> generatedAlwaysAs();
public abstract DataType<T> generatedAlwaysAs(Generator<T> generatedAlwaysAsValue);
@Override
public final Field<T> generatedAlwaysAs() {
Generator<T> s = generatedAlwaysAsGenerator();
return s == null ? null : s.get();
}
@Override
public abstract Generator<T> generatedAlwaysAsGenerator();
@Override
public final DataType<T> stored() {
@ -184,6 +209,12 @@ implements
@Override
public abstract GenerationOption generationOption();
@Override
public abstract DataType<T> generationLocation(GenerationLocation generationLocation);
@Override
public abstract GenerationLocation generationLocation();
@Override
public abstract DataType<T> collation(Collation c);

View File

@ -40,13 +40,17 @@ package org.jooq.impl;
import static org.jooq.Nullability.NOT_NULL;
import static org.jooq.impl.Tools.CONFIG;
import java.util.function.Supplier;
import org.jooq.CharacterSet;
import org.jooq.Collation;
import org.jooq.Comment;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Generator;
import org.jooq.Name;
import org.jooq.Nullability;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.tools.JooqLogger;
@ -69,8 +73,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<T> newGeneratedAlwaysAs,
Generator<T> newGeneratedAlwaysAs,
GenerationOption newGenerationOption,
GenerationLocation newGenerationLocation,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -85,8 +90,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
length0(),
n,
readonly(),
generatedAlwaysAs(),
generatedAlwaysAsGenerator(),
generationOption(),
generationLocation(),
collation(),
characterSet(),
!n.nullable() && identity(),
@ -105,8 +111,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
length0(),
nullability(),
r,
generatedAlwaysAs(),
generatedAlwaysAsGenerator(),
generationOption(),
generationLocation(),
collation(),
characterSet(),
identity(),
@ -117,7 +124,7 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
private static final JooqLogger logGeneratedAlwaysAs = JooqLogger.getLogger(AbstractDataTypeX.class, "generateAlwaysAs", 1);
@Override
public final DataType<T> generatedAlwaysAs(Field<T> g) {
public final DataType<T> generatedAlwaysAs(Generator<T> g) {
if (g != null && !CONFIG.commercial())
logGeneratedAlwaysAs.info("Computed columns", "Computed columns are a commercial only jOOQ feature. If you wish to profit from this feature, please upgrade to the jOOQ Professional Edition");
@ -129,6 +136,7 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
g != null ? true : readonly(),
g,
generationOption(),
generationLocation(),
collation(),
characterSet(),
identity(),
@ -147,7 +155,29 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
generatedAlwaysAsGenerator(),
g,
generationLocation(),
collation(),
characterSet(),
identity(),
defaultValue()
);
}
@Override
public final DataType<T> generationLocation(GenerationLocation g) {
if (g != null && !CONFIG.commercial())
logGeneratedAlwaysAs.info("Computed columns", "Computed columns are a commercial only jOOQ feature. If you wish to profit from this feature, please upgrade to the jOOQ Professional Edition");
return construct(
precision0(),
scale0(),
length0(),
nullability(),
readonly(),
generatedAlwaysAsGenerator(),
generationOption(),
g,
collation(),
characterSet(),
@ -164,8 +194,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
generatedAlwaysAsGenerator(),
generationOption(),
generationLocation(),
c,
characterSet(),
identity(),
@ -181,8 +212,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
generatedAlwaysAsGenerator(),
generationOption(),
generationLocation(),
collation(),
c,
identity(),
@ -198,8 +230,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
length0(),
i ? NOT_NULL : nullability(),
readonly(),
generatedAlwaysAs(),
generatedAlwaysAsGenerator(),
generationOption(),
generationLocation(),
collation(),
characterSet(),
i,
@ -215,8 +248,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
length0(),
nullability(),
readonly(),
d != null ? null : generatedAlwaysAs(),
d != null ? null : generatedAlwaysAsGenerator(),
generationOption(),
generationLocation(),
collation(),
characterSet(),
identity(),
@ -232,8 +266,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
generatedAlwaysAsGenerator(),
generationOption(),
generationLocation(),
collation(),
characterSet(),
identity(),
@ -249,8 +284,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
generatedAlwaysAsGenerator(),
generationOption(),
generationLocation(),
collation(),
characterSet(),
identity(),
@ -266,8 +302,9 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
l,
nullability(),
readonly(),
generatedAlwaysAs(),
generatedAlwaysAsGenerator(),
generationOption(),
generationLocation(),
collation(),
characterSet(),
identity(),

View File

@ -87,6 +87,7 @@ import org.jooq.DataType;
import org.jooq.DivideByOnStep;
import org.jooq.Field;
import org.jooq.ForeignKey;
import org.jooq.Generator;
import org.jooq.Identity;
import org.jooq.Index;
import org.jooq.JoinType;
@ -118,6 +119,7 @@ import org.jooq.TablePartitionByStep;
import org.jooq.UniqueKey;
// ...
// ...
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.tools.JooqLogger;
import org.jetbrains.annotations.NotNull;
@ -756,16 +758,30 @@ abstract class AbstractTable<R extends Record> extends AbstractNamed implements
* @param name The name of the field (case-sensitive!)
* @param type The data type of the field
*/
@SuppressWarnings("unchecked")
protected static final <R extends Record, T, X, U> TableField<R, U> createField(Name name, DataType<T> type, Table<R> table, String comment, Converter<X, U> converter, Binding<T, X> binding) {
final Binding<T, U> actualBinding = DefaultBinding.newBinding(converter, type, binding);
final DataType<U> actualType =
return createField(name, type, table, comment, converter, binding, null);
}
/**
* Subclasses may call this method to create {@link TableField} objects that
* are linked to this table.
*
* @param name The name of the field (case-sensitive!)
* @param type The data type of the field
*/
@SuppressWarnings("unchecked")
protected static final <R extends Record, T, X, U> TableField<R, U> createField(Name name, DataType<T> type, Table<R> table, String comment, Converter<X, U> converter, Binding<T, X> binding, Generator<U> generator) {
Binding<T, U> actualBinding = DefaultBinding.newBinding(converter, type, binding);
DataType<U> actualType =
converter == null && binding == null
? (DataType<U>) type
: type.asConvertedDataType(actualBinding);
if (generator != null)
actualType = actualType.generatedAlwaysAs(generator).generationLocation(GenerationLocation.CLIENT);
// [#5999] TODO: Allow for user-defined Names
final TableFieldImpl<R, U> tableField = new TableFieldImpl<>(name, actualType, table, DSL.comment(comment), actualBinding);
TableFieldImpl<R, U> tableField = new TableFieldImpl<>(name, actualType, table, DSL.comment(comment), actualBinding);
// [#1199] The public API of Table returns immutable field lists
if (table instanceof TableImpl)

View File

@ -38,15 +38,16 @@
package org.jooq.impl;
import static org.jooq.impl.Tools.CONFIG;
import static org.jooq.impl.Tools.CTX;
import org.jooq.CharacterSet;
import org.jooq.Collation;
import org.jooq.Configuration;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Generator;
import org.jooq.Nullability;
import org.jooq.SQLDialect;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
/**
@ -75,14 +76,15 @@ final class ArrayDataType<T> extends DefaultDataType<T[]> {
Integer length,
Nullability nullability,
boolean readonly,
Field<T[]> generatedAlwaysAs,
Generator<T[]> generatedAlwaysAs,
GenerationOption generationOption,
GenerationLocation generationLocation,
Collation collation,
CharacterSet characterSet,
boolean identity,
Field<T[]> defaultValue
) {
super(t, precision, scale, length, nullability, readonly, generatedAlwaysAs, generationOption, collation, characterSet, identity, defaultValue);
super(t, precision, scale, length, nullability, readonly, generatedAlwaysAs, generationOption, generationLocation, collation, characterSet, identity, defaultValue);
this.elementType = elementType;
}
@ -95,8 +97,9 @@ final class ArrayDataType<T> extends DefaultDataType<T[]> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<T[]> newGeneratedAlwaysAs,
Generator<T[]> newGeneratedAlwaysAs,
GenerationOption newGenerationOption,
GenerationLocation newGenerationLocation,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -112,6 +115,7 @@ final class ArrayDataType<T> extends DefaultDataType<T[]> {
newReadonly,
newGeneratedAlwaysAs,
newGenerationOption,
newGenerationLocation,
newCollation,
newCharacterSet,
newIdentity,

View File

@ -48,6 +48,7 @@ import org.jooq.Converter;
import org.jooq.Converters;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Generator;
import org.jooq.Nullability;
import org.jooq.Record;
import org.jooq.Result;
@ -55,11 +56,9 @@ import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.exception.DataTypeException;
import org.jooq.impl.DefaultBinding.InternalBinding;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A <code>DataType</code> used for converted types using {@link Converter}
*
@ -89,8 +88,9 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<U> newGeneratedAlwaysAs,
Generator<U> newGeneratedAlwaysAs,
GenerationOption newGenerationOption,
GenerationLocation newGenerationLocation,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -102,8 +102,9 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
newLength,
newNullability,
newReadonly,
(Field) newGeneratedAlwaysAs,
(Generator) newGeneratedAlwaysAs,
newGenerationOption,
newGenerationLocation,
newCollation,
newCharacterSet,
newIdentity,
@ -209,8 +210,8 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
}
@Override
public final Field<U> generatedAlwaysAs() {
return (Field<U>) delegate.generatedAlwaysAs();
public final Generator<U> generatedAlwaysAsGenerator() {
return (Generator<U>) delegate.generatedAlwaysAsGenerator();
}
@Override
@ -218,6 +219,11 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
return delegate.generationOption();
}
@Override
public final GenerationLocation generationLocation() {
return delegate.generationLocation();
}
@Override
public final Collation collation() {
return delegate.collation();

View File

@ -45,9 +45,11 @@ import org.jooq.Collation;
import org.jooq.Configuration;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Generator;
import org.jooq.Name;
import org.jooq.Nullability;
import org.jooq.SQLDialect;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
/**
@ -58,23 +60,25 @@ import org.jooq.impl.QOM.GenerationOption;
*/
final class DataTypeProxy<T> extends AbstractDataType<T> {
private AbstractDataType<T> type;
private final Integer overridePrecision;
private final Integer overrideScale;
private final Integer overrideLength;
private final Nullability overrideNullability;
private final Boolean overrideReadonly;
private final Field<T> overrideGeneratedAlwaysAs;
private final GenerationOption overrideGenerationOption;
private final Collation overrideCollation;
private final CharacterSet overrideCharacterSet;
private final Boolean overrideIdentity;
private final Field<T> overrideDefaultValue;
private AbstractDataType<T> type;
private final Integer overridePrecision;
private final Integer overrideScale;
private final Integer overrideLength;
private final Nullability overrideNullability;
private final Boolean overrideReadonly;
private final Generator<T> overrideGeneratedAlwaysAs;
private final GenerationOption overrideGenerationOption;
private final GenerationLocation overrideGenerationLocation;
private final Collation overrideCollation;
private final CharacterSet overrideCharacterSet;
private final Boolean overrideIdentity;
private final Field<T> overrideDefaultValue;
DataTypeProxy(AbstractDataType<T> type) {
this(type, null, null, null, null, null, null, null, null, null, null, null);
this(type, null, null, null, null, null, null, null, null, null, null, null, null);
}
@SuppressWarnings("unchecked")
private DataTypeProxy(
AbstractDataType<T> type,
Integer overridePrecision,
@ -82,8 +86,9 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
Integer overrideLength,
Nullability overrideNullability,
Boolean overrideReadonly,
Field<T> overrideGeneratedAlwaysAs,
Generator<T> overrideGeneratedAlwaysAs,
GenerationOption overrideGenerationOption,
GenerationLocation overrideGenerationLocation,
Collation overrideCollation,
CharacterSet overrideCharacterSet,
Boolean overrideIdentity,
@ -99,6 +104,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
this.overrideReadonly = overrideReadonly;
this.overrideGeneratedAlwaysAs = overrideGeneratedAlwaysAs;
this.overrideGenerationOption = overrideGenerationOption;
this.overrideGenerationLocation = overrideGenerationLocation;
this.overrideCollation = overrideCollation;
this.overrideCharacterSet = overrideCharacterSet;
this.overrideIdentity = overrideIdentity;
@ -159,6 +165,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
overrideGenerationLocation,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -182,6 +189,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
r,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
overrideGenerationLocation,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -190,12 +198,12 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
}
@Override
public final Field<T> generatedAlwaysAs() {
return defaultIfNull(overrideGeneratedAlwaysAs, type.generatedAlwaysAs());
public final Generator<T> generatedAlwaysAsGenerator() {
return defaultIfNull(overrideGeneratedAlwaysAs, type.generatedAlwaysAsGenerator());
}
@Override
public final DataType<T> generatedAlwaysAs(Field<T> g) {
public final DataType<T> generatedAlwaysAs(Generator<T> g) {
return new DataTypeProxy<>(
this,
overridePrecision,
@ -205,6 +213,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
g,
overrideGenerationOption,
overrideGenerationLocation,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -228,6 +237,31 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
overrideGeneratedAlwaysAs,
g,
overrideGenerationLocation,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
overrideDefaultValue
);
}
@Override
public final GenerationLocation generationLocation() {
return defaultIfNull(overrideGenerationLocation, type.generationLocation());
}
@Override
public final DataType<T> generationLocation(GenerationLocation g) {
return new DataTypeProxy<>(
this,
overridePrecision,
overrideScale,
overrideLength,
overrideNullability,
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
g,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -251,6 +285,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
overrideGenerationLocation,
c,
overrideCharacterSet,
overrideIdentity,
@ -274,6 +309,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
overrideGenerationLocation,
overrideCollation,
c,
overrideIdentity,
@ -297,6 +333,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
overrideGenerationLocation,
overrideCollation,
overrideCharacterSet,
i,
@ -320,6 +357,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
overrideGenerationLocation,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -373,6 +411,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
overrideGenerationLocation,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -396,6 +435,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
overrideGenerationLocation,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -419,6 +459,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideGenerationOption,
overrideGenerationLocation,
overrideCollation,
overrideCharacterSet,
overrideIdentity,

View File

@ -94,6 +94,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.regex.Pattern;
// ...
@ -105,6 +106,7 @@ import org.jooq.Converter;
import org.jooq.DataType;
import org.jooq.EnumType;
import org.jooq.Field;
import org.jooq.Generator;
import org.jooq.Name;
import org.jooq.Nullability;
import org.jooq.QualifiedRecord;
@ -112,6 +114,7 @@ import org.jooq.SQLDialect;
import org.jooq.exception.MappingException;
import org.jooq.exception.SQLDialectNotSupportedException;
import org.jooq.impl.DefaultBinding.InternalBinding;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.types.UByte;
import org.jooq.types.UInteger;
@ -256,8 +259,9 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
private final Nullability nullability;
private final boolean readonly;
private final Field<T> generatedAlwaysAs;
private final Generator<T> generatedAlwaysAs;
private final GenerationOption generationOption;
private final GenerationLocation generationLocation;
private final Collation collation;
private final CharacterSet characterSet;
private final boolean identity;
@ -327,10 +331,10 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
}
DefaultDataType(SQLDialect dialect, DataType<T> sqlDataType, Class<T> type, Binding<?, T> binding, Name qualifiedTypeName, String typeName, String castTypeName, Integer precision, Integer scale, Integer length, Nullability nullability, Field<T> defaultValue) {
this(dialect, sqlDataType, type, binding, qualifiedTypeName, typeName, castTypeName, precision, scale, length, nullability, false, null, GenerationOption.DEFAULT, null, null, false, defaultValue);
this(dialect, sqlDataType, type, binding, qualifiedTypeName, typeName, castTypeName, precision, scale, length, nullability, false, null, GenerationOption.DEFAULT, GenerationLocation.SERVER, null, null, false, defaultValue);
}
DefaultDataType(SQLDialect dialect, DataType<T> sqlDataType, Class<T> type, Binding<?, T> binding, Name qualifiedTypeName, String typeName, String castTypeName, Integer precision, Integer scale, Integer length, Nullability nullability, boolean readonly, Field<T> generatedAlwaysAs, GenerationOption generationOption, Collation collation, CharacterSet characterSet, boolean identity, Field<T> defaultValue) {
DefaultDataType(SQLDialect dialect, DataType<T> sqlDataType, Class<T> type, Binding<?, T> binding, Name qualifiedTypeName, String typeName, String castTypeName, Integer precision, Integer scale, Integer length, Nullability nullability, boolean readonly, Generator<T> generatedAlwaysAs, GenerationOption generationOption, GenerationLocation generationLocation, Collation collation, CharacterSet characterSet, boolean identity, Field<T> defaultValue) {
super(qualifiedTypeName, NO_COMMENT);
// Initialise final instance members
@ -352,6 +356,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
this.readonly = readonly;
this.generatedAlwaysAs = generatedAlwaysAs;
this.generationOption = generationOption == null ? GenerationOption.DEFAULT : generationOption;
this.generationLocation = generationLocation == null ? GenerationLocation.SERVER : generationLocation;
this.collation = collation;
this.characterSet = characterSet;
this.identity = identity;
@ -396,8 +401,9 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<T> newGeneratedAlwaysAs,
Generator<T> newGeneratedAlwaysAs,
GenerationOption newGenerationOption,
GenerationLocation newGenerationLocation,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -412,6 +418,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
newReadonly,
newGeneratedAlwaysAs,
newGenerationOption,
newGenerationLocation,
newCollation,
newCharacterSet,
newIdentity,
@ -429,8 +436,9 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
Integer length,
Nullability nullability,
boolean readonly,
Field<T> generatedAlwaysAs,
Generator<T> generatedAlwaysAs,
GenerationOption generationOption,
GenerationLocation generationLocation,
Collation collation,
CharacterSet characterSet,
boolean identity,
@ -451,6 +459,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
this.readonly = readonly;
this.generatedAlwaysAs = generatedAlwaysAs;
this.generationOption = generationOption;
this.generationLocation = generationLocation;
this.collation = collation;
this.characterSet = characterSet;
this.identity = identity;
@ -491,7 +500,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
}
@Override
public final Field<T> generatedAlwaysAs() {
public final Generator<T> generatedAlwaysAsGenerator() {
return generatedAlwaysAs;
}
@ -500,6 +509,11 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
return generationOption;
}
@Override
public final GenerationLocation generationLocation() {
return generationLocation;
}
@Override
public final Collation collation() {
return collation;
@ -556,7 +570,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
// ... and then, set them back to the original value
// [#2710] TODO: Remove this logic along with cached data types
return dataType.construct(precision, scale, length, nullability, readonly, generatedAlwaysAs, generationOption, collation, characterSet, identity, defaultValue);
return dataType.construct(precision, scale, length, nullability, readonly, generatedAlwaysAs, generationOption, generationLocation, collation, characterSet, identity, defaultValue);
}
// If this is already the dialect's specific data type, return this

View File

@ -65,8 +65,9 @@ final class DomainDataType<T> extends DefaultDataType<T> {
baseType.lengthDefined() ? baseType.length() : null,
baseType.nullability(),
baseType.readonly(),
baseType.generatedAlwaysAs(),
baseType.generatedAlwaysAsGenerator(),
baseType.generationOption(),
baseType.generationLocation(),
null, // TODO: Collation
null, // TODO: CharacterSet (?)
false,

View File

@ -39,6 +39,7 @@ package org.jooq.impl;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static org.jooq.Clause.FIELD_ROW;
import static org.jooq.Clause.INSERT_SELECT;
import static org.jooq.Clause.INSERT_VALUES;
@ -49,9 +50,11 @@ import static org.jooq.SQLDialect.YUGABYTEDB;
import static org.jooq.conf.WriteIfReadonly.IGNORE;
import static org.jooq.conf.WriteIfReadonly.THROW;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.Keywords.K_DEFAULT_VALUES;
import static org.jooq.impl.Keywords.K_VALUES;
import static org.jooq.impl.QueryPartCollectionView.wrap;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.anyMatch;
import static org.jooq.impl.Tools.collect;
import static org.jooq.impl.Tools.filter;
@ -64,10 +67,12 @@ import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@ -84,12 +89,15 @@ import org.jooq.Record;
import org.jooq.RenderContext.CastMode;
import org.jooq.SQLDialect;
import org.jooq.Select;
import org.jooq.SelectJoinStep;
import org.jooq.Table;
import org.jooq.conf.WriteIfReadonly;
import org.jooq.exception.DataTypeException;
import org.jooq.impl.AbstractStoreQuery.UnknownField;
import org.jooq.impl.QOM.UNotYetImplemented;
import org.jetbrains.annotations.NotNull;
/**
* @author Lukas Eder
*/
@ -124,6 +132,12 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple
.end(INSERT_VALUES);
}
// Single record inserts can use the standard syntax in any dialect
@ -171,13 +185,6 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple
@ -187,11 +194,7 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple
case FIREBIRD: {
ctx.formatSeparator()
.start(INSERT_SELECT)
.visit(insertSelect(ctx))
.end(INSERT_SELECT);
toSQLInsertSelect(ctx, insertSelect(ctx));
break;
}
@ -209,6 +212,38 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple
}
}
static final void toSQLInsertSelect(Context<?> ctx, Select<?> select) {
ctx.formatSeparator()
.start(INSERT_SELECT)
.visit(select)
.end(INSERT_SELECT);
}
static final Set<Field<?>> keysAndComputedOnClient(Set<Field<?>> keys, Table<?> table) {
return keys;
}
@ -521,15 +556,15 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple
return rows > 0;
}
final List<Field<?>> toSQLReferenceKeys(Context<?> ctx) {
final Set<Field<?>> toSQLReferenceKeys(Context<?> ctx) {
// [#1506] with DEFAULT VALUES, we might not have any columns to render
if (!isExecutable())
return emptyList();
return emptySet();
// [#2995] Do not generate empty column lists.
if (values.isEmpty())
return emptyList();
return emptySet();
// [#4629] Do not generate column lists for unknown columns
unknownFields: {
@ -537,11 +572,11 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple
if (!(field instanceof UnknownField))
break unknownFields;
return emptyList();
return emptySet();
}
// [#989] Avoid qualifying fields in INSERT field declaration
List<Field<?>> fields = collect(removeReadonly(ctx, flattenCollection(values.keySet(), true, true), e -> e));
Set<Field<?>> fields = keysAndComputedOnClient(keysFlattened(ctx), table);
if (!fields.isEmpty())
ctx.sql(" (").visit(wrap(fields).qualify(false)).sql(')');

View File

@ -67,6 +67,7 @@ import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.DSL.selectFrom;
import static org.jooq.impl.DSL.selectOne;
import static org.jooq.impl.FieldMapsForInsert.toSQLInsertSelect;
import static org.jooq.impl.Keywords.K_DEFAULT;
import static org.jooq.impl.Keywords.K_DEFAULT_VALUES;
import static org.jooq.impl.Keywords.K_DO_NOTHING;
@ -654,7 +655,7 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
.sql(' ')
.declareTables(true, c -> c.visit(table(c)));
List<Field<?>> fields = insertMaps.toSQLReferenceKeys(ctx);
Set<Field<?>> fields = insertMaps.toSQLReferenceKeys(ctx);
ctx.end(INSERT_INSERT_INTO);
@ -691,11 +692,7 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
// [#8353] TODO: Support overlapping embeddables
ctx.formatSeparator()
.start(INSERT_SELECT)
.visit(s)
.end(INSERT_SELECT);
toSQLInsertSelect(ctx, s);
ctx.data().remove(DATA_INSERT_SELECT_WITHOUT_INSERT_COLUMN_LIST);
ctx.data().remove(DATA_INSERT_SELECT);
}

View File

@ -49,10 +49,12 @@ import java.util.Map.Entry;
import org.jooq.CharacterSet;
import org.jooq.Collation;
import org.jooq.Field;
import org.jooq.Generator;
import org.jooq.Nullability;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.Row;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
/**
@ -86,14 +88,15 @@ final class MultisetDataType<R extends Record> extends DefaultDataType<Result<R>
Integer length,
Nullability nullability,
boolean readonly,
Field<Result<R>> generatedAlwaysAs,
Generator<Result<R>> generatedAlwaysAs,
GenerationOption generationOption,
GenerationLocation generationLocation,
Collation collation,
CharacterSet characterSet,
boolean identity,
Field<Result<R>> defaultValue
) {
super(t, precision, scale, length, nullability, readonly, generatedAlwaysAs, generationOption, collation, characterSet, identity, defaultValue);
super(t, precision, scale, length, nullability, readonly, generatedAlwaysAs, generationOption, generationLocation, collation, characterSet, identity, defaultValue);
this.row = row;
this.recordType = recordType;
@ -107,8 +110,9 @@ final class MultisetDataType<R extends Record> extends DefaultDataType<Result<R>
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<Result<R>> newGeneratedAlwaysAs,
Generator<Result<R>> newGeneratedAlwaysAs,
GenerationOption newGenerationOption,
GenerationLocation newGenerationLocation,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -125,6 +129,7 @@ final class MultisetDataType<R extends Record> extends DefaultDataType<Result<R>
newReadonly,
newGeneratedAlwaysAs,
newGenerationOption,
newGenerationLocation,
newCollation,
newCharacterSet,
newIdentity,

View File

@ -5815,6 +5815,24 @@ public final class QOM {
}
}
/**
* The <code>GenerationLocation</code> type.
* <p>
* Specify where a computed column should be computed, i.e. in the client or on the
* server.
*/
public enum GenerationLocation {
CLIENT(keyword("client")),
SERVER(keyword("server")),
;
final Keyword keyword;
private GenerationLocation(Keyword keyword) {
this.keyword = keyword;
}
}
/**
* The <code>GenerationOption</code> type.
* <p>

View File

@ -40,7 +40,6 @@ package org.jooq.impl;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import static org.jooq.impl.Tools.CONFIG;
import static org.jooq.impl.Tools.CTX;
import static org.jooq.impl.Tools.newRecord;
import static org.jooq.impl.Tools.recordType;
@ -51,9 +50,11 @@ import java.util.Map.Entry;
import org.jooq.CharacterSet;
import org.jooq.Collation;
import org.jooq.Field;
import org.jooq.Generator;
import org.jooq.Nullability;
import org.jooq.Record;
import org.jooq.Row;
import org.jooq.impl.QOM.GenerationLocation;
import org.jooq.impl.QOM.GenerationOption;
/**
@ -88,14 +89,15 @@ final class RecordDataType<R extends Record> extends DefaultDataType<R> {
Integer length,
Nullability nullability,
boolean readonly,
Field<R> generatedAlwaysAs,
Generator<R> generatedAlwaysAs,
GenerationOption generationOption,
GenerationLocation generationLocation,
Collation collation,
CharacterSet characterSet,
boolean identity,
Field<R> defaultValue
) {
super(t, precision, scale, length, nullability, readonly, generatedAlwaysAs, generationOption, collation, characterSet, identity, defaultValue);
super(t, precision, scale, length, nullability, readonly, generatedAlwaysAs, generationOption, generationLocation, collation, characterSet, identity, defaultValue);
this.row = row;
}
@ -108,8 +110,9 @@ final class RecordDataType<R extends Record> extends DefaultDataType<R> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<R> newGeneratedAlwaysAs,
Generator<R> newGeneratedAlwaysAs,
GenerationOption newGenerationOption,
GenerationLocation newGenerationLocation,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -125,6 +128,7 @@ final class RecordDataType<R extends Record> extends DefaultDataType<R> {
newReadonly,
newGeneratedAlwaysAs,
newGenerationOption,
newGenerationLocation,
newCollation,
newCharacterSet,
newIdentity,

View File

@ -83,6 +83,7 @@ import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.DSL.trueCondition;
import static org.jooq.impl.FieldMapForUpdate.removeReadonly;
import static org.jooq.impl.FieldMapsForInsert.keysAndComputedOnClient;
import static org.jooq.impl.Keywords.K_FROM;
import static org.jooq.impl.Keywords.K_LIMIT;
import static org.jooq.impl.Keywords.K_ORDER_BY;
@ -90,6 +91,7 @@ import static org.jooq.impl.Keywords.K_ROW;
import static org.jooq.impl.Keywords.K_SET;
import static org.jooq.impl.Keywords.K_UPDATE;
import static org.jooq.impl.Keywords.K_WHERE;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.fieldName;
import static org.jooq.impl.Tools.map;
import static org.jooq.impl.Tools.unqualified;
@ -97,9 +99,11 @@ import static org.jooq.impl.Tools.visitSubquery;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.jooq.Clause;
import org.jooq.Condition;
@ -180,6 +184,8 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
private static final Set<SQLDialect> SUPPORT_RVE_SET = SQLDialect.supportedBy(H2, HSQLDB, POSTGRES, YUGABYTEDB);
private static final Set<SQLDialect> REQUIRE_RVE_ROW_CLAUSE = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
@ -539,13 +545,54 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
return from;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
final void accept0(Context<?> ctx) {
accept1(ctx);
}
private final UpdateQueryImpl<R> copy(Consumer<? super UpdateQueryImpl<R>> consumer) {
UpdateQueryImpl<R> u = new UpdateQueryImpl<>(configuration(), with, table);
if (!returning.isEmpty())
u.setReturning(returning);
u.updateMap.putAll(updateMap);
u.multiRow = multiRow;
u.multiSelect = multiSelect;
u.multiValue = multiValue;
u.from.addAll(from);
u.condition.setWhere(condition.getWhere());
u.orderBy.addAll(orderBy);
u.limit = limit;
consumer.accept(u);
return u;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
final void accept1(Context<?> ctx) {
boolean declareTables = ctx.declareTables();
ctx.start(UPDATE_UPDATE)
.visit(K_UPDATE)
@ -589,10 +636,7 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
map.put(k, Tools.field(v, k));
}
ctx.formatIndentStart()
.formatSeparator()
.visit(map)
.formatIndentEnd();
toSQLUpdateMap(ctx, map);
}
@ -609,9 +653,6 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
else {
Row row = removeReadonly(ctx, multiRow);
@ -658,12 +699,8 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
}
// A regular (non-multi-row) update was specified
else {
ctx.formatIndentStart()
.formatSeparator()
.visit(updateMap)
.formatIndentEnd();
}
else
toSQLUpdateMap(ctx, updateMap);
ctx.end(UPDATE_SET);
@ -671,19 +708,9 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
switch (ctx.family()) {
default:
acceptFrom(ctx);
break;
}
acceptFrom(ctx);
if (limit != null && NO_SUPPORT_LIMIT.contains(ctx.dialect()) || !orderBy.isEmpty() && NO_SUPPORT_ORDER_BY_LIMIT.contains(ctx.dialect())) {
Field<?>[] keyFields =
@ -736,6 +763,13 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
ctx.end(UPDATE_RETURNING);
}
private static final void toSQLUpdateMap(Context<?> ctx, FieldMapForUpdate updateMap) {
ctx.formatIndentStart()
.formatSeparator()
.visit(updateMap)
.formatIndentEnd();
}