This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink-table-store.git
The following commit(s) were added to refs/heads/master by this push:
new 9844ed49 [FLINK-28289] Introduce Spark2 Reader for table store
9844ed49 is described below
commit 9844ed49085c6729e34403050d4e857ea0b6a9aa
Author: Nicholas Jiang <[email protected]>
AuthorDate: Thu Jul 14 15:17:33 2022 +0800
[FLINK-28289] Introduce Spark2 Reader for table store
This closes #213
---
docs/content/docs/engines/build.md | 4 +
docs/content/docs/engines/spark2.md | 68 ++++++
docs/content/docs/engines/trino.md | 2 +-
flink-table-store-spark2/pom.xml | 110 ++++++++++
.../flink/table/store/spark/SparkArrayData.java | 171 +++++++++++++++
.../table/store/spark/SparkDataSourceReader.java | 139 ++++++++++++
.../table/store/spark/SparkFilterConverter.java | 128 +++++++++++
.../table/store/spark/SparkInputPartition.java | 127 +++++++++++
.../flink/table/store/spark/SparkInternalRow.java | 240 +++++++++++++++++++++
.../flink/table/store/spark/SparkSource.java | 50 +++++
.../flink/table/store/spark/SparkTypeUtils.java | 185 ++++++++++++++++
.../store/spark/SpecializedGettersReader.java | 106 +++++++++
...org.apache.spark.sql.sources.DataSourceRegister | 16 ++
.../table/store/spark/SimpleTableTestHelper.java | 70 ++++++
.../store/spark/SparkFilterConverterTest.java | 176 +++++++++++++++
.../table/store/spark/SparkInternalRowTest.java | 109 ++++++++++
.../flink/table/store/spark/SparkReadITCase.java | 130 +++++++++++
.../flink/table/store/spark/SparkTypeTest.java | 99 +++++++++
.../src/test/resources/log4j2-test.properties | 38 ++++
pom.xml | 1 +
20 files changed, 1968 insertions(+), 1 deletion(-)
diff --git a/docs/content/docs/engines/build.md
b/docs/content/docs/engines/build.md
index 107020c6..b221f07b 100644
--- a/docs/content/docs/engines/build.md
+++ b/docs/content/docs/engines/build.md
@@ -50,6 +50,10 @@ You can find Hive bundled jar in
`./flink-table-store-hive/flink-table-store-hiv
You can find Spark bundled jar in
`./flink-table-store-spark/target/flink-table-store-spark-{{< version >}}.jar`.
+## Spark2
+
+You can find Spark2 bundled jar in
`./flink-table-store-spark2/target/flink-table-store-spark2-{{< version
>}}.jar`.
+
## Flink 1.14
Running:
diff --git a/docs/content/docs/engines/spark2.md
b/docs/content/docs/engines/spark2.md
new file mode 100644
index 00000000..c8976b99
--- /dev/null
+++ b/docs/content/docs/engines/spark2.md
@@ -0,0 +1,68 @@
+---
+title: "Spark2"
+weight: 4
+type: docs
+aliases:
+- /engines/spark2.html
+---
+<!--
+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.
+-->
+
+# Spark2
+
+Table Store supports reading table store tables through Spark.
+
+## Version
+
+Table Store supports Spark 2.4+. It is highly recommended to use Spark 2.4+
version with many improvements.
+
+## Install
+
+{{< stable >}}
+Download [flink-table-store-spark2-{{< version
>}}.jar](https://repo.maven.apache.org/maven2/org/apache/flink/flink-table-store-spark2/{{<
version >}}/flink-table-store-spark2-{{< version >}}.jar).
+{{< /stable >}}
+{{< unstable >}}
+You are using an unreleased version of Table Store, you need to manually
[Build Spark Bundled Jar]({{< ref "docs/engines/build" >}}) from the source
code.
+{{< /unstable >}}
+
+Use `--jars` in spark-sql:
+```bash
+spark-sql ... --jars flink-table-store-spark2-{{< version >}}.jar
+```
+
+Alternatively, you can copy `flink-table-store-spark2-{{< version >}}.jar`
under `spark/jars` in your Spark installation.
+
+## Create Temporary View
+
+Use the `CREATE TEMPORARY VIEW` command to create a Spark mapping table on top
of
+an existing Table Store table.
+
+```sql
+CREATE TEMPORARY VIEW myTable
+USING tablestore
+OPTIONS (
+ path "file:/tmp/warehouse/default.db/myTable"
+)
+```
+
+## Query Table
+
+```sql
+SELECT * FROM table_store.default.myTable;
+```
diff --git a/docs/content/docs/engines/trino.md
b/docs/content/docs/engines/trino.md
index c2c308b9..50bae94a 100644
--- a/docs/content/docs/engines/trino.md
+++ b/docs/content/docs/engines/trino.md
@@ -1,6 +1,6 @@
---
title: "Trino"
-weight: 4
+weight: 5
type: docs
aliases:
- /engines/trino.html
diff --git a/flink-table-store-spark2/pom.xml b/flink-table-store-spark2/pom.xml
new file mode 100644
index 00000000..4ee55da0
--- /dev/null
+++ b/flink-table-store-spark2/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>flink-table-store-parent</artifactId>
+ <groupId>org.apache.flink</groupId>
+ <version>0.2-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>flink-table-store-spark2</artifactId>
+ <name>Flink Table Store : Spark2</name>
+
+ <packaging>jar</packaging>
+
+ <properties>
+ <spark2.version>2.4.8</spark2.version>
+ <jackson.version>2.13.3</jackson.version>
+ </properties>
+
+ <dependencies>
+ <!-- Flink All dependencies -->
+ <dependency>
+ <groupId>org.apache.flink</groupId>
+ <artifactId>flink-table-store-shade</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.spark</groupId>
+ <artifactId>spark-sql_2.11</artifactId>
+ <version>${spark2.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.avro</groupId>
+ <artifactId>avro</artifactId>
+ </exclusion>
+ <!-- The Jackson dependencies of Spark 2.4.8 have
vulnerabilities. -->
+ <exclusion>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-scala_2.11</artifactId>
+ <version>${jackson.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>shade-flink</id>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <artifactSet>
+ <includes combine.children="append">
+
<include>org.apache.flink:flink-table-store-shade</include>
+ </includes>
+ </artifactSet>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git
a/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkArrayData.java
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkArrayData.java
new file mode 100644
index 00000000..969691b2
--- /dev/null
+++
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkArrayData.java
@@ -0,0 +1,171 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.table.store.utils.RowDataUtils;
+import org.apache.flink.table.types.logical.ArrayType;
+import org.apache.flink.table.types.logical.BigIntType;
+import org.apache.flink.table.types.logical.LogicalType;
+import org.apache.flink.table.types.logical.RowType;
+
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.catalyst.util.ArrayData;
+import org.apache.spark.sql.catalyst.util.MapData;
+import org.apache.spark.sql.types.DataType;
+import org.apache.spark.sql.types.Decimal;
+import org.apache.spark.unsafe.types.CalendarInterval;
+import org.apache.spark.unsafe.types.UTF8String;
+
+import static org.apache.flink.table.store.spark.SparkInternalRow.fromFlink;
+import static org.apache.flink.table.store.utils.RowDataUtils.copyArray;
+import static org.apache.flink.table.store.utils.TypeUtils.timestampPrecision;
+
+/** Spark {@link ArrayData} to wrap Flink {@code ArrayData}. */
+public class SparkArrayData extends ArrayData {
+
+ private final LogicalType elementType;
+
+ private org.apache.flink.table.data.ArrayData array;
+
+ public SparkArrayData(LogicalType elementType) {
+ this.elementType = elementType;
+ }
+
+ public SparkArrayData replace(org.apache.flink.table.data.ArrayData array)
{
+ this.array = array;
+ return this;
+ }
+
+ @Override
+ public int numElements() {
+ return array.size();
+ }
+
+ @Override
+ public ArrayData copy() {
+ return new SparkArrayData(elementType).replace(copyArray(array,
elementType));
+ }
+
+ @Override
+ public Object[] array() {
+ Object[] objects = new Object[numElements()];
+ for (int i = 0; i < objects.length; i++) {
+ objects[i] = fromFlink(RowDataUtils.get(array, i, elementType),
elementType);
+ }
+ return objects;
+ }
+
+ @Override
+ public void setNullAt(int i) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void update(int i, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isNullAt(int ordinal) {
+ return array.isNullAt(ordinal);
+ }
+
+ @Override
+ public boolean getBoolean(int ordinal) {
+ return array.getBoolean(ordinal);
+ }
+
+ @Override
+ public byte getByte(int ordinal) {
+ return array.getByte(ordinal);
+ }
+
+ @Override
+ public short getShort(int ordinal) {
+ return array.getShort(ordinal);
+ }
+
+ @Override
+ public int getInt(int ordinal) {
+ return array.getInt(ordinal);
+ }
+
+ @Override
+ public long getLong(int ordinal) {
+ if (elementType instanceof BigIntType) {
+ return array.getLong(ordinal);
+ }
+
+ return getTimestampMicros(ordinal);
+ }
+
+ private long getTimestampMicros(int ordinal) {
+ return fromFlink(array.getTimestamp(ordinal,
timestampPrecision(elementType)));
+ }
+
+ @Override
+ public float getFloat(int ordinal) {
+ return array.getFloat(ordinal);
+ }
+
+ @Override
+ public double getDouble(int ordinal) {
+ return array.getDouble(ordinal);
+ }
+
+ @Override
+ public Decimal getDecimal(int ordinal, int precision, int scale) {
+ return fromFlink(array.getDecimal(ordinal, precision, scale));
+ }
+
+ @Override
+ public UTF8String getUTF8String(int ordinal) {
+ return fromFlink(array.getString(ordinal));
+ }
+
+ @Override
+ public byte[] getBinary(int ordinal) {
+ return array.getBinary(ordinal);
+ }
+
+ @Override
+ public CalendarInterval getInterval(int ordinal) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InternalRow getStruct(int ordinal, int numFields) {
+ return fromFlink(array.getRow(ordinal, numFields), (RowType)
elementType);
+ }
+
+ @Override
+ public ArrayData getArray(int ordinal) {
+ return fromFlink(array.getArray(ordinal), (ArrayType) elementType);
+ }
+
+ @Override
+ public MapData getMap(int ordinal) {
+ return fromFlink(array.getMap(ordinal), elementType);
+ }
+
+ @Override
+ public Object get(int ordinal, DataType dataType) {
+ return SpecializedGettersReader.read(this, ordinal, dataType);
+ }
+}
diff --git
a/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkDataSourceReader.java
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkDataSourceReader.java
new file mode 100644
index 00000000..900e0405
--- /dev/null
+++
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkDataSourceReader.java
@@ -0,0 +1,139 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.table.store.file.data.DataFileMeta;
+import org.apache.flink.table.store.file.predicate.Predicate;
+import org.apache.flink.table.store.table.FileStoreTable;
+import org.apache.flink.table.store.table.source.Split;
+import org.apache.flink.table.store.utils.TypeUtils;
+import org.apache.flink.table.types.logical.RowType;
+
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.sources.Filter;
+import org.apache.spark.sql.sources.v2.reader.DataSourceReader;
+import org.apache.spark.sql.sources.v2.reader.InputPartition;
+import org.apache.spark.sql.sources.v2.reader.Statistics;
+import org.apache.spark.sql.sources.v2.reader.SupportsPushDownFilters;
+import org.apache.spark.sql.sources.v2.reader.SupportsPushDownRequiredColumns;
+import org.apache.spark.sql.sources.v2.reader.SupportsReportStatistics;
+import org.apache.spark.sql.types.StructType;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.OptionalLong;
+import java.util.stream.Collectors;
+
+/** A Spark {@link DataSourceReader} for table store. */
+public class SparkDataSourceReader
+ implements SupportsPushDownFilters,
+ SupportsPushDownRequiredColumns,
+ SupportsReportStatistics {
+
+ private final FileStoreTable table;
+
+ private List<Predicate> predicates = new ArrayList<>();
+ private Filter[] pushedFilters;
+ private int[] projectedFields;
+ private List<Split> splits;
+
+ public SparkDataSourceReader(FileStoreTable table) {
+ this.table = table;
+ }
+
+ @Override
+ public Filter[] pushFilters(Filter[] filters) {
+ SparkFilterConverter converter = new
SparkFilterConverter(table.schema().logicalRowType());
+ List<Predicate> predicates = new ArrayList<>();
+ List<Filter> pushed = new ArrayList<>();
+ for (Filter filter : filters) {
+ try {
+ predicates.add(converter.convert(filter));
+ pushed.add(filter);
+ } catch (UnsupportedOperationException ignore) {
+ }
+ }
+ this.predicates = predicates;
+ this.pushedFilters = pushed.toArray(new Filter[0]);
+ return filters;
+ }
+
+ @Override
+ public Filter[] pushedFilters() {
+ return pushedFilters;
+ }
+
+ @Override
+ public void pruneColumns(StructType requiredSchema) {
+ String[] pruneFields = requiredSchema.fieldNames();
+ List<String> fieldNames = table.schema().fieldNames();
+ int[] projected = new int[pruneFields.length];
+ for (int i = 0; i < projected.length; i++) {
+ projected[i] = fieldNames.indexOf(pruneFields[i]);
+ }
+ this.projectedFields = projected;
+ }
+
+ @Override
+ public Statistics estimateStatistics() {
+ long rowCount = 0L;
+
+ for (Split split : splits()) {
+ for (DataFileMeta file : split.files()) {
+ rowCount += file.rowCount();
+ }
+ }
+
+ final long numRows = rowCount;
+ final long sizeInBytes = readSchema().defaultSize() * numRows;
+
+ return new Statistics() {
+ @Override
+ public OptionalLong sizeInBytes() {
+ return OptionalLong.of(sizeInBytes);
+ }
+
+ @Override
+ public OptionalLong numRows() {
+ return OptionalLong.of(numRows);
+ }
+ };
+ }
+
+ @Override
+ public StructType readSchema() {
+ RowType rowType = table.schema().logicalRowType();
+ return SparkTypeUtils.fromFlinkRowType(
+ projectedFields == null ? rowType : TypeUtils.project(rowType,
projectedFields));
+ }
+
+ @Override
+ public List<InputPartition<InternalRow>> planInputPartitions() {
+ return splits().stream()
+ .map(split -> new SparkInputPartition(table, projectedFields,
predicates, split))
+ .collect(Collectors.toList());
+ }
+
+ protected List<Split> splits() {
+ if (splits == null) {
+ this.splits = table.newScan().withFilter(predicates).plan().splits;
+ }
+ return splits;
+ }
+}
diff --git
a/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkFilterConverter.java
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkFilterConverter.java
new file mode 100644
index 00000000..285911bb
--- /dev/null
+++
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkFilterConverter.java
@@ -0,0 +1,128 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.table.store.file.predicate.Predicate;
+import org.apache.flink.table.store.file.predicate.PredicateBuilder;
+import org.apache.flink.table.types.logical.LogicalType;
+import org.apache.flink.table.types.logical.RowType;
+
+import org.apache.spark.sql.sources.And;
+import org.apache.spark.sql.sources.EqualTo;
+import org.apache.spark.sql.sources.Filter;
+import org.apache.spark.sql.sources.GreaterThan;
+import org.apache.spark.sql.sources.GreaterThanOrEqual;
+import org.apache.spark.sql.sources.In;
+import org.apache.spark.sql.sources.IsNotNull;
+import org.apache.spark.sql.sources.IsNull;
+import org.apache.spark.sql.sources.LessThan;
+import org.apache.spark.sql.sources.LessThanOrEqual;
+import org.apache.spark.sql.sources.Not;
+import org.apache.spark.sql.sources.Or;
+import org.apache.spark.sql.sources.StringStartsWith;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import static
org.apache.flink.table.store.file.predicate.PredicateBuilder.convertJavaObject;
+
+/** Conversion from {@link Filter} to {@link Predicate}. */
+public class SparkFilterConverter {
+
+ private final RowType rowType;
+ private final PredicateBuilder builder;
+
+ public SparkFilterConverter(RowType rowType) {
+ this.rowType = rowType;
+ this.builder = new PredicateBuilder(rowType);
+ }
+
+ public Predicate convert(Filter filter) {
+ if (filter instanceof EqualTo) {
+ EqualTo eq = (EqualTo) filter;
+ // TODO deal with isNaN
+ int index = fieldIndex(eq.attribute());
+ Object literal = convertLiteral(index, eq.value());
+ return builder.equal(index, literal);
+ } else if (filter instanceof GreaterThan) {
+ GreaterThan gt = (GreaterThan) filter;
+ int index = fieldIndex(gt.attribute());
+ Object literal = convertLiteral(index, gt.value());
+ return builder.greaterThan(index, literal);
+ } else if (filter instanceof GreaterThanOrEqual) {
+ GreaterThanOrEqual gt = (GreaterThanOrEqual) filter;
+ int index = fieldIndex(gt.attribute());
+ Object literal = convertLiteral(index, gt.value());
+ return builder.greaterOrEqual(index, literal);
+ } else if (filter instanceof LessThan) {
+ LessThan lt = (LessThan) filter;
+ int index = fieldIndex(lt.attribute());
+ Object literal = convertLiteral(index, lt.value());
+ return builder.lessThan(index, literal);
+ } else if (filter instanceof LessThanOrEqual) {
+ LessThanOrEqual lt = (LessThanOrEqual) filter;
+ int index = fieldIndex(lt.attribute());
+ Object literal = convertLiteral(index, lt.value());
+ return builder.lessOrEqual(index, literal);
+ } else if (filter instanceof In) {
+ In in = (In) filter;
+ int index = fieldIndex(in.attribute());
+ return builder.in(
+ index,
+ Arrays.stream(in.values())
+ .map(v -> convertLiteral(index, v))
+ .collect(Collectors.toList()));
+ } else if (filter instanceof IsNull) {
+ return builder.isNull(fieldIndex(((IsNull) filter).attribute()));
+ } else if (filter instanceof IsNotNull) {
+ return builder.isNotNull(fieldIndex(((IsNotNull)
filter).attribute()));
+ } else if (filter instanceof And) {
+ And and = (And) filter;
+ return PredicateBuilder.and(convert(and.left()),
convert(and.right()));
+ } else if (filter instanceof Or) {
+ Or or = (Or) filter;
+ return PredicateBuilder.or(convert(or.left()),
convert(or.right()));
+ } else if (filter instanceof Not) {
+ Not not = (Not) filter;
+ return
convert(not.child()).negate().orElseThrow(UnsupportedOperationException::new);
+ } else if (filter instanceof StringStartsWith) {
+ StringStartsWith startsWith = (StringStartsWith) filter;
+ int index = fieldIndex(startsWith.attribute());
+ Object literal = convertLiteral(index, startsWith.value());
+ return builder.startsWith(index, literal);
+ }
+
+ // TODO: In, NotIn, AlwaysTrue, AlwaysFalse, EqualNullSafe
+ throw new UnsupportedOperationException();
+ }
+
+ private int fieldIndex(String field) {
+ int index = rowType.getFieldIndex(field);
+ // TODO: support nested field
+ if (index == -1) {
+ throw new UnsupportedOperationException();
+ }
+ return index;
+ }
+
+ private Object convertLiteral(int index, Object value) {
+ LogicalType type = rowType.getTypeAt(index);
+ return convertJavaObject(type, value);
+ }
+}
diff --git
a/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkInputPartition.java
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkInputPartition.java
new file mode 100644
index 00000000..f4c5432a
--- /dev/null
+++
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkInputPartition.java
@@ -0,0 +1,127 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.core.memory.DataInputViewStreamWrapper;
+import org.apache.flink.core.memory.DataOutputViewStreamWrapper;
+import org.apache.flink.table.data.RowData;
+import org.apache.flink.table.store.file.predicate.Predicate;
+import org.apache.flink.table.store.file.utils.RecordReader;
+import org.apache.flink.table.store.file.utils.RecordReaderIterator;
+import org.apache.flink.table.store.table.FileStoreTable;
+import org.apache.flink.table.store.table.source.Split;
+import org.apache.flink.table.store.table.source.TableRead;
+import org.apache.flink.table.store.utils.TypeUtils;
+import org.apache.flink.table.types.logical.RowType;
+
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.sources.v2.reader.InputPartition;
+import org.apache.spark.sql.sources.v2.reader.InputPartitionReader;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.UncheckedIOException;
+import java.util.List;
+
+import static org.apache.flink.table.store.file.predicate.PredicateBuilder.and;
+
+/** A Spark {@link InputPartition} for table store. */
+public class SparkInputPartition implements InputPartition<InternalRow> {
+
+ private static final long serialVersionUID = 1L;
+
+ private final FileStoreTable table;
+ private final int[] projectedFields;
+ private final List<Predicate> predicates;
+
+ private transient Split split;
+
+ public SparkInputPartition(
+ FileStoreTable table, int[] projectedFields, List<Predicate>
predicates, Split split) {
+ this.table = table;
+ this.projectedFields = projectedFields;
+ this.predicates = predicates;
+ this.split = split;
+ }
+
+ @Override
+ public InputPartitionReader<InternalRow> createPartitionReader() {
+ RecordReader<RowData> recordReader;
+ try {
+ TableRead tableRead = table.newRead();
+ if (projectedFields != null) {
+ tableRead.withProjection(projectedFields);
+ }
+ if (predicates.size() > 0) {
+ tableRead.withFilter(and(predicates));
+ }
+ recordReader = tableRead.createReader(split);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ RecordReaderIterator<RowData> iterator = new
RecordReaderIterator<>(recordReader);
+ SparkInternalRow row = new SparkInternalRow(readRowType());
+ return new InputPartitionReader<InternalRow>() {
+
+ @Override
+ public boolean next() {
+ if (iterator.hasNext()) {
+ row.replace(iterator.next());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public InternalRow get() {
+ return row;
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ iterator.close();
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+ };
+ }
+
+ @Override
+ public String[] preferredLocations() {
+ return new String[0];
+ }
+
+ private RowType readRowType() {
+ RowType rowType = table.schema().logicalRowType();
+ return projectedFields == null ? rowType : TypeUtils.project(rowType,
projectedFields);
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.defaultWriteObject();
+ split.serialize(new DataOutputViewStreamWrapper(out));
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
+ in.defaultReadObject();
+ split = Split.deserialize(new DataInputViewStreamWrapper(in));
+ }
+}
diff --git
a/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkInternalRow.java
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkInternalRow.java
new file mode 100644
index 00000000..c48e921f
--- /dev/null
+++
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkInternalRow.java
@@ -0,0 +1,240 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.table.data.DecimalData;
+import org.apache.flink.table.data.RowData;
+import org.apache.flink.table.data.StringData;
+import org.apache.flink.table.data.TimestampData;
+import org.apache.flink.table.types.logical.ArrayType;
+import org.apache.flink.table.types.logical.BigIntType;
+import org.apache.flink.table.types.logical.IntType;
+import org.apache.flink.table.types.logical.LogicalType;
+import org.apache.flink.table.types.logical.MapType;
+import org.apache.flink.table.types.logical.MultisetType;
+import org.apache.flink.table.types.logical.RowType;
+
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.catalyst.util.ArrayBasedMapData;
+import org.apache.spark.sql.catalyst.util.ArrayData;
+import org.apache.spark.sql.catalyst.util.DateTimeUtils;
+import org.apache.spark.sql.catalyst.util.MapData;
+import org.apache.spark.sql.types.DataType;
+import org.apache.spark.sql.types.Decimal;
+import org.apache.spark.unsafe.types.CalendarInterval;
+import org.apache.spark.unsafe.types.UTF8String;
+
+import static org.apache.flink.table.store.utils.RowDataUtils.copyRowData;
+import static org.apache.flink.table.store.utils.TypeUtils.timestampPrecision;
+
+/** Spark {@link InternalRow} to wrap {@link RowData}. */
+public class SparkInternalRow extends InternalRow {
+
+ private final RowType rowType;
+
+ private RowData row;
+
+ public SparkInternalRow(RowType rowType) {
+ this.rowType = rowType;
+ }
+
+ public SparkInternalRow replace(RowData row) {
+ this.row = row;
+ return this;
+ }
+
+ @Override
+ public int numFields() {
+ return row.getArity();
+ }
+
+ @Override
+ public void setNullAt(int i) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void update(int i, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InternalRow copy() {
+ return new SparkInternalRow(rowType).replace(copyRowData(row,
rowType));
+ }
+
+ @Override
+ public boolean isNullAt(int ordinal) {
+ return row.isNullAt(ordinal);
+ }
+
+ @Override
+ public boolean getBoolean(int ordinal) {
+ return row.getBoolean(ordinal);
+ }
+
+ @Override
+ public byte getByte(int ordinal) {
+ return row.getByte(ordinal);
+ }
+
+ @Override
+ public short getShort(int ordinal) {
+ return row.getShort(ordinal);
+ }
+
+ @Override
+ public int getInt(int ordinal) {
+ return row.getInt(ordinal);
+ }
+
+ @Override
+ public long getLong(int ordinal) {
+ if (rowType.getTypeAt(ordinal) instanceof BigIntType) {
+ return row.getLong(ordinal);
+ }
+
+ return getTimestampMicros(ordinal);
+ }
+
+ private long getTimestampMicros(int ordinal) {
+ LogicalType type = rowType.getTypeAt(ordinal);
+ return fromFlink(row.getTimestamp(ordinal, timestampPrecision(type)));
+ }
+
+ @Override
+ public float getFloat(int ordinal) {
+ return row.getFloat(ordinal);
+ }
+
+ @Override
+ public double getDouble(int ordinal) {
+ return row.getDouble(ordinal);
+ }
+
+ @Override
+ public Decimal getDecimal(int ordinal, int precision, int scale) {
+ DecimalData decimal = row.getDecimal(ordinal, precision, scale);
+ return fromFlink(decimal);
+ }
+
+ @Override
+ public UTF8String getUTF8String(int ordinal) {
+ return fromFlink(row.getString(ordinal));
+ }
+
+ @Override
+ public byte[] getBinary(int ordinal) {
+ return row.getBinary(ordinal);
+ }
+
+ @Override
+ public CalendarInterval getInterval(int ordinal) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InternalRow getStruct(int ordinal, int numFields) {
+ return fromFlink(row.getRow(ordinal, numFields), (RowType)
rowType.getTypeAt(ordinal));
+ }
+
+ @Override
+ public ArrayData getArray(int ordinal) {
+ return fromFlink(row.getArray(ordinal), (ArrayType)
rowType.getTypeAt(ordinal));
+ }
+
+ @Override
+ public MapData getMap(int ordinal) {
+ return fromFlink(row.getMap(ordinal), rowType.getTypeAt(ordinal));
+ }
+
+ @Override
+ public Object get(int ordinal, DataType dataType) {
+ return SpecializedGettersReader.read(this, ordinal, dataType);
+ }
+
+ public static Object fromFlink(Object o, LogicalType type) {
+ if (o == null) {
+ return null;
+ }
+ switch (type.getTypeRoot()) {
+ case TIMESTAMP_WITHOUT_TIME_ZONE:
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ return fromFlink((TimestampData) o);
+ case CHAR:
+ case VARCHAR:
+ return fromFlink((StringData) o);
+ case DECIMAL:
+ return fromFlink((DecimalData) o);
+ case ARRAY:
+ return fromFlink((org.apache.flink.table.data.ArrayData) o,
(ArrayType) type);
+ case MAP:
+ case MULTISET:
+ return fromFlink((org.apache.flink.table.data.MapData) o,
type);
+ case ROW:
+ return fromFlink((RowData) o, (RowType) type);
+ default:
+ return o;
+ }
+ }
+
+ public static UTF8String fromFlink(StringData string) {
+ return UTF8String.fromBytes(string.toBytes());
+ }
+
+ public static Decimal fromFlink(DecimalData decimal) {
+ return Decimal.apply(decimal.toBigDecimal());
+ }
+
+ public static InternalRow fromFlink(RowData row, RowType rowType) {
+ return new SparkInternalRow(rowType).replace(row);
+ }
+
+ public static long fromFlink(TimestampData timestamp) {
+ return DateTimeUtils.fromJavaTimestamp(timestamp.toTimestamp());
+ }
+
+ public static ArrayData fromFlink(
+ org.apache.flink.table.data.ArrayData array, ArrayType arrayType) {
+ return fromFlinkArrayElementType(array, arrayType.getElementType());
+ }
+
+ private static ArrayData fromFlinkArrayElementType(
+ org.apache.flink.table.data.ArrayData array, LogicalType
elementType) {
+ return new SparkArrayData(elementType).replace(array);
+ }
+
+ public static MapData fromFlink(org.apache.flink.table.data.MapData map,
LogicalType mapType) {
+ LogicalType keyType;
+ LogicalType valueType;
+ if (mapType instanceof MapType) {
+ keyType = ((MapType) mapType).getKeyType();
+ valueType = ((MapType) mapType).getValueType();
+ } else if (mapType instanceof MultisetType) {
+ keyType = ((MultisetType) mapType).getElementType();
+ valueType = new IntType();
+ } else {
+ throw new UnsupportedOperationException("Unsupported type: " +
mapType);
+ }
+
+ return new ArrayBasedMapData(
+ fromFlinkArrayElementType(map.keyArray(), keyType),
+ fromFlinkArrayElementType(map.valueArray(), valueType));
+ }
+}
diff --git
a/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkSource.java
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkSource.java
new file mode 100644
index 00000000..bd2fde5b
--- /dev/null
+++
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkSource.java
@@ -0,0 +1,50 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.table.store.table.FileStoreTable;
+import org.apache.flink.table.store.table.FileStoreTableFactory;
+
+import org.apache.spark.sql.sources.DataSourceRegister;
+import org.apache.spark.sql.sources.v2.DataSourceOptions;
+import org.apache.spark.sql.sources.v2.ReadSupport;
+import org.apache.spark.sql.sources.v2.reader.DataSourceReader;
+import org.apache.spark.sql.types.StructType;
+
+/** The Spark source for table store. */
+public class SparkSource implements DataSourceRegister, ReadSupport {
+
+ @Override
+ public String shortName() {
+ // Not use 'table-store' here, the '-' is not allowed in SQL
+ return "tablestore";
+ }
+
+ @Override
+ public DataSourceReader createReader(StructType schema, DataSourceOptions
options) {
+ return createReader(options);
+ }
+
+ @Override
+ public DataSourceReader createReader(DataSourceOptions options) {
+ FileStoreTable table =
FileStoreTableFactory.create(Configuration.fromMap(options.asMap()));
+ return new SparkDataSourceReader(table);
+ }
+}
diff --git
a/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkTypeUtils.java
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkTypeUtils.java
new file mode 100644
index 00000000..e60fa5a3
--- /dev/null
+++
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SparkTypeUtils.java
@@ -0,0 +1,185 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.table.types.logical.ArrayType;
+import org.apache.flink.table.types.logical.BigIntType;
+import org.apache.flink.table.types.logical.BinaryType;
+import org.apache.flink.table.types.logical.BooleanType;
+import org.apache.flink.table.types.logical.CharType;
+import org.apache.flink.table.types.logical.DateType;
+import org.apache.flink.table.types.logical.DecimalType;
+import org.apache.flink.table.types.logical.DoubleType;
+import org.apache.flink.table.types.logical.FloatType;
+import org.apache.flink.table.types.logical.IntType;
+import org.apache.flink.table.types.logical.LocalZonedTimestampType;
+import org.apache.flink.table.types.logical.LogicalType;
+import org.apache.flink.table.types.logical.MapType;
+import org.apache.flink.table.types.logical.MultisetType;
+import org.apache.flink.table.types.logical.RowType;
+import org.apache.flink.table.types.logical.RowType.RowField;
+import org.apache.flink.table.types.logical.SmallIntType;
+import org.apache.flink.table.types.logical.TimestampType;
+import org.apache.flink.table.types.logical.TinyIntType;
+import org.apache.flink.table.types.logical.VarBinaryType;
+import org.apache.flink.table.types.logical.VarCharType;
+import org.apache.flink.table.types.logical.utils.LogicalTypeDefaultVisitor;
+
+import org.apache.spark.sql.types.DataType;
+import org.apache.spark.sql.types.DataTypes;
+import org.apache.spark.sql.types.StructField;
+import org.apache.spark.sql.types.StructType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Utils for Spark {@link DataType}. */
+public class SparkTypeUtils {
+
+ private SparkTypeUtils() {}
+
+ public static StructType fromFlinkRowType(RowType type) {
+ return (StructType) fromFlinkType(type);
+ }
+
+ public static DataType fromFlinkType(LogicalType type) {
+ return type.accept(FlinkToSparkTypeVisitor.INSTANCE);
+ }
+
+ private static class FlinkToSparkTypeVisitor extends
LogicalTypeDefaultVisitor<DataType> {
+
+ private static final FlinkToSparkTypeVisitor INSTANCE = new
FlinkToSparkTypeVisitor();
+
+ @Override
+ public DataType visit(CharType charType) {
+ return DataTypes.StringType;
+ }
+
+ @Override
+ public DataType visit(VarCharType varCharType) {
+ return DataTypes.StringType;
+ }
+
+ @Override
+ public DataType visit(BooleanType booleanType) {
+ return DataTypes.BooleanType;
+ }
+
+ @Override
+ public DataType visit(BinaryType binaryType) {
+ return DataTypes.BinaryType;
+ }
+
+ @Override
+ public DataType visit(VarBinaryType varBinaryType) {
+ return DataTypes.BinaryType;
+ }
+
+ @Override
+ public DataType visit(DecimalType decimalType) {
+ return DataTypes.createDecimalType(decimalType.getPrecision(),
decimalType.getScale());
+ }
+
+ @Override
+ public DataType visit(TinyIntType tinyIntType) {
+ return DataTypes.ByteType;
+ }
+
+ @Override
+ public DataType visit(SmallIntType smallIntType) {
+ return DataTypes.ShortType;
+ }
+
+ @Override
+ public DataType visit(IntType intType) {
+ return DataTypes.IntegerType;
+ }
+
+ @Override
+ public DataType visit(BigIntType bigIntType) {
+ return DataTypes.LongType;
+ }
+
+ @Override
+ public DataType visit(FloatType floatType) {
+ return DataTypes.FloatType;
+ }
+
+ @Override
+ public DataType visit(DoubleType doubleType) {
+ return DataTypes.DoubleType;
+ }
+
+ @Override
+ public DataType visit(DateType dateType) {
+ return DataTypes.DateType;
+ }
+
+ @Override
+ public DataType visit(TimestampType timestampType) {
+ return DataTypes.TimestampType;
+ }
+
+ @Override
+ public DataType visit(LocalZonedTimestampType localZonedTimestampType)
{
+ return DataTypes.TimestampType;
+ }
+
+ @Override
+ public DataType visit(ArrayType arrayType) {
+ LogicalType elementType = arrayType.getElementType();
+ return DataTypes.createArrayType(elementType.accept(this),
elementType.isNullable());
+ }
+
+ @Override
+ public DataType visit(MultisetType multisetType) {
+ return DataTypes.createMapType(
+ multisetType.getElementType().accept(this),
DataTypes.IntegerType, false);
+ }
+
+ @Override
+ public DataType visit(MapType mapType) {
+ return DataTypes.createMapType(
+ mapType.getKeyType().accept(this),
+ mapType.getValueType().accept(this),
+ mapType.getValueType().isNullable());
+ }
+
+ @Override
+ public DataType visit(RowType rowType) {
+ List<StructField> fields = new
ArrayList<>(rowType.getFieldCount());
+ for (RowField field : rowType.getFields()) {
+ StructField structField =
+ DataTypes.createStructField(
+ field.getName(),
+ field.getType().accept(this),
+ field.getType().isNullable());
+ structField =
+
field.getDescription().map(structField::withComment).orElse(structField);
+ fields.add(structField);
+ }
+ return DataTypes.createStructType(fields);
+ }
+
+ @Override
+ protected DataType defaultMethod(LogicalType logicalType) {
+ throw new UnsupportedOperationException("Unsupported type: " +
logicalType);
+ }
+ }
+}
diff --git
a/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SpecializedGettersReader.java
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SpecializedGettersReader.java
new file mode 100644
index 00000000..5eaf1738
--- /dev/null
+++
b/flink-table-store-spark2/src/main/java/org/apache/flink/table/store/spark/SpecializedGettersReader.java
@@ -0,0 +1,106 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.spark.sql.catalyst.expressions.SpecializedGetters;
+import org.apache.spark.sql.types.ArrayType;
+import org.apache.spark.sql.types.BinaryType;
+import org.apache.spark.sql.types.BooleanType;
+import org.apache.spark.sql.types.ByteType;
+import org.apache.spark.sql.types.CalendarIntervalType;
+import org.apache.spark.sql.types.DataType;
+import org.apache.spark.sql.types.DateType;
+import org.apache.spark.sql.types.DecimalType;
+import org.apache.spark.sql.types.DoubleType;
+import org.apache.spark.sql.types.FloatType;
+import org.apache.spark.sql.types.IntegerType;
+import org.apache.spark.sql.types.LongType;
+import org.apache.spark.sql.types.MapType;
+import org.apache.spark.sql.types.NullType;
+import org.apache.spark.sql.types.ShortType;
+import org.apache.spark.sql.types.StringType;
+import org.apache.spark.sql.types.StructType;
+import org.apache.spark.sql.types.TimestampType;
+import org.apache.spark.sql.types.UserDefinedType;
+
+/** Reader of Spark {@link SpecializedGetters}. */
+public final class SpecializedGettersReader {
+
+ private SpecializedGettersReader() {}
+
+ public static Object read(SpecializedGetters obj, int ordinal, DataType
dataType) {
+ if (obj.isNullAt(ordinal) || dataType instanceof NullType) {
+ return null;
+ }
+ if (dataType instanceof BooleanType) {
+ return obj.getBoolean(ordinal);
+ }
+ if (dataType instanceof ByteType) {
+ return obj.getByte(ordinal);
+ }
+ if (dataType instanceof ShortType) {
+ return obj.getShort(ordinal);
+ }
+ if (dataType instanceof IntegerType) {
+ return obj.getInt(ordinal);
+ }
+ if (dataType instanceof LongType) {
+ return obj.getLong(ordinal);
+ }
+ if (dataType instanceof FloatType) {
+ return obj.getFloat(ordinal);
+ }
+ if (dataType instanceof DoubleType) {
+ return obj.getDouble(ordinal);
+ }
+ if (dataType instanceof StringType) {
+ return obj.getUTF8String(ordinal);
+ }
+ if (dataType instanceof DecimalType) {
+ DecimalType dt = (DecimalType) dataType;
+ return obj.getDecimal(ordinal, dt.precision(), dt.scale());
+ }
+ if (dataType instanceof DateType) {
+ return obj.getInt(ordinal);
+ }
+ if (dataType instanceof TimestampType) {
+ return obj.getLong(ordinal);
+ }
+ if (dataType instanceof CalendarIntervalType) {
+ return obj.getInterval(ordinal);
+ }
+ if (dataType instanceof BinaryType) {
+ return obj.getBinary(ordinal);
+ }
+ if (dataType instanceof StructType) {
+ return obj.getStruct(ordinal, ((StructType) dataType).size());
+ }
+ if (dataType instanceof ArrayType) {
+ return obj.getArray(ordinal);
+ }
+ if (dataType instanceof MapType) {
+ return obj.getMap(ordinal);
+ }
+ if (dataType instanceof UserDefinedType) {
+ return obj.get(ordinal, ((UserDefinedType) dataType).sqlType());
+ }
+
+ throw new UnsupportedOperationException("Unsupported data type " +
dataType.simpleString());
+ }
+}
diff --git
a/flink-table-store-spark2/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister
b/flink-table-store-spark2/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister
new file mode 100644
index 00000000..e0626103
--- /dev/null
+++
b/flink-table-store-spark2/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister
@@ -0,0 +1,16 @@
+# 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.
+
+org.apache.flink.table.store.spark.SparkSource
diff --git
a/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SimpleTableTestHelper.java
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SimpleTableTestHelper.java
new file mode 100644
index 00000000..705a961a
--- /dev/null
+++
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SimpleTableTestHelper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.core.fs.Path;
+import org.apache.flink.table.data.RowData;
+import org.apache.flink.table.store.CoreOptions;
+import org.apache.flink.table.store.file.schema.SchemaManager;
+import org.apache.flink.table.store.file.schema.UpdateSchema;
+import org.apache.flink.table.store.table.FileStoreTable;
+import org.apache.flink.table.store.table.FileStoreTableFactory;
+import org.apache.flink.table.store.table.sink.TableCommit;
+import org.apache.flink.table.store.table.sink.TableWrite;
+import org.apache.flink.table.types.logical.RowType;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/** A simple table test helper to write and commit. */
+public class SimpleTableTestHelper {
+
+ private final TableWrite writer;
+ private final TableCommit commit;
+
+ public SimpleTableTestHelper(Path path, RowType rowType) throws Exception {
+ Map<String, String> options = new HashMap<>();
+ // orc is shaded, can not find shaded classes in ide
+ options.put(CoreOptions.FILE_FORMAT.key(), "avro");
+ new SchemaManager(path)
+ .commitNewVersion(
+ new UpdateSchema(
+ rowType,
+ Collections.emptyList(),
+ Collections.emptyList(),
+ options,
+ ""));
+ Configuration conf = Configuration.fromMap(options);
+ conf.setString("path", path.toString());
+ FileStoreTable table = FileStoreTableFactory.create(conf);
+ this.writer = table.newWrite();
+ this.commit = table.newCommit("user");
+ }
+
+ public void write(RowData row) throws Exception {
+ writer.write(row);
+ }
+
+ public void commit() throws Exception {
+ commit.commit(UUID.randomUUID().toString(),
writer.prepareCommit(true));
+ }
+}
diff --git
a/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkFilterConverterTest.java
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkFilterConverterTest.java
new file mode 100644
index 00000000..7180f3d6
--- /dev/null
+++
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkFilterConverterTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.table.data.TimestampData;
+import org.apache.flink.table.store.file.predicate.Predicate;
+import org.apache.flink.table.store.file.predicate.PredicateBuilder;
+import org.apache.flink.table.types.logical.DateType;
+import org.apache.flink.table.types.logical.IntType;
+import org.apache.flink.table.types.logical.RowType;
+import org.apache.flink.table.types.logical.TimestampType;
+
+import org.apache.spark.sql.sources.EqualTo;
+import org.apache.spark.sql.sources.GreaterThan;
+import org.apache.spark.sql.sources.GreaterThanOrEqual;
+import org.apache.spark.sql.sources.In;
+import org.apache.spark.sql.sources.IsNotNull;
+import org.apache.spark.sql.sources.IsNull;
+import org.apache.spark.sql.sources.LessThan;
+import org.apache.spark.sql.sources.LessThanOrEqual;
+import org.junit.jupiter.api.Test;
+
+import java.sql.Date;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Test for {@link SparkFilterConverter}. */
+public class SparkFilterConverterTest {
+
+ @Test
+ public void testAll() {
+ RowType rowType =
+ new RowType(Collections.singletonList(new
RowType.RowField("id", new IntType())));
+ SparkFilterConverter converter = new SparkFilterConverter(rowType);
+ PredicateBuilder builder = new PredicateBuilder(rowType);
+
+ String field = "id";
+ IsNull isNull = IsNull.apply(field);
+ Predicate expectedIsNull = builder.isNull(0);
+ Predicate actualIsNull = converter.convert(isNull);
+ assertThat(actualIsNull).isEqualTo(expectedIsNull);
+
+ IsNotNull isNotNull = IsNotNull.apply(field);
+ Predicate expectedIsNotNull = builder.isNotNull(0);
+ Predicate actualIsNotNull = converter.convert(isNotNull);
+ assertThat(actualIsNotNull).isEqualTo(expectedIsNotNull);
+
+ LessThan lt = LessThan.apply(field, 1);
+ Predicate expectedLt = builder.lessThan(0, 1);
+ Predicate actualLt = converter.convert(lt);
+ assertThat(actualLt).isEqualTo(expectedLt);
+
+ LessThan ltNull = LessThan.apply(field, null);
+ Predicate expectedLtNull = builder.lessThan(0, null);
+ Predicate actualLtNull = converter.convert(ltNull);
+ assertThat(actualLtNull).isEqualTo(expectedLtNull);
+
+ LessThanOrEqual ltEq = LessThanOrEqual.apply(field, 1);
+ Predicate expectedLtEq = builder.lessOrEqual(0, 1);
+ Predicate actualLtEq = converter.convert(ltEq);
+ assertThat(actualLtEq).isEqualTo(expectedLtEq);
+
+ LessThanOrEqual ltEqNull = LessThanOrEqual.apply(field, null);
+ Predicate expectedLtEqNull = builder.lessOrEqual(0, null);
+ Predicate actualLtEqNull = converter.convert(ltEqNull);
+ assertThat(actualLtEqNull).isEqualTo(expectedLtEqNull);
+
+ GreaterThan gt = GreaterThan.apply(field, 1);
+ Predicate expectedGt = builder.greaterThan(0, 1);
+ Predicate actualGt = converter.convert(gt);
+ assertThat(actualGt).isEqualTo(expectedGt);
+
+ GreaterThan gtNull = GreaterThan.apply(field, null);
+ Predicate expectedGtNull = builder.greaterThan(0, null);
+ Predicate actualGtNull = converter.convert(gtNull);
+ assertThat(actualGtNull).isEqualTo(expectedGtNull);
+
+ GreaterThanOrEqual gtEq = GreaterThanOrEqual.apply(field, 1);
+ Predicate expectedGtEq = builder.greaterOrEqual(0, 1);
+ Predicate actualGtEq = converter.convert(gtEq);
+ assertThat(actualGtEq).isEqualTo(expectedGtEq);
+
+ GreaterThanOrEqual gtEqNull = GreaterThanOrEqual.apply(field, null);
+ Predicate expectedGtEqNull = builder.greaterOrEqual(0, null);
+ Predicate actualGtEqNull = converter.convert(gtEqNull);
+ assertThat(actualGtEqNull).isEqualTo(expectedGtEqNull);
+
+ EqualTo eq = EqualTo.apply(field, 1);
+ Predicate expectedEq = builder.equal(0, 1);
+ Predicate actualEq = converter.convert(eq);
+ assertThat(actualEq).isEqualTo(expectedEq);
+
+ EqualTo eqNull = EqualTo.apply(field, null);
+ Predicate expectedEqNull = builder.equal(0, null);
+ Predicate actualEqNull = converter.convert(eqNull);
+ assertThat(actualEqNull).isEqualTo(expectedEqNull);
+
+ In in = In.apply(field, new Object[] {1, null, 2});
+ Predicate expectedIn = builder.in(0, Arrays.asList(1, null, 2));
+ Predicate actualIn = converter.convert(in);
+ assertThat(actualIn).isEqualTo(expectedIn);
+
+ Object[] literals = new Object[30];
+ literals[0] = null;
+ for (int i = 1; i < literals.length; i++) {
+ literals[i] = i * 100;
+ }
+ In largeIn = In.apply(field, literals);
+ Predicate expectedLargeIn = builder.in(0, Arrays.asList(literals));
+ Predicate actualLargeIn = converter.convert(largeIn);
+ assertThat(actualLargeIn).isEqualTo(expectedLargeIn);
+ }
+
+ @Test
+ public void testTimestamp() {
+ RowType rowType =
+ new RowType(
+ Collections.singletonList(new RowType.RowField("x",
new TimestampType())));
+ SparkFilterConverter converter = new SparkFilterConverter(rowType);
+ PredicateBuilder builder = new PredicateBuilder(rowType);
+
+ Timestamp timestamp = Timestamp.valueOf("2018-10-18 00:00:57.907");
+ LocalDateTime localDateTime =
LocalDateTime.parse("2018-10-18T00:00:57.907");
+ Instant instant = localDateTime.toInstant(ZoneOffset.UTC);
+
+ Predicate instantExpression = converter.convert(GreaterThan.apply("x",
instant));
+ Predicate timestampExpression =
converter.convert(GreaterThan.apply("x", timestamp));
+ Predicate rawExpression =
+ builder.greaterThan(0,
TimestampData.fromLocalDateTime(localDateTime));
+
+ assertThat(timestampExpression).isEqualTo(rawExpression);
+ assertThat(instantExpression).isEqualTo(rawExpression);
+ }
+
+ @Test
+ public void testDate() {
+ RowType rowType =
+ new RowType(Collections.singletonList(new
RowType.RowField("x", new DateType())));
+ SparkFilterConverter converter = new SparkFilterConverter(rowType);
+ PredicateBuilder builder = new PredicateBuilder(rowType);
+
+ LocalDate localDate = LocalDate.parse("2018-10-18");
+ Date date = Date.valueOf(localDate);
+ int epochDay = (int) localDate.toEpochDay();
+
+ Predicate localDateExpression =
converter.convert(GreaterThan.apply("x", localDate));
+ Predicate dateExpression = converter.convert(GreaterThan.apply("x",
date));
+ Predicate rawExpression = builder.greaterThan(0, epochDay);
+
+ assertThat(dateExpression).isEqualTo(rawExpression);
+ assertThat(localDateExpression).isEqualTo(rawExpression);
+ }
+}
diff --git
a/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkInternalRowTest.java
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkInternalRowTest.java
new file mode 100644
index 00000000..4bfb58ab
--- /dev/null
+++
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkInternalRowTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.table.data.RowData;
+import org.apache.flink.table.data.conversion.RowRowConverter;
+import org.apache.flink.table.types.utils.TypeConversions;
+import org.apache.flink.types.Row;
+
+import org.apache.spark.sql.catalyst.CatalystTypeConverters;
+import org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.sql.Date;
+import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import scala.Function1;
+
+import static org.apache.flink.table.store.spark.SparkTypeTest.ALL_TYPES;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Test for {@link SparkInternalRow}. */
+public class SparkInternalRowTest {
+
+ @Test
+ public void test() {
+ Row row =
+ Row.of(
+ 1,
+ "jingsong",
+ 22.2,
+ Stream.of(
+ new AbstractMap.SimpleEntry<>("key1",
Row.of(1.2, 2.3)),
+ new AbstractMap.SimpleEntry<>("key2",
Row.of(2.4, 3.5)))
+ .collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue)),
+ new String[] {"v1", "v5"},
+ new Integer[] {10, 30},
+ true,
+ (byte) 22,
+ (short) 356,
+ 23567222L,
+ "varbinary_v".getBytes(StandardCharsets.UTF_8),
+ LocalDateTime.parse("2007-12-03T10:15:30"),
+ LocalDate.parse("2022-05-02"),
+ BigDecimal.valueOf(0.21),
+ BigDecimal.valueOf(65782123123.01),
+ BigDecimal.valueOf(62123123.5));
+
+ RowRowConverter flinkConverter =
+
RowRowConverter.create(TypeConversions.fromLogicalToDataType(ALL_TYPES));
+ flinkConverter.open(Thread.currentThread().getContextClassLoader());
+ RowData rowData = flinkConverter.toInternal(row);
+
+ Function1<Object, Object> sparkConverter =
+ CatalystTypeConverters.createToScalaConverter(
+ SparkTypeUtils.fromFlinkType(ALL_TYPES));
+ org.apache.spark.sql.Row sparkRow =
+ (org.apache.spark.sql.Row)
+ sparkConverter.apply(new
SparkInternalRow(ALL_TYPES).replace(rowData));
+
+ assertThat(sparkRow.getInt(0)).isEqualTo(1);
+ assertThat(sparkRow.getString(1)).isEqualTo("jingsong");
+ assertThat(sparkRow.getDouble(2)).isEqualTo(22.2);
+ assertThat(((GenericRowWithSchema)
sparkRow.getJavaMap(3).get("key1")).values())
+ .isEqualTo(new Object[] {1.2, 2.3});
+ assertThat(((GenericRowWithSchema)
sparkRow.getJavaMap(3).get("key2")).values())
+ .isEqualTo(new Object[] {2.4, 3.5});
+ assertThat(sparkRow.getList(4)).isEqualTo(Arrays.asList("v1", "v5"));
+ assertThat(sparkRow.getList(5)).isEqualTo(Arrays.asList(10, 30));
+ assertThat(sparkRow.getBoolean(6)).isEqualTo(true);
+ assertThat(sparkRow.getByte(7)).isEqualTo((byte) 22);
+ assertThat(sparkRow.getShort(8)).isEqualTo((short) 356);
+ assertThat(sparkRow.getLong(9)).isEqualTo(23567222L);
+ assertThat((byte[]) sparkRow.get(10))
+ .isEqualTo("varbinary_v".getBytes(StandardCharsets.UTF_8));
+ assertThat(sparkRow.getTimestamp(11).getTime())
+
.isEqualTo(Timestamp.valueOf(LocalDateTime.parse("2007-12-03T10:15:30")).getTime());
+ assertThat(sparkRow.getDate(12).getTime())
+
.isEqualTo(Date.valueOf(LocalDate.parse("2022-05-02")).getTime());
+
assertThat(sparkRow.getDecimal(13)).isEqualTo(BigDecimal.valueOf(0.21));
+
assertThat(sparkRow.getDecimal(14)).isEqualTo(BigDecimal.valueOf(65782123123.01));
+
assertThat(sparkRow.getDecimal(15)).isEqualTo(BigDecimal.valueOf(62123123.5));
+ }
+}
diff --git
a/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkReadITCase.java
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkReadITCase.java
new file mode 100644
index 00000000..86427f09
--- /dev/null
+++
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkReadITCase.java
@@ -0,0 +1,130 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.core.fs.Path;
+import org.apache.flink.table.data.GenericRowData;
+import org.apache.flink.table.data.StringData;
+import org.apache.flink.table.types.logical.BigIntType;
+import org.apache.flink.table.types.logical.IntType;
+import org.apache.flink.table.types.logical.RowType;
+import org.apache.flink.table.types.logical.VarCharType;
+import org.apache.flink.types.RowKind;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.spark.sql.Dataset;
+import org.apache.spark.sql.Row;
+import org.apache.spark.sql.SparkSession;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** ITCase for Spark reader. */
+public class SparkReadITCase {
+
+ private static File warehouse = null;
+
+ private static SparkSession spark = null;
+
+ private static Path tablePath1;
+
+ private static Path tablePath2;
+
+ @BeforeAll
+ public static void startSpark() throws Exception {
+ warehouse = File.createTempFile("warehouse", null);
+ assertThat(warehouse.delete()).isTrue();
+ Path warehousePath = new Path("file:" + warehouse);
+ spark = SparkSession.builder().master("local[2]").getOrCreate();
+
+ // Flink sink
+ tablePath1 = new Path(warehousePath, "default.db/t1");
+ SimpleTableTestHelper testHelper1 = createTestHelper(tablePath1);
+ testHelper1.write(GenericRowData.of(1, 2L,
StringData.fromString("1")));
+ testHelper1.write(GenericRowData.of(3, 4L,
StringData.fromString("2")));
+ testHelper1.write(GenericRowData.of(5, 6L,
StringData.fromString("3")));
+ testHelper1.write(GenericRowData.ofKind(RowKind.DELETE, 3, 4L,
StringData.fromString("2")));
+ testHelper1.commit();
+
+ tablePath2 = new Path(warehousePath, "default.db/t2");
+ SimpleTableTestHelper testHelper2 = createTestHelper(tablePath2);
+ testHelper2.write(GenericRowData.of(1, 2L,
StringData.fromString("1")));
+ testHelper2.write(GenericRowData.of(3, 4L,
StringData.fromString("2")));
+ testHelper2.commit();
+ testHelper2.write(GenericRowData.of(5, 6L,
StringData.fromString("3")));
+ testHelper2.write(GenericRowData.of(7, 8L,
StringData.fromString("4")));
+ testHelper2.commit();
+ }
+
+ private static SimpleTableTestHelper createTestHelper(Path tablePath)
throws Exception {
+ RowType rowType =
+ new RowType(
+ Arrays.asList(
+ new RowType.RowField("a", new IntType()),
+ new RowType.RowField("b", new BigIntType()),
+ new RowType.RowField("c", new VarCharType())));
+ return new SimpleTableTestHelper(tablePath, rowType);
+ }
+
+ @AfterAll
+ public static void stopSpark() throws IOException {
+ if (warehouse != null && warehouse.exists()) {
+ FileUtils.deleteDirectory(warehouse);
+ }
+ if (spark != null) {
+ spark.stop();
+ spark = null;
+ }
+ }
+
+ @Test
+ public void testNormal() {
+ innerTestNormal(
+ spark.read().format("tablestore").option("path",
tablePath1.toString()).load());
+ }
+
+ @Test
+ public void testFilterPushDown() {
+ innerTestFilterPushDown(
+ spark.read().format("tablestore").option("path",
tablePath2.toString()).load());
+ }
+
+ private void innerTestNormal(Dataset<Row> dataset) {
+ List<Row> results = dataset.collectAsList();
+ assertThat(results.toString()).isEqualTo("[[1,2,1], [5,6,3]]");
+
+ results = dataset.select("a", "c").collectAsList();
+ assertThat(results.toString()).isEqualTo("[[1,1], [5,3]]");
+
+ results = dataset.groupBy().sum("b").collectAsList();
+ assertThat(results.toString()).isEqualTo("[[8]]");
+ }
+
+ private void innerTestFilterPushDown(Dataset<Row> dataset) {
+ List<Row> results = dataset.filter("a < 4").select("a",
"c").collectAsList();
+ assertThat(results.toString()).isEqualTo("[[1,1], [3,2]]");
+ }
+}
diff --git
a/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkTypeTest.java
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkTypeTest.java
new file mode 100644
index 00000000..6a71c6af
--- /dev/null
+++
b/flink-table-store-spark2/src/test/java/org/apache/flink/table/store/spark/SparkTypeTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.flink.table.store.spark;
+
+import org.apache.flink.table.api.DataTypes;
+import org.apache.flink.table.api.TableSchema;
+import org.apache.flink.table.types.logical.RowType;
+
+import org.apache.spark.sql.types.StructType;
+import org.junit.jupiter.api.Test;
+
+import static
org.apache.flink.table.store.spark.SparkTypeUtils.fromFlinkRowType;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Test for {@link SparkTypeUtils}. */
+public class SparkTypeTest {
+
+ public static final RowType ALL_TYPES =
+ (RowType)
+ TableSchema.builder()
+ .field("id", DataTypes.INT().notNull())
+ .field("name", DataTypes.STRING()) /* optional by
default */
+ .field("salary", DataTypes.DOUBLE().notNull())
+ .field(
+ "locations",
+ DataTypes.MAP(
+ DataTypes.STRING().notNull(),
+ DataTypes.ROW(
+ DataTypes.FIELD(
+ "posX",
+
DataTypes.DOUBLE().notNull(),
+ "X field"),
+ DataTypes.FIELD(
+ "posY",
+
DataTypes.DOUBLE().notNull(),
+ "Y field"))))
+ .field("strArray",
DataTypes.ARRAY(DataTypes.STRING()).nullable())
+ .field("intArray",
DataTypes.ARRAY(DataTypes.INT()).nullable())
+ .field("boolean", DataTypes.BOOLEAN().nullable())
+ .field("tinyint", DataTypes.TINYINT())
+ .field("smallint", DataTypes.SMALLINT())
+ .field("bigint", DataTypes.BIGINT())
+ .field("bytes", DataTypes.BYTES())
+ .field("timestamp", DataTypes.TIMESTAMP())
+ .field("date", DataTypes.DATE())
+ .field("decimal", DataTypes.DECIMAL(2, 2))
+ .field("decimal2", DataTypes.DECIMAL(38, 2))
+ .field("decimal3", DataTypes.DECIMAL(10, 1))
+ .build()
+ .toRowDataType()
+ .getLogicalType()
+ .copy(true);
+
+ @Test
+ public void testAllTypes() {
+ String nestedRowMapType =
+ "StructField(locations,MapType("
+ + "StringType,"
+ + "StructType(StructField(posX,DoubleType,false),
StructField(posY,DoubleType,false)),true),true)";
+ String expected =
+ "StructType("
+ + "StructField(id,IntegerType,false), "
+ + "StructField(name,StringType,true), "
+ + "StructField(salary,DoubleType,false), "
+ + nestedRowMapType
+ + ", "
+ +
"StructField(strArray,ArrayType(StringType,true),true), "
+ +
"StructField(intArray,ArrayType(IntegerType,true),true), "
+ + "StructField(boolean,BooleanType,true), "
+ + "StructField(tinyint,ByteType,true), "
+ + "StructField(smallint,ShortType,true), "
+ + "StructField(bigint,LongType,true), "
+ + "StructField(bytes,BinaryType,true), "
+ + "StructField(timestamp,TimestampType,true), "
+ + "StructField(date,DateType,true), "
+ + "StructField(decimal,DecimalType(2,2),true), "
+ + "StructField(decimal2,DecimalType(38,2),true), "
+ + "StructField(decimal3,DecimalType(10,1),true))";
+
+ StructType sparkType = fromFlinkRowType(ALL_TYPES);
+ assertThat(sparkType.toString()).isEqualTo(expected);
+ }
+}
diff --git a/flink-table-store-spark2/src/test/resources/log4j2-test.properties
b/flink-table-store-spark2/src/test/resources/log4j2-test.properties
new file mode 100644
index 00000000..863665cf
--- /dev/null
+++ b/flink-table-store-spark2/src/test/resources/log4j2-test.properties
@@ -0,0 +1,38 @@
+################################################################################
+# 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.
+################################################################################
+
+# Set root logger level to OFF to not flood build logs
+# set manually to INFO for debugging purposes
+rootLogger.level = OFF
+rootLogger.appenderRef.test.ref = TestLogger
+
+appender.testlogger.name = TestLogger
+appender.testlogger.type = CONSOLE
+appender.testlogger.target = SYSTEM_ERR
+appender.testlogger.layout.type = PatternLayout
+appender.testlogger.layout.pattern = %-4r [%t] %-5p %c %x - %m%n
+
+logger.kafka.name = kafka
+logger.kafka.level = OFF
+logger.kafka2.name = state.change
+logger.kafka2.level = OFF
+
+logger.zookeeper.name = org.apache.zookeeper
+logger.zookeeper.level = OFF
+logger.I0Itec.name = org.I0Itec
+logger.I0Itec.level = OFF
diff --git a/pom.xml b/pom.xml
index b2408b37..674bcfe9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,7 @@ under the License.
<module>flink-table-store-hive</module>
<module>flink-table-store-kafka</module>
<module>flink-table-store-spark</module>
+ <module>flink-table-store-spark2</module>
</modules>
<properties>