diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java index f9ce2ea209..9d8d71ff4b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRecord.java @@ -386,7 +386,7 @@ implements if (index >= 0 && index < values.length) return index; - throw new IllegalArgumentException("No field at index " + index + " in Record type " + fields); + throw indexFail(fields, index); } /** diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldsImpl.java b/jOOQ/src/main/java/org/jooq/impl/FieldsImpl.java index 1ce728fa9d..1a2fd08904 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldsImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldsImpl.java @@ -38,9 +38,11 @@ package org.jooq.impl; +import static org.jooq.impl.DSL.row; import static org.jooq.impl.QueryPartListView.wrap; import static org.jooq.impl.Tools.EMPTY_FIELD; import static org.jooq.impl.Tools.converterOrFail; +import static org.jooq.impl.Tools.indexFail; import static org.jooq.impl.Tools.indexOrFail; import static org.jooq.impl.Tools.map; import static org.jooq.impl.Tools.newRecord; @@ -78,7 +80,14 @@ import org.jooq.tools.JooqLogger; * * @author Lukas Eder */ -final class FieldsImpl extends AbstractQueryPart implements RecordType, Mappable, UTransient { +final class FieldsImpl +extends + AbstractQueryPart +implements + RecordType, + Mappable, + UTransient +{ private static final JooqLogger log = JooqLogger.getLogger(FieldsImpl.class); Field[] fields; @@ -407,7 +416,7 @@ final class FieldsImpl extends AbstractQueryPart implements Re if (index >= 0 && index < fields.length) return index; - throw new IllegalArgumentException("No field at index " + index + " in Record type " + fields); + throw indexFail(this, index); } @Override @@ -660,4 +669,9 @@ final class FieldsImpl extends AbstractQueryPart implements Re public int hashCode() { return Arrays.hashCode(fields); } + + @Override + public String toString() { + return row(fields).toString(); + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java index 1f1bcb47d5..87a986280b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ResultImpl.java @@ -40,6 +40,7 @@ package org.jooq.impl; import static org.jooq.Records.intoList; import static org.jooq.Records.intoResultGroups; +import static org.jooq.impl.Tools.indexFail; import static org.jooq.impl.Tools.indexOrFail; import java.lang.reflect.Array; @@ -1068,7 +1069,7 @@ final class ResultImpl extends AbstractResult implements Re if (index >= 0 && index < fields.size()) return index; - throw new IllegalArgumentException("No field at index " + index + " in Record type " + fields); + throw indexFail(fields, index); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 125b9568db..84ab0e80f8 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -2238,7 +2238,26 @@ final class Tools { } static final IllegalArgumentException indexFail(Fields row, Field field) { - return new IllegalArgumentException("Field (" + field + ") is not contained in Row " + row); + if (lookupNested(row, field)) + return new IllegalArgumentException("Field " + field + " is not contained at the top level of row type containing nested row types. Unnest the nested row type first and access the field from there: " + row); + else + return new IllegalArgumentException("Field " + field + " is not contained in row type " + row); + } + + private static final boolean lookupNested(Fields row, Field field) { + + // [#15085] [#16721] Help users spot the problem if they accidentally nest row types + if (row.field(field) != null) + return true; + + for (Field f : row.fields()) { + if (f instanceof AbstractRowAsField rf) { + if (lookupNested(rf.fields0(), field)) + return true; + } + } + + return false; } static final int indexOrFail(Fields row, Field field) { @@ -2251,7 +2270,7 @@ final class Tools { } static final IllegalArgumentException indexFail(Fields row, String fieldName) { - throw new IllegalArgumentException("Field (" + fieldName + ") is not contained in Row " + row); + return indexFail(row, DSL.name(fieldName)); } static final int indexOrFail(Fields row, String fieldName) { @@ -2264,7 +2283,7 @@ final class Tools { } static final IllegalArgumentException indexFail(Fields row, Name fieldName) { - throw new IllegalArgumentException("Field (" + fieldName + ") is not contained in Row " + row); + return indexFail(row, DSL.field(fieldName)); } static final int indexOrFail(Fields row, Name fieldName) { @@ -2277,7 +2296,23 @@ final class Tools { } static final IllegalArgumentException indexFail(Fields row, int fieldIndex) { - throw new IllegalArgumentException("Field (" + fieldIndex + ") is not contained in Row " + row); + if (fieldIndex < countFlattened(row, 0)) + return new IllegalArgumentException("No field at index " + fieldIndex + " is not contained at the top level of row type containing nested row types. Unnest the nested row types first and access the field from there: " + row); + else + return new IllegalArgumentException("No field at index " + fieldIndex + " in row type " + row); + } + + private static final int countFlattened(Fields row, int count) { + + // [#15085] [#16721] Help users spot the problem if they accidentally nest row types + for (Field f : row.fields()) { + if (f instanceof AbstractRowAsField rf) + count += countFlattened(rf.fields0(), count); + else + count++; + } + + return count; } static final int indexOrFail(Fields row, int fieldIndex) {