2

I have a postgres database with some columns that are of type varchar[]. jOOQ and pgjdbc-ng aren't playing along nicely; jOOQ's DefaultBindContext has something along the lines of:

protected final BindContext bindValue0(Object value, Field<?> field) throws SQLException {
    SQLDialect dialect = configuration.dialect();

    // [#650] [#3108] Use the Field's Converter before actually binding any value
    Converter<?, ?> converter = field.getConverter();
    Class<?> type = converter.fromType();
    value = ((Converter) converter).to(value);

    //...

    else if (type.isArray()) {
        switch (dialect) {
            case POSTGRES: {
                stmt.setString(nextIndex(), toPGArrayString((Object[]) value));
                break;
            }

Which sets the statement variable to "{\"value1\", \"value2\", \"etc\"}", which is how you'd specify an array in a query. Later on, pgjdbc-ng has:

public static Object coerceToArray(Format format, Object val, Type type, Class<?> targetType, Map<String, Class<?>> typeMap, PGConnectionImpl connection) throws SQLException {
  if (val == null) {
    return null;
  }
  else if (val instanceof PGArray) {
    return coerceToArray(format, ((PGArray) val).getValue(), type, targetType, typeMap, connection);
  }
  else if (val.getClass().isArray()) {
    return coerceToArray(format, val, 0, Array.getLength(val), type, targetType, typeMap, connection);
  }

  throw createCoercionException(val.getClass(), targetType);
}

Which expects that the value on the statement will be of either type PGArray or an actual array; it fails to coerce the String representation of an array into a String representation of an array. :(

I am trying to write a jOOQ Converter that will convert between String[] and PGArray; ideally, this would mean that jOOQ's DefaultBindContext would leave the converted value well enough alone, and then pgjdbc-ng would be able to handle it correctly.

However, I have been unable to write a jOOQ schema configuration that allows me to do this. I've tried variations on:

<customType>    
    <customType>
        <name>StringArray</name>
        <type>java.lang.String[]</type>
        <converter>my.package.PGStringArrayConverter</converter>
    </customType>
</customTypes>

<forcedTypes>
    <forcedType>
        <name>StringArray</name>
        <types>varchar\[\]</types>
    </forcedType>
</forcedTypes>

Without having any luck; the generated table objects refer to a String[][], and varchar[] doesn't match on anything. Even if I break it down, so that the forcedType matches on any type but with an <expression> that only matches my column, and the Converter's type is java.lang.String, I end up with the java compiler complaining about being unable to cast Object[] to String[].

Is there any light at the end of this tunnel, or should I start looking to migrate my database?

Andrew Rueckert
  • 4,858
  • 1
  • 33
  • 44

1 Answers1

1

So, it turns out the answer is, "kinda."

I was creating my PGStringArrayConverter class backwards; I had created

public abstract class PGArrayConverter implements Converter<String[], PGArray>

What ended up working was:

public abstract class PGArrayConverter implements Converter<Object, String[]> {
    @Override
    public String[] from(final Object databaseObject) {
        if (databaseObject == null) {
            return null;
        }
        if (databaseObject.getClass().isArray()) {
            return (String[]) databaseObject;
        }
        return (String[]) ((PGArray)databaseObject).getValue();
    }

    @Override
    public Object to(final String[] userObject) {
        if (userObject == null) {
            return null;
        }
        return new PGArray(null, null, userObject);
    }

    @Override
    public Class<Object> fromType() {
        return Object.class;
    }

    @Override
    public Class<String[]> toType() {
        return String[].class;
    }
}

from() turned out kind of odd; I was getting arrays of Strings, rather than PGArrays. I don't think the database was aware that it was "supposed to" serialize them into PGArrays.

I also had to modify the generated code, which is slightly obnoxious. My schema.xml file contained:

<customType>
    <name>StringArray</name>
    <type>java.lang.String</type>
    <converter>my.package.PGStringArrayConverter</converter>
</customType>

<forcedType>
    <name>StringArray</name>
    <expression>.*tabular_answer_entry.text.*</expression>
    <types>.*</types>
</forcedType>

And I had to change the generated table files to remove the .getArrayDataType() call on the createField's data type:

public final org.jooq.TableField<my.package.gen.tables.records.TabularAnswerEntryRecord, java.lang.String[]> TEXT = createField("text", org.jooq.impl.DefaultDataType.getDefaultDataType("java.lang.String").getArrayDataType(), this, "", new my.package.PGStringArrayConverter());

to:

public final org.jooq.TableField<my.package.gen.tables.records.TabularAnswerEntryRecord, java.lang.String[]> TEXT = createField("text", org.jooq.impl.DefaultDataType.getDefaultDataType("java.lang.String"), this, "", new my.package.PGStringArrayConverter());

The whole solution feels kind of hacky, and I am going to have to write a monster of a unit test to make sure that this doesn't break when we update any of the relevant packages, but at least I am able to read from and write to my database.

Andrew Rueckert
  • 4,858
  • 1
  • 33
  • 44