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]

Reply via email to