Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Try the Vector Database from SQL

In progress: This Rust course material is awaiting a deeper review from the author.

Before you implement the table adapter or optimizer, use the supplied system once. You will create and populate an ordinary in-memory table, run one nearest-neighbor query, attach an index to its vector column, and see the physical plan change while the SQL result stays the same.

This tour uses the completed vector-db-from-scratch-datafusion example. You do not need to read or modify its source. Your own work begins on Day 1.

Launch the Supplied Shell

For an interactive run, start from the repository root:

cargo run -p vector-db-from-scratch-datafusion --example sql

The supplied DataFusion CLI starts with an empty course session and accepts semicolon-terminated SQL, including statements that span multiple lines. For a repeatable first run from the repository root, paste the whole transcript below into your terminal instead of entering the statements interactively:

cargo run -p vector-db-from-scratch-datafusion --example sql <<'SQL'
CREATE TABLE points (id BIGINT NOT NULL, payload VARCHAR NOT NULL, embedding REAL[3] NOT NULL);
INSERT INTO points VALUES (1, 'one', [1.0, 0.0, 0.0]), (2, 'two', [0.9, 0.1, 0.0]), (3, 'three', [0.0, 1.0, 0.0]), (4, 'four', [-1.0, 0.0, 0.0]), (5, 'five', [0.0, 0.0, 1.0]);
EXPLAIN SELECT id, payload FROM points ORDER BY cosine_distance(embedding, [1.0, 0.0, 0.0]) LIMIT 3;
SELECT id, payload FROM points ORDER BY cosine_distance(embedding, [1.0, 0.0, 0.0]) LIMIT 3;
CREATE INDEX points_embedding_idx ON points USING ivfflat (embedding);
EXPLAIN SELECT id, payload FROM points ORDER BY cosine_distance(embedding, [1.0, 0.0, 0.0]) LIMIT 3;
SELECT id, payload FROM points ORDER BY cosine_distance(embedding, [1.0, 0.0, 0.0]) LIMIT 3;
SQL

Before you run it, predict which rows should be nearest and why creating an index must not change them.

Observe the Stable Query and Changing Plan

Before the index exists, DataFusion reads the ordinary in-memory table:

SortExec: TopK(fetch=3), ...
  DataSourceExec: partitions=1, ...

The first query returns these rows:

1  one
2  two
3  three

Prediction: The next command attaches an index, but the following SELECT is byte-for-byte identical. Which physical plan leaf should change, and which three rows must not?

The CREATE INDEX statement builds the session’s cosine IVFFlat index and attaches it to the vector column you selected. The second EXPLAIN reaches the course-owned scan:

SortExec: TopK(fetch=3), ...
  VectorIndexScanExec: index=ivf_flat, metric=Cosine, query_dim=3, fetch=Some(3), ordered=false

The second query is byte-for-byte the same SQL and returns the same three rows. The index changes how candidates reach DataFusion’s final sort; it does not change the query contract.

Know What This Command Means

DataFusion parses and logically plans CREATE INDEX, but the pinned version does not provide a physical executor that can build this course’s index. The supplied shell therefore owns a bounded bridge from that statement to the course’s existing attachment path. The session is configured for cosine IVFFlat, while the statement supplies the index name, resolved table, and selected column:

CREATE INDEX points_embedding_idx ON points USING ivfflat (embedding)

The name may be any unused index name, and the table may be bare or schema/catalog qualified. The bridge can attach indexes to multiple distinct table/column pairs in one session. Each target must be a registered in-memory MemTable, and its selected column must be a non-null REAL[N] vector with positive width. Duplicate names or attachments, missing tables or columns, providers other than MemTable, nullable fields, vector fields with the wrong physical type or zero width, and an index kind different from the session configuration are rejected before an attachment is installed.

Prediction: Suppose the session also contains another eligible table with a different vector field. Which table, column, and index names must the bridge resolve from the SQL statement rather than hard-code from this points example?

An attachment is an immutable snapshot. After a table is indexed, INSERT, ALTER TABLE, and DROP TABLE against that table are rejected instead of making the index stale. Writes to unrelated tables remain legal, as does INSERT ... SELECT that reads indexed data into another table. The bridge does not add index persistence, DROP INDEX, automatic rebuilding, or a general catalog lifecycle.

Prediction: Why must a later INSERT into the indexed table be rejected unless the table update and a rebuilt index can become visible atomically?

That narrow boundary keeps the first experience concrete without turning the course into a parser or catalog project. Next, Day 1 opens the path you just used: you will build the Arrow table, attach one selected vector field, and make the optimizer choose VectorIndexScanExec only when doing so is safe.

Your feedback is greatly appreciated. Join our Discord community.
Found an issue? Open an issue or pull request at github.com/skyzh/vector-db-from-scratch.
vector-db-from-scratch-book © 2024-2026 by Alex Chi Z is licensed under CC BY-NC-SA 4.0.