[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)
This commit is contained in:
parent
e25723afa7
commit
c2be215b9f
@ -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),
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user