diff --git a/src/main/java/org/cojen/tupl/core/TupleKey.java b/src/main/java/org/cojen/tupl/core/TupleKey.java index aacb6295e..b78d47d75 100644 --- a/src/main/java/org/cojen/tupl/core/TupleKey.java +++ b/src/main/java/org/cojen/tupl/core/TupleKey.java @@ -133,7 +133,7 @@ public abstract static class Maker { public abstract TupleKey with(Object[] e0, Object e1); - public abstract TupleKey with(Object[] e0, Object[] e1); + public abstract TupleKey with(Object[] e0); static CallSite indyWith(MethodHandles.Lookup lookup, String name, MethodType mt) { MethodMaker mm = MethodMaker.begin(lookup, name, mt); diff --git a/src/main/java/org/cojen/tupl/table/join/JoinSpec.java b/src/main/java/org/cojen/tupl/table/join/JoinSpec.java index 746c4abed..a498aa908 100644 --- a/src/main/java/org/cojen/tupl/table/join/JoinSpec.java +++ b/src/main/java/org/cojen/tupl/table/join/JoinSpec.java @@ -664,14 +664,18 @@ void appendTo(StringBuilder b) { public static sealed class Column extends Node implements Source { private final Table mTable; private final ColumnInfo mColumn; + private final boolean mDefined; private boolean mNullable; - Column(Table table, ColumnInfo column, boolean nullable) { + Column(Table table, ColumnInfo column, boolean defined, boolean nullable) { mTable = table; mColumn = column; + mDefined = defined; mNullable = nullable; - nullableCheck(); + if (!defined) { + nullableCheck(); + } } @Override @@ -824,8 +828,14 @@ boolean isEmpty() throws IOException { @Override void nullable() { - mNullable = true; - nullableCheck(); + if (!mNullable) { + mNullable = true; + if (mDefined) { + mColumn.typeCode |= ColumnInfo.TYPE_NULLABLE; + } else { + nullableCheck(); + } + } } @Override @@ -864,7 +874,7 @@ public static final class PlannedColumn extends Column implements Cloneable { private long mFilterScore; PlannedColumn(Column column) { - super(column.mTable, column.mColumn, column.mNullable); + super(column.mTable, column.mColumn, column.mDefined, column.mNullable); } @Override @@ -1529,6 +1539,7 @@ private Node parseSource(boolean nullable) throws IOException { String name = mText.substring(start, --mPos); ColumnInfo column; + boolean defined; if (mAllColumns != null) { column = mAllColumns.get(name); @@ -1536,6 +1547,7 @@ private Node parseSource(boolean nullable) throws IOException { mPos = start; throw error("Unknown column"); } + defined = false; } else { if (mNumTables >= mTables.length) { throw new IllegalArgumentException("Not enough tables provided"); @@ -1544,6 +1556,7 @@ private Node parseSource(boolean nullable) throws IOException { column.name = name; column.type = mTables[mNumTables].rowType(); column.typeCode = ColumnInfo.TYPE_REFERENCE; + defined = true; } if (mSelectedColumns.putIfAbsent(name, column) != null) { @@ -1568,7 +1581,7 @@ private Node parseSource(boolean nullable) throws IOException { mNumTables++; - return new Column(table, column, nullable); + return new Column(table, column, defined, nullable); } /** diff --git a/src/main/java/org/cojen/tupl/table/join/JoinTableMaker.java b/src/main/java/org/cojen/tupl/table/join/JoinTableMaker.java index 66cef088a..e1b9d629e 100644 --- a/src/main/java/org/cojen/tupl/table/join/JoinTableMaker.java +++ b/src/main/java/org/cojen/tupl/table/join/JoinTableMaker.java @@ -87,8 +87,12 @@ private static Class makeJoinTypeClass(Map columns) { cm.sourceFile(JoinTableMaker.class.getSimpleName()); for (ColumnInfo ci : columns.values()) { - cm.addMethod(ci.type, ci.name).public_().abstract_() - .addAnnotation(Nullable.class, true); + MethodMaker mm = cm.addMethod(ci.type, ci.name).public_().abstract_(); + + if (ci.isNullable()) { + mm.addAnnotation(Nullable.class, true); + } + cm.addMethod(null, ci.name, ci.type).public_().abstract_(); } @@ -112,16 +116,20 @@ public static JoinTable join(String specStr, Table... tables) { TupleKey cacheKey; { - var names = new String[definedColumns.size()]; - var types = new Class[names.length]; + var pairs = new Object[definedColumns.size() * 2]; int i = 0; for (ColumnInfo column : definedColumns.values()) { - names[i] = column.name; - types[i] = column.type; - i++; + String name = column.name.intern(); + if (column.isNullable()) { + pairs[i++] = name; + pairs[i++] = column.type; + } else { + pairs[i++] = column.type; + pairs[i++] = name; + } } - assert i == names.length; - cacheKey = TupleKey.make.with(names, types, definedColumns); + assert i == pairs.length; + cacheKey = TupleKey.make.with(pairs); } return join(cTypeCache.obtain(cacheKey, definedColumns), spec);