Skip to content
Closed
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 @@ -1423,7 +1423,8 @@ abstract class DeltaInsertIntoTests(
}
}

test("insertInto: Timestamp No Timezone round trips across timezones") {
// Cast from TIMESTAMP_NTZ to TIMESTAMP has not been supported.
ignore("insertInto: Timestamp No Timezone round trips across timezones") {
val t1 = "timestamp_ntz"
withTable(t1) {
withTimeZone("GMT-8") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ object VeloxValidatorApi {
true
case dt
if !enableTimestampNtzValidation &&
dt.getClass.getSimpleName == "TimestampNTZType" =>
dt.typeName == "timestamp_ntz" =>
// Allow TimestampNTZ when validation is disabled (for development/testing)
// Use reflection to avoid compile-time dependency on Spark 3.4+ TimestampNTZType
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -767,5 +767,5 @@ object VeloxConfig extends ConfigRegistry {
"containing TimestampNTZ will fall back to Spark execution. Set to false during " +
"development/testing of TimestampNTZ support to allow native execution.")
.booleanConf
.createWithDefault(true)
.createWithDefault(false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ case class VeloxColumnarToRowExec(child: SparkPlan) extends ColumnarToRowExecBas
case _: DoubleType =>
case _: StringType =>
case _: TimestampType =>
case other if other.typeName == "timestamp_ntz" =>
case _: DateType =>
case _: BinaryType =>
case _: DecimalType =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ class FallbackSuite extends VeloxWholeStageTransformerSuite with AdaptiveSparkPl
}
}

test("fallback with index based schema evolution") {
testWithMinSparkVersion("fallback with index based schema evolution", "3.4") {
val query = "SELECT c2 FROM test"
Seq("parquet", "orc").foreach {
format =>
Expand All @@ -295,9 +295,7 @@ class FallbackSuite extends VeloxWholeStageTransformerSuite with AdaptiveSparkPl
runQueryAndCompare(query) {
df =>
val plan = df.queryExecution.executedPlan
val fallback = parquetUseColumnNames == "false" ||
orcUseColumnNames == "false"
assert(collect(plan) { case g: GlutenPlan => g }.isEmpty == fallback)
assert(collect(plan) { case g: GlutenPlan => g }.nonEmpty)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package org.apache.gluten.execution
import org.apache.gluten.config.GlutenConfig

import org.apache.spark.SparkConf
import org.apache.spark.sql.Row

import java.io.File

Expand Down Expand Up @@ -465,17 +466,17 @@ class VeloxParquetDataTypeValidationSuite extends VeloxWholeStageTransformerSuit
}
}

testWithMinSparkVersion("Fallback for TimestampNTZ type scan", "3.4") {
testWithMinSparkVersion("TimestampNTZ type scan", "3.4") {
withTempDir {
dir =>
val path = new File(dir, "ntz_data").toURI.getPath
val inputDf =
spark.sql("SELECT CAST('2024-01-01 00:00:00' AS TIMESTAMP_NTZ) AS ts_ntz")
inputDf.write.format("parquet").save(path)
val df = spark.read.format("parquet").load(path)
val df = spark.read.parquet(path)
val executedPlan = getExecutedPlan(df)
assert(!executedPlan.exists(plan => plan.isInstanceOf[BatchScanExecTransformer]))
checkAnswer(df, inputDf)
assert(executedPlan.exists(plan => plan.isInstanceOf[BatchScanExecTransformer]))
checkAnswer(df, Seq(Row(java.time.LocalDateTime.of(2024, 1, 1, 0, 0, 0, 0))))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
package org.apache.gluten.functions

import org.apache.gluten.execution.ProjectExecTransformer
import org.apache.gluten.execution.{BatchScanExecTransformer, ProjectExecTransformer}

import org.apache.spark.sql.execution.ProjectExec
import org.apache.spark.sql.types.Decimal
Expand Down Expand Up @@ -569,4 +569,43 @@ class DateFunctionsValidateSuite extends FunctionsValidateSuite {
}
}

testWithMinSparkVersion("read as timestamp_ntz", "3.4") {
val inputs: Seq[String] = Seq(
"1970-01-01",
"1970-01-01 00:00:00-02:00",
"1970-01-01 00:00:00 +02:00",
"2000-01-01",
"1970-01-01 00:00:00",
"2000-01-01 12:21:56",
"2015-03-18T12:03:17Z",
"2015-03-18 12:03:17",
"2015-03-18T12:03:17",
"2015-03-18 12:03:17.123",
"2015-03-18T12:03:17.123",
"2015-03-18T12:03:17.456",
"2015-03-18 12:03:17.456"
)

withTempPath {
dir =>
val path = dir.getAbsolutePath
val inputDF = spark.createDataset(inputs).toDF("input")
val df = inputDF.selectExpr("cast(input as timestamp_ntz) as ts")
df.coalesce(1).write.mode("overwrite").parquet(path)
val readDf = spark.read.parquet(path)
readDf.createOrReplaceTempView("view")

runQueryAndCompare("select * from view") {
checkGlutenPlan[BatchScanExecTransformer]
}

// Ensures the fallback of unsupported function works.
runQueryAndCompare("select hour(ts) from view") {
df =>
assert(collect(df.queryExecution.executedPlan) {
case p if p.isInstanceOf[ProjectExec] => p
}.nonEmpty)
}
}
}
}
8 changes: 6 additions & 2 deletions cpp/velox/substrait/SubstraitParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@

#include "SubstraitParser.h"
#include "TypeUtils.h"
#include "velox/common/base/Exceptions.h"

#include "VeloxSubstraitSignature.h"
#include "velox/common/base/Exceptions.h"

namespace gluten {

Expand Down Expand Up @@ -78,6 +77,8 @@ TypePtr SubstraitParser::parseType(const ::substrait::Type& substraitType, bool
return DATE();
case ::substrait::Type::KindCase::kTimestampTz:
return TIMESTAMP();
case ::substrait::Type::KindCase::kTimestamp:
return TIMESTAMP_UTC();
case ::substrait::Type::KindCase::kDecimal: {
auto precision = substraitType.decimal().precision();
auto scale = substraitType.decimal().scale();
Expand Down Expand Up @@ -356,6 +357,9 @@ int64_t SubstraitParser::getLiteralValue(const ::substrait::Expression::Literal&
memcpy(&decimalValue, decimal.c_str(), 16);
return static_cast<int64_t>(decimalValue);
}
if (literal.has_timestamp()) {
return literal.timestamp();
}
return literal.i64();
}

Expand Down
5 changes: 3 additions & 2 deletions cpp/velox/substrait/SubstraitToVeloxExpr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@

#include "SubstraitToVeloxExpr.h"
#include "TypeUtils.h"
#include "velox/type/Timestamp.h"
#include "velox/vector/FlatVector.h"
#include "velox/vector/VariantToVector.h"

#include "velox/type/Timestamp.h"

using namespace facebook::velox;

namespace {
Expand Down Expand Up @@ -133,6 +132,8 @@ TypePtr getScalarType(const ::substrait::Expression::Literal& literal) {
return DATE();
case ::substrait::Expression_Literal::LiteralTypeCase::kTimestampTz:
return TIMESTAMP();
case ::substrait::Expression_Literal::LiteralTypeCase::kTimestamp:
return TIMESTAMP_UTC();
case ::substrait::Expression_Literal::LiteralTypeCase::kString:
return VARCHAR();
case ::substrait::Expression_Literal::LiteralTypeCase::kVarChar:
Expand Down
1 change: 1 addition & 0 deletions cpp/velox/substrait/SubstraitToVeloxPlan.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include "utils/ConfigExtractor.h"
#include "utils/ObjectStore.h"
#include "utils/VeloxArrowUtils.h"
#include "utils/VeloxWriterUtils.h"

#include "config.pb.h"
Expand Down
2 changes: 1 addition & 1 deletion ep/build-velox/src/get-velox.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ set -exu

CURRENT_DIR=$(cd "$(dirname "$BASH_SOURCE")"; pwd)
VELOX_REPO=https://github.com/IBM/velox.git
VELOX_BRANCH=dft-2026_04_13
VELOX_BRANCH=ts_ntz_ibm
VELOX_ENHANCED_BRANCH=ibm-2026_04_13
VELOX_HOME=""
RUN_SETUP_SCRIPT=ON
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ object SparkArrowUtil {
} else {
new ArrowType.Timestamp(TimeUnit.MICROSECOND, "UTC")
}
case dt if dt.catalogString == "timestamp_ntz" =>
case dt if dt.typeName == "timestamp_ntz" =>
new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)
case YearMonthIntervalType.DEFAULT =>
new ArrowType.Interval(IntervalUnit.YEAR_MONTH)
Expand Down Expand Up @@ -168,8 +168,6 @@ object SparkArrowUtil {
}.asJava)
}

// TimestampNTZ is not supported for native computation, but the Arrow type mapping is needed
// for row-to-columnar transitions when the fallback validator tags NTZ operators.
def checkSchema(schema: StructType): Boolean = {
try {
SparkSchemaUtil.toArrowSchema(schema)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,13 @@ abstract class DeltaSuite extends WholeStageTransformerSuite {

// TIMESTAMP_NTZ was introduced in Spark 3.4 / Delta 2.4
testWithMinSparkVersion(
"delta: create table with TIMESTAMP_NTZ should fallback and return correct results",
"delta: create table with TIMESTAMP_NTZ and return correct results",
"3.4") {
withTable("delta_ntz") {
spark.sql("CREATE TABLE delta_ntz(c1 STRING, c2 TIMESTAMP, c3 TIMESTAMP_NTZ) USING DELTA")
spark.sql("""INSERT INTO delta_ntz VALUES
|('foo','2022-01-02 03:04:05.123456','2022-01-02 03:04:05.123456')""".stripMargin)
val df = runQueryAndCompare("select * from delta_ntz", noFallBack = false) { _ => }
val df = runQueryAndCompare("select * from delta_ntz") { _ => }
checkAnswer(
df,
Row(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.gluten.substrait.expression;

import org.apache.gluten.substrait.type.TimestampTypeNode;
import org.apache.gluten.substrait.type.TypeNode;

import io.substrait.proto.Expression.Literal.Builder;

public class TimestampNTZLiteralNode extends LiteralNodeWithValue<Long> {
public TimestampNTZLiteralNode(Long value) {
super(value, new TimestampTypeNode(true));
}

public TimestampNTZLiteralNode(Long value, TypeNode typeNode) {
super(value, typeNode);
}

@Override
protected void updateLiteralBuilder(Builder literalBuilder, Long value) {
literalBuilder.setTimestamp(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.gluten.substrait.type;

import io.substrait.proto.Type;

public class TimestampNTZTypeNode extends TypeNode {

public TimestampNTZTypeNode(Boolean nullable) {
super(nullable);
}

@Override
public Type toProtobuf() {
Type.Timestamp.Builder timestampBuilder = Type.Timestamp.newBuilder();
if (nullable) {
timestampBuilder.setNullability(Type.Nullability.NULLABILITY_NULLABLE);
} else {
timestampBuilder.setNullability(Type.Nullability.NULLABILITY_REQUIRED);
}

Type.Builder builder = Type.newBuilder();
builder.setTimestamp(timestampBuilder.build());
return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ public static TypeNode makeTimestamp(Boolean nullable) {
return new TimestampTypeNode(nullable);
}

public static TypeNode makeTimestampNTZ(Boolean nullable) {
return new TimestampNTZTypeNode(nullable);
}

public static TypeNode makeStruct(Boolean nullable, List<TypeNode> types, List<String> names) {
return new StructNode(nullable, types, names);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ object ConverterUtils extends Logging {
(StringType, isNullable(substraitType.getString.getNullability))
case Type.KindCase.BINARY =>
(BinaryType, isNullable(substraitType.getBinary.getNullability))
case Type.KindCase.TIMESTAMP =>
try {
(
Class
.forName("org.apache.spark.sql.types.TimestampNTZType$")
.getField("MODULE$")
.get(null)
.asInstanceOf[DataType],
isNullable(substraitType.getTimestamp.getNullability))
} catch {
case _: ClassNotFoundException =>
throw new GlutenNotSupportException(s"Type $substraitType not supported.")
}
case Type.KindCase.TIMESTAMP_TZ =>
(TimestampType, isNullable(substraitType.getTimestampTz.getNullability))
case Type.KindCase.DATE =>
Expand Down Expand Up @@ -226,6 +239,8 @@ object ConverterUtils extends Logging {
TypeBuilder.makeDecimal(nullable, precision, scale)
case TimestampType =>
TypeBuilder.makeTimestamp(nullable)
case other if other.typeName == "timestamp_ntz" =>
TypeBuilder.makeTimestampNTZ(nullable)
case m: MapType =>
TypeBuilder.makeMap(
nullable,
Expand Down Expand Up @@ -399,6 +414,7 @@ object ConverterUtils extends Logging {
case DoubleType => "fp64"
case DateType => "date"
case TimestampType => "ts"
case other if other.typeName == "timestamp_ntz" => "ts_ntz"
case StringType => "str"
case BinaryType => "vbin"
case DecimalType() =>
Expand Down
Loading
Loading