Skip to main content
TopK exposes a PostgreSQL wire protocol endpoint — any standard SQL client can connect and issue queries and writes against your collections.

Prerequisites

Setup

Connect using any PostgreSQL-compatible client — psql, psycopg2, node-postgres, tokio-postgres, and others.
Replace <region> with your selected region and <api-key> with your API key.
See available regions and get your API key.

Quick Start

Create a collection, insert documents, and run a semantic search with SQL:
The INDEX clause is a TopK extension to SQL that lets you declare search indexes inline with your column definitions.

Schemaless by default

TopK collections are schemaless by default. Documents can have any fields with any types — no schema declaration is required to store or query them. The only exception: fields you want to index must be declared, because index configuration (metric, index type, etc.) is attached to the field definition.
Column types are inferred from the schema, search function return values, and explicit casts. Columns that can’t be typed statically (e.g. unindexed fields with mixed values) fall back to JSON. Use the :: cast operator to force a specific wire type.
For example, to cast published_year to int4 and rating to float8, use:

SQL Reference

Table references

Statements use <table> as either a collection name or a collection plus partition:
The schema prefix is accepted but ignored — all collections live in a single namespace per project.

Partitions

Partitions can be specified with $ or the PARTITION keyword: Partition syntax applies to SELECT, INSERT, UPDATE, and DELETE only. CREATE TABLE and DROP TABLE name collections without partitions; partitions are created implicitly on first write.

SELECT

The primary query interface. Compute search scores in the projection, filter by metadata or text predicates in WHERE, and rank results by any scoring expression in ORDER BY.
OFFSET requires a LIMITOFFSET alone is not supported.

Projection

Each item in the select list must be one of:
  • A named column: SELECT title, rating
  • A struct subfield: SELECT metadata.publisher
  • An aliased expression: SELECT rating * 2 AS double_rating — expressions must have an AS alias
  • A search function: SELECT vector_distance(embedding, '[1,0,0,0]'::f32_vector) AS score
  • A wire-type cast: SELECT rating::float8 AS rating_f64
Wire-type casts (::int4, ::float8, ::text, …) control how the column is typed on the wire, which is useful when your driver infers the wrong type. Unaliased casts use the inner expression as the column name (SELECT title::text → column title). See Output type mapping for the full table.
COUNT(*) returns the number of matching documents. The result column is named _count unless aliased with AS.

WHERE filters

match(...) and match_tokens(...) search keyword-indexed text and can be ranked with bm25_score(). Combine text searches with AND / OR; add metadata filters with AND:
Text searches can be combined with each other using OR, and with metadata filters using AND.

Search Functions

TopK extends SQL with search-specific functions for scoring and filtering. Scoring functions compute relevance in SELECT; text search predicates filter documents in WHERE.
Scoring functions
Scoring functions compute a relevance score for each document. Call them in SELECT, give them an alias, then use that alias in ORDER BY to rank results. All scoring functions return the score as f32:
Text search predicates
Text search predicates filter documents in WHERE based on keyword matches. match(...) and match_tokens(...) enable BM25 scoring via bm25_score().
match_all(...) and match_any(...) are boolean-only predicates — they filter documents but do not affect relevance ranking:
Text searches can be combined with each other using OR, and with metadata filters using AND:
match(...) and match_tokens(...) cannot be combined with metadata conditions using OR. Use match_any(...) or match_all(...) for keyword predicates that need to appear in an OR alongside other conditions — they work in any logical expression but do not affect bm25_score().
Example queries

GROUP BY

Group rows by one or more key expressions and compute aggregations per group.
A GROUP BY key must be a bare identifier — either a real column (GROUP BY genre) or a SELECT-list alias for a computed expression:
Every item in the SELECT list of a GROUP BY query must be either a group key or an aggregate function call — arbitrary columns are not allowed. HAVING filters on the grouped/aggregated output and requires a GROUP BY clause:
GROUP BY ALL and ROLLUP / CUBE / GROUPING SETS modifiers are not supported.

INSERT

Upsert semantics: inserting a document with an existing _id replaces it. _id is required and must appear in the column list.
Scalars use plain literals. TopK-native values use ::topk_type casts or constructor calls — see Type System. Prefer casts in VALUES:

UPDATE

Updates one or more fields on existing documents. _id cannot be updated. A WHERE clause is required and must resolve to a set of document IDs. Value expressions in SET follow the same rules as INSERT VALUES.

DELETE

Deletes documents by ID or by filter expression. A WHERE clause is required unless the target is a partition, in which case the entire partition is dropped.

CREATE TABLE

Schema is defined once at collection creation. Indexes are declared inline on each column using a TopK-specific INDEX clause — standalone CREATE INDEX statements are not supported.
IF NOT EXISTS suppresses the error if the collection already exists.

Column types

NOT NULL marks a field as required. All columns are optional by default.

Index types

Example

DROP TABLE

Permanently deletes a collection and all of its documents and indexes. This operation is irreversible.
IF EXISTS suppresses the error if the collection does not exist.

information_schema

TopK exposes two information_schema virtual tables for inspecting collections and their schemas. Specify column names explicitly in the select list — SELECT * is not supported on virtual tables.

information_schema.tables

Returns one row per collection in the project. Use it to list collections or check whether a specific collection exists.
WHERE clauses are accepted but silently ignored — all collections are always returned.

information_schema.columns

Returns one row per declared field in a collection. Filter by collection name using WHERE table_name = '<name>'.
Additional AND clauses (e.g. AND table_schema = 'public') are accepted but silently ignored.
Data type mapping:

EXPLAIN

Returns the parsed Statement AST as a single plan TEXT column:

Session commands

SET/SHOW only recognize consistency_level; all other variable names return an error. The following commands are accepted and silently succeed:

PostgreSQL Compatibility

TopK speaks the PostgreSQL wire protocol but is purpose-built for search — not general-purpose relational queries. Some SQL features are intentionally absent because they don’t map to search semantics or would work against search performance.

Queries

Single ORDER BY key — Ranking is a single scoring expression. Combine multiple signals into one composite score rather than sorting by multiple columns: ORDER BY vec_dist * 0.7 + bm25_score() * 0.3 DESC. No SELECT * — Selecting all columns is not supported. Indexed vector fields cannot be projected directly — use search functions (vector_distance, semantic_similarity, …) to get scores from them instead. COUNT(*) without GROUP BY is a standalone aggregate — it returns a document count and cannot be combined with other columns in the same SELECT list. Inside a GROUP BY query, COUNT(*) behaves as an ordinary per-group aggregate and can be combined with other keys/aggregates — see GROUP BY. GROUP BY keys must be identifiers — group by a real column or a SELECT-list alias for a computed expression; you can’t group by an inline expression directly. GROUP BY ALL and ROLLUP/CUBE/GROUPING SETS are not supported. No whole-value equality on complex typestags = ARRAY['a'] is not supported. Use contains(tags, 'a') to check whether a single value exists in a list field — contains checks for one value at a time, not a list of values.

Writes

Upsert-only INSERTINSERT always replaces a document when _id already exists, so ON CONFLICT is built-in and not needed. INSERT … SELECT and RETURNING are not supported. ::topk_type casts in VALUES — Standard PostgreSQL casts (::float8, CAST(… AS text)) aren’t supported inside INSERT/UPDATE values. Use TopK-native casts (::f32_vector, ::f32_sparse_vector, …) instead — see Type System.

Transactions

BEGIN/COMMIT/ROLLBACK are accepted without error so that drivers that auto-wrap statements in transactions (psycopg2, SQLAlchemy, JDBC) connect without modification. Writes are not transactional; ROLLBACK does not undo changes.

Structs

Individual struct subfields can be selected using dot notation:
To retrieve multiple subfields, list each one explicitly — selecting a whole struct column is not supported:

Type System

TopK-native literals use ::topk_type casts (preferred in INSERT/UPDATE VALUES and search-function arguments) or equivalent constructor calls. PostgreSQL wire-type casts (::float8, …) apply only in SELECT projection.

Complex types → JSON

Vector, matrix, sparse, and list types are returned as JSON when selected. Indexed vector fields cannot be projected directly — use scoring functions to compute scores from them. The table below documents the wire format for each type:

Output type mapping

pgwire maps SELECT-list expressions to PostgreSQL OIDs. Explicit ::cast in the projection list overrides inference. Casts are stripped from the query plan — they only affect the wire type.