Applies to VAST Cluster 5.5. Examples use the VAST DataBase Python SDK 2.1.0.
This page is the API reference for the vastdb Python SDK, version 2.1.0. Every signature, default and exception on it comes from the 2.1.0 source. If you're new to the SDK, start with the quickstart. For idioms and patterns, see Python SDK.
Install
pip install vastdb==2.1.0
| Requirement | Value in 2.1.0 |
|---|---|
| Python | 3.10 or later (python_requires='>=3.10.0') |
| PyArrow | >=18.0,<=23.0.1 |
| Ibis | ibis-framework~=10.1 (used to build predicates) |
| Optional extra | pip install 'vastdb[adbc]' installs adbc-driver-vastdb~=0.0.26. Vector search needs it. |
Before you can connect, you need an endpoint, an access key pair and an identity policy that grants the Tabular actions. See setup prerequisites.
Object model
Every database operation runs inside a transaction. You reach objects by walking down from the transaction:
vastdb.connect(...) → Session
session.transaction() → Transaction (context manager: commit on success, roll back on exception)
tx.bucket(name) → Bucket
bucket.schema(name) → Schema (schemas can nest)
schema.table(name) → Table
import pyarrow as pa
import vastdb
session = vastdb.connect(endpoint="http://<vip-or-dns>", access="<access-key>", secret="<secret-key>")
with session.transaction() as tx:
schema = tx.bucket("<bucket>").create_schema("<schema>", fail_if_exists=False)
table = schema.create_table("<table>", pa.schema([("id", pa.int64()), ("name", pa.utf8())]),
fail_if_exists=False)
table.insert(pa.table({"id": [1, 2], "name": ["a", "b"]}))
result = table.select(columns=["name"], predicate=table["id"] > 1).read_all()
Objects you get from a transaction are bound to it. Once the with block exits they can't be used any more, and that includes the RecordBatchReader that select() returns. Read it all before the block ends. To reuse table metadata across transactions, use TableMetadata.
Module vastdb
| Name | Description |
|---|---|
connect(*args, **kwargs) -> Session |
A shorthand for Session(...) that takes the same arguments. |
version() -> str |
Returns the installed SDK version, e.g. "2.1.0". |
PartitionSpec, PartitionKey |
Partitioning |
VectorIndexSpec, DefaultQuantization |
Vector index |
QueryConfig, ImportConfig, BufferedInsertConfig, BackoffConfig |
Configuration |
BlobExpansionConfig, ExpansionFormat |
Blob expansion |
TableRef, TableType |
Table metadata |
SessionConfig isn't re-exported from the top level. Import it with from vastdb.config import SessionConfig.
Session
vastdb.session.Session(
access: str | None = None,
secret: str | None = None,
endpoint: str | None = None,
*,
max_entities_per_page: int = 10_000,
ssl_verify: bool = True,
timeout=None,
backoff_config: BackoffConfig | None = None,
adbc_driver_path: str | None = None,
end_user: str | None = None,
config: SessionConfig = SessionConfig(),
)
| Parameter | Description |
|---|---|
access, secret |
S3 access and secret key. If you omit them, they're read from AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY. If the variable isn't set, you get a KeyError. |
endpoint |
URL of the VAST endpoint. If omitted, it's read from AWS_S3_ENDPOINT_URL. If neither is set, you get ValueError: One of endpoint and AWS_S3_ENDPOINT_URL env is required. |
max_entities_per_page |
Page size for listing schemas, tables, columns and so on. |
ssl_verify |
Passed to the HTTP client. Set False to skip TLS certificate verification. |
timeout |
Per-request timeout, passed to the HTTP client (requests). None means no timeout. |
backoff_config |
Retry policy. See BackoffConfig and Retries. |
adbc_driver_path |
Path to the VAST ADBC driver library. If omitted, the SDK uses the driver from vastdb[adbc] when it's installed. You only need ADBC for vector search. |
end_user |
Runs ADBC (VAST Query Engine) operations, such as vector_search(), as this end user instead of as the key's owner. The SDK passes it as the vast.db.end_user connection option. The key's owner needs the s3:EndUserImpersonation permission. Row and column security then applies as that end user; see security. |
config |
Session defaults. See SessionConfig. |
Constructing a Session contacts the endpoint straight away to detect the cluster version, which decides which features you can use (see Version-gated features). If the endpoint isn't a VAST DataBase endpoint, the constructor raises an error. repr(session) never shows the secret key.
| Member | Description |
|---|---|
transaction() -> Transaction |
Returns a new transaction that hasn't started yet. Use it as a context manager. |
buffered_inserter(metadata: TableRef \| TableMetadata, executor: ThreadPoolExecutor, flush_config: BufferedInsertConfig \| None = None) |
Returns a buffered inserter for streaming record batches into a partitioned table. See Buffered inserts. Needs cluster 5.5 or later. |
features |
A Features object describing what the connected cluster version supports. |
Transaction
with session.transaction() as tx:
...
When you enter the block, a transaction starts. If the block exits normally, the transaction commits. If it exits because of an exception, the transaction rolls back and the exception propagates.
| Member | Description |
|---|---|
bucket(name: str) -> Bucket |
Returns the bucket. Raises errors.MissingBucket if it doesn't exist. |
table_from_metadata(metadata: TableMetadata) -> ITable |
Returns a table handle built from metadata you already have, without listing bucket, schema and table. See Reusing metadata across transactions. |
catalog(snapshot: Bucket \| None = None, fail_if_missing=True) -> Table \| None |
Returns the VAST Catalog table. See VAST Catalog. |
catalog_snapshots() -> list[Bucket] |
Lists the VAST Catalog snapshots. See snapshots and change detection. |
audit_log(fail_if_missing=True) -> Table \| None |
Returns the audit log table. See query the audit log. |
is_active -> bool |
Whether the transaction is open. |
active_txid -> int |
The transaction ID. Raises TransactionNotActiveError if the transaction isn't open. |
adbc_conn |
The ADBC connection for this transaction. Raises NoAdbcConnectionError if no ADBC driver is available. |
Bucket
| Member | Description |
|---|---|
name: str |
Bucket name. For a snapshot, this is "<bucket>/.snapshot/<snapshot>". |
create_schema(name: str, fail_if_exists=True) -> Schema |
Creates a top-level schema. If the schema already exists, raises errors.SchemaExists, or returns the existing one when fail_if_exists=False. |
schema(name: str, fail_if_missing=True) -> Schema \| None |
Returns a top-level schema. Raises errors.MissingSchema, or returns None when fail_if_missing=False. |
schemas(batch_size: int \| None = None) -> list[Schema] |
Lists the top-level schemas. |
snapshot(name: str, fail_if_missing=True) -> Bucket \| None |
Returns a snapshot of this bucket as a Bucket. Raises errors.MissingSnapshot, or returns None when fail_if_missing=False. |
snapshots() -> list[Bucket] |
Lists this bucket's snapshots, each as a Bucket. |
You can read a snapshot with the same schema() / table() / select() calls you use on the live bucket. For what snapshots contain, see snapshots and replication.
Schema
| Member | Description |
|---|---|
name: str |
Full schema path within the bucket. Nested schemas are /-separated. |
create_schema(name, fail_if_exists=True) -> Schema |
Creates a child schema. |
schema(name, fail_if_missing=True) -> Schema \| None |
Returns a child schema. |
schemas(batch_size=None) -> list[Schema] |
Lists the child schemas. |
create_table(...) -> Table |
See below. |
table(name: str, fail_if_missing=True) -> Table \| None |
Returns a table and loads its schema and stats. Raises errors.MissingTable, or returns None. |
tables(table_name: str = "", page_size=1000) -> list[Table] |
Lists the tables. If you pass table_name, it returns only that table, if it exists. Each returned Table loads its own metadata, so on a schema with many tables use tablenames() instead. |
tablenames(page_size=1000) -> list[str] |
Lists the table names only. |
drop() -> None |
Drops this schema. |
rename(new_name) -> None |
Renames this schema. |
Schema.create_table
create_table(
table_name: str,
columns: pa.Schema,
fail_if_exists: bool = True,
use_external_row_ids_allocation: bool = False,
sorting_key: list[str] | list[int] = [],
vector_index: VectorIndexSpec | None = None,
partition_spec: PartitionSpec | None = None,
) -> Table
| Parameter | Description |
|---|---|
table_name |
Name of the new table. |
columns |
The table's columns, as a PyArrow schema. Types that Ibis can't represent are rejected with errors.NotSupportedSchema. For supported types, see data types. |
fail_if_exists |
If False and the table exists, the existing table is returned unchanged. The other arguments aren't compared against it. |
use_external_row_ids_allocation |
Creates the table with user-defined row IDs. Needs cluster 5.1 or later. |
sorting_key |
Columns to sort by, as names or column indexes. A non-empty key creates a sorted table. |
vector_index |
Creates a vector-indexed table. See Vector index. |
partition_spec |
Creates a partitioned table. See PartitionSpec. |
Sorting, partitioning, vector indexing and user-defined row IDs can't all be combined freely. See limits for which combinations are allowed. For how to choose between them, see data modeling.
Table
schema.table() and schema.create_table() return a Table. tx.table_from_metadata() returns a TableInTransaction, which has the same query and write methods but lacks the DDL methods marked Table only below.
Properties and metadata
| Member | Description |
|---|---|
name -> str |
Table name. |
ref -> TableRef |
Bucket, schema and table name. |
path -> str |
"<bucket>/<schema>/<table>" |
arrow_schema -> pa.Schema |
The cached column schema. |
columns() -> pa.Schema |
Table only. Reloads the column schema from the server and returns it. |
retrieve_column_names() -> list[str] |
Fetches only the column names. On cluster 5.5 this is a cheaper call than loading the full schema. |
reload_schema() -> None |
Reloads the cached schema. |
stats |
TableStats. On a Table this reloads stats from the server each time you read it. On a TableInTransaction it returns the cached value, which may be None. |
get_stats() -> TableStats |
Table only. The same as stats. |
reload_stats() -> None |
Reloads the cached stats. |
sorted_columns() -> list[pa.Field] |
The table's sort-key columns. In 2.1.0, Table.sorted_columns() goes through the deprecated reload path internally, so it emits a DeprecationWarning even though sorted_columns() itself isn't deprecated. TableInTransaction.sorted_columns() returns the cached value and doesn't warn. |
sorted_table -> bool |
Table only. Whether the table is sorted. |
vector_index |
The table's VectorIndex, or None. |
handle -> int |
Table only. The server-side table handle. |
t["col"] |
Returns an Ibis column expression for building predicates. |
TableStats fields are num_rows, size_in_bytes, sorting_score, write_amplification, acummulative_row_inserition_count (the spelling is the SDK's), vips, is_external_rowid_alloc, sorting_key_enabled, sorting_done, endpoints, vector_index and partitioning_info.
vips lists the virtual IPs of the pool that the session's endpoint belongs to, so a session opened on a Query Engine endpoint reports Query Engine addresses. On a vector-indexed table, num_rows can be higher than the real row count while the cluster indexes new rows in the background; use a count query when you need the exact number.
select
select(
columns: list[str] | None = None,
predicate: ibis.expr.types.BooleanColumn | ibis.common.deferred.Deferred | None = None,
config: QueryConfig | None = None,
*,
internal_row_id: bool = False,
limit_rows: int | None = None,
) -> pa.RecordBatchReader
| Parameter | Description |
|---|---|
columns |
The columns to return. None returns all of them. Only these columns are read (projection pushdown). |
predicate |
A row filter that's evaluated on the cluster. See Predicates. True is treated as no filter, and False returns an empty result without running a query. |
config |
Query execution settings. See QueryConfig. |
internal_row_id |
Adds a $row_id column to the result. You need it for update() and delete(). |
limit_rows |
Stops once this many rows have been returned. It must be non-negative. |
The return value is a pyarrow.RecordBatchReader that streams results while you iterate. Call .read_all() to get a pa.Table, or .read_pandas(). Consume it before the transaction ends. If the transaction closes while the reader is still in use, you get errors.MissingTransaction.
How a query runs:
- The query is split into disjoint row ranges called splits, and the splits are spread across the data endpoints. Unless you set
QueryConfig.num_splits, the number of splits is estimated from the table's row count, asnum_rows // QueryConfig.rows_per_splitwith a minimum of 1. For a sorted table, the SDK also asks the server for an estimate that takes the predicate into account, and uses the smaller of the two. - By default only the endpoints the session discovers are used. To spread a query over more CNodes, set
QueryConfig.data_endpoints. See performance. - On a partitioned table, the SDK first selects the matching partitions, using only the parts of the predicate that reference partition source columns. It then queries each partition. If the predicate doesn't constrain partition columns, every partition is scanned.
select_splits
select_splits(columns=None, predicate=None, config=None, *, internal_row_id=False, limit_rows=None)
-> list[pa.RecordBatchReader]
Takes the same arguments as select(), but returns one reader per split instead of merging them. Use it when you want to parallelise consumption yourself, for example one split per worker process. Partitioned tables raise NotImplementedError("Method select_splits is not implemented for partitioned tables").
Predicates
You build predicates with Ibis expressions, either from the table (t["c"]) or from the deferred _ (from ibis import _). The SDK serialises a predicate and the cluster evaluates it. A predicate the SDK can't serialise raises NotImplementedError before anything is sent.
| Ibis expression | Meaning |
|---|---|
t["c"] > v, >=, <, <=, ==, != |
Comparison with a literal |
t["c"].isnull() |
IS NULL |
~t["c"].isnull() |
IS NOT NULL. ~ is only supported directly around isnull(). |
t["c"].isin([v1, v2]) |
IN (sent as OR'ed equalities) |
t["c"].between(lo, hi) |
BETWEEN |
t["c"].contains("s") |
Substring match (LIKE '%s%') |
t["c"].startswith("s") |
Prefix match. startswith("") is treated as IS NOT NULL. |
a & b, a \| b |
AND, OR |
Shape rules:
- The predicate must be an AND of ORs. Inside each OR group, every term must reference the same column. For example,
((t["a"] > 1) | (t["a"].isnull())) & (t["b"] == 2)works, but(t["a"] > 1) | (t["b"] == 2)raisesNotImplementedError. - Comparisons must be against a literal. Column-to-column comparisons aren't supported.
- You can filter on a struct sub-field (
t["s"]["x"]) only when it's a leaf. Fixed-size lists with a flat element type are supported. Other nested columns aren't.
For which operators push down per engine, see pushdowns.
insert
insert(rows: pa.RecordBatch | pa.Table, by_columns: bool = True) -> pa.ChunkedArray
Inserts the rows and returns the row IDs that were assigned to them. A pa.Table is combined into a single batch before it's sent. by_columns is ignored and only kept for backward compatibility.
Each request is limited to 5 MiB, and the SDK keeps within that for you:
- If the batch fits in one request, it's sent as a single insert.
- If it doesn't, the SDK first inserts as many columns as fit, splitting the rows across several requests if needed. That always includes the columns you can't update later: sort keys, partition columns and the vector column. This step returns the row IDs.
- The remaining columns are then written with updates against those row IDs.
All of these requests run in your transaction, so they commit or roll back together. An oversized batch does cost several requests, though, so batch size still affects throughput; see ingest. If a single row can't fit in one request, you get errors.TooWideRow("One of the rows in the given batch is too big for an rpc").
update and delete
update(rows: pa.RecordBatch | pa.Table, columns: list[str] | None = None) -> None
delete(rows: pa.RecordBatch | pa.Table) -> None
Both identify rows by a $row_id column, which must be present in rows. Without it you get errors.MissingRowIdColumn. update() writes the named columns, or every column except $row_id if you don't name any.
The usual way to get row IDs is to select them first:
with session.transaction() as tx:
t = tx.bucket("<bucket>").schema("<schema>").table("<table>")
hits = t.select(columns=["id"], predicate=t["id"] == 42, internal_row_id=True).read_all()
t.delete(hits)
The $row_id type depends on the table. It's uint64 on regular tables, and decimal128(38, 0) on sorted, partitioned and vector-indexed tables. If you take $row_id straight from select(..., internal_row_id=True), it has the right type for that table.
You can't update sort-key columns, partition columns or the vector-index column.
Import Parquet files
import_files(files_to_import: Iterable[str], config: ImportConfig | None = None) -> None
import_partitioned_files(files_and_partitions: dict[str, pa.RecordBatch], config: ImportConfig | None = None) -> None
These have the cluster import Parquet files that are already in a VAST S3 bucket, using the session's credentials. The data doesn't pass through the client. Paths have the form "/<bucket>/<path/to/file.parquet>". A path without the leading / raises errors.InvalidArgument.
The work is spread over the discovered data endpoints, or over ImportConfig.data_endpoints if you set it. By default the concurrency is twice the number of CNodes. Files are sent in batches of up to 10 per request. import_partitioned_files maps each file to a one-row RecordBatch that holds its partition values.
To infer a schema from the files, create the table and import in one call, use vastdb.util.create_table_from_files(schema, table_name, parquet_files, schema_merge_func=None, config=None). schema_merge_func must be one of:
| Function | Behaviour |
|---|---|
util.default_schema_merge (default) |
Each file's schema must be a subset of the largest. Otherwise it raises InvalidArgument("Found mismatch in parquet files schemas."). |
util.strict_schema_merge |
All files must have identical schemas. |
util.union_schema_merge |
Unifies all the schemas (pa.unify_schemas). |
See ingest for guidance.
Schema evolution — Table only
| Method | Description |
|---|---|
add_column(new_column: pa.Schema) |
Adds the columns in new_column. |
drop_column(column_to_drop: pa.Schema) |
Drops the columns in column_to_drop. |
rename_column(current_column_name: str, new_column_name: str) |
Renames a column. |
rename(new_name: str) |
Renames the table. |
drop() |
Drops the table. |
Sorting
| Method | Description |
|---|---|
add_sorting_key(sorting_key: list[str] \| list[int]) |
Table only. Adds a sort key to a table that doesn't already have one. It raises ValueError if the key is empty. Needs cluster 5.3 or later. |
sorting_done() -> bool |
Whether the background sort has caught up. It's always False for unsorted tables. |
sorting_score() -> int |
Sorting progress score. It's always 0 for unsorted tables. |
Partitions
These methods only work on partitioned tables. On any other table they raise AssertionError("partitions can only be used on a partitioned table").
| Method | Description |
|---|---|
partitions() -> ReadOnlyTable |
Returns a queryable view of the table's partitions. It supports select(...), arrow_schema and p["col"]. Besides the partition columns, each row has estimate columns est_row_count and est_byte_size. |
delete_partitions(partitions: pa.RecordBatch, *, allow_non_acid: bool = False) |
Deletes whole partitions. Each row of partitions identifies one partition, using the schema returned by partitions(). This operation is not transactional. It refuses to run with AssertionError unless you pass allow_non_acid=True. |
For loading data into partitioned tables, see Buffered inserts.
Semi-sorted projections
| Method | Description |
|---|---|
create_projection(projection_name: str, sorted_columns: list[str], unsorted_columns: list[str]) -> Projection |
Table only. Creates a semi-sorted projection. |
projection(name: str, include_stats: bool = True) -> Projection |
Returns a projection. Raises errors.MissingProjection if it doesn't exist. |
projections(projection_name: str = "", include_stats: bool = True) -> list[Projection] |
Lists the projections. |
Projection has the attributes name and stats, where stats.num_rows and stats.size_in_bytes are populated. Its methods are columns() -> pa.Schema, rename(new_name) and drop().
Queries use projections automatically. QueryConfig.use_semi_sorted_projections turns that off, and QueryConfig.semi_sorted_projection_name forces a particular projection (cluster 5.1 or later).
Imports table
Each table can have an imports table that records which files have been imported into it. It needs cluster 5.2 or later.
| Method | Description |
|---|---|
create_imports_table(fail_if_exists=True) -> ITable |
Table only. Creates the imports table. On a 5.5 cluster a new table already has one, so this raises InternalServerError (failed creating import table (res=EXIST)). fail_if_exists is ignored in 2.1.0. Use imports_table() instead. |
imports_table() -> ITable |
Returns the imports table, which you can query with select(). |
Vector index
Vector-indexed tables are created with create_table(..., vector_index=VectorIndexSpec(...)) and searched with vector_search(). Both need cluster 5.5 or later; on an older cluster the SDK raises Vector tables require 5.5+ VAST release. You can only add the index when you create the table, and a table with a vector index can't also be sorted or partitioned, or use user-defined row IDs; see limits. This section covers the SDK surface only. For search semantics, index behaviour and tuning, see VAST Vector Search, and for a worked example its Python SDK page.
VectorIndexSpec and DefaultQuantization are exported from the vastdb package (from vastdb import VectorIndexSpec, DefaultQuantization). VectorIndex is returned by the SDK; you don't build it yourself.
VectorIndexSpec is the index you ask for when you create a table:
@dataclass
class VectorIndexSpec:
column: str
distance_metric: str
cluster_quantization: str | DefaultQuantization = DefaultQuantization()
search_quantization: str | DefaultQuantization = DefaultQuantization()
| Field | Description |
|---|---|
column |
The name of the vector column. In the Arrow schema, it's a fixed-size list of non-nullable float32 items. |
distance_metric |
"l2sq", "cosine" or "ip". The 2.1.0 docstring lists only "l2sq" and "ip", but the cluster also accepts "cosine". |
cluster_quantization |
"scalar_8bit" or "none". Leave it as DefaultQuantization() to use the cluster's default (scalar_8bit). |
search_quantization |
"scalar_8bit" or "none". Leave it as DefaultQuantization() to use the cluster's default (scalar_8bit). |
If you set search_quantization="none", set cluster_quantization="none" too: the cluster rejects "scalar_8bit" clustering with "none" search with errors.BadRequest.
VectorIndex is the index a table has, as the cluster reports it:
@dataclass
class VectorIndex:
column: str
distance_metric: str
sql_distance_function: str
cluster_quantization: str
search_quantization: str
| Member | Description |
|---|---|
sql_distance_function |
The SQL function the cluster stores for the table's metric: array_distance for l2sq, array_cosine_distance for cosine, and array_inner_product for ip. vector_search() orders by it. |
to_vector_index_spec() |
Turns it back into a VectorIndexSpec. |
table.vector_index and table.stats.vector_index hold the table's VectorIndex, or None if it has no index. If table.vector_index is None for a table you know is indexed, load the table again in a new transaction. For a table created with VectorIndexSpec(column="embedding", distance_metric="l2sq"), its value is:
VectorIndex(column='embedding', distance_metric='l2sq', sql_distance_function='array_distance', cluster_quantization='scalar_8bit', search_quantization='scalar_8bit')
The same definition is in the table's Arrow schema metadata, table.arrow_schema.metadata, under the key b"VAST:vector_index_spec", as JSON with the column, metric, SQL function and both quantization settings.
Vector search
On tables indexed with the ip metric, don't use vector_search() in SDK 2.1.0: it returns the least similar rows first. Search those tables with SQL ordered by array_negative_inner_product; see inner-product indexes.
vector_search(vec: list[float], columns: list[str], limit: int, predicate=None) -> pa.RecordBatchReader
| Parameter | Description |
|---|---|
vec |
The query vector. Its length must match the column's dimension. |
columns |
The columns to return. |
limit |
How many nearest rows to return. Required; there's no default. limit=100000 returned 100,000 rows. |
predicate |
An optional filter, written like a select() predicate. |
The result has the columns you asked for, plus vast_pysdk_vector_dist, the distance to vec, sorted from smallest to largest. Consume the reader before the transaction ends.
vector_search() builds one SQL query from table.vector_index and runs it on the transaction's ADBC connection, so it needs the ADBC driver and s3:QueryEngine*. It has no option for the vector search session settings, so it uses the cluster's defaults (search session settings).
Errors:
- A query vector of the wrong length fails with
Cannot cast array of size <n> to array of size <m>. - If the ADBC driver isn't installed,
vector_search()raisesvastdb.transaction.NoAdbcConnectionError: Adbc Driver may not have been supplied. See ADBC.
Blob expansion
create_blob_expansion(), blob_expansion() and the BlobExpansion / BlobExpansionConfig / ExpansionFormat types manage blob expansion. They need cluster 5.5 or later. For how to set it up and what it does, see blob expansion in the VAST Event Broker docs.
TableMetadata and TableRef
TableRef(bucket: str, schema: str, table: str) names a table.
| Member | Value |
|---|---|
full_path / str(ref) |
"<bucket>/<schema>/<table>" |
query_engine_full_path |
'"<bucket>/<schema>".<table>', the form used in VAST Query Engine SQL |
TableMetadata(ref, arrow_schema=None, table_type=None, vector_index=None, partitions_metadata=None) holds a table's schema, type and stats, and you can keep it between transactions.
| Member | Description |
|---|---|
load(tx) |
Loads the schema, table type and stats. |
load_schema(tx) / load_stats(tx) |
Loads only the schema or only the stats. |
arrow_schema, stats, ref, table_type, partitioning, sorted_columns |
Cached values. table_type and sorted_columns raise ValueError if they haven't been loaded. |
TableType values are Regular, Elysium (a sorted table), TableImports, Partitioned, and SortedPartitions (a table that is both partitioned and sorted). A vector-indexed table reports Regular.
Reusing metadata across transactions
schema.table() makes several round trips each time you call it: it lists the table, loads the schema and loads the stats. For repeated work, load the metadata once and rebuild the table handle in each transaction:
from vastdb import TableRef
from vastdb.table_metadata import TableMetadata
md = TableMetadata(TableRef("<bucket>", "<schema>", "<table>"))
with session.transaction() as tx:
md.load(tx)
with session.transaction() as tx:
t = tx.table_from_metadata(md)
t.insert(batch)
If you already know the schema, pass arrow_schema= to skip loading it. That's enough for insert(), but select() also needs the table type, which only load(tx) sets. Without it, select() raises ValueError: TableType was not loaded.... Operations that don't need the schema, such as import_files(), work on a TableMetadata that has only a TableRef.
Buffered inserts (partitioned tables)
from concurrent.futures import ThreadPoolExecutor
from vastdb import BufferedInsertConfig, TableRef
with ThreadPoolExecutor(max_workers=8) as pool:
with session.buffered_inserter(TableRef("<bucket>", "<schema>", "<table>"), pool) as ins:
for batch in batches:
ins.add_batch(batch)
# leaving the inner block calls finish(): flushes what's left and waits for all writes
The inserter groups rows by partition and flushes each group through executor. Its methods are add_batch(batch) and finish(), and it can be used as a context manager. Calling add_batch() after finish() raises AssertionError. If the table isn't partitioned, you get ValueError("Table must be partitioned to use a buffered inserter").
Transactions: by default, each flush commits in its own transaction, so a failure part-way through leaves the earlier flushes committed. To make the whole load one transaction, pass BufferedInsertConfig(tx=...). See BufferedInsertConfig.
PartitionSpec and PartitionKey
from pyiceberg.transforms import BucketTransform, DayTransform, IdentityTransform
from vastdb import PartitionKey, PartitionSpec
spec = PartitionSpec([
PartitionKey("event_time", DayTransform()),
PartitionKey("event_type", IdentityTransform()),
])
PartitionKey(column: str, transform) takes a PyIceberg transform. PartitionSpec(partition_keys: list[PartitionKey]) allows at most 4 keys. More than that raises ValueError("A partitioned table can be partitioned on no more than 4 keys"). Partitioned tables need cluster 5.5 or later.
Configuration
All the configuration classes are in vastdb.config.
QueryConfig
| Field | Default | Description |
|---|---|---|
num_splits |
None (estimated) |
Number of disjoint row ranges, queried concurrently with separate requests. |
num_sub_splits |
4 |
Concurrent reads within a single request, run on the server. |
rows_per_split |
4_000_000 |
Used to estimate num_splits from the row count. |
limit_rows_per_sub_split |
131072 |
Rows returned per sub-split per response page. |
num_row_groups_per_sub_split |
8 |
Row groups each sub-split reads before moving on. The SDK notes that semi-sorted projections need the value 8. |
data_endpoints |
None |
Endpoints to spread the query across, one worker thread each. You may list an endpoint more than once. An empty list raises ValueError. |
use_semi_sorted_projections |
True |
Whether queries may use semi-sorted projections. |
semi_sorted_projection_name |
None |
Forces a particular projection. Needs cluster 5.1 or later. A forced projection returns only the rows it has already sorted, so recent rows can be missing; don't force projections in production queries. |
queue_priority |
None |
A non-negative integer. Lower values are served first. If unset, the request joins the end of the queue. |
query_id |
"" |
Used in worker thread names, which helps with debugging. |
ImportConfig
| Field | Default | Description |
|---|---|---|
import_concurrency |
None (2 × number of CNodes) |
Number of parallel import workers. |
data_endpoints |
None |
Endpoints to spread the import across. An empty list raises ValueError. |
key_names |
None |
Import key column names. If set, the import matches the file's rows to existing table rows on these columns and writes the file's other columns into them. Needs cluster 5.3.1 or later. See ingest. |
BufferedInsertConfig
A frozen dataclass.
| Field | Default | Description |
|---|---|---|
capacity_in_bytes |
10485760 (10 MiB) |
Maximum amount of data held in the buffer. |
target_num_rows_per_partition |
65536 |
Rows a partition must collect before it's flushed. When the buffer is full, or the timer fires, flushes happen anyway. |
timeout |
None |
A timedelta. When it elapses, everything is flushed. It can't be combined with tx. |
tx |
None |
One transaction for the whole load. It must already be active. If unset, each flush uses its own transaction. |
data_endpoints |
[] |
Endpoints to send flushes to. If empty, the session's endpoints are used. |
Invalid values raise ValueError. That covers non-positive sizes or timeouts, a tx that isn't active, and tx combined with timeout.
BackoffConfig
| Field | Default | Description |
|---|---|---|
wait_gen |
backoff.expo |
Wait generator from the backoff library. |
max_value |
None |
Upper limit on a single wait, in seconds. |
max_tries |
10 |
Maximum number of attempts. |
max_time |
60.0 |
Maximum total retry time, in seconds. |
backoff_log_level |
logging.DEBUG |
Log level for retry messages. |
SessionConfig
A frozen dataclass with one field, data_endpoints: list[str] | None = None. It sets the session-wide default data endpoints for queries and imports. An empty list raises ValueError.
Retries
The SDK retries a request automatically, following BackoffConfig, only in these cases:
errors.Slowdown: the cluster is throttling requests.errors.ConnectionErrorwithmay_retry=True: a connection failure on a read-only request, or partway through reading a query response. Queries resume from where they stopped.
Everything else goes straight to your code, including connection failures on write requests. The transaction context manager then rolls back. Whether to retry the whole transaction is your decision; see transactions in practice.
Version-gated features
The SDK checks the connected cluster's version before it uses these features. If the cluster is too old, it raises errors.NotSupportedVersion and no request is sent.
| Feature | SDK entry point | Minimum cluster |
|---|---|---|
| User-defined row IDs | create_table(use_external_row_ids_allocation=True) |
5.1 |
| Forcing a semi-sorted projection | QueryConfig.semi_sorted_projection_name |
5.1 |
| Imports table | create_imports_table(), imports_table() |
5.2 |
| Adding a sort key | add_sorting_key() |
5.3 |
| Import key columns | ImportConfig.key_names |
5.3.1 |
| Partitioned tables | Session.buffered_inserter() |
5.5 |
| Blob expansion | create_blob_expansion(), blob_expansion() |
5.5 |
| Vector-indexed tables | loading a table that has a vector index | 5.5 |
For the full SDK × cluster × connector matrix, see versions.
Exceptions
All SDK exceptions are in vastdb.errors. These are the main families:
| Family | Raised when |
|---|---|
HttpError subclasses (BadRequest, Forbidden, NotFound, Conflict, ServiceUnavailable/Slowdown, InternalServerError, …) |
The cluster returned an error. Each one carries code, message, method, url, status and headers. |
Missing* (MissingBucket, MissingSchema, MissingTable, MissingProjection, MissingSnapshot, MissingTransaction, MissingRowIdColumn) |
An object or required input doesn't exist. |
Exists (SchemaExists, TableExists) |
A create call used fail_if_exists=True and the object already exists. |
InvalidArgument (TooLargeRequest, TooWideRow) |
The SDK rejected the input. TooLargeRequest is raised before anything is sent. TooWideRow can be raised partway through an insert, after some of the row's columns have been written; see ingest. |
NotSupported* (NotSupportedVersion, NotSupportedSchema, NotSupportedCommand) |
The cluster version, a column type or the table kind doesn't support the operation. |
ConnectionError |
A network failure. may_retry says whether it was safe to retry. |
Error reference lists every error message with its cause and fix.
Deprecated in 2.1.0
These will be removed in vastdb 3.0.0:
| Deprecated | Use instead |
|---|---|
reload_sorted_columns() |
Nothing. Sorted columns now load with the schema. |
TableInTransaction.insert_in_column_batches(rows) |
insert(rows) |