Skip to content

Commit 3fd6143

Browse files
LuQQiuclaude
andauthored
feat: add vector column support for Lance via arrow FixedSizeList conversion (#75)
This PR adds support for writing Spark DataFrame `ArrayType` columns as Arrow `FixedSizeList` to enable vector embeddings storage and indexing in Lance format. Users can now mark array columns as vectors using metadata, enabling efficient similarity search and vector indexing for ML workloads. copied Spark Dataframe to Arrow Type converter and add custom Spark Array<Float/Double> to Arrow FixedSizeList conversion. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0198f9d commit 3fd6143

31 files changed

Lines changed: 1921 additions & 55 deletions

File tree

docker/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ WORKDIR ${SPARK_HOME}
3535

3636
ENV SPARK_VERSION=3.5.6
3737
ENV SPARK_MAJOR_VERSION=3.5
38-
ENV LANCE_SPARK_VERSION=0.0.5
39-
ENV LANCE_NS_VERSION=0.0.6
38+
ENV LANCE_SPARK_VERSION=0.0.6
39+
ENV LANCE_NS_VERSION=0.0.7
4040

4141
# Download spark
4242
RUN mkdir -p ${SPARK_HOME} \

docs/src/config.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ and namespace-specific options:
4343
=== "Spark Shell"
4444
```shell
4545
spark-shell \
46-
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.5 \
46+
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.6 \
4747
--conf spark.sql.catalog.lance=com.lancedb.lance.spark.LanceNamespaceSparkCatalog \
4848
--conf spark.sql.catalog.lance.impl=dir \
4949
--conf spark.sql.catalog.lance.root=/path/to/lance/database
@@ -52,7 +52,7 @@ and namespace-specific options:
5252
=== "Spark Submit"
5353
```shell
5454
spark-submit \
55-
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.5 \
55+
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.6 \
5656
--conf spark.sql.catalog.lance=com.lancedb.lance.spark.LanceNamespaceSparkCatalog \
5757
--conf spark.sql.catalog.lance.impl=dir \
5858
--conf spark.sql.catalog.lance.root=/path/to/lance/database \
@@ -155,7 +155,7 @@ Here we use LanceDB Cloud as an example of the REST namespace:
155155
=== "Spark Shell"
156156
```shell
157157
spark-shell \
158-
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.5 \
158+
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.6 \
159159
--conf spark.sql.catalog.lance=com.lancedb.lance.spark.LanceNamespaceSparkCatalog \
160160
--conf spark.sql.catalog.lance.impl=rest \
161161
--conf spark.sql.catalog.lance.headers.x-api-key=your-api-key \
@@ -166,7 +166,7 @@ Here we use LanceDB Cloud as an example of the REST namespace:
166166
=== "Spark Submit"
167167
```shell
168168
spark-submit \
169-
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.5 \
169+
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.6 \
170170
--conf spark.sql.catalog.lance=com.lancedb.lance.spark.LanceNamespaceSparkCatalog \
171171
--conf spark.sql.catalog.lance.impl=rest \
172172
--conf spark.sql.catalog.lance.headers.x-api-key=your-api-key \
@@ -237,7 +237,7 @@ Using the Glue namespace requires additional dependencies beyond the main Lance
237237
Example with Spark Shell:
238238
```shell
239239
spark-shell \
240-
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.5,com.lancedb:lance-namespace-glue:0.0.6,software.amazon.awssdk:bundle:2.20.0 \
240+
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.6,com.lancedb:lance-namespace-glue:0.0.7,software.amazon.awssdk:bundle:2.20.0 \
241241
--conf spark.sql.catalog.lance=com.lancedb.lance.spark.LanceNamespaceSparkCatalog \
242242
--conf spark.sql.catalog.lance.impl=glue \
243243
--conf spark.sql.catalog.lance.root=s3://your-bucket/lance
@@ -351,7 +351,7 @@ Using Hive namespaces requires additional JARs beyond the main Lance Spark bundl
351351
Example with Spark Shell for Hive 3.x:
352352
```shell
353353
spark-shell \
354-
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.5,com.lancedb:lance-namespace-hive3:0.0.6 \
354+
--packages com.lancedb:lance-spark-bundle-3.5_2.12:0.0.6,com.lancedb:lance-namespace-hive3:0.0.7 \
355355
--conf spark.sql.catalog.lance=com.lancedb.lance.spark.LanceNamespaceSparkCatalog \
356356
--conf spark.sql.catalog.lance.impl=hive3 \
357357
--conf spark.sql.catalog.lance.hadoop.hive.metastore.uris=thrift://metastore:9083 \

docs/src/install.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ If you want to also include the bundled jar in your own bundle, remove the provi
3232
<dependency>
3333
<groupId>com.lancedb</groupId>
3434
<artifactId>lance-spark-bundle-3.5_2.12</artifactId>
35-
<version>0.0.5</version>
35+
<version>0.0.6</version>
3636
<scope>provided</scope>
3737
</dependency>
3838
```
@@ -41,15 +41,15 @@ If you want to also include the bundled jar in your own bundle, remove the provi
4141
```gradle
4242
dependencies {
4343
// For Spark 3.5 (Scala 2.12)
44-
compileOnly 'com.lancedb:lance-spark-bundle-3.5_2.12:0.0.5'
44+
compileOnly 'com.lancedb:lance-spark-bundle-3.5_2.12:0.0.6'
4545
}
4646
```
4747

4848
=== "sbt"
4949
```scala
5050
libraryDependencies ++= Seq(
5151
// For Spark 3.5 (Scala 2.12)
52-
"com.lancedb" %% "lance-spark-bundle-3.5_2.12" % "0.0.5" % "provided"
52+
"com.lancedb" %% "lance-spark-bundle-3.5_2.12" % "0.0.6" % "provided"
5353
)
5454
```
5555

docs/src/operations/ddl/create-table.md

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
Create new Lance tables with SQL DDL statements.
44

5+
## Basic Table Creation
6+
57
Create a simple table:
68

79
```sql
8-
--
910
CREATE TABLE users (
1011
id BIGINT NOT NULL,
1112
name STRING,
@@ -29,4 +30,92 @@ CREATE TABLE events (
2930
>,
3031
occurred_at TIMESTAMP
3132
);
33+
```
34+
35+
## Vector Columns
36+
37+
Lance supports vector (embedding) columns for AI workloads. These columns are stored internally as Arrow `FixedSizeList[n]` where `n` is the vector dimension. Since Spark SQL doesn't have a native fixed-size array type, you must use `ARRAY<FLOAT>` or `ARRAY<DOUBLE>` with table properties to specify the fixed dimension. The Lance-Spark connector will automatically convert these to the appropriate Arrow FixedSizeList format during write operations.
38+
39+
### Supported Types
40+
41+
- **Element Types**: `FLOAT` (float32), `DOUBLE` (float64)
42+
- **Requirements**:
43+
- Vectors must be non-nullable
44+
- All vectors in a column must have the same dimension
45+
- Dimension is specified via table properties
46+
47+
### Creating Vector Columns
48+
49+
To create a table with vector columns, use the table property pattern `<column_name>.arrow.fixed-size-list.size` with the dimension as the value:
50+
51+
```sql
52+
CREATE TABLE embeddings_table (
53+
id INT NOT NULL,
54+
text STRING,
55+
embeddings ARRAY<FLOAT> NOT NULL
56+
) USING lance
57+
TBLPROPERTIES (
58+
'embeddings.arrow.fixed-size-list.size' = '128'
59+
);
60+
```
61+
62+
Create table with multiple vector columns of different dimensions:
63+
64+
```sql
65+
CREATE TABLE multi_vector_table (
66+
id INT NOT NULL,
67+
title STRING,
68+
text_embeddings ARRAY<FLOAT> NOT NULL,
69+
image_embeddings ARRAY<DOUBLE> NOT NULL
70+
) USING lance
71+
TBLPROPERTIES (
72+
'text_embeddings.arrow.fixed-size-list.size' = '384',
73+
'image_embeddings.arrow.fixed-size-list.size' = '512'
74+
);
75+
```
76+
77+
### Working with Vector Tables
78+
79+
Once created, you can insert data using SQL:
80+
81+
```sql
82+
-- Insert vector data (example with small vectors for clarity)
83+
INSERT INTO embeddings_table VALUES
84+
(1, 'first text', array(0.1, 0.2, 0.3, ...)), -- 128 float values
85+
(2, 'second text', array(0.4, 0.5, 0.6, ...));
86+
```
87+
88+
Query vector tables:
89+
90+
```sql
91+
-- Select vectors
92+
SELECT id, text FROM embeddings_table WHERE id = 1;
93+
94+
-- Count rows
95+
SELECT COUNT(*) FROM embeddings_table;
96+
```
97+
Note: When reading vector columns back, they are automatically converted to Spark's `ARRAY<FLOAT>` or `ARRAY<DOUBLE>` types for compatibility with Spark operations.
98+
99+
### Vector Indexing
100+
101+
After creating and populating vector columns, you can create vector indexes using Lance Python API for similarity search:
102+
103+
```python
104+
import lance
105+
106+
# Open the dataset
107+
ds = lance.dataset("/path/to/embeddings_table.lance")
108+
109+
# Create a vector index on the embeddings column
110+
ds.create_index(
111+
"embeddings",
112+
index_type="IVF_PQ",
113+
)
114+
115+
# Perform similarity search
116+
import numpy as np
117+
query_vector = np.random.rand(128).astype(np.float32)
118+
results = ds.to_table(
119+
nearest={"column": "embeddings", "q": query_vector, "k": 10}
120+
).to_pandas()
32121
```

docs/src/operations/ddl/dataframe-create-table.md

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Create Lance tables from DataFrames using the DataSource V2 API.
44

5+
## Basic DataFrame Creation
6+
57
=== "Python"
68
```python
79
# Create DataFrame
@@ -55,4 +57,159 @@ Create Lance tables from DataFrames using the DataSource V2 API.
5557

5658
// Write as new table using catalog
5759
df.writeTo("users").create();
58-
```
60+
```
61+
62+
## Creating Tables with Vector Columns
63+
64+
Lance supports vector (embedding) columns for AI workloads. These columns are stored internally as Arrow `FixedSizeList[n]` where `n` is the vector dimension. Since Spark DataFrames don't have a native fixed-size array type, you need to add metadata to your schema fields to indicate that an `ArrayType(FloatType)` or `ArrayType(DoubleType)` should be converted to Arrow FixedSizeList.
65+
66+
The metadata key `"arrow.fixed-size-list.size"` with a value like `128` tells the Lance-Spark connector to convert that array column to a `FixedSizeList[128]` during write operations.
67+
68+
### Supported Types
69+
70+
- **Element Types**: `FloatType` (float32), `DoubleType` (float64)
71+
- **Array Requirements**:
72+
- Must have `containsNull = false`
73+
- Column must be non-nullable
74+
- All arrays must have exactly the specified dimension
75+
76+
### Examples
77+
78+
=== "Python"
79+
```python
80+
from pyspark.sql.types import StructType, StructField, IntegerType, ArrayType, FloatType
81+
from pyspark.sql.types import Metadata
82+
83+
# Create schema with vector column
84+
vector_metadata = {"arrow.fixed-size-list.size": 128}
85+
schema = StructType([
86+
StructField("id", IntegerType(), False),
87+
StructField("embeddings", ArrayType(FloatType(), False), False, vector_metadata)
88+
])
89+
90+
# Create DataFrame with vector data
91+
import numpy as np
92+
data = [(i, np.random.rand(128).astype(np.float32).tolist()) for i in range(100)]
93+
df = spark.createDataFrame(data, schema)
94+
95+
# Write to Lance format
96+
df.writeTo("vectors_table").create()
97+
```
98+
99+
=== "Scala"
100+
```scala
101+
import org.apache.spark.sql.types._
102+
103+
// Create metadata for vector column
104+
val vectorMetadata = new MetadataBuilder()
105+
.putLong("arrow.fixed-size-list.size", 128)
106+
.build()
107+
108+
// Create schema with vector column
109+
val schema = StructType(Array(
110+
StructField("id", IntegerType, false),
111+
StructField("embeddings", ArrayType(FloatType, false), false, vectorMetadata)
112+
))
113+
114+
// Create DataFrame with vector data
115+
import scala.util.Random
116+
val data = (0 until 100).map { i =>
117+
(i, Array.fill(128)(Random.nextFloat()))
118+
}
119+
val df = spark.createDataFrame(data).toDF("id", "embeddings")
120+
121+
// Write to Lance format
122+
df.writeTo("vectors_table").create()
123+
```
124+
125+
=== "Java"
126+
```java
127+
import org.apache.spark.sql.types.*;
128+
129+
// Create metadata for vector column
130+
Metadata vectorMetadata = new MetadataBuilder()
131+
.putLong("arrow.fixed-size-list.size", 128)
132+
.build();
133+
134+
// Create schema with vector column
135+
StructType schema = new StructType(new StructField[] {
136+
DataTypes.createStructField("id", DataTypes.IntegerType, false),
137+
DataTypes.createStructField("embeddings",
138+
DataTypes.createArrayType(DataTypes.FloatType, false),
139+
false, vectorMetadata)
140+
});
141+
142+
// Create DataFrame with vector data
143+
List<Row> rows = new ArrayList<>();
144+
Random random = new Random();
145+
for (int i = 0; i < 100; i++) {
146+
float[] vector = new float[128];
147+
for (int j = 0; j < 128; j++) {
148+
vector[j] = random.nextFloat();
149+
}
150+
rows.add(RowFactory.create(i, vector));
151+
}
152+
Dataset<Row> df = spark.createDataFrame(rows, schema);
153+
154+
// Write to Lance format
155+
df.writeTo("vectors_table").create();
156+
```
157+
158+
### Creating Multiple Vector Columns
159+
160+
You can create DataFrames with multiple vector columns, each with different dimensions:
161+
162+
=== "Python"
163+
```python
164+
from pyspark.sql.types import DoubleType
165+
166+
# Create schema with multiple vector columns
167+
text_metadata = {"arrow.fixed-size-list.size": 384}
168+
image_metadata = {"arrow.fixed-size-list.size": 512}
169+
170+
schema = StructType([
171+
StructField("id", IntegerType(), False),
172+
StructField("text_embeddings", ArrayType(FloatType(), False), False, text_metadata),
173+
StructField("image_embeddings", ArrayType(DoubleType(), False), False, image_metadata)
174+
])
175+
176+
# Create DataFrame with multiple vector columns
177+
data = [
178+
(i,
179+
np.random.rand(384).astype(np.float32).tolist(),
180+
np.random.rand(512).tolist())
181+
for i in range(100)
182+
]
183+
df = spark.createDataFrame(data, schema)
184+
185+
# Write to Lance format
186+
df.writeTo("multi_vectors_table").create()
187+
```
188+
189+
### Vector Indexing
190+
191+
After creating tables with vector columns, you can create vector indexes for efficient similarity search using the Lance Python API:
192+
193+
```python
194+
import lance
195+
import numpy as np
196+
197+
# Open the dataset
198+
ds = lance.dataset("/path/to/vectors_table.lance")
199+
200+
# Create a vector index on the embeddings column
201+
ds.create_index(
202+
"embeddings",
203+
index_type="IVF_PQ",
204+
num_partitions=256,
205+
num_sub_vectors=16
206+
)
207+
208+
# Perform similarity search
209+
query_vector = np.random.rand(128).astype(np.float32)
210+
results = ds.to_table(
211+
nearest={"column": "embeddings", "q": query_vector, "k": 10}
212+
).to_pandas()
213+
```
214+
215+
Note: When reading vector columns back into Spark DataFrames, they are automatically converted to regular `ArrayType(FloatType)` or `ArrayType(DoubleType)` for compatibility with Spark operations.

lance-spark-3.4_2.12/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<parent>
99
<groupId>com.lancedb</groupId>
1010
<artifactId>lance-spark-root</artifactId>
11-
<version>0.0.5</version>
11+
<version>0.0.6</version>
1212
<relativePath>../pom.xml</relativePath>
1313
</parent>
1414

lance-spark-3.4_2.13/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<parent>
99
<groupId>com.lancedb</groupId>
1010
<artifactId>lance-spark-root</artifactId>
11-
<version>0.0.5</version>
11+
<version>0.0.6</version>
1212
<relativePath>../pom.xml</relativePath>
1313
</parent>
1414

lance-spark-3.5_2.12/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<parent>
99
<groupId>com.lancedb</groupId>
1010
<artifactId>lance-spark-root</artifactId>
11-
<version>0.0.5</version>
11+
<version>0.0.6</version>
1212
<relativePath>../pom.xml</relativePath>
1313
</parent>
1414

0 commit comments

Comments
 (0)