Skip to content

Commit a507a99

Browse files
committed
Expose ORC calendar controls through Java and JNI
Signed-off-by: Allen Xu <allxu@nvidia.com>
1 parent 3e8599b commit a507a99

7 files changed

Lines changed: 570 additions & 26 deletions

File tree

‎java/pom.xml‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,32 @@
136136
</pluginRepositories>
137137

138138
<dependencies>
139+
<dependency>
140+
<groupId>org.apache.orc</groupId>
141+
<artifactId>orc-core</artifactId>
142+
<version>1.9.4</version>
143+
<classifier>shaded-protobuf</classifier>
144+
<scope>test</scope>
145+
<exclusions>
146+
<!-- Use the hadoop-common dependency below instead of duplicate Hadoop classes. -->
147+
<exclusion>
148+
<groupId>org.apache.hadoop</groupId>
149+
<artifactId>hadoop-client-api</artifactId>
150+
</exclusion>
151+
</exclusions>
152+
</dependency>
153+
<dependency>
154+
<groupId>org.apache.hadoop</groupId>
155+
<artifactId>hadoop-hdfs-client</artifactId>
156+
<version>3.4.0</version>
157+
<scope>test</scope>
158+
</dependency>
159+
<dependency>
160+
<groupId>org.apache.hive</groupId>
161+
<artifactId>hive-storage-api</artifactId>
162+
<version>2.8.1</version>
163+
<scope>test</scope>
164+
</dependency>
139165
<dependency>
140166
<groupId>org.slf4j</groupId>
141167
<artifactId>slf4j-api</artifactId>
@@ -664,6 +690,28 @@
664690
</pluginManagement>
665691

666692
<plugins>
693+
<!-- Keep Apache ORC interoperability tests in cuDF's own Maven lifecycle.
694+
JNI imports src/test/java without these test-only dependencies.
695+
This execution also remains active with -Ppackaged-jar-tests. -->
696+
<plugin>
697+
<groupId>org.codehaus.mojo</groupId>
698+
<artifactId>build-helper-maven-plugin</artifactId>
699+
<version>3.6.0</version>
700+
<executions>
701+
<execution>
702+
<id>add-interop-tests</id>
703+
<phase>generate-test-sources</phase>
704+
<goals>
705+
<goal>add-test-source</goal>
706+
</goals>
707+
<configuration>
708+
<sources>
709+
<source>${project.basedir}/src/test/interop</source>
710+
</sources>
711+
</configuration>
712+
</execution>
713+
</executions>
714+
</plugin>
667715
<plugin>
668716
<artifactId>maven-antrun-plugin</artifactId>
669717
<version>1.8</version>

‎java/src/main/java/ai/rapids/cudf/ORCOptions.java‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
*
3-
* SPDX-FileCopyrightText: Copyright (c) 2019-2025, NVIDIA CORPORATION.
3+
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
44
* SPDX-License-Identifier: Apache-2.0
55
*
66
*/
@@ -19,6 +19,7 @@ public class ORCOptions extends ColumnFilterOptions {
1919
public static ORCOptions DEFAULT = new ORCOptions(new Builder());
2020

2121
private final boolean useNumPyTypes;
22+
private final boolean convertDatesToProleptic;
2223
private final DType unit;
2324
private final String[] decimal128Columns;
2425
private final boolean ignoreTimezoneInStripeFooter;
@@ -27,6 +28,7 @@ private ORCOptions(Builder builder) {
2728
super(builder);
2829
decimal128Columns = builder.decimal128Columns.toArray(new String[0]);
2930
useNumPyTypes = builder.useNumPyTypes;
31+
convertDatesToProleptic = builder.convertDatesToProleptic;
3032
unit = builder.unit;
3133
ignoreTimezoneInStripeFooter = builder.ignoreTimezoneInStripeFooter;
3234
}
@@ -35,6 +37,8 @@ boolean usingNumPyTypes() {
3537
return useNumPyTypes;
3638
}
3739

40+
boolean convertDatesToProleptic() { return convertDatesToProleptic; }
41+
3842
DType timeUnit() {
3943
return unit;
4044
}
@@ -53,6 +57,7 @@ public static Builder builder() {
5357

5458
public static class Builder extends ColumnFilterOptions.Builder<Builder> {
5559
private boolean useNumPyTypes = true;
60+
private boolean convertDatesToProleptic = false;
5661
private DType unit = DType.EMPTY;
5762

5863
final List<String> decimal128Columns = new ArrayList<>();
@@ -99,6 +104,19 @@ public Builder decimal128Column(String... names) {
99104
return this;
100105
}
101106

107+
/**
108+
* Convert DATE columns explicitly marked JULIAN_GREGORIAN to cuDF proleptic dates.
109+
* Disabled by default for callers that perform their own rebasing. Missing and UNKNOWN
110+
* calendar fields and TIMESTAMP columns are unchanged. Conversion applies to nested DATE
111+
* columns and precedes NumPy promotion. Unrepresentable dates cause a read error.
112+
* @param convert whether DATE conversion is enabled
113+
* @return builder for chaining
114+
*/
115+
public Builder convertDatesToProleptic(boolean convert) {
116+
this.convertDatesToProleptic = convert;
117+
return this;
118+
}
119+
102120
public Builder ignoreTimezoneInStripeFooter() {
103121
this.ignoreTimezoneInStripeFooter = true;
104122
return this;

‎java/src/main/java/ai/rapids/cudf/ORCWriterOptions.java‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,27 @@
1212
* that will be used by the ORC writer to write the file.
1313
*/
1414
public class ORCWriterOptions extends CompressionMetadataWriterOptions {
15+
/** Calendar recorded in the standard ORC file footer. */
16+
public enum CalendarKind {
17+
JULIAN_GREGORIAN(1),
18+
PROLEPTIC_GREGORIAN(2);
19+
20+
final int nativeId;
21+
22+
CalendarKind(int nativeId) {
23+
this.nativeId = nativeId;
24+
}
25+
}
26+
1527
private int stripeSizeRows;
1628
private String writerTimezone;
29+
private CalendarKind calendarKind;
1730

1831
private ORCWriterOptions(Builder builder) {
1932
super(builder);
2033
this.stripeSizeRows = builder.stripeSizeRows;
2134
this.writerTimezone = builder.writerTimezone;
35+
this.calendarKind = builder.calendarKind;
2236
}
2337

2438
public static Builder builder() {
@@ -33,11 +47,16 @@ public String getWriterTimezone() {
3347
return writerTimezone;
3448
}
3549

50+
public CalendarKind getCalendarKind() {
51+
return calendarKind;
52+
}
53+
3654
public static class Builder extends CompressionMetadataWriterOptions.Builder
3755
<Builder, ORCWriterOptions> {
3856
// < 1M rows default orc stripe rows, defined in cudf/cpp/include/cudf/io/orc.hpp
3957
private int stripeSizeRows = 1000000;
4058
private String writerTimezone = "UTC";
59+
private CalendarKind calendarKind = CalendarKind.PROLEPTIC_GREGORIAN;
4160

4261
public Builder withStripeSizeRows(int stripeSizeRows) {
4362
// maximum stripe size cannot be smaller than 512
@@ -69,6 +88,25 @@ public Builder withWriterTimezone(String writerTimezone) {
6988
return this;
7089
}
7190

91+
/**
92+
* Sets the calendar used for DATE encoding and recorded in the file footer.
93+
* Legacy mode rebases nested and top-level DATE values. It rejects TIMESTAMP columns,
94+
* dates in October 5-14, 1582, and dates outside the int32 hybrid domain.
95+
* Use {@link ORCOptions.Builder#convertDatesToProleptic(boolean)} to read legacy output
96+
* back as cuDF proleptic Gregorian dates.
97+
*
98+
* @param calendarKind calendar kind
99+
* @throws IllegalArgumentException if the calendar is null
100+
*/
101+
public Builder withCalendarKind(CalendarKind calendarKind) {
102+
if (calendarKind == null) {
103+
throw new IllegalArgumentException(
104+
"ORC calendar must not be null");
105+
}
106+
this.calendarKind = calendarKind;
107+
return this;
108+
}
109+
72110
public ORCWriterOptions build() {
73111
return new ORCWriterOptions(this);
74112
}

‎java/src/main/java/ai/rapids/cudf/Table.java‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,12 +342,14 @@ private static native long[] readORC(String[] filterColumnNames,
342342
String filePath, long address, long length,
343343
boolean usingNumPyTypes, int timeUnit,
344344
String[] decimal128Columns,
345-
boolean ignoreTimezoneInStripeFooter) throws CudfException;
345+
boolean ignoreTimezoneInStripeFooter,
346+
boolean convertDatesToProleptic) throws CudfException;
346347

347348
private static native long[] readORCFromDataSource(String[] filterColumnNames,
348349
boolean usingNumPyTypes, int timeUnit,
349350
String[] decimal128Columns,
350351
boolean ignoreTimezoneInStripeFooter,
352+
boolean convertDatesToProleptic,
351353
long dataSourceHandle) throws CudfException;
352354

353355
/**
@@ -363,6 +365,7 @@ private static native long[] readORCFromDataSource(String[] filterColumnNames,
363365
* the columns
364366
* @param isMapValues true if a column is a map
365367
* @param writerTimezone timezone that the written timestamps are relative to
368+
* @param calendarKind native calendar kind recorded in the ORC footer
366369
* @param filename local output path
367370
* @return a handle that is used in later calls to writeORCChunk and writeORCEnd.
368371
*/
@@ -377,6 +380,7 @@ private static native long writeORCFileBegin(String[] columnNames,
377380
boolean[] isMapValues,
378381
int stripeSizeRows,
379382
String writerTimezone,
383+
int calendarKind,
380384
String filename) throws CudfException;
381385

382386
/**
@@ -392,6 +396,7 @@ private static native long writeORCFileBegin(String[] columnNames,
392396
* the columns
393397
* @param isMapValues true if a column is a map
394398
* @param writerTimezone timezone that the written timestamps are relative to
399+
* @param calendarKind native calendar kind recorded in the ORC footer
395400
* @param consumer consumer of host buffers produced.
396401
* @return a handle that is used in later calls to writeORCChunk and writeORCEnd.
397402
*/
@@ -406,6 +411,7 @@ private static native long writeORCBufferBegin(String[] columnNames,
406411
boolean[] isMapValues,
407412
int stripeSizeRows,
408413
String writerTimezone,
414+
int calendarKind,
409415
HostBufferConsumer consumer,
410416
HostMemoryAllocator hostMemoryAllocator
411417
) throws CudfException;
@@ -1519,7 +1525,8 @@ public static Table readORC(ORCOptions opts, File path) {
15191525
return new Table(readORC(opts.getIncludeColumnNames(),
15201526
path.getAbsolutePath(), 0, 0,
15211527
opts.usingNumPyTypes(), opts.timeUnit().typeId.getNativeId(),
1522-
opts.getDecimal128Columns(), opts.ignoreTimezoneInStripeFooter()));
1528+
opts.getDecimal128Columns(), opts.ignoreTimezoneInStripeFooter(),
1529+
opts.convertDatesToProleptic()));
15231530
}
15241531

15251532
/**
@@ -1588,7 +1595,8 @@ public static Table readORC(ORCOptions opts, HostMemoryBuffer buffer,
15881595
return new Table(readORC(opts.getIncludeColumnNames(),
15891596
null, buffer.getAddress() + offset, len,
15901597
opts.usingNumPyTypes(), opts.timeUnit().typeId.getNativeId(),
1591-
opts.getDecimal128Columns(), opts.ignoreTimezoneInStripeFooter()));
1598+
opts.getDecimal128Columns(), opts.ignoreTimezoneInStripeFooter(),
1599+
opts.convertDatesToProleptic()));
15921600
}
15931601

15941602
public static Table readORC(ORCOptions opts, DataSource ds) {
@@ -1597,7 +1605,7 @@ public static Table readORC(ORCOptions opts, DataSource ds) {
15971605
return new Table(readORCFromDataSource(opts.getIncludeColumnNames(),
15981606
opts.usingNumPyTypes(), opts.timeUnit().typeId.getNativeId(),
15991607
opts.getDecimal128Columns(), opts.ignoreTimezoneInStripeFooter(),
1600-
dataSourceHandle));
1608+
opts.convertDatesToProleptic(), dataSourceHandle));
16011609
} finally {
16021610
DataSourceHelper.destroyWrapperDataSource(dataSourceHandle);
16031611
}
@@ -1698,6 +1706,7 @@ private ORCTableWriter(ORCWriterOptions options, File outputFile) {
16981706
options.getFlatIsMap(),
16991707
options.getStripeSizeRows(),
17001708
options.getWriterTimezone(),
1709+
options.getCalendarKind().nativeId,
17011710
outputFile.getAbsolutePath()));
17021711
this.consumer = null;
17031712
}
@@ -1715,6 +1724,7 @@ private ORCTableWriter(ORCWriterOptions options, HostBufferConsumer consumer,
17151724
options.getFlatIsMap(),
17161725
options.getStripeSizeRows(),
17171726
options.getWriterTimezone(),
1727+
options.getCalendarKind().nativeId,
17181728
consumer, hostMemoryAllocator));
17191729
this.consumer = consumer;
17201730
}

‎java/src/main/native/src/TableJni.cpp‎

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2493,6 +2493,7 @@ Java_ai_rapids_cudf_Table_readORCFromDataSource(JNIEnv* env,
24932493
jint unit,
24942494
jobjectArray dec128_col_names,
24952495
jboolean ignore_timezone_in_stripe_footer,
2496+
jboolean convert_dates_to_proleptic,
24962497
jlong ds_handle)
24972498
{
24982499
JNI_NULL_CHECK(env, ds_handle, "no data source handle given", 0);
@@ -2519,6 +2520,7 @@ Java_ai_rapids_cudf_Table_readORCFromDataSource(JNIEnv* env,
25192520
.timestamp_type(cudf::data_type(static_cast<cudf::type_id>(unit)))
25202521
.decimal128_columns(n_dec128_col_names.as_cpp_vector())
25212522
.ignore_timezone_in_stripe_footer(ignore_timezone_in_stripe_footer)
2523+
.convert_dates_to_proleptic(convert_dates_to_proleptic)
25222524
.build();
25232525
return convert_table_for_return(env, cudf::io::read_orc(opts).tbl);
25242526
}
@@ -2535,7 +2537,8 @@ Java_ai_rapids_cudf_Table_readORC(JNIEnv* env,
25352537
jboolean usingNumPyTypes,
25362538
jint unit,
25372539
jobjectArray dec128_col_names,
2538-
jboolean ignore_timezone_in_stripe_footer)
2540+
jboolean ignore_timezone_in_stripe_footer,
2541+
jboolean convert_dates_to_proleptic)
25392542
{
25402543
bool read_buffer = true;
25412544
if (buffer == 0) {
@@ -2579,6 +2582,7 @@ Java_ai_rapids_cudf_Table_readORC(JNIEnv* env,
25792582
.timestamp_type(cudf::data_type(static_cast<cudf::type_id>(unit)))
25802583
.decimal128_columns(n_dec128_col_names.as_cpp_vector())
25812584
.ignore_timezone_in_stripe_footer(ignore_timezone_in_stripe_footer)
2585+
.convert_dates_to_proleptic(convert_dates_to_proleptic)
25822586
.build();
25832587
return convert_table_for_return(env, cudf::io::read_orc(opts).tbl);
25842588
}
@@ -2599,6 +2603,7 @@ Java_ai_rapids_cudf_Table_writeORCBufferBegin(JNIEnv* env,
25992603
jbooleanArray j_is_map,
26002604
jint j_stripe_size_rows,
26012605
jstring j_writer_timezone,
2606+
jint j_calendar_kind,
26022607
jobject consumer,
26032608
jobject host_memory_allocator)
26042609
{
@@ -2650,16 +2655,18 @@ Java_ai_rapids_cudf_Table_writeORCBufferBegin(JNIEnv* env,
26502655
new cudf::jni::jni_writer_data_sink(env, consumer, host_memory_allocator));
26512656
sink_info sink{data_sink.get()};
26522657

2653-
auto stats = std::make_shared<cudf::io::writer_compression_statistics>();
2654-
chunked_orc_writer_options opts = chunked_orc_writer_options::builder(sink)
2655-
.metadata(std::move(metadata))
2656-
.compression(static_cast<compression_type>(j_compression))
2657-
.enable_statistics(ORC_STATISTICS_ROW_GROUP)
2658-
.key_value_metadata(kv_metadata)
2659-
.compression_statistics(stats)
2660-
.stripe_size_rows(j_stripe_size_rows)
2661-
.writer_timezone(writer_timezone.get())
2662-
.build();
2658+
auto stats = std::make_shared<cudf::io::writer_compression_statistics>();
2659+
chunked_orc_writer_options opts =
2660+
chunked_orc_writer_options::builder(sink)
2661+
.metadata(std::move(metadata))
2662+
.compression(static_cast<compression_type>(j_compression))
2663+
.enable_statistics(ORC_STATISTICS_ROW_GROUP)
2664+
.key_value_metadata(kv_metadata)
2665+
.compression_statistics(stats)
2666+
.stripe_size_rows(j_stripe_size_rows)
2667+
.writer_timezone(writer_timezone.get())
2668+
.calendar_kind(static_cast<orc::CalendarKind>(j_calendar_kind))
2669+
.build();
26632670
auto writer_ptr = std::make_unique<cudf::io::orc_chunked_writer>(opts);
26642671
cudf::jni::native_orc_writer_handle* ret = new cudf::jni::native_orc_writer_handle(
26652672
std::move(writer_ptr), std::move(data_sink), std::move(stats));
@@ -2681,6 +2688,7 @@ JNIEXPORT long JNICALL Java_ai_rapids_cudf_Table_writeORCFileBegin(JNIEnv* env,
26812688
jbooleanArray j_is_map,
26822689
jint j_stripe_size_rows,
26832690
jstring j_writer_timezone,
2691+
jint j_calendar_kind,
26842692
jstring j_output_path)
26852693
{
26862694
JNI_NULL_CHECK(env, j_col_names, "null columns", 0);
@@ -2728,16 +2736,18 @@ JNIEXPORT long JNICALL Java_ai_rapids_cudf_Table_writeORCFileBegin(JNIEnv* env,
27282736
[](std::string const& k, std::string const& v) { return std::make_pair(k, v); });
27292737

27302738
sink_info sink{output_path};
2731-
auto stats = std::make_shared<cudf::io::writer_compression_statistics>();
2732-
chunked_orc_writer_options opts = chunked_orc_writer_options::builder(sink)
2733-
.metadata(std::move(metadata))
2734-
.compression(static_cast<compression_type>(j_compression))
2735-
.enable_statistics(ORC_STATISTICS_ROW_GROUP)
2736-
.key_value_metadata(kv_metadata)
2737-
.compression_statistics(stats)
2738-
.stripe_size_rows(j_stripe_size_rows)
2739-
.writer_timezone(writer_timezone.get())
2740-
.build();
2739+
auto stats = std::make_shared<cudf::io::writer_compression_statistics>();
2740+
chunked_orc_writer_options opts =
2741+
chunked_orc_writer_options::builder(sink)
2742+
.metadata(std::move(metadata))
2743+
.compression(static_cast<compression_type>(j_compression))
2744+
.enable_statistics(ORC_STATISTICS_ROW_GROUP)
2745+
.key_value_metadata(kv_metadata)
2746+
.compression_statistics(stats)
2747+
.stripe_size_rows(j_stripe_size_rows)
2748+
.writer_timezone(writer_timezone.get())
2749+
.calendar_kind(static_cast<orc::CalendarKind>(j_calendar_kind))
2750+
.build();
27412751
auto writer_ptr = std::make_unique<cudf::io::orc_chunked_writer>(opts);
27422752
cudf::jni::native_orc_writer_handle* ret =
27432753
new cudf::jni::native_orc_writer_handle(std::move(writer_ptr), nullptr, std::move(stats));

0 commit comments

Comments
 (0)