Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -37,11 +37,14 @@
import org.apache.spark.sql.types.LongType;
import org.apache.spark.sql.types.ShortType;
import org.apache.spark.sql.types.StringType;
import org.apache.spark.sql.types.TimestampNTZType;
import org.apache.spark.sql.types.TimestampType;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

import scala.collection.JavaConverters;
import scala.collection.Seq;
Expand Down Expand Up @@ -110,6 +113,18 @@ private UserDefinedFunction intToOrderedLongUDF() {
return udf;
}

private UserDefinedFunction timestampNtzToLongUDF() {
UserDefinedFunction udf =
functions
.udf(
(LocalDateTime value) ->
value == null ? null : value.toEpochSecond(ZoneOffset.UTC),
DataTypes.LongType)
.withName("TIMESTAMP_NTZ_LONG");

return udf;
}

private UserDefinedFunction longToOrderedLongUDF() {
UserDefinedFunction udf =
functions
Expand Down Expand Up @@ -227,6 +242,8 @@ public Column sortedLexicographically(Column column, DataType type) {
return booleanToOrderedLongUDF().apply(column);
} else if (type instanceof TimestampType) {
return longToOrderedLongUDF().apply(column.cast(DataTypes.LongType));
} else if (type instanceof TimestampNTZType) {
return longToOrderedLongUDF().apply(timestampNtzToLongUDF().apply(column));
} else if (type instanceof DecimalType) {
return longToOrderedLongUDF().apply(column.cast(DataTypes.LongType));
} else if (type instanceof DateType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@
import org.apache.spark.sql.types.LongType;
import org.apache.spark.sql.types.ShortType;
import org.apache.spark.sql.types.StringType;
import org.apache.spark.sql.types.TimestampNTZType;
import org.apache.spark.sql.types.TimestampType;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

import scala.collection.JavaConverters;
import scala.collection.Seq;
Expand Down Expand Up @@ -166,6 +169,18 @@ private UserDefinedFunction intToOrderedBytesUDF() {
return udf;
}

private UserDefinedFunction timestampNtzToLongUDF() {
UserDefinedFunction udf =
functions
.udf(
(LocalDateTime value) ->
value == null ? null : value.toEpochSecond(ZoneOffset.UTC),
DataTypes.LongType)
.withName("TIMESTAMP_NTZ_LONG");

return udf;
}

private UserDefinedFunction longToOrderedBytesUDF() {
int position = inputCol;
UserDefinedFunction udf =
Expand Down Expand Up @@ -347,6 +362,8 @@ public Column sortedLexicographically(Column column, DataType type) {
return booleanToOrderedBytesUDF().apply(column);
} else if (type instanceof TimestampType) {
return longToOrderedBytesUDF().apply(column.cast(DataTypes.LongType));
} else if (type instanceof TimestampNTZType) {
return longToOrderedBytesUDF().apply(timestampNtzToLongUDF().apply(column));
} else if (type instanceof DateType) {
return longToOrderedBytesUDF().apply(column.cast(DataTypes.LongType));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.apache.spark.sql.types.StructType;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -85,4 +87,59 @@ void testBooleanColumnMapsNullFalseAndTrueToDistinctValues() {
SparkSession.clearDefaultSession();
}
}

@Test
void testTimestampNtzColumnIsSupported() {
SparkSession spark =
SparkSession.builder()
.master("local[1]")
.appName("spark-hilbert-udf-ntz-test")
.config("spark.ui.enabled", "false")
.getOrCreate();
try {
StructType schema =
new StructType(
new StructField[] {
new StructField(
"a", DataTypes.TimestampNTZType, true, Metadata.empty())
});
Dataset<Row> df =
spark.createDataFrame(
Arrays.asList(
RowFactory.create(LocalDateTime.of(2024, 1, 1, 0, 0, 0)),
RowFactory.create(LocalDateTime.of(2024, 6, 1, 12, 0, 0)),
RowFactory.create(LocalDateTime.of(2025, 1, 1, 0, 0, 0)),
RowFactory.create((LocalDateTime) null)),
schema);

SparkHilbertUDF udf = new SparkHilbertUDF();
// A paimon TIMESTAMP column reads back as timestamp_ntz unless the legacy mapping is
// on, and the dispatch used to reject it, so ordering by it threw "the type is
// unsupported" instead of clustering.
List<Row> rows =
df.select(
df.col("a"),
udf.sortedLexicographically(
df.col("a"), DataTypes.TimestampNTZType)
.as("hilbert"))
.orderBy(df.col("a").asc_nulls_first())
.collectAsList();

assertThat(rows).hasSize(4);
assertThat(rows.get(0).isNullAt(0)).isTrue();
assertThat(rows.get(0).getLong(1)).isEqualTo(Long.MAX_VALUE);

List<Long> nonNull = new ArrayList<>();
for (Row row : rows.subList(1, rows.size())) {
nonNull.add(row.getLong(1));
}
for (int i = 1; i < nonNull.size(); i++) {
assertThat(nonNull.get(i)).isGreaterThan(nonNull.get(i - 1));
}
} finally {
spark.stop();
SparkSession.clearActiveSession();
SparkSession.clearDefaultSession();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import org.apache.spark.sql.types.StructType;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -91,4 +93,63 @@ void testBooleanColumnKeepsFalseOffTheNullSentinel() {
SparkSession.clearDefaultSession();
}
}

@Test
void testTimestampNtzColumnIsSupported() {
SparkSession spark =
SparkSession.builder()
.master("local[1]")
.appName("spark-zorder-udf-ntz-test")
.config("spark.ui.enabled", "false")
.getOrCreate();
try {
StructType schema =
new StructType(
new StructField[] {
new StructField(
"a", DataTypes.TimestampNTZType, true, Metadata.empty())
});
Dataset<Row> df =
spark.createDataFrame(
Arrays.asList(
RowFactory.create(LocalDateTime.of(2024, 1, 1, 0, 0, 0)),
RowFactory.create(LocalDateTime.of(2024, 6, 1, 12, 0, 0)),
RowFactory.create(LocalDateTime.of(2025, 1, 1, 0, 0, 0)),
RowFactory.create((LocalDateTime) null)),
schema);

SparkZOrderUDF udf = new SparkZOrderUDF(1, 8, Integer.MAX_VALUE);
// A paimon TIMESTAMP column reads back as timestamp_ntz unless the legacy mapping is
// on, and the dispatch used to reject it, so ordering by it threw "the type is
// unsupported" instead of clustering.
List<Row> rows =
df.select(
df.col("a"),
functions
.hex(
udf.sortedLexicographically(
df.col("a"),
DataTypes.TimestampNTZType))
.as("zvalue"))
.orderBy(df.col("a").asc_nulls_first())
.collectAsList();

assertThat(rows).hasSize(4);
assertThat(rows.get(0).isNullAt(0)).isTrue();
assertThat(rows.get(0).getString(1)).isEqualTo("0000000000000000");

// the z-value of a non-null timestamp has to rise with the timestamp itself
List<String> nonNull = new ArrayList<>();
for (Row row : rows.subList(1, rows.size())) {
nonNull.add(row.getString(1));
}
for (int i = 1; i < nonNull.size(); i++) {
assertThat(nonNull.get(i)).isGreaterThan(nonNull.get(i - 1));
}
} finally {
spark.stop();
SparkSession.clearActiveSession();
SparkSession.clearDefaultSession();
}
}
}
Loading