> ## Documentation Index
> Fetch the complete documentation index at: https://docs.infino.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Tables

> Manage Infino tables. Define schemas, append rows, update and delete by predicate, compact Parquet files, and inspect the table catalog.

A table is a **schema** plus an **index spec**: which columns are full-text- and
vector-indexed (see [Indexing](/guides/indexing)). Create it once, then append rows.

## Create and append

<CodeGroup>
  ```python Python icon="python" theme={null}
  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("...")},
  ])
  ```

  ```typescript Node.js icon="node-js" theme={null}
  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("...") },
  ]);
  ```

  ```rust Rust icon="rust" theme={null}
  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)?;
  ```
</CodeGroup>

<Note>
  One `append` == one atomic commit, durable on return. Append in batches rather than
  row-by-row for throughput.
</Note>

## Open an existing table

A table persists, so you create it once and open it on later runs with `open_table`.

<CodeGroup>
  ```python Python icon="python" theme={null}
  docs = db.open_table("docs")
  ```

  ```typescript Node.js icon="node-js" theme={null}
  const docs = db.openTable("docs");
  ```

  ```rust Rust icon="rust" theme={null}
  let docs = db.open_table("docs")?;
  ```
</CodeGroup>

## 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](/quickstart) for the
companion crates Rust needs). `update` replaces matched rows 1:1 with the new rows you
supply.

<CodeGroup>
  ```python Python icon="python" theme={null}
  # 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)
  ```

  ```typescript Node.js icon="node-js" theme={null}
  // 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);
  ```

  ```rust Rust icon="rust" theme={null}
  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);
  ```
</CodeGroup>

<Warning>
  `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.
</Warning>

## Compact, list, and drop

<CodeGroup>
  ```python Python icon="python" theme={null}
  docs.optimize()                    # merge small / underfilled superfiles
  db.list_tables()                   # ["docs"]
  db.drop_table("docs", purge=True)  # purge=True also deletes the table's storage
  ```

  ```typescript Node.js icon="node-js" theme={null}
  docs.optimize();                   // merge small / underfilled superfiles
  db.listTables();                   // ["docs"]
  db.dropTable("docs", true);        // purge=true also deletes the table's storage
  ```

  ```rust Rust icon="rust" theme={null}
  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
  ```
</CodeGroup>

`optimize` accepts options (omit for engine defaults):

| Option (Python / Node.js / Rust)                     | Description                                                |
| ---------------------------------------------------- | ---------------------------------------------------------- |
| `max_memory_mb` / `maxMemoryMb`                      | build-time memory budget, in MB                            |
| `min_fill_percent` / `minFillPercent`                | only compact superfiles below this fill percent (0 to 100) |
| `target_superfile_size_mb` / `targetSuperfileSizeMb` | target merged-superfile size, in MB                        |

## 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

* [Indexing](/guides/indexing)
* [Search](/guides/search#full-text-bm25)
* [Connect & storage](/guides/storage)
