From 37612d24de3c66065a5b64ace8f1e8e0d4d85d83 Mon Sep 17 00:00:00 2001 From: lukaseder Date: Thu, 30 Apr 2015 16:40:55 +0200 Subject: [PATCH] [#4254] Add support for default routine parameters in PostgreSQL --- .../postgres/PostgresRoutineDefinition.java | 8 +- .../java/org/jooq/impl/AbstractRoutine.java | 78 +++++++++++++------ .../java/org/jooq/impl/FunctionTable.java | 7 ++ 3 files changed, 65 insertions(+), 28 deletions(-) diff --git a/jOOQ-meta/src/main/java/org/jooq/util/postgres/PostgresRoutineDefinition.java b/jOOQ-meta/src/main/java/org/jooq/util/postgres/PostgresRoutineDefinition.java index 956567057b..1f432658aa 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/postgres/PostgresRoutineDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/postgres/PostgresRoutineDefinition.java @@ -110,7 +110,8 @@ public class PostgresRoutineDefinition extends AbstractRoutineDefinition { PARAMETERS.NUMERIC_SCALE, PARAMETERS.UDT_NAME, PARAMETERS.ORDINAL_POSITION, - PARAMETERS.PARAMETER_MODE) + PARAMETERS.PARAMETER_MODE, + PARAMETERS.PARAMETER_DEFAULT) .from(PARAMETERS) .where(PARAMETERS.SPECIFIC_SCHEMA.equal(getSchema().getName())) .and(PARAMETERS.SPECIFIC_NAME.equal(specificName)) @@ -127,7 +128,7 @@ public class PostgresRoutineDefinition extends AbstractRoutineDefinition { record.getValue(PARAMETERS.NUMERIC_PRECISION), record.getValue(PARAMETERS.NUMERIC_SCALE), null, - null, + record.getValue(PARAMETERS.PARAMETER_DEFAULT) != null, record.getValue(PARAMETERS.UDT_NAME) ); @@ -135,7 +136,8 @@ public class PostgresRoutineDefinition extends AbstractRoutineDefinition { this, record.getValue(PARAMETERS.PARAMETER_NAME), record.getValue(PARAMETERS.ORDINAL_POSITION), - type + type, + record.getValue(PARAMETERS.PARAMETER_DEFAULT) != null ); addParameter(InOutDefinition.getFromString(inOut), parameter); diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java index 41cf74e5f9..3173738cea 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRoutine.java @@ -47,7 +47,9 @@ import static org.jooq.SQLDialect.FIREBIRD; // ... import static org.jooq.SQLDialect.POSTGRES; // ... +import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.function; +import static org.jooq.impl.DSL.name; import static org.jooq.impl.DSL.table; import static org.jooq.impl.DSL.using; import static org.jooq.impl.DSL.val; @@ -84,6 +86,7 @@ import org.jooq.Record; import org.jooq.RenderContext; import org.jooq.Result; import org.jooq.Routine; +import org.jooq.SQLDialect; import org.jooq.Schema; import org.jooq.UDTField; import org.jooq.exception.ControlFlowSignal; @@ -263,15 +266,24 @@ public abstract class AbstractRoutine extends AbstractQueryPart implements Ro @Override public final int execute() { + SQLDialect family = configuration.family(); + results.clear(); outValues.clear(); + // [#4254] In PostgreSQL, there are only functions, no procedures. Some + // functions cannot be called using a CallableStatement, e.g. those with + // DEFAULT parameters + if (family == POSTGRES) { + return executeSelectFromPOSTGRES(); + } + // Procedures (no return value) are always executed as CallableStatement - if (type == null) { + else if (type == null) { return executeCallableStatement(); } else { - switch (configuration.dialect().family()) { + switch (family) { // [#852] Some RDBMS don't allow for using JDBC procedure escape // syntax for functions. Select functions from DUAL instead @@ -280,7 +292,7 @@ public abstract class AbstractRoutine extends AbstractQueryPart implements Ro // [#692] HSQLDB cannot SELECT f() FROM [...] when f() // returns a cursor. Instead, SELECT * FROM table(f()) works if (SQLDataType.RESULT.equals(type.getSQLDataType())) { - return executeSelectFrom(); + return executeSelectFromHSQLDB(); } // Fall through @@ -306,13 +318,27 @@ public abstract class AbstractRoutine extends AbstractQueryPart implements Ro } } - private final int executeSelectFrom() { + private final int executeSelectFromHSQLDB() { DSLContext create = create(configuration); Result result = create.selectFrom(table(asField())).fetch(); outValues.put(returnParameter, result); return 0; } + private final int executeSelectFromPOSTGRES() { + DSLContext create = create(configuration); + Result result = create.select().from("{0}", asField()).fetch(); + + int i = 0; + + if (returnParameter != null) + outValues.put(returnParameter, returnParameter.getDataType().convert(result.getValue(0, i++))); + for (Parameter p : outParameters) + outValues.put(p, p.getDataType().convert(result.getValue(0, i++))); + + return 0; + } + private final int executeSelect() { final Field field = asField(); outValues.put(returnParameter, create(configuration).select(field).fetchOne(field)); @@ -469,15 +495,8 @@ public abstract class AbstractRoutine extends AbstractQueryPart implements Ro // IN parameters are rendered normally else { - Field value = getInValues().get(parameter); - - // Disambiguate overloaded procedure signatures - if (POSTGRES == context.family() && isOverloaded()) { - value = value.cast(parameter.getType()); - } - context.sql(separator); - toSQLInParam(context, parameter, value); + toSQLInParam(context, parameter, getInValues().get(parameter)); } separator = ", "; @@ -858,9 +877,16 @@ public abstract class AbstractRoutine extends AbstractQueryPart implements Ro */ private static final long serialVersionUID = -5730297947647252624L; + @SuppressWarnings("unchecked") RoutineField() { super(AbstractRoutine.this.getName(), - AbstractRoutine.this.type); + AbstractRoutine.this.type == null + + // [#4254] PostgreSQL may have stored functions that don't + // declare an explicit return type. Those function's return + // type is in fact a RECORD type, consisting of OUT paramterers + ? (DataType) SQLDataType.RESULT + : AbstractRoutine.this.type); } @Override @@ -868,23 +894,25 @@ public abstract class AbstractRoutine extends AbstractQueryPart implements Ro RenderContext local = create(ctx).renderContext(); toSQLQualifiedName(local); - Field[] array = new Field[getInParameters().size()]; + List> fields = new ArrayList>(); + for (Parameter parameter : getInParameters()) { - int i = 0; - for (Parameter p : getInParameters()) { + // [#1183] [#3533] Skip defaulted parameters + if (inValuesDefaulted.contains(parameter)) + continue; // Disambiguate overloaded function signatures - if (POSTGRES == ctx.family() && isOverloaded()) { - array[i] = getInValues().get(p).cast(p.getType()); - } - else { - array[i] = getInValues().get(p); - } - - i++; + if (ctx.family() == POSTGRES) + if (isOverloaded()) + fields.add(field("{0} := {1}", name(parameter.getName()), getInValues().get(parameter).cast(parameter.getType()))); + else + fields.add(field("{0} := {1}", name(parameter.getName()), getInValues().get(parameter))); + else + fields.add(getInValues().get(parameter)); } - Field result = function(local.render(), getDataType(), array); + Field result = function(local.render(), getDataType(), fields.toArray(new Field[fields.size()])); + // [#3592] Decrease SQL -> PL/SQL context switches with Oracle Scalar Subquery Caching if (TRUE.equals(settings(ctx.configuration()).isRenderScalarSubqueriesForStoredFunctions())) { diff --git a/jOOQ/src/main/java/org/jooq/impl/FunctionTable.java b/jOOQ/src/main/java/org/jooq/impl/FunctionTable.java index 81f0619f68..64593f4be1 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FunctionTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/FunctionTable.java @@ -88,6 +88,13 @@ class FunctionTable extends AbstractTable { break; } + // [#4254] This is required to enable using PostgreSQL functions + // with defaulted parameters. + case POSTGRES: { + ctx.visit(function); + break; + } + default: throw new SQLDialectNotSupportedException("FUNCTION TABLE is not supported for " + ctx.configuration().dialect()); }