From c466d68a85d255db151ac26d46532ddad74afed1 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 25 Aug 2023 11:32:29 +0200 Subject: [PATCH] [jOOQ/jOOQ#228] Enhance API to provide access to UDT members - WIP --- .../org/jooq/codegen/AbstractGenerator.java | 11 + .../codegen/DefaultGeneratorStrategy.java | 14 +- .../java/org/jooq/codegen/GenerationTool.java | 6 +- .../main/java/org/jooq/codegen/Generator.java | 10 + .../org/jooq/codegen/GeneratorStrategy.java | 6 +- .../java/org/jooq/codegen/JavaGenerator.java | 110 +++++++- .../main/kotlin/org/jooq/kotlin/Extensions.kt | 2 +- .../kotlin/org/jooq/meta/kotlin/Extensions.kt | 2 +- .../java/org/jooq/meta/jaxb/Generate.java | 42 +++ .../org/jooq/meta/xsd/jooq-codegen-3.19.0.xsd | 4 + jOOQ/src/main/java/org/jooq/UDTField.java | 2 + jOOQ/src/main/java/org/jooq/UDTPathField.java | 67 +++++ .../main/java/org/jooq/UDTPathTableField.java | 61 +++++ .../org/jooq/impl/FieldMapsForInsert.java | 4 +- .../src/main/java/org/jooq/impl/Internal.java | 223 ++++++++++++++++ .../java/org/jooq/impl/TableFieldImpl.java | 14 +- jOOQ/src/main/java/org/jooq/impl/Tools.java | 11 +- .../java/org/jooq/impl/UDTPathFieldImpl.java | 248 ++++++++++++++++++ .../org/jooq/impl/UDTPathTableFieldImpl.java | 76 ++++++ .../java/org/jooq/tools/reflect/Reflect.java | 4 +- 20 files changed, 891 insertions(+), 26 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/UDTPathField.java create mode 100644 jOOQ/src/main/java/org/jooq/UDTPathTableField.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/UDTPathFieldImpl.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/UDTPathTableFieldImpl.java diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java index 47486627e1..8c9646abaf 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java @@ -71,6 +71,7 @@ abstract class AbstractGenerator implements Generator { boolean generateDeprecationOnUnknownTypes = true; boolean generateIndexes = true; boolean generateRelations = true; + boolean generateUDTPaths = true; boolean generateImplicitJoinPathsToOne = true; boolean generateImplicitJoinPathsToMany = true; boolean generateImplicitJoinPathTableSubtypes = true; @@ -306,6 +307,16 @@ abstract class AbstractGenerator implements Generator { this.generateRelations = generateRelations; } + @Override + public boolean generateUDTPaths() { + return generateUDTPaths; + } + + @Override + public void setGenerateUDTPaths(boolean generateUDTPaths) { + this.generateUDTPaths = generateUDTPaths; + } + @Override public boolean generateImplicitJoinPathsToOne() { return generateImplicitJoinPathsToOne && generateRelations(); diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java index 8b7e224dcc..56d0b5d45d 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java @@ -66,6 +66,7 @@ import org.jooq.impl.SchemaImpl; import org.jooq.impl.TableImpl; import org.jooq.impl.TableRecordImpl; import org.jooq.impl.UDTImpl; +import org.jooq.impl.UDTPathTableFieldImpl; import org.jooq.impl.UDTRecordImpl; import org.jooq.impl.UpdatableRecordImpl; import org.jooq.meta.ArrayDefinition; @@ -335,14 +336,15 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { } else if (definition instanceof TableDefinition t) { switch (mode) { - case DAO: return DAOImpl.class.getName(); - case RECORD: return (t.getPrimaryKey() != null ? UpdatableRecordImpl.class : TableRecordImpl.class).getName(); + case DAO: return DAOImpl.class.getName(); + case RECORD: return (t.getPrimaryKey() != null ? UpdatableRecordImpl.class : TableRecordImpl.class).getName(); case DEFAULT: return TableImpl.class.getName(); } } else if (definition instanceof UDTDefinition) { switch (mode) { - case RECORD: return UDTRecordImpl.class.getName(); + case PATH: return UDTPathTableFieldImpl.class.getName(); + case RECORD: return UDTRecordImpl.class.getName(); case DEFAULT: return UDTImpl.class.getName(); } } @@ -464,7 +466,7 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { if (!(definition instanceof SchemaDefinition)) { // Some definitions have their dedicated subpackages, e.g. "tables", "routines" - String subPackage = getSubPackage(definition); + String subPackage = getSubPackage(definition, mode); if (!StringUtils.isBlank(subPackage)) { sb.append("."); sb.append(subPackage); @@ -547,7 +549,7 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { return result.toString(); } - private String getSubPackage(Definition definition) { + private String getSubPackage(Definition definition, Mode mode) { if (definition instanceof TableDefinition) return "tables"; @@ -561,6 +563,8 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { // [#330] [#6529] A UDT inside of a package is a PL/SQL RECORD type if (u.getPackage() != null) return "packages." + getJavaIdentifier(u.getPackage()).toLowerCase(targetLocale) + ".udt"; + else if (mode == Mode.PATH) + return "udt.paths"; else return "udt"; } diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java index aca92375ed..5f4daea0ef 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java @@ -736,9 +736,11 @@ public class GenerationTool { generator.setGenerateIndexes(g.getGenerate().isIndexes()); if (g.getGenerate().isRelations() != null) generator.setGenerateRelations(g.getGenerate().isRelations()); - if (g.getGenerate().isImplicitJoinPathsToMany() != null) - generator.setGenerateImplicitJoinPathsToOne(g.getGenerate().isImplicitJoinPathsToOne()); + if (g.getGenerate().isUdtPaths() != null) + generator.setGenerateUDTPaths(g.getGenerate().isUdtPaths()); if (g.getGenerate().isImplicitJoinPathsToOne() != null) + generator.setGenerateImplicitJoinPathsToOne(g.getGenerate().isImplicitJoinPathsToOne()); + if (g.getGenerate().isImplicitJoinPathsToMany() != null) generator.setGenerateImplicitJoinPathsToMany(g.getGenerate().isImplicitJoinPathsToMany()); if (g.getGenerate().isImplicitJoinPathTableSubtypes() != null) generator.setGenerateImplicitJoinPathTableSubtypes(g.getGenerate().isImplicitJoinPathTableSubtypes()); diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java index f78f57150a..7d34accacb 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java @@ -124,6 +124,16 @@ public interface Generator { */ void setGenerateRelations(boolean generateRelations); + /** + * Whether to generate UDT path expressions on tables and UDTs. + */ + boolean generateUDTPaths(); + + /** + * Whether to generate UDT path expressions on tables and UDTs. + */ + void setGenerateUDTPaths(boolean generateUDTPaths); + /** * Whether implicit join path constructors on generated tables for outgoing * foreign key relationships (to-one relationships) should be generated. diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/GeneratorStrategy.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/GeneratorStrategy.java index 24e5ba62a2..56521722eb 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/GeneratorStrategy.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/GeneratorStrategy.java @@ -567,9 +567,9 @@ public interface GeneratorStrategy { DOMAIN, /** - * The path mode. This is used when a {@link ForeignKeyDefinition} or an - * {@link InverseForeignKeyDefinition} is used to generate a path - * expression. + * The path mode. This is used when a {@link ForeignKeyDefinition}, an + * {@link InverseForeignKeyDefinition}, or a {@link UDTDefinition} is + * used to generate a path expression. */ PATH diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java index c8f104b0de..facfb004f0 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -95,8 +95,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.jooq.AggregateFunction; +import org.jooq.Binding; import org.jooq.Catalog; import org.jooq.Check; +import org.jooq.Comment; import org.jooq.Condition; import org.jooq.Configuration; import org.jooq.Constants; @@ -122,6 +124,7 @@ import org.jooq.PlainSQL; import org.jooq.Query; import org.jooq.QueryPart; import org.jooq.Record; +import org.jooq.RecordQualifier; import org.jooq.Records; import org.jooq.Result; import org.jooq.Row; @@ -3171,6 +3174,10 @@ public class JavaGenerator extends AbstractGenerator { for (UDTDefinition udt : database.getUDTs(schema)) { try { generateUDT(schema, udt); + + // [#228] Package UDTs can't be used as path expressions in SQL + if (generateUDTPaths() && udt.getPackage() == null) + generateUDTPath(schema, udt); } catch (Exception e) { log.error("Error while generating udt " + udt, e); @@ -3349,6 +3356,92 @@ public class JavaGenerator extends AbstractGenerator { printClassJavadoc(out, "The udt " + udt.getQualifiedInputName() + "."); } + @SuppressWarnings("unused") + protected void generateUDTPath(SchemaDefinition schema, UDTDefinition udt) { + JavaWriter out = newJavaWriter(getFile(udt, Mode.PATH)); + out.refConflicts(getStrategy().getJavaIdentifiers(udt.getAttributes())); + + log.info("Generating UDT Path", out.file().getName()); + + if (log.isDebugEnabled()) + for (AttributeDefinition attribute : udt.getAttributes()) + log.debug("With attribute", "name=" + attribute.getOutputName() + ", matching type names=" + attribute.getDefinedType().getMatchNames()); + + generateUDTPath(udt, out); + closeJavaWriter(out); + } + + protected void generateUDTPath(UDTDefinition udt, JavaWriter out) { + final String className = getStrategy().getJavaClassName(udt, Mode.PATH); + final String recordType = out.ref(getStrategy().getFullJavaClassName(udt, Mode.RECORD)); + final String classExtends = out.ref(getStrategy().getJavaClassExtends(udt, Mode.PATH)); + final List interfaces = out.ref(getStrategy().getJavaClassImplements(udt, Mode.PATH)); + final String udtId = out.ref(getStrategy().getFullJavaIdentifier(udt), 2); + + printPackage(out, udt, Mode.PATH); + + generateUDTPathClassJavadoc(udt, out); + printClassAnnotations(out, udt, Mode.PATH); + + out.println("%sclass %s extends %s[[before= implements ][%s]] {", + visibility(), className, Record.class, classExtends, recordType, interfaces); + out.printSerial(); + + for (AttributeDefinition attribute : udt.getAttributes()) { + final String attrTypeFull = getJavaType(attribute.getType(resolver(out)), out); + final String attrType = out.ref(attrTypeFull); + final String attrTypeRef = getJavaTypeReference(attribute.getDatabase(), attribute.getType(resolver(out)), out); + final String attrId = out.ref(getStrategy().getJavaIdentifier(attribute), 2); + final String attrName = attribute.getName(); + final List converter = out.ref(list(attribute.getType(resolver(out)).getConverter())); + final List binding = out.ref(list(attribute.getType(resolver(out)).getBinding())); + + if (!printDeprecationIfUnknownType(out, attrTypeFull)) + out.javadoc("The attribute %s.[[before= ][%s]]", attribute.getQualifiedOutputName(), list(escapeEntities(comment(attribute)))); + + if (attribute.getType().isUDT() && !attribute.getDatabase().isArrayType(attrTypeFull)) { + final SchemaDefinition attrUdtSchema = attribute.getDatabase().getSchema(attribute.getType().getQualifiedUserType().qualifier().last()); + final UDTDefinition attrUdt = attribute.getDatabase().getUDT(attrUdtSchema, attribute.getType().getUserType()); + final String attrPathType = out.ref(getStrategy().getFullJavaClassName(attrUdt, Mode.PATH)); + + out.println("%sfinal %s<%s, %s> %s = %s.createUDTPathField(%s.name(\"%s\"), %s, this, \"%s\", %s.class" + converterTemplate(converter) + converterTemplate(binding) + ");", + visibility(), attrPathType, recordType, attrType, attrId, Internal.class, DSL.class, escapeString(attrName), attrTypeRef, escapeString(""), attrPathType, converter, binding); + } + else { + final String attrPathType = out.ref(UDTField.class); + + out.println("%sfinal %s<%s, %s> %s = %s.createUDTPathField(%s.name(\"%s\"), %s, this, \"%s\", %s.class" + converterTemplate(converter) + converterTemplate(binding) + ");", + visibility(), attrPathType, recordType, attrType, attrId, Internal.class, DSL.class, escapeString(attrName), attrTypeRef, escapeString(""), attrPathType, converter, binding); + } + } + + out.println(); + out.println("%s%s(%s name, %s type, %s qualifier, %s comment, %s binding) {", + visibility(), className, Name.class, DataType.class, RecordQualifier.class, Comment.class, Binding.class); + out.println("super(name, type, qualifier, %s, comment, binding);", udtId); + out.println("}"); + + generateUDTPathClassFooter(udt, out); + out.println("}"); + closeJavaWriter(out); + } + + /** + * Subclasses may override this method to provide udt class footer code. + */ + @SuppressWarnings("unused") + protected void generateUDTPathClassFooter(UDTDefinition udt, JavaWriter out) {} + + /** + * Subclasses may override this method to provide their own Javadoc. + */ + protected void generateUDTPathClassJavadoc(UDTDefinition udt, JavaWriter out) { + if (generateCommentsOnUDTs()) + printClassJavadoc(out, udt); + else + printClassJavadoc(out, "The udt path for " + udt.getQualifiedInputName() + "."); + } + protected void generateUDTPojos(SchemaDefinition schema) { log.info("Generating UDT POJOs"); @@ -6316,8 +6409,21 @@ public class JavaGenerator extends AbstractGenerator { 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) + converterTemplate(generator) + ");", - columnVisibility, isStatic, TableField.class, recordType, columnType, columnId, DSL.class, columnName, columnTypeRef, tableRef, escapeString(comment(column)), converter, binding, generator); + if (generateInstanceFields() + && generateUDTPaths() + && column.getType().isUDT() + && !column.getDatabase().isArrayType(columnTypeFull) + ) { + final SchemaDefinition columnUdtSchema = column.getDatabase().getSchema(column.getType().getQualifiedUserType().qualifier().last()); + final UDTDefinition columnUdt = column.getDatabase().getUDT(columnUdtSchema, column.getType().getUserType()); + final String columnPathType = out.ref(getStrategy().getFullJavaClassName(columnUdt, Mode.PATH)); + + out.println("%sfinal %s<%s, %s> %s = %s.createUDTPathTableField(%s.name(\"%s\"), %s, this, \"%s\", %s.class" + converterTemplate(converter) + converterTemplate(binding) + ");", + visibility(), columnPathType, recordType, columnType, columnId, Internal.class, DSL.class, escapeString(columnName), columnTypeRef, escapeString(comment(column)), columnPathType, converter, binding); + } + else + 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); } } diff --git a/jOOQ-kotlin/src/main/kotlin/org/jooq/kotlin/Extensions.kt b/jOOQ-kotlin/src/main/kotlin/org/jooq/kotlin/Extensions.kt index 7e7c970e64..44712c8231 100644 --- a/jOOQ-kotlin/src/main/kotlin/org/jooq/kotlin/Extensions.kt +++ b/jOOQ-kotlin/src/main/kotlin/org/jooq/kotlin/Extensions.kt @@ -703,7 +703,7 @@ fun InformationSchema.indexes(block: MutableList.( } @JvmName("mutableListIndex") -fun MutableList.indexe(block: org.jooq.util.xml.jaxb.Index.() -> Unit) { +fun MutableList.index(block: org.jooq.util.xml.jaxb.Index.() -> Unit) { val e = org.jooq.util.xml.jaxb.Index() block(e) add(e) diff --git a/jOOQ-meta-kotlin/src/main/kotlin/org/jooq/meta/kotlin/Extensions.kt b/jOOQ-meta-kotlin/src/main/kotlin/org/jooq/meta/kotlin/Extensions.kt index f59a9e3b94..4de1a3da02 100644 --- a/jOOQ-meta-kotlin/src/main/kotlin/org/jooq/meta/kotlin/Extensions.kt +++ b/jOOQ-meta-kotlin/src/main/kotlin/org/jooq/meta/kotlin/Extensions.kt @@ -99,7 +99,7 @@ fun Matchers.indexes(block: MutableList.() -> Unit) { } @JvmName("mutableListMatchersIndexType") -fun MutableList.indexe(block: MatchersIndexType.() -> Unit) { +fun MutableList.index(block: MatchersIndexType.() -> Unit) { val e = MatchersIndexType() block(e) add(e) diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java index 6dab38cee8..8bb6a1aa22 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Generate.java @@ -37,6 +37,8 @@ public class Generate implements Serializable, XMLAppendable @XmlElement(defaultValue = "true") protected Boolean sequenceFlags = true; @XmlElement(defaultValue = "true") + protected Boolean udtPaths = true; + @XmlElement(defaultValue = "true") protected Boolean implicitJoinPathsToOne = true; @XmlElement(defaultValue = "true") protected Boolean implicitJoinPathsToMany = true; @@ -332,6 +334,30 @@ public class Generate implements Serializable, XMLAppendable this.sequenceFlags = value; } + /** + * Generate UDT path expressions on tables and on UDTs. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isUdtPaths() { + return udtPaths; + } + + /** + * Sets the value of the udtPaths property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setUdtPaths(Boolean value) { + this.udtPaths = value; + } + /** * Generate implicit join path constructors on generated tables for outgoing foreign key relationships (to-one relationships) * @@ -2892,6 +2918,11 @@ public class Generate implements Serializable, XMLAppendable return this; } + public Generate withUdtPaths(Boolean value) { + setUdtPaths(value); + return this; + } + public Generate withImplicitJoinPathsToOne(Boolean value) { setImplicitJoinPathsToOne(value); return this; @@ -3490,6 +3521,7 @@ public class Generate implements Serializable, XMLAppendable builder.append("indexes", indexes); builder.append("relations", relations); builder.append("sequenceFlags", sequenceFlags); + builder.append("udtPaths", udtPaths); builder.append("implicitJoinPathsToOne", implicitJoinPathsToOne); builder.append("implicitJoinPathsToMany", implicitJoinPathsToMany); builder.append("implicitJoinPathTableSubtypes", implicitJoinPathTableSubtypes); @@ -3646,6 +3678,15 @@ public class Generate implements Serializable, XMLAppendable return false; } } + if (udtPaths == null) { + if (other.udtPaths!= null) { + return false; + } + } else { + if (!udtPaths.equals(other.udtPaths)) { + return false; + } + } if (implicitJoinPathsToOne == null) { if (other.implicitJoinPathsToOne!= null) { return false; @@ -4628,6 +4669,7 @@ public class Generate implements Serializable, XMLAppendable result = ((prime*result)+((indexes == null)? 0 :indexes.hashCode())); result = ((prime*result)+((relations == null)? 0 :relations.hashCode())); result = ((prime*result)+((sequenceFlags == null)? 0 :sequenceFlags.hashCode())); + result = ((prime*result)+((udtPaths == null)? 0 :udtPaths.hashCode())); result = ((prime*result)+((implicitJoinPathsToOne == null)? 0 :implicitJoinPathsToOne.hashCode())); result = ((prime*result)+((implicitJoinPathsToMany == null)? 0 :implicitJoinPathsToMany.hashCode())); result = ((prime*result)+((implicitJoinPathTableSubtypes == null)? 0 :implicitJoinPathTableSubtypes.hashCode())); diff --git a/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.19.0.xsd b/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.19.0.xsd index 0941345058..1b2e327db2 100644 --- a/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.19.0.xsd +++ b/jOOQ-meta/src/main/resources/org/jooq/meta/xsd/jooq-codegen-3.19.0.xsd @@ -2166,6 +2166,10 @@ This is a prerequisite for various advanced features]]> + + + + diff --git a/jOOQ/src/main/java/org/jooq/UDTField.java b/jOOQ/src/main/java/org/jooq/UDTField.java index 68639072c7..6ee00a02cb 100644 --- a/jOOQ/src/main/java/org/jooq/UDTField.java +++ b/jOOQ/src/main/java/org/jooq/UDTField.java @@ -38,6 +38,7 @@ package org.jooq; +import org.jetbrains.annotations.Nullable; /** * A field contained in a UDT. @@ -54,5 +55,6 @@ public interface UDTField, T> extends Field { /** * @return The UDT this field is contained in */ + @Nullable UDT getUDT(); } diff --git a/jOOQ/src/main/java/org/jooq/UDTPathField.java b/jOOQ/src/main/java/org/jooq/UDTPathField.java new file mode 100644 index 0000000000..d7fe1e87b0 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/UDTPathField.java @@ -0,0 +1,67 @@ +/* + * 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 + * + * https://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: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +package org.jooq; + +import org.jetbrains.annotations.NotNull; + +/** + * A field dereferenced from a UDT path. + *

+ * Instances of this type cannot be created directly. They are available from + * generated code. + * + * @param The record type + * @param The field type + * @author Lukas Eder + */ +public interface UDTPathField, T> extends UDTField { + + /** + * @return The UDT path this field is contained in. + */ + @NotNull + RecordQualifier getQualifier(); + + /** + * @return The UDT path represented by this field. + */ + @NotNull + RecordQualifier asQualifier(); + +} diff --git a/jOOQ/src/main/java/org/jooq/UDTPathTableField.java b/jOOQ/src/main/java/org/jooq/UDTPathTableField.java new file mode 100644 index 0000000000..80a060b614 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/UDTPathTableField.java @@ -0,0 +1,61 @@ +/* + * 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 + * + * https://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: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +package org.jooq; + +import org.jetbrains.annotations.NotNull; + +/** + * A field dereferenced from a UDT path. + *

+ * Instances of this type cannot be created directly. They are available from + * generated code. + * + * @param The record type + * @param The field type + * @author Lukas Eder + */ +public interface UDTPathTableField, T> extends TableField, UDTPathField { + + /** + * @return The UDT path this field is contained in. + */ + @Override + @NotNull + RecordQualifier getQualifier(); +} diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java b/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java index 1ec9640028..b4dabe5db6 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java @@ -52,7 +52,6 @@ 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.FieldMapsForInsert.toSQLInsertSelect; import static org.jooq.impl.Keywords.K_DEFAULT_VALUES; import static org.jooq.impl.Keywords.K_VALUES; import static org.jooq.impl.QueryPartCollectionView.wrap; @@ -64,6 +63,7 @@ import static org.jooq.impl.Tools.flattenCollection; import static org.jooq.impl.Tools.flattenFieldOrRows; import static org.jooq.impl.Tools.lazy; import static org.jooq.impl.Tools.row0; +import static org.jooq.impl.Tools.BooleanDataKey.DATA_STORE_ASSIGNMENT; import java.util.AbstractList; import java.util.AbstractMap; @@ -754,7 +754,7 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple fields = keysFlattened(ctx, GeneratorStatementType.INSERT); if (!fields.isEmpty()) - ctx.sql(" (").visit(wrap(fields).qualify(false)).sql(')'); + ctx.data(DATA_STORE_ASSIGNMENT, true, c -> c.sql(" (").visit(wrap(fields).qualify(false)).sql(')')); return fields; } diff --git a/jOOQ/src/main/java/org/jooq/impl/Internal.java b/jOOQ/src/main/java/org/jooq/impl/Internal.java index 7692a3d3a5..022077caaf 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Internal.java +++ b/jOOQ/src/main/java/org/jooq/impl/Internal.java @@ -67,6 +67,7 @@ import org.jooq.Domain; import org.jooq.EmbeddableRecord; import org.jooq.Field; import org.jooq.ForeignKey; +import org.jooq.Generator; import org.jooq.Identity; import org.jooq.Index; import org.jooq.InverseForeignKey; @@ -79,6 +80,7 @@ import org.jooq.Queries; import org.jooq.Query; import org.jooq.QueryPart; import org.jooq.Record; +import org.jooq.RecordQualifier; // ... // ... import org.jooq.Result; @@ -95,11 +97,16 @@ import org.jooq.TableField; // ... // ... import org.jooq.UDT; +import org.jooq.UDTField; +import org.jooq.UDTPathField; +import org.jooq.UDTPathTableField; import org.jooq.UDTRecord; import org.jooq.UniqueKey; // ... import org.jooq.exception.DataAccessException; +import org.jooq.exception.DataTypeException; import org.jooq.impl.QOM.CreateTable; +import org.jooq.impl.QOM.GenerationLocation; // ... // ... @@ -118,6 +125,222 @@ import org.reactivestreams.Subscription; @org.jooq.Internal public final class Internal { + public static final , T, P extends UDTPathTableField> P createUDTPathTableField( + Name name, + DataType type, + Table table, + Class

returnType + ) { + return createUDTPathTableField(name, type, table, null, returnType, null, null, null); + } + + public static final , T, P extends UDTPathTableField> P createUDTPathTableField( + Name name, + DataType type, + Table table, + String comment, + Class

returnType + ) { + return createUDTPathTableField(name, type, table, comment, returnType, null, null, null); + } + + public static final , T, U, P extends UDTPathTableField> P createUDTPathTableField( + Name name, + DataType type, + Table table, + String comment, + Class

returnType, + Converter converter + ) { + return createUDTPathTableField(name, type, table, comment, returnType, converter, null, null); + } + + public static final , T, U, P extends UDTPathTableField> P createUDTPathTableField( + Name name, + DataType type, + Table table, + String comment, + Class

returnType, + Binding binding + ) { + return createUDTPathTableField(name, type, table, comment, returnType, null, binding, null); + } + + public static final , T, X, U, P extends UDTPathTableField> P createUDTPathTableField( + Name name, + DataType type, + Table table, + String comment, + Class

returnType, + Converter converter, + Binding binding + ) { + return createUDTPathTableField(name, type, table, comment, returnType, converter, binding, null); + } + + public static final , UR extends UDTRecord, T, P extends UDTPathTableField> P createUDTPathTableField( + Name name, + DataType type, + TR table, + String comment, + Class

returnType, + Generator generator + ) { + return createUDTPathTableField(name, type, table, comment, returnType, null, null, generator); + } + + public static final , UR extends UDTRecord, T, U, P extends UDTPathTableField> P createUDTPathTableField( + Name name, + DataType type, + TR table, + String comment, + Class

returnType, + Converter converter, + Generator generator + ) { + return createUDTPathTableField(name, type, table, comment, returnType, converter, null, generator); + } + + public static final , UR extends UDTRecord, T, U, P extends UDTPathTableField> P createUDTPathTableField( + Name name, + DataType type, + TR table, + String comment, + Class

returnType, + Binding binding, + Generator generator + ) { + return createUDTPathTableField(name, type, table, comment, returnType, null, binding, generator); + } + + @SuppressWarnings("unchecked") + public static final , UR extends UDTRecord, T, X, U, P extends UDTPathTableField> P createUDTPathTableField( + Name name, + DataType type, + TR table, + String comment, + Class

returnType, + Converter converter, + Binding binding, + Generator generator + ) { + Binding actualBinding = DefaultBinding.newBinding(converter, type, binding); + DataType actualType = + converter == null && binding == null + ? (DataType) type + : type.asConvertedDataType(actualBinding); + + if (generator != null) + actualType = actualType.generatedAlwaysAs(generator).generationLocation(GenerationLocation.CLIENT); + + // [#5999] TODO: Allow for user-defined Names + try { + P tableField = newInstance(name, table, null, comment, returnType, actualBinding, actualType); + + // [#1199] The public API of Table returns immutable field lists + if (table instanceof TableImpl t) + t.fields.add(tableField); + + return tableField; + } + catch (Exception e) { + throw new DataTypeException("Cannot instantiate " + returnType + ".", e); + } + } + + public static final , T, P extends UDTField> P createUDTPathField( + Name name, + DataType type, + UDTPathField qualifier, + Class

returnType + ) { + return createUDTPathField(name, type, qualifier, null, returnType, null, null); + } + + public static final , T, P extends UDTField> P createUDTPathField( + Name name, + DataType type, + UDTPathField qualifier, + String comment, + Class

returnType + ) { + return createUDTPathField(name, type, qualifier, comment, returnType, null, null); + } + + public static final , T, U, P extends UDTField> P createUDTPathField( + Name name, + DataType type, + UDTPathField qualifier, + String comment, + Class

returnType, + Converter converter + ) { + return createUDTPathField(name, type, qualifier, comment, returnType, converter, null); + } + + public static final , T, U, P extends UDTField> P createUDTPathField( + Name name, + DataType type, + UDTPathField qualifier, + String comment, + Class

returnType, + Binding binding + ) { + return createUDTPathField(name, type, qualifier, comment, returnType, null, binding); + } + + public static final , T, X, U, P extends UDTField> P createUDTPathField( + Name name, + DataType type, + UDTPathField qualifier, + String comment, + Class

returnType, + Converter converter, + Binding binding + ) { + Binding actualBinding = DefaultBinding.newBinding(converter, type, binding); + DataType actualType = + converter == null && binding == null + ? (DataType) type + : type.asConvertedDataType(actualBinding); + + // [#5999] TODO: Allow for user-defined Names + try { + + // [#228] While it would be cleaner to pass around a Function5 constructor reference, + // chances are that the cost on compilation speed is significantly higher than + // if we just use reflection + return newInstance(name, null, qualifier, comment, returnType, actualBinding, actualType); + } + catch (Exception e) { + throw new DataTypeException("Cannot instantiate " + returnType + ".", e); + } + } + + @SuppressWarnings("unchecked") + private static , T, X, U, P extends UDTField> P newInstance( + Name name, + RecordQualifier qualifier, + UDTPathField path, + String comment, + Class

returnType, + Binding actualBinding, + DataType actualType + ) throws Exception { + + // [#228] This case only happens in generated UDTPath types, never in generated Table types + if (returnType == UDTField.class) + return (P) new UDTPathFieldImpl<>(name, actualType, path.asQualifier(), path.getUDT(), DSL.comment(comment), actualBinding); + + // [#228] While it would be cleaner to pass around a Function5 constructor reference, + // chances are that the cost on compilation speed is significantly higher than + // if we just use reflection + else + return returnType + .getConstructor(Name.class, DataType.class, RecordQualifier.class, Comment.class, Binding.class) + .newInstance(name, actualType, qualifier == null ? path.asQualifier() : qualifier, DSL.comment(comment), actualBinding); + } + /** * Factory method for embeddable types. */ diff --git a/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java b/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java index 0cc739177a..1882e2e89e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableFieldImpl.java @@ -78,7 +78,7 @@ import org.jooq.tools.StringUtils; */ final class TableFieldImpl extends - AbstractField + AbstractField implements TableField, SimpleQueryPart, @@ -201,11 +201,15 @@ implements } private final void accept1(Context ctx) { - ctx.data(DATA_OMIT_CLAUSE_EVENT_EMISSION, true, c -> { - if (c.qualify() && getTable() != null && !FALSE.equals(ctx.data(DATA_RENDER_TABLE))) - c.visit(getTable()).sql('.'); + accept2(ctx, getTable(), getUnqualifiedName()); + } - c.visit(getUnqualifiedName()); + static final void accept2(Context ctx, Table table, Name unqualifiedName) { + ctx.data(DATA_OMIT_CLAUSE_EVENT_EMISSION, true, c -> { + if (c.qualify() && table != null && !FALSE.equals(ctx.data(DATA_RENDER_TABLE))) + c.visit(table).sql('.'); + + c.visit(unqualifiedName); }); } diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index dc1b9a9535..fae50b5c32 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -658,8 +658,14 @@ final class Tools { DATA_PARSE_ON_CONFLICT, /** - * [#13808] We're in a store assignment context (e.g. - * UPDATE or assignment statement). + * [#228] [#13808] We're in a store assignment context. + *

+ * This includes e.g. + *

    + *
  • INSERT columns list.
  • + *
  • UPDATE … SET clause.
  • + *
  • The procedural assignment statement.
  • + *
*/ DATA_STORE_ASSIGNMENT, @@ -668,7 +674,6 @@ final class Tools { * not the query itself. */ DATA_RENDER_IMPLICIT_JOIN, - ; private final boolean resetInSubqueryScope; diff --git a/jOOQ/src/main/java/org/jooq/impl/UDTPathFieldImpl.java b/jOOQ/src/main/java/org/jooq/impl/UDTPathFieldImpl.java new file mode 100644 index 0000000000..add35ffbb0 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/UDTPathFieldImpl.java @@ -0,0 +1,248 @@ +/* + * 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 + * + * https://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: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +package org.jooq.impl; + +import static java.lang.Boolean.TRUE; +import static org.jooq.impl.SchemaImpl.DEFAULT_SCHEMA; +import static org.jooq.impl.Tools.BooleanDataKey.DATA_STORE_ASSIGNMENT; +import static org.jooq.tools.StringUtils.defaultIfNull; + +import java.util.stream.Stream; + +import org.jooq.Binding; +import org.jooq.Catalog; +import org.jooq.Clause; +import org.jooq.Comment; +import org.jooq.Context; +import org.jooq.DataType; +import org.jooq.Field; +import org.jooq.Name; +import org.jooq.Package; +import org.jooq.QueryPart; +import org.jooq.Record; +import org.jooq.RecordQualifier; +// ... +import org.jooq.Row; +import org.jooq.Schema; +import org.jooq.Table; +// ... +import org.jooq.UDT; +import org.jooq.UDTPathField; +import org.jooq.UDTRecord; +import org.jooq.impl.QOM.UEmpty; +import org.jooq.impl.QOM.UNotYetImplemented; +import org.jooq.impl.Tools.BooleanDataKey; +import org.jooq.tools.StringUtils; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.ApiStatus.Internal; + +/** + * A common base type for UDT path fields. + * + * @author Lukas Eder + */ +@Internal +public /* non-final */ class UDTPathFieldImpl, T> +extends + AbstractField +implements + UDTPathField, + SimpleQueryPart, + TypedReference, + ScopeMappable, + UEmpty +{ + + private final RecordQualifier qualifier; + private final UDT udt; + + UDTPathFieldImpl(Name name, DataType type, RecordQualifier qualifier, UDT udt, Comment comment, Binding binding) { + super(qualify(qualifier, name), type, comment, binding); + + this.qualifier = qualifier; + this.udt = udt; + + qualifier.$name(); + } + + @Override + public final RecordQualifier getQualifier() { + return qualifier; + } + + @Override + public final RecordQualifier asQualifier() { + return new UDTPathFieldImplAsQualifier(); + } + + // [#228] TODO Refactor this logic into UDTImpl + private class UDTPathFieldImplAsQualifier + extends AbstractNamed + implements + RecordQualifier, + FieldsTrait, + UNotYetImplemented + { + UDTPathFieldImplAsQualifier() { + super(UDTPathFieldImpl.this.getQualifiedName(), UDTPathFieldImpl.this.getCommentPart()); + } + + RecordQualifier getQualifier() { + return UDTPathFieldImpl.this.getQualifier(); + } + + @Override + public final Catalog getCatalog() { + return null; + } + + @Override + public final Schema getSchema() { + return null; + } + + @Override + public final Schema $schema() { + return null; + } + + @Override + public final Row fieldsRow() { + return getUDT().fieldsRow(); + } + + @Override + public final Package getPackage() { + return getUDT().getPackage(); + } + + @Override + public final Class getRecordType() { + return getUDT().getRecordType(); + } + + @Override + public final DataType getDataType() { + return getUDT().getDataType(); + } + + @Override + public final U newRecord() { + return getUDT().newRecord(); + } + + @Override + public final void accept(Context ctx) { + UDTPathFieldImpl.this.accept(ctx); + } + } + + @Override + public final UDT getUDT() { + return udt; + } + + // ------------------------------------------------------------------------ + // XXX: QueryPart API + // ------------------------------------------------------------------------ + + @Override + final boolean parenthesised(Context ctx) { + return true; + } + + @Override + public boolean declaresFields() { + return super.declaresFields(); + } + + @Override + public final Clause[] clauses(Context ctx) { + return null; + } + + @Override + public final void accept(Context ctx) { + RecordQualifier q = getQualifier(); + + // [#228] The disambiguating wrapping in parentheses is only required in references to + // a UDT path identifier expression, not in assignments (where it is disallowed) + if (!TRUE.equals(ctx.data(DATA_STORE_ASSIGNMENT)) && q instanceof UDTPathFieldImpl.UDTPathFieldImplAsQualifier && ((UDTPathFieldImpl.UDTPathFieldImplAsQualifier) q).getQualifier() instanceof Table) + ctx.sql('(').visit(q).sql(").").visit(getUnqualifiedName()); + else if (q instanceof Table t) + TableFieldImpl.accept2(ctx, t, getUnqualifiedName()); + else + ctx.visit(q).sql('.').visit(getUnqualifiedName()); + } + + // ------------------------------------------------------------------------- + // XXX: Query Object Model + // ------------------------------------------------------------------------- + + // ------------------------------------------------------------------------ + // XXX: Object API + // ------------------------------------------------------------------------ + + @Override + public int hashCode() { + return defaultIfNull(getQualifier().getSchema(), DEFAULT_SCHEMA.get()).getQualifiedName() + .append(getQualifier().getUnqualifiedName()) + .append(getUDT().getUnqualifiedName()) + .append(getUnqualifiedName()).hashCode(); + } + + @Override + public boolean equals(Object that) { + if (this == that) + return true; + + // [#2144] UDTPathFieldImpl equality can be decided without executing the + // rather expensive implementation of AbstractQueryPart.equals() + if (that instanceof UDTPathField other) { + return + StringUtils.equals(getQualifier(), other.getQualifier()) && + StringUtils.equals(getUDT(), other.getUDT()) && + StringUtils.equals(getName(), other.getName()); + } + + return super.equals(that); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/UDTPathTableFieldImpl.java b/jOOQ/src/main/java/org/jooq/impl/UDTPathTableFieldImpl.java new file mode 100644 index 0000000000..827c1c2dcb --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/UDTPathTableFieldImpl.java @@ -0,0 +1,76 @@ +/* + * 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 + * + * https://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: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +package org.jooq.impl; + +import org.jooq.Binding; +import org.jooq.Comment; +import org.jooq.DataType; +import org.jooq.Name; +import org.jooq.Record; +import org.jooq.RecordQualifier; +import org.jooq.Table; +import org.jooq.UDT; +import org.jooq.UDTPathField; +import org.jooq.UDTPathTableField; +import org.jooq.UDTRecord; + +import org.jetbrains.annotations.ApiStatus.Internal; + +/** + * A common base type for table fields that are also {@link UDTPathField}. + * + * @author Lukas Eder + */ +@Internal +public /* non-final */ class UDTPathTableFieldImpl, T> +extends + UDTPathFieldImpl +implements + UDTPathTableField +{ + + public UDTPathTableFieldImpl(Name name, DataType type, RecordQualifier qualifier, UDT udt, Comment comment, Binding binding) { + super(name, type, qualifier, udt, comment, binding); + } + + @Override + public final Table getTable() { + return getQualifier() instanceof Table t ? t : null; + } +} diff --git a/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java b/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java index e11ab16a4e..c63b168f4f 100644 --- a/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java +++ b/jOOQ/src/main/java/org/jooq/tools/reflect/Reflect.java @@ -118,7 +118,7 @@ public class Reflect { *

      * Supplier<String> supplier = Reflect.compile("org.joor.Test", """
      *   package org.joor;
-     *   @MyAnnotation
+     *   @MyAnnotation
      *   class Test implements java.util.function.Supplier<String> {
      *     public String get() {
      *       return "Hello World!";
@@ -146,7 +146,7 @@ public class Reflect {
      * 

      * Supplier<String> supplier = Reflect.compile("org.joor.Test", """
      *   package org.joor;
-     *   @MyAnnotation
+     *   @MyAnnotation
      *   class Test implements java.util.function.Supplier<String> {
      *     public String get() {
      *       return "Hello World!";