> So the question is whether an empty record is a legal row type for an > aggregation node?
As that comment indicates, we have tried to avoid empty records — that is, a relational expression that produces a row type with zero fields — but as you have just discovered, we have failed to go all the way. Mathematically, it is purer to allow empty records. SQL does not allow them, they crop up naturally in quite a lot of corner cases, especially after trimming fields. Pragmatically, I assumed that quite a lot of code was making the assumption that records were not empty. And that empty records are sufficiently rare that we would never be able to find all of those places via testing. Is it time to decide? If we allow empty records, we should test that all relational operators can handle them. If we ban them, then we should (say) throw whenever someone registers a RelNode that has an empty row type. Julian > On Apr 29, 2021, at 7:25 AM, Konstantin Orlov <[email protected]> wrote: > > Hi all. > > I faced a problem preventing certain queries being planned because > RelFieldTrimmer throws > an ArrayIndexOutOfBoundsException with message "Index -1 out of bounds for > length 0”. > > The problem is here [1]: > > // If they are asking for no fields, we can't give them what they want, > // because zero-column records are illegal. Give them the last field, > // which is unlikely to be a system field. > if (fieldsUsed.isEmpty()) { > fieldsUsed = ImmutableBitSet.range(fieldCount - 1, fieldCount); > } > > In case fieldsUsed.isEmpty we returns last field, but it is currently > possible that fieldCount=0 as well. > > After some investigation I find out that the reason is empty record derived > as row type for Aggregate. > It is possible when an aggregate has an empty group key and no aggregate > calls. > > So the question is whether an empty record is a legal row type for an > aggregation node? > > Below is a reproducer for this problem, just put it at RelFieldTrimmerTest: > > @Test void test() { > class ContextImpl implements Context { > final Object target; > > ContextImpl(Object target) { > this.target = Objects.requireNonNull(target, "target"); > } > > @Override public <T extends Object> @Nullable T unwrap(Class<T> clazz) { > if (clazz.isInstance(target)) { > return clazz.cast(target); > } > return null; > } > } > > // RelBuilder hides problem when simplifyValues=true, hence we need to > disable it > final RelBuilder builder = RelBuilder.create(config() > .context(new > ContextImpl(RelBuilder.Config.DEFAULT.withSimplifyValues(false))).build()); > > final RelNode root = > builder.scan("EMP") > .aggregate(builder.groupKey()) > .filter(builder.literal(false)) > .project(builder.literal(42)) > .build(); > > final RelFieldTrimmer fieldTrimmer = new RelFieldTrimmer(null, builder); > fieldTrimmer.trim(root); // fails with ArrayIndexOutOfBoundsException: > Index -1 out of bounds for length 0 > } > > > [1] > https://github.com/apache/calcite/blob/master/core/src/main/java/org/apache/calcite/sql2rel/RelFieldTrimmer.java#L1197 > > -- > Regards, > Konstantin Orlov > > > >
