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 44712c8231..564b5ba1fc 100644 --- a/jOOQ-kotlin/src/main/kotlin/org/jooq/kotlin/Extensions.kt +++ b/jOOQ-kotlin/src/main/kotlin/org/jooq/kotlin/Extensions.kt @@ -508,6 +508,17 @@ fun MutableList.table(block: MappedTable.() -> Unit) { add(e) } +fun MappedSchema.udts(block: MutableList.() -> Unit) { + block(udts) +} + +@JvmName("mutableListMappedUDT") +fun MutableList.udt(block: MappedUDT.() -> Unit) { + val e = MappedUDT() + block(e) + add(e) +} + fun RenderMapping.schemata(block: MutableList.() -> Unit) { block(schemata) } diff --git a/jOOQ/src/main/java/org/jooq/RenamedTable.java b/jOOQ/src/main/java/org/jooq/RenamedTable.java index 6c8861e4b0..bdde156f32 100644 --- a/jOOQ/src/main/java/org/jooq/RenamedTable.java +++ b/jOOQ/src/main/java/org/jooq/RenamedTable.java @@ -46,7 +46,7 @@ import org.jooq.impl.TableImpl; * * @author Lukas Eder */ -class RenamedTable extends TableImpl { +final class RenamedTable extends TableImpl { RenamedTable(Schema schema, Table delegate, String rename) { super(name(rename), schema); diff --git a/jOOQ/src/main/java/org/jooq/RenamedUDT.java b/jOOQ/src/main/java/org/jooq/RenamedUDT.java new file mode 100644 index 0000000000..37366484c1 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/RenamedUDT.java @@ -0,0 +1,55 @@ +/* + * 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.jooq.impl.UDTImpl; + +/** + * A mapped table + * + * @author Lukas Eder + */ +final class RenamedUDT> extends UDTImpl { + + RenamedUDT(Schema schema, UDT delegate, String rename) { + super(rename, schema, delegate.getPackage(), delegate.isSynthetic()); + + for (Field field : delegate.fields()) + createField(field.getUnqualifiedName(), field.getDataType(), this); + } +} diff --git a/jOOQ/src/main/java/org/jooq/SchemaMapping.java b/jOOQ/src/main/java/org/jooq/SchemaMapping.java index a5dd604f30..752761474d 100644 --- a/jOOQ/src/main/java/org/jooq/SchemaMapping.java +++ b/jOOQ/src/main/java/org/jooq/SchemaMapping.java @@ -44,11 +44,15 @@ import static org.jooq.tools.StringUtils.isEmpty; import java.io.Serializable; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Function; +import java.util.function.Supplier; import org.jooq.conf.MappedCatalog; import org.jooq.conf.MappedSchema; +import org.jooq.conf.MappedSchemaObject; import org.jooq.conf.MappedTable; import org.jooq.conf.RenderMapping; import org.jooq.conf.Settings; @@ -96,6 +100,7 @@ public class SchemaMapping implements Serializable { private volatile transient Map catalogs; private volatile transient Map schemata; private volatile transient Map> tables; + private volatile transient Map> udts; /** * Construct a mapping from a {@link Configuration} object @@ -452,10 +457,31 @@ public class SchemaMapping implements Serializable { * @param table The generated table to be mapped * @return The configured table */ - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) @Nullable public Table map(Table table) { - Table result = table; + return map0(table, () -> (Map) getTables(), s -> s.getTables(), RenamedTable::new); + } + + /** + * Apply mapping to a given UDT + * + * @param udt The generated udt to be mapped + * @return The configured udt + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Nullable + public > UDT map(UDT udt) { + return map0(udt, () -> (Map) getUDTs(), s -> s.getUdts(), RenamedUDT::new); + } + + private Q map0( + Q part, + Supplier> map, + Function> schemaObjects, + Function3 rename + ) { + Q result = part; // [#4652] Don't initialise table mapping if not necessary if (result != null && (!mapping().getSchemata().isEmpty() || !mapping().getCatalogs().isEmpty())) { @@ -472,35 +498,35 @@ public class SchemaMapping implements Serializable { String catalogName = catalog.getName(); String schemaName = schema.getName(); - String tableName = result.getName(); + String name = result.getName(); String key = StringUtils.isEmpty(catalogName) ? - (StringUtils.isEmpty(schemaName) ? tableName : (schemaName + "." + tableName)) - : (catalogName + '.' + schemaName + '.' + tableName); + (StringUtils.isEmpty(schemaName) ? name : (schemaName + "." + name)) + : (catalogName + '.' + schemaName + '.' + name); // Lazy initialise table mapping - if (!getTables().containsKey(key)) { + if (!map.get().containsKey(key)) { // [#1857] thread-safe lazy initialisation for those users who // want to use Configuration and dependent objects in a "thread-safe" manner synchronized (this) { - if (!getTables().containsKey(key)) { + if (!map.get().containsKey(key)) { catalogLoop: for (MappedCatalog c : mapping().getCatalogs()) { if (matches(c, catalogName)) { for (MappedSchema s : c.getSchemata()) { if (matches(s, schemaName)) { - for (MappedTable t : s.getTables()) { + for (MappedSchemaObject t : schemaObjects.apply(s)) { // A configured mapping was found, add a renamed table - if (matches(t, tableName)) { + if (matches(t, name)) { // Ignore self-mappings and void-mappings if (!isBlank(t.getOutput())) - if (t.getInput() != null && !t.getOutput().equals(tableName)) - result = new RenamedTable<>(map(schema), result, t.getOutput()); + if (t.getInput() != null && !t.getOutput().equals(name)) + result = rename.apply(map(schema), result, t.getOutput()); else if (t.getInputExpression() != null) - result = new RenamedTable<>(map(schema), result, t.getInputExpression().matcher(tableName).replaceAll(t.getOutput())); + result = rename.apply(map(schema), result, t.getInputExpression().matcher(name).replaceAll(t.getOutput())); break catalogLoop; } @@ -509,7 +535,7 @@ public class SchemaMapping implements Serializable { } // [#7498] Even without table mapping configuration, we may still need to map the schema - result = new RenamedTable<>(map(schema), result, tableName); + result = rename.apply(map(schema), result, name); break catalogLoop; } } @@ -518,35 +544,35 @@ public class SchemaMapping implements Serializable { schemaLoop: for (MappedSchema s : mapping().getSchemata()) { if (matches(s, schemaName)) { - for (MappedTable t : s.getTables()) { + for (MappedSchemaObject t : schemaObjects.apply(s)) { // A configured mapping was found, add a renamed table - if (matches(t, tableName)) { + if (matches(t, name)) { // Ignore self-mappings and void-mappings if (!isBlank(t.getOutput())) - if (t.getInput() != null && !t.getOutput().equals(tableName)) - result = new RenamedTable<>(map(schema), result, t.getOutput()); + if (t.getInput() != null && !t.getOutput().equals(name)) + result = rename.apply(map(schema), result, t.getOutput()); else if (t.getInputExpression() != null) - result = new RenamedTable<>(map(schema), result, t.getInputExpression().matcher(tableName).replaceAll(t.getOutput())); + result = rename.apply(map(schema), result, t.getInputExpression().matcher(name).replaceAll(t.getOutput())); break schemaLoop; } } // [#7498] Even without table mapping configuration, we may still need to map the schema - result = new RenamedTable<>(map(schema), result, tableName); + result = rename.apply(map(schema), result, name); break schemaLoop; } } // Add mapped table or self if no mapping was found - getTables().put(key, result); + map.get().put(key, result); } } } - result = (Table) getTables().get(key); + result = map.get().get(key); } return result; @@ -562,7 +588,7 @@ public class SchemaMapping implements Serializable { || (s.getInputExpression() != null && s.getInputExpression().matcher(schemaName).matches()); } - private final boolean matches(MappedTable t, String tableName) { + private final boolean matches(MappedSchemaObject t, String tableName) { return (t.getInput() != null && tableName.equals(t.getInput())) || (t.getInputExpression() != null && t.getInputExpression().matcher(tableName).matches()); } @@ -624,6 +650,20 @@ public class SchemaMapping implements Serializable { return tables; } + private final Map> getUDTs() { + if (udts == null) { + + // [#1857] thread-safe lazy initialisation for those users who + // want to use Configuration and dependent objects in a "thread-safe" manner + synchronized (this) { + if (udts == null) { + udts = new HashMap<>(); + } + } + } + return udts; + } + // ------------------------------------------------------------------------ // Object API // ------------------------------------------------------------------------ diff --git a/jOOQ/src/main/java/org/jooq/conf/MappedSchema.java b/jOOQ/src/main/java/org/jooq/conf/MappedSchema.java index 4178ff59d4..7c817447a9 100644 --- a/jOOQ/src/main/java/org/jooq/conf/MappedSchema.java +++ b/jOOQ/src/main/java/org/jooq/conf/MappedSchema.java @@ -43,6 +43,9 @@ public class MappedSchema @XmlElementWrapper(name = "tables") @XmlElement(name = "table") protected List tables; + @XmlElementWrapper(name = "udts") + @XmlElement(name = "udt") + protected List udts; /** * The input schema name as defined in {@link org.jooq.Schema#getName()} @@ -121,6 +124,17 @@ public class MappedSchema this.tables = tables; } + public List getUdts() { + if (udts == null) { + udts = new ArrayList(); + } + return udts; + } + + public void setUdts(List udts) { + this.udts = udts; + } + /** * The input schema name as defined in {@link org.jooq.Schema#getName()} *

@@ -178,12 +192,34 @@ public class MappedSchema return this; } + public MappedSchema withUdts(MappedUDT... values) { + if (values!= null) { + for (MappedUDT value: values) { + getUdts().add(value); + } + } + return this; + } + + public MappedSchema withUdts(Collection values) { + if (values!= null) { + getUdts().addAll(values); + } + return this; + } + + public MappedSchema withUdts(List udts) { + setUdts(udts); + return this; + } + @Override public final void appendTo(XMLBuilder builder) { builder.append("input", input); builder.append("inputExpression", inputExpression); builder.append("output", output); builder.append("tables", "table", tables); + builder.append("udts", "udt", udts); } @Override @@ -241,6 +277,15 @@ public class MappedSchema return false; } } + if (udts == null) { + if (other.udts!= null) { + return false; + } + } else { + if (!udts.equals(other.udts)) { + return false; + } + } return true; } @@ -252,6 +297,7 @@ public class MappedSchema result = ((prime*result)+((inputExpression == null)? 0 :inputExpression.pattern().hashCode())); result = ((prime*result)+((output == null)? 0 :output.hashCode())); result = ((prime*result)+((tables == null)? 0 :tables.hashCode())); + result = ((prime*result)+((udts == null)? 0 :udts.hashCode())); return result; } diff --git a/jOOQ/src/main/java/org/jooq/conf/MappedSchemaObject.java b/jOOQ/src/main/java/org/jooq/conf/MappedSchemaObject.java new file mode 100644 index 0000000000..bb0221086b --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/conf/MappedSchemaObject.java @@ -0,0 +1,77 @@ +/* + * 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.conf; + +import java.util.regex.Pattern; + +import org.jooq.Qualified; + +/** + * A common base type for objects contained in {@link MappedSchema}, such as + * {@link MappedTable} or {@link MappedUDT}. + * + * @author Lukas Eder + */ +public interface MappedSchemaObject { + + /** + * The input name as defined in {@link Qualified#getName()} + *

+ * Either <input/> or <inputExpression/> must be provided. + */ + String getInput(); + + /** + * A regular expression matching the input name as defined in + * {@link Qualified#getName()} + *

+ * Either <input/> or <inputExpression/> must be provided + */ + Pattern getInputExpression(); + + /** + * The output name as it will be rendered in SQL. + *

    + *
  • When <input/> is provided, <output/> is a constant + * value.
  • + *
  • When <inputExpression/> is provided, <output/> is a + * replacement expression.
  • + *
+ */ + String getOutput(); +} diff --git a/jOOQ/src/main/java/org/jooq/conf/MappedTable.java b/jOOQ/src/main/java/org/jooq/conf/MappedTable.java index 266e0b3a0a..e24920c329 100644 --- a/jOOQ/src/main/java/org/jooq/conf/MappedTable.java +++ b/jOOQ/src/main/java/org/jooq/conf/MappedTable.java @@ -27,7 +27,7 @@ import org.jooq.util.jaxb.tools.XMLBuilder; }) public class MappedTable extends SettingsBase - implements Serializable, Cloneable, XMLAppendable + implements Serializable, Cloneable, MappedSchemaObject, XMLAppendable { private final static long serialVersionUID = 31900L; diff --git a/jOOQ/src/main/java/org/jooq/conf/MappedUDT.java b/jOOQ/src/main/java/org/jooq/conf/MappedUDT.java new file mode 100644 index 0000000000..9e5e647c1d --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/conf/MappedUDT.java @@ -0,0 +1,206 @@ + +package org.jooq.conf; + +import java.io.Serializable; +import java.util.regex.Pattern; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlType; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import org.jooq.util.jaxb.tools.XMLAppendable; +import org.jooq.util.jaxb.tools.XMLBuilder; + + +/** + * A udt mapping configuration. + * + * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "MappedUDT", propOrder = { + +}) +@SuppressWarnings({ + "all" +}) +public class MappedUDT + extends SettingsBase + implements Serializable, Cloneable, MappedSchemaObject, XMLAppendable +{ + + private final static long serialVersionUID = 31900L; + protected String input; + @XmlElement(type = String.class) + @XmlJavaTypeAdapter(RegexAdapter.class) + protected Pattern inputExpression; + @XmlElement(required = true) + protected String output; + + /** + * The input UDT as defined in {@link org.jooq.UDT#getName()} + *

+ * Either <input/> or <inputExpression/> must be provided. + * + */ + public String getInput() { + return input; + } + + /** + * The input UDT as defined in {@link org.jooq.UDT#getName()} + *

+ * Either <input/> or <inputExpression/> must be provided. + * + */ + public void setInput(String value) { + this.input = value; + } + + /** + * A regular expression matching the input UDT name as defined in {@link org.jooq.UDT#getName()} + *

+ * Either <input/> or <inputExpression/> must be provided + * + */ + public Pattern getInputExpression() { + return inputExpression; + } + + /** + * A regular expression matching the input UDT name as defined in {@link org.jooq.UDT#getName()} + *

+ * Either <input/> or <inputExpression/> must be provided + * + */ + public void setInputExpression(Pattern value) { + this.inputExpression = value; + } + + /** + * The output UDT as it will be rendered in SQL. + *

    + *
  • When <input/> is provided, <output/> is a constant value.
  • + *
  • When <inputExpression/> is provided, <output/> is a replacement expression.
  • + *
+ * + */ + public String getOutput() { + return output; + } + + /** + * The output UDT as it will be rendered in SQL. + *
    + *
  • When <input/> is provided, <output/> is a constant value.
  • + *
  • When <inputExpression/> is provided, <output/> is a replacement expression.
  • + *
+ * + */ + public void setOutput(String value) { + this.output = value; + } + + /** + * The input UDT as defined in {@link org.jooq.UDT#getName()} + *

+ * Either <input/> or <inputExpression/> must be provided. + * + */ + public MappedUDT withInput(String value) { + setInput(value); + return this; + } + + /** + * A regular expression matching the input UDT name as defined in {@link org.jooq.UDT#getName()} + *

+ * Either <input/> or <inputExpression/> must be provided + * + */ + public MappedUDT withInputExpression(Pattern value) { + setInputExpression(value); + return this; + } + + /** + * The output UDT as it will be rendered in SQL. + *

    + *
  • When <input/> is provided, <output/> is a constant value.
  • + *
  • When <inputExpression/> is provided, <output/> is a replacement expression.
  • + *
+ * + */ + public MappedUDT withOutput(String value) { + setOutput(value); + return this; + } + + @Override + public final void appendTo(XMLBuilder builder) { + builder.append("input", input); + builder.append("inputExpression", inputExpression); + builder.append("output", output); + } + + @Override + public String toString() { + XMLBuilder builder = XMLBuilder.nonFormatting(); + appendTo(builder); + return builder.toString(); + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (that == null) { + return false; + } + if (getClass()!= that.getClass()) { + return false; + } + MappedUDT other = ((MappedUDT) that); + if (input == null) { + if (other.input!= null) { + return false; + } + } else { + if (!input.equals(other.input)) { + return false; + } + } + if (inputExpression == null) { + if (other.inputExpression!= null) { + return false; + } + } else { + if (!inputExpression.pattern().equals(other.inputExpression.pattern())) { + return false; + } + } + if (output == null) { + if (other.output!= null) { + return false; + } + } else { + if (!output.equals(other.output)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = ((prime*result)+((input == null)? 0 :input.hashCode())); + result = ((prime*result)+((inputExpression == null)? 0 :inputExpression.pattern().hashCode())); + result = ((prime*result)+((output == null)? 0 :output.hashCode())); + return result; + } + +} diff --git a/jOOQ/src/main/java/org/jooq/conf/ObjectFactory.java b/jOOQ/src/main/java/org/jooq/conf/ObjectFactory.java index ce856082b9..e00fdf23c4 100644 --- a/jOOQ/src/main/java/org/jooq/conf/ObjectFactory.java +++ b/jOOQ/src/main/java/org/jooq/conf/ObjectFactory.java @@ -97,6 +97,14 @@ public class ObjectFactory { return new MappedTable(); } + /** + * Create an instance of {@link MappedUDT } + * + */ + public MappedUDT createMappedUDT() { + return new MappedUDT(); + } + /** * Create an instance of {@link RenderFormatting } * diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 42b8460531..c49f5d1d71 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -329,6 +329,7 @@ import org.jooq.TableElement; import org.jooq.TableField; import org.jooq.TableRecord; import org.jooq.UDT; +import org.jooq.UDTRecord; import org.jooq.UpdatableRecord; import org.jooq.WindowSpecification; import org.jooq.XML; @@ -3665,6 +3666,16 @@ final class Tools { return table; } + /** + * Map a {@link UDT} according to the configured {@link org.jooq.SchemaMapping} + */ + static final > UDT getMappedUDT(Scope scope, UDT udt) { + if (scope != null) + return scope.configuration().schemaMapping().map(udt); + + return udt; + } + /** * Map an {@link QualifiedRecord} according to the configured * {@link org.jooq.SchemaMapping} @@ -3680,6 +3691,14 @@ final class Tools { */ static final String getMappedUDTName(Scope scope, QualifiedRecord record) { RecordQualifier udt = record.getQualifier(); + + if (udt instanceof UDT u) { + UDT u2 = getMappedUDT(scope, u); + + if (u2 != null && u2 != u) + return u2.getQualifiedName().unquotedName().toString(); + } + Schema mapped = getMappedSchema(scope, udt.getSchema()); StringBuilder sb = new StringBuilder(); diff --git a/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.19.0.xsd b/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.19.0.xsd index 9690fa9c80..34800d0940 100644 --- a/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.19.0.xsd +++ b/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.19.0.xsd @@ -1778,6 +1778,10 @@ Either <input/> or <inputExpression/> must be provided]]> + + + + @@ -1814,6 +1818,39 @@ Either <input/> or <inputExpression/> must be provided]]> + + + + + + + + + + + + + +Either <input/> or <inputExpression/> must be provided.]]> + + + + +Either <input/> or <inputExpression/> must be provided]]> + + + + +
  • When <input/> is provided, <output/> is a constant value.
  • +
  • When <inputExpression/> is provided, <output/> is a replacement expression.
  • +]]>
    +
    +
    +
    +