You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fluss.apache.org by yu...@apache.org on 2025/07/31 08:32:41 UTC
(fluss) branch main updated: [lance] introduce lance connector and implement lake catalog (#1242)
This is an automated email from the ASF dual-hosted git repository.
yuxia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git
The following commit(s) were added to refs/heads/main by this push:
new 6fb4aec49 [lance] introduce lance connector and implement lake catalog (#1242)
6fb4aec49 is described below
commit 6fb4aec49c70e57f6dcd919e37945b988489d595
Author: xx789 <xx...@163.com>
AuthorDate: Thu Jul 31 16:32:38 2025 +0800
[lance] introduce lance connector and implement lake catalog (#1242)
---
.github/workflows/stage.sh | 3 +-
.../alibaba/fluss/bucketing/BucketingFunction.java | 2 +
.../com/alibaba/fluss/metadata/DataLakeFormat.java | 1 +
.../com/alibaba/fluss/row/encode/KeyEncoder.java | 3 +
fluss-dist/pom.xml | 7 +
fluss-dist/src/main/assemblies/plugins.xml | 8 +-
fluss-lake/fluss-lake-lance/pom.xml | 225 +++++++++++++++++++++
.../com/alibaba/fluss/lake/lance/LanceConfig.java | 168 +++++++++++++++
.../alibaba/fluss/lake/lance/LanceLakeCatalog.java | 84 ++++++++
.../alibaba/fluss/lake/lance/LanceLakeStorage.java | 43 ++++
.../fluss/lake/lance/LanceLakeStoragePlugin.java | 23 ++-
.../fluss/lake/lance/tiering/LanceCommittable.java | 22 +-
.../fluss/lake/lance/tiering/LanceWriteResult.java | 25 ++-
.../fluss/lake/lance/utils/LanceArrowUtils.java | 190 +++++++++++++++++
.../lake/lance/utils/LanceDatasetAdapter.java | 49 +++++
.../src/main/resources/META-INF/NOTICE | 37 ++++
.../licenses/LICENSE-EDL-1.0.eclipse-collections | 13 ++
.../LICENSE-EDL-1.0.eclipse-collections-api | 13 ++
.../licenses/LICENSE-EPL-1.0.eclipse-collections | 70 +++++++
.../LICENSE-EPL-1.0.eclipse-collections-api | 70 +++++++
.../resources/META-INF/licenses/LICENSE.slf4j-api | 21 ++
.../resources/META-INF/licenses/LICENSE.zstd-jni | 26 +++
...libaba.fluss.lake.lakestorage.LakeStoragePlugin | 17 ++
.../lake/lance/LakeEnabledTableCreateITCase.java | 204 +++++++++++++++++++
.../src/test/resources/log4j2-test.properties | 27 +++
.../org.junit.jupiter.api.extension.Extension | 19 ++
fluss-lake/pom.xml | 1 +
fluss-test-coverage/pom.xml | 2 +
28 files changed, 1338 insertions(+), 35 deletions(-)
diff --git a/.github/workflows/stage.sh b/.github/workflows/stage.sh
index 8d10d9b15..e96d34ddf 100755
--- a/.github/workflows/stage.sh
+++ b/.github/workflows/stage.sh
@@ -28,7 +28,8 @@ fluss-flink/fluss-flink-1.19,\
fluss-flink/fluss-flink-1.18,\
fluss-lake,\
fluss-lake/fluss-lake-paimon,\
-fluss-lake/fluss-lake-iceberg
+fluss-lake/fluss-lake-iceberg,\
+fluss-lake/fluss-lake-lance
"
function get_test_modules_for_stage() {
diff --git a/fluss-common/src/main/java/com/alibaba/fluss/bucketing/BucketingFunction.java b/fluss-common/src/main/java/com/alibaba/fluss/bucketing/BucketingFunction.java
index b08ed770d..8b06ae3c1 100644
--- a/fluss-common/src/main/java/com/alibaba/fluss/bucketing/BucketingFunction.java
+++ b/fluss-common/src/main/java/com/alibaba/fluss/bucketing/BucketingFunction.java
@@ -39,6 +39,8 @@ public interface BucketingFunction {
return new FlussBucketingFunction();
} else if (lakeFormat == DataLakeFormat.PAIMON) {
return new PaimonBucketingFunction();
+ } else if (lakeFormat == DataLakeFormat.LANCE) {
+ return new FlussBucketingFunction();
} else {
throw new UnsupportedOperationException("Unsupported lake format: " + lakeFormat);
}
diff --git a/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java b/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java
index b014ee814..af4af09f4 100644
--- a/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java
+++ b/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java
@@ -20,6 +20,7 @@ package com.alibaba.fluss.metadata;
/** An enum for datalake format. */
public enum DataLakeFormat {
PAIMON("paimon"),
+ LANCE("lance"),
ICEBERG("iceberg");
private final String value;
diff --git a/fluss-common/src/main/java/com/alibaba/fluss/row/encode/KeyEncoder.java b/fluss-common/src/main/java/com/alibaba/fluss/row/encode/KeyEncoder.java
index da3306455..b9574aa8c 100644
--- a/fluss-common/src/main/java/com/alibaba/fluss/row/encode/KeyEncoder.java
+++ b/fluss-common/src/main/java/com/alibaba/fluss/row/encode/KeyEncoder.java
@@ -47,6 +47,9 @@ public interface KeyEncoder {
return CompactedKeyEncoder.createKeyEncoder(rowType, keyFields);
} else if (lakeFormat == DataLakeFormat.PAIMON) {
return new PaimonKeyEncoder(rowType, keyFields);
+ } else if (lakeFormat == DataLakeFormat.LANCE) {
+ // use default compacted key encoder
+ return CompactedKeyEncoder.createKeyEncoder(rowType, keyFields);
} else if (lakeFormat == DataLakeFormat.ICEBERG) {
return new IcebergKeyEncoder(rowType, keyFields);
} else {
diff --git a/fluss-dist/pom.xml b/fluss-dist/pom.xml
index 43dd80f00..21dfa7c89 100644
--- a/fluss-dist/pom.xml
+++ b/fluss-dist/pom.xml
@@ -104,6 +104,13 @@
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-lake-lance</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-shaded-hadoop-2-uber</artifactId>
diff --git a/fluss-dist/src/main/assemblies/plugins.xml b/fluss-dist/src/main/assemblies/plugins.xml
index 3bffaf44c..9b5ffc3bf 100644
--- a/fluss-dist/src/main/assemblies/plugins.xml
+++ b/fluss-dist/src/main/assemblies/plugins.xml
@@ -101,13 +101,17 @@
<destName>fluss-lake-paimon-${project.version}.jar</destName>
<fileMode>0644</fileMode>
</file>
-
<file>
<source>../fluss-lake/fluss-lake-iceberg/target/fluss-lake-iceberg-${project.version}.jar</source>
<outputDirectory>plugins/iceberg/</outputDirectory>
<destName>fluss-lake-iceberg-${project.version}.jar</destName>
<fileMode>0644</fileMode>
</file>
- </files>
+ <file>
+ <source>../fluss-lake/fluss-lake-lance/target/fluss-lake-lance-${project.version}.jar</source>
+ <outputDirectory>plugins/lance/</outputDirectory>
+ <destName>fluss-lake-lance-${project.version}.jar</destName>
+ </file>
+ </files>
</assembly>
\ No newline at end of file
diff --git a/fluss-lake/fluss-lake-lance/pom.xml b/fluss-lake/fluss-lake-lance/pom.xml
new file mode 100644
index 000000000..65715230d
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/pom.xml
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-lake</artifactId>
+ <version>0.8-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>fluss-lake-lance</artifactId>
+ <name>Fluss : Lake : Lance</name>
+
+ <packaging>jar</packaging>
+
+ <properties>
+ <lance.version>0.26.1</lance.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-common</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.lancedb</groupId>
+ <artifactId>lance-core</artifactId>
+ <version>${lance.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-client</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-test-utils</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.curator</groupId>
+ <artifactId>curator-test</artifactId>
+ <version>${curator.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-server</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-hdfs-client</artifactId>
+ <version>${fluss.hadoop.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-common</artifactId>
+ <scope>provided</scope>
+ <exclusions>
+ <exclusion>
+ <artifactId>avro</artifactId>
+ <groupId>org.apache.avro</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>log4j</artifactId>
+ <groupId>log4j</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>slf4j-log4j12</artifactId>
+ <groupId>org.slf4j</groupId>
+ </exclusion>
+ <exclusion>
+ <groupId>ch.qos.reload4j</groupId>
+ <artifactId>reload4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-reload4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <artifactId>jdk.tools</artifactId>
+ <groupId>jdk.tools</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>protobuf-java</artifactId>
+ <groupId>com.google.protobuf</groupId>
+ </exclusion>
+ <exclusion>
+ <artifactId>commons-io</artifactId>
+ <groupId>commons-io</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-server</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Flink test dependency -->
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-flink-${flink.major.version}</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-common</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba.fluss</groupId>
+ <artifactId>fluss-flink-common</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.flink</groupId>
+ <artifactId>flink-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.flink</groupId>
+ <artifactId>flink-table-common</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.flink</groupId>
+ <artifactId>flink-table-runtime</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.flink</groupId>
+ <artifactId>flink-connector-base</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.flink</groupId>
+ <artifactId>flink-connector-files</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.flink</groupId>
+ <artifactId>flink-table-test-utils</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>shade-fluss</id>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <artifactSet>
+ <includes>
+ <include>*:*</include>
+ </includes>
+ </artifactSet>
+ <filters>
+ <filter>
+ <artifact>*</artifact>
+ <excludes>
+ <exclude>LICENSE*</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceConfig.java b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceConfig.java
new file mode 100644
index 000000000..5d7218d89
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceConfig.java
@@ -0,0 +1,168 @@
+/*
+ * 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 com.alibaba.fluss.lake.lance;
+
+import com.lancedb.lance.ReadOptions;
+import com.lancedb.lance.WriteParams;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/** Lance Configuration. */
+public class LanceConfig implements Serializable {
+ private static final long serialVersionUID = 1L;
+ public static final String LANCE_FILE_SUFFIX = ".lance";
+
+ private static final String block_size = "block_size";
+ private static final String version = "version";
+ private static final String index_cache_size = "index_cache_size";
+ private static final String metadata_cache_size = "metadata_cache_size";
+ private static final String max_row_per_file = "max_row_per_file";
+ private static final String max_rows_per_group = "max_rows_per_group";
+ private static final String max_bytes_per_file = "max_bytes_per_file";
+ private static final String ak = "access_key_id";
+ private static final String sk = "secret_access_key";
+ private static final String endpoint = "aws_endpoint";
+ private static final String region = "aws_region";
+ private static final String virtual_hosted_style = "virtual_hosted_style_request";
+ private static final String allow_http = "allow_http";
+ private static final String batch_size = "batch_size";
+ private static final String warehouse = "warehouse";
+
+ private final Map<String, String> options;
+ private final String databaseName;
+ private final String tableName;
+ private final String datasetUri;
+
+ public LanceConfig(
+ String databaseName,
+ String tableName,
+ String warehouse,
+ Map<String, String> properties) {
+ this.databaseName = databaseName;
+ this.tableName = tableName;
+ this.options = properties;
+
+ this.datasetUri = warehouse + "/" + databaseName + "/" + tableName + LANCE_FILE_SUFFIX;
+ }
+
+ public static LanceConfig from(
+ Map<String, String> properties, String databaseName, String tableName) {
+ if (!properties.containsKey(warehouse)) {
+ throw new IllegalArgumentException("Missing required option " + warehouse);
+ }
+ return new LanceConfig(databaseName, tableName, properties.get(warehouse), properties);
+ }
+
+ public static int getBatchSize(LanceConfig config) {
+ Map<String, String> options = config.getOptions();
+ if (options.containsKey(batch_size)) {
+ return Integer.parseInt(options.get(batch_size));
+ }
+ return 512;
+ }
+
+ public Map<String, String> getOptions() {
+ return options;
+ }
+
+ public String getDatabaseName() {
+ return databaseName;
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public String getDatasetUri() {
+ return datasetUri;
+ }
+
+ public static ReadOptions genReadOptionFromConfig(LanceConfig config) {
+ ReadOptions.Builder builder = new ReadOptions.Builder();
+ Map<String, String> maps = config.getOptions();
+ if (maps.containsKey(block_size)) {
+ builder.setBlockSize(Integer.parseInt(maps.get(block_size)));
+ }
+ if (maps.containsKey(version)) {
+ builder.setVersion(Integer.parseInt(maps.get(version)));
+ }
+ if (maps.containsKey(index_cache_size)) {
+ builder.setIndexCacheSize(Integer.parseInt(maps.get(index_cache_size)));
+ }
+ if (maps.containsKey(metadata_cache_size)) {
+ builder.setMetadataCacheSize(Integer.parseInt(maps.get(metadata_cache_size)));
+ }
+ builder.setStorageOptions(genStorageOptions(config));
+ return builder.build();
+ }
+
+ public static WriteParams genWriteParamsFromConfig(LanceConfig config) {
+ WriteParams.Builder builder = new WriteParams.Builder();
+ Map<String, String> maps = config.getOptions();
+ if (maps.containsKey(max_row_per_file)) {
+ builder.withMaxRowsPerFile(Integer.parseInt(maps.get(max_row_per_file)));
+ }
+ if (maps.containsKey(max_rows_per_group)) {
+ builder.withMaxRowsPerGroup(Integer.parseInt(maps.get(max_rows_per_group)));
+ }
+ if (maps.containsKey(max_bytes_per_file)) {
+ builder.withMaxBytesPerFile(Long.parseLong(maps.get(max_bytes_per_file)));
+ }
+ builder.withStorageOptions(genStorageOptions(config));
+ return builder.build();
+ }
+
+ private static Map<String, String> genStorageOptions(LanceConfig config) {
+ Map<String, String> storageOptions = new HashMap<>();
+ Map<String, String> maps = config.getOptions();
+ if (maps.containsKey(ak) && maps.containsKey(sk) && maps.containsKey(endpoint)) {
+ storageOptions.put(ak, maps.get(ak));
+ storageOptions.put(sk, maps.get(sk));
+ storageOptions.put(endpoint, maps.get(endpoint));
+ }
+ if (maps.containsKey(region)) {
+ storageOptions.put(region, maps.get(region));
+ }
+ if (maps.containsKey(virtual_hosted_style)) {
+ storageOptions.put(virtual_hosted_style, maps.get(virtual_hosted_style));
+ }
+ if (maps.containsKey(allow_http)) {
+ storageOptions.put(allow_http, maps.get(allow_http));
+ }
+ return storageOptions;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LanceConfig config = (LanceConfig) o;
+ return Objects.equals(databaseName, config.databaseName)
+ && Objects.equals(tableName, config.tableName)
+ && Objects.equals(options, config.options);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(databaseName, tableName, options);
+ }
+}
diff --git a/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceLakeCatalog.java b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceLakeCatalog.java
new file mode 100644
index 000000000..daaf26eb2
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceLakeCatalog.java
@@ -0,0 +1,84 @@
+/*
+ * 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 com.alibaba.fluss.lake.lance;
+
+import com.alibaba.fluss.config.Configuration;
+import com.alibaba.fluss.lake.lakestorage.LakeCatalog;
+import com.alibaba.fluss.lake.lance.utils.LanceArrowUtils;
+import com.alibaba.fluss.lake.lance.utils.LanceDatasetAdapter;
+import com.alibaba.fluss.metadata.TableDescriptor;
+import com.alibaba.fluss.metadata.TablePath;
+
+import com.lancedb.lance.WriteParams;
+import org.apache.arrow.vector.types.TimeUnit;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.Schema;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.alibaba.fluss.metadata.TableDescriptor.BUCKET_COLUMN_NAME;
+import static com.alibaba.fluss.metadata.TableDescriptor.OFFSET_COLUMN_NAME;
+import static com.alibaba.fluss.metadata.TableDescriptor.TIMESTAMP_COLUMN_NAME;
+
+/** A Lance implementation of {@link LakeCatalog}. */
+public class LanceLakeCatalog implements LakeCatalog {
+ private static final List<Field> SYSTEM_COLUMNS = new ArrayList<>();
+
+ static {
+ SYSTEM_COLUMNS.add(Field.nullable(BUCKET_COLUMN_NAME, new ArrowType.Int(32, true)));
+ SYSTEM_COLUMNS.add(Field.nullable(OFFSET_COLUMN_NAME, new ArrowType.Int(64, true)));
+ SYSTEM_COLUMNS.add(
+ Field.nullable(
+ TIMESTAMP_COLUMN_NAME,
+ new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)));
+ }
+
+ private final Configuration options;
+
+ public LanceLakeCatalog(Configuration config) {
+ this.options = config;
+ }
+
+ @Override
+ public void createTable(TablePath tablePath, TableDescriptor tableDescriptor) {
+ LanceConfig config =
+ LanceConfig.from(
+ options.toMap(), tablePath.getDatabaseName(), tablePath.getTableName());
+ WriteParams params = LanceConfig.genWriteParamsFromConfig(config);
+
+ List<Field> fields = new ArrayList<>();
+ // set schema
+ fields.addAll(
+ LanceArrowUtils.toArrowSchema(tableDescriptor.getSchema().getRowType())
+ .getFields());
+ // add system metadata columns to schema
+ fields.addAll(SYSTEM_COLUMNS);
+ try {
+ LanceDatasetAdapter.createDataset(config.getDatasetUri(), new Schema(fields), params);
+ } catch (RuntimeException e) {
+ throw new RuntimeException("Table " + tablePath + " creation failed", e);
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ LakeCatalog.super.close();
+ }
+}
diff --git a/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceLakeStorage.java b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceLakeStorage.java
new file mode 100644
index 000000000..11e6fef28
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceLakeStorage.java
@@ -0,0 +1,43 @@
+/*
+ * 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 com.alibaba.fluss.lake.lance;
+
+import com.alibaba.fluss.config.Configuration;
+import com.alibaba.fluss.lake.lakestorage.LakeStorage;
+import com.alibaba.fluss.lake.lance.tiering.LanceCommittable;
+import com.alibaba.fluss.lake.lance.tiering.LanceWriteResult;
+import com.alibaba.fluss.lake.writer.LakeTieringFactory;
+
+/** Lance implementation of {@link LakeStorage}. */
+public class LanceLakeStorage implements LakeStorage {
+ private final Configuration config;
+
+ public LanceLakeStorage(Configuration configuration) {
+ this.config = configuration;
+ }
+
+ @Override
+ public LakeTieringFactory<LanceWriteResult, LanceCommittable> createLakeTieringFactory() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public LanceLakeCatalog createLakeCatalog() {
+ return new LanceLakeCatalog(config);
+ }
+}
diff --git a/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceLakeStoragePlugin.java
similarity index 59%
copy from fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java
copy to fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceLakeStoragePlugin.java
index b014ee814..e5d65534d 100644
--- a/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java
+++ b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/LanceLakeStoragePlugin.java
@@ -15,21 +15,24 @@
* limitations under the License.
*/
-package com.alibaba.fluss.metadata;
+package com.alibaba.fluss.lake.lance;
-/** An enum for datalake format. */
-public enum DataLakeFormat {
- PAIMON("paimon"),
- ICEBERG("iceberg");
+import com.alibaba.fluss.config.Configuration;
+import com.alibaba.fluss.lake.lakestorage.LakeStorage;
+import com.alibaba.fluss.lake.lakestorage.LakeStoragePlugin;
- private final String value;
+/** Lance implementation of {@link LakeStoragePlugin}. */
+public class LanceLakeStoragePlugin implements LakeStoragePlugin {
- DataLakeFormat(String value) {
- this.value = value;
+ private static final String IDENTIFIER = "lance";
+
+ @Override
+ public String identifier() {
+ return IDENTIFIER;
}
@Override
- public String toString() {
- return value;
+ public LakeStorage createLakeStorage(Configuration configuration) {
+ return new LanceLakeStorage(configuration);
}
}
diff --git a/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/tiering/LanceCommittable.java
similarity index 63%
copy from fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java
copy to fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/tiering/LanceCommittable.java
index b014ee814..c54a444db 100644
--- a/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java
+++ b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/tiering/LanceCommittable.java
@@ -15,21 +15,21 @@
* limitations under the License.
*/
-package com.alibaba.fluss.metadata;
+package com.alibaba.fluss.lake.lance.tiering;
-/** An enum for datalake format. */
-public enum DataLakeFormat {
- PAIMON("paimon"),
- ICEBERG("iceberg");
+import com.lancedb.lance.FragmentMetadata;
- private final String value;
+import java.util.List;
- DataLakeFormat(String value) {
- this.value = value;
+/** The committable that derived from {@link LanceWriteResult} to commit to Lance. */
+public class LanceCommittable {
+ private final List<FragmentMetadata> committable;
+
+ public LanceCommittable(List<FragmentMetadata> committable) {
+ this.committable = committable;
}
- @Override
- public String toString() {
- return value;
+ public List<FragmentMetadata> committable() {
+ return committable;
}
}
diff --git a/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/tiering/LanceWriteResult.java
similarity index 58%
copy from fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java
copy to fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/tiering/LanceWriteResult.java
index b014ee814..b9528d153 100644
--- a/fluss-common/src/main/java/com/alibaba/fluss/metadata/DataLakeFormat.java
+++ b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/tiering/LanceWriteResult.java
@@ -15,21 +15,24 @@
* limitations under the License.
*/
-package com.alibaba.fluss.metadata;
+package com.alibaba.fluss.lake.lance.tiering;
-/** An enum for datalake format. */
-public enum DataLakeFormat {
- PAIMON("paimon"),
- ICEBERG("iceberg");
+import com.lancedb.lance.FragmentMetadata;
- private final String value;
+import java.io.Serializable;
+import java.util.List;
- DataLakeFormat(String value) {
- this.value = value;
+/** The write result of Lance lake writer to pass to commiter to commit. */
+public class LanceWriteResult implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final List<FragmentMetadata> commitMessage;
+
+ public LanceWriteResult(List<FragmentMetadata> commitMessage) {
+ this.commitMessage = commitMessage;
}
- @Override
- public String toString() {
- return value;
+ public List<FragmentMetadata> commitMessage() {
+ return commitMessage;
}
}
diff --git a/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/utils/LanceArrowUtils.java b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/utils/LanceArrowUtils.java
new file mode 100644
index 000000000..cf92952c9
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/utils/LanceArrowUtils.java
@@ -0,0 +1,190 @@
+/*
+ * 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 com.alibaba.fluss.lake.lance.utils;
+
+import com.alibaba.fluss.types.BigIntType;
+import com.alibaba.fluss.types.BinaryType;
+import com.alibaba.fluss.types.BooleanType;
+import com.alibaba.fluss.types.BytesType;
+import com.alibaba.fluss.types.CharType;
+import com.alibaba.fluss.types.DataType;
+import com.alibaba.fluss.types.DataTypeDefaultVisitor;
+import com.alibaba.fluss.types.DateType;
+import com.alibaba.fluss.types.DecimalType;
+import com.alibaba.fluss.types.DoubleType;
+import com.alibaba.fluss.types.FloatType;
+import com.alibaba.fluss.types.IntType;
+import com.alibaba.fluss.types.LocalZonedTimestampType;
+import com.alibaba.fluss.types.RowType;
+import com.alibaba.fluss.types.SmallIntType;
+import com.alibaba.fluss.types.StringType;
+import com.alibaba.fluss.types.TimeType;
+import com.alibaba.fluss.types.TimestampType;
+import com.alibaba.fluss.types.TinyIntType;
+
+import org.apache.arrow.vector.types.DateUnit;
+import org.apache.arrow.vector.types.FloatingPointPrecision;
+import org.apache.arrow.vector.types.TimeUnit;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.FieldType;
+import org.apache.arrow.vector.types.pojo.Schema;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Utilities for Arrow. */
+public class LanceArrowUtils {
+ /** Returns the Arrow schema of the specified type. */
+ public static Schema toArrowSchema(RowType rowType) {
+ List<Field> fields =
+ rowType.getFields().stream()
+ .map(f -> toArrowField(f.getName(), f.getType()))
+ .collect(Collectors.toList());
+ return new Schema(fields);
+ }
+
+ private static Field toArrowField(String fieldName, DataType logicalType) {
+ FieldType fieldType =
+ new FieldType(
+ logicalType.isNullable(),
+ logicalType.accept(DataTypeToArrowTypeConverter.INSTANCE),
+ null);
+ return new Field(fieldName, fieldType, null);
+ }
+
+ private static class DataTypeToArrowTypeConverter extends DataTypeDefaultVisitor<ArrowType> {
+
+ private static final DataTypeToArrowTypeConverter INSTANCE =
+ new DataTypeToArrowTypeConverter();
+
+ @Override
+ public ArrowType visit(TinyIntType tinyIntType) {
+ return new ArrowType.Int(8, true);
+ }
+
+ @Override
+ public ArrowType visit(SmallIntType smallIntType) {
+ return new ArrowType.Int(2 * 8, true);
+ }
+
+ @Override
+ public ArrowType visit(IntType intType) {
+ return new ArrowType.Int(4 * 8, true);
+ }
+
+ @Override
+ public ArrowType visit(BigIntType bigIntType) {
+ return new ArrowType.Int(8 * 8, true);
+ }
+
+ @Override
+ public ArrowType visit(BooleanType booleanType) {
+ return ArrowType.Bool.INSTANCE;
+ }
+
+ @Override
+ public ArrowType visit(FloatType floatType) {
+ return new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE);
+ }
+
+ @Override
+ public ArrowType visit(DoubleType doubleType) {
+ return new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE);
+ }
+
+ @Override
+ public ArrowType visit(CharType varCharType) {
+ return ArrowType.Utf8.INSTANCE;
+ }
+
+ @Override
+ public ArrowType visit(StringType stringType) {
+ return ArrowType.Utf8.INSTANCE;
+ }
+
+ @Override
+ public ArrowType visit(BinaryType binaryType) {
+ return new ArrowType.FixedSizeBinary(binaryType.getLength());
+ }
+
+ @Override
+ public ArrowType visit(BytesType bytesType) {
+ return ArrowType.Binary.INSTANCE;
+ }
+
+ @Override
+ public ArrowType visit(DecimalType decimalType) {
+ return ArrowType.Decimal.createDecimal(
+ decimalType.getPrecision(), decimalType.getScale(), null);
+ }
+
+ @Override
+ public ArrowType visit(DateType dateType) {
+ return new ArrowType.Date(DateUnit.DAY);
+ }
+
+ @Override
+ public ArrowType visit(TimeType timeType) {
+ if (timeType.getPrecision() == 0) {
+ return new ArrowType.Time(TimeUnit.SECOND, 32);
+ } else if (timeType.getPrecision() >= 1 && timeType.getPrecision() <= 3) {
+ return new ArrowType.Time(TimeUnit.MILLISECOND, 32);
+ } else if (timeType.getPrecision() >= 4 && timeType.getPrecision() <= 6) {
+ return new ArrowType.Time(TimeUnit.MICROSECOND, 64);
+ } else {
+ return new ArrowType.Time(TimeUnit.NANOSECOND, 64);
+ }
+ }
+
+ @Override
+ public ArrowType visit(LocalZonedTimestampType localZonedTimestampType) {
+ if (localZonedTimestampType.getPrecision() == 0) {
+ return new ArrowType.Timestamp(TimeUnit.SECOND, null);
+ } else if (localZonedTimestampType.getPrecision() >= 1
+ && localZonedTimestampType.getPrecision() <= 3) {
+ return new ArrowType.Timestamp(TimeUnit.MILLISECOND, null);
+ } else if (localZonedTimestampType.getPrecision() >= 4
+ && localZonedTimestampType.getPrecision() <= 6) {
+ return new ArrowType.Timestamp(TimeUnit.MICROSECOND, null);
+ } else {
+ return new ArrowType.Timestamp(TimeUnit.NANOSECOND, null);
+ }
+ }
+
+ @Override
+ public ArrowType visit(TimestampType timestampType) {
+ if (timestampType.getPrecision() == 0) {
+ return new ArrowType.Timestamp(TimeUnit.SECOND, null);
+ } else if (timestampType.getPrecision() >= 1 && timestampType.getPrecision() <= 3) {
+ return new ArrowType.Timestamp(TimeUnit.MILLISECOND, null);
+ } else if (timestampType.getPrecision() >= 4 && timestampType.getPrecision() <= 6) {
+ return new ArrowType.Timestamp(TimeUnit.MICROSECOND, null);
+ } else {
+ return new ArrowType.Timestamp(TimeUnit.NANOSECOND, null);
+ }
+ }
+
+ @Override
+ protected ArrowType defaultMethod(DataType dataType) {
+ throw new UnsupportedOperationException(
+ String.format(
+ "Unsupported data type %s currently.", dataType.asSummaryString()));
+ }
+ }
+}
diff --git a/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/utils/LanceDatasetAdapter.java b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/utils/LanceDatasetAdapter.java
new file mode 100644
index 000000000..ed50abcc9
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/java/com/alibaba/fluss/lake/lance/utils/LanceDatasetAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.alibaba.fluss.lake.lance.utils;
+
+import com.alibaba.fluss.lake.lance.LanceConfig;
+
+import com.lancedb.lance.Dataset;
+import com.lancedb.lance.ReadOptions;
+import com.lancedb.lance.WriteParams;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.types.pojo.Schema;
+
+import java.util.Optional;
+
+/** Lance dataset API adapter. */
+public class LanceDatasetAdapter {
+ private static final BufferAllocator allocator = new RootAllocator();
+
+ public static void createDataset(String datasetUri, Schema schema, WriteParams params) {
+ Dataset.create(allocator, datasetUri, schema, params).close();
+ }
+
+ public static Optional<Schema> getSchema(LanceConfig config) {
+ String uri = config.getDatasetUri();
+ ReadOptions options = LanceConfig.genReadOptionFromConfig(config);
+ try (Dataset dataset = Dataset.open(allocator, uri, options)) {
+ return Optional.of(dataset.getSchema());
+ } catch (IllegalArgumentException e) {
+ // dataset not found
+ return Optional.empty();
+ }
+ }
+}
diff --git a/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/NOTICE b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/NOTICE
new file mode 100644
index 000000000..786813d32
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,37 @@
+fluss-lake-lance
+Copyright 2025 The Apache Software Foundation
+
+This project includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This project bundles the following dependencies under the Apache Software License 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
+
+- com.fasterxml.jackson.core:jackson-annotations:2.15.3
+- com.fasterxml.jackson.core:jackson-core:2.15.3
+- com.fasterxml.jackson.core:jackson-databind:2.15.3
+- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.3
+- com.google.code.findbugs:jsr305:1.3.9
+- org.apache.commons:commons-lang3:3.18.0
+- io.netty:netty-buffer:4.1.104.Final
+- io.netty:netty-common:4.1.104.Final
+- org.apache.arrow:arrow-c-data:15.0.0
+- org.apache.arrow:arrow-dataset:15.0.0
+- org.apache.arrow:arrow-format:15.0.0
+- org.apache.arrow:arrow-memory-core:15.0.0
+- org.apache.arrow:arrow-memory-netty:15.0.0
+- org.apache.arrow:arrow-vector:15.0.0
+- org.questdb:jar-jni:1.1.1
+- com.lancedb:lance-core:0.26.1
+- commons-codec:commons-codec:1.4
+- com.google.flatbuffers:flatbuffers-java:23.5.26
+
+This project bundles the following dependencies under the MIT (https://opensource.org/licenses/MIT)
+See bundled license files for details.
+
+- org.slf4j:slf4j-api:1.7.36
+
+This project bundles the following dependencies under both EDL 1.0 License (https://www.eclipse.org/org/documents/edl-v10.php) and the Eclipse Public License - v 1.0 (https://www.eclipse.org/org/documents/epl-1.0/EPL-1.0.txt).
+See bundled license files for details.
+
+- org.eclipse.collections:eclipse-collections-api:11.1.0
+- org.eclipse.collections:eclipse-collections:11.1.0
\ No newline at end of file
diff --git a/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EDL-1.0.eclipse-collections b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EDL-1.0.eclipse-collections
new file mode 100644
index 000000000..332519708
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EDL-1.0.eclipse-collections
@@ -0,0 +1,13 @@
+Eclipse Distribution License - v 1.0
+
+Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFI [...]
\ No newline at end of file
diff --git a/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EDL-1.0.eclipse-collections-api b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EDL-1.0.eclipse-collections-api
new file mode 100644
index 000000000..332519708
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EDL-1.0.eclipse-collections-api
@@ -0,0 +1,13 @@
+Eclipse Distribution License - v 1.0
+
+Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFI [...]
\ No newline at end of file
diff --git a/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EPL-1.0.eclipse-collections b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EPL-1.0.eclipse-collections
new file mode 100644
index 000000000..3d967aee7
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EPL-1.0.eclipse-collections
@@ -0,0 +1,70 @@
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+i) changes to the Program, and
+ii) additions to the Program;
+where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
+b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contributio [...]
+c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted here [...]
+d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+b) its license agreement:
+i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
+ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
+iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
+iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+b) a copy of this Agreement must be included with each copy of the Program.
+Contributors may not remove or alter any copyright notices contained within the Program.
+
+Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Con [...]
+
+For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims [...]
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Ag [...]
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF [...]
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Reci [...]
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the respons [...]
+
+This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.
\ No newline at end of file
diff --git a/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EPL-1.0.eclipse-collections-api b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EPL-1.0.eclipse-collections-api
new file mode 100644
index 000000000..3d967aee7
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE-EPL-1.0.eclipse-collections-api
@@ -0,0 +1,70 @@
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+i) changes to the Program, and
+ii) additions to the Program;
+where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
+b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contributio [...]
+c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted here [...]
+d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+b) its license agreement:
+i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
+ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
+iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
+iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+b) a copy of this Agreement must be included with each copy of the Program.
+Contributors may not remove or alter any copyright notices contained within the Program.
+
+Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Con [...]
+
+For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims [...]
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Ag [...]
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF [...]
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Reci [...]
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the respons [...]
+
+This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.
\ No newline at end of file
diff --git a/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE.slf4j-api b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE.slf4j-api
new file mode 100644
index 000000000..93119e70e
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE.slf4j-api
@@ -0,0 +1,21 @@
+Copyright (c) 2004-2017 QOS.ch
+ All rights reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE.zstd-jni b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE.zstd-jni
new file mode 100644
index 000000000..66abb8ae7
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/licenses/LICENSE.zstd-jni
@@ -0,0 +1,26 @@
+Zstd-jni: JNI bindings to Zstd Library
+
+Copyright (c) 2015-present, Luben Karavelov/ All rights reserved.
+
+BSD License
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this
+ list of conditions and the following disclaimer in the documentation and/or
+ other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/services/com.alibaba.fluss.lake.lakestorage.LakeStoragePlugin b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/services/com.alibaba.fluss.lake.lakestorage.LakeStoragePlugin
new file mode 100644
index 000000000..693ee63cc
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/main/resources/META-INF/services/com.alibaba.fluss.lake.lakestorage.LakeStoragePlugin
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2025 Alibaba Group Holding Ltd.
+#
+# Licensed 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.
+#
+
+com.alibaba.fluss.lake.lance.LanceLakeStoragePlugin
\ No newline at end of file
diff --git a/fluss-lake/fluss-lake-lance/src/test/java/com/alibaba/fluss/lake/lance/LakeEnabledTableCreateITCase.java b/fluss-lake/fluss-lake-lance/src/test/java/com/alibaba/fluss/lake/lance/LakeEnabledTableCreateITCase.java
new file mode 100644
index 000000000..e0c16e30d
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/test/java/com/alibaba/fluss/lake/lance/LakeEnabledTableCreateITCase.java
@@ -0,0 +1,204 @@
+/*
+ * 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 com.alibaba.fluss.lake.lance;
+
+import com.alibaba.fluss.client.Connection;
+import com.alibaba.fluss.client.ConnectionFactory;
+import com.alibaba.fluss.client.admin.Admin;
+import com.alibaba.fluss.config.ConfigOptions;
+import com.alibaba.fluss.config.Configuration;
+import com.alibaba.fluss.exception.FlussRuntimeException;
+import com.alibaba.fluss.lake.lance.utils.LanceDatasetAdapter;
+import com.alibaba.fluss.metadata.DataLakeFormat;
+import com.alibaba.fluss.metadata.Schema;
+import com.alibaba.fluss.metadata.TableDescriptor;
+import com.alibaba.fluss.metadata.TablePath;
+import com.alibaba.fluss.server.testutils.FlussClusterExtension;
+import com.alibaba.fluss.types.DataTypes;
+
+import org.apache.arrow.vector.types.DateUnit;
+import org.apache.arrow.vector.types.FloatingPointPrecision;
+import org.apache.arrow.vector.types.TimeUnit;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.FieldType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import java.nio.file.Files;
+import java.util.Arrays;
+
+import static com.alibaba.fluss.metadata.TableDescriptor.BUCKET_COLUMN_NAME;
+import static com.alibaba.fluss.metadata.TableDescriptor.OFFSET_COLUMN_NAME;
+import static com.alibaba.fluss.metadata.TableDescriptor.TIMESTAMP_COLUMN_NAME;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** ITCase for create lake enabled table with lance as lake storage. */
+class LakeEnabledTableCreateITCase {
+
+ @RegisterExtension
+ public static final FlussClusterExtension FLUSS_CLUSTER_EXTENSION =
+ FlussClusterExtension.builder()
+ .setNumOfTabletServers(3)
+ .setClusterConf(initConfig())
+ .build();
+
+ private static Configuration lanceConf;
+
+ private static final String DATABASE = "fluss";
+
+ private static final int BUCKET_NUM = 3;
+
+ private Connection conn;
+ private Admin admin;
+
+ @BeforeEach
+ protected void setup() {
+ conn = ConnectionFactory.createConnection(FLUSS_CLUSTER_EXTENSION.getClientConfig());
+ admin = conn.getAdmin();
+ }
+
+ @AfterEach
+ protected void teardown() throws Exception {
+ if (admin != null) {
+ admin.close();
+ admin = null;
+ }
+
+ if (conn != null) {
+ conn.close();
+ conn = null;
+ }
+ }
+
+ private static Configuration initConfig() {
+ Configuration conf = new Configuration();
+ lanceConf = new Configuration();
+ conf.set(ConfigOptions.DATALAKE_FORMAT, DataLakeFormat.LANCE);
+ conf.setString("datalake.format", "lance");
+ String warehousePath;
+ try {
+ warehousePath =
+ Files.createTempDirectory("fluss-testing-datalake-enabled")
+ .resolve("warehouse")
+ .toString();
+ } catch (Exception e) {
+ throw new FlussRuntimeException("Failed to create warehouse path");
+ }
+ conf.setString("datalake.lance.warehouse", warehousePath);
+ lanceConf.setString("warehouse", warehousePath);
+ return conf;
+ }
+
+ @Test
+ void testLogTable() throws Exception {
+ // test bucket key log table
+ TableDescriptor logTable =
+ TableDescriptor.builder()
+ .schema(
+ Schema.newBuilder()
+ .column("log_c1", DataTypes.INT())
+ .column("log_c2", DataTypes.STRING())
+ .column("log_c3", DataTypes.TINYINT())
+ .column("log_c4", DataTypes.SMALLINT())
+ .column("log_c5", DataTypes.BIGINT())
+ .column("log_c6", DataTypes.BOOLEAN())
+ .column("log_c7", DataTypes.FLOAT())
+ .column("log_c8", DataTypes.DOUBLE())
+ .column("log_c9", DataTypes.CHAR(1))
+ .column("log_c10", DataTypes.BINARY(10))
+ .column("log_c11", DataTypes.BYTES())
+ .column("log_c12", DataTypes.DECIMAL(10, 2))
+ .column("log_c13", DataTypes.DATE())
+ .column("log_c14", DataTypes.TIME())
+ .column("log_c15", DataTypes.TIMESTAMP_LTZ())
+ .column("log_c16", DataTypes.TIMESTAMP())
+ .build())
+ .property(ConfigOptions.TABLE_DATALAKE_ENABLED, true)
+ .distributedBy(BUCKET_NUM, "log_c1", "log_c2")
+ .build();
+ TablePath logTablePath = TablePath.of(DATABASE, "log_table");
+ admin.createTable(logTablePath, logTable, false).get();
+ LanceConfig config = LanceConfig.from(lanceConf.toMap(), DATABASE, "log_table");
+
+ // check the gotten log table
+ Field logC1 = new Field("log_c1", FieldType.nullable(new ArrowType.Int(4 * 8, true)), null);
+ Field logC2 = new Field("log_c2", FieldType.nullable(new ArrowType.Utf8()), null);
+ Field logC3 = new Field("log_c3", FieldType.nullable(new ArrowType.Int(8, true)), null);
+ Field logC4 = new Field("log_c4", FieldType.nullable(new ArrowType.Int(2 * 8, true)), null);
+ Field logC5 = new Field("log_c5", FieldType.nullable(new ArrowType.Int(8 * 8, true)), null);
+ Field logC6 = new Field("log_c6", FieldType.nullable(new ArrowType.Bool()), null);
+ Field logC7 =
+ new Field(
+ "log_c7",
+ FieldType.nullable(
+ new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE)),
+ null);
+ Field logC8 =
+ new Field(
+ "log_c8",
+ FieldType.nullable(
+ new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE)),
+ null);
+ Field logC9 = new Field("log_c9", FieldType.nullable(new ArrowType.Utf8()), null);
+ Field logC10 =
+ new Field("log_c10", FieldType.nullable(new ArrowType.FixedSizeBinary(10)), null);
+ Field logC11 = new Field("log_c11", FieldType.nullable(new ArrowType.Binary()), null);
+ Field logC12 = new Field("log_c12", FieldType.nullable(new ArrowType.Decimal(10, 2)), null);
+ Field logC13 =
+ new Field("log_c13", FieldType.nullable(new ArrowType.Date(DateUnit.DAY)), null);
+ Field logC14 =
+ new Field(
+ "log_c14",
+ FieldType.nullable(new ArrowType.Time(TimeUnit.SECOND, 32)),
+ null);
+ Field logC15 =
+ new Field(
+ "log_c15",
+ FieldType.nullable(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)),
+ null);
+ Field logC16 =
+ new Field(
+ "log_c16",
+ FieldType.nullable(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)),
+ null);
+
+ // for __bucket, __offset, __timestamp
+ Field logC17 =
+ new Field(
+ BUCKET_COLUMN_NAME, FieldType.nullable(new ArrowType.Int(32, true)), null);
+ Field logC18 =
+ new Field(
+ OFFSET_COLUMN_NAME, FieldType.nullable(new ArrowType.Int(64, true)), null);
+ Field logC19 =
+ new Field(
+ TIMESTAMP_COLUMN_NAME,
+ FieldType.nullable(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)),
+ null);
+
+ org.apache.arrow.vector.types.pojo.Schema expectedSchema =
+ new org.apache.arrow.vector.types.pojo.Schema(
+ Arrays.asList(
+ logC1, logC2, logC3, logC4, logC5, logC6, logC7, logC8, logC9,
+ logC10, logC11, logC12, logC13, logC14, logC15, logC16, logC17,
+ logC18, logC19));
+ assertThat(expectedSchema).isEqualTo(LanceDatasetAdapter.getSchema(config).get());
+ }
+}
diff --git a/fluss-lake/fluss-lake-lance/src/test/resources/log4j2-test.properties b/fluss-lake/fluss-lake-lance/src/test/resources/log4j2-test.properties
new file mode 100644
index 000000000..fabc6bf5a
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/test/resources/log4j2-test.properties
@@ -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.
+#
+# Set root logger level to OFF to not flood build logs
+# set manually to INFO for debugging purposes
+rootLogger.level=OFF
+rootLogger.appenderRef.test.ref=TestLogger
+appender.testlogger.name=TestLogger
+appender.testlogger.type=CONSOLE
+appender.testlogger.target=SYSTEM_ERR
+appender.testlogger.layout.type=PatternLayout
+appender.testlogger.layout.pattern=%-4r [%t] %-5p %c %x - %m%n
+
diff --git a/fluss-lake/fluss-lake-lance/src/test/resources/org.junit.jupiter.api.extension.Extension b/fluss-lake/fluss-lake-lance/src/test/resources/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 000000000..590243ea8
--- /dev/null
+++ b/fluss-lake/fluss-lake-lance/src/test/resources/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+com.alibaba.fluss.testutils.common.TestLoggerExtension
\ No newline at end of file
diff --git a/fluss-lake/pom.xml b/fluss-lake/pom.xml
index dadae6d52..d84d10a0d 100644
--- a/fluss-lake/pom.xml
+++ b/fluss-lake/pom.xml
@@ -78,6 +78,7 @@
<modules>
<module>fluss-lake-paimon</module>
<module>fluss-lake-iceberg</module>
+ <module>fluss-lake-lance</module>
</modules>
<packaging>pom</packaging>
</project>
\ No newline at end of file
diff --git a/fluss-test-coverage/pom.xml b/fluss-test-coverage/pom.xml
index 97c35e6eb..48288186b 100644
--- a/fluss-test-coverage/pom.xml
+++ b/fluss-test-coverage/pom.xml
@@ -329,6 +329,8 @@
<exclude>com.alibaba.fluss.lake.batch.ArrowRecordBatch</exclude>
<exclude>com.alibaba.fluss.lake.committer.CommittedLakeSnapshot</exclude>
<exclude>com.alibaba.fluss.lake.paimon.FlussDataTypeToPaimonDataType</exclude>
+ <!-- start exclude for lake lance -->
+ <exclude>com.alibaba.fluss.lake.lance.*</exclude>
<!-- temporarily exclude iceberg -->
<exclude>com.alibaba.fluss.lake.iceberg.*</exclude>
<!-- start exclude for flink tiering service -->