This is an automated email from the ASF dual-hosted git repository.
xiangfu0 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new d390c122aaf Deprecate TimeFieldSpec and block new TimeFieldSpec schema
creation (#18502)
d390c122aaf is described below
commit d390c122aaf53e0da839f627c51410e696f87b63
Author: Xiang Fu <[email protected]>
AuthorDate: Mon Jun 1 19:16:37 2026 -0700
Deprecate TimeFieldSpec and block new TimeFieldSpec schema creation (#18502)
Step toward removing the long-deprecated TimeFieldSpec (apache/pinot#2756).
1. Formal deprecation
- Add the @Deprecated annotation to TimeFieldSpec (it had only Javadoc
@deprecated before) and to Schema#getTimeFieldSpec so the compiler
and IDE actually warn on call sites.
- Narrow @SuppressWarnings("deprecation") on internal SPI bridges that
intentionally keep handling TimeFieldSpec for backward-compat JSON
deserialization (FieldSpec @JsonSubTypes, Schema#addField, the
_timeFieldSpec field, getSpecForTimeColumn, convertToDateTimeFieldSpec)
so the SPI module stays warning-clean.
2. Block new TimeFieldSpec schema creation
- Reject schemas with fieldType=TIME in the REST-driven overload
SchemaUtils.validate(schema, tableConfigs, isIgnoreCase). POST,
PUT, and /schemas/validate all uniformly reject and point users at
DateTimeFieldSpec and apache/pinot#2756.
- The check is deliberately NOT placed in the single-arg
SchemaUtils.validate(schema) used by server-side schema loading
(RealtimeTableDataManager, TableConfigsRestletResource,
SchemaValidator) so existing legacy schemas in ZK keep loading.
- Migrate the shared integration-test schema fixtures
(On_Time_*, test_null_handling, upsert_upload_segment_test,
dedupIngestionTestSchema, kinesis/airlineStats_data_reduced) from
TimeFieldSpec to DateTimeFieldSpec.
Rolling-upgrade note: during a mixed-version controller deployment,
newer controllers reject schemas containing TimeFieldSpec via the REST
API while older controllers still accept them. Existing schemas in ZK
and segment generation continue to work on both versions.
A follow-up PR will add a controller rewrite step to migrate the
remaining legacy schemas in ZK automatically.
Co-authored-by: Claude Opus 4.7 <[email protected]>
---
.../api/PinotSchemaRestletResourceTest.java | 32 ++++++++++++++++++++++
...ime_Performance_2014_100k_subset_nonulls.schema | 9 +++---
...ormance_2014_100k_subset_nonulls_columns.schema | 9 +++---
.../test/resources/dedupIngestionTestSchema.schema | 11 ++++----
.../kinesis/airlineStats_data_reduced.schema | 11 ++++----
.../src/test/resources/test_null_handling.schema | 11 ++++----
.../resources/upsert_upload_segment_test.schema | 11 ++++----
.../pinot/segment/local/utils/SchemaUtils.java | 14 ++++++++++
.../java/org/apache/pinot/spi/data/FieldSpec.java | 2 +-
.../java/org/apache/pinot/spi/data/Schema.java | 7 +++++
.../org/apache/pinot/spi/data/TimeFieldSpec.java | 11 ++++----
11 files changed, 93 insertions(+), 35 deletions(-)
diff --git
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSchemaRestletResourceTest.java
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSchemaRestletResourceTest.java
index d7848a359d5..088031e93ec 100644
---
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSchemaRestletResourceTest.java
+++
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSchemaRestletResourceTest.java
@@ -227,6 +227,38 @@ public class PinotSchemaRestletResourceTest {
"{\"unrecognizedProperties\":{\"/illegalKey1\":1},\"status\":\"transcript2
successfully added\"}");
}
+ @Test
+ public void testRejectDeprecatedTimeFieldSpec()
+ throws Exception {
+ PinotAdminClient adminClient = TEST_INSTANCE.getOrCreateAdminClient();
+ String timeSchemaJson = "{\n"
+ + " \"schemaName\" : \"legacyTimeSchema\",\n"
+ + " \"dimensionFieldSpecs\" : [ { \"name\" : \"d1\", \"dataType\" :
\"STRING\" } ],\n"
+ + " \"timeFieldSpec\" : {\n"
+ + " \"incomingGranularitySpec\" : { \"name\" : \"ts\", \"dataType\"
: \"LONG\","
+ + " \"timeType\" : \"MILLISECONDS\" }\n"
+ + " }\n"
+ + "}";
+
+ // POST must reject because TimeFieldSpec is deprecated; the check lives
in SchemaUtils.validate so the
+ // POST/PUT/validate paths are uniformly strict. Existing schemas already
in ZK keep loading because the
+ // single-arg SchemaUtils.validate(schema) used by server-side load does
NOT apply this check.
+ RuntimeException runtimeException = expectThrows(RuntimeException.class,
+ () -> runUnchecked(() ->
adminClient.getSchemaClient().createSchema(timeSchemaJson)));
+ Throwable cause = unwrap(runtimeException);
+ assertTrue(cause instanceof PinotAdminValidationException, "Unexpected
exception: " + cause);
+ assertTrue(cause.getMessage().contains("TimeFieldSpec"),
+ "Expected TimeFieldSpec error, got: " + cause.getMessage());
+
+ // The /schemas/validate endpoint follows the same rule (also routes
through SchemaUtils.validate).
+ RuntimeException validateException = expectThrows(RuntimeException.class,
+ () -> runUnchecked(() ->
adminClient.getSchemaClient().validateSchema(timeSchemaJson)));
+ Throwable validateCause = unwrap(validateException);
+ assertTrue(validateCause instanceof PinotAdminValidationException,
"Unexpected exception: " + validateCause);
+ assertTrue(validateCause.getMessage().contains("TimeFieldSpec"),
+ "Expected TimeFieldSpec error, got: " + validateCause.getMessage());
+ }
+
@Test
public void testSchemaDeletionWithLogicalTable()
throws Exception {
diff --git
a/pinot-integration-tests/src/test/resources/On_Time_On_Time_Performance_2014_100k_subset_nonulls.schema
b/pinot-integration-tests/src/test/resources/On_Time_On_Time_Performance_2014_100k_subset_nonulls.schema
index 7dff51a4c49..5ce1110e5ea 100644
---
a/pinot-integration-tests/src/test/resources/On_Time_On_Time_Performance_2014_100k_subset_nonulls.schema
+++
b/pinot-integration-tests/src/test/resources/On_Time_On_Time_Performance_2014_100k_subset_nonulls.schema
@@ -325,11 +325,12 @@
"dataType": "INT"
}
],
- "timeFieldSpec": {
- "incomingGranularitySpec": {
+ "dateTimeFieldSpecs": [
+ {
"name": "DaysSinceEpoch",
"dataType": "INT",
- "timeType": "DAYS"
+ "format": "EPOCH|DAYS",
+ "granularity": "DAYS"
}
- }
+ ]
}
\ No newline at end of file
diff --git
a/pinot-integration-tests/src/test/resources/On_Time_On_Time_Performance_2014_100k_subset_nonulls_columns.schema
b/pinot-integration-tests/src/test/resources/On_Time_On_Time_Performance_2014_100k_subset_nonulls_columns.schema
index dfdfa576328..e335b692e2a 100644
---
a/pinot-integration-tests/src/test/resources/On_Time_On_Time_Performance_2014_100k_subset_nonulls_columns.schema
+++
b/pinot-integration-tests/src/test/resources/On_Time_On_Time_Performance_2014_100k_subset_nonulls_columns.schema
@@ -281,11 +281,12 @@
"dataType": "INT"
}
],
- "timeFieldSpec": {
- "incomingGranularitySpec": {
+ "dateTimeFieldSpecs": [
+ {
"name": "DaysSinceEpoch",
"dataType": "INT",
- "timeType": "DAYS"
+ "format": "EPOCH|DAYS",
+ "granularity": "DAYS"
}
- }
+ ]
}
\ No newline at end of file
diff --git
a/pinot-integration-tests/src/test/resources/dedupIngestionTestSchema.schema
b/pinot-integration-tests/src/test/resources/dedupIngestionTestSchema.schema
index 993c42eec91..63bdc0c3d19 100644
--- a/pinot-integration-tests/src/test/resources/dedupIngestionTestSchema.schema
+++ b/pinot-integration-tests/src/test/resources/dedupIngestionTestSchema.schema
@@ -11,13 +11,14 @@
"name": "name"
}
],
- "timeFieldSpec": {
- "incomingGranularitySpec": {
- "timeType": "DAYS",
+ "dateTimeFieldSpecs": [
+ {
+ "name": "DaysSinceEpoch",
"dataType": "INT",
- "name": "DaysSinceEpoch"
+ "format": "EPOCH|DAYS",
+ "granularity": "DAYS"
}
- },
+ ],
"primaryKeyColumns": ["id"],
"schemaName": "dedupSchema"
}
\ No newline at end of file
diff --git
a/pinot-integration-tests/src/test/resources/kinesis/airlineStats_data_reduced.schema
b/pinot-integration-tests/src/test/resources/kinesis/airlineStats_data_reduced.schema
index 73499150445..98fbece6fcd 100644
---
a/pinot-integration-tests/src/test/resources/kinesis/airlineStats_data_reduced.schema
+++
b/pinot-integration-tests/src/test/resources/kinesis/airlineStats_data_reduced.schema
@@ -21,12 +21,13 @@
"name": "FlightNum"
}
],
- "timeFieldSpec": {
- "incomingGranularitySpec": {
- "timeType": "DAYS",
+ "dateTimeFieldSpecs": [
+ {
+ "name": "DaysSinceEpoch",
"dataType": "INT",
- "name": "DaysSinceEpoch"
+ "format": "EPOCH|DAYS",
+ "granularity": "DAYS"
}
- },
+ ],
"schemaName": "mytable"
}
diff --git
a/pinot-integration-tests/src/test/resources/test_null_handling.schema
b/pinot-integration-tests/src/test/resources/test_null_handling.schema
index 0105f95d884..1c0d0ec59af 100644
--- a/pinot-integration-tests/src/test/resources/test_null_handling.schema
+++ b/pinot-integration-tests/src/test/resources/test_null_handling.schema
@@ -25,13 +25,14 @@
"notNull": "false"
}
],
- "timeFieldSpec": {
- "incomingGranularitySpec": {
- "timeType": "DAYS",
+ "dateTimeFieldSpecs": [
+ {
+ "name": "DaysSinceEpoch",
"dataType": "INT",
- "name": "DaysSinceEpoch"
+ "format": "EPOCH|DAYS",
+ "granularity": "DAYS"
}
- },
+ ],
"schemaName": "mytable",
"enableColumnBasedNullHandling": true
}
diff --git
a/pinot-integration-tests/src/test/resources/upsert_upload_segment_test.schema
b/pinot-integration-tests/src/test/resources/upsert_upload_segment_test.schema
index 3f656f7936a..f9add32f8b5 100644
---
a/pinot-integration-tests/src/test/resources/upsert_upload_segment_test.schema
+++
b/pinot-integration-tests/src/test/resources/upsert_upload_segment_test.schema
@@ -21,13 +21,14 @@
"name": "salary"
}
],
- "timeFieldSpec": {
- "incomingGranularitySpec": {
- "timeType": "DAYS",
+ "dateTimeFieldSpecs": [
+ {
+ "name": "DaysSinceEpoch",
"dataType": "INT",
- "name": "DaysSinceEpoch"
+ "format": "EPOCH|DAYS",
+ "granularity": "DAYS"
}
- },
+ ],
"primaryKeyColumns": ["clientId"],
"schemaName": "upsertSchema"
}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/SchemaUtils.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/SchemaUtils.java
index 904fd2ceac1..b4ae42641c0 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/SchemaUtils.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/SchemaUtils.java
@@ -74,12 +74,26 @@ public class SchemaUtils {
}
public static void validate(Schema schema, List<TableConfig> tableConfigs,
boolean isIgnoreCase) {
+ // The deprecation reject is intentionally placed only in this REST-driven
overload (not in the single-arg
+ // `validate(Schema)` that server-side table loading uses), so existing
legacy schemas in ZK keep
+ // loading. New / updated schemas submitted via the controller REST API
are blocked.
+ // See https://github.com/apache/pinot/issues/2756 for the TimeFieldSpec
deprecation plan.
+ rejectDeprecatedTimeFieldSpec(schema);
for (TableConfig tableConfig : tableConfigs) {
validateCompatibilityWithTableConfig(schema, tableConfig);
}
validate(schema, isIgnoreCase);
}
+ @SuppressWarnings("deprecation")
+ private static void rejectDeprecatedTimeFieldSpec(Schema schema) {
+ if (schema.getTimeFieldSpec() != null) {
+ throw new IllegalStateException(
+ "TimeFieldSpec (fieldType=TIME) is deprecated; use DateTimeFieldSpec
(fieldType=DATE_TIME) instead. "
+ + "See https://github.com/apache/pinot/issues/2756");
+ }
+ }
+
/**
* Validates the following:
* 1) Column name should not contain blank space.
diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java
b/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java
index 38cdd120c76..f5548080526 100644
--- a/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java
@@ -60,7 +60,7 @@ import org.apache.pinot.spi.utils.TimestampUtils;
* <li>"virtualColumnProvider": the virtual column provider to use for this
field.</li>
* </ul>
*/
-@SuppressWarnings("unused")
+@SuppressWarnings({"unused", "deprecation"})
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "fieldType",
diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/data/Schema.java
b/pinot-spi/src/main/java/org/apache/pinot/spi/data/Schema.java
index a023e17a1bc..55319f73ed5 100644
--- a/pinot-spi/src/main/java/org/apache/pinot/spi/data/Schema.java
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/data/Schema.java
@@ -77,6 +77,7 @@ public final class Schema implements Serializable {
private boolean _enableColumnBasedNullHandling;
private final List<DimensionFieldSpec> _dimensionFieldSpecs = new
ArrayList<>();
private final List<MetricFieldSpec> _metricFieldSpecs = new ArrayList<>();
+ @SuppressWarnings("deprecation")
private TimeFieldSpec _timeFieldSpec;
private final List<DateTimeFieldSpec> _dateTimeFieldSpecs = new
ArrayList<>();
private final List<ComplexFieldSpec> _complexFieldSpecs = new ArrayList<>();
@@ -266,6 +267,9 @@ public final class Schema implements Serializable {
}
}
+ /// @deprecated [TimeFieldSpec] is deprecated. Use
[#getSpecForTimeColumn(String)] or
+ /// [#getDateTimeSpec(String)] instead.
+ @Deprecated
public TimeFieldSpec getTimeFieldSpec() {
return _timeFieldSpec;
}
@@ -298,6 +302,7 @@ public final class Schema implements Serializable {
}
}
+ @SuppressWarnings("deprecation")
public void addField(FieldSpec fieldSpec) {
Preconditions.checkNotNull(fieldSpec);
String columnName = fieldSpec.getName();
@@ -497,6 +502,7 @@ public final class Schema implements Serializable {
*/
@JsonIgnore
@Nullable
+ @SuppressWarnings("deprecation")
public DateTimeFieldSpec getSpecForTimeColumn(String timeColumnName) {
FieldSpec fieldSpec = _fieldSpecMap.get(timeColumnName);
if (fieldSpec != null) {
@@ -983,6 +989,7 @@ public final class Schema implements Serializable {
* the dateTimeFieldSpec,
* and configure a transform function for the conversion from incoming
*/
+ @SuppressWarnings("deprecation")
public static DateTimeFieldSpec convertToDateTimeFieldSpec(TimeFieldSpec
timeFieldSpec) {
DateTimeFieldSpec dateTimeFieldSpec = new DateTimeFieldSpec();
TimeGranularitySpec incomingGranularitySpec =
timeFieldSpec.getIncomingGranularitySpec();
diff --git
a/pinot-spi/src/main/java/org/apache/pinot/spi/data/TimeFieldSpec.java
b/pinot-spi/src/main/java/org/apache/pinot/spi/data/TimeFieldSpec.java
index c7ec035784c..a3cc0074803 100644
--- a/pinot-spi/src/main/java/org/apache/pinot/spi/data/TimeFieldSpec.java
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/data/TimeFieldSpec.java
@@ -26,12 +26,11 @@ import org.apache.pinot.spi.utils.EqualityUtils;
import org.apache.pinot.spi.utils.JsonUtils;
-/**
- * @deprecated Use {@link DateTimeFieldSpec} instead.
- * This should only be used in 1) tests 2) wherever required for backward
compatible handling of schemas with
- * TimeFieldSpec
- * https://github.com/apache/pinot/issues/2756
- */
+/// @deprecated Use [DateTimeFieldSpec] instead.
+/// This should only be used in 1) tests 2) wherever required for backward
compatible handling of schemas with
+/// TimeFieldSpec
+/// <https://github.com/apache/pinot/issues/2756>
+@Deprecated
@JsonIgnoreProperties(ignoreUnknown = true)
@SuppressWarnings("unused")
public final class TimeFieldSpec extends FieldSpec {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]