Compatibility Matrix
DB9 implements a PostgreSQL-compatible SQL engine over TiKV distributed storage. Most PostgreSQL clients, ORMs, and drivers work without changes. This page documents where DB9 matches PostgreSQL, where it diverges, and what is not available.
Use this matrix when evaluating DB9 for a new project or migrating an existing PostgreSQL application.
Summary
Section titled “Summary”| Category | Coverage | Notes |
|---|---|---|
| SQL DML (SELECT, INSERT, UPDATE, DELETE) | Full | JOINs, CTEs, window functions, subqueries, upsert, RETURNING |
| SQL DDL (CREATE, ALTER, DROP) | Near-full | No partitioning, table inheritance, or foreign data wrappers |
| Data types | 20+ types | All common types incl. INET; no XML, CIDR/MACADDR; range types limited to a partial INT4RANGE |
| Indexes | B-tree, GIN and HNSW | GiST/Hash/SP-GiST/BRIN are rejected |
| Transactions | Full | READ COMMITTED and REPEATABLE READ enforced; SERIALIZABLE is downgraded to REPEATABLE READ |
| Built-in functions | 200+ | String, math, date/time, JSON/JSONB, array, aggregate, window, FTS |
| Wire protocol | pgwire v3 | Simple Query, Extended Query, COPY, prepared statements |
| ORM compatibility | 99%+ | Prisma, Drizzle, Sequelize, Knex, TypeORM, GORM, SQLAlchemy tested |
| System catalogs | 50+ views | pg_catalog, information_schema, cron schema |
| Extensions | 9 built-in | http, fs9, pg_cron, vector, embedding, uuid-ossp, hstore, parquet, zhparser (plus pgcrypto and plpgsql as metadata-only shims) |
| PL/pgSQL | Partial | Basics supported, including CONTINUE and dynamic EXECUTE; exception handling and nested blocks require a DO block; no WHILE / FOREACH loops or cursors |
| Replication | None | No logical or streaming replication |
DML and Queries
Section titled “DML and Queries”| Feature | Status |
|---|---|
| SELECT with FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT/OFFSET | Supported |
| DISTINCT / DISTINCT ON | Supported |
| JOINs (INNER, LEFT, RIGHT, FULL OUTER, CROSS) | Supported |
| Subqueries (correlated, scalar, EXISTS, IN) | Supported, including inside a LATERAL body |
| CTEs (WITH … AS) | Supported, including a WITH inside a LATERAL body and a name reused across scopes of one statement. A duplicate name within one WITH list is a different case: PostgreSQL rejects it, DB9 accepts it and silently resolves to the last definition — see DML |
| Recursive CTEs (WITH RECURSIVE) | Supported, up to 1,000 iterations |
Data-modifying CTEs (WITH w AS (INSERT ... RETURNING ...)) | Supported over the PostgreSQL wire protocol. Over the HTTP SQL API the data-modifying CTE must be the first definition in the WITH list, otherwise the connection is dropped; see the caution below |
| Set operations (UNION, INTERSECT, EXCEPT) | Supported |
| Window functions (ROW_NUMBER, RANK, LAG, LEAD, etc.) | Supported |
| INSERT with VALUES, SELECT, DEFAULT, RETURNING | Supported |
| INSERT ON CONFLICT (upsert) | Supported |
| UPDATE with WHERE, subqueries, RETURNING | Supported |
| DELETE with WHERE, subqueries, RETURNING | Supported |
| CASE expressions | Supported |
Row-constructor comparisons ((a,b) = (c,d)) | Supported — =, <>, <, <=, > and >= follow PostgreSQL’s NULL rules. Two forms do not: a row tested against a literal list with IN / NOT IN returns true/false where the result should be unknown, so a filter can return rows PostgreSQL excludes; and IS [NOT] DISTINCT FROM raises 42883 when a row holds a bare NULL literal opposite a typed value or column. See the caution below |
| COPY (CSV, TEXT) | Supported for the COPY <table> [(columns)] form (BINARY format not supported). The query form COPY (SELECT ...) TO STDOUT is rejected with 0A000 |
| EXPLAIN / EXPLAIN ANALYZE | Supported (ANALYZE adds summary runtime stats — actual rows, execution time, KV counters — but no per-operator timing; FORMAT JSON is honored). Plan rows are returned over pgwire only — the HTTP SQL API returns an empty result |
| LATERAL joins | Supported, including correlated bodies, a WITH clause inside the body, and correlated subqueries inside the body |
| Feature | Status |
|---|---|
| CREATE/ALTER/DROP TABLE | Supported |
| CREATE TABLE AS / SELECT INTO | Supported — but the new table gets an extra _rowid primary key column that is visible in SELECT *, see DDL |
| Column constraints (PRIMARY KEY, UNIQUE, NOT NULL, DEFAULT, CHECK) | Supported |
| Foreign keys (CASCADE, SET NULL, SET DEFAULT, RESTRICT, NO ACTION) | Supported — all five actions behave as declared, 23503 on violation, and TRUNCATE of a referenced table is refused — but dropping the referenced table bypasses the check entirely, see the caution below |
| Generated columns | Supported |
| SERIAL / BIGSERIAL | Supported |
| CREATE/DROP INDEX (B-tree, GIN, HNSW) | Supported |
| CREATE INDEX CONCURRENTLY | Supported |
| Expression indexes, partial indexes | Supported |
| CREATE/DROP VIEW | Supported |
| CREATE/DROP MATERIALIZED VIEW, REFRESH MATERIALIZED VIEW | Supported |
| CREATE/DROP SEQUENCE | Supported |
| ALTER SEQUENCE | Partial (only OWNER TO / OWNED BY; all other clauses — RESTART, INCREMENT BY, RENAME TO, SET SCHEMA, … — raise 0A000. Use SETVAL() to reposition) |
| DROP SEQUENCE dependency checks | Not supported (a sequence used by a column default can be dropped, breaking the table) |
| CREATE/ALTER TYPE (enum) | Supported, with gaps — enum values compare and sort by label text rather than declaration order (silently), see Advanced SQL — Custom Types. ALTER TYPE supports only the enum-value forms and RENAME TO, and DROP TYPE does not cascade and does not check array-column or return-type dependents, see DDL — Other DDL |
| CREATE/DROP SCHEMA | Supported |
| CREATE/DROP FUNCTION (PL/pgSQL) | Supported |
| CREATE/DROP TRIGGER (BEFORE/AFTER, INSERT/UPDATE/DELETE) | Supported, with gaps — a BEFORE trigger function may not use loops, CASE statements, EXECUTE, exception handlers or nested blocks (CREATE TRIGGER refuses it — 0A000, or 42601 for WHILE and FOREACH), and FOR EACH STATEMENT runs once per row, see Advanced SQL — Triggers |
| TRUNCATE | TRUNCATE t / TRUNCATE TABLE t only — the multi-table form and every optional clause (CASCADE, RESTRICT, ONLY, RESTART/CONTINUE IDENTITY) are rejected (42601). A table that another table’s foreign key references cannot be truncated (0A000), as in PostgreSQL, see the caution below |
| Row-Level Security (ENABLE/DISABLE/FORCE RLS) | Supported |
| CREATE/ALTER/DROP POLICY | Supported |
| Table partitioning (RANGE, LIST, HASH) | Not supported |
| Table inheritance | Not supported |
| Foreign data wrappers (FDW) | Not supported |
| Tablespaces | Not supported (TiKV manages storage placement) |
Transactions
Section titled “Transactions”| Feature | Status |
|---|---|
| BEGIN / COMMIT / ROLLBACK | Supported |
| SAVEPOINT / RELEASE / ROLLBACK TO | Supported |
| READ COMMITTED isolation | Supported — statement-level snapshots, as in PostgreSQL. Default level. |
| REPEATABLE READ isolation | Supported — transaction-level snapshot |
| SERIALIZABLE isolation | Downgraded to REPEATABLE READ, see note below |
| READ UNCOMMITTED isolation | Supported — behaves as READ COMMITTED, as in PostgreSQL |
| SET LOCAL (transaction-scoped settings) | Supported |
| READ ONLY transactions | Supported — DML and DDL writes are rejected with SQLSTATE 25006 |
| DEFERRABLE transactions | Not supported (requires SERIALIZABLE) |
Advisory locks (pg_advisory_lock family) | Supported (node-local; not coordinated across multiple db9-server processes) |
Data Types
Section titled “Data Types”Supported
Section titled “Supported”| Type | Aliases | Notes |
|---|---|---|
| BOOLEAN | BOOL | |
| SMALLINT | INT2 | Stored as INT4 internally |
| INTEGER | INT, INT4 | |
| BIGINT | INT8 | |
| REAL | FLOAT4 | Stored as FLOAT8 internally |
| DOUBLE PRECISION | FLOAT8 | |
| NUMERIC | DECIMAL | With precision and scale, up to precision 1000 |
| TEXT | Variable-length, no limit | |
| VARCHAR(n) | CHARACTER VARYING | |
| CHAR(n) | CHARACTER | Stored as VARCHAR internally |
| BYTEA | Binary data | |
| DATE | ||
| TIME | Without time zone | |
| TIMESTAMP | Without time zone, microsecond precision | |
| TIMESTAMPTZ | With time zone, microsecond precision | |
| INTERVAL | ||
| JSON | Stored as text | |
| JSONB | Canonicalized (sorted keys, normalized whitespace) | |
| UUID | ||
| INET | IPv4/IPv6 host or network address; equality and ordering only — no network operators/functions, no CIDR | |
| BOOLEAN[] / INT[] / TEXT[] / etc. | 1-dimensional arrays of any supported type | |
| TSVECTOR | Full-text search document representation | |
| TSQUERY | Full-text search query | |
| VECTOR(n) | pgvector-compatible; for HNSW indexes and distance operators | |
| NAME | PostgreSQL identifier type |
Not Supported
Section titled “Not Supported”| Type | Notes |
|---|---|
| XML | |
| CIDR / MACADDR | Network address types; INET is supported (equality/ordering only) |
| Range types (INT4RANGE, TSRANGE, etc.) | INT4RANGE is accepted but only partly implemented — see the caution below. INT8RANGE, NUMRANGE, DATERANGE, TSRANGE, and TSTZRANGE do not exist |
| Composite types (user-defined row types) | Declarable and writable via a text literal, but unreadable field-wise — see the caution below. Enum types are supported via CREATE TYPE, but sort by label text rather than declaration order, see Advanced SQL — Custom Types |
| Large objects (OID-based streaming) | BYTEA is available for binary data |
| Multi-dimensional arrays | Only 1-dimensional arrays; an INT[][] column is catalogued as text[] and rejects every INSERT (42846) |
| Money | |
| Bit string (BIT, VARBIT) | Accepted: BIT(n) and VARBIT store and return bit strings, but an over-length value is silently truncated instead of raising; BIT(1) becomes BOOLEAN. Both BIT VARYING spellings are rejected (42601) — see the caution below |
Indexes
Section titled “Indexes”| Type | Status | Notes |
|---|---|---|
| B-tree | Full | Default. Point, range, bounded-range, in-list, expression, partial indexes |
| GIN | Full | JSONB containment (@>), full-text search (@@), array operators. Only TSVECTOR, JSONB and array columns are indexable — any other column type fails with a bare XX000, see below |
| HNSW | Near-full | Approximate k-NN over VECTOR columns. Builds on both transports and the planner uses it — but only for an inlined literal probe with a LIMIT and no WHERE clause: an inline embedding(...) call, a driver-bound parameter, or any filter falls back to an exact scan. The indexed column must declare a width (VECTOR(1024), not bare VECTOR) and the table needs a single-column primary key — see the note below |
| GiST | Not supported | CREATE INDEX is rejected |
| Hash | Not supported | CREATE INDEX is rejected |
| SP-GiST | Not supported | CREATE INDEX is rejected |
| BRIN | Not supported | CREATE INDEX is rejected |
btree, gin, and hnsw are the only usable access methods, and pg_am lists exactly those
three plus heap. An unusable method is always rejected at CREATE INDEX time — never
accepted-then-ignored — but it comes back as one of two different errors, and which one tells
you whether the method is merely unsupported or entirely unknown:
-- gist, hash, spgist, brin — named and rejectedERROR: access method "gist" is not supported (0A000)HINT: Only btree, gin, and hnsw indexes are currently supported.
-- anything else, including ivfflat — not a known name at allERROR: access method "ivfflat" does not exist (42704)So GiST, Hash, SP-GiST and BRIN give 0A000 with the HINT above, while every other
spelling gives 42704 — despite none of the four appearing in pg_am. IVFFlat falls in the
second group: HNSW is the only vector index type.
A GIN index can only be built over a column type that has a GIN operator class — TSVECTOR,
JSONB, and array types such as TEXT[] or INT[]. On any other column type the statement
fails with a bare internal error, which does not say that the type is the problem:
CREATE INDEX ON docs USING gin (body); -- body is TEXTERROR: internal error (XX000)PostgreSQL reports 42704 data type text has no default operator class for access method "gin"
here, and DB9’s own hnsw path produces that clearer error, so the bare XX000 is specific to
GIN. If you hit it, check the column’s type first — it is not a sign that GIN is unavailable.
There is no pg_trgm, so GIN cannot accelerate LIKE on a TEXT column; use full-text search
over a TSVECTOR column instead.
HNSW indexes carry extra structural requirements: the indexed column must declare its width
(VECTOR(1024), not bare VECTOR), the table needs a single-column primary key (no-PK and
composite-PK tables fail with a bare XX000), and an INTEGER/BIGINT primary key must hold only
non-negative values (a negative one at build time gives 22023). UUID and TEXT primary keys
are fine. Partial (WHERE) and multi-column HNSW indexes are rejected. Building over a bare
VECTOR column that holds rows drops the connection rather than returning an error — see
Vector Search.
Queries fall back to an exact sequential scan when no usable index exists, so all distance operators
(<->, <=>, <#>) and functions return correct results either way. See
pgvector for details.
GIN indexes on JSONB columns are fully functional for containment queries (@>). Queries using @> on GIN-indexed columns use index scans instead of sequential scans.
Wire Protocol
Section titled “Wire Protocol”| Feature | Status |
|---|---|
| pgwire v3 | Supported |
| Simple Query (text) | Supported |
| Extended Query (Parse, Bind, Describe, Execute) | Supported |
| Binary parameter encoding | Supported |
| COPY (CSV, TEXT) | Supported for the COPY <table> [(columns)] form (BINARY format not supported); COPY (SELECT ...) TO STDOUT is rejected |
| Prepared statements | Supported |
| Portals | Supported |
| Multiple result sets | Supported |
| SCRAM-SHA-256 authentication | Supported (at pgwire layer) |
| LISTEN / NOTIFY | Supported (notifications are delivered on commit to sessions holding an open connection; LISTEN requires a persistent pgwire session, so the stateless HTTP SQL API can send NOTIFY but cannot receive) |
| Logical replication protocol | Not supported |
| Streaming replication | Not supported |
Functions
Section titled “Functions”DB9 implements 200+ built-in functions across these categories:
| Category | Examples |
|---|---|
| String | upper, lower, concat, substring, replace, trim, split_part, regexp_match, format |
| Math | abs, ceil, floor, round, sqrt, power, log, random, trunc, trig functions |
| Date/Time | now, date_trunc, date_part, extract, age, to_char, to_timestamp, make_date |
| Aggregate | count, sum, avg, min, max, string_agg, array_agg, json_agg, bool_and/or |
| Window | row_number, rank, dense_rank, lag, lead, first_value, last_value, ntile |
| JSON/JSONB | jsonb_build_object, jsonb_set, jsonb_extract_path, jsonb_array_elements, jsonb_each, jsonb_typeof, to_jsonb, row_to_json |
| Array | array_length, array_agg, unnest, array_append, array_cat, array_position, string_to_array |
| Full-text search | to_tsvector, to_tsquery, plainto_tsquery, ts_rank, ts_headline, setweight |
| UUID | uuid_generate_v4 and related functions |
| Type conversion | cast, to_char, to_number, to_date, to_timestamp |
| Conditional | coalesce, nullif, greatest, least |
| HTTP (scalar) | http_get, http_post, http_put, http_delete returning JSONB |
| Document chunking | CHUNK_TEXT — table-valued function for RAG pipelines |
| Storage | db9_refresh_storage_stats — trigger storage scan |
| System | current_user, current_database, current_schema, pg_typeof, version |
JSON/JSONB Operators
Section titled “JSON/JSONB Operators”| Operator | Description | Status |
|---|---|---|
-> | Get JSON object field by key (returns JSON) | Supported |
->> | Get JSON object field by key (returns text) | Supported |
#> | Get element by path (returns JSON) | Supported |
#>> | Get element by path (returns text) | Supported |
@> | Contains | Supported |
<@ | Contained by | Supported |
? | Key exists | Supported |
?| | Any key exists | Supported |
?& | All keys exist | Supported |
|| | Concatenate | Supported |
- | Delete key | Supported |
#- | Delete path | Supported |
PL/pgSQL
Section titled “PL/pgSQL”| Feature | Status |
|---|---|
| DECLARE / BEGIN / END blocks | Supported |
Variable assignment (:=) | Supported |
| SELECT INTO | Supported |
| IF / THEN / ELSIF / ELSE / END IF | Supported |
| FOR loops (query iteration and integer range) | Supported |
| PERFORM (execute without result) | Supported |
EXIT (loop termination, plain and EXIT WHEN <condition>) | Supported |
CONTINUE (plain and CONTINUE WHEN <condition>) | Supported |
| RAISE (NOTICE, WARNING, ERROR) | Supported |
| RETURN / RETURN NEXT / RETURN QUERY | Supported |
| RETURNS TABLE syntax | Supported |
| CASE statements (in PL/pgSQL) | Supported |
| WHILE loops | Not supported |
| FOREACH … IN ARRAY | Not supported — 42601 (syntax error: sql parser error: Expected an SQL statement, found: FOREACH) |
| Cursor operations (FOR…IN CURSOR) | Not supported |
| Exception handling (BEGIN…EXCEPTION) | DO blocks only |
Dynamic SQL (EXECUTE, including INTO and USING) | Supported, in both a CREATE FUNCTION body and a DO block |
| Nested BEGIN…END blocks | DO blocks only |
PL/pgSQL host differences
Section titled “PL/pgSQL host differences”A DO block is a more capable PL/pgSQL host than a CREATE FUNCTION body. Exception
handling and nested blocks run inside DO but are rejected in a function body with 0A000
(... requires a Session-owned interactive DO host).
| Feature | CREATE FUNCTION body | DO block |
|---|---|---|
| CASE statements | Supported | Supported |
Exception handling (BEGIN ... EXCEPTION) | 0A000 | Supported |
Dynamic SQL (EXECUTE, including INTO and USING) | Supported | Supported |
Nested BEGIN ... END blocks | 0A000 | Supported |
| WHILE and FOREACH loops | 42601 | 42601 |
| Cursor operations | 42601 / 42704 | 42601 / 42704 |
EXECUTE takes a literal, a variable, or an expression as its command string, and INTO and
USING behave as in PostgreSQL — EXECUTE 'SELECT $1 * 2' INTO n USING p works in both hosts.
The 0A000, WHILE and FOREACH errors are raised when the function is called;
CREATE FUNCTION itself succeeds. Cursors fail earlier, when the cursor is declared — c CURSOR FOR ... with
42601 (unexpected token in PL/pgSQL type declaration: FOR) and c refcursor with 42704
(type "refcursor" does not exist) — so a function that declares one is never created.
Exception handlers in a DO block follow PostgreSQL rollback semantics: statements that
ran before the exception are rolled back, and only the handler’s effects persist.
Multi-line DECLARE entries parse correctly in both hosts: a declaration whose type or value
spans several lines yields the same value as PostgreSQL (v int := 1 / + 2; is 3, and
v int / := 42; is 42).
Other procedural languages (PL/Python, PL/Perl, PL/v8) are not supported.
ORM and Driver Compatibility
Section titled “ORM and Driver Compatibility”DB9 is tested against major ORMs with a combined pass rate above 99%:
| ORM / Driver | Tested Version | Pass Rate | Notes |
|---|---|---|---|
| Prisma | 5.7+ | 100% (89/89) | Binary wire protocol; $queryRaw for advanced SQL |
| Drizzle | 0.29+ | 100% (75/75) | Type-safe queries; full query builder support |
| Sequelize | 6.35+ | 100% (87/87) | Raw queries for window/CTE features |
| Knex.js | 3.1+ | 100% (97/97) | Full query builder, window functions, CTEs |
| TypeORM | 0.3.17+ | 98% (147/150) | 3 tests skipped (schema introspection edge cases) |
| node-postgres (pg) | 8.11+ | Full | Native pgwire client |
| SQLAlchemy | 2.0+ | Tested | JSONB operators, RETURNING, transaction patterns |
| GORM (Go) | 1.25+ | Tested | CRUD, transactions, foreign keys |
Known ORM limitations:
- Schema introspection queries may return incomplete results for some ORMs that rely heavily on
information_schema - Some ORMs assume PostgreSQL-specific system functions that are not yet implemented
System Catalogs
Section titled “System Catalogs”DB9 implements 50+ virtual tables across pg_catalog, information_schema, and extension schemas.
pg_catalog
Section titled “pg_catalog”| View | Status | Notes |
|---|---|---|
| pg_class | Supported | Relations (tables, views, indexes, sequences) |
| pg_attribute | Supported | Column definitions |
| pg_index | Supported | Index metadata |
| pg_constraint | Supported | Constraints (PK, FK, UNIQUE, CHECK). Every scan of this view fails with XX000 while a dangling foreign key exists — see the DDL caution |
| pg_type | Supported | Data types |
| pg_proc | Supported | Functions and procedures |
| pg_namespace | Supported | Schemas |
| pg_roles / pg_user | Supported | Users and roles |
| pg_database | Supported | Database metadata |
| pg_sequence | Supported | Sequence state |
| pg_attrdef | Supported | Column defaults |
| pg_extension | Supported | Installed extensions |
| pg_am | Supported | Access methods |
| pg_trigger | Supported | Trigger definitions — no tgtype column; read the declared timing and row/statement level from information_schema.triggers |
| pg_depend / pg_description | Supported | Object dependencies and comments |
| pg_settings | Supported | Server and db9.* runtime parameters; category is always NULL and short_desc is sparse |
| pg_stat_user_tables | Stub | Returns rows but statistics columns are zeros |
| pg_stat_statements | Not available | |
| pg_publication | Writable, but inert | A bare CREATE PUBLICATION p; succeeds on both transports and the row persists here — but nothing is replicated. FOR/WITH clauses are rejected with 0A000. See the caution below |
| pg_publication_rel | Writable, but inert | ALTER PUBLICATION ... ADD/DROP/SET TABLE records membership here and it persists — but nothing is replicated |
| pg_publication_namespace | Stub (empty) | ALTER PUBLICATION ... ADD TABLES IN SCHEMA is rejected with 0A000, so this is never populated |
| pg_replication_slots | Stub (empty) | 19 columns, including conflicting plus the PostgreSQL 17 additions failover/synced (two_phase_at and inactive_since are absent). No slot can be created — pg_create_logical_replication_slot() exists (in the FROM clause only) but ends at 55000 logical replication is not provisioned for this database. See the caution below |
| pg_subscription | Not available | CREATE SUBSCRIPTION is not parsed at all |
information_schema
Section titled “information_schema”| View | Status |
|---|---|
| columns | Supported |
| tables | Supported |
| schemata | Supported |
| table_constraints | Supported |
| key_column_usage | Supported |
| referential_constraints | Supported |
| check_constraints | Supported |
| constraint_column_usage | Supported |
| table_privileges | Supported |
| sequences | Supported |
| routines | Supported |
| triggers | Supported |
| views | Not available |
information_schema.views is not implemented — querying it fails with relation "views" does not exist (42P01). To list views, use pg_views, or filter information_schema.tables on table_type:
SELECT viewname FROM pg_views WHERE schemaname = 'public';
-- Portable alternative:SELECT table_name FROM information_schema.tablesWHERE table_schema = 'public' AND table_type = 'VIEW';The twelve Supported views above are the complete information_schema surface in DB9.
Extensions
Section titled “Extensions”DB9 includes 11 built-in extension descriptors — 9 functional extensions plus pgcrypto and plpgsql, which are metadata-only shims that register no functions. Custom or third-party extensions cannot be installed.
| Extension | Version | Default | Description |
|---|---|---|---|
| http | 1.0.0 | Yes | HTTP client (GET, POST, PUT, DELETE, PATCH, HEAD) |
| pg_cron | 1.0.0 | Yes | Job scheduler with cron expressions. Scheduling is disabled in the current release — cron.schedule() returns feature "cron" is unavailable (PreActivationSeal) (55000). Catalog views and cron.unschedule / cron.cancel resolve. See pg_cron |
| fs9 | 1.0.0 | No | File system operations (read, write, list, glob) |
| vector | 0.8.1 | No | pgvector-compatible vector type and HNSW indexes |
| embedding | 1.0.0 | No | Built-in text embedding generation |
| uuid-ossp | 1.1 | No | UUID generation functions (functions are built-in; extension is a metadata shim) |
| hstore | 1.0 | No | Key-value store type (metadata shim with limited semantics) |
| parquet | 1.0.0 | No | Parquet file import |
| zhparser | 2.0.0 | No | Chinese full-text search tokenizer |
| pgcrypto | 1.3 | No | Metadata shim only — registers no functions (see below) |
| plpgsql | 1.0 | No | Metadata shim; PL/pgSQL itself is compiled in |
CREATE EXTENSION pgcrypto is accepted so Supabase and ORM bootstrap scripts run unchanged, but it provides no functions. crypt(), gen_salt(), hmac(), encrypt()/decrypt(), pgp_sym_encrypt() and gen_random_bytes() all fail with 42883. gen_random_uuid() and digest() work, but they are DB9 built-ins and need no extension.
Extensions not available: PostGIS, pg_partman, pg_stat_statements, pg_trgm, ltree, citext, and all other PostgreSQL contrib extensions. These raise 42704 extension is not available at CREATE EXTENSION time.
Not Supported
Section titled “Not Supported”These PostgreSQL features are not available in DB9:
| Feature | Category |
|---|---|
| Table partitioning (RANGE, LIST, HASH) | DDL |
| Table inheritance | DDL |
| Foreign data wrappers (FDW) | DDL |
| Tablespaces | DDL |
| Rules (CREATE RULE) | DDL |
Logical replication (CREATE SUBSCRIPTION is unparsed; a bare CREATE PUBLICATION is accepted but inert — see pg_publication above) | Replication |
| Streaming replication | Replication |
| SERIALIZABLE isolation (true serializable) | Transactions |
| DEFERRABLE transactions | Transactions |
| Large objects (OID-based) | Data |
| XML type | Data |
| Network types (CIDR, MACADDR) | Data |
Range types (except a partial INT4RANGE) | Data |
| PL/Python, PL/Perl, PL/v8 | Languages |
PL/pgSQL WHILE and FOREACH loops, and cursors | Languages |
| Custom extensions | Extensions |
pg_dump run against a DB9 database, in any format (use db9 db dump, or COPY over pgwire) | Tools |
| pg_basebackup | Tools |
Validation
Section titled “Validation”You can verify compatibility for your specific use case:
-- Check supported typesSELECT typname FROM pg_type WHERE typnamespace = 11 ORDER BY typname;
-- Check available extensionsSELECT * FROM pg_extension;
-- Check installed functions ('f' = function, 'p' = procedure, 'w' = window).-- Note: pg_proc lists only catalog-registered functions. Many built-ins (SUM,-- GENERATE_SERIES, UNNEST, ...) run in the executor and never appear here — see /docs/sql/catalog/SELECT proname, prokind FROM pg_proc WHERE pronamespace = 11 ORDER BY proname;
-- Check catalog coverage. pg_tables lists user schemas only, so query pg_class-- to enumerate the pg_catalog relations:SELECT n.nspname AS schema, c.relname AS catalog_tableFROM pg_class cJOIN pg_namespace n ON n.oid = c.relnamespaceWHERE n.nspname = 'pg_catalog'ORDER BY c.relname;
-- Verify transaction isolationSHOW transaction_isolation; -- returns 'read committed' (the default)
-- SERIALIZABLE is not implemented. On the PostgreSQL wire protocol it is accepted-- with a warning and downgraded; on the HTTP SQL API it returns an error:SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;-- WARNING: TiKV provides snapshot isolation; SERIALIZABLE has been downgraded to REPEATABLE READNext Pages
Section titled “Next Pages”- SQL Reference — detailed SQL syntax and function reference
- Architecture — how DB9’s SQL engine connects to TiKV storage
- Extensions — guide to all 9 built-in extensions
- Limits and Quotas — operational limits and safety boundaries
- Production Checklist — evaluate compatibility gaps before going live
- Migrate from Neon — step-by-step Neon to DB9 migration
- Migrate from PostgreSQL — general PostgreSQL migration path