[jOOQ/jOOQ#7406] Add code generation support for SETOF UDT returning

functions in PostgreSQL
This commit is contained in:
Lukas Eder 2024-11-08 14:31:45 +01:00
parent 4ffd806253
commit 1200fa0fa5
4 changed files with 74 additions and 17 deletions

View File

@ -1643,7 +1643,7 @@ public class JavaGenerator extends AbstractGenerator {
for (TableDefinition table : database.getTables(schema)) {
try {
if (table.isTableValuedFunction() && table.getReferencedTable() != table)
if (table.isTableValuedFunction() && table.getReferencedTableOrUDT() != table)
continue;
generateRecord(table);
@ -7204,7 +7204,7 @@ public class JavaGenerator extends AbstractGenerator {
final String tableId = scala
? out.ref(getStrategy().getFullJavaIdentifier(table), 2)
: getStrategy().getJavaIdentifier(table);
final String recordType = out.ref(getStrategy().getFullJavaClassName(table.getReferencedTable(), Mode.RECORD));
final String recordType = out.ref(getStrategy().getFullJavaClassName(table.getReferencedTableOrUDT(), Mode.RECORD));
final String classExtends = out.ref(getStrategy().getJavaClassExtends(table, Mode.DEFAULT));
final List<String> interfaces = out.ref(getStrategy().getJavaClassImplements(table, Mode.DEFAULT));
final String schemaId = generateDefaultSchema(schema)
@ -8578,7 +8578,7 @@ public class JavaGenerator extends AbstractGenerator {
// [#10481] Use the types from replaced embeddables if applicable
List<Definition> replacingEmbeddablesAndUnreplacedColumns = replacingEmbeddablesAndUnreplacedColumns(table);
int degree = replacingEmbeddablesAndUnreplacedColumns.size();
int referencedDegree = replacingEmbeddablesAndUnreplacedColumns(table.getReferencedTable()).size();
int referencedDegree = replacingEmbeddablesAndUnreplacedColumns(table.getReferencedTableOrUDT()).size();
String rowType = refRowType(out, replacingEmbeddablesAndUnreplacedColumns);
String rowTypeContravariantJava = refRowType(out, replacingEmbeddablesAndUnreplacedColumns, s -> "? super " + s);
@ -11013,7 +11013,7 @@ public class JavaGenerator extends AbstractGenerator {
return;
}
final String recordClassName = out.ref(getStrategy().getFullJavaClassName(function.getReferencedTable(), Mode.RECORD));
final String recordClassName = out.ref(getStrategy().getFullJavaClassName(function.getReferencedTableOrUDT(), Mode.RECORD));
// [#3456] Local variables should not collide with actual function arguments
final String configurationArgument = disambiguateJavaMemberName(function.getParameters(), "configuration");
@ -11067,7 +11067,11 @@ public class JavaGenerator extends AbstractGenerator {
}
protected void printRecordTypeMethod(JavaWriter out, Definition tableOrUDT) {
final String className = out.ref(getStrategy().getFullJavaClassName(tableOrUDT instanceof TableDefinition ? ((TableDefinition) tableOrUDT).getReferencedTable() : tableOrUDT, Mode.RECORD));
final String className = out.ref(getStrategy().getFullJavaClassName(
tableOrUDT instanceof TableDefinition
? ((TableDefinition) tableOrUDT).getReferencedTableOrUDT()
: tableOrUDT, Mode.RECORD
));
out.javadoc("The class holding records for this type");

View File

@ -71,6 +71,7 @@ implements
private final SchemaDefinition referencedSchema;
private final String referencedName;
private TableDefinition referencedTable;
private Definition referencedTableOrUDT;
public AbstractTableDefinition(SchemaDefinition schema, String name, String comment) {
this(schema, name, comment, TableType.TABLE, null);
@ -353,6 +354,22 @@ implements
return referencedTable;
}
@Override
public final Definition getReferencedTableOrUDT() {
if (referencedTableOrUDT == null) {
if (referencedSchema != null)
referencedTableOrUDT = getDatabase().getTable(referencedSchema, referencedName);
if (referencedTableOrUDT == null)
referencedTableOrUDT = getDatabase().getUDT(referencedSchema, referencedName);
if (referencedTableOrUDT == null)
referencedTableOrUDT = this;
}
return referencedTableOrUDT;
}
@Override
protected List<ColumnDefinition> getElements0() throws SQLException {
return null;

View File

@ -236,8 +236,31 @@ public interface TableDefinition extends Definition {
* <code>true</code> and the table valued function references a table
* type</li>
* </ul>
*
* @deprecated - [#7406] - 3.20.0 - Use {@link #getReferencedTableOrUDT()}
* instead.
*/
@Deprecated
@NotNull
TableDefinition getReferencedTable();
/**
* The referenced table or UDT type, if this
* {@link #isTableValuedFunction()}.
* <p>
* This returns:
* <ul>
* <li><code>this</code>, if {@link #isTableValuedFunction()} ==
* <code>false</code></li>
* <li><code>this</code>, if {@link #isTableValuedFunction()} ==
* <code>true</code> but the table valued function doesn't reference a table
* type</li>
* <li>Another {@link TableDefinition} or {@link UDTDefinition}, if
* {@link #isTableValuedFunction()} == <code>true</code> and the table
* valued function references a table type</li>
* </ul>
*/
@NotNull
Definition getReferencedTableOrUDT();
}

View File

@ -48,8 +48,10 @@ import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.rowNumber;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.DSL.substring;
import static org.jooq.impl.DSL.val;
import static org.jooq.impl.DSL.when;
import static org.jooq.meta.postgres.PostgresRoutineDefinition.pNumericPrecision;
import static org.jooq.meta.postgres.information_schema.Tables.ATTRIBUTES;
import static org.jooq.meta.postgres.information_schema.Tables.COLUMNS;
import static org.jooq.meta.postgres.information_schema.Tables.PARAMETERS;
import static org.jooq.meta.postgres.information_schema.Tables.ROUTINES;
@ -73,6 +75,7 @@ import org.jooq.meta.DefaultColumnDefinition;
import org.jooq.meta.DefaultDataTypeDefinition;
import org.jooq.meta.ParameterDefinition;
import org.jooq.meta.SchemaDefinition;
import org.jooq.meta.postgres.information_schema.tables.Attributes;
import org.jooq.meta.postgres.information_schema.tables.Columns;
import org.jooq.meta.postgres.information_schema.tables.Parameters;
import org.jooq.meta.postgres.information_schema.tables.Routines;
@ -112,6 +115,7 @@ public class PostgresTableValuedFunction extends AbstractTableDefinition {
Routines r = ROUTINES.as("r");
Parameters p = PARAMETERS.as("p");
Columns c = COLUMNS.as("c");
Attributes a = ATTRIBUTES.as("a");
Columns x = COLUMNS.as("x");
PgClass pg_c = PG_CLASS.as("pgc");
PgAttribute pg_a = PG_ATTRIBUTE.as("pga");
@ -120,6 +124,7 @@ public class PostgresTableValuedFunction extends AbstractTableDefinition {
Field<Integer> pPrecision = pNumericPrecision(p);
Field<Integer> cPrecision = nvl(c.DATETIME_PRECISION, c.NUMERIC_PRECISION);
Field<Integer> aPrecision = nvl(a.DATETIME_PRECISION, a.NUMERIC_PRECISION);
Field<Integer> rPrecision = nvl(r.DATETIME_PRECISION, r.NUMERIC_PRECISION);
@ -127,6 +132,7 @@ public class PostgresTableValuedFunction extends AbstractTableDefinition {
for (Record record : create()
// [#3375] The first subselect is expected to return only those
@ -165,16 +171,17 @@ public class PostgresTableValuedFunction extends AbstractTableDefinition {
// table-valued functions that return a SETOF [ table type ], as that
// table reference is reported via a TYPE_UDT that matches a table
// from INFORMATION_SCHEMA.TABLES
// [#7406] Exclude composite types
select(
coalesce(c.COLUMN_NAME, getName()).as(c.COLUMN_NAME),
coalesce(c.ORDINAL_POSITION, inline(1)).as(c.ORDINAL_POSITION),
coalesce(c.COLUMN_NAME, a.ATTRIBUTE_NAME, val(getName())).as(c.COLUMN_NAME),
coalesce(c.ORDINAL_POSITION, a.ORDINAL_POSITION, inline(1)).as(c.ORDINAL_POSITION),
db.arrayDataType(x.DATA_TYPE, x.UDT_NAME, pg_a.ATTNDIMS).as(c.DATA_TYPE),
coalesce(c.CHARACTER_MAXIMUM_LENGTH, r.CHARACTER_MAXIMUM_LENGTH ).as(c.CHARACTER_MAXIMUM_LENGTH),
coalesce(cPrecision, rPrecision).as(c.NUMERIC_PRECISION),
coalesce(c.NUMERIC_SCALE, r.NUMERIC_SCALE).as(c.NUMERIC_SCALE),
coalesce(c.IS_NULLABLE, inline("true")).as(c.IS_NULLABLE),
coalesce(c.COLUMN_DEFAULT, inline((String) null)).as(c.COLUMN_DEFAULT),
coalesce(c.UDT_SCHEMA, inline((String) null)).as(c.UDT_SCHEMA),
coalesce(c.CHARACTER_MAXIMUM_LENGTH, a.CHARACTER_MAXIMUM_LENGTH, r.CHARACTER_MAXIMUM_LENGTH ).as(c.CHARACTER_MAXIMUM_LENGTH),
coalesce(cPrecision, aPrecision, rPrecision).as(c.NUMERIC_PRECISION),
coalesce(c.NUMERIC_SCALE, a.NUMERIC_SCALE, r.NUMERIC_SCALE).as(c.NUMERIC_SCALE),
coalesce(c.IS_NULLABLE, a.IS_NULLABLE, inline("true")).as(c.IS_NULLABLE),
coalesce(c.COLUMN_DEFAULT, a.ATTRIBUTE_DEFAULT, inline((String) null)).as(c.COLUMN_DEFAULT),
coalesce(c.UDT_SCHEMA, a.ATTRIBUTE_UDT_SCHEMA, inline((String) null)).as(c.UDT_SCHEMA),
db.arrayUdtName(x.DATA_TYPE, x.UDT_NAME).as(c.UDT_NAME)
)
.from(r)
@ -185,20 +192,26 @@ public class PostgresTableValuedFunction extends AbstractTableDefinition {
.join(pg_p)
.on(pg_p.PRONAME.concat("_").concat(pg_p.OID).eq(r.SPECIFIC_NAME))
.and(pg_p.pgNamespace().NSPNAME.eq(r.SPECIFIC_SCHEMA))
// [#7406] COLUMNS and ATTRIBUTES are mutually exclusive, hence no cross product here
.leftJoin(c)
.on(row(r.TYPE_UDT_CATALOG, r.TYPE_UDT_SCHEMA, r.TYPE_UDT_NAME)
.eq(c.TABLE_CATALOG, c.TABLE_SCHEMA, c.TABLE_NAME))
.leftJoin(a)
.on(row(r.TYPE_UDT_CATALOG, r.TYPE_UDT_SCHEMA, r.TYPE_UDT_NAME)
.eq(a.UDT_CATALOG, a.UDT_SCHEMA, a.UDT_NAME))
.leftJoin(pg_c)
.on(c.TABLE_NAME.eq(pg_c.RELNAME))
.on(nvl(c.TABLE_NAME, a.UDT_NAME).eq(pg_c.RELNAME))
.and(pg_c.pgNamespace().NSPNAME.eq(r.SPECIFIC_SCHEMA))
.leftJoin(pg_a)
.on(pg_c.OID.eq(pg_a.ATTRELID))
.and(c.COLUMN_NAME.eq(pg_a.ATTNAME))
.and(nvl(c.COLUMN_NAME, a.ATTRIBUTE_NAME).eq(pg_a.ATTNAME))
.crossApply(
select(
coalesce(c.DATA_TYPE, r.DATA_TYPE).as(x.DATA_TYPE),
coalesce(c.DATA_TYPE, a.DATA_TYPE, r.DATA_TYPE).as(x.DATA_TYPE),
coalesce(
c.UDT_NAME, r.UDT_NAME,
c.UDT_NAME, a.ATTRIBUTE_UDT_NAME, r.UDT_NAME,
field(select(pg_t.TYPNAME).from(pg_t).where(pg_t.OID.eq(pg_p.PRORETTYPE)))
).as(x.UDT_NAME)
).asTable(x)