Read CSV Files from Object Storage
This example reads a CSV file from S3-compatible object storage with the
filesystem connector and the csv format. The cp-flink-sql image ships
the csv format but not the filesystem connector, so you bundle both into one
artifact and load it child-first.
For the artifact mechanism, see Custom Connectors and Formats in Confluent Manager for Apache Flink.
Important
This recipe reads files. Writing to object storage through the filesystem sink is not supported on the shipped image, because its S3 plugin has no recoverable writer.
Prerequisites
Complete the examples setup first. It provides the
test environment, artifact management, and the MinIO object store with the
mc alias this example uses. This example also needs:
A CSV file in the object store at
s3://flink/demo-data/products.csv. Create it and upload it withmc:cat > products.csv <<'EOF' p-100,Wireless Mouse,24.99 p-101,Mechanical Keyboard,89.50 p-102,USB-C Hub,39.00 p-103,Laptop Stand,32.75 EOF mc cp products.csv minio/flink/demo-data/products.csv
The
flink-connector-filesandflink-csvJARs that match the Flink cluster version. The build below pullsflink-connector-files-1.19.1.jarandflink-csv-1.19.1.jarfrom Maven Central.
Bundle the connector and format into one JAR
The image ships flink-csv in its distribution but not
flink-connector-files. If you upload the two JARs as separate artifacts, the
job fails at runtime with NoClassDefFoundError:
org/apache/flink/connector/file/table/factories/BulkReaderFormatFactory: the
distribution’s flink-csv factory loads parent-first and cannot see the
filesystem connector’s classes. Merge both JARs into one artifact so the CSV and
file classes live together. Build the bundle with the Maven Shade plugin and its
ServicesResourceTransformer, which merges the two factory service files
instead of letting one overwrite the other. pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.confluent.examples.flink</groupId>
<artifactId>filesystem-csv-bundle</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<flink.version>1.19.1</flink.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-files</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-csv</artifactId>
<version>${flink.version}</version>
</dependency>
</dependencies>
<build>
<finalName>filesystem-csv-bundle</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<!-- Bundle only these two JARs; their transitive Flink
dependencies are already on the pool image. -->
<artifactSet>
<includes>
<include>org.apache.flink:flink-connector-files</include>
<include>org.apache.flink:flink-csv</include>
</includes>
</artifactSet>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Build it:
mvn package
The build writes target/filesystem-csv-bundle.jar with a single merged
META-INF/services/org.apache.flink.table.factories.Factory listing the
filesystem connector and both CSV factories.
Upload the bundle as a connector and as a format
CMF resolves the connector and the format independently, each in its own
classloader, so upload the same bundle twice: once labeled connector with the
filesystem identifier, and once labeled format with the csv
identifier.
fs-connector.json:
{
"apiVersion": "cmf.confluent.io/v1",
"kind": "Artifact",
"metadata": {
"name": "filesystem-connector.jar",
"labels": {
"cmf.confluent.io/sql-artifact-type": "connector",
"cmf.confluent.io/sql-artifact-identifier": "filesystem"
}
},
"spec": {}
}
csv-format.json:
{
"apiVersion": "cmf.confluent.io/v1",
"kind": "Artifact",
"metadata": {
"name": "csv-format.jar",
"labels": {
"cmf.confluent.io/sql-artifact-type": "format",
"cmf.confluent.io/sql-artifact-identifier": "csv"
}
},
"spec": {}
}
Upload the bundle under each:
curl -X POST http://cmf:8080/cmf/api/v1/environments/test/artifacts \
-F 'artifact=@fs-connector.json;type=application/json' \
-F 'file=@filesystem-csv-bundle.jar'
curl -X POST http://cmf:8080/cmf/api/v1/environments/test/artifacts \
-F 'artifact=@csv-format.json;type=application/json' \
-F 'file=@filesystem-csv-bundle.jar'
Create a compute pool that loads the bundle child-first
By default Flink loads org.apache.flink. classes parent-first, so the
distribution’s flink-csv factory shadows the bundle and dangles on the missing
filesystem classes. Create a dedicated compute pool whose
classloader.parent-first-patterns.default is the stock list with
org.apache.flink. removed, so the bundle’s CSV and file classes resolve
together, child-first. The pool also carries the S3 credentials the job uses to
read the CSV. Save this as filesystem-pool.json:
{
"apiVersion": "cmf.confluent.io/v1",
"kind": "ComputePool",
"metadata": { "name": "filesystem-pool" },
"spec": {
"type": "DEDICATED",
"clusterSpec": {
"flinkVersion": "v1_19",
"image": "confluentinc/cp-flink-sql:1.19-cp11",
"flinkConfiguration": {
"taskmanager.numberOfTaskSlots": "4",
"classloader.parent-first-patterns.default": "java.;scala.;com.esotericsoftware.kryo;org.apache.hadoop.;javax.annotation.;org.xml;javax.xml;org.apache.xerces;org.w3c;org.rocksdb.;org.slf4j;org.apache.log4j;org.apache.logging;org.apache.commons.logging;ch.qos.logback",
"s3.endpoint": "http://minio.flink.svc.cluster.local:9000",
"s3.path.style.access": "true",
"s3.connection.ssl.enabled": "false",
"s3.access-key": "console",
"s3.secret-key": "console123"
},
"taskManager": { "resource": { "cpu": 0.5, "memory": "1024m" } },
"jobManager": { "resource": { "cpu": "500m", "memory": "1536m" } }
}
}
}
confluent flink compute-pool create filesystem-pool.json \
--environment test \
--url http://cmf:8080
Removing org.apache.flink. from parent-first is a broad change, so keep it on
this dedicated pool rather than the shared pool.
Create the table
Create a table on the CSV file, setting connector to filesystem and
format to csv:
confluent flink statement create fs-create \
--url http://cmf:8080 \
--environment test \
--compute-pool filesystem-pool \
--catalog _env_test \
--database default \
--sql "CREATE TABLE \`products\` (\`id\` STRING, \`name\` STRING, \`price\` DOUBLE) WITH ('connector' = 'filesystem', 'path' = 's3://flink/demo-data/products.csv', 'format' = 'csv');"
Read the file
Run the query on the child-first pool:
confluent flink statement create fs-select \
--url http://cmf:8080 \
--environment test \
--compute-pool filesystem-pool \
--catalog _env_test \
--database default \
--sql "SELECT * FROM \`products\`;"
The query returns the four rows from the CSV file:
id name price
p-100 Wireless Mouse 24.99
p-101 Mechanical Keyboard 89.5
p-102 USB-C Hub 39.0
p-103 Laptop Stand 32.75
Clean up
Drop the table, then remove the pool and both artifacts:
confluent flink statement create fs-drop \
--url http://cmf:8080 --environment test --compute-pool filesystem-pool \
--catalog _env_test --database default \
--sql "DROP TABLE IF EXISTS \`products\`;"
curl -X DELETE http://cmf:8080/cmf/api/v1/environments/test/compute-pools/filesystem-pool
curl -X DELETE http://cmf:8080/cmf/api/v1/environments/test/artifacts/filesystem-connector.jar
curl -X DELETE http://cmf:8080/cmf/api/v1/environments/test/artifacts/csv-format.jar
Remove the CSV file from object storage:
mc rm minio/flink/demo-data/products.csv