From c2be215b9f9a51facfceb59612a104eafc40ed2a Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Mon, 12 Sep 2022 16:54:21 +0200 Subject: [PATCH] [jOOQ/jOOQ#252] More fixes - Replaced int based PostgresUtils state machine by enum based one - Support parsing nested arrays in toPGObjectOrArray() - Handle {{}} case, which isn't supported in PG - Code generation support for multi dimensional arrays in UDTs (Java) --- .../java/org/jooq/codegen/JavaGenerator.java | 50 +++++- .../meta/postgres/PostgresUDTDefinition.java | 31 +++- .../org/jooq/util/postgres/PostgresUtils.java | 154 +++++++++++------- 3 files changed, 163 insertions(+), 72 deletions(-) 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 f298818145..955970f976 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -197,7 +197,6 @@ import org.jooq.meta.jaxb.VisibilityModifier; // ... // ... // ... -import org.jooq.meta.postgres.PostgresDatabase; import org.jooq.meta.postgres.PostgresRoutineDefinition; import org.jooq.tools.JooqLogger; import org.jooq.tools.StopWatch; @@ -2197,7 +2196,7 @@ public class JavaGenerator extends AbstractGenerator { final boolean isArrayOfUDTs = isArrayOfUDTs(t, r); final String udtType = (isUDT || isArray) - ? out.ref(getJavaType(((TypedElementDefinition) column).getType(r), out, Mode.RECORD)) + ? out.ref(getJavaType(t.getType(r), out, Mode.RECORD)) : ""; final String udtArrayElementType = isUDTArray ? out.ref(database.getArray(t.getType(r).getSchema(), t.getType(r).getQualifiedUserType()).getElementType(r).getJavaType(r)) @@ -2266,8 +2265,8 @@ public class JavaGenerator extends AbstractGenerator { getStrategy().getJavaMemberName(column, Mode.POJO)); } else { - if (pojoArgument) - if (isUDTArray) + if (pojoArgument) { + if (isUDTArray) { out.println("%s(value.%s() == null ? null : new %s(value.%s().stream().map(%s::new).collect(%s.toList())));", getStrategy().getJavaSetterName(column, Mode.RECORD), getStrategy().getJavaGetterName(column, Mode.POJO), @@ -2277,17 +2276,47 @@ public class JavaGenerator extends AbstractGenerator { : getStrategy().getJavaGetterName(column, Mode.POJO), udtArrayElementType, Collectors.class); - else if (isArrayOfUDTs) - out.println("%s(value.%s() == null ? null : %s.of(value.%s()).map(%s::new).toArray(%s[]::new));", + } + else if (isArrayOfUDTs) { + final String columnTypeFull = getJavaType(t.getType(resolver(out, Mode.POJO)), out, Mode.POJO); + final String columnType = out.ref(columnTypeFull); + final String brackets = columnType.substring(columnType.indexOf("[]")); + + String mapping = udtArrayElementType + "::new"; + String arrayType = udtArrayElementType + "[]"; + int dimensions = brackets.length() / 2 - 1; + + + for (; dimensions > 0; dimensions--) { + String a = "a" + dimensions; + mapping = a + " -> Stream.of(" + a + ").map(" + mapping + ").toArray(" + arrayType + "::new)"; + arrayType += "[]"; + } + + /* + * + setA3(value.getA3() == null ? null : + Stream.of(value.getA3()) + .map(a1 -> Stream.of(a1) + .map(a2 -> Stream.of(a2) + .map(UMultidimARecord::new) + .toArray(UMultidimARecord[]::new)) + .toArray(UMultidimARecord[][]::new)) + .toArray(UMultidimARecord[][][]::new)); + */ + + out.println("%s(value.%s() == null ? null : %s.of(value.%s()).map(%s).toArray(%s%s::new));", getStrategy().getJavaSetterName(column, Mode.RECORD), getStrategy().getJavaGetterName(column, Mode.POJO), Stream.class, generatePojosAsJavaRecordClasses() ? getStrategy().getJavaMemberName(column, Mode.POJO) : getStrategy().getJavaGetterName(column, Mode.POJO), + mapping, udtArrayElementType, - udtArrayElementType); - else if (isUDT || isArray) + brackets); + } + else if (isUDT || isArray) { out.println("%s(value.%s() == null ? null : new %s(value.%s()));", getStrategy().getJavaSetterName(column, Mode.RECORD), getStrategy().getJavaGetterName(column, Mode.POJO), @@ -2295,12 +2324,15 @@ public class JavaGenerator extends AbstractGenerator { generatePojosAsJavaRecordClasses() ? getStrategy().getJavaMemberName(column, Mode.POJO) : getStrategy().getJavaGetterName(column, Mode.POJO)); - else + } + else { out.println("%s(value.%s());", getStrategy().getJavaSetterName(column, Mode.RECORD), generatePojosAsJavaRecordClasses() ? getStrategy().getJavaMemberName(column, Mode.POJO) : getStrategy().getJavaGetterName(column, Mode.POJO)); + } + } else out.println("%s(%s);", getStrategy().getJavaSetterName(column, Mode.RECORD), diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresUDTDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresUDTDefinition.java index d4838a7cc1..e449c1a8c5 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresUDTDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresUDTDefinition.java @@ -45,6 +45,9 @@ import static org.jooq.impl.DSL.when; 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.DOMAINS; +import static org.jooq.meta.postgres.pg_catalog.Tables.PG_ATTRIBUTE; +import static org.jooq.meta.postgres.pg_catalog.Tables.PG_CLASS; +import static org.jooq.meta.postgres.pg_catalog.Tables.PG_NAMESPACE; import java.sql.SQLException; import java.util.ArrayList; @@ -55,10 +58,15 @@ import org.jooq.Record; import org.jooq.meta.AbstractUDTDefinition; import org.jooq.meta.AttributeDefinition; import org.jooq.meta.DataTypeDefinition; +import org.jooq.meta.Database; import org.jooq.meta.DefaultAttributeDefinition; import org.jooq.meta.DefaultDataTypeDefinition; import org.jooq.meta.RoutineDefinition; import org.jooq.meta.SchemaDefinition; +import org.jooq.meta.postgres.pg_catalog.Tables; +import org.jooq.meta.postgres.pg_catalog.tables.PgAttribute; +import org.jooq.meta.postgres.pg_catalog.tables.PgClass; +import org.jooq.meta.postgres.pg_catalog.tables.PgNamespace; public class PostgresUDTDefinition extends AbstractUDTDefinition { @@ -70,14 +78,19 @@ public class PostgresUDTDefinition extends AbstractUDTDefinition { protected List getElements0() throws SQLException { List result = new ArrayList<>(); + PostgresDatabase db = (PostgresDatabase) getDatabase(); + + PgNamespace n = PG_NAMESPACE.as("n"); + PgClass c = PG_CLASS.as("c"); + PgAttribute a = PG_ATTRIBUTE.as("a"); + for (Record record : create().select( ATTRIBUTES.ATTRIBUTE_NAME, ATTRIBUTES.ORDINAL_POSITION, coalesce( DOMAINS.DATA_TYPE, when(ATTRIBUTES.DATA_TYPE.eq(inline("USER-DEFINED")).and(ATTRIBUTES.ATTRIBUTE_UDT_NAME.eq(inline("geometry"))), inline("geometry")) - .when(ATTRIBUTES.DATA_TYPE.eq(inline("ARRAY")), substring(ATTRIBUTES.ATTRIBUTE_UDT_NAME, inline(2)).concat(inline(" ARRAY"))) - .else_(ATTRIBUTES.DATA_TYPE) + .else_(db.arrayDataType(ATTRIBUTES.DATA_TYPE, ATTRIBUTES.ATTRIBUTE_UDT_NAME, a.ATTNDIMS)) ).as(ATTRIBUTES.DATA_TYPE), coalesce(DOMAINS.CHARACTER_MAXIMUM_LENGTH, ATTRIBUTES.CHARACTER_MAXIMUM_LENGTH).as(ATTRIBUTES.CHARACTER_MAXIMUM_LENGTH), coalesce(DOMAINS.NUMERIC_PRECISION, ATTRIBUTES.NUMERIC_PRECISION).as(ATTRIBUTES.NUMERIC_PRECISION), @@ -85,17 +98,23 @@ public class PostgresUDTDefinition extends AbstractUDTDefinition { ATTRIBUTES.IS_NULLABLE, ATTRIBUTES.ATTRIBUTE_DEFAULT, ATTRIBUTES.ATTRIBUTE_UDT_SCHEMA, - when(ATTRIBUTES.DATA_TYPE.eq(inline("ARRAY")), substring(ATTRIBUTES.ATTRIBUTE_UDT_NAME, inline(2))) - .else_(ATTRIBUTES.ATTRIBUTE_UDT_NAME).as(ATTRIBUTES.ATTRIBUTE_UDT_NAME)) + db.arrayUdtName(ATTRIBUTES.DATA_TYPE, ATTRIBUTES.ATTRIBUTE_UDT_NAME).as(ATTRIBUTES.ATTRIBUTE_UDT_NAME)) .from(ATTRIBUTES) + .join(n) + .on(ATTRIBUTES.UDT_SCHEMA.eq(n.NSPNAME)) + .join(c) + .on(ATTRIBUTES.UDT_NAME.eq(c.RELNAME)) + .and(n.OID.eq(c.RELNAMESPACE)) + .join(a) + .on(ATTRIBUTES.ATTRIBUTE_NAME.eq(a.ATTNAME)) + .and(c.OID.eq(a.ATTRELID)) .leftJoin(DOMAINS) .on(ATTRIBUTES.ATTRIBUTE_UDT_CATALOG.eq(DOMAINS.DOMAIN_CATALOG)) .and(ATTRIBUTES.ATTRIBUTE_UDT_SCHEMA.eq(DOMAINS.DOMAIN_SCHEMA)) .and(ATTRIBUTES.ATTRIBUTE_UDT_NAME.eq(DOMAINS.DOMAIN_NAME)) .where(ATTRIBUTES.UDT_SCHEMA.equal(getSchema().getName())) .and(ATTRIBUTES.UDT_NAME.equal(getName())) - .orderBy(ATTRIBUTES.ORDINAL_POSITION) - .fetch()) { + .orderBy(ATTRIBUTES.ORDINAL_POSITION)) { SchemaDefinition typeSchema = null; diff --git a/jOOQ/src/main/java/org/jooq/util/postgres/PostgresUtils.java b/jOOQ/src/main/java/org/jooq/util/postgres/PostgresUtils.java index 6fa88cd080..1a1443ca14 100644 --- a/jOOQ/src/main/java/org/jooq/util/postgres/PostgresUtils.java +++ b/jOOQ/src/main/java/org/jooq/util/postgres/PostgresUtils.java @@ -40,6 +40,7 @@ package org.jooq.util.postgres; import static java.lang.Integer.toOctalString; import static org.jooq.impl.Internal.converterContext; import static org.jooq.tools.StringUtils.leftPad; +import static org.jooq.util.postgres.PostgresUtils.PGState.*; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -75,15 +76,19 @@ import org.jetbrains.annotations.ApiStatus.Internal; @Internal public class PostgresUtils { - private static final String POSTGRESQL_HEX_STRING_PREFIX = "\\x"; + private static final String POSTGRESQL_HEX_STRING_PREFIX = "\\x"; // PGobject parsing state machine - private static final int PG_OBJECT_INIT = 0; - private static final int PG_OBJECT_BEFORE_VALUE = 1; - private static final int PG_OBJECT_QUOTED_VALUE = 2; - private static final int PG_OBJECT_UNQUOTED_VALUE = 3; - private static final int PG_OBJECT_AFTER_VALUE = 4; - private static final int PG_OBJECT_END = 5; + enum PGState { + PG_OBJECT_INIT, + PG_OBJECT_BEFORE_VALUE, + PG_OBJECT_QUOTED_VALUE, + PG_OBJECT_UNQUOTED_VALUE, + PG_OBJECT_NESTED_VALUE, + PG_OBJECT_NESTED_QUOTED_VALUE, + PG_OBJECT_AFTER_VALUE, + PG_OBJECT_END + } /** * Parse a Postgres-encoded bytea string @@ -335,40 +340,32 @@ public class PostgresUtils { private static List toPGObjectOrArray(String input, char open, char close) { List values = new ArrayList(); int i = 0; - int state = PG_OBJECT_INIT; + PGState state = PG_OBJECT_INIT; + int nestLevel = 0; StringBuilder sb = null; while (i < input.length()) { char c = input.charAt(i); switch (state) { - // Initial state case PG_OBJECT_INIT: - // Consume the opening bracket - if (c == open) { + if (c == open) state = PG_OBJECT_BEFORE_VALUE; - } break; - // Before a new value case PG_OBJECT_BEFORE_VALUE: sb = new StringBuilder(); - // Consume "empty" if (c == ',') { values.add(null); state = PG_OBJECT_BEFORE_VALUE; } - - // Consume "empty" else if (c == close) { values.add(null); state = PG_OBJECT_END; } - - // Consume the opening quote else if (c == '"') { state = PG_OBJECT_QUOTED_VALUE; } @@ -381,7 +378,12 @@ public class PostgresUtils { state = PG_OBJECT_AFTER_VALUE; } - // Consume a character + // [#252] Consume nested array + else if (c == open) { + sb.append(c); + state = PG_OBJECT_NESTED_VALUE; + nestLevel++; + } else { sb.append(c); state = PG_OBJECT_UNQUOTED_VALUE; @@ -389,26 +391,17 @@ public class PostgresUtils { break; - // A "value" is being created case PG_OBJECT_QUOTED_VALUE: - - // Consume a quote if (c == '"') { - - // Consume an escaped quote if (input.charAt(i + 1) == '"') { sb.append(c); i++; } - - // Consume the closing quote else { values.add(sb.toString()); state = PG_OBJECT_AFTER_VALUE; } } - - // Consume a backslash else if (c == '\\') { char n = input.charAt(i + 1); @@ -419,57 +412,92 @@ public class PostgresUtils { } // Consume an "illegal" backslash (?) - else { + else sb.append(c); + } + else + sb.append(c); + + break; + + case PG_OBJECT_UNQUOTED_VALUE: + if (c == close) { + values.add(sb.toString()); + state = PG_OBJECT_END; + } + else if (c == ',') { + values.add(sb.toString()); + state = PG_OBJECT_BEFORE_VALUE; + } + else + sb.append(c); + + break; + + case PG_OBJECT_NESTED_VALUE: + if (c == close) { + nestLevel--; + sb.append(c); + + if (nestLevel == 0) { + values.add(sb.toString()); + state = PG_OBJECT_AFTER_VALUE; } } - - // Consume any other character + else if (c == open) { + nestLevel++; + sb.append(c); + } + else if (c == '"') { + state = PG_OBJECT_NESTED_QUOTED_VALUE; + sb.append(c); + } else { sb.append(c); } break; - // A value is being created - case PG_OBJECT_UNQUOTED_VALUE: - - // Consume the closing bracket - if (c == close) { - values.add(sb.toString()); - state = PG_OBJECT_END; + case PG_OBJECT_NESTED_QUOTED_VALUE: + if (c == '"') { + if (input.charAt(i + 1) == '"') { + sb.append(c); + sb.append(c); + i++; + } + else { + sb.append(c); + state = PG_OBJECT_NESTED_VALUE; + } } + else if (c == '\\') { + char n = input.charAt(i + 1); - // Consume the value separator - else if (c == ',') { - values.add(sb.toString()); - state = PG_OBJECT_BEFORE_VALUE; + // [#10467] Consume an escaped backslash or quote + if (n == '\\' || n == '"') { + sb.append(c); + sb.append(n); + i++; + } + + // Consume an "illegal" backslash (?) + else + sb.append(c); } - - // Consume any other character - else { + else sb.append(c); - } break; - // A value was just added case PG_OBJECT_AFTER_VALUE: - - // Consume the closing bracket - if (c == close) { + if (c == close) state = PG_OBJECT_END; - } - - // Consume the value separator - else if (c == ',') { + else if (c == ',') state = PG_OBJECT_BEFORE_VALUE; - } break; } - // Consume next character i++; } @@ -498,7 +526,10 @@ public class PostgresUtils { // [#252] Multi dimensional array support else if (o instanceof Object[] a) - toPGArrayString0(a, sb); + if (isDeepEmpty(a)) + ; + else + toPGArrayString0(a, sb); else sb.append("\"") .append(StringUtils.replace(StringUtils.replace(toPGString(o), "\\", "\\\\"), "\"", "\\\"")) @@ -511,6 +542,15 @@ public class PostgresUtils { return sb; } + private static boolean isDeepEmpty(Object[] a) { + if (a.length == 0) + return true; + else if (a.length == 1 && a[0] instanceof Object[] b) + return isDeepEmpty(b); + else + return false; + } + /** * Create a PostgreSQL string representation of any object. */