[jOOQ/jOOQ#11829] Add DataType.getRow() to track nested ROW types and allow access to their Field<?>[]

This commit is contained in:
Lukas Eder 2021-05-03 13:07:57 +02:00
parent ea80188d20
commit f2580592dd
9 changed files with 212 additions and 37 deletions

View File

@ -75,6 +75,7 @@ import java.util.function.Function;
import org.jooq.Converters.UnknownType;
import org.jooq.exception.DataTypeException;
import org.jooq.impl.DSL;
import org.jooq.impl.SQLDataType;
import org.jooq.tools.Convert;
import org.jooq.types.DayToSecond;
@ -148,6 +149,13 @@ public interface DataType<T> extends Named {
@Nullable
Domain<T> getDomain();
/**
* Get the nested record's {@link Row} definition, if this is a
* {@link #isRecord()}, or <code>NULL</code> otherwise.
*/
@Nullable
Row getRow();
/**
* Retrieve the Java type associated with ARRAYs of this data type.
*/
@ -889,6 +897,15 @@ public interface DataType<T> extends Named {
*/
boolean isUDT();
/**
* Whether this data type is a nested record type.
* <p>
* This is true for anonymous, structural nested record types constructed
* with {@link DSL#row(SelectField...)} or for nominal nested record types,
* such as {@link #isUDT()} or {@link #isEmbeddable()}.
*/
boolean isRecord();
/**
* Whether this data type is an enum type.
*/

View File

@ -85,13 +85,17 @@ import org.jooq.Name;
import org.jooq.Nullability;
// ...
import org.jooq.QualifiedRecord;
import org.jooq.RecordType;
import org.jooq.Result;
import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.XML;
import org.jooq.tools.Convert;
import org.jooq.types.Interval;
import org.jooq.types.UNumber;
import org.jetbrains.annotations.Nullable;
/**
* @author Lukas Eder
*/
@ -478,6 +482,11 @@ abstract class AbstractDataType<T> extends AbstractNamed implements DataType<T>
return null;
}
@Override
public /* non-final */ Row getRow() {
return null;
}
@ -646,6 +655,11 @@ abstract class AbstractDataType<T> extends AbstractNamed implements DataType<T>
return QualifiedRecord.class.isAssignableFrom(tType0());
}
@Override
public final boolean isRecord() {
return Record.class.isAssignableFrom(tType0());
}
@Override
public final boolean isEnum() {
return EnumType.class.isAssignableFrom(tType0());
@ -676,6 +690,7 @@ abstract class AbstractDataType<T> extends AbstractNamed implements DataType<T>
abstract String castTypeSuffix0();
abstract String castTypeName0();
abstract Class<?> tType0();
abstract Class<T> uType0();
abstract Integer precision0();
abstract Integer scale0();
abstract Integer length0();

View File

@ -51,7 +51,7 @@ import org.jooq.Nullability;
*/
final class ArrayDataType<T> extends DefaultDataType<T[]> {
final DataType<T> elementType;
final DataType<T> elementType;
public ArrayDataType(DataType<T> elementType) {
super(null, elementType.getArrayType(), elementType.getTypeName(), elementType.getCastTypeName());
@ -63,7 +63,7 @@ final class ArrayDataType<T> extends DefaultDataType<T[]> {
* [#3225] Performant constructor for creating derived types.
*/
ArrayDataType(
DefaultDataType<T[]> t,
AbstractDataType<T[]> t,
DataType<T> elementType,
Integer precision,
Integer scale,
@ -94,7 +94,7 @@ final class ArrayDataType<T> extends DefaultDataType<T[]> {
) {
return new ArrayDataType<>(
this,
(DefaultDataType<T>) elementType,
(AbstractDataType<T>) elementType,
newPrecision,
newScale,
newLength,

View File

@ -45,6 +45,8 @@ import org.jooq.Converter;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Nullability;
import org.jooq.RecordType;
import org.jooq.Row;
import org.jooq.SQLDialect;
/**
@ -55,8 +57,8 @@ import org.jooq.SQLDialect;
@SuppressWarnings({ "rawtypes", "unchecked" })
final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
private final AbstractDataTypeX<T> delegate;
private final Binding<? super T, U> binding;
final AbstractDataTypeX<T> delegate;
final Binding<? super T, U> binding;
ConvertedDataType(AbstractDataTypeX<T> delegate, Binding<? super T, U> binding) {
super(delegate.getQualifiedName(), delegate.getCommentPart());
@ -92,6 +94,11 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
).asConvertedDataType(binding);
}
@Override
public final Row getRow() {
return delegate.getRow();
}
@Override
public final DataType<U> getSQLDataType() {
return (DataType<U>) delegate.getSQLDataType();
@ -167,6 +174,11 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
return delegate.tType0();
}
@Override
final Class<U> uType0() {
return binding.converter().toType();
}
@Override
final Integer precision0() {
return delegate.precision0();

View File

@ -258,6 +258,11 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
return type.tType0();
}
@Override
final Class<T> uType0() {
return type.uType0();
}
@Override
final Integer precision0() {
return defaultIfNull(overridePrecision, type.precision0());

View File

@ -232,6 +232,8 @@ import org.jooq.types.YearToMonth;
import org.jooq.types.YearToSecond;
import org.jooq.util.postgres.PostgresUtils;
import org.jetbrains.annotations.Nullable;
// ...
// ...
@ -3430,12 +3432,9 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
render.sql("::").visit(((TableRecord<?>) value).getTable().getQualifiedName());
}
private static final <T> T pgFromString(Class<T> type, String string) {
return pgFromString(Converters.identity(type), string);
}
@SuppressWarnings("unchecked")
private static final <T> T pgFromString(Converter<?, T> converter, String string) {
private static final <T> T pgFromString(Field<T> field, String string) {
Converter<?, T> converter = field.getConverter();
Class<T> type = Reflect.wrapper(converter.toType());
if (string == null)
@ -3495,7 +3494,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
else if (type == UUID.class)
return (T) UUID.fromString(string);
else if (type.isArray())
return (T) pgNewArray(type, string);
return (T) pgNewArray(field, type, string);
@ -3506,7 +3505,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
// [#11812] UDTRecords/TableRecords or InternalRecords that don't have an explicit converter
&& (!InternalRecord.class.isAssignableFrom(type) || type == converter.fromType()))
return (T) pgNewRecord(type, null, string);
return (T) pgNewRecord(type, (AbstractRow<?>) field.getDataType().getRow(), string);
else if (type == Object.class)
return (T) string;
@ -3514,7 +3513,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
// which would cause a StackOverflowError, here!
else if (type != converter.fromType()) {
Converter<Object, T> c = (Converter<Object, T>) converter;
return c.from(pgFromString(c.fromType(), string));
return c.from(pgFromString(field("converted_field", ((ConvertedDataType<?, ?>) field.getDataType()).delegate), string));
}
throw new UnsupportedOperationException("Class " + type + " is not supported");
@ -3532,7 +3531,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
* @return The converted {@link UDTRecord}
*/
@SuppressWarnings("unchecked")
static final Record pgNewRecord(Class<?> type, AbstractRow<Record> fields, final Object object) {
static final Record pgNewRecord(Class<?> type, AbstractRow<?> fields, final Object object) {
if (object == null)
return null;
@ -3548,9 +3547,9 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
// - Temporal data
// - Everything else: VARCHAR
if (fields == null && Record.class.isAssignableFrom(type))
fields = (AbstractRow<Record>) Tools.row0(Tools.fields(values.size(), SQLDataType.VARCHAR));
fields = Tools.row0(Tools.fields(values.size(), SQLDataType.VARCHAR));
return Tools.newRecord(true, (Class<Record>) type, fields)
return Tools.newRecord(true, (Class<Record>) type, (AbstractRow<Record>) fields)
.operate(record -> {
Row row = record.fieldsRow();
@ -3562,7 +3561,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
}
private static final <T> void pgSetValue(Record record, Field<T> field, String value) {
record.set(field, pgFromString(field.getConverter(), value));
record.set(field, pgFromString(field, value));
}
/**
@ -3574,21 +3573,24 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
* @param string A String representation of an array
* @return The converted array
*/
private static final Object[] pgNewArray(Class<?> type, String string) {
private static final Object[] pgNewArray(Field<?> field, Class<?> type, String string) {
if (string == null)
return null;
try {
Class<?> component = type.getComponentType();
return Tools.map(
toPGArray(string),
v -> pgFromString(component, v),
size -> (Object[]) java.lang.reflect.Array.newInstance(component, size)
v -> pgFromString(field("array_element", field.getDataType().getArrayComponentDataType()), v),
size -> (Object[]) java.lang.reflect.Array.newInstance(type.getComponentType(), size)
);
}
catch (Exception e) {
throw new DataTypeException("Error while creating array", e);
// [#11823]
if (type.getComponentType().getSimpleName().equals("UnknownType"))
throw new DataTypeException("Error while creating array for UnknownType. Please provide an explicit Class<U> type to your converter, see https://github.com/jOOQ/jOOQ/issues/11823", e);
else
throw new DataTypeException("Error while creating array", e);
}
}
}

View File

@ -360,7 +360,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
* [#3225] Performant constructor for creating derived types.
*/
DefaultDataType(
DefaultDataType<T> t,
AbstractDataType<T> t,
Integer precision,
Integer scale,
Integer length,
@ -372,14 +372,14 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
) {
super(t.getQualifiedName(), NO_COMMENT);
this.dialect = t.dialect;
this.sqlDataType = t.sqlDataType;
this.uType = t.uType;
this.tType = t.tType;
this.typeName = t.typeName;
this.castTypeName = t.castTypeName;
this.castTypePrefix = t.castTypePrefix;
this.castTypeSuffix = t.castTypeSuffix;
this.dialect = t.getDialect();
this.sqlDataType = t.getSQLDataType();
this.uType = t.uType0();
this.tType = t.tType0();
this.typeName = t.typeName0();
this.castTypeName = t.castTypeName0();
this.castTypePrefix = t.castTypePrefix0();
this.castTypeSuffix = t.castTypeSuffix0();
this.nullability = nullability;
this.collation = collation;
@ -392,9 +392,9 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
// [#10362] User bindings and/or converters need to be retained
this.binding =
t.binding instanceof org.jooq.impl.DefaultBinding.AbstractBinding
? binding(this, (Converter<T, T>) t.binding.converter())
: t.binding;
t.getBinding() instanceof org.jooq.impl.DefaultBinding.AbstractBinding
? binding(this, (Converter<T, T>) t.getBinding().converter())
: t.getBinding();
}
private static final Integer integerPrecision(Class<?> type, Integer precision) {
@ -528,6 +528,11 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
return tType;
}
@Override
final Class<T> uType0() {
return uType;
}
@Override
public final SQLDialect getDialect() {
return dialect;

View File

@ -0,0 +1,120 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import static org.jooq.impl.Tools.recordType;
import org.jooq.CharacterSet;
import org.jooq.Collation;
import org.jooq.Field;
import org.jooq.Nullability;
import org.jooq.Record;
import org.jooq.RecordType;
import org.jooq.Row;
import org.jetbrains.annotations.Nullable;
/**
* A wrapper for anonymous array data types
*
* @author Lukas Eder
*/
final class RecordDataType<R extends Record> extends DefaultDataType<R> {
final Row row;
@SuppressWarnings("unchecked")
public RecordDataType(Row row) {
// [#11829] TODO: Implement this correctly for UDTRecord, TableRecord, and EmbeddableRecord
super(null, (Class<R>) recordType(row.size()), "record", "record");
this.row = row;
}
/**
* [#3225] Performant constructor for creating derived types.
*/
RecordDataType(
DefaultDataType<R> t,
Row row,
Integer precision,
Integer scale,
Integer length,
Nullability nullability,
Collation collation,
CharacterSet characterSet,
boolean identity,
Field<R> defaultValue
) {
super(t, precision, scale, length, nullability, collation, characterSet, identity, defaultValue);
this.row = row;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
DefaultDataType<R> construct(
Integer newPrecision,
Integer newScale,
Integer newLength,
Nullability
newNullability,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
Field<R> newDefaultValue
) {
return new RecordDataType<>(
this,
row,
newPrecision,
newScale,
newLength,
newNullability,
newCollation,
newCharacterSet,
newIdentity,
(Field) newDefaultValue
);
}
@Override
public final Row getRow() {
return row;
}
}

View File

@ -76,7 +76,6 @@ import static org.jooq.impl.Tools.BooleanDataKey.DATA_LIST_ALREADY_INDENTED;
import java.util.Set;
import org.jooq.Context;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Name;
import org.jooq.Record;
@ -99,7 +98,7 @@ final class RowField<ROW extends Row, REC extends Record> extends AbstractField<
@SuppressWarnings({ "unchecked", "rawtypes" })
RowField(final ROW row, Name as) {
super(as, (DataType) SQLDataType.RECORD, CommentImpl.NO_COMMENT, binding(fromNullable(
super(as, new RecordDataType<>(row), CommentImpl.NO_COMMENT, binding(fromNullable(
Object.class,
(Class<REC>) Tools.recordType(row.size()),