vibesql-server 0.1.3

Network server with PostgreSQL wire protocol for VibeSQL
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
//! Integration tests for error handling
//!
//! Tests malformed requests, SQL errors, and edge cases.

mod common;

use common::{parse_backend_messages, start_test_server, TestClient};

/// Test SQL syntax error handling
#[tokio::test]
async fn test_sql_syntax_error() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Send invalid SQL
    client.send_query("SELEKT * FROM nowhere").await.expect("Failed to send query");

    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);

    // Should have ErrorResponse
    assert!(messages.iter().any(|m| m.is_error()), "Should receive ErrorResponse for syntax error");

    // Should still be ready for more queries
    assert!(
        messages.iter().any(|m| m.is_ready_for_query()),
        "Should be ready for queries after error"
    );

    // Verify connection still works
    client.send_query("SELECT 1").await.expect("Failed to send query");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);
    assert!(
        messages.iter().any(|m| m.is_command_complete()),
        "Connection should still work after error"
    );

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test error on non-existent table
#[tokio::test]
async fn test_nonexistent_table_error() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Query non-existent table
    client
        .send_query("SELECT * FROM table_that_does_not_exist")
        .await
        .expect("Failed to send query");

    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);

    assert!(
        messages.iter().any(|m| m.is_error()),
        "Should receive ErrorResponse for non-existent table"
    );
    assert!(messages.iter().any(|m| m.is_ready_for_query()), "Should still be ready for queries");

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test error on invalid column reference
#[tokio::test]
async fn test_invalid_column_error() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Create table and verify it succeeded
    client
        .send_query("CREATE TABLE col_test (id INT, name VARCHAR(50))")
        .await
        .expect("Failed to CREATE");
    let create_data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let create_messages = parse_backend_messages(&create_data);
    assert!(
        create_messages.iter().any(|m| m.is_command_complete()),
        "CREATE TABLE should succeed, got messages: {:?}",
        create_messages.iter().map(|m| m.msg_type as char).collect::<Vec<_>>()
    );

    // Query non-existent column
    client
        .send_query("SELECT nonexistent_column FROM col_test")
        .await
        .expect("Failed to send query");

    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);

    assert!(
        messages.iter().any(|m| m.is_error()),
        "Should receive ErrorResponse for invalid column, got messages: {:?}",
        messages.iter().map(|m| m.msg_type as char).collect::<Vec<_>>()
    );

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test multiple errors in sequence don't crash server
#[tokio::test]
async fn test_multiple_errors_resilience() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Send multiple erroneous queries
    for i in 0..5 {
        client.send_query(&format!("INVALID_STATEMENT_{}", i)).await.expect("Failed to send query");
        let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
        let messages = parse_backend_messages(&data);
        assert!(messages.iter().any(|m| m.is_error()), "Query {} should error", i);
        assert!(
            messages.iter().any(|m| m.is_ready_for_query()),
            "Should still be ready after error {}",
            i
        );
    }

    // Connection should still work for valid queries
    client.send_query("SELECT 1").await.expect("Failed to send valid query");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);
    assert!(
        messages.iter().any(|m| m.is_command_complete()),
        "Valid query should succeed after errors"
    );

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test constraint violation error (duplicate primary key)
#[tokio::test]
async fn test_constraint_violation_error() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Create table with primary key
    client
        .send_query("CREATE TABLE pk_test (id INT PRIMARY KEY, value INT)")
        .await
        .expect("Failed to CREATE");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Insert first row
    client.send_query("INSERT INTO pk_test VALUES (1, 100)").await.expect("Failed to INSERT");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Try to insert duplicate primary key
    client
        .send_query("INSERT INTO pk_test VALUES (1, 200)")
        .await
        .expect("Failed to send duplicate INSERT");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);

    // Should either error or succeed depending on implementation
    // Most DBs would error on duplicate PK
    assert!(
        messages.iter().any(|m| m.is_ready_for_query()),
        "Should be ready for queries after constraint check"
    );

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test error recovery - valid query after error
#[tokio::test]
async fn test_error_recovery() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Create valid table
    client.send_query("CREATE TABLE recovery_test (id INT)").await.expect("Failed to CREATE");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Cause an error
    client.send_query("SELECT * FROM no_such_table").await.expect("Failed to send bad query");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);
    assert!(messages.iter().any(|m| m.is_error()));

    // Recovery: valid insert
    client.send_query("INSERT INTO recovery_test VALUES (1)").await.expect("Failed to INSERT");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);
    assert!(
        messages.iter().any(|m| m.is_command_complete()),
        "Should complete INSERT after error recovery"
    );

    // Recovery: valid select
    client.send_query("SELECT * FROM recovery_test").await.expect("Failed to SELECT");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);
    assert!(messages.iter().any(|m| m.is_data_row()), "Should return data after error recovery");

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test error on DROP non-existent table
#[tokio::test]
async fn test_drop_nonexistent_table_error() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Try to drop non-existent table
    client.send_query("DROP TABLE nonexistent_table").await.expect("Failed to send DROP");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);

    assert!(messages.iter().any(|m| m.is_error()), "Should error when dropping non-existent table");
    assert!(messages.iter().any(|m| m.is_ready_for_query()), "Should still be ready for queries");

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test error message format (should have severity and message)
#[tokio::test]
async fn test_error_message_format() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Cause an error
    client.send_query("INVALID SYNTAX HERE").await.expect("Failed to send query");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);

    let error_msg = messages.iter().find(|m| m.is_error());
    assert!(error_msg.is_some(), "Should have error message");

    // Error message payload should contain field codes
    let error = error_msg.unwrap();
    // Check that payload is not empty (contains severity, code, message fields)
    assert!(!error.payload.is_empty(), "Error payload should not be empty");

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test alternating errors and successes
#[tokio::test]
async fn test_alternating_errors_successes() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    for i in 0..10 {
        if i % 2 == 0 {
            // Success case
            client.send_query(&format!("SELECT {}", i)).await.expect("Failed to send query");
            let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
            let messages = parse_backend_messages(&data);
            assert!(
                messages.iter().any(|m| m.is_command_complete()),
                "Even iteration {} should succeed",
                i
            );
        } else {
            // Error case
            client.send_query("INVALID SQL").await.expect("Failed to send query");
            let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
            let messages = parse_backend_messages(&data);
            assert!(messages.iter().any(|m| m.is_error()), "Odd iteration {} should error", i);
        }
    }

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test type mismatch error (inserting wrong type)
#[tokio::test]
async fn test_type_mismatch_error() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Create table with specific type
    client.send_query("CREATE TABLE type_test (id INT)").await.expect("Failed to CREATE");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Insert string into INT column - may error or coerce depending on implementation
    client
        .send_query("INSERT INTO type_test VALUES ('not an integer')")
        .await
        .expect("Failed to send INSERT");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);

    // Should either error or succeed with coercion
    assert!(
        messages.iter().any(|m| m.is_ready_for_query()),
        "Should still be ready for queries after type check"
    );

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test error on ambiguous query
#[tokio::test]
async fn test_ambiguous_column_error() {
    let server = start_test_server().await;
    let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");

    client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Create two tables with same column name
    client.send_query("CREATE TABLE ambig1 (id INT, value INT)").await.expect("Failed to CREATE");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    client.send_query("CREATE TABLE ambig2 (id INT, value INT)").await.expect("Failed to CREATE");
    let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");

    // Try ambiguous query without qualification - this might work or error
    // depending on whether the DB requires explicit join
    client.send_query("SELECT id FROM ambig1, ambig2").await.expect("Failed to send query");
    let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
    let messages = parse_backend_messages(&data);

    // Either error or succeed - both are valid outcomes
    assert!(messages.iter().any(|m| m.is_ready_for_query()), "Should still be ready for queries");

    client.send_terminate().await.expect("Failed to terminate");
    server.shutdown();
}

/// Test server handles concurrent error conditions gracefully
#[tokio::test]
async fn test_concurrent_errors() {
    let server = start_test_server().await;
    let addr = server.addr();

    let handles: Vec<_> = (0..5)
        .map(|i| {
            tokio::spawn(async move {
                let mut client = TestClient::connect(addr).await.expect("Failed to connect");
                client
                    .send_startup(&format!("user{}", i), "testdb")
                    .await
                    .expect("Failed to startup");
                let _ =
                    client.read_until_message_type(b'Z').await.expect("Failed to read response");

                // Each client causes multiple errors
                for j in 0..3 {
                    client
                        .send_query(&format!("INVALID_QUERY_{}_{}", i, j))
                        .await
                        .expect("Failed to send query");
                    let data = client
                        .read_until_message_type(b'Z')
                        .await
                        .expect("Failed to read response");
                    let messages = parse_backend_messages(&data);
                    assert!(messages.iter().any(|m| m.is_error()));
                }

                // Then a valid query
                client.send_query("SELECT 1").await.expect("Failed to send valid query");
                let data =
                    client.read_until_message_type(b'Z').await.expect("Failed to read response");
                let messages = parse_backend_messages(&data);
                assert!(
                    messages.iter().any(|m| m.is_command_complete()),
                    "Valid query should succeed for client {}",
                    i
                );

                client.send_terminate().await.expect("Failed to terminate");
            })
        })
        .collect();

    for handle in handles {
        handle.await.expect("Client task failed");
    }

    server.shutdown();
}