- 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)
This commit is contained in:
Lukas Eder 2022-09-12 16:54:21 +02:00
parent e25723afa7
commit c2be215b9f
3 changed files with 163 additions and 72 deletions

View File

@ -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),

View File

@ -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<AttributeDefinition> getElements0() throws SQLException {
List<AttributeDefinition> 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;

View File

@ -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 <code>bytea</code> string
@ -335,40 +340,32 @@ public class PostgresUtils {
private static List<String> toPGObjectOrArray(String input, char open, char close) {
List<String> values = new ArrayList<String>();
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.
*/