Skip to content

Commit 0471fa8

Browse files
committed
docs: update SQL syntax and refine content across all doc sections
Revise OIDC provider DDL to use keyword-based syntax (ISSUER, JWKS_URI, AUDIENCE, CLAIM MAPPING WHEN) instead of WITH (...) object notation, aligning the reference docs with the implemented SQL grammar. Add IF NOT EXISTS / IF EXISTS variants for CREATE INDEX and DROP INDEX, document GRANT SELECT on COLLECTION/TABLE, and improve DROP DATABASE FORCE/CASCADE descriptions in the DDL reference. Expand and correct content in storage-engines (vector, graph, array, timeseries), connectivity (pgwire, sync-protocol), crdt-sync (overview, conflict-policies, constraint-validation), administration (auth, RBAC, monitoring, multi-tenancy), and temporal/bitemporal pages.
1 parent e08bc05 commit 0471fa8

20 files changed

Lines changed: 323 additions & 213 deletions

docs/administration/authentication.rdx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,41 @@ NodeDB supports multiple authentication methods simultaneously.
1111

1212
```sql
1313
CREATE USER alice WITH PASSWORD 'strong_password';
14+
CREATE USER IF NOT EXISTS alice WITH PASSWORD 'strong_password';
1415
CREATE USER bob WITH PASSWORD 'secret' ROLE readonly;
16+
DROP USER alice;
17+
DROP USER IF EXISTS alice;
1518
```
1619

20+
`CREATE USER ... IF NOT EXISTS` and `DROP USER ... IF EXISTS` make user DDL idempotent.
21+
1722
```bash
1823
psql -h localhost -p 6432 -U alice
1924
```
2025

2126
## API Keys
2227

23-
### Basic API Key Usage
28+
API keys enable programmatic access for services and applications:
2429

2530
```sql
26-
CREATE API KEY 'my-service' ROLE readwrite;
27-
DROP API KEY 'my-service';
28-
```
29-
30-
```bash
31-
curl -H "Authorization: Bearer <api-key>" http://localhost:6480/v1/query
31+
CREATE API KEY FOR alice [EXPIRES <seconds>] [WITH SCOPES '<scope>', ...] [WITH DATABASES (<db1>, <db2>)];
32+
LIST API KEYS FOR alice; -- or SHOW API KEYS FOR alice
33+
REVOKE API KEY <key_id>;
3234
```
3335

34-
### Database Scoping
36+
- `FOR <user>` is mandatory
37+
- Key is shown once; store it securely
38+
- `EXPIRES` is seconds until revocation (optional)
39+
- `WITH SCOPES` restricts operations (e.g., 'read:collections', 'write:data')
40+
- `WITH DATABASES` restricts collection access (optional; empty = user's default databases)
3541

36-
API keys can be scoped to specific databases, restricting the key's access:
42+
Example:
3743

38-
```sql
39-
CREATE API KEY 'web-app' FOR user alice WITH DATABASES (prod_orders, prod_users);
40-
CREATE API KEY 'analytics' FOR user bob WITH DATABASES (analytics_db);
41-
42-
SHOW API KEYS; -- displays accessible_databases for each key
44+
```bash
45+
curl -H "Authorization: Bearer <api-key>" http://localhost:6480/v1/query \
46+
-d '{"sql": "SELECT 1"}'
4347
```
4448

45-
An API key with an empty database list inherits the owner's accessible databases at bind time. A non-empty list is an explicit restriction — the key can only access the listed databases.
46-
4749
### Service Accounts
4850

4951
Service accounts are privileged accounts designed for application-to-database connections:
@@ -52,7 +54,7 @@ Service accounts are privileged accounts designed for application-to-database co
5254
CREATE SERVICE ACCOUNT etl_worker FOR DATABASE analytics_db;
5355
ALTER SERVICE ACCOUNT etl_worker SET DATABASES (analytics_db, staging_db);
5456

55-
CREATE API KEY 'etl-key' FOR SERVICE ACCOUNT etl_worker;
57+
CREATE API KEY FOR etl_worker WITH DATABASES (analytics_db);
5658
```
5759

5860
Service accounts are scoped to a single tenant (inherited from the caller) and support per-database access control, inheriting or narrowing their scope when API keys are created on them.

docs/administration/monitoring.rdx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ curl http://localhost:6480/health/live # liveness probe
4343
curl http://localhost:6480/health/ready # WAL recovered, ready for queries
4444
```
4545

46+
## Graph Statistics
47+
48+
For per-collection edge counts and graph cardinality, use `SHOW GRAPH STATS`:
49+
50+
```sql
51+
SHOW GRAPH STATS 'collection_name' VERBOSE;
52+
SHOW GRAPH STATS 'collection_name' AS OF SYSTEM TIME <ms>;
53+
```
54+
55+
See [Graph Engine](../storage-engines/graph) for complete `SHOW GRAPH STATS` documentation.
56+
4657
## Key Metrics
4758

4859
| Metric Category | Examples |
@@ -88,7 +99,7 @@ These metrics are emitted from the dispatch layer (qps), memory governor (memory
8899

89100
## Cross-shard transaction metrics
90101

91-
| Metric | Type | Description |
102+
| Metric | Type | Description |
92103
| ------ | ---- | ----------- |
93104
| `nodedb_sequencer_epochs_total` | counter | Epochs proposed by the sequencer |
94105
| `nodedb_sequencer_epoch_duration_ms` | histogram | Time to drain and propose each epoch |

docs/administration/multi-tenancy.rdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ CREATE TENANT acme;
2424
CREATE USER alice WITH PASSWORD 'secret' ROLE readwrite TENANT 42;
2525
```
2626

27+
### Superuser Session Tenant Switching
28+
29+
A superuser can switch the session tenant at runtime:
30+
31+
```sql
32+
SET TENANT = 'acme' | 1234 | DEFAULT;
33+
```
34+
35+
This changes which tenant's data is visible for subsequent queries in the session. Non-superusers cannot use `SET TENANT`.
36+
2737
## Quotas
2838

2939
```sql

docs/administration/oidc-sso.rdx

Lines changed: 37 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -18,46 +18,32 @@ NodeDB validates JWT bearer tokens against your OIDC provider's public key set (
1818
Register your OIDC provider's details:
1919

2020
```sql
21-
CREATE OIDC PROVIDER okta WITH (
22-
issuer = 'https://yourorgname.okta.com/',
23-
jwks_url = 'https://yourorgname.okta.com/.well-known/oauth2/default/v1/keys',
24-
audience = 'https://nodedb.example.com',
25-
claim_mapping = [
26-
{ claim_name = 'email', claim_value = null, effect = {
27-
default_database = 'self_service'
28-
} },
29-
{ claim_name = 'groups', claim_value = 'engineering', effect = {
30-
add_databases = ['engineering_prod'],
31-
add_roles = ['data_analyst']
32-
} }
33-
]
34-
);
21+
CREATE OIDC PROVIDER okta ISSUER 'https://yourorgname.okta.com/' JWKS_URI 'https://yourorgname.okta.com/.well-known/oauth2/default/v1/keys' AUDIENCE 'https://nodedb.example.com'
22+
CLAIM MAPPING
23+
WHEN email = null SET DEFAULT_DATABASE = 1
24+
WHEN groups = 'engineering' SET ADD DATABASES [2] ADD ROLES ['data_analyst'];
3525
```
3626

3727
**Fields:**
3828

39-
| Field | Required | Meaning |
40-
|-------|----------|---------|
41-
| `issuer` | Yes | Token issuer URL (e.g., https://accounts.google.com) |
42-
| `jwks_url` | Yes | Public key set URL for signature validation |
43-
| `audience` | Yes | Expected JWT `aud` claim; mismatches are rejected |
44-
| `claim_mapping` | Yes | Array of rules mapping JWT claims to NodeDB identity fields |
29+
**Syntax:**
30+
- `ISSUER '<url>'` — Token issuer URL (e.g., https://accounts.google.com)
31+
- `JWKS_URI '<url>'` — Public key set URL for signature validation
32+
- `AUDIENCE '<aud>'` — (optional) Expected JWT `aud` claim; mismatches are rejected
33+
- `CLAIM MAPPING WHEN ... [SET DEFAULT_DATABASE = <id>] [ADD DATABASES [...]] [ADD ROLES [...]]` — Rules mapping JWT claims to NodeDB identity
4534

4635
NodeDB fetches the JWKS once, caches it in memory, and refreshes it when:
4736
- A token arrives with an unknown key ID (`kid`)
4837
- The cache TTL expires (typically 24 hours)
49-
- You explicitly reload via `ALTER OIDC PROVIDER ... REFRESH JWKS`
5038

5139
## Claim Mapping
5240

53-
Claim mapping rules translate JWT claims into NodeDB identity fields and access grants. Each rule specifies:
41+
Claim mapping rules translate JWT claims into NodeDB identity fields and access grants. Each rule in the `CLAIM MAPPING` clause specifies:
5442

55-
- `claim_name` — JWT claim name to match (e.g., `email`, `groups`, `org_id`)
56-
- `claim_value` — (optional) specific value to match; if omitted, rule applies to all values
57-
- `effect` — what happens when the rule matches:
58-
- `default_database` — set as the user's default database for queries
59-
- `add_databases` — grant access to these databases
60-
- `add_roles` — assign these roles to the session
43+
- `WHEN <claim> = '<value>'` — JWT claim name and value to match (e.g., `WHEN email = 'alice@example.com'` or `WHEN groups = 'engineering'`)
44+
- `SET DEFAULT_DATABASE = <db_id>` — (optional) set as the user's default database (numeric ID)
45+
- `ADD DATABASES [<id>, ...]` — (optional) grant access to these databases (numeric IDs)
46+
- `ADD ROLES ['<role>', ...]` — (optional) assign these roles to the session (quoted role names)
6147

6248
**Example**: An Okta JWT like:
6349

@@ -73,18 +59,11 @@ Claim mapping rules translate JWT claims into NodeDB identity fields and access
7359
Maps to:
7460

7561
```sql
76-
claim_mapping = [
77-
{ claim_name = 'email', claim_value = null, effect = {
78-
default_database = 'analytics'
79-
} },
80-
{ claim_name = 'groups', claim_value = 'engineering', effect = {
81-
add_databases = ['engineering_db'],
82-
add_roles = ['data_analyst']
83-
} },
84-
{ claim_name = 'groups', claim_value = 'admins', effect = {
85-
add_roles = ['cluster_admin']
86-
} }
87-
]
62+
CREATE OIDC PROVIDER okta ISSUER 'https://yourorgname.okta.com/' JWKS_URI 'https://...' AUDIENCE 'api'
63+
CLAIM MAPPING
64+
WHEN email = 'alice@acme.com' SET DEFAULT_DATABASE = 2
65+
WHEN groups = 'engineering' ADD DATABASES [3] ADD ROLES ['data_analyst']
66+
WHEN groups = 'admins' ADD ROLES ['cluster_admin'];
8867
```
8968

9069
The session inherits all matching rules' effects. If multiple rules grant database access, the union is used.
@@ -96,26 +75,16 @@ The session inherits all matching rules' effects. If multiple rules grant databa
9675

9776
## Updating a Provider
9877

99-
Modify JWKS URL, audience, or claim mappings after creation:
78+
Replace the claim mapping rules after creation:
10079

10180
```sql
102-
ALTER OIDC PROVIDER okta SET (
103-
audience = 'https://api.example.com'
104-
);
105-
106-
ALTER OIDC PROVIDER okta SET (
107-
claim_mapping = [
108-
{ claim_name = 'email', claim_value = null, effect = {
109-
default_database = 'analytics'
110-
} },
111-
{ claim_name = 'dept_name', claim_value = 'sales', effect = {
112-
add_databases = ['sales_db'],
113-
add_roles = ['sales_role']
114-
} }
115-
]
116-
);
81+
ALTER OIDC PROVIDER okta SET CLAIM MAPPING
82+
WHEN email = 'alice@acme.com' SET DEFAULT_DATABASE = 2
83+
WHEN dept_name = 'sales' ADD DATABASES [5] ADD ROLES ['sales_role'];
11784
```
11885

86+
`ALTER OIDC PROVIDER ... SET CLAIM MAPPING` replaces the full rule set. To change the issuer, JWKS URI, or audience, drop and recreate the provider.
87+
11988
Changes take effect immediately for new logins. Existing sessions tied to the provider remain valid until token expiry.
12089

12190
## Listing Providers
@@ -126,23 +95,14 @@ See all configured OIDC providers:
12695
SHOW OIDC PROVIDERS;
12796
```
12897

129-
**Output columns:**
130-
131-
| Column | Meaning |
132-
|--------|---------|
133-
| `name` | Provider name (e.g., `okta`) |
134-
| `issuer` | Token issuer URL |
135-
| `audience` | Expected `aud` claim |
136-
| `jwks_url` | Public key set URL |
137-
| `claim_mapping` | Array of mapping rules (summary) |
138-
| `created_at` | Timestamp |
98+
**Output includes provider name, issuer, audience, JWKS URI, claim mapping rules, and creation timestamp.**
13999

140100
## Removing a Provider
141101

142102
Drop a provider:
143103

144104
```sql
145-
DROP OIDC PROVIDER okta;
105+
DROP OIDC PROVIDER IF EXISTS okta;
146106
```
147107

148108
Existing sessions using tokens from that provider are revoked at their next request.
@@ -167,19 +127,13 @@ NodeDB validates tokens in this order:
167127
### Step 1: Configure the Provider
168128

169129
```sql
170-
CREATE OIDC PROVIDER auth0 WITH (
171-
issuer = 'https://your-tenant.us.auth0.com/',
172-
jwks_url = 'https://your-tenant.us.auth0.com/.well-known/jwks.json',
173-
audience = 'https://api.example.com',
174-
claim_mapping = [
175-
{ claim_name = 'email', claim_value = null, effect = {
176-
default_database = 'production'
177-
} },
178-
{ claim_name = 'https://api.example.com/roles', claim_value = 'readwrite', effect = {
179-
add_roles = ['readwrite']
180-
} }
181-
]
182-
);
130+
CREATE OIDC PROVIDER auth0
131+
ISSUER 'https://your-tenant.us.auth0.com/'
132+
JWKS_URI 'https://your-tenant.us.auth0.com/.well-known/jwks.json'
133+
AUDIENCE 'https://api.example.com'
134+
CLAIM MAPPING
135+
WHEN email = null SET DEFAULT_DATABASE = 1
136+
WHEN https://api.example.com/roles = 'readwrite' ADD ROLES ['readwrite'];
183137
```
184138

185139
### Step 2: Get a Token from Auth0
@@ -204,14 +158,14 @@ curl -H "Authorization: Bearer eyJhbGciOi..." \
204158
-d '{"sql": "SELECT 1"}'
205159
```
206160

207-
Or via native protocol (e.g., with `nodedb-client`):
161+
Or via native protocol (e.g., with `nodedb-client`) on port 6433:
208162

209163
```rust
210164
let auth = AuthMethod::OidcBearer {
211165
token: "eyJhbGciOi...".to_string(),
212166
provider: "auth0".to_string(),
213167
};
214-
let session = nodedb_client::connect("localhost:6432", auth).await?;
168+
let session = nodedb_client::connect("localhost:6433", auth).await?;
215169
```
216170

217171
### Step 4: Query as the Authenticated User
@@ -301,7 +255,7 @@ Regular users cannot modify providers.
301255
## Troubleshooting
302256

303257
**Token rejected: invalid signature**
304-
- JWKS cache may be stale. Manually refresh: `ALTER OIDC PROVIDER <name> REFRESH JWKS`
258+
- JWKS cache may be stale; it will auto-refresh on next unknown key ID
305259
- Check that the issuer matches exactly (including trailing `/`)
306260

307261
**Token rejected: audience mismatch**

docs/administration/rbac.rdx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ description: Role-based access control with GRANT, REVOKE, and built-in role hie
1111
| -------------- | ------------------------------ |
1212
| `readonly` | SELECT on all collections |
1313
| `readwrite` | SELECT, INSERT, UPDATE, DELETE |
14-
| `admin` | All operations + DDL |
1514
| `tenant_admin` | Admin within a tenant |
15+
| `monitor` | Read metrics, health, audit; no data access |
1616
| `superuser` | Unrestricted (cross-tenant) |
1717
| `cluster_admin` | Cluster-wide admin operations without full superuser capabilities |
1818

@@ -78,19 +78,27 @@ Certain administrative DDL operations are gated by required roles. Attempting an
7878

7979
```sql
8080
CREATE ROLE analyst;
81+
CREATE ROLE IF NOT EXISTS analyst;
8182
CREATE ROLE data_engineer;
83+
DROP ROLE analyst;
84+
DROP ROLE IF EXISTS analyst;
8285
```
8386

87+
`CREATE ROLE ... IF NOT EXISTS` and `DROP ROLE ... IF EXISTS` make role DDL idempotent.
88+
8489
## Granting Permissions
8590

8691
```sql
87-
GRANT SELECT ON orders TO analyst;
88-
GRANT INSERT, UPDATE ON orders TO data_engineer;
92+
GRANT SELECT ON COLLECTION orders TO analyst;
93+
GRANT SELECT ON TABLE orders TO analyst;
94+
GRANT INSERT, UPDATE ON COLLECTION orders TO data_engineer;
8995
GRANT ALL ON orders TO admin;
9096
GRANT EXECUTE ON FUNCTION full_name TO analyst;
9197
GRANT BACKUP ON TENANT acme TO ops_user;
9298
```
9399

100+
`ON COLLECTION <name>` and `ON TABLE <name>` are explicit object-type keywords (default is collection if neither specified).
101+
94102
## Revoking
95103

96104
```sql

docs/connectivity/native-protocol.rdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Every native connection performs a versioned handshake before any opcode frame.
5353
| `magic` | 4 B | `NDBA` |
5454
| `proto_version` | u16 | Negotiated version (`max(proto_min_client, proto_min_server) ≤ v ≤ min(proto_max_client, proto_max_server)`) |
5555
| `capabilities` | u64 | Server-side capability bitmask |
56-
| `server_version` | length-prefixed UTF-8 | Build identifier (e.g. `"nodedb 0.1.0+abc123"`) |
56+
| `server_version` | length-prefixed UTF-8 | Build identifier (e.g. `"NodeDB/0.3.0"`) |
5757
| `limits` | `Limits` struct | Per-op caps the server enforces (see below) |
5858

5959
### `HelloErrorFrame`

docs/connectivity/pgwire.rdx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,40 @@ psql -h localhost -p 6432
2323
- Server-side cursors (SCROLL, BACKWARD, MOVE, WITH HOLD)
2424
- Temporary tables (session-scoped)
2525

26+
## Introspection & Administration
27+
28+
### SHOW Commands
29+
30+
```sql
31+
SHOW ROLES;
32+
SHOW STATS; -- alias: SHOW SERVER STATS
33+
SHOW METRICS;
34+
SHOW MEMORY;
35+
SHOW TENANTS WITH NAME <name>;
36+
```
37+
38+
`SHOW STATS` returns live query latencies, throughput, and resource usage. `SHOW METRICS` exports Prometheus-format metrics. `SHOW MEMORY` shows per-engine memory allocation and jemalloc stats.
39+
40+
### SET Commands
41+
42+
Session-level configuration:
43+
44+
```sql
45+
SET TENANT = '<name>' | <id> | DEFAULT; -- superuser only — switch session tenant
46+
```
47+
48+
### pg_catalog Compatibility
49+
50+
NodeDB exposes virtual `pg_catalog` tables for Postgres-client compatibility, BI tools, and ORM introspection:
51+
52+
- `pg_class` — Collections and materialized views
53+
- `pg_namespace` — Databases and schemas
54+
- `pg_attribute` — Collection columns and types
55+
- `pg_index` — Index metadata
56+
- `pg_type` — Data types
57+
58+
Tools like DBeaver, Tableau, and SQLAlchemy automatically query these tables. No special setup required.
59+
2660
## Compatible Clients
2761

2862
Any PostgreSQL client library: libpq, JDBC, psycopg2, node-postgres, SQLAlchemy, Prisma, Diesel, tokio-postgres, and more.

0 commit comments

Comments
 (0)