Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ static <T> ColumnStorage<T> makeLocal(ColumnStorage<T> 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 type -> DateBuilder.fromAddress(size, data, validity).seal(storage);
case TimeOfDayType type ->
TimeOfDayBuilder.fromAddress(size, data, validity).seal(storage, type);
default -> storage;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<LocalDate> {
final class DateBuilder extends ValidityBuilder
implements BuilderForType<LocalDate>, 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
Expand All @@ -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);
}
Expand All @@ -60,36 +71,81 @@ 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<LocalDate> doSeal() {
return seal(null, DateType.INSTANCE);
}

final Storage<LocalDate> 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;
} else {
return false;
}
return super.canRetypeTo(type);
}

@Override
public Builder retypeTo(StorageType<?> type) {
if (allowDateToDateTimeConversion && Objects.equals(type, DateTimeType.INSTANCE)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit but would invert the if and throw with the body not surrounded anymore

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

body not surrounded anymore

  • that's a bit less FP style
  • but I often prefer it as well

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<LocalDate> seal() {
return seal(null);
}

final Storage<LocalDate> 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<LocalDate> 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<LocalDate> {

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();
}
}
Loading