Skip to main content
A table is a schema plus an index spec: which columns are full-text- and vector-indexed (see Indexing). Create it once, then append rows.

Create and append

import infino
import pyarrow as pa

# Durable storage (a path or s3:// URI), required if you'll update/delete later.
db = infino.connect("./data")

schema = pa.schema([
    pa.field("doc_id", pa.large_utf8(), nullable=False),
    pa.field("body", pa.large_utf8(), nullable=False),
    pa.field("embedding", pa.list_(pa.float32(), 384), nullable=False),
])
docs = db.create_table(
    "docs", schema,
    infino.IndexSpec().fts("body").vector("embedding", 384, 64, "cosine"),
)

# One append is one atomic commit, durable when the call returns.
docs.append([
    {"doc_id": "1", "body": "To cancel, open Settings then Billing.", "embedding": embed("...")},
])
import { connect, IndexSpec } from "@infino-ai/infino";

// Durable storage (a path or s3:// URI), required if you'll update/delete later.
const db = connect("./data");

const docs = db.createTable(
  "docs",
  { doc_id: "large_utf8", body: "large_utf8", embedding: { vector: 384 } },
  new IndexSpec().fts("body").vector("embedding", 384, 64, "cosine"),
);

// One append is one atomic commit, durable when the call returns.
docs.append([
  { doc_id: "1", body: "To cancel, open Settings then Billing.", embedding: embed("...") },
]);
use std::sync::Arc;
use arrow_schema::{DataType, Field, Schema};
use infino::{connect, IndexSpec, Metric};

// Durable storage (a path or s3:// URI), required if you'll update/delete later.
let db = connect("./data")?;

let item = Arc::new(Field::new("item", DataType::Float32, true));
let schema = Arc::new(Schema::new(vec![
    Field::new("doc_id", DataType::LargeUtf8, false),
    Field::new("body", DataType::LargeUtf8, false),
    Field::new("embedding", DataType::FixedSizeList(item, 384), false),
]));
let docs = db.create_table(
    "docs", schema,
    IndexSpec::new().fts("body").vector("embedding", 384, 64, Metric::Cosine),
)?;

// One append is one atomic commit. Build `batch` as an Arrow RecordBatch
// matching the schema (see the Quickstart).
docs.append(&batch)?;
One append == one atomic commit, durable on return. Append in batches rather than row-by-row for throughput.

Open an existing table

A table persists, so you create it once and open it on later runs with open_table.
docs = db.open_table("docs")
const docs = db.openTable("docs");
let docs = db.open_table("docs")?;

Update and delete

In Python and Node.js, update and delete match rows by a SQL predicate string. In Rust, they take a DataFusion Expr (col(...).eq(lit(...))), where col and lit come from the datafusion-expr crate (see the Quickstart for the companion crates Rust needs). update replaces matched rows 1:1 with the new rows you supply.
# Replace the row(s) matching the predicate (1:1 with the new rows):
docs.update("doc_id = '1'", [
    {"doc_id": "1", "body": "Refunds go to the original payment method.", "embedding": embed("...")},
])

# Delete rows matching a predicate:
stats = docs.delete("doc_id = '1'")
print(stats.matched, stats.n_tombstoned)
// Replace the row(s) matching the predicate (1:1 with the new rows):
docs.update("doc_id = '1'", [
  { doc_id: "1", body: "Refunds go to the original payment method.", embedding: embed("...") },
]);

// Delete rows matching a predicate:
const stats = docs.delete("doc_id = '1'");
console.log(stats.matched, stats.nTombstoned);
use datafusion_expr::{col, lit};   // build predicates as DataFusion expressions

// Replace the row(s) matching the predicate (1:1 with the new rows in `batch`):
docs.update(col("doc_id").eq(lit("1")), &batch)?;

// Delete rows matching a predicate:
let stats = docs.delete(col("doc_id").eq(lit("1")))?;
println!("{} matched, {} tombstoned", stats.matched, stats.n_tombstoned);
update and delete require durable storage: a path or s3:// URI, not memory://. update is 1:1, so the matched row count must equal the replacement count.

Compact, list, and drop

docs.optimize()                    # merge small / underfilled superfiles
db.list_tables()                   # ["docs"]
db.drop_table("docs", purge=True)  # purge=True also deletes the table's storage
docs.optimize();                   // merge small / underfilled superfiles
db.listTables();                   // ["docs"]
db.dropTable("docs", true);        // purge=true also deletes the table's storage
use infino::OptimizeOptions;

docs.optimize(&OptimizeOptions::default())?;  // merge small / underfilled superfiles
db.list_tables()?;                            // Vec<String>
db.drop_table("docs", true)?;                 // purge = true also deletes storage
optimize accepts options (omit for engine defaults):
Option (Python / Node.js / Rust)Description
max_memory_mb / maxMemoryMbbuild-time memory budget, in MB
min_fill_percent / minFillPercentonly compact superfiles below this fill percent (0 to 100)
target_superfile_size_mb / targetSuperfileSizeMbtarget merged-superfile size, in MB

Reclaim storage (gc)

Compaction and interrupted writes can leave behind storage objects that the table no longer references. gc deletes them and reports what it reclaimed. It only removes objects older than a grace window (in seconds), so it never races a concurrent reader or writer; requires durable storage.
report = docs.gc(3600.0)                        # delete orphans older than 1 hour
print(report.bytes_freed, report.objects_deleted)
const report = docs.gc(3600);                   // delete orphans older than 1 hour
console.log(report.bytesFreed, report.objectsDeleted);
use std::time::Duration;

let report = docs.gc(Duration::from_secs(3600))?;  // delete orphans older than 1 hour
println!("{} {}", report.bytes_freed, report.objects_deleted);
optimize already runs a best-effort gc after compacting, so calling gc directly is mainly for reclaiming on a schedule or after bulk deletes.

Limitations

  • One writer per table at a time. Appends/updates/deletes are serialized through a single atomic commit (concurrent writers from other processes retry).
  • update is 1:1. The matched row count must equal the replacement-row count.
  • update / delete need durable storage, not memory://.
  • Schema is fixed at creation. Adding or changing columns means a new table.

See also

Last modified on July 6, 2026