[jOOQ/jOOQ#18098] Record::key produces unattached records

This commit is contained in:
Lukas Eder 2025-03-07 09:41:54 +01:00
parent 0a4a0a87c9
commit 7e132ce7cf
16 changed files with 63 additions and 57 deletions

View File

@ -1434,9 +1434,9 @@ abstract class AbstractDMLQuery<R extends Record> extends AbstractRowCountQuery
((Result) getResult()).add(
Tools.newRecord(
true,
originalConfiguration,
AbstractRecord.class,
fields,
originalConfiguration)
fields)
.operate(record -> {
record.values[0] = id;
record.originals[0] = id;

View File

@ -208,7 +208,7 @@ implements
return (T) get(index);
else if (nonReplacingEmbeddable(field))
return (T) Tools
.newRecord(fetched, ((EmbeddableTableField<?, ?>) field).recordType)
.newRecord(fetched, configuration(), ((EmbeddableTableField<?, ?>) field).recordType)
.operate(new TransferRecordState<>(embeddedFields(field)));
else
throw Tools.indexFail(fields, field);
@ -402,7 +402,7 @@ implements
*/
@Override
public Record original() {
return Tools.newRecord(fetched, (Class<AbstractRecord>) getClass(), fields, configuration())
return Tools.newRecord(fetched, configuration(), (Class<AbstractRecord>) getClass(), fields)
.operate(record -> {
for (int i = 0; i < originals.length; i++)
record.values[i] = record.originals[i] = originals[i];
@ -419,7 +419,7 @@ implements
return (T) original(index);
else if (nonReplacingEmbeddable(field))
return (T) Tools
.newRecord(fetched, ((EmbeddableTableField<?, ?>) field).recordType)
.newRecord(fetched, configuration(), ((EmbeddableTableField<?, ?>) field).recordType)
.operate(((AbstractRecord) original()).new TransferRecordState<>(embeddedFields(field)));
else
throw Tools.indexFail(fields, field);
@ -686,7 +686,7 @@ implements
@Override
public final Record into(Field<?>... f) {
return Tools.newRecord(fetched, Record.class, Tools.row0(f), configuration()).operate(new TransferRecordState<Record>(f));
return Tools.newRecord(fetched, configuration(), Record.class, Tools.row0(f)).operate(new TransferRecordState<Record>(f));
}
@ -834,15 +834,15 @@ implements
@Override
public final <R extends Record> R into(Table<R> table) {
return Tools.newRecord(fetched, table, configuration()).operate(new TransferRecordState<>(table.fields()));
return Tools.newRecord(fetched, configuration(), table).operate(new TransferRecordState<>(table.fields()));
}
final <R extends Record> R intoRecord(R record) {
return Tools.newRecord(fetched, () -> record, configuration()).operate(new TransferRecordState<>(null));
return Tools.newRecord(fetched, configuration(), () -> record).operate(new TransferRecordState<>(null));
}
final <R extends Record> R intoRecord(Class<R> type) {
return (R) Tools.newRecord(fetched, type, fields, configuration()).operate(new TransferRecordState<>(null));
return (R) Tools.newRecord(fetched, configuration(), type, fields).operate(new TransferRecordState<>(null));
}
class TransferRecordState<R extends Record> implements ThrowingFunction<R, R, MappingException> {

View File

@ -1342,7 +1342,7 @@ final class CursorImpl<R extends Record> extends AbstractCursor<R> {
);
@SuppressWarnings("unchecked")
private final RecordDelegate<AbstractRecord> recordDelegate = Tools.newRecord(true, (Supplier<AbstractRecord>) factory, ((DefaultExecuteContext) ctx).originalConfiguration());
private final RecordDelegate<AbstractRecord> recordDelegate = Tools.newRecord(true, ((DefaultExecuteContext) ctx).originalConfiguration(), (Supplier<AbstractRecord>) factory);
@Override
public final boolean hasNext() {
@ -1547,7 +1547,7 @@ final class CursorImpl<R extends Record> extends AbstractCursor<R> {
);
value = (T) Tools.newRecord(true, (Class<AbstractRecord>) recordType, (AbstractRow<AbstractRecord>) nested, ((DefaultExecuteContext) ctx).originalConfiguration())
value = (T) Tools.newRecord(true, ((DefaultExecuteContext) ctx).originalConfiguration(), (Class<AbstractRecord>) recordType, (AbstractRow<AbstractRecord>) nested)
.operate(operation);
// [#7100] TODO: Is there a more elegant way to do this?

View File

@ -4534,7 +4534,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
// [#17074] Do this only if requested (e.g. native ROW may be generated despite JSON MULTISET emulation at the top level)
if (skipDegree1 && row.size() == 1 && emulateMultiset(ctx.configuration()) != NestedCollectionEmulation.NATIVE) {
result = new ResultImpl<>(ctx.configuration(), row);
result.add(newRecord(true, (Class<R>) type.getRecordType(), row, ctx.configuration()).operate(r -> {
result.add(newRecord(true, ctx.configuration(), (Class<R>) type.getRecordType(), row).operate(r -> {
DefaultBindingGetResultSetContext<?> c = new DefaultBindingGetResultSetContext<>(ctx.executeContext(), ctx.resultSet(), ctx.index());
r.field(0).getBinding().get((BindingGetResultSetContext) c);
r.fromArray(c.value());
@ -4730,7 +4730,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
if (fields == null && Record.class.isAssignableFrom(type))
fields = Tools.row0(Tools.fields(values.size(), SQLDataType.VARCHAR));
return Tools.newRecord(true, (Class<Record>) type, (AbstractRow<Record>) fields)
return Tools.newRecord(true, originalConfiguration(ctx), (Class<Record>) type, (AbstractRow<Record>) fields)
.operate(r -> {
Row row = r.fieldsRow();
@ -4896,7 +4896,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
Configuration c = originalConfiguration(ctx);
Result<R> result = new ResultImpl<>(c, row);
result.add(newRecord(true, recordType, row, c).operate(r -> {
result.add(newRecord(true, c, recordType, row).operate(r -> {
r.from(asList(s));
r.touched(false);
return r;

View File

@ -4869,7 +4869,7 @@ public class DefaultDSLContext extends AbstractScope implements DSLContext, Seri
@Override
public Record newRecord(Field<?>... fields) {
return Tools.newRecord(false, RecordImplN.class, Tools.row0(fields), configuration()).operate(null);
return Tools.newRecord(false, configuration(), RecordImplN.class, Tools.row0(fields)).operate(null);
}
@Override
@ -4993,17 +4993,17 @@ public class DefaultDSLContext extends AbstractScope implements DSLContext, Seri
@Override
public <R extends UDTRecord<R>> R newRecord(UDT<R> type) {
return Tools.newRecord(false, type, configuration()).operate(null);
return Tools.newRecord(false, configuration(), type).operate(null);
}
@Override
public <R extends Record> R newRecord(Table<R> table) {
return Tools.newRecord(false, table, configuration()).operate(null);
return Tools.newRecord(false, configuration(), table).operate(null);
}
@Override
public <R extends Record> R newRecord(Table<R> table, final Object source) {
return Tools.newRecord(false, table, configuration())
return Tools.newRecord(false, configuration(), table)
.operate(record -> {
record.from(source);
return record;

View File

@ -845,7 +845,7 @@ public class DefaultRecordMapper<R extends Record, E> implements RecordMapper<R,
nestedMappedFields.forEach((prefix, list) -> {
NestedMappingInfo nestedMappingInfo = nestedMappingInfos.get(prefix);
nestedMappingInfo.row = Tools.row0(list);
nestedMappingInfo.recordDelegate = newRecord(true, recordType(nestedMappingInfo.row.size()), nestedMappingInfo.row, configuration);
nestedMappingInfo.recordDelegate = newRecord(true, configuration, recordType(nestedMappingInfo.row.size()), nestedMappingInfo.row);
for (java.lang.reflect.Field member : getMatchingMembers(configuration, type, prefix, true))
nestedMappingInfo.mappers.add(
@ -1128,7 +1128,7 @@ public class DefaultRecordMapper<R extends Record, E> implements RecordMapper<R,
if (nestedMappedFields[i] != null) {
nestedMappingInfo[i].row = row0(nestedMappedFields[i].toArray(EMPTY_FIELD));
nestedMappingInfo[i].recordDelegate = newRecord(true, recordType(nestedMappingInfo[i].row.size()), nestedMappingInfo[i].row, configuration);
nestedMappingInfo[i].recordDelegate = newRecord(true, configuration, recordType(nestedMappingInfo[i].row.size()), nestedMappingInfo[i].row);
nestedMappingInfo[i].mappers.add(
nestedMappingInfo[i].row.fields.mapper(configuration, parameterTypes[propertyIndexes[nestedMappingInfo[i].indexLookup.get(0)]])
);

View File

@ -116,7 +116,7 @@ public class DefaultRecordUnmapper<E, R extends Record> implements RecordUnmappe
}
private final Record newRecord() {
return Tools.newRecord(false, recordType, row, configuration).operate(null);
return Tools.newRecord(false, configuration, recordType, row).operate(null);
}
private static final void setValue(Record record, Object source, java.lang.reflect.Field member, Field<?> field, ConverterContext converterContext)

View File

@ -190,7 +190,7 @@ implements
public final RecordMapper<R, Record> mapper(Field<?>[] f) {
AbstractRow<?> row = Tools.row0(f == null ? EMPTY_FIELD : f);
return r -> newRecord(false, AbstractRecord.class, row, r.configuration()).operate(x -> {
return r -> newRecord(false, r.configuration(), AbstractRecord.class, row).operate(x -> {
for (Field<?> field : row.fields.fields)
Tools.copyValue((AbstractRecord) x, field, r, field);

View File

@ -191,7 +191,7 @@ final class JSONReader<R extends Record> {
)
: null;
result.add(newRecord(true, recordType, actualRow, ctx.configuration()).operate(r -> {
result.add(newRecord(true, ctx.configuration(), recordType, actualRow).operate(r -> {
if (multiset)
r.from(list);
else
@ -218,7 +218,7 @@ final class JSONReader<R extends Record> {
if (record == null)
result.add(null);
else
result.add(newRecord(true, recordType, actualRow, ctx.configuration()).operate(r -> {
result.add(newRecord(true, ctx.configuration(), recordType, actualRow).operate(r -> {
r.from(record);
r.touched(false);
return r;
@ -297,7 +297,7 @@ final class JSONReader<R extends Record> {
List<Object> l = (List<Object>) record.get(i);
patchRecord(ctx, multiset, actualRow, l);
record.set(i, newRecord(true, recordType, actualRow, ctx.configuration()).operate(r -> {
record.set(i, newRecord(true, ctx.configuration(), recordType, actualRow).operate(r -> {
r.from(l);
r.touched(false);
return r;

View File

@ -71,7 +71,7 @@ final class ListHandler<R extends Record> {
Result<R> result = new ResultImpl<>(ctx.configuration(), row);
for (Object o : list)
result.add(newRecord(true, recordType, row, ctx.configuration()).operate(r -> {
result.add(newRecord(true, ctx.configuration(), recordType, row).operate(r -> {
Object[] attributes =
o instanceof Struct s
? s.getAttributes()

View File

@ -168,7 +168,7 @@ final class MultisetDataType<R extends Record> extends DefaultDataType<Result<R>
ResultImpl<R> result = new ResultImpl<>(CONFIG.get(), row);
for (Object record : l)
result.add(newRecord(true, recordType, row, CONFIG.get())
result.add(newRecord(true, CONFIG.get(), recordType, row)
.operate(r -> {
// [#12014] TODO: Fix this and remove workaround

View File

@ -431,7 +431,15 @@ final class R2DBC {
// TODO: This call is duplicated from CursorImpl and related classes.
// Refactor this call to make sure code is re-used, especially when
// ExecuteListener lifecycle management is implemented
RecordDelegate<AbstractRecord> delegate = Tools.newRecord(true, recordFactory(null, (Class<AbstractRecord>) query.getRecordType(), (AbstractRow<AbstractRecord>) Tools.row0(fields)), query.configuration());
RecordDelegate<AbstractRecord> delegate = Tools.newRecord(
true,
query.configuration(),
recordFactory(
null,
(Class<AbstractRecord>) query.getRecordType(),
(AbstractRow<AbstractRecord>) Tools.row0(fields)
)
);
// TODO: What data to pass here?
DefaultBindingGetResultSetContext<?> ctx = new DefaultBindingGetResultSetContext(

View File

@ -172,7 +172,7 @@ final class RecordDataType<R extends Record> extends DefaultDataType<R> {
|| object instanceof List
|| object instanceof Struct
) {
return newRecord(true, getRecordType(), row, CONFIG.get())
return newRecord(true, CONFIG.get(), getRecordType(), row)
.operate(r -> {
// [#12014] TODO: Fix this and remove workaround

View File

@ -1382,55 +1382,42 @@ final class Tools {
/**
* Create a new record
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Class<R> type) {
return newRecord(fetched, type, null);
}
/**
* Create a new record.
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Class<R> type, AbstractRow<R> fields) {
return newRecord(fetched, type, fields, null);
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Configuration configuration, Class<R> type) {
return newRecord(fetched, configuration, type, null);
}
/**
* Create a new {@link Table} or {@link UDT} record.
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, RecordQualifier<R> type) {
return newRecord(fetched, type, null);
}
/**
* Create a new {@link Table} or {@link UDT} record.
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, RecordQualifier<R> type, Configuration configuration) {
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Configuration configuration, RecordQualifier<R> type) {
return newRecord(
fetched,
configuration,
recordFactory(
type,
type.getRecordType(),
(AbstractRow<R>) type.fieldsRow()
),
configuration
)
);
}
/**
* Create a new record.
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Class<? extends R> type, AbstractRow<? extends R> fields, Configuration configuration) {
return newRecord(fetched, recordFactory(null, type, fields), configuration);
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Configuration configuration, Class<? extends R> type, AbstractRow<? extends R> fields) {
return newRecord(fetched, configuration, recordFactory(null, type, fields));
}
/**
* Create a new record.
*/
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Supplier<R> factory, Configuration configuration) {
static final <R extends Record> RecordDelegate<R> newRecord(boolean fetched, Configuration configuration, Supplier<R> factory) {
return new RecordDelegate<>(configuration, factory, fetched);
}
@ -3839,7 +3826,7 @@ final class Tools {
*/
@SuppressWarnings("unchecked")
static final String getMappedUDTName(Scope scope, Class<? extends QualifiedRecord<?>> type) {
return getMappedUDTName(scope, Tools.newRecord(false, (Class<QualifiedRecord<?>>) type).operate(null));
return getMappedUDTName(scope, Tools.newRecord(false, scope.configuration(), (Class<QualifiedRecord<?>>) type).operate(null));
}
/**

View File

@ -100,7 +100,12 @@ import org.jooq.tools.StringUtils;
* @author Lukas Eder
*/
@org.jooq.Internal
public class UpdatableRecordImpl<R extends UpdatableRecord<R>> extends TableRecordImpl<R> implements UpdatableRecord<R> {
public class UpdatableRecordImpl<R extends UpdatableRecord<R>>
extends TableRecordImpl<R>
implements
UpdatableRecord<R>
{
private static final JooqLogger log = JooqLogger.getLogger(UpdatableRecordImpl.class);
private static final Set<SQLDialect> NO_SUPPORT_FOR_UPDATE = SQLDialect.supportedBy(SQLITE);
private static final Set<SQLDialect> NO_SUPPORT_MERGE_RETURNING = SQLDialect.supportedBy(DERBY, IGNITE);
@ -111,7 +116,13 @@ public class UpdatableRecordImpl<R extends UpdatableRecord<R>> extends TableReco
@Override
public Record key() {
AbstractRecord result = Tools.newRecord(fetched, AbstractRecord.class, (AbstractRow<AbstractRecord>) Tools.row0(getPrimaryKey().getFieldsArray())).operate(null);
AbstractRecord result = Tools.newRecord(
fetched,
configuration(),
AbstractRecord.class,
(AbstractRow<AbstractRecord>) Tools.row0(getPrimaryKey().getFieldsArray())
).operate(null);
result.setValues(result.fields.fields.fields, this);
return result;
}
@ -465,7 +476,7 @@ public class UpdatableRecordImpl<R extends UpdatableRecord<R>> extends TableReco
// [#3359] The "fetched" flag must be set to false to enforce INSERT statements on
// subsequent store() calls - when Settings.updatablePrimaryKeys is set.
// R vs Record casting is needed in Java 8 it seems
return (R) Tools.newRecord(false, (Table<Record>) (Table) getTable(), configuration())
return (R) Tools.newRecord(false, configuration(), (Table<Record>) (Table) getTable())
.operate((Record copy) -> {
// Copy all fields. This marks them all as isChanged, which is important

View File

@ -288,7 +288,7 @@ final class XMLHandler<R extends Record> extends DefaultHandler {
s.inRecord--;
initResult();
s.result.add(newRecord(true, s.recordType, s.row, ctx.configuration()).operate(s::into));
s.result.add(newRecord(true, ctx.configuration(), s.recordType, s.row).operate(s::into));
s.values.clear();
s.column = 0;
}
@ -304,7 +304,7 @@ final class XMLHandler<R extends Record> extends DefaultHandler {
Field<?> f = peek.row.field(peek.column);
if ("record".equalsIgnoreCase(qName) && f.getDataType().isRecord()) {
peek.values.add(newRecord(true, s.recordType, s.row, ctx.configuration()).operate(s::into));
peek.values.add(newRecord(true, ctx.configuration(), s.recordType, s.row).operate(s::into));
s = states.pop();
s.inRecord--;
break x;