Skip to content

Commit 75e0e60

Browse files
committed
Document PG wire format
1 parent 351af50 commit 75e0e60

File tree

3 files changed

+231
-0
lines changed

3 files changed

+231
-0
lines changed

docs/docs/nav.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const nav = {
6868
page('Unreal Reference', 'unreal/reference', 'unreal/reference.md'),
6969
section('SQL'),
7070
page('SQL Reference', 'sql', 'sql/index.md'),
71+
page('PostgreSQL wire protocol (PGWire) ', 'sql/pg-wire', 'sql/pg-wire.md'),
7172
section('Subscriptions'),
7273
page('Subscription Reference', 'subscriptions', 'subscriptions/index.md'),
7374
page(

docs/docs/sql/pg-wire.md

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# PostgreSQL Wire Protocol (PGWire) Compatibility
2+
3+
*SpacetimeDB* supports the [PostgreSQL wire protocol (](https://www.postgresql.org/docs/current/protocol.html)[
4+
*PGWire*](https://www.postgresql.org/docs/current/protocol.html)[)](https://www.postgresql.org/docs/current/protocol.html),
5+
enabling compatibility with PostgreSQL clients and tools.
6+
7+
The PostgreSQL wire protocol is a network protocol used by PostgreSQL clients to communicate with compatible servers. It
8+
defines how messages are formatted and exchanged between client and server. The protocol is agnostic to the query
9+
dialect,
10+
meaning it can be used with different SQL engines and feature sets, in concrete, *SpacetimeDB*.
11+
12+
This allows users to leverage the existing PostgreSQL ecosystem, including drivers, ORMs, IDEs, CLI tools, and GUI
13+
clients that support PostgreSQL.
14+
15+
When using *PGWire* with *SpacetimeDB*, consider the following:
16+
17+
## Feature Support
18+
19+
SpacetimeDB is progressively adding PostgreSQL client compatibility. Some features are unsupported, partially
20+
implemented, or behave differently:
21+
22+
- **Protocol Version**: Only *PGWire* protocol *version 3.0* is supported, and only the *Simple Query Protocol*, and
23+
without parameterized queries.
24+
- **SQL Features**: Only the subset of SQL features documented in the [SQL documentation](index.md) are
25+
supported. Subscription queries do not update in real time.
26+
- **Authentication**: SpacetimeDB does not implement database users or roles. The connection string
27+
`user_name@database_name` ignores `user_name`; only `database_name` is used. Authentication is based on the *auth
28+
token*
29+
provided via the `password` field.
30+
- **SSL/TLS**: SSL is supported only for `maincloud` deployments (without mutual TLS). Other deployments (such as
31+
`standalone`) do not support SSL/TLS connections.
32+
- **System Tables and Views**: SpacetimeDB provides its own system tables (e.g., `SELECT * FROM st_table`) for
33+
introspection. These are not PostgreSQL-compatible, so tools relying on PostgreSQL system catalogs will not work.
34+
- **Port and Host**:
35+
- In `standalone` deployments, specify the port with `spacetime start --pg-port <port>`. Without this flag,
36+
connections using the PostgreSQL protocol are not enabled.
37+
- In `maincloud` deployments, the port is always `5432`.
38+
- **Transactions**: User-defined transactions (`BEGIN TRANSACTION`, `COMMIT`, etc.) are not supported. Each SQL
39+
statement executes in its own transaction context. Client libraries should disable automatic transaction handling.
40+
41+
## Connection Parameters
42+
43+
To connect to SpacetimeDB using a PostgreSQL client, use the following parameters:
44+
45+
- **Host**:
46+
- `localhost` for `standalone` or `local` deployments
47+
- `maincloud.spacetimedb.com` for `maincloud` deployments
48+
- **Port**:
49+
- `5432` for `maincloud`
50+
- The value passed with `--pg-port` for `standalone`
51+
- **Database**: The target SpacetimeDB database
52+
- **User**: Any string (ignored by SpacetimeDB)
53+
- **Password**: The `auth token`
54+
- **SSL Mode**: `require` (only for `maincloud`)
55+
56+
### Auth Token
57+
58+
> **Warning**: The `auth token` is sensitive. Do not expose it in logs, version control, or insecure locations.
59+
60+
SpacetimeDB uses the `password` field to pass the `auth token`. Obtain the token with:
61+
62+
```bash
63+
spacetime login show --token
64+
```
65+
66+
To export the token to `PGPASSWORD`:
67+
68+
```python
69+
# token.py
70+
import sys, re
71+
72+
text = sys.stdin.read()
73+
match = re.search(r"Your auth token \(don't share this!\) is\s+(\S+)", text)
74+
if not match:
75+
sys.exit("No token found")
76+
77+
print(f"export PGPASSWORD={match.group(1)}")
78+
```
79+
80+
```bash
81+
eval "$(spacetime login show --token | python3 ~/token.py)"
82+
```
83+
84+
## Examples
85+
86+
Assume you are using the `quickstart-chat` database created in
87+
the [Rust Module Quickstart](/docs/modules/rust/quickstart) or [C# Module Quickstart](/docs/modules/c-sharp/quickstart),
88+
and have set the `auth token` as shown above.
89+
90+
### Using `psql`
91+
92+
Standalone or local deployment:
93+
94+
```bash
95+
psql "host=localhost port=5432 user=any dbname=quickstart-chat"
96+
```
97+
98+
Maincloud deployment:
99+
100+
```bash
101+
psql "host=maincloud.spacetimedb.com port=5432 user=any dbname=quickstart-chat sslmode=require"
102+
```
103+
104+
> **Note**: Introspection commands such as `\dt` will not work, as SpacetimeDB does not support PostgreSQL schemas.
105+
106+
### Using Python (`psycopg2`)
107+
108+
```python
109+
import psycopg2
110+
import os
111+
112+
conn = psycopg2.connect(
113+
host="localhost", # or "maincloud.spacetimedb.com" for maincloud
114+
port=5432,
115+
dbname="quickstart-chat",
116+
user="any",
117+
password=os.getenv("PGPASSWORD"),
118+
sslmode="disable" # use "require" for maincloud
119+
)
120+
conn.set_session(autocommit=True) # disable transactions
121+
122+
print("Running query:")
123+
with conn.cursor() as cur:
124+
cur.execute("SELECT * FROM message;")
125+
for row in cur.fetchall():
126+
print(row)
127+
128+
conn.close()
129+
print("Done.")
130+
```
131+
132+
### Using Rust (`tokio-postgres` + `rustls`)
133+
134+
We use the `tokio-postgres-rustls` because is stricter, so we can show how disables certificate verification.
135+
136+
```toml
137+
# Cargo.toml
138+
[dependencies]
139+
anyhow = "1.0.71"
140+
tokio-postgres = "0.7.14"
141+
tokio-postgres-rustls = "0.13.0"
142+
tokio = { version = "1.47.1", features = ["full"] }
143+
rustls = "0.23.32"
144+
```
145+
146+
```rust
147+
// main.rs
148+
use std::env;
149+
use std::sync::Arc;
150+
use rustls::client::danger::{ServerCertVerified, ServerCertVerifier};
151+
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
152+
use rustls::{ClientConfig, Error, RootCertStore, SignatureScheme};
153+
use tokio_postgres_rustls::MakeRustlsConnect;
154+
155+
#[derive(Debug)]
156+
struct NoVerifier;
157+
158+
impl ServerCertVerifier for NoVerifier {
159+
fn verify_server_cert(
160+
&self,
161+
_: &CertificateDer<'_>,
162+
_: &[CertificateDer<'_>],
163+
_: &ServerName<'_>,
164+
_: &[u8],
165+
_: UnixTime,
166+
) -> Result<ServerCertVerified, Error> {
167+
Ok(ServerCertVerified::assertion())
168+
}
169+
170+
fn verify_tls12_signature(
171+
&self,
172+
_: &[u8],
173+
_: &CertificateDer<'_>,
174+
_: &rustls::DigitallySignedStruct,
175+
) -> Result<rustls::client::danger::HandshakeSignatureValid, Error> {
176+
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
177+
}
178+
179+
fn verify_tls13_signature(
180+
&self,
181+
_: &[u8],
182+
_: &CertificateDer<'_>,
183+
_: &rustls::DigitallySignedStruct,
184+
) -> Result<rustls::client::danger::HandshakeSignatureValid, Error> {
185+
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
186+
}
187+
188+
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
189+
vec![
190+
SignatureScheme::ECDSA_NISTP384_SHA384,
191+
SignatureScheme::ECDSA_NISTP256_SHA256,
192+
SignatureScheme::RSA_PSS_SHA512,
193+
SignatureScheme::RSA_PSS_SHA384,
194+
SignatureScheme::RSA_PSS_SHA256,
195+
SignatureScheme::ED25519,
196+
]
197+
}
198+
}
199+
200+
#[tokio::main]
201+
async fn main() -> Result<(), anyhow::Error> {
202+
let password = env::var("PGPASSWORD").expect("PGPASSWORD not set");
203+
204+
let mut config = ClientConfig::builder()
205+
.with_root_certificates(RootCertStore::empty())
206+
.with_no_client_auth();
207+
208+
config.dangerous().set_certificate_verifier(Arc::new(NoVerifier));
209+
let connector = MakeRustlsConnect::new(config);
210+
211+
let (client, connection) = tokio_postgres::connect(
212+
&format!(
213+
"host=localhost port=5432 user=any sslmode=require dbname=quickstart-chat password={password}"
214+
),
215+
connector,
216+
).await?;
217+
218+
tokio::spawn(async move { connection.await.expect("connection error") });
219+
220+
println!("Running query:");
221+
let rows = client.simple_query("SELECT * FROM message;").await?;
222+
for row in rows {
223+
println!("Row: {:?}", row);
224+
}
225+
println!("Done.");
226+
Ok(())
227+
}
228+
```
229+

docs/nav.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ const nav: Nav = {
9898

9999
section('SQL'),
100100
page('SQL Reference', 'sql', 'sql/index.md'),
101+
page('PostgreSQL wire protocol (PGWire) ', 'sql/pg-wire', 'sql/pg-wire.md'),
101102

102103
section('Subscriptions'),
103104
page('Subscription Reference', 'subscriptions', 'subscriptions/index.md'),

0 commit comments

Comments
 (0)