From 3d4c7ef75b7ec058ffbca7c311a1641ab9539046 Mon Sep 17 00:00:00 2001 From: vicennial Date: Tue, 14 Jan 2025 15:49:24 +0100 Subject: [PATCH 1/7] squash --- connect-examples/plugin-example/README.md | 131 ++++++++++++++++++ .../plugin-example/client/pom.xml | 112 +++++++++++++++ .../client/src/main/resources/log4j2.xml | 30 ++++ .../main/scala/org/example/CustomTable.scala | 100 +++++++++++++ .../org/example/CustomTableBuilder.scala | 94 +++++++++++++ .../src/main/scala/org/example/Main.scala | 58 ++++++++ .../plugin-example/common/pom.xml | 76 ++++++++++ .../common/src/main/protobuf/base.proto | 26 ++++ .../common/src/main/protobuf/commands.proto | 62 +++++++++ .../common/src/main/protobuf/relations.proto | 33 +++++ connect-examples/plugin-example/pom.xml | 43 ++++++ .../resources/dummy_data.custom | 6 + .../resources/spark-daria_2.13-1.2.3.jar | Bin 0 -> 187330 bytes .../plugin-example/server/pom.xml | 91 ++++++++++++ .../org/example/CustomCommandPlugin.scala | 93 +++++++++++++ .../scala/org/example/CustomPluginBase.scala | 27 ++++ .../org/example/CustomRelationPlugin.scala | 59 ++++++++ .../main/scala/org/example/CustomTable.scala | 83 +++++++++++ 18 files changed, 1124 insertions(+) create mode 100644 connect-examples/plugin-example/README.md create mode 100644 connect-examples/plugin-example/client/pom.xml create mode 100644 connect-examples/plugin-example/client/src/main/resources/log4j2.xml create mode 100644 connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala create mode 100644 connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala create mode 100644 connect-examples/plugin-example/client/src/main/scala/org/example/Main.scala create mode 100644 connect-examples/plugin-example/common/pom.xml create mode 100644 connect-examples/plugin-example/common/src/main/protobuf/base.proto create mode 100644 connect-examples/plugin-example/common/src/main/protobuf/commands.proto create mode 100644 connect-examples/plugin-example/common/src/main/protobuf/relations.proto create mode 100644 connect-examples/plugin-example/pom.xml create mode 100644 connect-examples/plugin-example/resources/dummy_data.custom create mode 100644 connect-examples/plugin-example/resources/spark-daria_2.13-1.2.3.jar create mode 100644 connect-examples/plugin-example/server/pom.xml create mode 100644 connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala create mode 100644 connect-examples/plugin-example/server/src/main/scala/org/example/CustomPluginBase.scala create mode 100644 connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala create mode 100644 connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala diff --git a/connect-examples/plugin-example/README.md b/connect-examples/plugin-example/README.md new file mode 100644 index 0000000000000..f1d529d672025 --- /dev/null +++ b/connect-examples/plugin-example/README.md @@ -0,0 +1,131 @@ +# Spark Plugin Example - Custom Datasource Handler + +This example demonstrates a modular maven-based project architecture with separate client, server and common components. It leverages the extensibility of Spark Connect to create a plugin that may be attached to the server to extend the functionality of the Spark Connect server as a whole. Below is a detailed overview of the setup and functionality. + +## Project Structure + +``` +├── common/ # Shared protobuf/utilities/classes +├── client/ # Sample client implementation +│ ├── src/ # Source code for client functionality +│ ├── pom.xml # Maven configuration for the client +├── server/ # Server-side plugin extension +│ ├── src/ # Source code for server functionality +│ ├── pom.xml # Maven configuration for the server +├── resources/ # Static resources +├── pom.xml # Parent Maven configuration +``` + +## Functionality Overview + +To demonstrate the extensibility of Spark Connect, a custom datasource handler, `CustomTable` is +implemented in the server module. The class handles reading, writing and processing data stored in +a custom format, here we simply use the `.custom` extension (which itself is a wrapper over `.csv` +files). + +First and foremost, the client and the server must be able to communicate with each other through +custom messages that 'understand' our custom data format. This is achieved by defining custom +protobuf messages in the `common` module. The client and server modules both depend on the `common` +module to access these messages. +- `common/src/main/protobuf/base.proto`: Defines the base `CustomTable` which is simply represented +by a path and a name. +```protobuf +message CustomTable { + string path = 1; + string name = 2; +} +``` +- `common/src/main/protobuf/commands.proto`: Defines the custom commands that the client can send +to the server. These commands are typically operations that the server can perform, such as cloning +an existing custom table. +```protobuf +message CustomCommand { + oneof command_type { + CreateTable create_table = 1; + CloneTable clone_table = 2; + } +} +``` +- `common/src/main/protobuf/relations.proto`: Defines custom `relations`, which are a mechanism through which an optional input dataset is transformed into an + output dataset such as a Scan. +```protobuf +message Scan { + CustomTable table = 1; +} +``` + +On the client side, the `CustomTable` class mimics the style of Spark's `Dataset` API, allowing the +user to perform and chain operations on a `CustomTable` object. + +On the server side, a similar `CustomTable` class is implemented to handle the core functionality of +reading, writing and processing data in the custom format. The plugins (`CustomCommandPlugin` and +`CustomRelationPlugin`) are responsible for processing the custom protobuf messages sent from the client +(those defined in the `common` module) and delegating the appropriate actions to the `CustomTable`. + + + +## Build and Run Instructions + +1. **Navigate to the sample project from `SPARK_HOME`**: + ```bash + cd connect-examples/plugin-example + ``` + +2. **Build and package the modules**: + ```bash + mvn clean package + ``` + +3. **Download the `4.0.0-preview2` release to use as the Spark Connect Server**: + - Choose a distribution from https://archive.apache.org/dist/spark/spark-4.0.0-preview2/. + - Example: `curl -L https://archive.apache.org/dist/spark/spark-4.0.0-preview2/spark-4.0.0-preview2-bin-hadoop3.tgz | tar xz` + +4. **Copy relevant JARs to the root of the unpacked Spark distribution**: + ```bash + cp \ + /connect-examples/plugin-example/resources/spark-daria_2.13-1.2.3.jar \ + /connect-examples/plugin-example/common/target/spark-plugin-example-common-1.0-SNAPSHOT.jar \ + /connect-examples/plugin-example/server/target/spark-plugin-example-server-1.0-SNAPSHOT.jar \ + . + ``` +5. **Start the Spark Connect Server with the relevant JARs**: + ```bash + bin/spark-connect-shell \ + --jars spark-plugin-example-server-1.0-SNAPSHOT.jar,spark-plugin-example-common-1.0-SNAPSHOT.jar,spark-daria_2.13-1.2.3.jar \ + --conf spark.connect.extensions.relation.classes=org.example.CustomRelationPlugin \ + --conf spark.connect.extensions.command.classes=org.example.CustomCommandPlugin + ``` +6. **In a different terminal, navigate back to the root of the sample project and start the client**: + ```bash + java -cp client/target/spark-plugin-example-client-scala-1.0-SNAPSHOT.jar org.example.Main + ``` +7. **Notice the printed output in the client terminal as well as the creation of the cloned table**: +```protobuf +Explaning plan for custom table: sample_table with path: /spark/connect-examples/plugin-example/client/../resources/dummy_data.custom +== Parsed Logical Plan == +Relation [id#2,name#3] csv + +== Analyzed Logical Plan == +id: int, name: string +Relation [id#2,name#3] csv + +== Optimized Logical Plan == +Relation [id#2,name#3] csv + +== Physical Plan == +FileScan csv [id#2,name#3] Batched: false, DataFilters: [], Format: CSV, Location: InMemoryFileIndex(1 paths)[file:/Users/venkata.gudesa/spark/connect-examples/plugin-example/resou..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct + +Explaning plan for custom table: cloned_table with path: /connect-examples/plugin-example/client/../resources/cloned_data.custom +== Parsed Logical Plan == +Relation [id#2,name#3] csv + +== Analyzed Logical Plan == +id: int, name: string +Relation [id#2,name#3] csv + +== Optimized Logical Plan == +Relation [id#2,name#3] csv + +== Physical Plan == +FileScan csv [id#2,name#3] Batched: false, DataFilters: [], Format: CSV, Location: InMemoryFileIndex(1 paths)[file:/Users/venkata.gudesa/spark/connect-examples/plugin-example/resou..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct +``` \ No newline at end of file diff --git a/connect-examples/plugin-example/client/pom.xml b/connect-examples/plugin-example/client/pom.xml new file mode 100644 index 0000000000000..d3ed50b26559a --- /dev/null +++ b/connect-examples/plugin-example/client/pom.xml @@ -0,0 +1,112 @@ + + + + + 4.0.0 + + + org.example + spark-plugin-example + 1.0-SNAPSHOT + ../pom.xml + + + spark-plugin-example-client-scala + jar + + + + + org.example + spark-plugin-example-common + 1.0-SNAPSHOT + + + com.google.protobuf + protobuf-java + + + + + + org.apache.spark + spark-connect-common_${scala.binary} + ${spark.version} + + + + org.apache.spark + spark-connect-client-jvm_${scala.binary} + ${spark.version} + + + + org.scala-lang + scala-library + ${scala.version} + + + + + + src/main/scala + + + + + net.alchim31.maven + scala-maven-plugin + 4.9.2 + + + + compile + testCompile + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + + shade + + + false + true + false + + + + + + + + + + + \ No newline at end of file diff --git a/connect-examples/plugin-example/client/src/main/resources/log4j2.xml b/connect-examples/plugin-example/client/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000..21b0d9719193e --- /dev/null +++ b/connect-examples/plugin-example/client/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala b/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala new file mode 100644 index 0000000000000..f883a30723d97 --- /dev/null +++ b/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala @@ -0,0 +1,100 @@ +/* + * 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.example + +import com.google.protobuf.Any +import org.apache.spark.connect.proto.Command +import org.example.proto +import org.example.proto.CreateTable.Column.{DataType => ProtoDataType} +import org.apache.spark.sql.{functions, Column, DataFrame, Dataset, Row, SparkSession} + +class CustomTable private (private val df: Dataset[Row], private val table: proto.CustomTable) { + + private def spark = df.sparkSession + + def toDF: Dataset[Row] = df + + def explain(): Unit = { + println(s"Explaning plan for custom table: ${table.getName} with path: ${table.getPath}") + df.explain("extended") + } + + def clone(target: String, newName: String, replace: Boolean): CustomTable = { + val cloneTableProto = proto.CloneTable + .newBuilder() + .setTable( + proto.CustomTable + .newBuilder() + .setName(table.getName) + .setPath(table.getPath)) + .setClone( + proto.CustomTable + .newBuilder() + .setName(newName) + .setPath(target)) + .setReplace(replace) + .build() + val customCommand = proto.CustomCommand + .newBuilder() + .setCloneTable(cloneTableProto) + .build() + // Pack the CustomCommand into Any + val customCommandAny = Any.pack(customCommand) + // Set the Any as the extension of a Command + val commandProto = Command + .newBuilder() + .setExtension(customCommandAny) + .build() + // Execute the command + spark.execute(commandProto) + CustomTable.from(spark, newName, target) + } +} + +object CustomTable { + def from(spark: SparkSession, name: String, path: String): CustomTable = { + val table = proto.CustomTable + .newBuilder() + .setName(name) + .setPath(path) + .build() + val relation = proto.CustomRelation + .newBuilder() + .setScan(proto.Scan.newBuilder().setTable(table)) + .build() + val customRelation = Any.pack(relation) + val df = spark.newDataFrame(f => f.setExtension(customRelation)) + new CustomTable(df, table) + } + + def create(spark: SparkSession): CustomTableBuilder = new CustomTableBuilder(spark) + + object DataType extends Enumeration { + type DataType = Value + val Int, String, Float, Boolean = Value + + def toProto(dataType: DataType): ProtoDataType = { + dataType match { + case Int => ProtoDataType.INT + case String => ProtoDataType.STRING + case Float => ProtoDataType.FLOAT + case Boolean => ProtoDataType.BOOLEAN + } + } + } +} diff --git a/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala b/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala new file mode 100644 index 0000000000000..34ae41df37d48 --- /dev/null +++ b/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala @@ -0,0 +1,94 @@ +/* + * 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.example + +import org.apache.spark.sql.SparkSession +import org.example.CustomTable +import com.google.protobuf.Any +import org.apache.spark.connect.proto.Command + +class CustomTableBuilder private[example] (spark: SparkSession) { + import CustomTableBuilder._ + + private var name: String = _ + private var path: String = _ + private var columns: Seq[Column] = Seq.empty + + def name(name: String): CustomTableBuilder = { + this.name = name + this + } + + def path(path: String): CustomTableBuilder = { + this.path = path + this + } + + def addColumn(name: String, dataType: CustomTable.DataType.Value): CustomTableBuilder = { + columns = columns :+ Column(name, dataType) + this + } + + def build(): CustomTable = { + require(name != null, "Name must be set") + require(path != null, "Path must be set") + require(columns.nonEmpty, "At least one column must be added") + + // Define the table creation proto + val createTableProtoBuilder = proto.CreateTable + .newBuilder() + .setTable( + proto.CustomTable + .newBuilder() + .setPath(path) + .setName(name) + .build()) + + columns.foreach { column => + createTableProtoBuilder.addColumns( + proto.CreateTable.Column + .newBuilder() + .setName(column.name) + .setDataType(CustomTable.DataType.toProto(column.dataType)) + .build()) + } + val createTableProto = createTableProtoBuilder.build() // Build the CreateTable object + + // Wrap the CreateTable proto in CustomCommand + val customCommand = proto.CustomCommand + .newBuilder() + .setCreateTable(createTableProto) + .build() + + // Pack the CustomCommand into Any + val customCommandAny = Any.pack(customCommand) + // Set the Any as the extension of a Command + val commandProto = Command + .newBuilder() + .setExtension(customCommandAny) + .build() + + // Execute the command + spark.execute(commandProto) + CustomTable.from(spark, name, path) + } +} + +object CustomTableBuilder { + private case class Column(name: String, dataType: CustomTable.DataType.Value) +} diff --git a/connect-examples/plugin-example/client/src/main/scala/org/example/Main.scala b/connect-examples/plugin-example/client/src/main/scala/org/example/Main.scala new file mode 100644 index 0000000000000..be6144af95a5f --- /dev/null +++ b/connect-examples/plugin-example/client/src/main/scala/org/example/Main.scala @@ -0,0 +1,58 @@ +/* + * 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.example + +import java.nio.file.{Path, Paths} +import org.apache.spark.sql.SparkSession +import org.apache.spark.sql.types.{StructType, StructField, StringType, IntegerType} +import org.example.proto +import org.example.proto.CreateTable.Column.{DataType => ProtoDataType} +import com.google.protobuf.Any +import org.apache.spark.connect.proto.Command + +object Main { + def main(args: Array[String]): Unit = { + val spark = SparkSession.builder().remote("sc://localhost").build() + + // Step 1: Create a custom table from existing data + val tableName = "sample_table" + val fileName = "dummy_data.custom" + + val workingDirectory = System.getProperty("user.dir") + val fullPath = Paths.get(workingDirectory, s"../resources/$fileName") + val customTable = CustomTable + .create(spark) + .name(tableName) + .path(fullPath.toString) + .addColumn("id", CustomTable.DataType.Int) + .addColumn("name", CustomTable.DataType.String) + .build() + + // Step 2: Verify + customTable.explain() + + // Step 3: Clone the custom table + val clonedPath = fullPath.getParent.resolve("cloned_data.custom") + val clonedName = "cloned_table" + val clonedTable = + customTable.clone(target = clonedPath.toString, newName = clonedName, replace = true) + + // Step 4: Verify + clonedTable.explain() + } +} diff --git a/connect-examples/plugin-example/common/pom.xml b/connect-examples/plugin-example/common/pom.xml new file mode 100644 index 0000000000000..ee2dc15ec83e6 --- /dev/null +++ b/connect-examples/plugin-example/common/pom.xml @@ -0,0 +1,76 @@ + + + + + 4.0.0 + + + org.example + spark-plugin-example + 1.0-SNAPSHOT + ../pom.xml + + + spark-plugin-example-common + jar + + + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + org.scala-lang + scala-library + ${scala.version} + + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.0 + true + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + src/main/protobuf + + + + generate-sources + + compile + + + + + + + \ No newline at end of file diff --git a/connect-examples/plugin-example/common/src/main/protobuf/base.proto b/connect-examples/plugin-example/common/src/main/protobuf/base.proto new file mode 100644 index 0000000000000..59d09393a98ff --- /dev/null +++ b/connect-examples/plugin-example/common/src/main/protobuf/base.proto @@ -0,0 +1,26 @@ +/* + * 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. + */ + +syntax = 'proto3'; + +option java_multiple_files = true; +option java_package = "org.example.proto"; + +message CustomTable { + string path = 1; + string name = 2; +} \ No newline at end of file diff --git a/connect-examples/plugin-example/common/src/main/protobuf/commands.proto b/connect-examples/plugin-example/common/src/main/protobuf/commands.proto new file mode 100644 index 0000000000000..3b1a03d2f9014 --- /dev/null +++ b/connect-examples/plugin-example/common/src/main/protobuf/commands.proto @@ -0,0 +1,62 @@ +/* + * 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. + */ + +syntax = 'proto3'; + +option java_multiple_files = true; +option java_package = "org.example.proto"; + +import "base.proto"; + +message CustomCommand { + oneof command_type { + CreateTable create_table = 1; + CloneTable clone_table = 2; + } +} + +message CreateTable { + + // Column in the schema of the table. + message Column { + // (Required) Name of the column. + string name = 1; + // (Required) Data type of the column. + enum DataType { + DATA_TYPE_UNSPECIFIED = 0; // Default value + INT = 1; // Integer data type + STRING = 2; // String data type + FLOAT = 3; // Float data type + BOOLEAN = 4; // Boolean data type + } + DataType data_type = 2; + } + // (Required) Table properties. + CustomTable table = 1; + // (Required) List of columns in the schema of the table. + repeated Column columns = 2; +} + +message CloneTable { + // (Required) The source table to clone. + CustomTable table = 1; + // (Required) Path to the location where the data of the cloned table should be stored. + CustomTable clone = 2; + // (Required) Overwrites the target location when true. + bool replace = 3; +} + diff --git a/connect-examples/plugin-example/common/src/main/protobuf/relations.proto b/connect-examples/plugin-example/common/src/main/protobuf/relations.proto new file mode 100644 index 0000000000000..8f95223a930b6 --- /dev/null +++ b/connect-examples/plugin-example/common/src/main/protobuf/relations.proto @@ -0,0 +1,33 @@ +/* + * 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. + */ + +syntax = 'proto3'; + +option java_multiple_files = true; +option java_package = "org.example.proto"; + +import "base.proto"; + +message CustomRelation { + oneof relation_type { + Scan scan = 1; + } +} + +message Scan { + CustomTable table = 1; +} diff --git a/connect-examples/plugin-example/pom.xml b/connect-examples/plugin-example/pom.xml new file mode 100644 index 0000000000000..58cad2db62cf9 --- /dev/null +++ b/connect-examples/plugin-example/pom.xml @@ -0,0 +1,43 @@ + + + + + 4.0.0 + + org.example + spark-plugin-example + 1.0-SNAPSHOT + pom + + common + server + client + + + 17 + 17 + UTF-8 + 2.13 + 2.13.15 + 3.25.4 + 4.0.0-preview2 + + + diff --git a/connect-examples/plugin-example/resources/dummy_data.custom b/connect-examples/plugin-example/resources/dummy_data.custom new file mode 100644 index 0000000000000..0a6645b757222 --- /dev/null +++ b/connect-examples/plugin-example/resources/dummy_data.custom @@ -0,0 +1,6 @@ +id,name +1,John Doe +2,Jane Smith +3,Bob Johnson +4,Alice Williams +5,Charlie Brown \ No newline at end of file diff --git a/connect-examples/plugin-example/resources/spark-daria_2.13-1.2.3.jar b/connect-examples/plugin-example/resources/spark-daria_2.13-1.2.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..31703de77709d0fb637607e5b7f28cc1b79bb6a5 GIT binary patch literal 187330 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%0Dj2)q2K>PO^J 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 + + + + 4.0.0 + + + org.example + spark-plugin-example + 1.0-SNAPSHOT + ../pom.xml + + + spark-plugin-example-server + jar + + + + + org.example + spark-plugin-example-common + 1.0-SNAPSHOT + + + + org.scala-lang + scala-library + ${scala.version} + + + + org.apache.spark + spark-sql_${scala.binary} + ${spark.version} + provided + + + + org.apache.spark + spark-connect_${scala.binary} + ${spark.version} + provided + + + + + com.github.mrpowers + spark-daria_2.12 + 1.2.3 + provided + + + + + src/main/scala + + + + + net.alchim31.maven + scala-maven-plugin + 4.9.2 + + + + compile + testCompile + + + + + + + \ No newline at end of file diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala new file mode 100644 index 0000000000000..324df000aec4e --- /dev/null +++ b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala @@ -0,0 +1,93 @@ +/* + * 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.example + +import com.google.protobuf.Any +import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan +import org.apache.spark.sql.connect.planner.SparkConnectPlanner +import org.apache.spark.sql.connect.plugin.CommandPlugin +import org.apache.spark.sql.types.{StringType, IntegerType, FloatType, DoubleType, BooleanType, LongType, StructType, StructField, DataType} +import org.example.CustomTable +import org.example.proto +import org.example.proto.CreateTable.Column.{DataType => ProtoDataType} + +import scala.collection.JavaConverters._ + +class CustomCommandPlugin extends CommandPlugin with CustomPluginBase { + override def process(raw: Array[Byte], planner: SparkConnectPlanner): Boolean = { + val command = Any.parseFrom(raw) + println(s"Received command: ${command}") + if (command.is(classOf[proto.CustomCommand])) { + processInternal(command.unpack(classOf[proto.CustomCommand]), planner) + true + } else { + false + } + } + + private def processInternal( + command: proto.CustomCommand, + planner: SparkConnectPlanner): Unit = { + command.getCommandTypeCase match { + case proto.CustomCommand.CommandTypeCase.CREATE_TABLE => + processCreateTable(planner, command.getCreateTable) + case proto.CustomCommand.CommandTypeCase.CLONE_TABLE => + processCloneTable(planner, command.getCloneTable) + case _ => + throw new IllegalArgumentException( + s"Unsupported command type: ${command.getCommandTypeCase}") + } + } + + private def processCreateTable( + planner: SparkConnectPlanner, + createTable: proto.CreateTable): Unit = { + val tableName = createTable.getTable.getName + val tablePath = createTable.getTable.getPath + + val schema = StructType(createTable.getColumnsList.asScala.toSeq.map { column => + StructField( + column.getName, + protoDataTypeToSparkType(column.getDataType), + nullable = true // Assuming all columns are nullable for simplicity + ) + }) + + CustomTable.createTable(tableName, tablePath, planner.session, schema) + } + + private def protoDataTypeToSparkType(protoType: ProtoDataType): DataType = { + protoType match { + case ProtoDataType.INT => IntegerType + case ProtoDataType.STRING => StringType + case ProtoDataType.FLOAT => FloatType + case ProtoDataType.BOOLEAN => BooleanType + case _ => + throw new IllegalArgumentException(s"Unsupported or unknown data type: ${protoType}") + } + } + + private def processCloneTable(planner: SparkConnectPlanner, msg: proto.CloneTable): Unit = { + val sourceTable = getCustomTable(msg.getTable) + CustomTable.cloneTable( + sourceTable, + msg.getClone.getName, + msg.getClone.getPath, + msg.getReplace) + } +} diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomPluginBase.scala b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomPluginBase.scala new file mode 100644 index 0000000000000..b0732d51be451 --- /dev/null +++ b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomPluginBase.scala @@ -0,0 +1,27 @@ +/* + * 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.example + +import org.apache.spark.sql.connect.planner.SparkConnectPlanner +import org.example.proto + +trait CustomPluginBase { + protected def getCustomTable(table: proto.CustomTable): CustomTable = { + CustomTable.getTable(table.getName, table.getPath) + } +} diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala new file mode 100644 index 0000000000000..84775e66f6df7 --- /dev/null +++ b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala @@ -0,0 +1,59 @@ +/* + * 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.example + +import java.util.Optional + +import com.google.protobuf.Any +import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan +import org.apache.spark.sql.connect.planner.SparkConnectPlanner +import org.apache.spark.sql.connect.plugin.RelationPlugin +import org.example.{CustomPluginBase, CustomTable} +import org.example.proto + +class CustomRelationPlugin extends RelationPlugin with CustomPluginBase { + override def transform( + raw: Array[Byte], + planner: SparkConnectPlanner): Optional[LogicalPlan] = { + val rel = Any.parseFrom(raw) + if (rel.is(classOf[proto.CustomRelation])) { + val customRelation = rel.unpack(classOf[proto.CustomRelation]) + // Transform the custom relation + Optional.of(transformInner(customRelation, planner)) + } else { + Optional.empty() + } + } + + private def transformInner( + relation: proto.CustomRelation, + planner: SparkConnectPlanner): LogicalPlan = { + relation.getRelationTypeCase match { + case proto.CustomRelation.RelationTypeCase.SCAN => + transformScan(relation.getScan, planner) + case _ => + throw new IllegalArgumentException( + s"Unsupported relation type: ${relation.getRelationTypeCase}") + } + } + + private def transformScan(scan: proto.Scan, planner: SparkConnectPlanner): LogicalPlan = { + val customTable = getCustomTable(scan.getTable) + customTable.toDF().queryExecution.analyzed + } +} diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala new file mode 100644 index 0000000000000..12c4d5482f59a --- /dev/null +++ b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala @@ -0,0 +1,83 @@ +/* + * 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.example + +import java.util.UUID +import com.github.mrpowers.spark.daria.sql.DariaWriters +import org.apache.spark.sql.{Dataset, Row, SparkSession} +import org.apache.spark.sql.types.StructType + +class CustomTable private (identifier: CustomTable.Identifier, df: Dataset[Row]) { + + def toDF(): Dataset[Row] = df + def flush(): Unit = { + // write dataset to disk as csv file + DariaWriters.writeSingleFile( + df = df, + format = "csv", + sc = df.sparkSession.sparkContext, + tmpFolder = s"./${UUID.randomUUID().toString}", + filename = identifier.path, + saveMode = "overwrite") + } +} + +object CustomTable { + + private case class Identifier(name: String, path: String) + // Collection holding all the CustomTable instances and searchable by the identifer + private val tablesByIdentifier = scala.collection.mutable.Map[Identifier, CustomTable]() + + private[example] def createTable( + name: String, + path: String, + spark: SparkSession, + schema: StructType): CustomTable = { + val identifier = Identifier(name, path) + val df = spark.read + .option("header", "true") + .schema(schema) + .csv(path) + val table = new CustomTable(identifier, df) + tablesByIdentifier(identifier) = table + table + } + + private[example] def cloneTable( + sourceTable: CustomTable, + newName: String, + newPath: String, + replace: Boolean): CustomTable = { + val newIdentifier = Identifier(newName, newPath) + val clonedDf = sourceTable.toDF() + val clonedTable = new CustomTable(newIdentifier, clonedDf) + clonedTable.flush() + tablesByIdentifier(newIdentifier) = clonedTable + clonedTable + } + + // Factory method to get a CustomTable based on it's identifer + def getTable(name: String, path: String): CustomTable = { + val identifier = Identifier(name, path) + tablesByIdentifier.get(identifier) match { + case Some(table) => table + case None => + throw new IllegalArgumentException(s"Table with identifier $identifier not found") + } + } +} From fd722eaf7cfbdb9365dc9e73cf98d8193690bae2 Mon Sep 17 00:00:00 2001 From: vicennial Date: Wed, 22 Jan 2025 11:20:32 +0100 Subject: [PATCH 2/7] documentation and lint --- .../plugin-example/client/pom.xml | 2 +- .../main/scala/org/example/CustomTable.scala | 48 +++++++++++++++++ .../org/example/CustomTableBuilder.scala | 40 ++++++++++++++ .../plugin-example/common/pom.xml | 2 +- .../common/src/main/protobuf/base.proto | 4 +- .../common/src/main/protobuf/commands.proto | 1 - .../common/src/main/protobuf/relations.proto | 1 + connect-examples/plugin-example/pom.xml | 1 - .../plugin-example/server/pom.xml | 2 +- .../org/example/CustomCommandPlugin.scala | 34 ++++++++++++ .../org/example/CustomRelationPlugin.scala | 23 ++++++++ .../main/scala/org/example/CustomTable.scala | 53 +++++++++++++++++-- 12 files changed, 202 insertions(+), 9 deletions(-) diff --git a/connect-examples/plugin-example/client/pom.xml b/connect-examples/plugin-example/client/pom.xml index d3ed50b26559a..da779c146ed33 100644 --- a/connect-examples/plugin-example/client/pom.xml +++ b/connect-examples/plugin-example/client/pom.xml @@ -109,4 +109,4 @@ - \ No newline at end of file + diff --git a/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala b/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala index f883a30723d97..2642f71c9e5cb 100644 --- a/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala +++ b/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala @@ -23,17 +23,42 @@ import org.example.proto import org.example.proto.CreateTable.Column.{DataType => ProtoDataType} import org.apache.spark.sql.{functions, Column, DataFrame, Dataset, Row, SparkSession} +/** + * Represents a custom table with associated DataFrame and metadata. + * + * @param df The underlying DataFrame. + * @param table The metadata of the custom table. + */ class CustomTable private (private val df: Dataset[Row], private val table: proto.CustomTable) { + /** + * Returns the Spark session associated with the DataFrame. + */ private def spark = df.sparkSession + /** + * Converts the custom table to a DataFrame. + * + * @return The underlying DataFrame. + */ def toDF: Dataset[Row] = df + /** + * Prints the execution plan of the custom table. + */ def explain(): Unit = { println(s"Explaning plan for custom table: ${table.getName} with path: ${table.getPath}") df.explain("extended") } + /** + * Clones the custom table to a new location with a new name. + * + * @param target The target path for the cloned table. + * @param newName The new name for the cloned table. + * @param replace Whether to replace the target location if it exists. + * @return A new `CustomTable` instance representing the cloned table. + */ def clone(target: String, newName: String, replace: Boolean): CustomTable = { val cloneTableProto = proto.CloneTable .newBuilder() @@ -67,6 +92,14 @@ class CustomTable private (private val df: Dataset[Row], private val table: prot } object CustomTable { + /** + * Creates a `CustomTable` from the given Spark session, name, and path. + * + * @param spark The Spark session. + * @param name The name of the table. + * @param path The path of the table. + * @return A new `CustomTable` instance. + */ def from(spark: SparkSession, name: String, path: String): CustomTable = { val table = proto.CustomTable .newBuilder() @@ -82,12 +115,27 @@ object CustomTable { new CustomTable(df, table) } + /** + * Creates a new `CustomTableBuilder` instance. + * + * @param spark The Spark session. + * @return A new `CustomTableBuilder` instance. + */ def create(spark: SparkSession): CustomTableBuilder = new CustomTableBuilder(spark) + /** + * Enumeration for data types. + */ object DataType extends Enumeration { type DataType = Value val Int, String, Float, Boolean = Value + /** + * Converts a `DataType` to its corresponding `ProtoDataType`. + * + * @param dataType The data type to convert. + * @return The corresponding `ProtoDataType`. + */ def toProto(dataType: DataType): ProtoDataType = { dataType match { case Int => ProtoDataType.INT diff --git a/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala b/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala index 34ae41df37d48..88d81c44a548f 100644 --- a/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala +++ b/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala @@ -22,6 +22,11 @@ import org.example.CustomTable import com.google.protobuf.Any import org.apache.spark.connect.proto.Command +/** + * Builder class for constructing a `CustomTable` instance. + * + * @param spark The Spark session. + */ class CustomTableBuilder private[example] (spark: SparkSession) { import CustomTableBuilder._ @@ -29,21 +34,46 @@ class CustomTableBuilder private[example] (spark: SparkSession) { private var path: String = _ private var columns: Seq[Column] = Seq.empty + /** + * Sets the name of the custom table. + * + * @param name The name of the table. + * @return The current `CustomTableBuilder` instance. + */ def name(name: String): CustomTableBuilder = { this.name = name this } + /** + * Sets the path of the custom table. + * + * @param path The path of the table. + * @return The current `CustomTableBuilder` instance. + */ def path(path: String): CustomTableBuilder = { this.path = path this } + /** + * Adds a column to the custom table. + * + * @param name The name of the column. + * @param dataType The data type of the column. + * @return The current `CustomTableBuilder` instance. + */ def addColumn(name: String, dataType: CustomTable.DataType.Value): CustomTableBuilder = { columns = columns :+ Column(name, dataType) this } + /** + * Builds the `CustomTable` instance. + * + * @return A new `CustomTable` instance. + * @throws IllegalArgumentException if name, path, or columns are not set. + */ def build(): CustomTable = { require(name != null, "Name must be set") require(path != null, "Path must be set") @@ -59,6 +89,7 @@ class CustomTableBuilder private[example] (spark: SparkSession) { .setName(name) .build()) + // Add columns to the table creation proto columns.foreach { column => createTableProtoBuilder.addColumns( proto.CreateTable.Column @@ -85,10 +116,19 @@ class CustomTableBuilder private[example] (spark: SparkSession) { // Execute the command spark.execute(commandProto) + + // After the command is executed, create a client-side representation of the table with the + // leaf node of the client-side dataset being the Scan node for the custom table. CustomTable.from(spark, name, path) } } object CustomTableBuilder { + /** + * Case class representing a column in the custom table. + * + * @param name The name of the column. + * @param dataType The data type of the column. + */ private case class Column(name: String, dataType: CustomTable.DataType.Value) } diff --git a/connect-examples/plugin-example/common/pom.xml b/connect-examples/plugin-example/common/pom.xml index ee2dc15ec83e6..0e284b27cd08e 100644 --- a/connect-examples/plugin-example/common/pom.xml +++ b/connect-examples/plugin-example/common/pom.xml @@ -73,4 +73,4 @@ - \ No newline at end of file + diff --git a/connect-examples/plugin-example/common/src/main/protobuf/base.proto b/connect-examples/plugin-example/common/src/main/protobuf/base.proto index 59d09393a98ff..3e7181d7195ea 100644 --- a/connect-examples/plugin-example/common/src/main/protobuf/base.proto +++ b/connect-examples/plugin-example/common/src/main/protobuf/base.proto @@ -21,6 +21,8 @@ option java_multiple_files = true; option java_package = "org.example.proto"; message CustomTable { + // Path to the custom table. string path = 1; + // Name of the custom table. string name = 2; -} \ No newline at end of file +} diff --git a/connect-examples/plugin-example/common/src/main/protobuf/commands.proto b/connect-examples/plugin-example/common/src/main/protobuf/commands.proto index 3b1a03d2f9014..d1ca8a1a183cb 100644 --- a/connect-examples/plugin-example/common/src/main/protobuf/commands.proto +++ b/connect-examples/plugin-example/common/src/main/protobuf/commands.proto @@ -59,4 +59,3 @@ message CloneTable { // (Required) Overwrites the target location when true. bool replace = 3; } - diff --git a/connect-examples/plugin-example/common/src/main/protobuf/relations.proto b/connect-examples/plugin-example/common/src/main/protobuf/relations.proto index 8f95223a930b6..e742c3c4b02e7 100644 --- a/connect-examples/plugin-example/common/src/main/protobuf/relations.proto +++ b/connect-examples/plugin-example/common/src/main/protobuf/relations.proto @@ -29,5 +29,6 @@ message CustomRelation { } message Scan { + // (Required) Table to scan. CustomTable table = 1; } diff --git a/connect-examples/plugin-example/pom.xml b/connect-examples/plugin-example/pom.xml index 58cad2db62cf9..55aa742f5d796 100644 --- a/connect-examples/plugin-example/pom.xml +++ b/connect-examples/plugin-example/pom.xml @@ -40,4 +40,3 @@ 4.0.0-preview2 - diff --git a/connect-examples/plugin-example/server/pom.xml b/connect-examples/plugin-example/server/pom.xml index e959730815cc8..ce40802b1e49e 100644 --- a/connect-examples/plugin-example/server/pom.xml +++ b/connect-examples/plugin-example/server/pom.xml @@ -88,4 +88,4 @@ - \ No newline at end of file + diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala index 324df000aec4e..dbf90cccfe2db 100644 --- a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala +++ b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala @@ -29,6 +29,14 @@ import org.example.proto.CreateTable.Column.{DataType => ProtoDataType} import scala.collection.JavaConverters._ class CustomCommandPlugin extends CommandPlugin with CustomPluginBase { + + /** + * Processes the raw byte array containing the command. + * + * @param raw The raw byte array of the command. + * @param planner The SparkConnectPlanner instance. + * @return True if the command was processed, false otherwise. + */ override def process(raw: Array[Byte], planner: SparkConnectPlanner): Boolean = { val command = Any.parseFrom(raw) println(s"Received command: ${command}") @@ -40,6 +48,12 @@ class CustomCommandPlugin extends CommandPlugin with CustomPluginBase { } } + /** + * Processes the unpacked CustomCommand. + * + * @param command The unpacked CustomCommand. + * @param planner The SparkConnectPlanner instance. + */ private def processInternal( command: proto.CustomCommand, planner: SparkConnectPlanner): Unit = { @@ -54,12 +68,19 @@ class CustomCommandPlugin extends CommandPlugin with CustomPluginBase { } } + /** + * Processes the CreateTable command. + * + * @param planner The SparkConnectPlanner instance. + * @param createTable The CreateTable message. + */ private def processCreateTable( planner: SparkConnectPlanner, createTable: proto.CreateTable): Unit = { val tableName = createTable.getTable.getName val tablePath = createTable.getTable.getPath + // Convert the list of columns from the protobuf message to a Spark schema val schema = StructType(createTable.getColumnsList.asScala.toSeq.map { column => StructField( column.getName, @@ -68,9 +89,16 @@ class CustomCommandPlugin extends CommandPlugin with CustomPluginBase { ) }) + // Create the table using the CustomTable utility CustomTable.createTable(tableName, tablePath, planner.session, schema) } + /** + * Converts a protobuf DataType to a Spark DataType. + * + * @param protoType The protobuf DataType. + * @return The corresponding Spark DataType. + */ private def protoDataTypeToSparkType(protoType: ProtoDataType): DataType = { protoType match { case ProtoDataType.INT => IntegerType @@ -82,6 +110,12 @@ class CustomCommandPlugin extends CommandPlugin with CustomPluginBase { } } + /** + * Processes the CloneTable command. + * + * @param planner The SparkConnectPlanner instance. + * @param msg The CloneTable message. + */ private def processCloneTable(planner: SparkConnectPlanner, msg: proto.CloneTable): Unit = { val sourceTable = getCustomTable(msg.getTable) CustomTable.cloneTable( diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala index 84775e66f6df7..58f85409d62a6 100644 --- a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala +++ b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala @@ -27,6 +27,14 @@ import org.example.{CustomPluginBase, CustomTable} import org.example.proto class CustomRelationPlugin extends RelationPlugin with CustomPluginBase { + + /** + * Transforms the raw byte array containing the relation into a Spark logical plan. + * + * @param raw The raw byte array of the relation. + * @param planner The SparkConnectPlanner instance. + * @return An Optional containing the LogicalPlan if the relation was processed, empty otherwise. + */ override def transform( raw: Array[Byte], planner: SparkConnectPlanner): Optional[LogicalPlan] = { @@ -40,6 +48,13 @@ class CustomRelationPlugin extends RelationPlugin with CustomPluginBase { } } + /** + * Transforms the unpacked CustomRelation into a Spark logical plan. + * + * @param relation The unpacked CustomRelation. + * @param planner The SparkConnectPlanner instance. + * @return The corresponding Spark LogicalPlan. + */ private def transformInner( relation: proto.CustomRelation, planner: SparkConnectPlanner): LogicalPlan = { @@ -52,8 +67,16 @@ class CustomRelationPlugin extends RelationPlugin with CustomPluginBase { } } + /** + * Transforms the Scan relation into a Spark logical plan. + * + * @param scan The Scan message. + * @param planner The SparkConnectPlanner instance. + * @return The corresponding Spark LogicalPlan. + */ private def transformScan(scan: proto.Scan, planner: SparkConnectPlanner): LogicalPlan = { val customTable = getCustomTable(scan.getTable) + // Convert the custom table to a DataFrame and get its logical plan customTable.toDF().queryExecution.analyzed } } diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala index 12c4d5482f59a..92bcef2b43bad 100644 --- a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala +++ b/connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala @@ -22,11 +22,26 @@ import com.github.mrpowers.spark.daria.sql.DariaWriters import org.apache.spark.sql.{Dataset, Row, SparkSession} import org.apache.spark.sql.types.StructType +/** + * Represents a custom table with an identifier and a DataFrame. + * + * @param identifier The unique identifier for the table. + * @param df The DataFrame associated with the table. + */ class CustomTable private (identifier: CustomTable.Identifier, df: Dataset[Row]) { + /** + * Returns the DataFrame associated with the table. + * + * @return The DataFrame. + */ def toDF(): Dataset[Row] = df + + /** + * Writes the DataFrame to disk as a CSV file. + */ def flush(): Unit = { - // write dataset to disk as csv file + // Write dataset to disk as a CSV file DariaWriters.writeSingleFile( df = df, format = "csv", @@ -39,10 +54,26 @@ class CustomTable private (identifier: CustomTable.Identifier, df: Dataset[Row]) object CustomTable { + /** + * Represents the unique identifier for a custom table. + * + * @param name The name of the table. + * @param path The path where the table is stored. + */ private case class Identifier(name: String, path: String) - // Collection holding all the CustomTable instances and searchable by the identifer + + // Collection holding all the CustomTable instances and searchable by the identifier private val tablesByIdentifier = scala.collection.mutable.Map[Identifier, CustomTable]() + /** + * Creates a new custom table. + * + * @param name The name of the table. + * @param path The path where the table is stored. + * @param spark The SparkSession instance. + * @param schema The schema of the table. + * @return The created CustomTable instance. + */ private[example] def createTable( name: String, path: String, @@ -58,6 +89,15 @@ object CustomTable { table } + /** + * Clones an existing custom table. + * + * @param sourceTable The source table to clone. + * @param newName The name of the new table. + * @param newPath The path where the new table will be stored. + * @param replace Whether to replace the existing table if it exists. + * @return The cloned CustomTable instance. + */ private[example] def cloneTable( sourceTable: CustomTable, newName: String, @@ -71,7 +111,14 @@ object CustomTable { clonedTable } - // Factory method to get a CustomTable based on it's identifer + /** + * Retrieves a custom table based on its identifier. + * + * @param name The name of the table. + * @param path The path where the table is stored. + * @return The CustomTable instance. + * @throws IllegalArgumentException if the table is not found. + */ def getTable(name: String, path: String): CustomTable = { val identifier = Identifier(name, path) tablesByIdentifier.get(identifier) match { From 58ef98373c523b7cf36c26ddc56d12a54d902f57 Mon Sep 17 00:00:00 2001 From: vicennial Date: Wed, 22 Jan 2025 11:53:02 +0100 Subject: [PATCH 3/7] rename + lint --- .../README.md | 26 ++++++++++-------- .../client/pom.xml | 6 ++-- .../client/src/main/resources/log4j2.xml | 0 .../main/scala/org/example/CustomTable.scala | 2 +- .../org/example/CustomTableBuilder.scala | 0 .../src/main/scala/org/example/Main.scala | 0 .../common/pom.xml | 4 +-- .../common/src/main/protobuf/base.proto | 0 .../common/src/main/protobuf/commands.proto | 0 .../common/src/main/protobuf/relations.proto | 0 .../pom.xml | 2 +- .../resources/dummy_data.custom | 0 .../resources/spark-daria_2.13-1.2.3.jar | Bin .../server/pom.xml | 6 ++-- .../org/example/CustomCommandPlugin.scala | 0 .../scala/org/example/CustomPluginBase.scala | 0 .../org/example/CustomRelationPlugin.scala | 0 .../main/scala/org/example/CustomTable.scala | 0 18 files changed, 24 insertions(+), 22 deletions(-) rename connect-examples/{plugin-example => server-library-example}/README.md (73%) rename connect-examples/{plugin-example => server-library-example}/client/pom.xml (95%) rename connect-examples/{plugin-example => server-library-example}/client/src/main/resources/log4j2.xml (100%) rename connect-examples/{plugin-example => server-library-example}/client/src/main/scala/org/example/CustomTable.scala (97%) rename connect-examples/{plugin-example => server-library-example}/client/src/main/scala/org/example/CustomTableBuilder.scala (100%) rename connect-examples/{plugin-example => server-library-example}/client/src/main/scala/org/example/Main.scala (100%) rename connect-examples/{plugin-example => server-library-example}/common/pom.xml (95%) rename connect-examples/{plugin-example => server-library-example}/common/src/main/protobuf/base.proto (100%) rename connect-examples/{plugin-example => server-library-example}/common/src/main/protobuf/commands.proto (100%) rename connect-examples/{plugin-example => server-library-example}/common/src/main/protobuf/relations.proto (100%) rename connect-examples/{plugin-example => server-library-example}/pom.xml (96%) rename connect-examples/{plugin-example => server-library-example}/resources/dummy_data.custom (100%) rename connect-examples/{plugin-example => server-library-example}/resources/spark-daria_2.13-1.2.3.jar (100%) rename connect-examples/{plugin-example => server-library-example}/server/pom.xml (93%) rename connect-examples/{plugin-example => server-library-example}/server/src/main/scala/org/example/CustomCommandPlugin.scala (100%) rename connect-examples/{plugin-example => server-library-example}/server/src/main/scala/org/example/CustomPluginBase.scala (100%) rename connect-examples/{plugin-example => server-library-example}/server/src/main/scala/org/example/CustomRelationPlugin.scala (100%) rename connect-examples/{plugin-example => server-library-example}/server/src/main/scala/org/example/CustomTable.scala (100%) diff --git a/connect-examples/plugin-example/README.md b/connect-examples/server-library-example/README.md similarity index 73% rename from connect-examples/plugin-example/README.md rename to connect-examples/server-library-example/README.md index f1d529d672025..52df6d7029871 100644 --- a/connect-examples/plugin-example/README.md +++ b/connect-examples/server-library-example/README.md @@ -1,6 +1,8 @@ -# Spark Plugin Example - Custom Datasource Handler +# Spark Server Library Example - Custom Datasource Handler -This example demonstrates a modular maven-based project architecture with separate client, server and common components. It leverages the extensibility of Spark Connect to create a plugin that may be attached to the server to extend the functionality of the Spark Connect server as a whole. Below is a detailed overview of the setup and functionality. +This example demonstrates a modular maven-based project architecture with separate client, server +and common components. It leverages the extensibility of Spark Connect to create a server library +that may be attached to the server to extend the functionality of the Spark Connect server as a whole. Below is a detailed overview of the setup and functionality. ## Project Structure @@ -68,7 +70,7 @@ reading, writing and processing data in the custom format. The plugins (`CustomC 1. **Navigate to the sample project from `SPARK_HOME`**: ```bash - cd connect-examples/plugin-example + cd connect-examples/server-library-example ``` 2. **Build and package the modules**: @@ -83,25 +85,25 @@ reading, writing and processing data in the custom format. The plugins (`CustomC 4. **Copy relevant JARs to the root of the unpacked Spark distribution**: ```bash cp \ - /connect-examples/plugin-example/resources/spark-daria_2.13-1.2.3.jar \ - /connect-examples/plugin-example/common/target/spark-plugin-example-common-1.0-SNAPSHOT.jar \ - /connect-examples/plugin-example/server/target/spark-plugin-example-server-1.0-SNAPSHOT.jar \ + /connect-examples/server-library-example/resources/spark-daria_2.13-1.2.3.jar \ + /connect-examples/server-library-example/common/target/spark-server-library-example-common-1.0-SNAPSHOT.jar \ + /connect-examples/server-library-example/server/target/spark-server-library-example-server-extension-1.0-SNAPSHOT.jar \ . ``` 5. **Start the Spark Connect Server with the relevant JARs**: ```bash bin/spark-connect-shell \ - --jars spark-plugin-example-server-1.0-SNAPSHOT.jar,spark-plugin-example-common-1.0-SNAPSHOT.jar,spark-daria_2.13-1.2.3.jar \ + --jars spark-server-library-example-server-extension,spark-server-library-example-common-1.0-SNAPSHOT.jar,spark-daria_2.13-1.2.3.jar \ --conf spark.connect.extensions.relation.classes=org.example.CustomRelationPlugin \ --conf spark.connect.extensions.command.classes=org.example.CustomCommandPlugin ``` 6. **In a different terminal, navigate back to the root of the sample project and start the client**: ```bash - java -cp client/target/spark-plugin-example-client-scala-1.0-SNAPSHOT.jar org.example.Main + java -cp client/target/spark-server-library-client-package-scala-1.0-SNAPSHOT.jar org.example.Main ``` 7. **Notice the printed output in the client terminal as well as the creation of the cloned table**: ```protobuf -Explaning plan for custom table: sample_table with path: /spark/connect-examples/plugin-example/client/../resources/dummy_data.custom +Explaining plan for custom table: sample_table with path: /spark/connect-examples/server-library-example/client/../resources/dummy_data.custom == Parsed Logical Plan == Relation [id#2,name#3] csv @@ -113,9 +115,9 @@ Relation [id#2,name#3] csv Relation [id#2,name#3] csv == Physical Plan == -FileScan csv [id#2,name#3] Batched: false, DataFilters: [], Format: CSV, Location: InMemoryFileIndex(1 paths)[file:/Users/venkata.gudesa/spark/connect-examples/plugin-example/resou..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct +FileScan csv [id#2,name#3] Batched: false, DataFilters: [], Format: CSV, Location: InMemoryFileIndex(1 paths)[file:/Users/venkata.gudesa/spark/connect-examples/server-library-example/resou..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct -Explaning plan for custom table: cloned_table with path: /connect-examples/plugin-example/client/../resources/cloned_data.custom +Explaining plan for custom table: cloned_table with path: /connect-examples/server-library-example/client/../resources/cloned_data.custom == Parsed Logical Plan == Relation [id#2,name#3] csv @@ -127,5 +129,5 @@ Relation [id#2,name#3] csv Relation [id#2,name#3] csv == Physical Plan == -FileScan csv [id#2,name#3] Batched: false, DataFilters: [], Format: CSV, Location: InMemoryFileIndex(1 paths)[file:/Users/venkata.gudesa/spark/connect-examples/plugin-example/resou..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct +FileScan csv [id#2,name#3] Batched: false, DataFilters: [], Format: CSV, Location: InMemoryFileIndex(1 paths)[file:/Users/venkata.gudesa/spark/connect-examples/server-library-example/resou..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct ``` \ No newline at end of file diff --git a/connect-examples/plugin-example/client/pom.xml b/connect-examples/server-library-example/client/pom.xml similarity index 95% rename from connect-examples/plugin-example/client/pom.xml rename to connect-examples/server-library-example/client/pom.xml index da779c146ed33..e93d00ff7d402 100644 --- a/connect-examples/plugin-example/client/pom.xml +++ b/connect-examples/server-library-example/client/pom.xml @@ -23,19 +23,19 @@ org.example - spark-plugin-example + spark-server-library-example 1.0-SNAPSHOT ../pom.xml - spark-plugin-example-client-scala + spark-server-library-client-package-scala jar org.example - spark-plugin-example-common + spark-server-library-example-common 1.0-SNAPSHOT diff --git a/connect-examples/plugin-example/client/src/main/resources/log4j2.xml b/connect-examples/server-library-example/client/src/main/resources/log4j2.xml similarity index 100% rename from connect-examples/plugin-example/client/src/main/resources/log4j2.xml rename to connect-examples/server-library-example/client/src/main/resources/log4j2.xml diff --git a/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala b/connect-examples/server-library-example/client/src/main/scala/org/example/CustomTable.scala similarity index 97% rename from connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala rename to connect-examples/server-library-example/client/src/main/scala/org/example/CustomTable.scala index 2642f71c9e5cb..101f7873268fd 100644 --- a/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTable.scala +++ b/connect-examples/server-library-example/client/src/main/scala/org/example/CustomTable.scala @@ -47,7 +47,7 @@ class CustomTable private (private val df: Dataset[Row], private val table: prot * Prints the execution plan of the custom table. */ def explain(): Unit = { - println(s"Explaning plan for custom table: ${table.getName} with path: ${table.getPath}") + println(s"Explaining plan for custom table: ${table.getName} with path: ${table.getPath}") df.explain("extended") } diff --git a/connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala b/connect-examples/server-library-example/client/src/main/scala/org/example/CustomTableBuilder.scala similarity index 100% rename from connect-examples/plugin-example/client/src/main/scala/org/example/CustomTableBuilder.scala rename to connect-examples/server-library-example/client/src/main/scala/org/example/CustomTableBuilder.scala diff --git a/connect-examples/plugin-example/client/src/main/scala/org/example/Main.scala b/connect-examples/server-library-example/client/src/main/scala/org/example/Main.scala similarity index 100% rename from connect-examples/plugin-example/client/src/main/scala/org/example/Main.scala rename to connect-examples/server-library-example/client/src/main/scala/org/example/Main.scala diff --git a/connect-examples/plugin-example/common/pom.xml b/connect-examples/server-library-example/common/pom.xml similarity index 95% rename from connect-examples/plugin-example/common/pom.xml rename to connect-examples/server-library-example/common/pom.xml index 0e284b27cd08e..c88e8f1646689 100644 --- a/connect-examples/plugin-example/common/pom.xml +++ b/connect-examples/server-library-example/common/pom.xml @@ -23,12 +23,12 @@ org.example - spark-plugin-example + spark-server-library-example 1.0-SNAPSHOT ../pom.xml - spark-plugin-example-common + spark-server-library-example-common jar diff --git a/connect-examples/plugin-example/common/src/main/protobuf/base.proto b/connect-examples/server-library-example/common/src/main/protobuf/base.proto similarity index 100% rename from connect-examples/plugin-example/common/src/main/protobuf/base.proto rename to connect-examples/server-library-example/common/src/main/protobuf/base.proto diff --git a/connect-examples/plugin-example/common/src/main/protobuf/commands.proto b/connect-examples/server-library-example/common/src/main/protobuf/commands.proto similarity index 100% rename from connect-examples/plugin-example/common/src/main/protobuf/commands.proto rename to connect-examples/server-library-example/common/src/main/protobuf/commands.proto diff --git a/connect-examples/plugin-example/common/src/main/protobuf/relations.proto b/connect-examples/server-library-example/common/src/main/protobuf/relations.proto similarity index 100% rename from connect-examples/plugin-example/common/src/main/protobuf/relations.proto rename to connect-examples/server-library-example/common/src/main/protobuf/relations.proto diff --git a/connect-examples/plugin-example/pom.xml b/connect-examples/server-library-example/pom.xml similarity index 96% rename from connect-examples/plugin-example/pom.xml rename to connect-examples/server-library-example/pom.xml index 55aa742f5d796..d9cf169d8bc3a 100644 --- a/connect-examples/plugin-example/pom.xml +++ b/connect-examples/server-library-example/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.example - spark-plugin-example + spark-server-library-example 1.0-SNAPSHOT pom diff --git a/connect-examples/plugin-example/resources/dummy_data.custom b/connect-examples/server-library-example/resources/dummy_data.custom similarity index 100% rename from connect-examples/plugin-example/resources/dummy_data.custom rename to connect-examples/server-library-example/resources/dummy_data.custom diff --git a/connect-examples/plugin-example/resources/spark-daria_2.13-1.2.3.jar b/connect-examples/server-library-example/resources/spark-daria_2.13-1.2.3.jar similarity index 100% rename from connect-examples/plugin-example/resources/spark-daria_2.13-1.2.3.jar rename to connect-examples/server-library-example/resources/spark-daria_2.13-1.2.3.jar diff --git a/connect-examples/plugin-example/server/pom.xml b/connect-examples/server-library-example/server/pom.xml similarity index 93% rename from connect-examples/plugin-example/server/pom.xml rename to connect-examples/server-library-example/server/pom.xml index ce40802b1e49e..0677f9eed3308 100644 --- a/connect-examples/plugin-example/server/pom.xml +++ b/connect-examples/server-library-example/server/pom.xml @@ -23,19 +23,19 @@ org.example - spark-plugin-example + spark-server-library-example 1.0-SNAPSHOT ../pom.xml - spark-plugin-example-server + spark-server-library-example-server-extension jar org.example - spark-plugin-example-common + spark-server-library-example-common 1.0-SNAPSHOT diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala b/connect-examples/server-library-example/server/src/main/scala/org/example/CustomCommandPlugin.scala similarity index 100% rename from connect-examples/plugin-example/server/src/main/scala/org/example/CustomCommandPlugin.scala rename to connect-examples/server-library-example/server/src/main/scala/org/example/CustomCommandPlugin.scala diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomPluginBase.scala b/connect-examples/server-library-example/server/src/main/scala/org/example/CustomPluginBase.scala similarity index 100% rename from connect-examples/plugin-example/server/src/main/scala/org/example/CustomPluginBase.scala rename to connect-examples/server-library-example/server/src/main/scala/org/example/CustomPluginBase.scala diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala b/connect-examples/server-library-example/server/src/main/scala/org/example/CustomRelationPlugin.scala similarity index 100% rename from connect-examples/plugin-example/server/src/main/scala/org/example/CustomRelationPlugin.scala rename to connect-examples/server-library-example/server/src/main/scala/org/example/CustomRelationPlugin.scala diff --git a/connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala b/connect-examples/server-library-example/server/src/main/scala/org/example/CustomTable.scala similarity index 100% rename from connect-examples/plugin-example/server/src/main/scala/org/example/CustomTable.scala rename to connect-examples/server-library-example/server/src/main/scala/org/example/CustomTable.scala From 3c3ace7f0287c99d83daaebb8a53bd0017c02e9d Mon Sep 17 00:00:00 2001 From: vicennial Date: Fri, 24 Jan 2025 18:49:29 +0100 Subject: [PATCH 4/7] documentation and lint --- .../src/main/scala/org/example/CustomCommandPlugin.scala | 8 +++++++- .../src/main/scala/org/example/CustomRelationPlugin.scala | 8 ++++++++ dev/.rat-excludes | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomCommandPlugin.scala b/connect-examples/server-library-example/server/src/main/scala/org/example/CustomCommandPlugin.scala index dbf90cccfe2db..5a8c41e693abc 100644 --- a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomCommandPlugin.scala +++ b/connect-examples/server-library-example/server/src/main/scala/org/example/CustomCommandPlugin.scala @@ -28,6 +28,13 @@ import org.example.proto.CreateTable.Column.{DataType => ProtoDataType} import scala.collection.JavaConverters._ +/** + * Commands are distinct actions that can be executed. Unlike relations, which focus on the + * transformation and nesting of output data, commands represent singular operations that perform + * specific tasks on the data. + * In this example, the `CustomCommandPlugin` handles operations related to creating and duplicating + * custom tables. + */ class CustomCommandPlugin extends CommandPlugin with CustomPluginBase { /** @@ -39,7 +46,6 @@ class CustomCommandPlugin extends CommandPlugin with CustomPluginBase { */ override def process(raw: Array[Byte], planner: SparkConnectPlanner): Boolean = { val command = Any.parseFrom(raw) - println(s"Received command: ${command}") if (command.is(classOf[proto.CustomCommand])) { processInternal(command.unpack(classOf[proto.CustomCommand]), planner) true diff --git a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomRelationPlugin.scala b/connect-examples/server-library-example/server/src/main/scala/org/example/CustomRelationPlugin.scala index 58f85409d62a6..77abbb67b9591 100644 --- a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomRelationPlugin.scala +++ b/connect-examples/server-library-example/server/src/main/scala/org/example/CustomRelationPlugin.scala @@ -26,6 +26,14 @@ import org.apache.spark.sql.connect.plugin.RelationPlugin import org.example.{CustomPluginBase, CustomTable} import org.example.proto +/** + * Relations are fundamental to dataset transformations, acting as the mechanism through which an + * input dataset is transformed into an output dataset. Conceptually, relations can be likened to + * tables within a database, manipulated to achieve desired outcomes. + * In this example, the `CustomRelationPlugin` handles the transformation related to scanning + * custom tables. The scan relation would appear as leaf nodes in a dataset's associated logical + * plan node when it involves reads from the custom tables. + */ class CustomRelationPlugin extends RelationPlugin with CustomPluginBase { /** diff --git a/dev/.rat-excludes b/dev/.rat-excludes index d8c9196293950..69fe405b1be27 100644 --- a/dev/.rat-excludes +++ b/dev/.rat-excludes @@ -139,3 +139,4 @@ core/src/main/resources/org/apache/spark/ui/static/package.json testCommitLog .*\.har .nojekyll +dummy_data.custom From 1fa593ca284ae21e7fd2302ceed867f025b54e45 Mon Sep 17 00:00:00 2001 From: vicennial Date: Wed, 12 Feb 2025 22:30:53 +0100 Subject: [PATCH 5/7] package name and import ordering --- connect-examples/server-library-example/README.md | 8 ++++---- .../server-library-example/client/pom.xml | 4 ++-- .../examples/serverlibrary}/CustomTable.scala | 7 ++++--- .../serverlibrary}/CustomTableBuilder.scala | 9 +++++---- .../connect/examples/serverlibrary}/Main.scala | 14 ++++++++------ .../server-library-example/common/pom.xml | 2 +- .../common/src/main/protobuf/base.proto | 2 +- .../common/src/main/protobuf/commands.proto | 2 +- .../common/src/main/protobuf/relations.proto | 2 +- connect-examples/server-library-example/pom.xml | 2 +- .../server-library-example/server/pom.xml | 4 ++-- .../serverlibrary}/CustomCommandPlugin.scala | 13 +++++++------ .../examples/serverlibrary}/CustomPluginBase.scala | 4 ++-- .../serverlibrary}/CustomRelationPlugin.scala | 7 ++++--- .../examples/serverlibrary}/CustomTable.scala | 9 +++++---- 15 files changed, 48 insertions(+), 41 deletions(-) rename connect-examples/server-library-example/client/src/main/scala/org/{example => apache/connect/examples/serverlibrary}/CustomTable.scala (95%) rename connect-examples/server-library-example/client/src/main/scala/org/{example => apache/connect/examples/serverlibrary}/CustomTableBuilder.scala (95%) rename connect-examples/server-library-example/client/src/main/scala/org/{example => apache/connect/examples/serverlibrary}/Main.scala (87%) rename connect-examples/server-library-example/server/src/main/scala/org/{example => apache/connect/examples/serverlibrary}/CustomCommandPlugin.scala (92%) rename connect-examples/server-library-example/server/src/main/scala/org/{example => apache/connect/examples/serverlibrary}/CustomPluginBase.scala (90%) rename connect-examples/server-library-example/server/src/main/scala/org/{example => apache/connect/examples/serverlibrary}/CustomRelationPlugin.scala (94%) rename connect-examples/server-library-example/server/src/main/scala/org/{example => apache/connect/examples/serverlibrary}/CustomTable.scala (96%) diff --git a/connect-examples/server-library-example/README.md b/connect-examples/server-library-example/README.md index 52df6d7029871..62a1a1d8cf820 100644 --- a/connect-examples/server-library-example/README.md +++ b/connect-examples/server-library-example/README.md @@ -93,13 +93,13 @@ reading, writing and processing data in the custom format. The plugins (`CustomC 5. **Start the Spark Connect Server with the relevant JARs**: ```bash bin/spark-connect-shell \ - --jars spark-server-library-example-server-extension,spark-server-library-example-common-1.0-SNAPSHOT.jar,spark-daria_2.13-1.2.3.jar \ - --conf spark.connect.extensions.relation.classes=org.example.CustomRelationPlugin \ - --conf spark.connect.extensions.command.classes=org.example.CustomCommandPlugin + --jars spark-server-library-example-server-extension-1.0-SNAPSHOT.jar,spark-server-library-example-common-1.0-SNAPSHOT.jar,spark-daria_2.13-1.2.3.jar \ + --conf spark.connect.extensions.relation.classes=org.apache.connect.examples.serverlibrary.CustomRelationPlugin \ + --conf spark.connect.extensions.command.classes=org.apache.connect.examples.serverlibrary.CustomCommandPlugin ``` 6. **In a different terminal, navigate back to the root of the sample project and start the client**: ```bash - java -cp client/target/spark-server-library-client-package-scala-1.0-SNAPSHOT.jar org.example.Main + java -cp client/target/spark-server-library-client-package-scala-1.0-SNAPSHOT.jar org.apache.connect.examples.serverlibrary.Main ``` 7. **Notice the printed output in the client terminal as well as the creation of the cloned table**: ```protobuf diff --git a/connect-examples/server-library-example/client/pom.xml b/connect-examples/server-library-example/client/pom.xml index e93d00ff7d402..c00e957bd59f4 100644 --- a/connect-examples/server-library-example/client/pom.xml +++ b/connect-examples/server-library-example/client/pom.xml @@ -22,7 +22,7 @@ 4.0.0 - org.example + org.apache.connect.examples.serverlibrary spark-server-library-example 1.0-SNAPSHOT ../pom.xml @@ -34,7 +34,7 @@ - org.example + org.apache.connect.examples.serverlibrary spark-server-library-example-common 1.0-SNAPSHOT diff --git a/connect-examples/server-library-example/client/src/main/scala/org/example/CustomTable.scala b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTable.scala similarity index 95% rename from connect-examples/server-library-example/client/src/main/scala/org/example/CustomTable.scala rename to connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTable.scala index 101f7873268fd..782a246d92984 100644 --- a/connect-examples/server-library-example/client/src/main/scala/org/example/CustomTable.scala +++ b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTable.scala @@ -15,14 +15,15 @@ * limitations under the License. */ -package org.example +package org.apache.connect.examples.serverlibrary import com.google.protobuf.Any import org.apache.spark.connect.proto.Command -import org.example.proto -import org.example.proto.CreateTable.Column.{DataType => ProtoDataType} import org.apache.spark.sql.{functions, Column, DataFrame, Dataset, Row, SparkSession} +import org.apache.connect.examples.serverlibrary.proto +import org.apache.connect.examples.serverlibrary.proto.CreateTable.Column.{DataType => ProtoDataType} + /** * Represents a custom table with associated DataFrame and metadata. * diff --git a/connect-examples/server-library-example/client/src/main/scala/org/example/CustomTableBuilder.scala b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableBuilder.scala similarity index 95% rename from connect-examples/server-library-example/client/src/main/scala/org/example/CustomTableBuilder.scala rename to connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableBuilder.scala index 88d81c44a548f..a1b8ffdb8dd72 100644 --- a/connect-examples/server-library-example/client/src/main/scala/org/example/CustomTableBuilder.scala +++ b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableBuilder.scala @@ -15,19 +15,20 @@ * limitations under the License. */ -package org.example +package org.apache.connect.examples.serverlibrary -import org.apache.spark.sql.SparkSession -import org.example.CustomTable import com.google.protobuf.Any import org.apache.spark.connect.proto.Command +import org.apache.spark.sql.SparkSession + +import org.apache.connect.examples.serverlibrary.CustomTable /** * Builder class for constructing a `CustomTable` instance. * * @param spark The Spark session. */ -class CustomTableBuilder private[example] (spark: SparkSession) { +class CustomTableBuilder private[serverlibrary] (spark: SparkSession) { import CustomTableBuilder._ private var name: String = _ diff --git a/connect-examples/server-library-example/client/src/main/scala/org/example/Main.scala b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/Main.scala similarity index 87% rename from connect-examples/server-library-example/client/src/main/scala/org/example/Main.scala rename to connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/Main.scala index be6144af95a5f..046e8821f3070 100644 --- a/connect-examples/server-library-example/client/src/main/scala/org/example/Main.scala +++ b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/Main.scala @@ -15,15 +15,17 @@ * limitations under the License. */ -package org.example +package org.apache.connect.examples.serverlibrary import java.nio.file.{Path, Paths} -import org.apache.spark.sql.SparkSession -import org.apache.spark.sql.types.{StructType, StructField, StringType, IntegerType} -import org.example.proto -import org.example.proto.CreateTable.Column.{DataType => ProtoDataType} + import com.google.protobuf.Any import org.apache.spark.connect.proto.Command +import org.apache.spark.sql.SparkSession +import org.apache.spark.sql.types.{StructType, StructField, StringType, IntegerType} + +import org.apache.connect.examples.serverlibrary.proto +import org.apache.connect.examples.serverlibrary.proto.CreateTable.Column.{DataType => ProtoDataType} object Main { def main(args: Array[String]): Unit = { @@ -34,7 +36,7 @@ object Main { val fileName = "dummy_data.custom" val workingDirectory = System.getProperty("user.dir") - val fullPath = Paths.get(workingDirectory, s"../resources/$fileName") + val fullPath = Paths.get(workingDirectory, s"resources/$fileName") val customTable = CustomTable .create(spark) .name(tableName) diff --git a/connect-examples/server-library-example/common/pom.xml b/connect-examples/server-library-example/common/pom.xml index c88e8f1646689..4e5f57f6ca08e 100644 --- a/connect-examples/server-library-example/common/pom.xml +++ b/connect-examples/server-library-example/common/pom.xml @@ -22,7 +22,7 @@ 4.0.0 - org.example + org.apache.connect.examples.serverlibrary spark-server-library-example 1.0-SNAPSHOT ../pom.xml diff --git a/connect-examples/server-library-example/common/src/main/protobuf/base.proto b/connect-examples/server-library-example/common/src/main/protobuf/base.proto index 3e7181d7195ea..9d902a587ed37 100644 --- a/connect-examples/server-library-example/common/src/main/protobuf/base.proto +++ b/connect-examples/server-library-example/common/src/main/protobuf/base.proto @@ -18,7 +18,7 @@ syntax = 'proto3'; option java_multiple_files = true; -option java_package = "org.example.proto"; +option java_package = "org.apache.connect.examples.serverlibrary.proto"; message CustomTable { // Path to the custom table. diff --git a/connect-examples/server-library-example/common/src/main/protobuf/commands.proto b/connect-examples/server-library-example/common/src/main/protobuf/commands.proto index d1ca8a1a183cb..13d9945cfe61d 100644 --- a/connect-examples/server-library-example/common/src/main/protobuf/commands.proto +++ b/connect-examples/server-library-example/common/src/main/protobuf/commands.proto @@ -18,7 +18,7 @@ syntax = 'proto3'; option java_multiple_files = true; -option java_package = "org.example.proto"; +option java_package = "org.apache.connect.examples.serverlibrary.proto"; import "base.proto"; diff --git a/connect-examples/server-library-example/common/src/main/protobuf/relations.proto b/connect-examples/server-library-example/common/src/main/protobuf/relations.proto index e742c3c4b02e7..1ebf0e640bef1 100644 --- a/connect-examples/server-library-example/common/src/main/protobuf/relations.proto +++ b/connect-examples/server-library-example/common/src/main/protobuf/relations.proto @@ -18,7 +18,7 @@ syntax = 'proto3'; option java_multiple_files = true; -option java_package = "org.example.proto"; +option java_package = "org.apache.connect.examples.serverlibrary.proto"; import "base.proto"; diff --git a/connect-examples/server-library-example/pom.xml b/connect-examples/server-library-example/pom.xml index d9cf169d8bc3a..df3d732a8d781 100644 --- a/connect-examples/server-library-example/pom.xml +++ b/connect-examples/server-library-example/pom.xml @@ -21,7 +21,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.example + org.apache.connect.examples.serverlibrary spark-server-library-example 1.0-SNAPSHOT pom diff --git a/connect-examples/server-library-example/server/pom.xml b/connect-examples/server-library-example/server/pom.xml index 0677f9eed3308..16a277deb0c41 100644 --- a/connect-examples/server-library-example/server/pom.xml +++ b/connect-examples/server-library-example/server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 - org.example + org.apache.connect.examples.serverlibrary spark-server-library-example 1.0-SNAPSHOT ../pom.xml @@ -34,7 +34,7 @@ - org.example + org.apache.connect.examples.serverlibrary spark-server-library-example-common 1.0-SNAPSHOT diff --git a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomCommandPlugin.scala b/connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomCommandPlugin.scala similarity index 92% rename from connect-examples/server-library-example/server/src/main/scala/org/example/CustomCommandPlugin.scala rename to connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomCommandPlugin.scala index 5a8c41e693abc..2253c4b238be4 100644 --- a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomCommandPlugin.scala +++ b/connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomCommandPlugin.scala @@ -15,21 +15,22 @@ * limitations under the License. */ -package org.example +package org.apache.connect.examples.serverlibrary + +import scala.collection.JavaConverters._ import com.google.protobuf.Any import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan import org.apache.spark.sql.connect.planner.SparkConnectPlanner import org.apache.spark.sql.connect.plugin.CommandPlugin import org.apache.spark.sql.types.{StringType, IntegerType, FloatType, DoubleType, BooleanType, LongType, StructType, StructField, DataType} -import org.example.CustomTable -import org.example.proto -import org.example.proto.CreateTable.Column.{DataType => ProtoDataType} -import scala.collection.JavaConverters._ +import org.apache.connect.examples.serverlibrary.CustomTable +import org.apache.connect.examples.serverlibrary.proto +import org.apache.connect.examples.serverlibrary.proto.CreateTable.Column.{DataType => ProtoDataType} /** - * Commands are distinct actions that can be executed. Unlike relations, which focus on the + * Commands are distinct actions that can be executed. Unlike relations, which focus on the * transformation and nesting of output data, commands represent singular operations that perform * specific tasks on the data. * In this example, the `CustomCommandPlugin` handles operations related to creating and duplicating diff --git a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomPluginBase.scala b/connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomPluginBase.scala similarity index 90% rename from connect-examples/server-library-example/server/src/main/scala/org/example/CustomPluginBase.scala rename to connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomPluginBase.scala index b0732d51be451..df73e0d9a0fb8 100644 --- a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomPluginBase.scala +++ b/connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomPluginBase.scala @@ -15,10 +15,10 @@ * limitations under the License. */ -package org.example +package org.apache.connect.examples.serverlibrary +import org.apache.connect.examples.serverlibrary.proto import org.apache.spark.sql.connect.planner.SparkConnectPlanner -import org.example.proto trait CustomPluginBase { protected def getCustomTable(table: proto.CustomTable): CustomTable = { diff --git a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomRelationPlugin.scala b/connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomRelationPlugin.scala similarity index 94% rename from connect-examples/server-library-example/server/src/main/scala/org/example/CustomRelationPlugin.scala rename to connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomRelationPlugin.scala index 77abbb67b9591..7b444803a065f 100644 --- a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomRelationPlugin.scala +++ b/connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomRelationPlugin.scala @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.example +package org.apache.connect.examples.serverlibrary import java.util.Optional @@ -23,8 +23,9 @@ import com.google.protobuf.Any import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan import org.apache.spark.sql.connect.planner.SparkConnectPlanner import org.apache.spark.sql.connect.plugin.RelationPlugin -import org.example.{CustomPluginBase, CustomTable} -import org.example.proto + +import org.apache.connect.examples.serverlibrary.{CustomPluginBase, CustomTable} +import org.apache.connect.examples.serverlibrary.proto /** * Relations are fundamental to dataset transformations, acting as the mechanism through which an diff --git a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomTable.scala b/connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTable.scala similarity index 96% rename from connect-examples/server-library-example/server/src/main/scala/org/example/CustomTable.scala rename to connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTable.scala index 92bcef2b43bad..e1630cc25edd4 100644 --- a/connect-examples/server-library-example/server/src/main/scala/org/example/CustomTable.scala +++ b/connect-examples/server-library-example/server/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTable.scala @@ -15,12 +15,13 @@ * limitations under the License. */ -package org.example +package org.apache.connect.examples.serverlibrary import java.util.UUID + import com.github.mrpowers.spark.daria.sql.DariaWriters -import org.apache.spark.sql.{Dataset, Row, SparkSession} import org.apache.spark.sql.types.StructType +import org.apache.spark.sql.{Dataset, Row, SparkSession} /** * Represents a custom table with an identifier and a DataFrame. @@ -74,7 +75,7 @@ object CustomTable { * @param schema The schema of the table. * @return The created CustomTable instance. */ - private[example] def createTable( + private[serverlibrary] def createTable( name: String, path: String, spark: SparkSession, @@ -98,7 +99,7 @@ object CustomTable { * @param replace Whether to replace the existing table if it exists. * @return The cloned CustomTable instance. */ - private[example] def cloneTable( + private[serverlibrary] def cloneTable( sourceTable: CustomTable, newName: String, newPath: String, From f59aee1f8fcc20155e1fe6b377de2ddff7c9ad42 Mon Sep 17 00:00:00 2001 From: vicennial Date: Wed, 12 Feb 2025 22:42:53 +0100 Subject: [PATCH 6/7] more renames --- connect-examples/server-library-example/README.md | 8 ++++---- connect-examples/server-library-example/client/pom.xml | 4 ++-- .../{Main.scala => CustomTableExample.scala} | 2 +- connect-examples/server-library-example/common/pom.xml | 2 +- connect-examples/server-library-example/pom.xml | 2 +- connect-examples/server-library-example/server/pom.xml | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) rename connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/{Main.scala => CustomTableExample.scala} (98%) diff --git a/connect-examples/server-library-example/README.md b/connect-examples/server-library-example/README.md index 62a1a1d8cf820..a054e1d926548 100644 --- a/connect-examples/server-library-example/README.md +++ b/connect-examples/server-library-example/README.md @@ -86,20 +86,20 @@ reading, writing and processing data in the custom format. The plugins (`CustomC ```bash cp \ /connect-examples/server-library-example/resources/spark-daria_2.13-1.2.3.jar \ - /connect-examples/server-library-example/common/target/spark-server-library-example-common-1.0-SNAPSHOT.jar \ - /connect-examples/server-library-example/server/target/spark-server-library-example-server-extension-1.0-SNAPSHOT.jar \ + /connect-examples/server-library-example/common/target/spark-server-library-example-common-1.0.0.jar \ + /connect-examples/server-library-example/server/target/spark-server-library-example-server-extension-1.0.0.jar \ . ``` 5. **Start the Spark Connect Server with the relevant JARs**: ```bash bin/spark-connect-shell \ - --jars spark-server-library-example-server-extension-1.0-SNAPSHOT.jar,spark-server-library-example-common-1.0-SNAPSHOT.jar,spark-daria_2.13-1.2.3.jar \ + --jars spark-server-library-example-server-extension-1.0.0.jar,spark-server-library-example-common-1.0.0.jar,spark-daria_2.13-1.2.3.jar \ --conf spark.connect.extensions.relation.classes=org.apache.connect.examples.serverlibrary.CustomRelationPlugin \ --conf spark.connect.extensions.command.classes=org.apache.connect.examples.serverlibrary.CustomCommandPlugin ``` 6. **In a different terminal, navigate back to the root of the sample project and start the client**: ```bash - java -cp client/target/spark-server-library-client-package-scala-1.0-SNAPSHOT.jar org.apache.connect.examples.serverlibrary.Main + java -cp client/target/spark-server-library-client-package-scala-1.0.0.jar org.apache.connect.examples.serverlibrary.CustomTableExample ``` 7. **Notice the printed output in the client terminal as well as the creation of the cloned table**: ```protobuf diff --git a/connect-examples/server-library-example/client/pom.xml b/connect-examples/server-library-example/client/pom.xml index c00e957bd59f4..364920a2ec22b 100644 --- a/connect-examples/server-library-example/client/pom.xml +++ b/connect-examples/server-library-example/client/pom.xml @@ -24,7 +24,7 @@ org.apache.connect.examples.serverlibrary spark-server-library-example - 1.0-SNAPSHOT + 1.0.0 ../pom.xml @@ -36,7 +36,7 @@ org.apache.connect.examples.serverlibrary spark-server-library-example-common - 1.0-SNAPSHOT + 1.0.0 com.google.protobuf diff --git a/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/Main.scala b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableExample.scala similarity index 98% rename from connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/Main.scala rename to connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableExample.scala index 046e8821f3070..f8fd4aa8a06eb 100644 --- a/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/Main.scala +++ b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableExample.scala @@ -27,7 +27,7 @@ import org.apache.spark.sql.types.{StructType, StructField, StringType, IntegerT import org.apache.connect.examples.serverlibrary.proto import org.apache.connect.examples.serverlibrary.proto.CreateTable.Column.{DataType => ProtoDataType} -object Main { +object CustomTableExample { def main(args: Array[String]): Unit = { val spark = SparkSession.builder().remote("sc://localhost").build() diff --git a/connect-examples/server-library-example/common/pom.xml b/connect-examples/server-library-example/common/pom.xml index 4e5f57f6ca08e..592c43f26770b 100644 --- a/connect-examples/server-library-example/common/pom.xml +++ b/connect-examples/server-library-example/common/pom.xml @@ -24,7 +24,7 @@ org.apache.connect.examples.serverlibrary spark-server-library-example - 1.0-SNAPSHOT + 1.0.0 ../pom.xml diff --git a/connect-examples/server-library-example/pom.xml b/connect-examples/server-library-example/pom.xml index df3d732a8d781..1723f3b0154fa 100644 --- a/connect-examples/server-library-example/pom.xml +++ b/connect-examples/server-library-example/pom.xml @@ -23,7 +23,7 @@ org.apache.connect.examples.serverlibrary spark-server-library-example - 1.0-SNAPSHOT + 1.0.0 pom common diff --git a/connect-examples/server-library-example/server/pom.xml b/connect-examples/server-library-example/server/pom.xml index 16a277deb0c41..b95c5e3d61615 100644 --- a/connect-examples/server-library-example/server/pom.xml +++ b/connect-examples/server-library-example/server/pom.xml @@ -24,7 +24,7 @@ org.apache.connect.examples.serverlibrary spark-server-library-example - 1.0-SNAPSHOT + 1.0.0 ../pom.xml @@ -36,7 +36,7 @@ org.apache.connect.examples.serverlibrary spark-server-library-example-common - 1.0-SNAPSHOT + 1.0.0 From 18af68b08962c3942e6cf2019e96a33a1d531df3 Mon Sep 17 00:00:00 2001 From: vicennial Date: Wed, 12 Feb 2025 22:47:53 +0100 Subject: [PATCH 7/7] use .data instead of .custom --- connect-examples/server-library-example/README.md | 2 +- .../connect/examples/serverlibrary/CustomTableExample.scala | 4 ++-- .../resources/{dummy_data.custom => dummy_data.data} | 0 dev/.rat-excludes | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) rename connect-examples/server-library-example/resources/{dummy_data.custom => dummy_data.data} (100%) diff --git a/connect-examples/server-library-example/README.md b/connect-examples/server-library-example/README.md index a054e1d926548..6028a66cd5c7b 100644 --- a/connect-examples/server-library-example/README.md +++ b/connect-examples/server-library-example/README.md @@ -117,7 +117,7 @@ Relation [id#2,name#3] csv == Physical Plan == FileScan csv [id#2,name#3] Batched: false, DataFilters: [], Format: CSV, Location: InMemoryFileIndex(1 paths)[file:/Users/venkata.gudesa/spark/connect-examples/server-library-example/resou..., PartitionFilters: [], PushedFilters: [], ReadSchema: struct -Explaining plan for custom table: cloned_table with path: /connect-examples/server-library-example/client/../resources/cloned_data.custom +Explaining plan for custom table: cloned_table with path: /connect-examples/server-library-example/client/../resources/cloned_data.data == Parsed Logical Plan == Relation [id#2,name#3] csv diff --git a/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableExample.scala b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableExample.scala index f8fd4aa8a06eb..8470465cd7a0b 100644 --- a/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableExample.scala +++ b/connect-examples/server-library-example/client/src/main/scala/org/apache/connect/examples/serverlibrary/CustomTableExample.scala @@ -33,7 +33,7 @@ object CustomTableExample { // Step 1: Create a custom table from existing data val tableName = "sample_table" - val fileName = "dummy_data.custom" + val fileName = "dummy_data.data" val workingDirectory = System.getProperty("user.dir") val fullPath = Paths.get(workingDirectory, s"resources/$fileName") @@ -49,7 +49,7 @@ object CustomTableExample { customTable.explain() // Step 3: Clone the custom table - val clonedPath = fullPath.getParent.resolve("cloned_data.custom") + val clonedPath = fullPath.getParent.resolve("cloned_data.data") val clonedName = "cloned_table" val clonedTable = customTable.clone(target = clonedPath.toString, newName = clonedName, replace = true) diff --git a/connect-examples/server-library-example/resources/dummy_data.custom b/connect-examples/server-library-example/resources/dummy_data.data similarity index 100% rename from connect-examples/server-library-example/resources/dummy_data.custom rename to connect-examples/server-library-example/resources/dummy_data.data diff --git a/dev/.rat-excludes b/dev/.rat-excludes index 69fe405b1be27..d8c9196293950 100644 --- a/dev/.rat-excludes +++ b/dev/.rat-excludes @@ -139,4 +139,3 @@ core/src/main/resources/org/apache/spark/ui/static/package.json testCommitLog .*\.har .nojekyll -dummy_data.custom