This is an automated email from the ASF dual-hosted git repository.
caicancai pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new bf2b81b27d [CALCITE-7580] Remove Gandiva dependency from Arrow adapter
bf2b81b27d is described below
commit bf2b81b27d76ef3571ba676dbc7bce4ccb4a0413
Author: Cancai Cai <[email protected]>
AuthorDate: Sat Jun 6 22:43:38 2026 +0800
[CALCITE-7580] Remove Gandiva dependency from Arrow adapter
---
arrow/build.gradle.kts | 1 -
.../adapter/arrow/AbstractArrowEnumerator.java | 30 ++-
.../adapter/arrow/ArrowDirectEnumerator.java | 31 +--
.../calcite/adapter/arrow/ArrowEnumerable.java | 25 +--
.../adapter/arrow/ArrowFilterEnumerator.java | 238 ++++++++++++++++-----
.../adapter/arrow/ArrowProjectEnumerator.java | 80 -------
.../apache/calcite/adapter/arrow/ArrowRules.java | 2 +-
.../apache/calcite/adapter/arrow/ArrowTable.java | 143 +------------
.../calcite/adapter/arrow/ArrowTranslator.java | 69 ++++--
.../calcite/adapter/arrow/ConditionToken.java | 51 ++++-
.../calcite/adapter/arrow/ArrowAdapterTest.java | 50 +++++
.../calcite/adapter/arrow/ArrowDataTest.java | 29 +++
.../calcite/adapter/arrow/ArrowExtension.java | 23 +-
bom/build.gradle.kts | 1 -
.../java/org/apache/calcite/rex/RexSimplify.java | 26 +--
.../calcite/sql/type/SqlTypeFactoryImpl.java | 2 +-
.../apache/calcite/sql2rel/SqlToRelConverter.java | 34 +--
.../org/apache/calcite/rex/RexProgramTest.java | 20 --
.../calcite/sql/type/SqlTypeFactoryTest.java | 25 ---
.../apache/calcite/test/SqlToRelConverterTest.java | 18 --
.../apache/calcite/test/SqlToRelConverterTest.xml | 24 ---
gradle.properties | 1 -
site/_docs/history.md | 5 +
.../org/apache/calcite/test/SqlToRelFixture.java | 5 -
24 files changed, 408 insertions(+), 525 deletions(-)
diff --git a/arrow/build.gradle.kts b/arrow/build.gradle.kts
index 598aa8a879..c75a8b5f67 100644
--- a/arrow/build.gradle.kts
+++ b/arrow/build.gradle.kts
@@ -23,7 +23,6 @@
implementation("com.google.guava:guava")
implementation("org.apache.arrow:arrow-memory-netty")
implementation("org.apache.arrow:arrow-vector")
- implementation("org.apache.arrow.gandiva:arrow-gandiva")
annotationProcessor("org.immutables:value")
compileOnly("org.immutables:value-annotations")
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/AbstractArrowEnumerator.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/AbstractArrowEnumerator.java
index e188757b0d..486e3f60bd 100644
---
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/AbstractArrowEnumerator.java
+++
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/AbstractArrowEnumerator.java
@@ -24,9 +24,7 @@
import org.apache.arrow.vector.TimeStampVector;
import org.apache.arrow.vector.ValueVector;
import org.apache.arrow.vector.VectorSchemaRoot;
-import org.apache.arrow.vector.VectorUnloader;
import org.apache.arrow.vector.ipc.ArrowFileReader;
-import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
import org.apache.arrow.vector.types.TimeUnit;
import org.apache.arrow.vector.types.pojo.ArrowType;
@@ -51,8 +49,6 @@ abstract class AbstractArrowEnumerator implements
Enumerator<Object> {
this.currRowIndex = -1;
}
- abstract void evaluateOperator(ArrowRecordBatch arrowRecordBatch);
-
protected void loadNextArrowBatch() {
try {
final VectorSchemaRoot vsr = arrowFileReader.getVectorSchemaRoot();
@@ -60,14 +56,32 @@ protected void loadNextArrowBatch() {
this.valueVectors.add(vsr.getVector(i));
}
this.rowCount = vsr.getRowCount();
- VectorUnloader vectorUnloader = new VectorUnloader(vsr);
- ArrowRecordBatch arrowRecordBatch = vectorUnloader.getRecordBatch();
- evaluateOperator(arrowRecordBatch);
} catch (IOException e) {
throw Util.toUnchecked(e);
}
}
+ /** Loads the next non-empty Arrow batch. */
+ protected boolean loadNextNonEmptyArrowBatch() {
+ while (true) {
+ final boolean hasNextBatch;
+ try {
+ hasNextBatch = arrowFileReader.loadNextBatch();
+ } catch (IOException e) {
+ throw Util.toUnchecked(e);
+ }
+ if (!hasNextBatch) {
+ return false;
+ }
+ currRowIndex = -1;
+ valueVectors.clear();
+ loadNextArrowBatch();
+ if (rowCount > 0) {
+ return true;
+ }
+ }
+ }
+
@Override public Object current() {
if (fields.size() == 1) {
return getValue(this.valueVectors.get(0), currRowIndex);
@@ -85,7 +99,7 @@ protected void loadNextArrowBatch() {
* <p>For {@link TimeStampVector}, converts the raw value to
* milliseconds since epoch, which is the representation used by
* Calcite's Enumerable runtime for TIMESTAMP types. */
- private static Object getValue(ValueVector vector, int index) {
+ protected static Object getValue(ValueVector vector, int index) {
if (vector instanceof TimeStampVector) {
if (vector.isNull(index)) {
return null;
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowDirectEnumerator.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowDirectEnumerator.java
index 0cdec7baeb..787ffd88d9 100644
---
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowDirectEnumerator.java
+++
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowDirectEnumerator.java
@@ -17,19 +17,11 @@
package org.apache.calcite.adapter.arrow;
import org.apache.calcite.util.ImmutableIntList;
-import org.apache.calcite.util.Util;
import org.apache.arrow.vector.ipc.ArrowFileReader;
-import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
-
-import java.io.IOException;
/**
* Enumerator that reads projected Arrow value-vectors directly.
- *
- * <p>This path is used for identity projections that Gandiva cannot project
- * through the existing {@code Projector} path, such as Arrow binary vectors.
- * It is not a replacement for Gandiva expression evaluation.
*/
class ArrowDirectEnumerator extends AbstractArrowEnumerator {
private final Runnable onClose;
@@ -40,27 +32,14 @@ class ArrowDirectEnumerator extends AbstractArrowEnumerator
{
this.onClose = onClose;
}
- @Override protected void evaluateOperator(ArrowRecordBatch arrowRecordBatch)
{
- }
-
@Override public boolean moveNext() {
- if (currRowIndex >= rowCount - 1) {
- final boolean hasNextBatch;
- try {
- hasNextBatch = arrowFileReader.loadNextBatch();
- } catch (IOException e) {
- throw Util.toUnchecked(e);
- }
- if (hasNextBatch) {
- currRowIndex = 0;
- this.valueVectors.clear();
- loadNextArrowBatch();
+ while (currRowIndex >= rowCount - 1) {
+ if (!loadNextNonEmptyArrowBatch()) {
+ return false;
}
- return hasNextBatch;
- } else {
- currRowIndex++;
- return true;
}
+ currRowIndex++;
+ return true;
}
@Override public void close() {
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowEnumerable.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowEnumerable.java
index 84ed5997aa..b9c0c4171e 100644
--- a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowEnumerable.java
+++ b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowEnumerable.java
@@ -21,11 +21,10 @@
import org.apache.calcite.util.ImmutableIntList;
import org.apache.calcite.util.Util;
-import org.apache.arrow.gandiva.evaluator.Filter;
-import org.apache.arrow.gandiva.evaluator.Projector;
import org.apache.arrow.vector.ipc.ArrowFileReader;
+import org.apache.arrow.vector.types.pojo.Schema;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import java.util.List;
/**
* Enumerable that reads from Arrow value-vectors.
@@ -33,28 +32,24 @@
class ArrowEnumerable extends AbstractEnumerable<Object> {
private final ArrowFileReader arrowFileReader;
private final ImmutableIntList fields;
- private final @Nullable Projector projector;
- private final @Nullable Filter filter;
+ private final List<List<List<String>>> conditions;
+ private final Schema schema;
private final Runnable onClose;
ArrowEnumerable(ArrowFileReader arrowFileReader, ImmutableIntList fields,
- @Nullable Projector projector, @Nullable Filter filter,
- Runnable onClose) {
+ List<List<List<String>>> conditions, Schema schema, Runnable onClose) {
this.arrowFileReader = arrowFileReader;
- this.projector = projector;
- this.filter = filter;
+ this.conditions = conditions;
+ this.schema = schema;
this.fields = fields;
this.onClose = onClose;
}
@Override public Enumerator<Object> enumerator() {
try {
- if (projector != null) {
- return new ArrowProjectEnumerator(arrowFileReader, fields, projector,
- onClose);
- } else if (filter != null) {
- return new ArrowFilterEnumerator(arrowFileReader, fields, filter,
- onClose);
+ if (!conditions.isEmpty()) {
+ return new ArrowFilterEnumerator(arrowFileReader, fields,
+ conditions, schema, onClose);
}
// No projector and no filter means the query is an identity projection
// that should read selected value-vectors directly.
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowFilterEnumerator.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowFilterEnumerator.java
index 5eddec2249..2f154cc6ef 100644
---
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowFilterEnumerator.java
+++
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowFilterEnumerator.java
@@ -19,91 +19,215 @@
import org.apache.calcite.util.ImmutableIntList;
import org.apache.calcite.util.Util;
-import org.apache.arrow.gandiva.evaluator.Filter;
-import org.apache.arrow.gandiva.evaluator.SelectionVector;
-import org.apache.arrow.gandiva.evaluator.SelectionVectorInt16;
-import org.apache.arrow.gandiva.exceptions.GandivaException;
-import org.apache.arrow.memory.ArrowBuf;
-import org.apache.arrow.memory.BufferAllocator;
-import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.ValueVector;
+import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.ipc.ArrowFileReader;
-import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
-
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.Schema;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
import static java.util.Objects.requireNonNull;
/**
- * Enumerator that reads from a filtered collection of Arrow value-vectors.
+ * Enumerator that evaluates Arrow filter tokens in Java.
*/
class ArrowFilterEnumerator extends AbstractArrowEnumerator {
- private final BufferAllocator allocator;
- private final Filter filter;
- private @Nullable ArrowBuf buf;
- private @Nullable SelectionVector selectionVector;
- private int selectionVectorIndex;
-
+ private final List<List<ConditionToken>> conditions;
+ private final Schema schema;
private final Runnable onClose;
+ private final List<ValueVector> filterVectors;
+ private final Map<String, Pattern> likePatterns;
- ArrowFilterEnumerator(ArrowFileReader arrowFileReader, ImmutableIntList
fields,
- Filter filter, Runnable onClose) {
+ ArrowFilterEnumerator(ArrowFileReader arrowFileReader,
+ ImmutableIntList fields, List<List<List<String>>> conditions,
+ Schema schema, Runnable onClose) {
super(arrowFileReader, fields);
- this.allocator = new RootAllocator(Long.MAX_VALUE);
- this.filter = filter;
+ this.conditions = toConditionTokens(conditions);
+ this.schema = schema;
this.onClose = onClose;
+ this.filterVectors = new ArrayList<>(schema.getFields().size());
+ this.likePatterns = new HashMap<>();
}
- @Override void evaluateOperator(ArrowRecordBatch arrowRecordBatch) {
+ @Override protected void loadNextArrowBatch() {
+ super.loadNextArrowBatch();
+ final VectorSchemaRoot root;
try {
- this.buf = this.allocator.buffer((long) rowCount * 2);
- this.selectionVector = new SelectionVectorInt16(buf);
- filter.evaluate(arrowRecordBatch, selectionVector);
- } catch (GandivaException e) {
+ root = arrowFileReader.getVectorSchemaRoot();
+ } catch (IOException e) {
throw Util.toUnchecked(e);
}
+ filterVectors.clear();
+ for (int i = 0; i < schema.getFields().size(); i++) {
+ filterVectors.add(root.getVector(i));
+ }
}
@Override public boolean moveNext() {
- if (selectionVector == null
- || selectionVectorIndex >= selectionVector.getRecordCount()) {
- boolean hasNextBatch;
- while (true) {
- try {
- hasNextBatch = arrowFileReader.loadNextBatch();
- } catch (IOException e) {
- throw Util.toUnchecked(e);
+ while (true) {
+ if (currRowIndex >= rowCount - 1) {
+ if (!loadNextNonEmptyArrowBatch()) {
+ return false;
}
- if (hasNextBatch) {
- selectionVectorIndex = 0;
- this.valueVectors.clear();
- loadNextArrowBatch();
- requireNonNull(selectionVector, "selectionVector");
- if (selectionVectorIndex >= selectionVector.getRecordCount()) {
- // the "filtered" batch is empty, but there may be more batches to
fetch
- continue;
- }
- currRowIndex = selectionVector.getIndex(selectionVectorIndex++);
+ }
+ currRowIndex++;
+ if (matches(currRowIndex)) {
+ return true;
+ }
+ }
+ }
+
+ private boolean matches(int rowIndex) {
+ for (List<ConditionToken> orGroup : conditions) {
+ boolean any = false;
+ for (ConditionToken token : orGroup) {
+ if (matches(token, rowIndex)) {
+ any = true;
+ break;
}
- return hasNextBatch;
}
- } else {
- currRowIndex = selectionVector.getIndex(selectionVectorIndex++);
- return true;
+ if (!any) {
+ return false;
+ }
}
+ return true;
}
- @Override public void close() {
- try {
- if (buf != null) {
- buf.close();
+ private boolean matches(ConditionToken token, int rowIndex) {
+ final Object value = getValue(fieldVector(token.fieldName), rowIndex);
+ switch (token.operator) {
+ case IS_NULL:
+ return value == null;
+ case IS_NOT_NULL:
+ return value != null;
+ case IS_TRUE:
+ return Boolean.TRUE.equals(value);
+ case IS_FALSE:
+ return Boolean.FALSE.equals(value);
+ case IS_NOT_TRUE:
+ return !Boolean.TRUE.equals(value);
+ case IS_NOT_FALSE:
+ return !Boolean.FALSE.equals(value);
+ case EQUAL:
+ return value != null && compare(value, literal(token)) == 0;
+ case NOT_EQUAL:
+ return value != null && compare(value, literal(token)) != 0;
+ case LESS_THAN:
+ return value != null && compare(value, literal(token)) < 0;
+ case LESS_THAN_OR_EQUAL:
+ return value != null && compare(value, literal(token)) <= 0;
+ case GREATER_THAN:
+ return value != null && compare(value, literal(token)) > 0;
+ case GREATER_THAN_OR_EQUAL:
+ return value != null && compare(value, literal(token)) >= 0;
+ case LIKE:
+ return value != null
+ && like(value.toString(), requireNonNull(token.value, "value"));
+ default:
+ throw new AssertionError("Unhandled Arrow filter operator: " +
token.operator);
+ }
+ }
+
+ private ValueVector fieldVector(String fieldName) {
+ final Field field = schema.findField(fieldName);
+ final int index = schema.getFields().indexOf(field);
+ if (index < 0) {
+ throw new IllegalArgumentException("Unknown Arrow field: " + fieldName);
+ }
+ return filterVectors.get(index);
+ }
+
+ private static Object literal(ConditionToken token) {
+ final String type = requireNonNull(token.valueType, "valueType");
+ final String value = requireNonNull(token.value, "value");
+ if (type.startsWith("decimal")) {
+ return new BigDecimal(value);
+ } else if (type.equals("integer")) {
+ return Integer.valueOf(value);
+ } else if (type.equals("long")) {
+ return Long.valueOf(value);
+ } else if (type.equals("float")) {
+ return Float.valueOf(value);
+ } else if (type.equals("double")) {
+ return Double.valueOf(value);
+ } else if (type.equals("string")) {
+ return unquote(value);
+ }
+ throw new UnsupportedOperationException("Unsupported literal type: " +
type);
+ }
+
+ private static int compare(Object left, Object right) {
+ if (left instanceof BigDecimal || right instanceof BigDecimal) {
+ return toBigDecimal(left).compareTo(toBigDecimal(right));
+ }
+ if (left instanceof Number && right instanceof Number) {
+ return Double.compare(((Number) left).doubleValue(),
+ ((Number) right).doubleValue());
+ }
+ return left.toString().compareTo(right.toString());
+ }
+
+ private static BigDecimal toBigDecimal(Object value) {
+ if (value instanceof BigDecimal) {
+ return (BigDecimal) value;
+ }
+ return new BigDecimal(value.toString());
+ }
+
+ private boolean like(String value, String pattern) {
+ final String unquotedPattern = unquote(pattern);
+ final Pattern compiledPattern =
+ likePatterns.computeIfAbsent(unquotedPattern, p -> {
+ return Pattern.compile(toRegex(p), Pattern.DOTALL);
+ });
+ return compiledPattern.matcher(value).matches();
+ }
+
+ private static String toRegex(String pattern) {
+ final StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < pattern.length(); i++) {
+ final char c = pattern.charAt(i);
+ if (c == '%') {
+ builder.append(".*");
+ } else if (c == '_') {
+ builder.append('.');
+ } else {
+ builder.append(Pattern.quote(String.valueOf(c)));
}
- filter.close();
- } catch (GandivaException e) {
- throw Util.toUnchecked(e);
- } finally {
- onClose.run();
}
+ return builder.toString();
+ }
+
+ private static String unquote(String value) {
+ if (value.length() >= 2 && value.charAt(0) == '\''
+ && value.charAt(value.length() - 1) == '\'') {
+ return value.substring(1, value.length() - 1).replace("''", "'");
+ }
+ return value;
+ }
+
+ private static List<List<ConditionToken>> toConditionTokens(
+ List<List<List<String>>> conditions) {
+ final List<List<ConditionToken>> result =
+ new ArrayList<>(conditions.size());
+ for (List<List<String>> orGroup : conditions) {
+ final List<ConditionToken> tokens = new ArrayList<>(orGroup.size());
+ for (List<String> token : orGroup) {
+ tokens.add(ConditionToken.fromTokenList(token));
+ }
+ result.add(tokens);
+ }
+ return result;
+ }
+
+ @Override public void close() {
+ onClose.run();
}
}
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowProjectEnumerator.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowProjectEnumerator.java
deleted file mode 100644
index 0895f36cf1..0000000000
---
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowProjectEnumerator.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.calcite.adapter.arrow;
-
-import org.apache.calcite.util.ImmutableIntList;
-import org.apache.calcite.util.Util;
-
-import org.apache.arrow.gandiva.evaluator.Projector;
-import org.apache.arrow.gandiva.exceptions.GandivaException;
-import org.apache.arrow.vector.ipc.ArrowFileReader;
-import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
-
-import java.io.IOException;
-
-/**
- * Enumerator that reads from a projected collection of Arrow value-vectors.
- */
-class ArrowProjectEnumerator extends AbstractArrowEnumerator {
- private final Projector projector;
- private final Runnable onClose;
-
- ArrowProjectEnumerator(ArrowFileReader arrowFileReader, ImmutableIntList
fields,
- Projector projector, Runnable onClose) {
- super(arrowFileReader, fields);
- this.projector = projector;
- this.onClose = onClose;
- }
-
- @Override protected void evaluateOperator(ArrowRecordBatch arrowRecordBatch)
{
- try {
- projector.evaluate(arrowRecordBatch, valueVectors);
- } catch (GandivaException e) {
- throw Util.toUnchecked(e);
- }
- }
-
- @Override public boolean moveNext() {
- if (currRowIndex >= rowCount - 1) {
- final boolean hasNextBatch;
- try {
- hasNextBatch = arrowFileReader.loadNextBatch();
- } catch (IOException e) {
- throw Util.toUnchecked(e);
- }
- if (hasNextBatch) {
- currRowIndex = 0;
- this.valueVectors.clear();
- loadNextArrowBatch();
- }
- return hasNextBatch;
- } else {
- currRowIndex++;
- return true;
- }
- }
-
- @Override public void close() {
- try {
- projector.close();
- } catch (GandivaException e) {
- throw Util.toUnchecked(e);
- } finally {
- onClose.run();
- }
- }
-}
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowRules.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowRules.java
index 6e268d6469..3da1527f10 100644
--- a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowRules.java
+++ b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowRules.java
@@ -100,7 +100,7 @@ RelNode convert(Filter filter) {
final RelTraitSet traitSet =
filter.getTraitSet().replace(ArrowRel.CONVENTION);
// Expand SEARCH (e.g. IN, BETWEEN) before pushing to Arrow,
- // since Gandiva does not support SEARCH natively.
+ // since the Arrow adapter does not support SEARCH natively.
final RexNode condition =
RexUtil.expandSearch(filter.getCluster().getRexBuilder(), null,
filter.getCondition());
return new ArrowFilter(filter.getCluster(), traitSet,
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTable.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTable.java
index 74438efe2a..2585f5d156 100644
--- a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTable.java
+++ b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTable.java
@@ -38,17 +38,9 @@
import org.apache.calcite.util.ImmutableIntList;
import org.apache.calcite.util.Util;
-import org.apache.arrow.gandiva.evaluator.Filter;
-import org.apache.arrow.gandiva.evaluator.Projector;
-import org.apache.arrow.gandiva.exceptions.GandivaException;
-import org.apache.arrow.gandiva.expression.Condition;
-import org.apache.arrow.gandiva.expression.ExpressionTree;
-import org.apache.arrow.gandiva.expression.TreeBuilder;
-import org.apache.arrow.gandiva.expression.TreeNode;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.ipc.ArrowFileReader;
import org.apache.arrow.vector.ipc.SeekableReadChannel;
-import org.apache.arrow.vector.types.pojo.ArrowType;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.Schema;
@@ -58,20 +50,15 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Type;
-import java.util.ArrayList;
import java.util.List;
-import static java.lang.Double.parseDouble;
-import static java.lang.Float.parseFloat;
-import static java.lang.Integer.parseInt;
-import static java.lang.Long.parseLong;
import static java.util.Objects.requireNonNull;
/**
* Table backed by an Apache Arrow file.
*
- * <p>Reads data from an Arrow IPC file on disk and supports projection
- * and filter push-down via the Gandiva expression compiler.
+ * <p>Reads data from an Arrow IPC file on disk. Projections and filters read
+ * directly from Arrow value-vectors.
*
* <p>Implements {@link TranslatableTable} so that it can be converted into
* an {@link ArrowTableScan} for query planning, and {@link QueryableTable}
@@ -116,43 +103,6 @@ public class ArrowTable extends AbstractTable
public Enumerable<Object> query(DataContext root, ImmutableIntList fields,
List<List<List<String>>> conditions) {
requireNonNull(fields, "fields");
- final Projector projector;
- final Filter filter;
-
- if (conditions.isEmpty()) {
- filter = null;
- projector = makeProjector(fields);
- } else {
- projector = null;
-
- final List<TreeNode> conjuncts = new ArrayList<>(conditions.size());
- for (List<List<String>> orGroup : conditions) {
- final List<TreeNode> disjuncts = new ArrayList<>(orGroup.size());
- for (List<String> conditionParts : orGroup) {
- disjuncts.add(
- convertConditionToGandiva(
- ConditionToken.fromTokenList(conditionParts)));
- }
- if (disjuncts.size() == 1) {
- conjuncts.add(disjuncts.get(0));
- } else {
- conjuncts.add(TreeBuilder.makeOr(disjuncts));
- }
- }
- final Condition filterCondition;
- if (conjuncts.size() == 1) {
- filterCondition = TreeBuilder.makeCondition(conjuncts.get(0));
- } else {
- filterCondition =
- TreeBuilder.makeCondition(TreeBuilder.makeAnd(conjuncts));
- }
-
- try {
- filter = Filter.make(schema, filterCondition);
- } catch (GandivaException e) {
- throw Util.toUnchecked(e);
- }
- }
FileInputStream fis = null;
try {
@@ -163,7 +113,7 @@ public Enumerable<Object> query(DataContext root,
ImmutableIntList fields,
final FileInputStream fisRef = fis;
final Runnable onClose = () -> closeSilently(fisRef);
fis = null; // ownership transferred to onClose
- return new ArrowEnumerable(reader, fields, projector, filter, onClose);
+ return new ArrowEnumerable(reader, fields, conditions, schema, onClose);
} catch (IOException e) {
throw Util.toUnchecked(e);
} finally {
@@ -202,70 +152,6 @@ private static RelDataType deduceRowType(Schema schema,
return builder.build();
}
- private @Nullable Projector makeProjector(ImmutableIntList fields) {
- if (requiresDirectVectorProjection(fields)) {
- // Returning null selects ArrowEnumerable's direct vector-read path.
- // Use that path because Gandiva does not support identity projection
- // expressions over Arrow List and binary vectors.
- return null;
- }
-
- final List<ExpressionTree> expressionTrees = new ArrayList<>();
- for (int fieldOrdinal : fields) {
- Field field = schema.getFields().get(fieldOrdinal);
- TreeNode node = TreeBuilder.makeField(field);
- expressionTrees.add(TreeBuilder.makeExpression(node, field));
- }
- try {
- return Projector.make(schema, expressionTrees);
- } catch (GandivaException e) {
- throw Util.toUnchecked(e);
- }
- }
-
- /** Returns whether selected fields should be projected by reading Arrow
- * value-vectors directly rather than by creating a Gandiva projector.
- *
- * <p>CALCITE-7541 extends this direct projection path for Arrow binary
vector
- * families because Gandiva cannot project them through the existing identity
- * projection path. Queries with filters still use Gandiva filters; this
direct
- * path only applies to no-filter projections.
- */
- private boolean requiresDirectVectorProjection(ImmutableIntList fields) {
- for (int fieldOrdinal : fields) {
- switch (schema.getFields().get(fieldOrdinal).getType().getTypeID()) {
- case List:
- case Binary:
- case LargeBinary:
- case FixedSizeBinary:
- return true;
- default:
- break;
- }
- }
- return false;
- }
-
- /** Converts a single {@link ConditionToken} into a Gandiva {@link
TreeNode}. */
- private TreeNode convertConditionToGandiva(ConditionToken token) {
- final List<TreeNode> treeNodes = new ArrayList<>(2);
- treeNodes.add(
- TreeBuilder.makeField(schema.getFields()
- .get(
- schema.getFields().indexOf(
- schema.findField(token.fieldName)))));
-
- if (token.isBinary()) {
- treeNodes.add(
- makeLiteralNode(
- requireNonNull(token.value, "value"),
- requireNonNull(token.valueType, "valueType")));
- }
-
- return TreeBuilder.makeFunction(
- token.operator, treeNodes, new ArrowType.Bool());
- }
-
/** Closes an {@link AutoCloseable} without throwing. */
private static void closeSilently(AutoCloseable closeable) {
try {
@@ -275,29 +161,6 @@ private static void closeSilently(AutoCloseable closeable)
{
}
}
- private static TreeNode makeLiteralNode(String literal, String type) {
- if (type.startsWith("decimal")) {
- String[] typeParts =
- type.substring(type.indexOf('(') + 1, type.indexOf(')')).split(",");
- int precision = parseInt(typeParts[0]);
- int scale = parseInt(typeParts[1]);
- return TreeBuilder.makeDecimalLiteral(literal, precision, scale);
- } else if (type.equals("integer")) {
- return TreeBuilder.makeLiteral(parseInt(literal));
- } else if (type.equals("long")) {
- return TreeBuilder.makeLiteral(parseLong(literal));
- } else if (type.equals("float")) {
- return TreeBuilder.makeLiteral(parseFloat(literal));
- } else if (type.equals("double")) {
- return TreeBuilder.makeLiteral(parseDouble(literal));
- } else if (type.equals("string")) {
- return TreeBuilder.makeStringLiteral(literal.substring(1,
literal.length() - 1));
- } else {
- throw new IllegalArgumentException("Invalid literal " + literal
- + ", type " + type);
- }
- }
-
/**
* Implementation of {@link Queryable} based on a {@link ArrowTable}.
*
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTranslator.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTranslator.java
index 0ec6804052..2b42295981 100644
--- a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTranslator.java
+++ b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTranslator.java
@@ -35,13 +35,26 @@
import java.util.ArrayList;
import java.util.List;
+import static org.apache.calcite.adapter.arrow.ConditionToken.Operator.EQUAL;
+import static
org.apache.calcite.adapter.arrow.ConditionToken.Operator.GREATER_THAN;
+import static
org.apache.calcite.adapter.arrow.ConditionToken.Operator.GREATER_THAN_OR_EQUAL;
+import static
org.apache.calcite.adapter.arrow.ConditionToken.Operator.IS_FALSE;
+import static
org.apache.calcite.adapter.arrow.ConditionToken.Operator.IS_NOT_FALSE;
+import static
org.apache.calcite.adapter.arrow.ConditionToken.Operator.IS_NOT_NULL;
+import static
org.apache.calcite.adapter.arrow.ConditionToken.Operator.IS_NOT_TRUE;
+import static org.apache.calcite.adapter.arrow.ConditionToken.Operator.IS_NULL;
+import static org.apache.calcite.adapter.arrow.ConditionToken.Operator.IS_TRUE;
+import static
org.apache.calcite.adapter.arrow.ConditionToken.Operator.LESS_THAN;
+import static
org.apache.calcite.adapter.arrow.ConditionToken.Operator.LESS_THAN_OR_EQUAL;
+import static org.apache.calcite.adapter.arrow.ConditionToken.Operator.LIKE;
+import static
org.apache.calcite.adapter.arrow.ConditionToken.Operator.NOT_EQUAL;
import static
org.apache.calcite.util.DateTimeStringUtils.ISO_DATETIME_FRACTIONAL_SECOND_FORMAT;
import static org.apache.calcite.util.DateTimeStringUtils.getDateFormatter;
import static java.util.Objects.requireNonNull;
/**
- * Translates a {@link RexNode} expression to Gandiva predicate tokens.
+ * Translates a {@link RexNode} expression to Arrow predicate tokens.
*/
class ArrowTranslator {
final RexBuilder rexBuilder;
@@ -65,7 +78,7 @@ public static ArrowTranslator create(RexBuilder rexBuilder,
*
* <p>If exceeded, {@link RexUtil#toCnf(RexBuilder, int, RexNode)} returns
* the original expression unchanged, which may cause the subsequent
- * translation to Gandiva predicates to fail with an
+ * translation to Arrow predicates to fail with an
* {@link UnsupportedOperationException}. When invoked by the Arrow adapter
* module, the exception is caught and the plan falls back to
* an Enumerable convention. */
@@ -120,32 +133,32 @@ private static Object literalValue(RexLiteral literal) {
private ConditionToken translateMatch2(RexNode node) {
switch (node.getKind()) {
case EQUALS:
- return translateBinary("equal", "=", (RexCall) node);
+ return translateBinary(EQUAL, EQUAL, (RexCall) node);
case NOT_EQUALS:
- return translateBinary("not_equal", "<>", (RexCall) node);
+ return translateBinary(NOT_EQUAL, NOT_EQUAL, (RexCall) node);
case LESS_THAN:
- return translateBinary("less_than", ">", (RexCall) node);
+ return translateBinary(LESS_THAN, GREATER_THAN, (RexCall) node);
case LESS_THAN_OR_EQUAL:
- return translateBinary("less_than_or_equal_to", ">=", (RexCall) node);
+ return translateBinary(LESS_THAN_OR_EQUAL, GREATER_THAN_OR_EQUAL,
(RexCall) node);
case GREATER_THAN:
- return translateBinary("greater_than", "<", (RexCall) node);
+ return translateBinary(GREATER_THAN, LESS_THAN, (RexCall) node);
case GREATER_THAN_OR_EQUAL:
- return translateBinary("greater_than_or_equal_to", "<=", (RexCall) node);
+ return translateBinary(GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL,
(RexCall) node);
case IS_NULL:
- return translateUnary("isnull", (RexCall) node);
+ return translateUnary(IS_NULL, (RexCall) node);
case IS_NOT_NULL:
- return translateUnary("isnotnull", (RexCall) node);
+ return translateUnary(IS_NOT_NULL, (RexCall) node);
case IS_NOT_TRUE:
- return translateUnary("isnottrue", (RexCall) node);
+ return translateUnary(IS_NOT_TRUE, (RexCall) node);
case IS_NOT_FALSE:
- return translateUnary("isnotfalse", (RexCall) node);
+ return translateUnary(IS_NOT_FALSE, (RexCall) node);
case INPUT_REF:
final RexInputRef inputRef = (RexInputRef) node;
- return ConditionToken.unary(fieldNames.get(inputRef.getIndex()),
"istrue");
+ return ConditionToken.unary(fieldNames.get(inputRef.getIndex()),
IS_TRUE);
case NOT:
- return translateUnary("isfalse", (RexCall) node);
+ return translateUnary(IS_FALSE, (RexCall) node);
case LIKE:
- return translateBinary("like", null, (RexCall) node);
+ return translateBinaryNoReverse(LIKE, (RexCall) node);
default:
throw new UnsupportedOperationException("Unsupported operator " + node);
}
@@ -155,7 +168,8 @@ private ConditionToken translateMatch2(RexNode node) {
* Translates a call to a binary operator, reversing arguments if
* necessary.
*/
- private ConditionToken translateBinary(String op, String rop, RexCall call) {
+ private ConditionToken translateBinary(ConditionToken.Operator op,
+ ConditionToken.Operator rop, RexCall call) {
final RexNode left = call.operands.get(0);
final RexNode right = call.operands.get(1);
@Nullable ConditionToken expression = translateBinary2(op, left, right);
@@ -169,9 +183,21 @@ private ConditionToken translateBinary(String op, String
rop, RexCall call) {
throw new UnsupportedOperationException("Unsupported binary operator " +
call);
}
+ /** Translates a call to a binary operator without reversing arguments. */
+ private ConditionToken translateBinaryNoReverse(ConditionToken.Operator op,
+ RexCall call) {
+ final RexNode left = call.operands.get(0);
+ final RexNode right = call.operands.get(1);
+ @Nullable ConditionToken expression = translateBinary2(op, left, right);
+ if (expression != null) {
+ return expression;
+ }
+ throw new UnsupportedOperationException("Unsupported binary operator " +
call);
+ }
+
/** Translates a call to a binary operator. Returns null on failure. */
- private @Nullable ConditionToken translateBinary2(String op, RexNode left,
- RexNode right) {
+ private @Nullable ConditionToken translateBinary2(
+ ConditionToken.Operator op, RexNode left, RexNode right) {
if (right.getKind() != SqlKind.LITERAL) {
return null;
}
@@ -191,7 +217,7 @@ private ConditionToken translateBinary(String op, String
rop, RexCall call) {
/** Combines a field name, operator, and literal to produce a binary
* condition token. */
- private ConditionToken translateOp2(String op, String name,
+ private ConditionToken translateOp2(ConditionToken.Operator op, String name,
RexLiteral right) {
Object value = literalValue(right);
String valueString = value.toString();
@@ -209,7 +235,7 @@ private ConditionToken translateOp2(String op, String name,
}
/** Translates a call to a unary operator. */
- private ConditionToken translateUnary(String op, RexCall call) {
+ private ConditionToken translateUnary(ConditionToken.Operator op, RexCall
call) {
final RexNode opNode = call.operands.get(0);
@Nullable ConditionToken expression = translateUnary2(op, opNode);
@@ -221,7 +247,8 @@ private ConditionToken translateUnary(String op, RexCall
call) {
}
/** Translates a call to a unary operator. Returns null on failure. */
- private @Nullable ConditionToken translateUnary2(String op, RexNode opNode) {
+ private @Nullable ConditionToken translateUnary2(ConditionToken.Operator op,
+ RexNode opNode) {
if (opNode.getKind() == SqlKind.INPUT_REF) {
final RexInputRef inputRef = (RexInputRef) opNode;
final String name = fieldNames.get(inputRef.getIndex());
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ConditionToken.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ConditionToken.java
index 44d3facea7..c5b690840a 100644
--- a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ConditionToken.java
+++ b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ConditionToken.java
@@ -25,7 +25,7 @@
import static java.util.Objects.requireNonNull;
/**
- * A structured representation of a single Gandiva predicate condition.
+ * A structured representation of a single Arrow predicate condition.
*
* <p>A condition is either unary (e.g. {@code IS NULL}) or binary
* (e.g. {@code =}, {@code <}). Unary conditions have a field name
@@ -36,11 +36,11 @@
*/
class ConditionToken {
final String fieldName;
- final String operator;
+ final Operator operator;
final @Nullable String value;
final @Nullable String valueType;
- private ConditionToken(String fieldName, String operator,
+ private ConditionToken(String fieldName, Operator operator,
@Nullable String value, @Nullable String valueType) {
this.fieldName = requireNonNull(fieldName, "fieldName");
this.operator = requireNonNull(operator, "operator");
@@ -50,7 +50,7 @@ private ConditionToken(String fieldName, String operator,
/** Creates a binary condition token
* (e.g. {@code intField equal 12 integer}). */
- static ConditionToken binary(String fieldName, String operator,
+ static ConditionToken binary(String fieldName, Operator operator,
String value, String valueType) {
return new ConditionToken(fieldName, operator,
requireNonNull(value, "value"),
@@ -59,7 +59,7 @@ static ConditionToken binary(String fieldName, String
operator,
/** Creates a unary condition token
* (e.g. {@code intField isnull}). */
- static ConditionToken unary(String fieldName, String operator) {
+ static ConditionToken unary(String fieldName, Operator operator) {
return new ConditionToken(fieldName, operator, null, null);
}
@@ -76,22 +76,55 @@ boolean isBinary() {
* binary conditions. */
List<String> toTokenList() {
if (isBinary()) {
- return ImmutableList.of(fieldName, operator,
+ return ImmutableList.of(fieldName, operator.token,
requireNonNull(value, "value"),
requireNonNull(valueType, "valueType"));
}
- return ImmutableList.of(fieldName, operator);
+ return ImmutableList.of(fieldName, operator.token);
}
/** Creates a {@code ConditionToken} from a serialized string list. */
static ConditionToken fromTokenList(List<String> tokens) {
final int size = tokens.size();
if (size == 4) {
- return binary(tokens.get(0), tokens.get(1),
+ return binary(tokens.get(0), Operator.of(tokens.get(1)),
tokens.get(2), tokens.get(3));
} else if (size == 2) {
- return unary(tokens.get(0), tokens.get(1));
+ return unary(tokens.get(0), Operator.of(tokens.get(1)));
}
throw new IllegalArgumentException("Invalid condition tokens: " + tokens);
}
+
+ /** Operators supported by the Arrow adapter filter representation. */
+ enum Operator {
+ IS_NULL("isnull"),
+ IS_NOT_NULL("isnotnull"),
+ IS_TRUE("istrue"),
+ IS_FALSE("isfalse"),
+ IS_NOT_TRUE("isnottrue"),
+ IS_NOT_FALSE("isnotfalse"),
+ EQUAL("equal"),
+ NOT_EQUAL("not_equal"),
+ LESS_THAN("less_than"),
+ LESS_THAN_OR_EQUAL("less_than_or_equal_to"),
+ GREATER_THAN("greater_than"),
+ GREATER_THAN_OR_EQUAL("greater_than_or_equal_to"),
+ LIKE("like");
+
+ final String token;
+
+ Operator(String token) {
+ this.token = token;
+ }
+
+ static Operator of(String token) {
+ for (Operator operator : values()) {
+ if (operator.token.equals(token)) {
+ return operator;
+ }
+ }
+ throw new UnsupportedOperationException(
+ "Unsupported Arrow filter operator: " + token);
+ }
+ }
}
diff --git
a/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowAdapterTest.java
b/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowAdapterTest.java
index 275bdd0be7..8c9fda7f08 100644
--- a/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowAdapterTest.java
+++ b/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowAdapterTest.java
@@ -82,6 +82,11 @@ static void initializeArrowState(@TempDir Path sharedTempDir)
arrowDataGenerator.writeArrowData(dataLocationFile);
arrowDataGenerator.writeScottEmpData(arrowFilesDirectory);
+ File emptyBatchDataLocationFile =
+ arrowFilesDirectory.resolve("arrowemptybatch.arrow").toFile();
+ ArrowDataTest emptyBatchDataGenerator = new ArrowDataTest();
+
emptyBatchDataGenerator.writeArrowDataWithEmptyBatch(emptyBatchDataLocationFile);
+
File datatypeLocationFile =
arrowFilesDirectory.resolve("arrowdatatype.arrow").toFile();
ArrowDataTest arrowtypeDataGenerator = new ArrowDataTest();
arrowtypeDataGenerator.writeArrowDataType(datatypeLocationFile);
@@ -260,6 +265,51 @@ static void initializeArrowState(@TempDir Path
sharedTempDir)
.explainContains(plan);
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7580">[CALCITE-7580]
+ * Remove Gandiva dependency from Arrow adapter</a>. */
+ @Test void testArrowProjectSkipsEmptyBatch() {
+ String sql = "select \"intField\", \"stringField\" from arrowemptybatch\n";
+ String result = "intField=0; stringField=0\n"
+ + "intField=1; stringField=1\n"
+ + "intField=2; stringField=2\n";
+
+ CalciteAssert.that()
+ .with(arrow)
+ .query(sql)
+ .returns(result);
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7580">[CALCITE-7580]
+ * Remove Gandiva dependency from Arrow adapter</a>. */
+ @Test void testArrowFilterSkipsEmptyBatch() {
+ String sql = "select \"intField\", \"stringField\"\n"
+ + "from arrowemptybatch\n"
+ + "where \"intField\" > 0";
+ String result = "intField=1; stringField=1\n"
+ + "intField=2; stringField=2\n";
+
+ CalciteAssert.that()
+ .with(arrow)
+ .query(sql)
+ .returns(result);
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7580">[CALCITE-7580]
+ * Remove Gandiva dependency from Arrow adapter</a>. */
+ @Test void testArrowFilterSkipsEmptyBatchWithNoMatches() {
+ String sql = "select \"intField\", \"stringField\"\n"
+ + "from arrowemptybatch\n"
+ + "where \"intField\" < 0";
+
+ CalciteAssert.that()
+ .with(arrow)
+ .query(sql)
+ .returns("");
+ }
+
@Test void testArrowProjectFieldsWithIntegerFilter() {
String sql = "select \"intField\", \"stringField\"\n"
+ "from arrowdata\n"
diff --git
a/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowDataTest.java
b/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowDataTest.java
index a53cc1231a..4b9af441aa 100644
--- a/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowDataTest.java
+++ b/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowDataTest.java
@@ -301,6 +301,35 @@ public void writeArrowBinaryData(File file) throws
IOException {
fileOutputStream.close();
}
+ public void writeArrowDataWithEmptyBatch(File file) throws IOException {
+ Schema arrowSchema = makeArrowSchema();
+ try (RootAllocator allocator = new RootAllocator(Integer.MAX_VALUE);
+ VectorSchemaRoot vectorSchemaRoot =
+ VectorSchemaRoot.create(arrowSchema, allocator);
+ FileOutputStream fileOutputStream = new FileOutputStream(file);
+ ArrowFileWriter arrowFileWriter =
+ new ArrowFileWriter(vectorSchemaRoot, null,
+ fileOutputStream.getChannel())) {
+ arrowFileWriter.start();
+
+ vectorSchemaRoot.setRowCount(0);
+ for (Field field : vectorSchemaRoot.getSchema().getFields()) {
+ vectorSchemaRoot.getVector(field.getName()).setValueCount(0);
+ }
+ arrowFileWriter.writeBatch();
+
+ int rowCount = 3;
+ vectorSchemaRoot.setRowCount(rowCount);
+ intField(vectorSchemaRoot.getVector("intField"), rowCount);
+ varCharField(vectorSchemaRoot.getVector("stringField"), rowCount);
+ floatField(vectorSchemaRoot.getVector("floatField"), rowCount);
+ longField(vectorSchemaRoot.getVector("longField"), rowCount);
+ arrowFileWriter.writeBatch();
+
+ arrowFileWriter.end();
+ }
+ }
+
public void writeArrowDataType(File file) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(file);
Schema arrowSchema = makeArrowDateTypeSchema();
diff --git
a/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowExtension.java
b/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowExtension.java
index ab1f5c2a88..4600dab100 100644
--- a/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowExtension.java
+++ b/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowExtension.java
@@ -18,22 +18,12 @@
import org.apache.calcite.config.CalciteSystemProperty;
-import org.apache.arrow.gandiva.evaluator.Projector;
-import org.apache.arrow.gandiva.exceptions.GandivaException;
-import org.apache.arrow.gandiva.expression.ExpressionTree;
-import org.apache.arrow.vector.types.pojo.Schema;
-
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* JUnit5 extension to handle Arrow tests.
- *
- * <p>Tests will be skipped if the Gandiva library cannot be loaded on the
given platform.
*/
class ArrowExtension implements ExecutionCondition {
@@ -41,8 +31,7 @@ class ArrowExtension implements ExecutionCondition {
* Whether to run this test.
*
* <p>Enabled by default, unless explicitly disabled from command line
- * ({@code -Dcalcite.test.arrow=false}) or if Gandiva library, used to
implement arrow
- * filtering/projection, cannot be loaded.
+ * ({@code -Dcalcite.test.arrow=false}).
*
* @return {@code true} if the test is enabled and can run in the current
environment,
* {@code false} otherwise
@@ -51,16 +40,6 @@ class ArrowExtension implements ExecutionCondition {
final ExtensionContext context) {
boolean enabled = CalciteSystemProperty.TEST_ARROW.value();
- try {
- Schema emptySchema = new Schema(new ArrayList<>(), null);
- List<ExpressionTree> expressions = new ArrayList<>();
- Projector.make(emptySchema, expressions);
- } catch (GandivaException e) {
- // this exception comes from using an empty expression,
- // but the JNI library was loaded properly
- } catch (UnsatisfiedLinkError e) {
- enabled = false;
- }
if (enabled) {
return ConditionEvaluationResult.enabled("Arrow tests enabled");
diff --git a/bom/build.gradle.kts b/bom/build.gradle.kts
index f00ed7d8e5..64cb532afd 100644
--- a/bom/build.gradle.kts
+++ b/bom/build.gradle.kts
@@ -101,7 +101,6 @@ fun DependencyConstraintHandlerScope.runtimev(
apiv("org.apache.arrow:arrow-memory-netty", "arrow")
apiv("org.apache.arrow:arrow-vector", "arrow")
apiv("org.apache.arrow:arrow-jdbc", "arrow")
- apiv("org.apache.arrow.gandiva:arrow-gandiva", "arrow-gandiva")
apiv("org.apache.calcite.avatica:avatica-core", "calcite.avatica")
apiv("org.apache.calcite.avatica:avatica-server", "calcite.avatica")
apiv("org.apache.cassandra:cassandra-all")
diff --git a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
index 0ffb60454e..4d7702295b 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
@@ -529,14 +529,11 @@ private RexNode simplifyLike(RexCall e, RexUnknownAs
unknownAs) {
}
if (e.operands.size() == 3 && e.operands.get(2) instanceof RexLiteral) {
final RexLiteral escapeLiteral = (RexLiteral) e.operands.get(2);
- final String escapeStr =
requireNonNull(escapeLiteral.getValueAs(String.class));
- if (escapeStr.length() == 1) {
- char escape = escapeStr.charAt(0);
- e = (RexCall) rexBuilder
- .makeCall(e.getParserPosition(), e.getOperator(),
e.operands.get(0),
- rexBuilder.makeLiteral(simplifyLikeString(likeStr, escape,
'%'),
- e.operands.get(1).getType(), true, true), escapeLiteral);
- }
+ Character escape =
requireNonNull(escapeLiteral.getValueAs(Character.class));
+ e = (RexCall) rexBuilder
+ .makeCall(e.getParserPosition(), e.getOperator(),
e.operands.get(0),
+ rexBuilder.makeLiteral(simplifyLikeString(likeStr, escape,
'%'),
+ e.operands.get(1).getType(), true, true), escapeLiteral);
}
}
return simplifyGenericNode(e);
@@ -546,7 +543,7 @@ private RexNode simplifyLike(RexCall e, RexUnknownAs
unknownAs) {
// string with even escapes 'AA\\\\%%__%%AA' simplify to 'AA\\__%AA'
// string with odd escapes 'AA\\\\\\%%__%%AA' simplify to 'AA\\\\\\%__%AA'
private String simplifyMixedWildcards(String str, char escape) {
- Pattern pattern = getWildCardPattern(escape);
+ Pattern pattern = Pattern.compile("[_%]+");
Matcher matcher = pattern.matcher(str);
StringBuilder builder = new StringBuilder();
int from = 0;
@@ -570,17 +567,6 @@ && consecutiveSameCharCountBefore(str, start - 1, escape)
% 2 == 1) {
return builder.toString();
}
- private static Pattern getWildCardPattern(char escape) {
- switch (escape) {
- case '%':
- return Pattern.compile("_+");
- case '_':
- return Pattern.compile("%+");
- default:
- return Pattern.compile("[_%]+");
- }
- }
-
// Tool method: count the number of consecutive identical characters before
index
private int consecutiveSameCharCountBefore(String str, int index, char
escape) {
int count = 0;
diff --git
a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFactoryImpl.java
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFactoryImpl.java
index d0ccab7dfd..115b66fa21 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFactoryImpl.java
@@ -561,7 +561,7 @@ private static void assertBasic(SqlTypeName typeName) {
}
}
- if (type.getSqlTypeName().getFamily() ==
resultType.getSqlTypeName().getFamily()
+ if (type.getSqlTypeName() == resultType.getSqlTypeName()
&& type.getSqlTypeName().allowsPrec()
&& type.getPrecision() != resultType.getPrecision()) {
final int precision =
diff --git
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 32017ace1d..4622176a07 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -1870,7 +1870,7 @@ public RelNode convertToSingleValueSubq(
if (leftKeys.size() == 1) {
SqlCall sqlCall =
comparisonOp.createCall(rightVals.getParserPosition(),
leftKeys.get(0), rightVals);
- rexComparison = ensureComparisonTypes(bb.convertExpression(sqlCall));
+ rexComparison = bb.convertExpression(sqlCall);
} else {
assert rightVals instanceof SqlCall;
final SqlBasicCall call = (SqlBasicCall) rightVals;
@@ -1880,10 +1880,9 @@ public RelNode convertToSingleValueSubq(
RexUtil.composeConjunction(rexBuilder,
transform(
Pair.zip(leftKeys, call.getOperandList()),
- pair -> ensureComparisonTypes(
- bb.convertExpression(
-
comparisonOp.createCall(rightVals.getParserPosition(),
- pair.left, pair.right)))));
+ pair -> bb.convertExpression(
+ comparisonOp.createCall(rightVals.getParserPosition(),
+ pair.left, pair.right))));
}
comparisons.add(rexComparison);
}
@@ -1902,31 +1901,6 @@ public RelNode convertToSingleValueSubq(
}
}
- /**
- * Ensures that a comparison expression has matching operand types. If the
- * operands have different type names, casts the right operand to match the
- * left operand's type. This handles the case where type coercion is disabled
- * and the IN-to-OR expansion produces comparisons with mismatched types
- * (e.g., DATE = CHAR).
- */
- private RexNode ensureComparisonTypes(RexNode node) {
- if (validator != null && validator.config().typeCoercionEnabled()) {
- return node;
- }
- if (node instanceof RexCall) {
- final RexCall call = (RexCall) node;
- if (call.operands.size() == 2) {
- final RexNode left = call.operands.get(0);
- final RexNode right = call.operands.get(1);
- if (left.getType().getSqlTypeName() !=
right.getType().getSqlTypeName()) {
- final RexNode castRight = rexBuilder.ensureType(left.getType(),
right, true);
- return rexBuilder.makeCall(call.getOperator(), left, castRight);
- }
- }
- }
- return node;
- }
-
/**
* Converts a {@link SqlNodeList} (for example an IN-list or VALUES list)
* into a relational expression and produces a Rex-level sub-query that
diff --git a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
index ac7b5aebe6..2bdc7a1d56 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexProgramTest.java
@@ -4358,10 +4358,6 @@ private void checkSarg(String message, Sarg sarg,
* Multiple consecutive '%' in the string matched by LIKE should simplify to
a single '%'</a>,
* <a
href="https://issues.apache.org/jira/browse/CALCITE-7153">[CALCITE-7153]
* Mixed wildcards of _ and % need to be simplified in LIKE operator</a>.
- * <a
href="https://issues.apache.org/jira/browse/CALCITE-7578">[CALCITE-7578]
- * LIKE with empty ESCAPE might fail with
StringIndexOutOfBoundsException</a>.
- * <a
href="https://issues.apache.org/jira/browse/CALCITE-7588">[CALCITE-7588]
- * LIKE with ESCAPE symbols containing wildcards fails</a>.
* */
@Test void testSimplifyLike() {
final RexNode ref = input(tVarchar(true, 10), 0);
@@ -4425,14 +4421,6 @@ private void checkSarg(String message, Sarg sarg,
"LIKE($0, '###%%#%#%A#%%#%A%###%%', '#')");
checkSimplifyUnchanged(like(ref, literal("A"), literal("#")));
checkSimplifyUnchanged(like(ref, literal("%A"), literal("#")));
- checkSimplifyUnchanged(like(ref, literal("TE%_ST"), literal("%")));
- checkSimplifyUnchanged(like(ref, literal("TE%%ST"), literal("%")));
- checkSimplifyUnchanged(like(ref, literal("a%_b%%c"), literal("%")));
- checkSimplifyUnchanged(like(ref, literal("%_%%A%_"), literal("%")));
- // escape char equal to the '_' wildcard. '%E__S%' ESCAPE '_' is a literal
'_'.
- checkSimplifyUnchanged(like(ref, literal("%E__S%"), literal("_")));
- checkSimplifyUnchanged(like(ref, literal("TE_%ST"), literal("_")));
- checkSimplifyUnchanged(like(ref, literal("a_%b__c"), literal("_")));
// As above, but ref is NOT NULL
final RexNode refMandatory = vVarcharNotNull(0);
@@ -4463,14 +4451,6 @@ private void checkSarg(String message, Sarg sarg,
// NOT(SIMILAR TO) is not optimized
checkSimplifyUnchanged(
not(rexBuilder.makeCall(SqlStdOperatorTable.SIMILAR_TO, ref,
literal("%"))));
-
- try {
- // Empty ESCAPE
- checkSimplifyUnchanged(like(ref, literal("a"), literal("")));
- } catch (RuntimeException e) {
- assertThat(e.getMessage(),
- containsString("Invalid escape character ''"));
- }
}
@Test void testSimplifyNullCheckInFilter() {
diff --git
a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
index f0ff190d28..8f5e5e4018 100644
--- a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java
@@ -33,7 +33,6 @@
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasToString;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -173,30 +172,6 @@ class SqlTypeFactoryTest {
assertThat(leastRestrictive.getPrecision(), is(3));
}
- /**
- * Test case for
- * <a href="https://issues.apache.org/jira/browse/CALCITE-7567">
- * LeastRetrictiveSqlType for TIMESTAMP, TIMESTAMP_LTZ might ignore
precision</a>. */
- @Test void testLeastRestrictiveForTimestampAndTimestampLtz() {
- SqlTypeFixture f = new SqlTypeFixture();
- RelDataType ltz0 =
-
f.typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 0);
- RelDataType leastRestrictive =
- f.typeFactory.leastRestrictive(Lists.newArrayList(ltz0,
f.sqlTimestampPrec3));
- assertThat(leastRestrictive, is(notNullValue()));
- assertThat(leastRestrictive.getPrecision(), is(3));
- }
-
- @Test void testLeastRestrictiveForTimestampLtzAndTimestamp() {
- SqlTypeFixture f = new SqlTypeFixture();
- RelDataType ltz0 =
-
f.typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, 0);
- RelDataType leastRestrictive =
- f.typeFactory.leastRestrictive(Lists.newArrayList(f.sqlTimestampPrec3,
ltz0));
- assertThat(leastRestrictive, is(notNullValue()));
- assertThat(leastRestrictive.getPrecision(), is(3));
- }
-
@Test void testLeastRestrictiveForTimestampAndDate() {
SqlTypeFixture f = new SqlTypeFixture();
RelDataType leastRestrictive =
diff --git
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 723d749be6..4e8d17cd7a 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -2213,24 +2213,6 @@ void checkCorrelatedMapSubQuery(boolean expand) {
sql(sql).withExpand(false).ok();
}
- /** Test case for
- * <a
href="https://issues.apache.org/jira/browse/CALCITE-7562">[CALCITE-7562]
- * SqlToRel misses CAST in case IN expression without type coercion</a>. */
- @Test void testInDateColumnWithoutTypeCoercion() {
- final String sql =
- "select * from emp_b where birthdate in ('2000-06-30', '2000-09-27')";
- sql(sql).withTypeCoercion(false).ok();
- }
-
- /** Test case for
- * <a
href="https://issues.apache.org/jira/browse/CALCITE-7562">[CALCITE-7562]
- * SqlToRel misses CAST in case IN expression without type coercion</a>. */
- @Test void testInDateColumnWithTypeCoercion() {
- final String sql =
- "select * from emp_b where birthdate in ('2000-06-30', '2000-09-27')";
- sql(sql).ok();
- }
-
@Test void testInValueListLong() {
// Go over the default threshold of 20 to force a sub-query.
final String sql = "select empno from emp where deptno in"
diff --git
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 8aa2171d3f..5ee7180efa 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -3266,30 +3266,6 @@ LogicalFilter(condition=[=($cor0.DEPTNO, $7)])
LogicalAggregate(group=[{0}], S=[SUM($1)], agg#1=[COUNT()])
LogicalProject(DEPTNO=[$7], SAL=[$5])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testInDateColumnWithTypeCoercion">
- <Resource name="sql">
- <![CDATA[select * from emp_b where birthdate in ('2000-06-30',
'2000-09-27')]]>
- </Resource>
- <Resource name="plan">
- <![CDATA[
-LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], BIRTHDATE=[$9])
- LogicalFilter(condition=[OR(=($9, CAST('2000-06-30'):DATE NOT NULL), =($9,
CAST('2000-09-27'):DATE NOT NULL))])
- LogicalTableScan(table=[[CATALOG, SALES, EMP_B]])
-]]>
- </Resource>
- </TestCase>
- <TestCase name="testInDateColumnWithoutTypeCoercion">
- <Resource name="sql">
- <![CDATA[select * from emp_b where birthdate in ('2000-06-30',
'2000-09-27')]]>
- </Resource>
- <Resource name="plan">
- <![CDATA[
-LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], BIRTHDATE=[$9])
- LogicalFilter(condition=[OR(=($9, CAST('2000-06-30'):DATE NOT NULL), =($9,
CAST('2000-09-27'):DATE NOT NULL))])
- LogicalTableScan(table=[[CATALOG, SALES, EMP_B]])
]]>
</Resource>
</TestCase>
diff --git a/gradle.properties b/gradle.properties
index eb0bd778ae..fd82318264 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -81,7 +81,6 @@ jandex.version=3.5.3
# elasticsearch does not like asm:6.2.1+
aggdesigner-algorithm.version=6.1
apiguardian-api.version=1.1.2
-arrow-gandiva.version=15.0.0
arrow.version=15.0.0
asm.version=9.9.1
byte-buddy.version=1.18.8
diff --git a/site/_docs/history.md b/site/_docs/history.md
index aba03c8f3c..ac3be25990 100644
--- a/site/_docs/history.md
+++ b/site/_docs/history.md
@@ -49,6 +49,11 @@ ## <a
href="https://github.com/apache/calcite/releases/tag/calcite-1.43.0">1.43.
#### Breaking Changes
{: #breaking-1-43-0}
+* [<a
href="https://issues.apache.org/jira/browse/CALCITE-7580">CALCITE-7580</a>]
+ Remove Gandiva dependency from Arrow adapter. Arrow adapter projection and
+ filter evaluation now run in Java, and the `arrow-gandiva` dependency is no
+ longer included in the Arrow module or BOM.
+
#### New features
{: #new-features-1-43-0}
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlToRelFixture.java
b/testkit/src/main/java/org/apache/calcite/test/SqlToRelFixture.java
index 784cb1eba1..a6685cf952 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlToRelFixture.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlToRelFixture.java
@@ -178,11 +178,6 @@ public SqlToRelFixture withConformance(SqlConformance
conformance) {
.withValidatorConfig(c -> c.withConformance(conformance)));
}
- public SqlToRelFixture withTypeCoercion(boolean enabled) {
- return withFactory(f ->
- f.withValidatorConfig(c -> c.withTypeCoercionEnabled(enabled)));
- }
-
public SqlToRelFixture withDiffRepos(DiffRepository diffRepos) {
return new SqlToRelFixture(sql, decorrelate, tester, factory, trim,
expression, diffRepos);