gwp 0.2.1

A standalone, pure Rust gRPC wire protocol for GQL (ISO/IEC 39075)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
//! The pluggable backend trait for GQL database engines.
//!
//! Any GQL-compatible database implements `GqlBackend` to plug into
//! the wire protocol server. The trait covers session lifecycle,
//! statement execution, and transaction management.

use std::collections::HashMap;
use std::pin::Pin;

use crate::error::GqlError;
use crate::proto;
use crate::types::Value;

use super::auth::AuthInfo;

/// Opaque session identifier issued at handshake.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SessionHandle(pub String);

/// Opaque transaction identifier issued at begin.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TransactionHandle(pub String);

/// Configuration for a new session, derived from the handshake request.
#[derive(Debug, Clone)]
pub struct SessionConfig {
    /// Protocol version requested by the client.
    pub protocol_version: u32,
    /// Client metadata (driver name, version, platform).
    pub client_info: HashMap<String, String>,
    /// Identity from the authentication validator, if authentication was performed.
    pub auth_info: Option<AuthInfo>,
}

/// A session property to configure.
#[derive(Debug, Clone)]
pub enum SessionProperty {
    /// Set the current schema.
    Schema(String),
    /// Set the current graph.
    Graph(String),
    /// Set the session timezone (UTC offset in minutes).
    TimeZone(i32),
    /// Set a named session parameter.
    Parameter {
        /// Parameter name.
        name: String,
        /// Parameter value.
        value: Value,
    },
}

/// What to reset on a session.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResetTarget {
    /// Reset all session state to defaults.
    All,
    /// Reset current schema.
    Schema,
    /// Reset current graph.
    Graph,
    /// Reset timezone.
    TimeZone,
    /// Reset all parameters.
    Parameters,
}

/// A single frame in the result stream from executing a GQL statement.
#[derive(Debug, Clone)]
pub enum ResultFrame {
    /// Column metadata and result type. Always the first frame.
    Header(proto::ResultHeader),
    /// A batch of rows.
    Batch(proto::RowBatch),
    /// Completion status and statistics. Always the last frame.
    Summary(proto::ResultSummary),
}

/// Stream of result frames produced by statement execution.
///
/// Backends return a `ResultStream` from `execute()`. The server
/// converts each frame into a gRPC `ExecuteResponse` message.
pub trait ResultStream: Send + 'static {
    /// Get the next result frame.
    ///
    /// Returns `Ok(None)` when all frames have been delivered.
    fn poll_next(
        self: Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Option<Result<ResultFrame, GqlError>>>;
}

// ============================================================================
// Catalog types
// ============================================================================

/// Summary information about a schema.
#[derive(Debug, Clone)]
pub struct SchemaInfo {
    /// Schema name.
    pub name: String,
    /// Number of graphs in the schema.
    pub graph_count: u32,
    /// Number of graph types in the schema.
    pub graph_type_count: u32,
}

/// Summary information about a graph.
#[derive(Debug, Clone)]
pub struct GraphInfo {
    /// Schema this graph belongs to.
    pub schema: String,
    /// Graph name.
    pub name: String,
    /// Number of nodes in the graph.
    pub node_count: u64,
    /// Number of edges in the graph.
    pub edge_count: u64,
    /// Graph type name (empty if open).
    pub graph_type: String,
    /// Storage mode (e.g. `InMemory`, `Persistent`).
    pub storage_mode: String,
    /// Memory limit in bytes, if configured.
    pub memory_limit_bytes: Option<u64>,
    /// Whether backward edges are maintained.
    pub backward_edges: Option<bool>,
    /// Number of worker threads.
    pub threads: Option<u32>,
}

/// Graph type specification for creating a graph.
#[derive(Debug, Clone)]
pub enum GraphTypeSpec {
    /// Open graph type (ANY GRAPH).
    Open,
    /// Reference to a named graph type.
    Named(String),
}

/// Configuration for creating a new graph.
#[derive(Debug, Clone)]
pub struct CreateGraphConfig {
    /// Schema to create the graph in.
    pub schema: String,
    /// Graph name.
    pub name: String,
    /// Whether to skip if the graph already exists.
    pub if_not_exists: bool,
    /// Whether to replace an existing graph.
    pub or_replace: bool,
    /// Graph type specification.
    pub type_spec: Option<GraphTypeSpec>,
    /// Optional source graph to copy from (`schema.graph` qualified name).
    pub copy_of: Option<String>,
    /// Storage mode (e.g. `InMemory`, `Persistent`).
    pub storage_mode: String,
    /// Optional memory limit in bytes.
    pub memory_limit_bytes: Option<u64>,
    /// Whether to maintain backward edges.
    pub backward_edges: Option<bool>,
    /// Number of worker threads.
    pub threads: Option<u32>,
    /// Whether write-ahead logging is enabled.
    pub wal_enabled: Option<bool>,
    /// WAL durability mode.
    pub wal_durability: Option<String>,
}

/// Summary information about a graph type.
#[derive(Debug, Clone)]
pub struct GraphTypeInfo {
    /// Schema this graph type belongs to.
    pub schema: String,
    /// Graph type name.
    pub name: String,
}

/// The pluggable backend trait for GQL database engines.
///
/// Implement this trait to connect any GQL-compatible database to the
/// wire protocol server. The server handles gRPC transport, session
/// management, and protocol details - the backend focuses on executing
/// GQL statements and managing data.
#[tonic::async_trait]
pub trait GqlBackend: Send + Sync + 'static {
    /// Create a new session with the given configuration.
    ///
    /// Called during handshake. The backend should allocate any per-session
    /// resources and return a handle for subsequent calls.
    async fn create_session(&self, config: &SessionConfig) -> Result<SessionHandle, GqlError>;

    /// Close a session and release its resources.
    ///
    /// Called when the client explicitly closes the session or when
    /// the connection drops. Any active transaction should be rolled back.
    async fn close_session(&self, session: &SessionHandle) -> Result<(), GqlError>;

    /// Set a session property (schema, graph, timezone, or parameter).
    async fn configure_session(
        &self,
        session: &SessionHandle,
        property: SessionProperty,
    ) -> Result<(), GqlError>;

    /// Reset session state to defaults.
    async fn reset_session(
        &self,
        session: &SessionHandle,
        target: ResetTarget,
    ) -> Result<(), GqlError>;

    /// Execute a GQL statement and return a stream of result frames.
    ///
    /// The stream should emit frames in order: Header, then zero or more
    /// Batch frames, then Summary. The server converts these into
    /// streaming gRPC `ExecuteResponse` messages.
    async fn execute(
        &self,
        session: &SessionHandle,
        statement: &str,
        parameters: &HashMap<String, Value>,
        transaction: Option<&TransactionHandle>,
    ) -> Result<Pin<Box<dyn ResultStream>>, GqlError>;

    /// Begin an explicit transaction.
    ///
    /// Returns a transaction handle for use in subsequent `execute`,
    /// `commit`, and `rollback` calls.
    async fn begin_transaction(
        &self,
        session: &SessionHandle,
        mode: proto::TransactionMode,
    ) -> Result<TransactionHandle, GqlError>;

    /// Commit the transaction.
    async fn commit(
        &self,
        session: &SessionHandle,
        transaction: &TransactionHandle,
    ) -> Result<(), GqlError>;

    /// Roll back the transaction.
    async fn rollback(
        &self,
        session: &SessionHandle,
        transaction: &TransactionHandle,
    ) -> Result<(), GqlError>;

    // =========================================================================
    // Catalog operations (optional - sec 12)
    // =========================================================================

    /// List all schemas.
    async fn list_schemas(&self) -> Result<Vec<SchemaInfo>, GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    /// Create a schema.
    async fn create_schema(&self, _name: &str, _if_not_exists: bool) -> Result<(), GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    /// Drop a schema. Returns whether it existed.
    async fn drop_schema(&self, _name: &str, _if_exists: bool) -> Result<bool, GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    /// List all graphs in a schema.
    async fn list_graphs(&self, _schema: &str) -> Result<Vec<GraphInfo>, GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    /// Create a graph. Returns the new graph info.
    async fn create_graph(&self, _config: CreateGraphConfig) -> Result<GraphInfo, GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    /// Drop a graph. Returns whether it existed.
    async fn drop_graph(
        &self,
        _schema: &str,
        _name: &str,
        _if_exists: bool,
    ) -> Result<bool, GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    /// Get detailed information about a graph.
    async fn get_graph_info(&self, _schema: &str, _name: &str) -> Result<GraphInfo, GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    /// List graph types in a schema.
    async fn list_graph_types(&self, _schema: &str) -> Result<Vec<GraphTypeInfo>, GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    /// Create a graph type.
    async fn create_graph_type(
        &self,
        _schema: &str,
        _name: &str,
        _if_not_exists: bool,
        _or_replace: bool,
    ) -> Result<(), GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    /// Drop a graph type. Returns whether it existed.
    async fn drop_graph_type(
        &self,
        _schema: &str,
        _name: &str,
        _if_exists: bool,
    ) -> Result<bool, GqlError> {
        Err(GqlError::Protocol("catalog not supported".into()))
    }

    // =========================================================================
    // Admin operations (optional)
    // =========================================================================

    /// Get detailed graph statistics.
    async fn get_graph_stats(&self, _graph: &str) -> Result<AdminStats, GqlError> {
        Err(GqlError::Protocol("admin not supported".into()))
    }

    /// Get WAL status for a graph.
    async fn wal_status(&self, _graph: &str) -> Result<AdminWalStatus, GqlError> {
        Err(GqlError::Protocol("admin not supported".into()))
    }

    /// Force a WAL checkpoint on a graph.
    async fn wal_checkpoint(&self, _graph: &str) -> Result<(), GqlError> {
        Err(GqlError::Protocol("admin not supported".into()))
    }

    /// Validate graph integrity.
    async fn validate(&self, _graph: &str) -> Result<AdminValidationResult, GqlError> {
        Err(GqlError::Protocol("admin not supported".into()))
    }

    /// Create an index on a graph.
    async fn create_index(&self, _graph: &str, _index: IndexDefinition) -> Result<(), GqlError> {
        Err(GqlError::Protocol("admin not supported".into()))
    }

    /// Drop an index from a graph.
    async fn drop_index(&self, _graph: &str, _index: IndexDefinition) -> Result<bool, GqlError> {
        Err(GqlError::Protocol("admin not supported".into()))
    }

    // =========================================================================
    // Search operations (optional)
    // =========================================================================

    /// Vector similarity search (KNN).
    async fn vector_search(&self, _req: VectorSearchParams) -> Result<Vec<SearchHit>, GqlError> {
        Err(GqlError::Protocol("search not supported".into()))
    }

    /// Full-text search (BM25).
    async fn text_search(&self, _req: TextSearchParams) -> Result<Vec<SearchHit>, GqlError> {
        Err(GqlError::Protocol("search not supported".into()))
    }

    /// Hybrid search (vector + text with rank fusion).
    async fn hybrid_search(&self, _req: HybridSearchParams) -> Result<Vec<SearchHit>, GqlError> {
        Err(GqlError::Protocol("search not supported".into()))
    }
}

// ============================================================================
// Admin types
// ============================================================================

/// Detailed graph statistics.
#[derive(Debug, Clone)]
pub struct AdminStats {
    /// Number of nodes.
    pub node_count: u64,
    /// Number of edges.
    pub edge_count: u64,
    /// Number of distinct labels.
    pub label_count: u64,
    /// Number of distinct edge types.
    pub edge_type_count: u64,
    /// Number of distinct property keys.
    pub property_key_count: u64,
    /// Number of indexes.
    pub index_count: u64,
    /// Memory usage in bytes.
    pub memory_bytes: u64,
    /// Disk usage in bytes (if persistent).
    pub disk_bytes: Option<u64>,
}

/// WAL status information.
#[derive(Debug, Clone)]
pub struct AdminWalStatus {
    /// Whether WAL is enabled.
    pub enabled: bool,
    /// WAL file path.
    pub path: Option<String>,
    /// WAL size in bytes.
    pub size_bytes: u64,
    /// Number of WAL records.
    pub record_count: u64,
    /// Last checkpoint timestamp.
    pub last_checkpoint: Option<u64>,
    /// Current epoch.
    pub current_epoch: u64,
}

/// Validation result.
#[derive(Debug, Clone)]
pub struct AdminValidationResult {
    /// Whether validation passed (no errors).
    pub valid: bool,
    /// Validation errors.
    pub errors: Vec<ValidationDiagnostic>,
    /// Validation warnings.
    pub warnings: Vec<ValidationDiagnostic>,
}

/// A single validation diagnostic (error or warning).
#[derive(Debug, Clone)]
pub struct ValidationDiagnostic {
    /// Diagnostic code.
    pub code: String,
    /// Human-readable message.
    pub message: String,
    /// Optional context (e.g. the affected element or property).
    pub context: Option<String>,
}

/// Index definition for create/drop operations.
#[derive(Debug, Clone)]
pub enum IndexDefinition {
    /// Property hash index.
    Property {
        /// Property name.
        property: String,
    },
    /// Vector similarity index (HNSW).
    Vector {
        /// Node label.
        label: String,
        /// Property name.
        property: String,
        /// Expected dimensions.
        dimensions: Option<u32>,
        /// Distance metric.
        metric: Option<String>,
        /// HNSW links per node.
        m: Option<u32>,
        /// Construction beam width.
        ef_construction: Option<u32>,
    },
    /// Full-text index (BM25).
    Text {
        /// Node label.
        label: String,
        /// Property name.
        property: String,
    },
}

// ============================================================================
// Search types
// ============================================================================

/// Vector search parameters.
#[derive(Debug, Clone)]
pub struct VectorSearchParams {
    /// Graph name.
    pub graph: String,
    /// Node label.
    pub label: String,
    /// Property name.
    pub property: String,
    /// Query vector.
    pub query_vector: Vec<f32>,
    /// Number of results.
    pub k: u32,
    /// Search beam width.
    pub ef: Option<u32>,
    /// Property filters.
    pub filters: std::collections::HashMap<String, Value>,
}

/// Text search parameters.
#[derive(Debug, Clone)]
pub struct TextSearchParams {
    /// Graph name.
    pub graph: String,
    /// Node label.
    pub label: String,
    /// Property name.
    pub property: String,
    /// Search query text.
    pub query: String,
    /// Number of results.
    pub k: u32,
}

/// Hybrid search parameters.
#[derive(Debug, Clone)]
pub struct HybridSearchParams {
    /// Graph name.
    pub graph: String,
    /// Node label.
    pub label: String,
    /// Text property name.
    pub text_property: String,
    /// Vector property name.
    pub vector_property: String,
    /// Text query.
    pub query_text: String,
    /// Optional vector query.
    pub query_vector: Vec<f32>,
    /// Number of results.
    pub k: u32,
}

/// A single search result hit.
///
/// Search results use a numeric `node_id` (uint64) rather than the opaque
/// `bytes` element ID from the GQL type system. This is an internal
/// identifier suitable for fast lookups; it is not the same as `Node.id`.
#[derive(Debug, Clone)]
pub struct SearchHit {
    /// Internal numeric node identifier (not the opaque GQL element ID).
    pub node_id: u64,
    /// Relevance score (distance for vector, BM25 for text).
    pub score: f64,
    /// Node properties.
    pub properties: std::collections::HashMap<String, Value>,
}