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 ad58ea226c..aa2753901f 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/AbstractGenerator.java @@ -72,6 +72,7 @@ abstract class AbstractGenerator implements Generator { boolean generateSequences = true; boolean generateUDTs = true; boolean generateTables = true; + boolean generateEmbeddables = true; boolean generateRecords = true; boolean generateRecordsImplementingRecordN = true; boolean generatePojos = false; @@ -356,6 +357,16 @@ abstract class AbstractGenerator implements Generator { this.generateTables = generateTables; } + @Override + public boolean generateEmbeddables() { + return generateEmbeddables; + } + + @Override + public void setGenerateEmbeddables(boolean generateEmbeddables) { + this.generateEmbeddables = generateEmbeddables; + } + @Override public boolean generateRecords() { 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 a5a950621e..efed2d3890 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/DefaultGeneratorStrategy.java @@ -51,6 +51,7 @@ import org.jooq.meta.ArrayDefinition; import org.jooq.meta.CatalogDefinition; import org.jooq.meta.Definition; import org.jooq.meta.DomainDefinition; +import org.jooq.meta.EmbeddableDefinition; import org.jooq.meta.EnumDefinition; import org.jooq.meta.ForeignKeyDefinition; import org.jooq.meta.IndexDefinition; @@ -294,15 +295,12 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { .replace('.', '_') )); - if (mode == Mode.RECORD) { + if (mode == Mode.RECORD) result.append("Record"); - } - else if (mode == Mode.DAO) { + else if (mode == Mode.DAO) result.append("Dao"); - } - else if (mode == Mode.INTERFACE) { + else if (mode == Mode.INTERFACE) result.insert(0, "I"); - } return result.toString(); } @@ -312,6 +310,11 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy { return "tables"; } + // [#2530] Embeddable types + else if (definition instanceof EmbeddableDefinition) { + return "embeddables"; + } + // [#799] UDT's are also packages else if (definition instanceof UDTDefinition) { UDTDefinition udt = (UDTDefinition) definition; 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 a2202b0199..27f682af08 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationTool.java @@ -490,6 +490,7 @@ public class GenerationTool { database.setIncludeRoutines(!FALSE.equals(d.isIncludeRoutines())); database.setIncludeSequences(!FALSE.equals(d.isIncludeSequences())); database.setIncludeTables(!FALSE.equals(d.isIncludeTables())); + database.setIncludeEmbeddables(!FALSE.equals(d.isIncludeEmbeddables())); database.setIncludeTriggerRoutines(TRUE.equals(d.isIncludeTriggerRoutines())); database.setIncludeUDTs(!FALSE.equals(d.isIncludeUDTs())); database.setIncludeUniqueKeys(!FALSE.equals(d.isIncludeUniqueKeys())); @@ -502,6 +503,7 @@ public class GenerationTool { database.setConfiguredCustomTypes(d.getCustomTypes()); database.setConfiguredEnumTypes(d.getEnumTypes()); database.setConfiguredForcedTypes(d.getForcedTypes()); + database.setConfiguredEmbeddables(d.getEmbeddables()); database.setLogSlowQueriesAfterSeconds(defaultIfNull(g.getDatabase().getLogSlowQueriesAfterSeconds(), 5)); if (d.getRegexFlags() != null) @@ -616,6 +618,8 @@ public class GenerationTool { generator.setGenerateUDTs(g.getGenerate().isUdts()); if (g.getGenerate().isTables() != null) generator.setGenerateTables(g.getGenerate().isTables()); + if (g.getGenerate().isEmbeddables() != null) + generator.setGenerateEmbeddables(g.getGenerate().isEmbeddables()); if (g.getGenerate().isRecords() != null) generator.setGenerateRecords(g.getGenerate().isRecords()); if (g.getGenerate().isRecordsImplementingRecordN() != null) 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 5063178d59..1fff111744 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/Generator.java @@ -202,6 +202,16 @@ public interface Generator { */ void setGenerateTables(boolean generateTables); + /** + * Whether embeddable types should be generated + */ + boolean generateEmbeddables(); + + /** + * Whether embeddable types should be generated + */ + void setGenerateEmbeddables(boolean generateEmbeddables); + /** * Whether TableRecords should be generated in addition to tables */ 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 44620cc159..6700fe11f0 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -111,6 +111,7 @@ import org.jooq.impl.CatalogImpl; import org.jooq.impl.DAOImpl; import org.jooq.impl.DSL; import org.jooq.impl.DefaultDataType; +import org.jooq.impl.EmbeddableRecordImpl; import org.jooq.impl.Internal; import org.jooq.impl.PackageImpl; import org.jooq.impl.SQLDataType; @@ -131,6 +132,8 @@ import org.jooq.meta.Database; import org.jooq.meta.DefaultDataTypeDefinition; import org.jooq.meta.Definition; import org.jooq.meta.DomainDefinition; +import org.jooq.meta.EmbeddableColumnDefinition; +import org.jooq.meta.EmbeddableDefinition; import org.jooq.meta.EnumDefinition; import org.jooq.meta.ForeignKeyDefinition; import org.jooq.meta.IdentityDefinition; @@ -507,6 +510,10 @@ public class JavaGenerator extends AbstractGenerator { generateTables(schema); } + if (generateEmbeddables() && database.getEmbeddables(schema).size() > 0) { + generateEmbeddables(schema); + } + if (generatePojos() && database.getTables(schema).size() > 0) { generatePojos(schema); } @@ -1011,7 +1018,7 @@ public class JavaGenerator extends AbstractGenerator { out.println(); if (scala) - out.tab(1).println("private object Identities%s {", block); + out.tab(1).println("private object Identities%s {", block); else out.tab(1).println("private static class Identities%s {", block); } @@ -1063,7 +1070,7 @@ public class JavaGenerator extends AbstractGenerator { out.println(); if (scala) - out.tab(1).println("private object UniqueKeys%s {", block); + out.tab(1).println("private object UniqueKeys%s {", block); else out.tab(1).println("private static class UniqueKeys%s {", block); } @@ -1114,13 +1121,13 @@ public class JavaGenerator extends AbstractGenerator { out.println(); if (scala) - out.tab(1).println("private object ForeignKeys%s {", block); + out.tab(1).println("private object ForeignKeys%s {", block); else out.tab(1).println("private static class ForeignKeys%s {", block); } if (scala) - out.tab(2).println("val %s : %s[%s, %s] = %s.createForeignKey(%s, %s, \"%s\", [[%s]])", + out.tab(2).println("val %s : %s[%s, %s] = %s.createForeignKey(%s, %s, \"%s\", [[%s]])", getStrategy().getJavaIdentifier(foreignKey), ForeignKey.class, out.ref(getStrategy().getFullJavaClassName(foreignKey.getKeyTable(), Mode.RECORD)), @@ -1180,29 +1187,35 @@ public class JavaGenerator extends AbstractGenerator { generateRecord0(udt, out); } - private final void generateRecord0(Definition tableOrUdt, JavaWriter out) { - final UniqueKeyDefinition key = (tableOrUdt instanceof TableDefinition) - ? ((TableDefinition) tableOrUdt).getPrimaryKey() + private final void generateRecord0(Definition tableUdtOrEmbeddable, JavaWriter out) { + final UniqueKeyDefinition key = (tableUdtOrEmbeddable instanceof TableDefinition) + ? ((TableDefinition) tableUdtOrEmbeddable).getPrimaryKey() : null; - final String className = getStrategy().getJavaClassName(tableOrUdt, Mode.RECORD); - final String tableIdentifier = out.ref(getStrategy().getFullJavaIdentifier(tableOrUdt), 2); - final List interfaces = out.ref(getStrategy().getJavaClassImplements(tableOrUdt, Mode.RECORD)); - final List> columns = getTypedElements(tableOrUdt); + final String className = getStrategy().getJavaClassName(tableUdtOrEmbeddable, Mode.RECORD); + final String tableIdentifier = !(tableUdtOrEmbeddable instanceof EmbeddableDefinition) + ? out.ref(getStrategy().getFullJavaIdentifier(tableUdtOrEmbeddable), 2) + : null; + final List interfaces = out.ref(getStrategy().getJavaClassImplements(tableUdtOrEmbeddable, Mode.RECORD)); + final List> columns = getTypedElements(tableUdtOrEmbeddable); - printPackage(out, tableOrUdt, Mode.RECORD); + printPackage(out, tableUdtOrEmbeddable, Mode.RECORD); - if (tableOrUdt instanceof TableDefinition) - generateRecordClassJavadoc((TableDefinition) tableOrUdt, out); + if (tableUdtOrEmbeddable instanceof TableDefinition) + generateRecordClassJavadoc((TableDefinition) tableUdtOrEmbeddable, out); + else if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) + generateEmbeddableClassJavadoc((EmbeddableDefinition) tableUdtOrEmbeddable, out); else - generateUDTRecordClassJavadoc((UDTDefinition) tableOrUdt, out); + generateUDTRecordClassJavadoc((UDTDefinition) tableUdtOrEmbeddable, out); - printClassAnnotations(out, tableOrUdt.getSchema()); - if (tableOrUdt instanceof TableDefinition) - printTableJPAAnnotation(out, (TableDefinition) tableOrUdt); + printClassAnnotations(out, tableUdtOrEmbeddable.getSchema()); + if (tableUdtOrEmbeddable instanceof TableDefinition) + printTableJPAAnnotation(out, (TableDefinition) tableUdtOrEmbeddable); Class baseClass; - if (tableOrUdt instanceof UDTDefinition) + if (tableUdtOrEmbeddable instanceof UDTDefinition) baseClass = UDTRecordImpl.class; + else if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) + baseClass = EmbeddableRecordImpl.class; else if (generateRelations() && key != null) baseClass = UpdatableRecordImpl.class; else @@ -1225,12 +1238,14 @@ public class JavaGenerator extends AbstractGenerator { interfaces.add(rowTypeRecord); } - if (generateInterfaces()) { - interfaces.add(out.ref(getStrategy().getFullJavaClassName(tableOrUdt, Mode.INTERFACE))); - } + if (generateInterfaces()) + interfaces.add(out.ref(getStrategy().getFullJavaClassName(tableUdtOrEmbeddable, Mode.INTERFACE))); if (scala) - out.println("class %s extends %s[%s](%s)[[before= with ][separator= with ][%s]] {", className, baseClass, className, tableIdentifier, interfaces); + if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) + out.println("class %s extends %s[%s]()[[before= with ][separator= with ][%s]] {", className, baseClass, className, interfaces); + else + out.println("class %s extends %s[%s](%s)[[before= with ][separator= with ][%s]] {", className, baseClass, className, tableIdentifier, interfaces); else out.println("public class %s extends %s<%s>[[before= implements ][%s]] {", className, baseClass, className, interfaces); @@ -1239,16 +1254,30 @@ public class JavaGenerator extends AbstractGenerator { for (int i = 0; i < degree; i++) { TypedElementDefinition column = columns.get(i); - if (tableOrUdt instanceof TableDefinition) { + if (tableUdtOrEmbeddable instanceof TableDefinition) { generateRecordSetter(column, i, out); generateRecordGetter(column, i, out); } + else if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) { + generateEmbeddableSetter(column, i, out); + generateEmbeddableGetter(column, i, out); + } else { generateUDTRecordSetter(column, i, out); generateUDTRecordGetter(column, i, out); } } + if (tableUdtOrEmbeddable instanceof TableDefinition) { + List embeddables = ((TableDefinition) tableUdtOrEmbeddable).getEmbeddables(); + + for (int i = 0; i < embeddables.size(); i++) { + EmbeddableDefinition embeddable = embeddables.get(i); + + // [#2530] TODO: Implement setters and getters for embeddables here + } + } + if (generateRelations() && key != null) { int keyDegree = key.getKeyColumns().size(); @@ -1270,10 +1299,10 @@ public class JavaGenerator extends AbstractGenerator { } } - if (tableOrUdt instanceof UDTDefinition) { + if (tableUdtOrEmbeddable instanceof UDTDefinition) { // [#799] Oracle UDT's can have member procedures - for (RoutineDefinition routine : ((UDTDefinition) tableOrUdt).getRoutines()) { + for (RoutineDefinition routine : ((UDTDefinition) tableUdtOrEmbeddable).getRoutines()) { // Instance methods ship with a SELF parameter at the first position // [#1584] Static methods don't have that @@ -1334,6 +1363,9 @@ public class JavaGenerator extends AbstractGenerator { for (int i = 1; i <= degree; i++) { TypedElementDefinition column = columns.get(i - 1); + if (column instanceof EmbeddableColumnDefinition) + column = ((EmbeddableColumnDefinition) column).getColumn(); + final String colTypeFull = getJavaType(column.getType(resolver())); final String colType = out.ref(colTypeFull); final String colIdentifier = out.ref(getStrategy().getFullJavaIdentifier(column), colRefSegments(column)); @@ -1349,7 +1381,12 @@ public class JavaGenerator extends AbstractGenerator { out.tab(1).overrideInherit(); out.tab(1).println("public %s<%s> field%s() {", Field.class, colType, i); - out.tab(2).println("return %s;", colIdentifier); + + if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) + out.tab(2).println("return (%s<%s>) FIELDS[%s];", Field.class, colType, i - 1); + else + out.tab(2).println("return %s;", colIdentifier); + out.tab(1).println("}"); } } @@ -1471,17 +1508,35 @@ public class JavaGenerator extends AbstractGenerator { } } - if (generateInterfaces()) { - printFromAndInto(out, tableOrUdt); - } + if (generateInterfaces()) + printFromAndInto(out, tableUdtOrEmbeddable); if (scala) { } else { out.tab(1).header("Constructors"); + + if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) { + out.println(); + out.tab(1).println("private static final %s[] FIELDS = {", Field.class); + + for (EmbeddableColumnDefinition column : ((EmbeddableDefinition) tableUdtOrEmbeddable).getColumns()) { + final String colIdentifier = out.ref(getStrategy().getFullJavaIdentifier(column.getColumn()), colRefSegments(column)); + + out.tab(2).println("%s.field(%s.name(\"%s\"), %s.getDataType()),", DSL.class, DSL.class, column.getOutputName(), colIdentifier); + } + + out.tab(1).println("};"); + out.println(); + } + out.tab(1).javadoc("Create a detached %s", className); + out.tab(1).println("public %s() {", className); - out.tab(2).println("super(%s);", tableIdentifier); + if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) + out.tab(2).println("super(FIELDS);"); + else + out.tab(2).println("super(%s);", tableIdentifier); out.tab(1).println("}"); } @@ -1496,7 +1551,7 @@ public class JavaGenerator extends AbstractGenerator { final String type = out.ref(getJavaType(column.getType(resolver()))); if (scala) - arguments.add(columnMember + " : " + type); + arguments.add(columnMember + " : " + type); else arguments.add(type + " " + columnMember); } @@ -1509,7 +1564,11 @@ public class JavaGenerator extends AbstractGenerator { } else { out.tab(1).println("public %s([[%s]]) {", className, arguments); - out.tab(2).println("super(%s);", tableIdentifier); + + if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) + out.tab(2).println("this();", tableIdentifier); + else + out.tab(2).println("super(%s);", tableIdentifier); } out.println(); @@ -1519,7 +1578,7 @@ public class JavaGenerator extends AbstractGenerator { final String columnMember = getStrategy().getJavaMemberName(column, Mode.DEFAULT); if (scala) - out.tab(2).println("set(%s, %s)", i, columnMember); + out.tab(2).println("set(%s, %s)", i, columnMember); else out.tab(2).println("set(%s, %s);", i, columnMember); } @@ -1527,10 +1586,12 @@ public class JavaGenerator extends AbstractGenerator { out.tab(1).println("}"); } - if (tableOrUdt instanceof TableDefinition) - generateRecordClassFooter((TableDefinition) tableOrUdt, out); + if (tableUdtOrEmbeddable instanceof TableDefinition) + generateRecordClassFooter((TableDefinition) tableUdtOrEmbeddable, out); + else if (tableUdtOrEmbeddable instanceof EmbeddableDefinition) + generateEmbeddableClassFooter((EmbeddableDefinition) tableUdtOrEmbeddable, out); else - generateUDTRecordClassFooter((UDTDefinition) tableOrUdt, out); + generateUDTRecordClassFooter((UDTDefinition) tableUdtOrEmbeddable, out); out.println("}"); } @@ -1542,6 +1603,13 @@ public class JavaGenerator extends AbstractGenerator { generateRecordSetter0(column, index, out); } + /** + * Subclasses may override this method to provide their own embeddable setters. + */ + protected void generateEmbeddableSetter(TypedElementDefinition column, int index, JavaWriter out) { + generateRecordSetter0(column, index, out); + } + /** * Subclasses may override this method to provide their own record setters. */ @@ -1651,6 +1719,13 @@ public class JavaGenerator extends AbstractGenerator { generateRecordGetter0(column, index, out); } + /** + * Subclasses may override this method to provide their own embeddable getters. + */ + protected void generateEmbeddableGetter(TypedElementDefinition column, int index, JavaWriter out) { + generateRecordGetter0(column, index, out); + } + /** * Subclasses may override this method to provide their own record getters. */ @@ -1668,7 +1743,7 @@ public class JavaGenerator extends AbstractGenerator { if (!printDeprecationIfUnknownType(out, typeFull)) out.tab(1).javadoc("Getter for %s.%s", name, columnComment(column, comment)); - if (column.getContainer() instanceof TableDefinition) + if (column instanceof ColumnDefinition) printColumnJPAAnnotation(out, (ColumnDefinition) column); printValidationAnnotation(out, column); @@ -1718,6 +1793,19 @@ public class JavaGenerator extends AbstractGenerator { printClassJavadoc(out, "The table " + table.getQualifiedInputName() + "."); } + /** + * Subclasses may override this method to provide record class footer code. + */ + @SuppressWarnings("unused") + protected void generateEmbeddableClassFooter(EmbeddableDefinition embeddable, JavaWriter out) {} + + /** + * Subclasses may override this method to provide their own Javadoc. + */ + protected void generateEmbeddableClassJavadoc(EmbeddableDefinition embeddable, JavaWriter out) { + printClassJavadoc(out, "The embeddable " + embeddable.getQualifiedInputName() + "."); + } + private String refRowType(JavaWriter out, Collection> columns) { StringBuilder result = new StringBuilder(); String separator = ""; @@ -1784,7 +1872,7 @@ public class JavaGenerator extends AbstractGenerator { printTableJPAAnnotation(out, (TableDefinition) tableOrUDT); if (scala) - out.println("trait %s[[before= extends ][%s]] {", className, interfaces); + out.println("trait %s[[before= extends ][%s]] {", className, interfaces); else out.println("public interface %s[[before= extends ][%s]] {", className, interfaces); @@ -1813,14 +1901,14 @@ public class JavaGenerator extends AbstractGenerator { out.tab(1).javadoc("Load data from another generated Record/POJO implementing the common interface %s", local); if (scala) - out.tab(1).println("def from(from : %s)", qualified); + out.tab(1).println("def from(from : %s)", qualified); else out.tab(1).println("public void from(%s from);", qualified); out.tab(1).javadoc("Copy data into another generated Record/POJO implementing the common interface %s", local); if (scala) - out.tab(1).println("def into [E <: %s](into : E) : E", qualified); + out.tab(1).println("def into [E <: %s](into : E) : E", qualified); else out.tab(1).println("public E into(E into);", qualified); } @@ -2221,7 +2309,7 @@ public class JavaGenerator extends AbstractGenerator { printClassAnnotations(out, schema); if (scala) - out.println("object UDTs {"); + out.println("object UDTs {"); else out.println("public class UDTs {"); @@ -2233,7 +2321,7 @@ public class JavaGenerator extends AbstractGenerator { out.tab(1).javadoc("The type %s", udt.getQualifiedOutputName()); if (scala) - out.tab(1).println("val %s = %s", id, fullId); + out.tab(1).println("val %s = %s", id, fullId); else out.tab(1).println("public static %s %s = %s;", className, id, fullId); } @@ -2811,7 +2899,7 @@ public class JavaGenerator extends AbstractGenerator { printClassAnnotations(out, schema); if (scala) - out.println("object Tables {"); + out.println("object Tables {"); else out.println("public class Tables {"); @@ -2832,9 +2920,9 @@ public class JavaGenerator extends AbstractGenerator { out.tab(1).javadoc(comment); if (scala) - out.tab(1).println("val %s = %s", id, fullId); + out.tab(1).println("val %s = %s", id, fullId); else - out.tab(1).println("public static final %s %s = %s;", className, id, fullId); + out.tab(1).println("public static final %s %s = %s;", className, id, fullId); } // [#3797] Table-valued functions generate two different literals in @@ -2902,7 +2990,7 @@ public class JavaGenerator extends AbstractGenerator { } if (scala) - tType = Record.class.getName() + keyColumns.size() + "[" + generics + "]"; + tType = Record.class.getName() + keyColumns.size() + "[" + generics + "]"; else tType = Record.class.getName() + keyColumns.size() + "<" + generics + ">"; } @@ -2968,10 +3056,10 @@ public class JavaGenerator extends AbstractGenerator { } if (keyColumns.size() == 1) { - if (scala) + if (scala) out.tab(2).println("o.%s", getStrategy().getJavaGetterName(keyColumns.get(0), Mode.POJO)); - else - out.tab(2).println("return object.%s();", getStrategy().getJavaGetterName(keyColumns.get(0), Mode.POJO)); + else + out.tab(2).println("return object.%s();", getStrategy().getJavaGetterName(keyColumns.get(0), Mode.POJO)); } // [#2574] This should be replaced by a call to a method on the target table's Key type @@ -2980,16 +3068,16 @@ public class JavaGenerator extends AbstractGenerator { String separator = ""; for (ColumnDefinition column : keyColumns) { - if (scala) - params += separator + "o." + getStrategy().getJavaGetterName(column, Mode.POJO); - else - params += separator + "object." + getStrategy().getJavaGetterName(column, Mode.POJO) + "()"; + if (scala) + params += separator + "o." + getStrategy().getJavaGetterName(column, Mode.POJO); + else + params += separator + "object." + getStrategy().getJavaGetterName(column, Mode.POJO) + "()"; separator = ", "; } if (scala) - out.tab(2).println("compositeKeyRecord(%s)", params); + out.tab(2).println("compositeKeyRecord(%s)", params); else out.tab(2).println("return compositeKeyRecord(%s);", params); } @@ -3653,18 +3741,16 @@ public class JavaGenerator extends AbstractGenerator { } private List> getTypedElements(Definition definition) { - if (definition instanceof TableDefinition) { + if (definition instanceof TableDefinition) return ((TableDefinition) definition).getColumns(); - } - else if (definition instanceof UDTDefinition) { + else if (definition instanceof EmbeddableDefinition) + return ((EmbeddableDefinition) definition).getColumns(); + else if (definition instanceof UDTDefinition) return ((UDTDefinition) definition).getAttributes(); - } - else if (definition instanceof RoutineDefinition) { + else if (definition instanceof RoutineDefinition) return ((RoutineDefinition) definition).getAllParameters(); - } - else { + else throw new IllegalArgumentException("Unsupported type : " + definition); - } } /** @@ -3761,7 +3847,7 @@ public class JavaGenerator extends AbstractGenerator { } else { out.println("public class %s extends %s<%s>[[before= implements ][%s]] {", - className, TableImpl.class, recordType, interfaces); + className, TableImpl.class, recordType, interfaces); out.printSerial(); printSingletonInstance(out, table); } @@ -3794,6 +3880,27 @@ public class JavaGenerator extends AbstractGenerator { } } + // [#2530] Embeddable types + for (EmbeddableDefinition embeddable : table.getEmbeddables()) { + final String columnId = out.ref(getStrategy().getJavaIdentifier(embeddable), colRefSegments(null)); + final String columnType = out.ref(getStrategy().getFullJavaClassName(embeddable, Mode.RECORD)); + + final List columnIds = new ArrayList(); + for (EmbeddableColumnDefinition column : embeddable.getColumns()) + columnIds.add(out.ref(getStrategy().getJavaIdentifier(column), colRefSegments(column))); + + out.tab(1).javadoc("The embeddable type %s.", embeddable.getOutputName()); + + if (scala) { + out.tab(1).println("val %s : %s[%s, %s] = %s.createEmbeddable(%s.name(\"%s\"), classOf[%s], this, [[%s]])", + columnId, TableField.class, recordType, columnType, Internal.class, DSL.class, embeddable.getName(), columnType, columnIds); + } + else { + out.tab(1).println("public final %s<%s, %s> %s = %s.createEmbeddable(%s.name(\"%s\"), %s.class, this, [[%s]]);", + TableField.class, recordType, columnType, columnId, Internal.class, DSL.class, embeddable.getName(), columnType, columnIds); + } + } + if (scala) { out.tab(1).javadoc("Create a %s table reference", table.getQualifiedOutputName()); out.tab(1).println("def this() = {"); @@ -4335,6 +4442,28 @@ public class JavaGenerator extends AbstractGenerator { closeJavaWriter(out); } + protected void generateEmbeddables(SchemaDefinition schema) { + log.info("Generating embeddables"); + + for (EmbeddableDefinition embeddable : database.getEmbeddables(schema)) { + try { + generateEmbeddable(schema, embeddable); + } + catch (Exception e) { + log.error("Error while generating embeddable " + embeddable, e); + } + } + + watch.splitInfo("Tables generated"); + } + + @SuppressWarnings("unused") + protected void generateEmbeddable(SchemaDefinition schema, EmbeddableDefinition embeddable) { + JavaWriter out = newJavaWriter(getFile(embeddable, Mode.RECORD)); + generateRecord0(embeddable, out); + closeJavaWriter(out); + } + private String converterTemplate(List converter) { if (converter == null || converter.isEmpty()) return "[[]]"; @@ -4644,7 +4773,7 @@ public class JavaGenerator extends AbstractGenerator { String getter = getStrategy().getJavaGetterName(column, Mode.INTERFACE); if (scala) - out.tab(2).println("%s(from.%s)", setter, getter); + out.tab(2).println("%s(from.%s)", setter, getter); else out.tab(2).println("%s(from.%s());", setter, getter); } @@ -4653,7 +4782,7 @@ public class JavaGenerator extends AbstractGenerator { if (generateInterfaces() && !generateImmutableInterfaces()) { if (scala) { - out.tab(1).println("public E into(E into) {", qualified); + out.tab(1).println("public E into(E into) {", qualified); out.tab(2).println("into.from(this)"); out.tab(2).println("return into"); out.tab(1).println("}"); @@ -4962,7 +5091,7 @@ public class JavaGenerator extends AbstractGenerator { printPackage(out, routine); if (scala) { - out.println("object %s {", className); + out.println("object %s {", className); for (ParameterDefinition parameter : routine.getAllParameters()) { final String paramTypeFull = getJavaType(parameter.getType(resolver())); final String paramType = out.ref(paramTypeFull); @@ -4997,7 +5126,7 @@ public class JavaGenerator extends AbstractGenerator { } else { out.println("public class %s extends %s<%s>[[before= implements ][%s]] {", - className, AbstractRoutine.class, returnType, interfaces); + className, AbstractRoutine.class, returnType, interfaces); out.printSerial(); for (ParameterDefinition parameter : routine.getAllParameters()) { @@ -5022,7 +5151,7 @@ public class JavaGenerator extends AbstractGenerator { if (scala) { - out.tab(1).println("{"); + out.tab(1).println("{"); } else { out.tab(1).javadoc("Create a new routine call instance"); @@ -5039,30 +5168,30 @@ public class JavaGenerator extends AbstractGenerator { final String paramId = getStrategy().getJavaIdentifier(parameter); if (parameter.equals(routine.getReturnValue())) { - if (scala) + if (scala) out.tab(2).println("setReturnParameter(%s.%s)", className, paramId); - else - out.tab(2).println("setReturnParameter(%s);", paramId); + else + out.tab(2).println("setReturnParameter(%s);", paramId); } else if (routine.getInParameters().contains(parameter)) { if (routine.getOutParameters().contains(parameter)) { - if (scala) + if (scala) out.tab(2).println("addInOutParameter(%s.%s)", className, paramId); - else - out.tab(2).println("addInOutParameter(%s);", paramId); + else + out.tab(2).println("addInOutParameter(%s);", paramId); } else { - if (scala) + if (scala) out.tab(2).println("addInParameter(%s.%s)", className, paramId); - else - out.tab(2).println("addInParameter(%s);", paramId); + else + out.tab(2).println("addInParameter(%s);", paramId); } } else { - if (scala) + if (scala) out.tab(2).println("addOutParameter(%s.%s)", className, paramId); - else - out.tab(2).println("addOutParameter(%s);", paramId); + else + out.tab(2).println("addOutParameter(%s);", paramId); } @@ -5078,7 +5207,7 @@ public class JavaGenerator extends AbstractGenerator { } if (routine.getOverload() != null) { - if (scala) + if (scala) out.tab(2).println("setOverloaded(true)"); else out.tab(2).println("setOverloaded(true);"); @@ -5106,7 +5235,7 @@ public class JavaGenerator extends AbstractGenerator { out.tab(1).javadoc("Set the %s parameter IN value to the routine", parameter.getOutputName()); if (scala) { - out.tab(1).println("def %s(%s : %s) : Unit = {", setter, paramName, refNumberType(out, parameter.getType(resolver()))); + out.tab(1).println("def %s(%s : %s) : Unit = {", setter, paramName, refNumberType(out, parameter.getType(resolver()))); out.tab(2).println("set%s(%s.%s, %s)", numberValue, className, paramId, paramName); out.tab(1).println("}"); } @@ -5208,7 +5337,7 @@ public class JavaGenerator extends AbstractGenerator { if (scala) out.tab(1).print("def %s(", - getStrategy().getJavaMethodName(function, Mode.DEFAULT)); + getStrategy().getJavaMethodName(function, Mode.DEFAULT)); else out.tab(1).print("public static %s<%s> %s(", function.isAggregate() ? AggregateFunction.class : Field.class, @@ -5220,7 +5349,7 @@ public class JavaGenerator extends AbstractGenerator { out.print(separator); if (scala) { - out.print("%s : ", getStrategy().getJavaMemberName(parameter)); + out.print("%s : ", getStrategy().getJavaMemberName(parameter)); if (parametersAsField) { out.print("%s[%s]", Field.class, refExtendsNumberType(out, parameter.getType(resolver()))); @@ -5242,13 +5371,13 @@ public class JavaGenerator extends AbstractGenerator { } if (scala) { - out.println(") : %s[%s] = {", + out.println(") : %s[%s] = {", function.isAggregate() ? AggregateFunction.class : Field.class, functionType); out.tab(2).println("val %s = new %s", localVar, className); } else { - out.println(") {"); + out.println(") {"); out.tab(2).println("%s %s = new %s();", className, localVar, className); } @@ -5402,10 +5531,10 @@ public class JavaGenerator extends AbstractGenerator { String glue = ""; if (!instance) { - if (scala) - out.print("%s : %s", configurationArgument, Configuration.class); - else - out.print("%s %s", Configuration.class, configurationArgument); + if (scala) + out.print("%s : %s", configurationArgument, Configuration.class); + else + out.print("%s %s", Configuration.class, configurationArgument); glue = ", "; } @@ -5420,7 +5549,7 @@ public class JavaGenerator extends AbstractGenerator { final String paramMember = getStrategy().getJavaMemberName(parameter); if (scala) - out.print("%s%s : %s", glue, paramMember, paramType); + out.print("%s%s : %s", glue, paramMember, paramType); else out.print("%s%s %s", glue, paramType, paramMember); @@ -5428,7 +5557,7 @@ public class JavaGenerator extends AbstractGenerator { } if (scala) { - out.println(") : %s = {", functionType); + out.println(") : %s = {", functionType); out.tab(2).println("val %s = new %s()", localVar, className); } else { @@ -5482,7 +5611,7 @@ public class JavaGenerator extends AbstractGenerator { out.tab(1).javadoc("Call %s", procedure.getQualifiedOutputName()); if (scala) { - out.tab(1).print("def "); + out.tab(1).print("def "); } else { out.tab(1).print("public "); @@ -5508,10 +5637,10 @@ public class JavaGenerator extends AbstractGenerator { String glue = ""; if (!instance) { - if (scala) - out.print("%s : %s", configurationArgument, Configuration.class); - else - out.print("%s %s", Configuration.class, configurationArgument); + if (scala) + out.print("%s : %s", configurationArgument, Configuration.class); + else + out.print("%s %s", Configuration.class, configurationArgument); glue = ", "; } @@ -5594,24 +5723,24 @@ public class JavaGenerator extends AbstractGenerator { out.tab(2).println("from((%s) %s.%s());", columnTypeInterface, localVar, getter); } else { - if (scala) + if (scala) out.tab(2).println("from(%s.%s)", localVar, getter); - else - out.tab(2).println("from(%s.%s());", localVar, getter); + else + out.tab(2).println("from(%s.%s());", localVar, getter); } } if (outParams.size() == 1) { - if (scala) + if (scala) out.tab(2).println("return %s.%s", localVar, getter); - else - out.tab(2).println("return %s.%s();", localVar, getter); + else + out.tab(2).println("return %s.%s();", localVar, getter); } else if (outParams.size() > 1) { - if (scala) + if (scala) out.tab(2).println("return %s", localVar); - else - out.tab(2).println("return %s;", localVar); + else + out.tab(2).println("return %s;", localVar); } } @@ -5780,10 +5909,10 @@ public class JavaGenerator extends AbstractGenerator { boolean hasCatalogVersion = !StringUtils.isBlank(catalogVersions.get(catalog)); boolean hasSchemaVersion = !StringUtils.isBlank(schemaVersions.get(schema)); - if (scala) + if (scala) out.tab(1).println("value = %s(", out.ref("scala.Array")); - else - out.tab(1).println("value = {"); + else + out.tab(1).println("value = {"); out.tab(2).println("\"http://www.jooq.org\","); out.tab(2).println("\"jOOQ version:%s\"%s", Constants.VERSION, (hasCatalogVersion || hasSchemaVersion ? "," : "")); @@ -5802,10 +5931,10 @@ public class JavaGenerator extends AbstractGenerator { out.tab(1).println("comments = \"This class is generated by jOOQ\""); } else { - if (scala) + if (scala) out.tab(1).println("value = %s(", out.ref("scala.Array")); - else - out.tab(1).println("value = {"); + else + out.tab(1).println("value = {"); out.tab(2).println("\"http://www.jooq.org\","); out.tab(2).println("\"jOOQ version:%s\"", Constants.VERSION); diff --git a/jOOQ-meta/src/main/java/META-INF/sun-jaxb.episode b/jOOQ-meta/src/main/java/META-INF/sun-jaxb.episode index 636ec00030..42e5e32e53 100644 --- a/jOOQ-meta/src/main/java/META-INF/sun-jaxb.episode +++ b/jOOQ-meta/src/main/java/META-INF/sun-jaxb.episode @@ -173,6 +173,12 @@ + + + + + + @@ -191,6 +197,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java index fb80d84f92..b46c6c2310 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java @@ -40,6 +40,7 @@ package org.jooq.meta; import static org.jooq.impl.DSL.falseCondition; import static org.jooq.meta.AbstractTypedElementDefinition.customType; +import static org.jooq.tools.StringUtils.defaultIfEmpty; import java.io.IOException; import java.io.StringReader; @@ -76,6 +77,8 @@ import org.jooq.impl.DefaultExecuteListenerProvider; import org.jooq.impl.SQLDataType; import org.jooq.meta.jaxb.CatalogMappingType; import org.jooq.meta.jaxb.CustomType; +import org.jooq.meta.jaxb.Embeddable; +import org.jooq.meta.jaxb.EmbeddableField; import org.jooq.meta.jaxb.EnumType; import org.jooq.meta.jaxb.ForcedType; import org.jooq.meta.jaxb.ForcedTypeObjectType; @@ -112,6 +115,7 @@ public abstract class AbstractDatabase implements Database { private boolean includeExcludeColumns; private boolean includeInvisibleColumns = true; private boolean includeTables = true; + private boolean includeEmbeddables = true; private boolean includeRoutines = true; private boolean includeTriggerRoutines = false; private boolean includePackages = true; @@ -138,6 +142,7 @@ public abstract class AbstractDatabase implements Database { private List configuredCustomTypes; private List configuredEnumTypes; private List configuredForcedTypes; + private List configuredEmbeddables; private SchemaVersionProvider schemaVersionProvider; private CatalogVersionProvider catalogVersionProvider; private Comparator orderProvider; @@ -161,6 +166,7 @@ public abstract class AbstractDatabase implements Database { private List foreignKeys; private List checkConstraints; private List tables; + private List embeddables; private List enums; private List domains; private List udts; @@ -177,6 +183,8 @@ public abstract class AbstractDatabase implements Database { private transient Map> foreignKeysBySchema; private transient Map> checkConstraintsBySchema; private transient Map> tablesBySchema; + private transient Map> embeddablesBySchema; + private transient Map> embeddablesByTable; private transient Map> enumsBySchema; private transient Map> udtsBySchema; private transient Map> arraysBySchema; @@ -357,6 +365,9 @@ public abstract class AbstractDatabase implements Database { } final boolean matches(Pattern pattern, Definition definition) { + if (pattern == null) + return false; + if (!getRegexMatchesPartialQualification()) return pattern.matcher(definition.getName()).matches() || pattern.matcher(definition.getQualifiedName()).matches(); @@ -371,6 +382,9 @@ public abstract class AbstractDatabase implements Database { } final Pattern pattern(String regex) { + if (regex == null) + return null; + Pattern pattern = patterns.get(regex); if (pattern == null) { @@ -766,6 +780,16 @@ public abstract class AbstractDatabase implements Database { this.includeTables = includeTables; } + @Override + public final boolean getIncludeEmbeddables() { + return includeEmbeddables; + } + + @Override + public final void setIncludeEmbeddables(boolean includeEmbeddables) { + this.includeEmbeddables = includeEmbeddables; + } + @Override public final boolean getIncludeRoutines() { return includeRoutines; @@ -1453,6 +1477,84 @@ public abstract class AbstractDatabase implements Database { return null; } + @Override + public final void setConfiguredEmbeddables(List configuredEmbeddables) { + this.configuredEmbeddables = configuredEmbeddables; + } + + @Override + public final List getConfiguredEmbeddables() { + if (configuredEmbeddables == null) + configuredEmbeddables = new ArrayList(); + + return configuredEmbeddables; + } + + @Override + public final List getEmbeddables() { + List result = new ArrayList(); + + for (SchemaDefinition schema : getSchemata()) { + for (TableDefinition table : getTables(schema)) { + for (Embeddable embeddable : getConfiguredEmbeddables()) { + List columns = new ArrayList(); + List names = new ArrayList(); + + for (EmbeddableField embeddableField : embeddable.getFields()) { + boolean matched = false; + + for (ColumnDefinition column : table.getColumns()) + if (matches(pattern(embeddableField.getExpression()), column)) + if (matched) + log.warn("EmbeddableField configuration matched several columns in table " + table + ": " + embeddableField); + else + matched = columns.add(column) && names.add(defaultIfEmpty(embeddableField.getName(), column.getName())); + } + + if (columns.size() == embeddable.getFields().size()) + result.add(new DefaultEmbeddableDefinition(embeddable.getName(), names, table, columns)); + } + } + } + + return result; + } + + @Override + public final List getEmbeddables(SchemaDefinition schema) { + if (embeddables == null) { + embeddables = new ArrayList(); + + if (getIncludeEmbeddables()) { + try { + List r = getEmbeddables(); + + embeddables = sort(r); + // indexes = sort(filterExcludeInclude(r)); TODO Support include / exclude for indexes (and constraints!) + log.info("Embeddables fetched", fetchedSize(r, embeddables)); + } + catch (Exception e) { + log.error("Error while fetching embeddables", e); + } + } + else + log.info("Embeddables excluded"); + } + + if (embeddablesBySchema == null) + embeddablesBySchema = new LinkedHashMap>(); + + return filterSchema(embeddables, schema, embeddablesBySchema); + } + + @Override + public final List getEmbeddables(TableDefinition table) { + if (embeddablesByTable == null) + embeddablesByTable = new LinkedHashMap>(); + + return filterTable(getEmbeddables(table.getSchema()), table, embeddablesByTable); + } + @Override public final EnumDefinition getEnum(SchemaDefinition schema, String name) { return getEnum(schema, name, false); @@ -1795,18 +1897,40 @@ public abstract class AbstractDatabase implements Database { } protected final List filterSchema(List definitions, SchemaDefinition schema) { - if (schema == null) { + if (schema == null) return definitions; - } - else { - List result = new ArrayList(); - for (T definition : definitions) - if (definition.getSchema().equals(schema)) - result.add(definition); + List result = new ArrayList(); - return result; + for (T definition : definitions) + if (definition.getSchema().equals(schema)) + result.add(definition); + + return result; + } + + protected final List filterTable(List definitions, TableDefinition table, Map> cache) { + List result = cache.get(table); + + if (result == null) { + result = filterTable(definitions, table); + cache.put(table, result); } + + return result; + } + + protected final List filterTable(List definitions, TableDefinition table) { + if (table == null) + return definitions; + + List result = new ArrayList(); + + for (T definition : definitions) + if (definition.getTable().equals(table)) + result.add(definition); + + return result; } @Override diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java index dd09b73d42..59deb9a0cf 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractTableDefinition.java @@ -57,9 +57,9 @@ public abstract class AbstractTableDefinition extends AbstractElementContainerDefinition implements TableDefinition { - private List parameters; - private TableDefinition parentTable; - private List childTables; + private List parameters; + private TableDefinition parentTable; + private List childTables; public AbstractTableDefinition(SchemaDefinition schema, String name, String comment) { super(schema, name, comment); @@ -68,6 +68,11 @@ implements TableDefinition { this.childTables = new ArrayList(); } + @Override + public final List getEmbeddables() { + return getDatabase().getEmbeddables(this); + } + @Override public final UniqueKeyDefinition getPrimaryKey() { UniqueKeyDefinition primaryKey = null; diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/Database.java b/jOOQ-meta/src/main/java/org/jooq/meta/Database.java index 4b305ab3ad..70cd0beeb0 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/Database.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/Database.java @@ -49,6 +49,7 @@ import org.jooq.SQLDialect; import org.jooq.Table; import org.jooq.meta.jaxb.CatalogMappingType; import org.jooq.meta.jaxb.CustomType; +import org.jooq.meta.jaxb.Embeddable; import org.jooq.meta.jaxb.EnumType; import org.jooq.meta.jaxb.ForcedType; import org.jooq.meta.jaxb.RegexFlag; @@ -151,6 +152,21 @@ public interface Database extends AutoCloseable { */ TableDefinition getTable(SchemaDefinition schema, Name name, boolean ignoreCase); + /** + * Get all embeddables. + */ + List getEmbeddables(); + + /** + * Get all embeddables for a given schema. + */ + List getEmbeddables(SchemaDefinition schema); + + /** + * Get all embeddables for a given table. + */ + List getEmbeddables(TableDefinition table); + /** * The enum UDTs defined in this database. */ @@ -511,6 +527,16 @@ public interface Database extends AutoCloseable { */ boolean getIncludeTables(); + /** + * Whether embeddable types should be included. + */ + void setIncludeEmbeddables(boolean includeEmbeddables); + + /** + * Whether embeddable types should be included. + */ + boolean getIncludeEmbeddables(); + /** * Whether invisible columns should be included. */ @@ -755,6 +781,17 @@ public interface Database extends AutoCloseable { */ ForcedType getConfiguredForcedType(Definition definition, DataTypeDefinition definedType); + /** + * Configure the embeddable types. + */ + void setConfiguredEmbeddables(List configuredEmbeddables); + + /** + * Get the configured embeddable type definitions for any given + * {@link Definition}. + */ + List getConfiguredEmbeddables(); + /** * Get the dialect for this database. */ diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableColumnDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableColumnDefinition.java new file mode 100644 index 0000000000..bf22130c3a --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableColumnDefinition.java @@ -0,0 +1,66 @@ +/* + * 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.meta; + +/** + * @author Lukas Eder + */ +public class DefaultEmbeddableColumnDefinition + extends AbstractTypedElementDefinition + implements EmbeddableColumnDefinition { + + private final ColumnDefinition column; + private final int position; + + public DefaultEmbeddableColumnDefinition(EmbeddableDefinition container, String name, ColumnDefinition column, int position) { + super(container, name, position, column.getDefinedType(), column.getComment()); + + this.column = column; + this.position = position; + } + + @Override + public final int getPosition() { + return position; + } + + @Override + public final ColumnDefinition getColumn() { + return column; + } +} diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableDefinition.java new file mode 100644 index 0000000000..acdcd14f28 --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/DefaultEmbeddableDefinition.java @@ -0,0 +1,95 @@ +/* + * 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.meta; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Lukas Eder + */ +public class DefaultEmbeddableDefinition + extends AbstractElementContainerDefinition + implements EmbeddableDefinition { + + private final List columnNames; + private final TableDefinition table; + private final List embeddableColumns; + + public DefaultEmbeddableDefinition(String name, List columnNames, TableDefinition table, List columns) { + super(table.getSchema(), name, ""); + + this.columnNames = columnNames; + this.table = table; + this.embeddableColumns = new ArrayList(); + + for (int i = 0; i < columns.size(); i++) + embeddableColumns.add(new DefaultEmbeddableColumnDefinition(this, columnNames.get(i), columns.get(i), i)); + } + + @Override + public final TableDefinition getTable() { + return table; + } + + @Override + protected List getElements0() throws SQLException { + return embeddableColumns; + } + + @Override + public final List getColumns() { + return getElements(); + } + + @Override + public final EmbeddableColumnDefinition getColumn(String columnName) { + return getElement(columnName); + } + + @Override + public final EmbeddableColumnDefinition getColumn(String columnName, boolean ignoreCase) { + return getElement(columnName, ignoreCase); + } + + @Override + public final EmbeddableColumnDefinition getColumn(int columnIndex) { + return getElement(columnIndex); + } +} diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableColumnDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableColumnDefinition.java new file mode 100644 index 0000000000..8a44d0a5a1 --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableColumnDefinition.java @@ -0,0 +1,58 @@ +/* + * 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.meta; + +/** + * An interface defining a column of an embeddable type. + * + * @author Lukas Eder + */ +public interface EmbeddableColumnDefinition extends TypedElementDefinition { + + /** + * The column position in the embeddable type. + */ + int getPosition(); + + /** + * The backing column definition. + */ + ColumnDefinition getColumn(); + +} diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableDefinition.java new file mode 100644 index 0000000000..3896fe7ec1 --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/EmbeddableDefinition.java @@ -0,0 +1,70 @@ +/* + * 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.meta; + +import java.util.List; + +/** + * The definition of an embeddable type. + * + * @author Lukas Eder + */ +public interface EmbeddableDefinition extends TableElementDefinition { + + /** + * All columns in the type, table or view. + */ + List getColumns(); + + /** + * Get a column in this type by its name. + */ + EmbeddableColumnDefinition getColumn(String columnName); + + /** + * Get a column in this type by its name. + */ + EmbeddableColumnDefinition getColumn(String columnName, boolean ignoreCase); + + /** + * Get a column in this type by its index (starting at 0). + */ + EmbeddableColumnDefinition getColumn(int columnIndex); + +} diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/IndexDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/IndexDefinition.java index d7e332fc1a..5cfae97ee5 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/IndexDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/IndexDefinition.java @@ -44,12 +44,7 @@ import java.util.List; * * @author Lukas Eder */ -public interface IndexDefinition extends Definition { - - /** - * The table holding this index. - */ - TableDefinition getTable(); +public interface IndexDefinition extends TableElementDefinition { /** * The list of columns making up the index. diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java index 9cf8308a1a..69ebd2d3dd 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/TableDefinition.java @@ -70,6 +70,11 @@ public interface TableDefinition extends Definition { */ ColumnDefinition getColumn(int columnIndex); + /** + * All embeddable types in the table. + */ + List getEmbeddables(); + /** * Get the indexes for this table. */ diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/TableElementDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/TableElementDefinition.java new file mode 100644 index 0000000000..a39ead233c --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/TableElementDefinition.java @@ -0,0 +1,53 @@ +/* + * 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.meta; + +/** + * The definition of an object that is contained in a table. + * + * @author Lukas Eder + */ +public interface TableElementDefinition extends Definition { + + /** + * The table that this object is part of. + */ + TableDefinition getTable(); + +} diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Database.java b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Database.java index e04bd2416a..8f441616d7 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Database.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/Database.java @@ -57,6 +57,8 @@ public class Database implements Serializable @XmlElement(defaultValue = "true") protected Boolean includeTables = true; @XmlElement(defaultValue = "true") + protected Boolean includeEmbeddables = true; + @XmlElement(defaultValue = "true") protected Boolean includeRoutines = true; @XmlElement(defaultValue = "false") protected Boolean includeTriggerRoutines = false; @@ -140,6 +142,9 @@ public class Database implements Serializable @XmlElementWrapper(name = "schemata") @XmlElement(name = "schema") protected List schemata; + @XmlElementWrapper(name = "embeddables") + @XmlElement(name = "embeddable") + protected List embeddables; @XmlElementWrapper(name = "customTypes") @XmlElement(name = "customType") protected List customTypes; @@ -379,6 +384,30 @@ public class Database implements Serializable this.includeTables = value; } + /** + * This flag indicates whether embeddable types should be included in output produced by this database + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isIncludeEmbeddables() { + return includeEmbeddables; + } + + /** + * Sets the value of the includeEmbeddables property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setIncludeEmbeddables(Boolean value) { + this.includeEmbeddables = value; + } + /** * This flag indicates whether routines should be included in output produced by this database * @@ -1299,6 +1328,17 @@ public class Database implements Serializable this.schemata = schemata; } + public List getEmbeddables() { + if (embeddables == null) { + embeddables = new ArrayList(); + } + return embeddables; + } + + public void setEmbeddables(List embeddables) { + this.embeddables = embeddables; + } + public List getCustomTypes() { if (customTypes == null) { customTypes = new ArrayList(); @@ -1378,6 +1418,11 @@ public class Database implements Serializable return this; } + public Database withIncludeEmbeddables(Boolean value) { + setIncludeEmbeddables(value); + return this; + } + public Database withIncludeRoutines(Boolean value) { setIncludeRoutines(value); return this; @@ -1606,6 +1651,27 @@ public class Database implements Serializable return this; } + public Database withEmbeddables(Embeddable... values) { + if (values!= null) { + for (Embeddable value: values) { + getEmbeddables().add(value); + } + } + return this; + } + + public Database withEmbeddables(Collection values) { + if (values!= null) { + getEmbeddables().addAll(values); + } + return this; + } + + public Database withEmbeddables(List embeddables) { + setEmbeddables(embeddables); + return this; + } + public Database withCustomTypes(CustomType... values) { if (values!= null) { for (CustomType value: values) { @@ -1711,6 +1777,11 @@ public class Database implements Serializable sb.append(includeTables); sb.append(""); } + if (includeEmbeddables!= null) { + sb.append(""); + sb.append(includeEmbeddables); + sb.append(""); + } if (includeRoutines!= null) { sb.append(""); sb.append(includeRoutines); @@ -1903,6 +1974,15 @@ public class Database implements Serializable } sb.append(""); } + if (embeddables!= null) { + sb.append(""); + for (int i = 0; (i"); + sb.append(embeddables.get(i)); + sb.append(""); + } + sb.append(""); + } if (customTypes!= null) { sb.append(""); for (int i = 0; (i fields; + + /** + * The name of the embeddable type + * + * @return + * possible object is + * {@link String } + * + */ + public String getName() { + return name; + } + + /** + * Sets the value of the name property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setName(String value) { + this.name = value; + } + + public List getFields() { + if (fields == null) { + fields = new ArrayList(); + } + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public Embeddable withName(String value) { + setName(value); + return this; + } + + public Embeddable withFields(EmbeddableField... values) { + if (values!= null) { + for (EmbeddableField value: values) { + getFields().add(value); + } + } + return this; + } + + public Embeddable withFields(Collection values) { + if (values!= null) { + getFields().addAll(values); + } + return this; + } + + public Embeddable withFields(List fields) { + setFields(fields); + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (name!= null) { + sb.append(""); + sb.append(name); + sb.append(""); + } + if (fields!= null) { + sb.append(""); + for (int i = 0; (i"); + sb.append(fields.get(i)); + sb.append(""); + } + sb.append(""); + } + return sb.toString(); + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (that == null) { + return false; + } + if (getClass()!= that.getClass()) { + return false; + } + Embeddable other = ((Embeddable) that); + if (name == null) { + if (other.name!= null) { + return false; + } + } else { + if (!name.equals(other.name)) { + return false; + } + } + if (fields == null) { + if (other.fields!= null) { + return false; + } + } else { + if (!fields.equals(other.fields)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = ((prime*result)+((name == null)? 0 :name.hashCode())); + result = ((prime*result)+((fields == null)? 0 :fields.hashCode())); + return result; + } + +} diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/EmbeddableField.java b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/EmbeddableField.java new file mode 100644 index 0000000000..13b043c21d --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/EmbeddableField.java @@ -0,0 +1,173 @@ + + + + + + + + +package org.jooq.meta.jaxb; + +import java.io.Serializable; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import org.jooq.util.jaxb.tools.StringAdapter; + + +/** + *

Java class for EmbeddableField complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType name="EmbeddableField">
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <all>
+ *         <element name="name" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
+ *         <element name="expression" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *       </all>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "EmbeddableField", propOrder = { + +}) +@SuppressWarnings({ + "all" +}) +public class EmbeddableField implements Serializable +{ + + private final static long serialVersionUID = 31200L; + @XmlJavaTypeAdapter(StringAdapter.class) + protected String name; + @XmlElement(required = true) + @XmlJavaTypeAdapter(StringAdapter.class) + protected String expression; + + /** + * A name for the field in case the regex does not produce unique names for all matches. + * + * @return + * possible object is + * {@link String } + * + */ + public String getName() { + return name; + } + + /** + * Sets the value of the name property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setName(String value) { + this.name = value; + } + + /** + * A regex matching all column names that are part of the embeddable type. The regex must match only one column per table. + * + * @return + * possible object is + * {@link String } + * + */ + public String getExpression() { + return expression; + } + + /** + * Sets the value of the expression property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setExpression(String value) { + this.expression = value; + } + + public EmbeddableField withName(String value) { + setName(value); + return this; + } + + public EmbeddableField withExpression(String value) { + setExpression(value); + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (name!= null) { + sb.append(""); + sb.append(name); + sb.append(""); + } + if (expression!= null) { + sb.append(""); + sb.append(expression); + sb.append(""); + } + return sb.toString(); + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (that == null) { + return false; + } + if (getClass()!= that.getClass()) { + return false; + } + EmbeddableField other = ((EmbeddableField) that); + if (name == null) { + if (other.name!= null) { + return false; + } + } else { + if (!name.equals(other.name)) { + return false; + } + } + if (expression == null) { + if (other.expression!= null) { + return false; + } + } else { + if (!expression.equals(other.expression)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = ((prime*result)+((name == null)? 0 :name.hashCode())); + result = ((prime*result)+((expression == null)? 0 :expression.hashCode())); + return result; + } + +} 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 2d7e77f608..2e4865b8da 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 @@ -67,6 +67,8 @@ public class Generate implements Serializable @XmlElement(defaultValue = "true") protected Boolean tables = true; @XmlElement(defaultValue = "true") + protected Boolean embeddables = true; + @XmlElement(defaultValue = "true") protected Boolean records = true; @XmlElement(defaultValue = "true") protected Boolean recordsImplementingRecordN = true; @@ -534,6 +536,30 @@ public class Generate implements Serializable this.tables = value; } + /** + * Generate embeddable classes. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isEmbeddables() { + return embeddables; + } + + /** + * Sets the value of the embeddables property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setEmbeddables(Boolean value) { + this.embeddables = value; + } + /** * Generate TableRecord classes. * @@ -1835,6 +1861,11 @@ public class Generate implements Serializable return this; } + public Generate withEmbeddables(Boolean value) { + setEmbeddables(value); + return this; + } + public Generate withRecords(Boolean value) { setRecords(value); return this; @@ -2163,6 +2194,11 @@ public class Generate implements Serializable sb.append(tables); sb.append(""); } + if (embeddables!= null) { + sb.append(""); + sb.append(embeddables); + sb.append(""); + } if (records!= null) { sb.append(""); sb.append(records); @@ -2563,6 +2599,15 @@ public class Generate implements Serializable return false; } } + if (embeddables == null) { + if (other.embeddables!= null) { + return false; + } + } else { + if (!embeddables.equals(other.embeddables)) { + return false; + } + } if (records == null) { if (other.records!= null) { return false; @@ -3035,6 +3080,7 @@ public class Generate implements Serializable result = ((prime*result)+((links == null)? 0 :links.hashCode())); result = ((prime*result)+((keys == null)? 0 :keys.hashCode())); result = ((prime*result)+((tables == null)? 0 :tables.hashCode())); + result = ((prime*result)+((embeddables == null)? 0 :embeddables.hashCode())); result = ((prime*result)+((records == null)? 0 :records.hashCode())); result = ((prime*result)+((recordsImplementingRecordN == null)? 0 :recordsImplementingRecordN.hashCode())); result = ((prime*result)+((pojos == null)? 0 :pojos.hashCode())); diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/ObjectFactory.java b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/ObjectFactory.java index 7741a7acd8..99fee71e25 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/ObjectFactory.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/jaxb/ObjectFactory.java @@ -180,6 +180,22 @@ public class ObjectFactory { return new EnumType(); } + /** + * Create an instance of {@link Embeddable } + * + */ + public Embeddable createEmbeddable() { + return new Embeddable(); + } + + /** + * Create an instance of {@link EmbeddableField } + * + */ + public EmbeddableField createEmbeddableField() { + return new EmbeddableField(); + } + /** * Create an instance of {@link ForcedType } * diff --git a/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.12.0.xsd b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.12.0.xsd index f9e3d67e19..c83a180697 100644 --- a/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.12.0.xsd +++ b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.12.0.xsd @@ -471,6 +471,10 @@ Excludes match before includes, i.e. excludes have a higher priority.]]> + + + + @@ -695,6 +699,10 @@ generated artefacts.]]> This comparator can be used to influence the order of any object that is produced by jOOQ meta, and thus, indirectly, the order of declared objects in generated code.]]> + + + + @@ -791,6 +799,12 @@ for Oracle.]]> + + + + + + @@ -829,6 +843,38 @@ for Oracle.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -959,6 +1005,10 @@ jOOQ version used for source code.]]> + + + + diff --git a/jOOQ/src/main/java/org/jooq/EmbeddableRecord.java b/jOOQ/src/main/java/org/jooq/EmbeddableRecord.java new file mode 100644 index 0000000000..8a9e5a4443 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/EmbeddableRecord.java @@ -0,0 +1,65 @@ +/* + * 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; + +/** + * A record originating from a single table + * + * @param The embeddable record type + * @author Lukas Eder + */ +public interface EmbeddableRecord> extends Record { + + /** + * {@inheritDoc} + */ + @Override + R original(); + + /** + * {@inheritDoc} + */ + @Override + R with(Field field, T value); + + /** + * {@inheritDoc} + */ + @Override + R with(Field field, U value, Converter converter); +} diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java index 93ac1590a9..e292691a17 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java @@ -1460,32 +1460,26 @@ abstract class AbstractField extends AbstractNamed implements Field { @SuppressWarnings("unchecked") private final Field numeric() { - if (getDataType().isNumeric()) { + if (getDataType().isNumeric()) return (Field) this; - } - else { + else return (Field) cast(BigDecimal.class); - } } @SuppressWarnings("unchecked") private final Field varchar() { - if (getDataType().isString()) { + if (getDataType().isString()) return (Field) this; - } - else { + else return cast(String.class); - } } @SuppressWarnings("unchecked") private final Field date() { - if (getDataType().isTemporal()) { + if (getDataType().isTemporal()) return (Field) this; - } - else { + else return (Field) cast(Timestamp.class); - } } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java index 674644444c..26e9c6bec6 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java @@ -40,7 +40,9 @@ package org.jooq.impl; import static java.util.Arrays.asList; import static org.jooq.conf.SettingsTools.updatablePrimaryKeys; +import static org.jooq.impl.Tools.embeddedFields; import static org.jooq.impl.Tools.indexOrFail; +import static org.jooq.impl.Tools.isEmbeddable; import static org.jooq.impl.Tools.resetChangedOnNotNull; import static org.jooq.impl.Tools.settings; import static org.jooq.impl.Tools.ThreadGuard.Guard.RECORD_TOSTRING; @@ -62,6 +64,7 @@ import org.jooq.CSVFormat; import org.jooq.ChartFormat; import org.jooq.Converter; import org.jooq.DataType; +import org.jooq.EmbeddableRecord; import org.jooq.Field; import org.jooq.JSONFormat; import org.jooq.Name; @@ -240,7 +243,16 @@ abstract class AbstractRecord extends AbstractStore implements Record { @Override public final T get(Field field) { - return (T) get(indexOrFail(fieldsRow(), field)); + if (field instanceof EmbeddableTableField) { + Field[] f = embeddedFields(field); + + return (T) Tools + .newRecord(fetched, ((EmbeddableTableField) field).recordType) + .operate(new TransferRecordState(f)); + } + else { + return (T) get(indexOrFail(fieldsRow(), field)); + } } @Override @@ -310,15 +322,24 @@ abstract class AbstractRecord extends AbstractStore implements Record { } protected final void set(int index, Object value) { - set(index, (Field) field(index), value); + set(index, field(index), value); } @Override public final void set(Field field, T value) { - set(indexOrFail(fields, field), field, value); + if (isEmbeddable(field) && value instanceof EmbeddableRecord) { + Field[] f = embeddedFields(field); + Object[] v = ((EmbeddableRecord) value).intoArray(); + + for (int i = 0; i < f.length; i++) + set(indexOrFail(fields, f[i]), f[i], v[i]); + } + else { + set(indexOrFail(fields, field), field, value); + } } - final void set(int index, Field field, T value) { + final void set(int index, Field field, Object value) { // Relevant issues documenting this method's behaviour: // [#945] Avoid bugs resulting from setting the same value twice // [#948] To allow for controlling the number of hard-parses diff --git a/jOOQ/src/main/java/org/jooq/impl/CompareCondition.java b/jOOQ/src/main/java/org/jooq/impl/CompareCondition.java index 94a902375d..950d11834b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CompareCondition.java +++ b/jOOQ/src/main/java/org/jooq/impl/CompareCondition.java @@ -55,10 +55,13 @@ import static org.jooq.SQLDialect.POSTGRES; // ... import static org.jooq.conf.ParamType.INLINED; import static org.jooq.impl.DSL.inline; +import static org.jooq.impl.DSL.row; import static org.jooq.impl.Keywords.K_AS; import static org.jooq.impl.Keywords.K_CAST; import static org.jooq.impl.Keywords.K_ESCAPE; import static org.jooq.impl.Keywords.K_VARCHAR; +import static org.jooq.impl.Tools.embeddedFields; +import static org.jooq.impl.Tools.isEmbeddable; import java.util.EnumSet; @@ -99,6 +102,13 @@ final class CompareCondition extends AbstractCondition implements LikeEscapeStep @Override public final void accept(Context ctx) { + if (isEmbeddable(field1) && isEmbeddable(field2)) + ctx.visit(row(embeddedFields(field1)).compare(comparator, embeddedFields(field2))); + else + accept0(ctx); + } + + private final void accept0(Context ctx) { SQLDialect family = ctx.family(); Field lhs = field1; Field rhs = field2; diff --git a/jOOQ/src/main/java/org/jooq/impl/EmbeddableRecordImpl.java b/jOOQ/src/main/java/org/jooq/impl/EmbeddableRecordImpl.java new file mode 100644 index 0000000000..441e610d1f --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/EmbeddableRecordImpl.java @@ -0,0 +1,97 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import org.jooq.Converter; +import org.jooq.EmbeddableRecord; +import org.jooq.Field; +import org.jooq.Row; + +/** + * A record implementation for a record originating from a single table + *

+ * This type is for JOOQ INTERNAL USE only. Do not reference directly + * + * @author Lukas Eder + */ +public class EmbeddableRecordImpl> extends AbstractRecord implements EmbeddableRecord { + + /** + * Generated UID + */ + private static final long serialVersionUID = -1260149220986944763L; + + public EmbeddableRecordImpl(Field... fields) { + super(fields); + } + + @SuppressWarnings("unchecked") + @Override + public final R with(Field field, T value) { + return (R) super.with(field, value); + } + + @SuppressWarnings("unchecked") + @Override + public final R with(Field field, U value, Converter converter) { + return (R) super.with(field, value, converter); + } + + /* + * Subclasses may override this method + */ + @Override + public Row fieldsRow() { + return fields; + } + + /* + * Subclasses may override this method + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Row valuesRow() { + return new RowImpl(Tools.fields(intoArray(), fields.fields.fields())); + } + + @SuppressWarnings("unchecked") + @Override + public final R original() { + return (R) super.original(); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/EmbeddableTableField.java b/jOOQ/src/main/java/org/jooq/impl/EmbeddableTableField.java new file mode 100644 index 0000000000..5af1bd9e2f --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/EmbeddableTableField.java @@ -0,0 +1,87 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import static org.jooq.impl.Tools.BooleanDataKey.DATA_LIST_ALREADY_INDENTED; + +import org.jooq.Context; +import org.jooq.Name; +import org.jooq.Record; +import org.jooq.SQLDialect; +import org.jooq.Table; +import org.jooq.TableField; + +/** + * @author Lukas Eder + */ +final class EmbeddableTableField extends AbstractField implements TableField { + + /** + * Generated UID + */ + private static final long serialVersionUID = -7105430856294526440L; + final Table table; + final TableField[] fields; + final Class recordType; + + EmbeddableTableField(Name name, Class recordType, Table table, TableField[] fields) { + super(name, new DefaultDataType(SQLDialect.DEFAULT, recordType, name.last())); + + this.table = table; + this.recordType = recordType; + this.fields = fields; + } + + // ------------------------------------------------------------------------- + // TableField API + // ------------------------------------------------------------------------- + + @Override + public final void accept(Context ctx) { + Object previous = ctx.data(DATA_LIST_ALREADY_INDENTED); + + ctx.data(DATA_LIST_ALREADY_INDENTED, true); + ctx.visit(new QueryPartList>(fields)); + ctx.data(DATA_LIST_ALREADY_INDENTED, previous); + } + + @Override + public final Table getTable() { + return table; + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java b/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java index b0f0568d9e..e905a1fcad 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java @@ -43,6 +43,7 @@ import static org.jooq.SQLDialect.POSTGRES; import static org.jooq.SQLDialect.SQLITE; // ... // ... +import static org.jooq.impl.Tools.flattenEntrySet; import java.util.EnumSet; import java.util.Map; @@ -87,12 +88,11 @@ final class FieldMapForUpdate extends AbstractQueryPartMap, Field> { boolean restoreQualify = ctx.qualify(); boolean supportsQualify = NO_SUPPORT_QUALIFY.contains(ctx.family()) ? false : restoreQualify; - for (Entry, Field> entry : entrySet()) { + for (Entry, Field> entry : flattenEntrySet(entrySet())) { ctx.sql(separator); - if (!"".equals(separator)) { + if (!"".equals(separator)) ctx.formatNewLine(); - } ctx.start(assignmentClause) .qualify(supportsQualify) diff --git a/jOOQ/src/main/java/org/jooq/impl/Internal.java b/jOOQ/src/main/java/org/jooq/impl/Internal.java index c3b32cfc8a..4959f6052e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Internal.java +++ b/jOOQ/src/main/java/org/jooq/impl/Internal.java @@ -61,6 +61,13 @@ import org.jooq.UniqueKey; */ public final class Internal { + /** + * Factory method for embeddable types. + */ + public static final TableField createEmbeddable(Name name, Class recordType, Table table, TableField... fields) { + return new EmbeddableTableField(name, recordType, table, fields); + } + /** * Factory method for indexes. */ diff --git a/jOOQ/src/main/java/org/jooq/impl/SortFieldImpl.java b/jOOQ/src/main/java/org/jooq/impl/SortFieldImpl.java index 8a8e09be53..c84e094ad7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SortFieldImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SortFieldImpl.java @@ -133,34 +133,39 @@ final class SortFieldImpl extends AbstractQueryPart implements SortField { ctx.visit(nvl2(field, ifNotNull, ifNull)) .sql(", "); - acceptFieldAndOrder(ctx); - + acceptFieldAndOrder(ctx, false); break; } // DERBY, H2, HSQLDB, ORACLE, POSTGRES default: { - acceptFieldAndOrder(ctx); - - if (nullsFirst) - ctx.sql(' ').visit(K_NULLS_FIRST); - else - ctx.sql(' ').visit(K_NULLS_LAST); - + acceptFieldAndOrder(ctx, true); break; } } } else { - acceptFieldAndOrder(ctx); + acceptFieldAndOrder(ctx, false); } } - private final void acceptFieldAndOrder(Context ctx) { - ctx.visit(field); + private final void acceptFieldAndOrder(Context ctx, boolean includeNulls) { + String separator = ""; - if (order != SortOrder.DEFAULT) - ctx.sql(' ') - .visit(order.toKeyword()); + for (Field f : Tools.flatten(field)) { + ctx.sql(separator).visit(f); + + if (order != SortOrder.DEFAULT) + ctx.sql(' ') + .visit(order.toKeyword()); + + if (includeNulls) + if (nullsFirst) + ctx.sql(' ').visit(K_NULLS_FIRST); + else + ctx.sql(' ').visit(K_NULLS_LAST); + + separator = ", "; + } } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index b65e4081bb..669c518da7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -40,6 +40,7 @@ package org.jooq.impl; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static java.lang.Character.isJavaIdentifierPart; +import static java.util.Collections.singletonList; // ... // ... // ... @@ -166,6 +167,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; +import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -176,6 +178,8 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -201,6 +205,7 @@ import org.jooq.Context; import org.jooq.Cursor; import org.jooq.DSLContext; import org.jooq.DataType; +import org.jooq.EmbeddableRecord; import org.jooq.EnumType; import org.jooq.ExecuteContext; import org.jooq.ExecuteListener; @@ -4772,4 +4777,164 @@ final class Tools { static final boolean isEmpty(Object[] array) { return array == null || array.length == 0; } + + static final boolean isEmbeddable(Object object) { + return object instanceof EmbeddableTableField + || object instanceof Val && ((Val) object).value instanceof EmbeddableRecord + || object instanceof EmbeddableRecord; + } + + static final Field[] embeddedFields(Object object) { + return object instanceof EmbeddableTableField + ? ((EmbeddableTableField) object).fields + : object instanceof Val && ((Val) object).value instanceof EmbeddableRecord + ? ((EmbeddableRecord) ((Val) object).value).valuesRow().fields() + : object instanceof EmbeddableRecord + ? ((EmbeddableRecord) object).valuesRow().fields() + : null; + } + + /** + * Flatten out an {@link EmbeddableTableField}. + */ + static final > Iterable flatten(final E field) { + return new Iterable() { + @Override + public Iterator iterator() { + Iterator it = singletonList(field).iterator(); + + if (field instanceof EmbeddableTableField) + return new FlatteningIterator(it) { + @SuppressWarnings("unchecked") + @Override + List flatten(E e) { + return (List) Arrays.asList(((EmbeddableTableField) e).fields); + } + }; + else + return it; + } + }; + } + + /** + * Flatten out {@link EmbeddableTableField} elements contained in an + * ordinary iterable. + */ + static final > Iterable flattenCollection(final Iterable iterable) { + return new Iterable() { + @Override + public Iterator iterator() { + return new FlatteningIterator(iterable.iterator()) { + @SuppressWarnings("unchecked") + @Override + List flatten(E e) { + if (e instanceof EmbeddableTableField) + return (List) Arrays.asList(((EmbeddableTableField) e).fields); + + return null; + } + }; + } + }; + } + + /** + * Flatten out {@link EmbeddableTableField} elements contained in an + * entry set iterable. + */ + static final , Field>> Iterable flattenEntrySet(final Iterable iterable) { + return new Iterable() { + @Override + public Iterator iterator() { + return new FlatteningIterator(iterable.iterator()) { + @SuppressWarnings("unchecked") + @Override + List flatten(E e) { + if (e.getKey() instanceof EmbeddableTableField) { + List result = new ArrayList(); + Field[] keys = embeddedFields(e.getKey()); + Field[] values = embeddedFields(e.getValue()); + + for (int i = 0; i < keys.length; i++) + result.add((E) new SimpleImmutableEntry, Field>( + keys[i], values[i] + )); + + return result; + } + + return null; + } + }; + } + }; + } + + /** + * A base implementation for {@link EmbeddableTableField} flattening + * iterators with a default implementation for {@link Iterator#remove()} for + * convenience in the Java 6 build. + */ + static abstract class FlatteningIterator implements Iterator { + private final Iterator delegate; + private Iterator flatten; + private E next; + + FlatteningIterator(Iterator delegate) { + this.delegate = delegate; + } + + abstract List flatten(E e); + + private final void move() { + if (next == null) { + if (flatten != null) { + if (flatten.hasNext()) { + next = flatten.next(); + return; + } + else { + flatten = null; + } + } + + if (delegate.hasNext()) { + next = delegate.next(); + + List flattened = flatten(next); + if (flattened == null) + return; + + next = null; + flatten = flattened.iterator(); + move(); + return; + } + } + } + + @Override + public final boolean hasNext() { + move(); + return next != null; + } + + @Override + public final E next() { + move(); + + if (next == null) + throw new NoSuchElementException(); + + E result = next; + next = null; + return result; + } + + @Override + public final void remove() { + throw new UnsupportedOperationException("remove"); + } + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Val.java b/jOOQ/src/main/java/org/jooq/impl/Val.java index b01b439058..fecd452098 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Val.java +++ b/jOOQ/src/main/java/org/jooq/impl/Val.java @@ -39,11 +39,15 @@ package org.jooq.impl; import static org.jooq.conf.ParamType.NAMED; import static org.jooq.conf.ParamType.NAMED_OR_INLINED; +import static org.jooq.impl.Tools.embeddedFields; +import static org.jooq.impl.Tools.BooleanDataKey.DATA_LIST_ALREADY_INDENTED; import java.sql.SQLException; import org.jooq.Context; import org.jooq.DataType; +import org.jooq.EmbeddableRecord; +import org.jooq.Field; import org.jooq.RenderContext; import org.jooq.conf.ParamType; import org.jooq.exception.DataAccessException; @@ -70,7 +74,14 @@ final class Val extends AbstractParam { @Override public void accept(Context ctx) { - if (ctx instanceof RenderContext) { + if (value instanceof EmbeddableRecord) { + Object previous = ctx.data(DATA_LIST_ALREADY_INDENTED); + + ctx.data(DATA_LIST_ALREADY_INDENTED, true); + ctx.visit(new QueryPartList>(embeddedFields(this))); + ctx.data(DATA_LIST_ALREADY_INDENTED, previous); + } + else if (ctx instanceof RenderContext) { ParamType paramType = ctx.paramType(); if (isInline(ctx))