algeon-datafusion 0.1.2

DataFusion physical-plan adapter for native GPU execution.
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
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
use super::*;

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn create_view_statement_executes_once_and_persists_across_statements() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;

    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-view-").tempdir().unwrap();
    let parquet_path = dir.path().join("edges.parquet");
    write_update_edges_parquet(&parquet_path);
    let source_ddl = format!(
        "CREATE EXTERNAL TABLE target_edges_source STORED AS PARQUET LOCATION '{}'",
        parquet_path.to_string_lossy()
    );
    let _ = execute_batches(&mut client, &source_ddl).await;

    let flight_info = client
        .execute("CREATE VIEW target_edges AS SELECT * FROM target_edges_source".to_string(), None)
        .await
        .unwrap();
    let advertised_schema = flight_info.clone().try_decode_schema().unwrap();
    let (stream_schema, batches) = execute_flight_info_with_stream_schema(&mut client, flight_info).await;
    assert_eq!(advertised_schema.fields().len(), 0, "DDL FlightInfo must advertise no result-set columns");
    assert_eq!(stream_schema.fields().len(), 0, "DDL DoGet stream must match the advertised empty schema");
    assert_eq!(batches.iter().map(|batch| batch.num_rows()).sum::<usize>(), 0);

    let batches = execute_batches(&mut client, "SELECT COUNT(*) AS edge_count FROM target_edges").await;

    server.abort();

    assert_eq!(batches.len(), 1);
    assert_eq!(int_value(batches[0].column(0)), Some(2));
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn strict_flight_sql_executes_mixed_distinct_and_count_over_union() {
    let (listener, addr) = free_local_addr();
    let env =
        std::collections::BTreeMap::from([("ALGEON_SERVER_NATIVE_EXECUTION_MODE", "native_required".to_string())]);
    let mut config = test_server_config_with_env(env);
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;
    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-distinct-union-").tempdir().unwrap();
    let parquet_path = dir.path().join("vertices.parquet");
    write_update_edges_parquet(&parquet_path);
    for table in ["alpha", "beta"] {
        execute_batches(
            &mut client,
            &format!("CREATE EXTERNAL TABLE {table} STORED AS PARQUET LOCATION '{}'", parquet_path.to_string_lossy()),
        )
        .await;
    }

    let flight_info = client
        .execute(
            "SELECT source, COUNT(DISTINCT vertex_id) AS vertices, COUNT(*) AS rows \
             FROM ( \
                 SELECT 'alpha' AS source, src AS vertex_id FROM alpha \
                 UNION ALL \
                 SELECT 'beta' AS source, src AS vertex_id FROM beta \
             ) scores \
             GROUP BY source \
             ORDER BY source"
                .to_string(),
            None,
        )
        .await
        .unwrap();
    let query_id = query_id_from_statement_flight_info(&flight_info);
    let (_schema, batches) = execute_flight_info_with_stream_schema(&mut client, flight_info).await;
    let batch = batches.iter().find(|batch| batch.num_rows() > 0).unwrap();
    let sources = batch.column(0).as_any().downcast_ref::<StringArray>().unwrap();
    let vertices = batch.column(1).as_any().downcast_ref::<Int64Array>().unwrap();
    let rows = batch.column(2).as_any().downcast_ref::<Int64Array>().unwrap();

    assert_eq!([sources.value(0), sources.value(1)], ["alpha", "beta"]);
    assert_eq!([vertices.value(0), vertices.value(1)], [2, 2]);
    assert_eq!([rows.value(0), rows.value(1)], [2, 2]);
    assert_single_sealed_attempt_report(&get_query_stats_raw(&mut client, &query_id).await);

    server.abort();
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn strict_flight_sql_executes_grouped_aggregate_topk_then_left_join() {
    let (listener, addr) = free_local_addr();
    let env =
        std::collections::BTreeMap::from([("ALGEON_SERVER_NATIVE_EXECUTION_MODE", "native_required".to_string())]);
    let mut config = test_server_config_with_env(env);
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;
    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-aggregate-topk-join-").tempdir().unwrap();
    let relationships_path = dir.path().join("edgesummary.parquet");
    let vertex_info_path = dir.path().join("vertexinfo.parquet");
    write_int64_pairs_parquet(&relationships_path, ["sourceId", "targetId"], [0, 1], [1, 2]);
    write_int64_pairs_parquet(&vertex_info_path, ["vertexId", "vertexName"], [0, 1], [10, 20]);
    let schema = "3f1c2b8e-6d4a-4e0b-9a7c-5b2d8e1f4a60";
    execute_batches(&mut client, &format!(r#"CREATE SCHEMA "{schema}""#)).await;
    for (table, parquet_path) in [("edgesummary", &relationships_path), ("vertexinfo", &vertex_info_path)] {
        execute_batches(
            &mut client,
            &format!(
                r#"CREATE EXTERNAL TABLE "{schema}".{table} STORED AS PARQUET LOCATION '{}'"#,
                parquet_path.to_string_lossy()
            ),
        )
        .await;
    }

    let flight_info = client
        .execute(
            format!(
                r#"WITH top_degrees AS (
                 SELECT "sourceId" AS "vertexId", COUNT(*) AS out_degree
                 FROM "{schema}".edgesummary
                 GROUP BY "sourceId"
                 ORDER BY out_degree DESC
                 LIMIT 30
             )
             SELECT d."vertexId", n."vertexName", d.out_degree
             FROM top_degrees d
             LEFT JOIN "{schema}".vertexinfo n ON d."vertexId" = n."vertexId"
             ORDER BY d.out_degree DESC
             LIMIT 30"#
            ),
            None,
        )
        .await
        .unwrap();
    let query_id = query_id_from_statement_flight_info(&flight_info);
    let (_schema, batches) = execute_flight_info_with_stream_schema(&mut client, flight_info).await;
    let batch = batches.iter().find(|batch| batch.num_rows() > 0).unwrap();
    let vertex_ids = batch.column(0).as_any().downcast_ref::<Int64Array>().unwrap();
    let vertex_names = batch.column(1).as_any().downcast_ref::<Int64Array>().unwrap();
    let out_degrees = batch.column(2).as_any().downcast_ref::<Int64Array>().unwrap();
    let mut rows = (0..batch.num_rows())
        .map(|row| (vertex_ids.value(row), vertex_names.value(row), out_degrees.value(row)))
        .collect::<Vec<_>>();
    rows.sort_unstable();

    assert_eq!(rows, vec![(0, 10, 1), (1, 20, 1)]);
    assert_single_sealed_attempt_report(&get_query_stats_raw(&mut client, &query_id).await);

    server.abort();
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn prepared_create_view_query_advertises_empty_schema_and_persists() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;

    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-prepared-view-").tempdir().unwrap();
    let parquet_path = dir.path().join("edges.parquet");
    write_update_edges_parquet(&parquet_path);
    let source_ddl = format!(
        "CREATE EXTERNAL TABLE prepared_target_edges_source STORED AS PARQUET LOCATION '{}'",
        parquet_path.to_string_lossy()
    );
    let _ = execute_batches(&mut client, &source_ddl).await;

    let mut prepared = client
        .prepare(
            "CREATE VIEW prepared_target_edges AS \
             SELECT src + 10 AS src, dst + 10 AS dst FROM prepared_target_edges_source"
                .to_string(),
            None,
        )
        .await
        .unwrap();
    assert_eq!(prepared.dataset_schema().unwrap().fields().len(), 0, "DDL prepared statements do not return rows");

    let flight_info = prepared.execute().await.unwrap();
    let advertised_schema = flight_info.clone().try_decode_schema().unwrap();
    let (stream_schema, batches) = execute_flight_info_with_stream_schema(&mut client, flight_info).await;
    assert_eq!(advertised_schema.fields().len(), 0, "prepared DDL FlightInfo must advertise no result set");
    assert_eq!(stream_schema.fields().len(), 0, "prepared DDL DoGet stream must match the advertised empty schema");
    assert_eq!(batches.iter().map(|batch| batch.num_rows()).sum::<usize>(), 0);

    prepared.close().await.unwrap();
    let batches = execute_batches(&mut client, "SELECT src, dst FROM prepared_target_edges ORDER BY src").await;

    server.abort();

    assert_eq!(batches.len(), 1);
    assert_eq!(batches[0].schema().fields().len(), 2);
    assert_eq!(batches[0].num_rows(), 2);
    assert_eq!(int_value(batches[0].column(0)), Some(10));
    assert_eq!(int_value(batches[0].column(1)), Some(11));
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn drop_table_on_view_reports_object_type_mismatch() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;

    let _ = execute_batches(&mut client, "CREATE VIEW drop_view_probe AS SELECT 1 AS x").await;

    let error = client.execute("DROP TABLE drop_view_probe".to_string(), None).await.unwrap_err();
    let status = unwrap_flight_status(error);
    assert_eq!(status.code(), Code::InvalidArgument);
    assert_query_id_metadata(&status);
    assert_error_metadata(&status, "invalid_request", "invalid_input", "permanent");
    assert!(status.message().contains("is a view"), "message must explain actual object type: {}", status.message());
    assert!(status.message().contains("DROP VIEW"), "message must name the corrective DDL: {}", status.message());
    assert_no_diagnostic_fields(status.message());
    let payload = get_query_stats_raw(&mut client, &query_id_from_status(&status)).await;
    assert!(
        payload.contains("operation=algeon_datafusion.server.flight.get_flight_info_statement.create_logical_plan"),
        "{payload}"
    );
    assert!(payload.contains("fact.requested_table_type=TABLE"), "{payload}");
    assert!(payload.contains("fact.actual_table_type=VIEW"), "{payload}");
    assert!(!payload.contains("fact.object_name="), "raw object name must not be retained: {payload}");

    let error = client.execute_update("DROP TABLE IF EXISTS drop_view_probe".to_string(), None).await.unwrap_err();
    let status = unwrap_flight_status(error);
    assert_eq!(status.code(), Code::InvalidArgument);
    assert_query_id_metadata(&status);
    assert_error_metadata(&status, "invalid_request", "invalid_input", "permanent");
    assert!(status.message().contains("DROP VIEW"));
    assert_no_diagnostic_fields(status.message());
    let payload = get_query_stats_raw(&mut client, &query_id_from_status(&status)).await;
    assert!(payload.contains("operation=algeon_datafusion.server.flight.do_put_statement_update.sql"), "{payload}");
    assert!(payload.contains("fact.requested_table_type=TABLE"), "{payload}");
    assert!(payload.contains("fact.actual_table_type=VIEW"), "{payload}");
    assert!(!payload.contains("fact.object_name="), "raw object name must not be retained: {payload}");

    let _ = execute_batches(&mut client, "DROP VIEW drop_view_probe").await;
    let error = client.execute("SELECT * FROM drop_view_probe".to_string(), None).await.unwrap_err();
    let status = unwrap_flight_status(error);

    server.abort();

    assert_eq!(status.code(), Code::InvalidArgument);
    assert_error_metadata(&status, "datafusion_planning", "planning", "permanent");
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn drop_view_on_table_reports_object_type_mismatch() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;

    let _ = execute_batches(&mut client, "CREATE TABLE drop_table_probe AS SELECT 1 AS x").await;

    let error = client.execute("DROP VIEW drop_table_probe".to_string(), None).await.unwrap_err();
    let status = unwrap_flight_status(error);
    assert_eq!(status.code(), Code::InvalidArgument);
    assert_query_id_metadata(&status);
    assert_error_metadata(&status, "invalid_request", "invalid_input", "permanent");
    assert!(status.message().contains("base table"), "message must explain actual object type: {}", status.message());
    assert!(status.message().contains("DROP TABLE"), "message must name the corrective DDL: {}", status.message());
    assert_no_diagnostic_fields(status.message());

    let error = client.execute_update("DROP VIEW IF EXISTS drop_table_probe".to_string(), None).await.unwrap_err();
    let status = unwrap_flight_status(error);
    assert_eq!(status.code(), Code::InvalidArgument);
    assert_query_id_metadata(&status);
    assert_error_metadata(&status, "invalid_request", "invalid_input", "permanent");
    assert!(status.message().contains("DROP TABLE"));
    assert_no_diagnostic_fields(status.message());

    let _ = execute_batches(&mut client, "DROP TABLE drop_table_probe").await;
    let error = client.execute("SELECT * FROM drop_table_probe".to_string(), None).await.unwrap_err();
    let status = unwrap_flight_status(error);

    server.abort();

    assert_eq!(status.code(), Code::InvalidArgument);
    assert_error_metadata(&status, "datafusion_planning", "planning", "permanent");
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn dropping_a_missing_table_or_view_reports_object_not_found() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;

    for sql in ["DROP TABLE drop_missing_probe", "DROP VIEW drop_missing_probe"] {
        let error = client.execute_update(sql.to_string(), None).await.unwrap_err();
        let status = unwrap_flight_status(error);
        assert_eq!(status.code(), Code::NotFound, "{sql}: {status:?}");
        assert_query_id_metadata(&status);
        assert_error_metadata(&status, "object_not_found", "not_found", "permanent");

        let flight_info = client.execute(sql.to_string(), None).await.unwrap();
        let ticket = flight_info.endpoint.first().and_then(|endpoint| endpoint.ticket.clone()).unwrap();
        let status = match client.do_get(ticket).await {
            Err(error) => unwrap_flight_status(error),
            Ok(stream) => unwrap_flight_status(stream.try_collect::<Vec<_>>().await.unwrap_err()),
        };
        assert_eq!(status.code(), Code::NotFound, "{sql}: {status:?}");
        assert_error_metadata(&status, "object_not_found", "not_found", "permanent");
    }

    server.abort();
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn statement_update_create_external_table_executes_and_persists() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;
    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-update-").tempdir().unwrap();
    let parquet_path = dir.path().join("edges.parquet");
    write_update_edges_parquet(&parquet_path);

    let record_count = client
        .execute_update(
            format!(
                "CREATE EXTERNAL TABLE update_edges_direct STORED AS PARQUET LOCATION '{}'",
                parquet_path.to_string_lossy()
            ),
            None,
        )
        .await
        .unwrap();
    let batches = execute_batches(&mut client, "SELECT COUNT(*) AS edge_count FROM update_edges_direct").await;

    server.abort();

    assert_eq!(record_count, 0, "DDL statements do not update table rows");
    assert_eq!(batches.len(), 1);
    assert_eq!(int_value(batches[0].column(0)), Some(2));
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn prepared_ddl_create_action_omits_dataset_schema_bytes() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;
    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-prepared-schema-").tempdir().unwrap();
    let parquet_path = dir.path().join("edges.parquet");
    write_update_edges_parquet(&parquet_path);

    let prepared = create_prepared_raw(
        &mut client,
        format!(
            "CREATE EXTERNAL TABLE schema_probe_edges STORED AS PARQUET LOCATION '{}'",
            parquet_path.to_string_lossy()
        ),
    )
    .await;
    close_prepared_raw(&mut client, prepared.prepared_statement_handle.clone()).await;

    server.abort();

    assert!(
        prepared.dataset_schema.is_empty(),
        "non-result-set prepared DDL must omit dataset_schema rather than advertise a 0-column result"
    );
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn prepared_create_action_handle_resolves_query_stats() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;

    let prepared = create_prepared_raw(&mut client, "select 1 as one".to_string()).await;
    let query_id =
        String::from_utf8(prepared.prepared_statement_handle.to_vec()).expect("handle must be UTF-8 query id");
    let payload = get_query_stats_raw(&mut client, &query_id).await;
    close_prepared_raw(&mut client, prepared.prepared_statement_handle).await;

    server.abort();

    assert!(payload.contains(&format!("# query_id={query_id}")), "{payload}");
    assert!(payload.contains("# outcome=succeeded"), "{payload}");
    assert!(payload.contains("# sql=select 1 as one"), "{payload}");
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn prepared_statement_query_create_external_table_executes_and_persists() {
    // JDBC clients such as DataGrip wrap every statement in a server-side
    // prepared statement and run it through `execute()` (the query channel),
    // never `execute_update()`. DDL must therefore execute on the prepared
    // query path the same way a non-prepared statement does.
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;
    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-prepared-query-").tempdir().unwrap();
    let parquet_path = dir.path().join("edges.parquet");
    write_update_edges_parquet(&parquet_path);

    let mut prepared = client
        .prepare(
            format!(
                "CREATE EXTERNAL TABLE query_edges_prepared STORED AS PARQUET LOCATION '{}'",
                parquet_path.to_string_lossy()
            ),
            None,
        )
        .await
        .unwrap();
    assert_eq!(prepared.dataset_schema().unwrap().fields().len(), 0, "DDL prepared statements do not return rows");

    // Execute through the query channel; DDL yields an empty result set rather
    // than the `execute_update` rejection that used to block DataGrip-class
    // clients.
    let flight_info = prepared.execute().await.unwrap();
    let query_id = query_id_from_statement_flight_info(&flight_info);
    let ticket = flight_info.endpoint.first().and_then(|endpoint| endpoint.ticket.clone()).unwrap();
    let ddl_batches = client.do_get(ticket).await.unwrap().try_collect::<Vec<_>>().await.unwrap();
    let ddl_rows: usize = ddl_batches.iter().map(|batch| batch.num_rows()).sum();
    assert_eq!(ddl_rows, 0, "DDL executed through the query channel returns no rows");
    let payload = get_query_stats_raw(&mut client, &query_id).await;
    assert_single_sealed_attempt_report(&payload);

    prepared.close().await.unwrap();
    let batches = execute_batches(&mut client, "SELECT COUNT(*) AS edge_count FROM query_edges_prepared").await;

    server.abort();

    assert_eq!(batches.len(), 1);
    assert_eq!(int_value(batches[0].column(0)), Some(2));
}

// Regression for the observed JDBC/DataGrip flow: the same prepared DDL handle
// is first executed as an update, then the client also asks for a query ticket.
// The query-channel pass must be a no-op with an empty result, not a duplicate
// DDL execution or a client-visible failure.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn prepared_ddl_update_then_query_double_send_is_deduplicated() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;
    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-dedup-mixed-").tempdir().unwrap();
    let parquet_path = dir.path().join("edges.parquet");
    write_update_edges_parquet(&parquet_path);

    let create_sql = format!(
        "CREATE EXTERNAL TABLE dedup_mixed_edges STORED AS PARQUET LOCATION '{}'",
        parquet_path.to_string_lossy()
    );

    let mut prepared = client.prepare(create_sql, None).await.unwrap();
    assert_eq!(prepared.dataset_schema().unwrap().fields().len(), 0, "DDL prepared statements do not return rows");

    let update_count = prepared.execute_update().await.unwrap();
    assert_eq!(update_count, 0, "DDL statements do not update table rows");

    let flight_info = prepared.execute().await.unwrap();
    let ticket = flight_info.endpoint.first().and_then(|endpoint| endpoint.ticket.clone()).unwrap();
    let rows: usize =
        client.do_get(ticket).await.unwrap().try_collect::<Vec<_>>().await.unwrap().iter().map(|b| b.num_rows()).sum();
    assert_eq!(rows, 0, "query-channel duplicate of an already-executed DDL handle must be an empty no-op");

    prepared.close().await.unwrap();
    let batches = execute_batches(&mut client, "SELECT COUNT(*) AS edge_count FROM dedup_mixed_edges").await;

    server.abort();

    assert_eq!(batches.len(), 1);
    assert_eq!(int_value(batches[0].column(0)), Some(2));
}

// Regression: a prepared DDL handle re-executed twice; the double-send a
// JDBC client such as DataGrip triggers; must succeed both times. DataGrip /
// the Arrow Flight SQL JDBC driver classify DDL prepared statements as updates
// when no result-set schema is advertised. The second execution is deduplicated
// into a no-op instead of failing with "already exists".
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn prepared_ddl_double_execute_is_deduplicated_on_update_channel() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;
    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-dedup-update-").tempdir().unwrap();
    let parquet_path = dir.path().join("edges.parquet");
    write_update_edges_parquet(&parquet_path);

    let create_sql = format!(
        "CREATE EXTERNAL TABLE dedup_update_edges STORED AS PARQUET LOCATION '{}'",
        parquet_path.to_string_lossy()
    );

    let mut prepared = client.prepare(create_sql, None).await.unwrap();
    assert_eq!(prepared.dataset_schema().unwrap().fields().len(), 0, "DDL prepared statements do not return rows");

    let first = prepared.execute_update().await.unwrap();
    assert_eq!(first, 0, "DDL statements do not update table rows");

    // The double-send: same handle, executed again. Must be a no-op, not an
    // "already exists" rejection.
    let second = prepared.execute_update().await.unwrap();
    assert_eq!(second, 0, "re-executing the same prepared DDL handle must be a deduplicated no-op");

    prepared.close().await.unwrap();
    let batches = execute_batches(&mut client, "SELECT COUNT(*) AS edge_count FROM dedup_update_edges").await;

    server.abort();

    assert_eq!(batches.len(), 1);
    assert_eq!(int_value(batches[0].column(0)), Some(2));
}

// Regression: the same double-send on the query channel (a client that runs a
// prepared DDL through `execute()` + `do_get` twice) is likewise deduplicated.
// The second `do_get` returns an empty result rather than re-running the DDL.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn prepared_ddl_double_execute_is_deduplicated_on_query_channel() {
    let (listener, addr) = free_local_addr();
    let mut config = test_server_config();
    declare_test_gpu_ownership(&mut config);
    config.bind_addr = addr;

    let server = tokio::spawn(async move { serve_with_listener(config, listener).await });
    let mut client = connect_with_retry(addr).await;
    let dir = tempfile::Builder::new().prefix("algeon-datafusion-flight-sql-dedup-query-").tempdir().unwrap();
    let parquet_path = dir.path().join("edges.parquet");
    write_update_edges_parquet(&parquet_path);

    let create_sql = format!(
        "CREATE EXTERNAL TABLE dedup_query_edges STORED AS PARQUET LOCATION '{}'",
        parquet_path.to_string_lossy()
    );

    let mut prepared = client.prepare(create_sql, None).await.unwrap();
    assert_eq!(prepared.dataset_schema().unwrap().fields().len(), 0, "DDL prepared statements do not return rows");

    let info1 = prepared.execute().await.unwrap();
    let ticket1 = info1.endpoint.first().and_then(|endpoint| endpoint.ticket.clone()).unwrap();
    let rows1: usize =
        client.do_get(ticket1).await.unwrap().try_collect::<Vec<_>>().await.unwrap().iter().map(|b| b.num_rows()).sum();
    assert_eq!(rows1, 0, "DDL executed through the query channel returns no rows");

    // The double-send: same handle, executed again on the query channel.
    let info2 = prepared.execute().await.unwrap();
    let ticket2 = info2.endpoint.first().and_then(|endpoint| endpoint.ticket.clone()).unwrap();
    let rows2: usize =
        client.do_get(ticket2).await.unwrap().try_collect::<Vec<_>>().await.unwrap().iter().map(|b| b.num_rows()).sum();
    assert_eq!(rows2, 0, "re-executing the same prepared DDL handle must be a deduplicated no-op");

    prepared.close().await.unwrap();
    let batches = execute_batches(&mut client, "SELECT COUNT(*) AS edge_count FROM dedup_query_edges").await;

    server.abort();

    assert_eq!(batches.len(), 1);
    assert_eq!(int_value(batches[0].column(0)), Some(2));
}