SQLx Support for Kinetica via PostgreSQL Wire Protocol
This module provides helpers for using SQLx with Kinetica’s PostgreSQL wire protocol support.
Overview
Kinetica supports the PostgreSQL wire protocol on port 5432, which means you can use SQLx’s PostgreSQL driver directly. This module provides:
KineticaPool- A type alias forPgPoolwith Kinetica-specific configurationKineticaPoolOptions- Builder for creating connection pools with sensible defaultsconnect,connect_with_urlandconnect_with_options- Convenience functions for establishing connectionsKineticaText,KineticaTimestamp,KineticaBytes,KineticaJson,KineticaVectorandKineticaArray- for the Kinetica types the protocol does not carry as PostgreSQL does
Quick Start
Type Mappings
Kinetica’s PostgreSQL protocol carries its types as below, as measured against Kinetica 7.2. Where the PostgreSQL type does not carry the value whole, read and write it through the wrappers this module provides (see KineticaText and its siblings).
| Kinetica type | Sent as | Read as | Bind as |
|---|---|---|---|
BOOLEAN | BOOL | bool | KineticaText(bool) |
TINYINT, SMALLINT, INTEGER | INT4 | i32 | KineticaText(i32) |
BIGINT | INT8 | i64 | KineticaText(i64) |
UNSIGNED BIGINT | TEXT | KineticaText<u64> | KineticaText(u64) |
REAL | FLOAT4 | f32 | KineticaText(f32) |
DOUBLE | FLOAT8 | f64 | KineticaText(f64) |
DECIMAL(p,s) | NUMERIC | a decimal type, with SQLx’s bigdecimal or rust_decimal feature; or CAST(col AS VARCHAR) as KineticaText<String> | KineticaText of its text |
VARCHAR, VARCHAR(n) | TEXT | String | KineticaText(&str) |
IPV4 | TEXT | KineticaText<Ipv4Addr> | KineticaText(Ipv4Addr) |
UUID | TEXT | KineticaText<uuid::Uuid> | KineticaText(uuid::Uuid) |
DATE | DATE | chrono::NaiveDate | KineticaText(NaiveDate) |
TIME | TEXT | KineticaText<NaiveTime> | KineticaText(NaiveTime) |
DATETIME | TIMESTAMP, without its milliseconds | CAST(col AS VARCHAR) as KineticaText<NaiveDateTime> | KineticaText(NaiveDateTime) |
TIMESTAMP | INT8: milliseconds since the epoch | KineticaTimestamp | KineticaTimestamp |
BLOB | BYTEA | Vec<u8> or KineticaBytes | KineticaBytes |
GEOMETRY, BLOB(WKT) | none: the query fails | ST_ASTEXT(col) as String | KineticaText of its WKT |
JSON | JSON | serde_json::Value or KineticaJson<T> | KineticaJson(value) |
VECTOR(n) | TEXT, e.g. [1,2.5,3] | KineticaVector | KineticaVector in a plain INSERT; KineticaText(vector.as_slice()) elsewhere |
arrays, e.g. INTEGER[3] | PostgreSQL arrays numbered from 0 | KineticaArray<T> | KineticaArray<T> |
This crate enables SQLx’s chrono and json features, so NaiveDate and serde_json::Value read as they are. Nulls read as Option<_> of any of these, and bind as None.
What goes wrong without the wrappers:
- SQLx’s
Vec<T>refuses Kinetica’s arrays, which are numbered from 0. - A
DATETIMEread as aNaiveDateTime, or bound as one, loses its milliseconds. - Kinetica refuses
uuid::Uuid,NaiveTime, decimal andVec<u8>(BYTEA) parameters. - SQLx binds
serde_json::Valueandsqlx::types::JsonasJSONB, which Kinetica refuses as invalid JSON. - A plain
INSERTrefuses a vector as text, and everything else refuses one as raw floats.
Parameters
Kinetica reads every parameter after a statement’s first as text, whatever type SQLx sends it as. A second integer, float or date parameter therefore fails to convert in an INSERT. In a WHERE clause it matches nothing, so an UPDATE or DELETE reports success and changes no rows. A literal beside such a parameter can be stored as NULL. Text parameters work in every position, and Kinetica converts each to its column’s type, so bind every parameter through a wrapper, which sends text:
JSON, Arrays and Vectors
Each is inserted, updated, filtered on and deleted by with ordinary SQL, binding the wrappers above. Measured against Kinetica 7.2:
- JSON. Bind documents with
KineticaJson, which takes anySerializevalue; read them asKineticaJson<T>for anyDeserializeOwnedT.JSON_VALUE(col, $1)</code> (whose path may be a parameter), <code>JSON\_QUERY</code>, <code>JSON\_EXISTS</code>, <code>JSON\_LENGTH</code>, <code>JSON\_TYPE</code>, <code>IS\_JSON</code>, <code>JSON\_OBJECT</code> and <code>JSON\_ARRAY</code> work, with nested paths in <code>GROUP BY</code> and <code>JOIN</code> too. A SQL <code>NULL</code> and the JSON <code>null</code> stay distinct. Declare JSON columns as <code>JSON</code>: <code>VARCHAR(json)</code> and <code>STRING(json)</code> are plain strings on the server.</li> <li><strong>Full-text search.</strong> A <code>JSON(text\_search)</code> column, on a server with <code>enable\_text\_search</code>, is searched by key with <code>text\_match(col, $1)(bool) and ranked withtext_match_bm25ortext_match_bm25_global(f64), the query bound asKineticaText(“customer.tier:Enterprise”). Do not UPDATE such a column: on Kinetica 7.2.3.21 that crashes a worker rank. Delete the row and insert it again. - Arrays. Bind with
KineticaArray, and useKineticaArray<Option<T>>for arrays holding nulls.ARRAY_CONTAINS,ARRAY_CONTAINS_ANY,ARRAY_CONTAINS_ALL,ARRAY_LENGTH,col[i](numbered from 1),ARRAY_APPEND,ARRAY_CONCAT,ARRAY_SLICEandARRAY_TO_STRINGwork; arrays they compute arrive as JSON text, whichKineticaArrayreads too. A numericARRAY_CONTAINS(col, $1)</code> finds nothing whatever is bound: write <code>ARRAY\_CONTAINS\_ANY(col, $1)with a one-elementKineticaArray. A fixed size, as inINTEGER[3], is not enforced, and an infinite float is refused. - Vectors. A plain
INSERT … VALUES (n,VECTOR(n, dims)</code> and function arguments take <code>KineticaText(vector.as\_slice())</code>. <code>L1\_DISTANCE</code>, <code>L2\_DISTANCE</code>, <code>COSINE\_DISTANCE</code>, <code>DOT\_PRODUCT</code> and <code>L2\_NORM</code> return <code>f32</code>, as does <code>v <-> CAST(1 AS VECTOR(n)); a barev <-> $1is refused.
Read arrays through sqlx::query: in the text form sqlx::raw_sql returns, Kinetica writes null in capitals wherever it occurs in a string element.
Compile-Time Checked Queries
SQLx supports compile-time checked queries with the query! macro. However, this requires a live database connection during compilation. For Kinetica, you can:
- Use the offline mode with
sqlx prepare - Use runtime queries with
sqlx::query()
Transactions
Kinetica’s PostgreSQL protocol does not support transactions: pool.begin() fails (“got unexpected connection status after attempting to begin transaction”), and each statement applies when it runs.
Connection Pooling Best Practices
For optimal performance with Kinetica:
- Set
max_connectionsbased on your workload (default: 10) - Use
min_connectionsfor warm connection pools - Set appropriate timeouts for your network latency
The server serves one session per thread, up to its postgres_proxy.max_threads (64 by default), shared by every client. It ends a session when its client sends PostgreSQL’s Terminate message, which pool.close() sends. A connection closed without it, as when a process exits or a task panics with the connection open, keeps its session until the server’s postgres_proxy.idle_connection_timeout (300 seconds by default). Pools that outgrow the server’s sessions wait, then fail with sqlx::Error::PoolTimedOut. SQLx 0.8’s pool.close() can return while a connection on its way back to the pool is still open: before a program ends, close again once pool.size() shows it is back (see SQLX_API.md).
Limitations
Besides the types and parameters above:
- Transactions are not supported.
- Sessions are limited by the server, and outlive connections that are not closed (see above).
- PostgreSQL-specific features, such as
LISTEN/NOTIFY, are not available. - Kinetica-specific SQL extensions should be written as raw SQL strings.
For full fidelity on every Kinetica type, and for Kinetica-specific operations (bulk insert, type management, KiFS), use the native API of the Kinetica client. It carries each value with its column’s properties.
Structs
BulkInserter<GenericRecord> for high-performance shard-aware ingestion.Vec<T> refuses; this reads them, in the binary form a query’s results come in and in the text form sqlx::raw_sql gives, and writes them as the JSON text Kinetica takes. It also reads the JSON text Kinetica sends for an array a function computes, such as ARRAY_APPEND(col, 4).BLOB column’s value. Kinetica refuses a BYTEA parameter, but takes the bytes of a text parameter as they are, so this sends them under the text type; it reads the BYTEA Kinetica sends.JSON column’s value: anything serde serializes, bound directly as JSON, and read back from the JSON Kinetica sends.TIMESTAMP column’s value: milliseconds since the Unix epoch, which Kinetica sends as BIGINT and takes as text.VECTOR(n) column’s value. Kinetica sends it as text, [1,2.5,3], and reads it from a parameter in two ways, depending on where it is:Enums
Constants
pub const DEFAULT_DATABASE: &str = “postgres”;Default database name (PostgreSQL compatibility).
pub const DEFAULT_PORT: u16 = 5432;Default PostgreSQL wire protocol port for Kinetica.
Traits
KineticaText.KineticaArray.PgPool with Kinetica-specific helpers.KineticaText.Functions
PgConnectOptions.