diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/Builder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/Builder.java index 25ab2e7c24e3..772836e172b9 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/Builder.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/Builder.java @@ -121,7 +121,7 @@ static ColumnStorage makeLocal(ColumnStorage storage) { DoubleBuilder.fromAddress(size, data, validity, type).seal(storage, type); case TextType type -> StringBuilder.fromAddress(size, data, validity, type).seal(storage, type); - case DateType type -> DateBuilder.fromAddress(size, data, validity).seal(storage, type); + case DateType _ -> DateBuilder.fromAddress(size, data, validity).seal(storage); case TimeOfDayType type -> TimeOfDayBuilder.fromAddress(size, data, validity).seal(storage, type); default -> storage; diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/DateBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/DateBuilder.java index aa377897ecfa..96a42dbb68f1 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/DateBuilder.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/DateBuilder.java @@ -1,47 +1,56 @@ package org.enso.table.data.column.builder; import java.lang.foreign.MemorySegment; +import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.IntBuffer; import java.time.LocalDate; -import java.util.BitSet; import java.util.Objects; import org.enso.table.data.column.storage.ColumnStorage; +import org.enso.table.data.column.storage.DateStorage; import org.enso.table.data.column.storage.Storage; -import org.enso.table.data.column.storage.TypedStorage; import org.enso.table.data.column.storage.type.DateTimeType; import org.enso.table.data.column.storage.type.DateType; import org.enso.table.data.column.storage.type.StorageType; import org.enso.table.error.ValueTypeMismatchException; /** A builder for LocalDate columns. */ -final class DateBuilder extends TypedBuilder { +final class DateBuilder extends ValidityBuilder + implements BuilderForType, BuilderWithRetyping { private final boolean allowDateToDateTimeConversion; + private IntBuffer data; DateBuilder(int size, boolean allowDateToDateTimeConversion) { - super(DateType.INSTANCE, new LocalDate[size]); + this(size, 0, 0, allowDateToDateTimeConversion); + } + + private DateBuilder(int size, long data, long validity, boolean allowDateToDateTimeConversion) { + super(size, validity); + this.data = allocBuffer(size, data); this.allowDateToDateTimeConversion = allowDateToDateTimeConversion; } - static DateBuilder fromAddress(int size, long data, long validity) { - var validityBuffer = - MemorySegment.ofAddress(validity).reinterpret((size + 7) / 8).asByteBuffer(); - var bits = BitSet.valueOf(validityBuffer); - var buf = - MemorySegment.ofAddress(data) - .reinterpret(Integer.BYTES * size) - .asByteBuffer() - .order(ByteOrder.LITTLE_ENDIAN); - - var b = new DateBuilder(size, false); - for (var i = 0; i < size; i++) { - var day = buf.getInt(); - if (bits.get(i)) { - b.append(LocalDate.ofEpochDay(day)); - } else { - b.appendNulls(1); - } + private static IntBuffer allocBuffer(int initialSize, long data) { + var wholeDataSize = Long.BYTES * initialSize; + ByteBuffer buf; + if (data == 0L) { + buf = ByteBuffer.allocateDirect(wholeDataSize).order(ByteOrder.LITTLE_ENDIAN); + } else { + var seg = MemorySegment.ofAddress(data).reinterpret(wholeDataSize); + buf = seg.asByteBuffer().order(ByteOrder.LITTLE_ENDIAN); } - return b; + assert buf.capacity() == wholeDataSize; + assert buf.order() == ByteOrder.LITTLE_ENDIAN; + return buf.asIntBuffer(); + } + + static DateBuilder fromAddress(int size, long data, long validity) { + return new DateBuilder(size, data, validity, false); + } + + @Override + public boolean accepts(Object o) { + return o instanceof LocalDate; } @Override @@ -51,7 +60,9 @@ public DateBuilder append(Object o) { appendNulls(1); } else { try { - data[currentSize++] = (LocalDate) o; + var local = (LocalDate) o; + this.setValid(currentSize); + data.put(currentSize++, Math.toIntExact(local.toEpochDay())); } catch (ClassCastException e) { throw new ValueTypeMismatchException(getType(), o); } @@ -60,36 +71,77 @@ public DateBuilder append(Object o) { } @Override - public boolean accepts(Object o) { - return o instanceof LocalDate; + public DateBuilder appendNulls(int count) { + doAppendNulls(count); + return this; } @Override - protected ColumnStorage doSeal() { - return seal(null, DateType.INSTANCE); - } - - final Storage seal(ColumnStorage otherStorage, DateType type) { - return new TypedStorage<>(type, data, otherStorage); + public void appendBulkStorage(ColumnStorage storage) { + var size = storage.getSize(); + for (var i = 0L; i < size; i++) { + var item = storage.getItemBoxed(i); + append(item); + } } @Override public boolean canRetypeTo(StorageType type) { - if (allowDateToDateTimeConversion && Objects.equals(type, DateTimeType.INSTANCE)) { - return true; - } - return super.canRetypeTo(type); + return allowDateToDateTimeConversion && Objects.equals(type, DateTimeType.INSTANCE); } @Override public Builder retypeTo(StorageType type) { if (allowDateToDateTimeConversion && Objects.equals(type, DateTimeType.INSTANCE)) { - var res = new DateTimeBuilder(data.length, true); + var res = new DateTimeBuilder(data.capacity(), true); for (int i = 0; i < currentSize; i++) { - res.append(data[i]); + res.append(getData(i)); } return res; + } else { + throw new UnsupportedOperationException(); } - return super.retypeTo(type); + } + + @Override + protected int getDataSize() { + return this.data.capacity(); + } + + @Override + protected void resize(int desiredCapacity) { + var newData = allocBuffer(desiredCapacity, 0); + int toCopy = Math.min(currentSize, data.capacity()); + newData.put(0, this.data, 0, toCopy); + this.data = newData; + } + + @Override + public ColumnStorage seal() { + return seal(null); + } + + final Storage seal(ColumnStorage otherStorage) { + ensureFreeSpaceFor(0); + var buf = data.asReadOnlyBuffer().position(0).limit(currentSize); + var validity = this.validityMap(); + + return new DateStorage(buf, validity, otherStorage); + } + + @Override + public StorageType getType() { + return DateType.INSTANCE; + } + + @Override + public void copyDataTo(Object[] items) { + for (var i = 0; i < items.length && i < currentSize; i++) { + items[i] = getData(i); + } + } + + private final LocalDate getData(int i) { + return LocalDate.ofEpochDay(data.get(i)); } } diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/DoubleBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/DoubleBuilder.java index 8abc0ac87829..4ce54c099cf9 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/DoubleBuilder.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/DoubleBuilder.java @@ -19,7 +19,7 @@ import org.enso.table.problems.ProblemAggregator; /** A builder for floating point columns. */ -sealed class DoubleBuilder extends NumericBuilder implements BuilderForDouble +sealed class DoubleBuilder extends ValidityBuilder implements BuilderForDouble permits InferredDoubleBuilder { protected final PrecisionLossAggregator precisionLossAggregator; private DoubleBuffer data; diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/LongBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/LongBuilder.java index a1c3a7b6cae2..13c92035a8c7 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/LongBuilder.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/LongBuilder.java @@ -19,7 +19,7 @@ import org.enso.table.problems.ProblemAggregator; /** A builder for integer columns. */ -sealed class LongBuilder extends NumericBuilder implements BuilderForLong, BuilderWithRetyping +sealed class LongBuilder extends ValidityBuilder implements BuilderForLong, BuilderWithRetyping permits BoundCheckedIntegerBuilder { protected final ProblemAggregator problemAggregator; private LongBuffer data; diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/builder/NumericBuilder.java b/std-bits/table/src/main/java/org/enso/table/data/column/builder/ValidityBuilder.java similarity index 95% rename from std-bits/table/src/main/java/org/enso/table/data/column/builder/NumericBuilder.java rename to std-bits/table/src/main/java/org/enso/table/data/column/builder/ValidityBuilder.java index da9fb82ec47d..ee51c4e397df 100644 --- a/std-bits/table/src/main/java/org/enso/table/data/column/builder/NumericBuilder.java +++ b/std-bits/table/src/main/java/org/enso/table/data/column/builder/ValidityBuilder.java @@ -5,7 +5,8 @@ import org.enso.table.util.ImmutableBitSet; /** A common base for builders with lazily initialized validity bitmap. */ -abstract sealed class NumericBuilder implements Builder permits DoubleBuilder, LongBuilder { +abstract sealed class ValidityBuilder implements Builder + permits DoubleBuilder, LongBuilder, DateBuilder { private BitSet validityMap; int currentSize; @@ -15,7 +16,7 @@ abstract sealed class NumericBuilder implements Builder permits DoubleBuilder, L * @param size the size of buffer to allocate * @param validity address of validity bitmap to read or {@code 0} to assume all data are valid */ - protected NumericBuilder(int size, long validity) { + protected ValidityBuilder(int size, long validity) { if (validity != 0L) { var seg = MemorySegment.ofAddress(validity).reinterpret((size + 7) / 8); var valid = seg.asByteBuffer(); diff --git a/std-bits/table/src/main/java/org/enso/table/data/column/storage/DateStorage.java b/std-bits/table/src/main/java/org/enso/table/data/column/storage/DateStorage.java new file mode 100644 index 000000000000..58cee502a5f8 --- /dev/null +++ b/std-bits/table/src/main/java/org/enso/table/data/column/storage/DateStorage.java @@ -0,0 +1,57 @@ +package org.enso.table.data.column.storage; + +import java.lang.foreign.MemorySegment; +import java.nio.IntBuffer; +import java.time.LocalDate; +import org.enso.table.data.column.storage.type.DateType; +import org.enso.table.util.ImmutableBitSet; + +/** A column containing local dates */ +public final class DateStorage extends Storage { + + private final IntBuffer data; + private final ImmutableBitSet validityMap; + + /** original proxy storage to keep from being garbage collected */ + private final ColumnStorage proxy; + + /** + * @param data the underlying data + * @param validityMap a bit set denoting at index {@code i} whether there is a real value at that + * index. + * @param otherStorage reference to proxy storage to prevent it from being GCed while this storage + * is used + */ + public DateStorage(IntBuffer data, ImmutableBitSet validityMap, ColumnStorage otherStorage) { + super(DateType.INSTANCE); + this.data = data; + this.validityMap = validityMap; + this.proxy = otherStorage; + } + + @Override + public long getSize() { + return data.limit(); + } + + @Override + public LocalDate getItemBoxed(long index) { + var at = Math.toIntExact(index); + if (validityMap.get(at)) { + var local = data.get(at); + return LocalDate.ofEpochDay(local); + } else { + return null; + } + } + + @Override + public long addressOfData() { + return MemorySegment.ofBuffer(data).address(); + } + + @Override + public long addressOfValidity() { + return MemorySegment.ofBuffer(validityMap.rawData()).address(); + } +}