Skip to content

Commit c57057b

Browse files
committed
implement stub ops for NullStorage so that operations on Null columns can work
1 parent 16af554 commit c57057b

File tree

6 files changed

+206
-25
lines changed

6 files changed

+206
-25
lines changed

distribution/lib/Standard/Table/0.0.0-dev/src/Column.enso

+1
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ type Column
536536
run_binary_op self fn other new_name
537537
_ ->
538538
run_vectorized_binary_op self Java_Storage.Maps.SUB other
539+
Nothing -> self.const Nothing
539540

540541
## ALIAS multiply, product, times
541542
GROUP Standard.Base.Operators

distribution/lib/Standard/Table/0.0.0-dev/src/Internal/Value_Type_Helpers.enso

+2
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ type Addition_Kind
210210
## PRIVATE
211211
Text_Concat
212212

213+
## Returns the operation kind based on types of the inputs.
214+
If both inputs are Null, it is impossible to tell the kind, so Nothing is returned and the caller may decide what to do.
213215
private resolve_operation_kind arg1:Any arg2:Any operation_name:Text find_operation_kind:(Value_Type -> Any) -> Any | Nothing =
214216
kinds = [arg1, arg2]
215217
. map on_problems=No_Wrap.Value find_operation_kind

std-bits/table/src/main/java/org/enso/table/data/column/operation/cast/StorageConverter.java

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.enso.table.data.column.storage.type.DateType;
1010
import org.enso.table.data.column.storage.type.FloatType;
1111
import org.enso.table.data.column.storage.type.IntegerType;
12+
import org.enso.table.data.column.storage.type.NullType;
1213
import org.enso.table.data.column.storage.type.StorageType;
1314
import org.enso.table.data.column.storage.type.TextType;
1415
import org.enso.table.data.column.storage.type.TimeOfDayType;
@@ -31,6 +32,7 @@ static StorageConverter<?> fromStorageType(StorageType storageType) {
3132
case TimeOfDayType timeOfDayType -> new ToTimeOfDayStorageConverter();
3233
case BigIntegerType bigIntegerType -> new ToBigIntegerConverter();
3334
case BigDecimalType bigDecimalType -> new ToBigDecimalConverter();
35+
case NullType nullType -> throw new IllegalArgumentException("Cannot cast to Null type.");
3436
};
3537
}
3638
}

std-bits/table/src/main/java/org/enso/table/data/column/storage/NullStorage.java

+171-5
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,22 @@
22

33
import java.util.BitSet;
44
import java.util.List;
5+
import org.enso.table.data.column.builder.BoolBuilder;
6+
import org.enso.table.data.column.operation.map.BinaryMapOperation;
57
import org.enso.table.data.column.operation.map.MapOperationProblemAggregator;
8+
import org.enso.table.data.column.operation.map.MapOperationStorage;
69
import org.enso.table.data.column.storage.type.NullType;
710
import org.enso.table.data.column.storage.type.StorageType;
811
import org.enso.table.data.mask.OrderMask;
912
import org.enso.table.data.mask.SliceRange;
13+
import org.enso.table.error.UnexpectedColumnTypeException;
14+
import org.enso.table.error.UnexpectedTypeException;
15+
import org.graalvm.polyglot.Value;
1016

1117
/** A specialized storage that can be used by columns that contain only null values. */
1218
public class NullStorage extends Storage<Void> {
1319
private final int size;
20+
private final MapOperationStorage<Void, NullStorage> ops = buildOps();
1421

1522
public NullStorage(int size) {
1623
this.size = size;
@@ -36,26 +43,66 @@ public Void getItemBoxed(int idx) {
3643
return null;
3744
}
3845

46+
private static MapOperationStorage<Void, NullStorage> buildOps() {
47+
MapOperationStorage<Void, NullStorage> ops = new MapOperationStorage<>();
48+
ops.add(new NullOp(Maps.EQ));
49+
ops.add(new NullOp(Maps.LT));
50+
ops.add(new NullOp(Maps.LTE));
51+
ops.add(new NullOp(Maps.GT));
52+
ops.add(new NullOp(Maps.GTE));
53+
54+
ops.add(new NullOp(Maps.MUL));
55+
ops.add(new NullOp(Maps.ADD));
56+
ops.add(new NullOp(Maps.SUB));
57+
ops.add(new NullOp(Maps.DIV));
58+
ops.add(new NullOp(Maps.MOD));
59+
ops.add(new NullOp(Maps.POWER));
60+
61+
ops.add(new NullAndOp());
62+
ops.add(new NullOrOp());
63+
64+
ops.add(new NullOp(Maps.STARTS_WITH));
65+
ops.add(new NullOp(Maps.ENDS_WITH));
66+
ops.add(new NullOp(Maps.CONTAINS));
67+
ops.add(new NullOp(Maps.LIKE));
68+
ops.add(new NullOp(Maps.TEXT_LEFT));
69+
ops.add(new NullOp(Maps.TEXT_RIGHT));
70+
71+
ops.add(new CoalescingNullOp(Maps.MIN));
72+
ops.add(new CoalescingNullOp(Maps.MAX));
73+
74+
return ops;
75+
}
76+
3977
@Override
4078
public boolean isBinaryOpVectorized(String name) {
41-
return false;
79+
return ops.isSupportedBinary(name);
4280
}
4381

4482
@Override
4583
public Storage<?> runVectorizedBinaryMap(
4684
String name, Object argument, MapOperationProblemAggregator problemAggregator) {
47-
throw new IllegalArgumentException("Operation " + name + " is not vectorized for NullStorage");
85+
return ops.runBinaryMap(name, this, argument, problemAggregator);
4886
}
4987

5088
@Override
5189
public Storage<?> runVectorizedZip(
5290
String name, Storage<?> argument, MapOperationProblemAggregator problemAggregator) {
53-
throw new IllegalArgumentException("Operation " + name + " is not vectorized for NullStorage");
91+
return ops.runZip(name, this, argument, problemAggregator);
5492
}
5593

5694
@Override
57-
public Storage<?> fillMissingFromPrevious(BoolStorage missingIndicator) {
58-
return this;
95+
public boolean isTernaryOpVectorized(String name) {
96+
return ops.isSupportedTernary(name);
97+
}
98+
99+
@Override
100+
public Storage<?> runVectorizedTernaryMap(
101+
String name,
102+
Object argument0,
103+
Object argument1,
104+
MapOperationProblemAggregator problemAggregator) {
105+
return ops.runTernaryMap(name, this, argument0, argument1, problemAggregator);
59106
}
60107

61108
@Override
@@ -82,4 +129,123 @@ public Storage<?> appendNulls(int count) {
82129
public Storage<Void> slice(List<SliceRange> ranges) {
83130
return new NullStorage(SliceRange.totalLength(ranges));
84131
}
132+
133+
@Override
134+
public Storage<?> fillMissingFromPrevious(BoolStorage missingIndicator) {
135+
return this;
136+
}
137+
138+
/** A binary operation that always returns null. */
139+
private static class NullOp extends BinaryMapOperation<Void, NullStorage> {
140+
public NullOp(String name) {
141+
super(name);
142+
}
143+
144+
@Override
145+
public Storage<?> runBinaryMap(
146+
NullStorage storage, Object arg, MapOperationProblemAggregator problemAggregator) {
147+
// We return the same storage as-is, because all lhs arguments are guaranteed to be null.
148+
return storage;
149+
}
150+
151+
@Override
152+
public Storage<?> runZip(
153+
NullStorage storage, Storage<?> arg, MapOperationProblemAggregator problemAggregator) {
154+
// We return the same storage as-is, because all lhs arguments are guaranteed to be null.
155+
return storage;
156+
}
157+
}
158+
159+
/**
160+
* A binary operation that always returns the other argument.
161+
*
162+
* <p>Useful for implementing operations that should return the other argument when the left-hand
163+
* side is null, e.g. min.
164+
*/
165+
private static class CoalescingNullOp extends BinaryMapOperation<Void, NullStorage> {
166+
public CoalescingNullOp(String name) {
167+
super(name);
168+
}
169+
170+
@Override
171+
public Storage<?> runBinaryMap(
172+
NullStorage storage, Object arg, MapOperationProblemAggregator problemAggregator) {
173+
return Storage.fromRepeatedItem(Value.asValue(arg), storage.size(), problemAggregator);
174+
}
175+
176+
@Override
177+
public Storage<?> runZip(
178+
NullStorage storage, Storage<?> arg, MapOperationProblemAggregator problemAggregator) {
179+
return arg;
180+
}
181+
}
182+
183+
private abstract static class BoolAndNullOp extends BinaryMapOperation<Void, NullStorage> {
184+
public BoolAndNullOp(String name) {
185+
super(name);
186+
}
187+
188+
protected abstract Boolean doBool(boolean a);
189+
190+
@Override
191+
public Storage<?> runBinaryMap(
192+
NullStorage storage, Object arg, MapOperationProblemAggregator problemAggregator) {
193+
if (arg == null) {
194+
return new NullStorage(storage.size());
195+
} else if (arg instanceof Boolean b) {
196+
return Storage.fromRepeatedItem(
197+
Value.asValue(doBool(b)), storage.size(), problemAggregator);
198+
} else {
199+
throw new UnexpectedTypeException("Boolean", arg.toString());
200+
}
201+
}
202+
203+
@Override
204+
public Storage<?> runZip(
205+
NullStorage storage, Storage<?> arg, MapOperationProblemAggregator problemAggregator) {
206+
if (arg instanceof BoolStorage boolStorage) {
207+
BoolBuilder builder = new BoolBuilder(storage.size());
208+
for (int i = 0; i < storage.size(); i++) {
209+
if (boolStorage.isNothing(i)) {
210+
builder.appendNulls(1);
211+
} else {
212+
builder.append(doBool(boolStorage.getItem(i)));
213+
}
214+
}
215+
return builder.seal();
216+
} else {
217+
throw new UnexpectedColumnTypeException("Boolean");
218+
}
219+
}
220+
}
221+
222+
private static class NullAndOp extends BoolAndNullOp {
223+
public NullAndOp() {
224+
super(Maps.AND);
225+
}
226+
227+
@Override
228+
protected Boolean doBool(boolean a) {
229+
if (a) {
230+
return null;
231+
} else {
232+
return false;
233+
}
234+
}
235+
}
236+
237+
private static class NullOrOp extends BoolAndNullOp {
238+
public NullOrOp() {
239+
super(Maps.OR);
240+
}
241+
242+
@Override
243+
protected Boolean doBool(boolean a) {
244+
if (a) {
245+
return true;
246+
} else {
247+
return null;
248+
}
249+
}
250+
}
85251
}

std-bits/table/src/main/java/org/enso/table/data/column/storage/Storage.java

+29
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.enso.table.data.column.operation.cast.CastProblemAggregator;
1010
import org.enso.table.data.column.operation.cast.StorageConverter;
1111
import org.enso.table.data.column.operation.map.MapOperationProblemAggregator;
12+
import org.enso.table.data.column.storage.numeric.LongConstantStorage;
1213
import org.enso.table.data.column.storage.numeric.LongStorage;
1314
import org.enso.table.data.column.storage.type.IntegerType;
1415
import org.enso.table.data.column.storage.type.StorageType;
@@ -445,4 +446,32 @@ public final Storage<?> cast(
445446
public Object getItemAsObject(long index) {
446447
return getItemBoxed((int) index);
447448
}
449+
450+
/** Creates a storage containing a single repeated item. */
451+
public static Storage<?> fromRepeatedItem(
452+
Value item, int repeat, ProblemAggregator problemAggregator) {
453+
if (repeat < 0) {
454+
throw new IllegalArgumentException("Repeat count must be non-negative.");
455+
}
456+
457+
Object converted = Polyglot_Utils.convertPolyglotValue(item);
458+
459+
if (converted == null) {
460+
return new NullStorage(repeat);
461+
}
462+
463+
if (converted instanceof Long longValue) {
464+
return new LongConstantStorage(longValue, repeat);
465+
}
466+
467+
StorageType storageType = StorageType.forBoxedItem(converted);
468+
Builder builder = Builder.getForType(storageType, repeat, problemAggregator);
469+
Context context = Context.getCurrent();
470+
for (int i = 0; i < repeat; i++) {
471+
builder.appendNoGrow(converted);
472+
context.safepoint();
473+
}
474+
475+
return builder.seal();
476+
}
448477
}

std-bits/table/src/main/java/org/enso/table/data/table/Column.java

+1-20
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import org.enso.base.polyglot.Polyglot_Utils;
66
import org.enso.table.data.column.builder.Builder;
77
import org.enso.table.data.column.builder.InferredBuilder;
8-
import org.enso.table.data.column.storage.NullStorage;
98
import org.enso.table.data.column.storage.Storage;
109
import org.enso.table.data.column.storage.type.StorageType;
1110
import org.enso.table.data.mask.OrderMask;
@@ -165,25 +164,7 @@ public static Column fromItemsNoDateConversion(
165164
*/
166165
public static Column fromRepeatedItem(
167166
String name, Value item, int repeat, ProblemAggregator problemAggregator) {
168-
if (repeat < 0) {
169-
throw new IllegalArgumentException("Repeat count must be non-negative.");
170-
}
171-
172-
Object converted = Polyglot_Utils.convertPolyglotValue(item);
173-
174-
if (converted == null) {
175-
return new Column(name, new NullStorage(repeat));
176-
}
177-
178-
StorageType storageType = StorageType.forBoxedItem(converted);
179-
Builder builder = Builder.getForType(storageType, repeat, problemAggregator);
180-
Context context = Context.getCurrent();
181-
for (int i = 0; i < repeat; i++) {
182-
builder.appendNoGrow(converted);
183-
context.safepoint();
184-
}
185-
186-
return new Column(name, builder.seal());
167+
return new Column(name, Storage.fromRepeatedItem(item, repeat, problemAggregator));
187168
}
188169

189170
/**

0 commit comments

Comments
 (0)