quack-ruby 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/LICENSE-DuckDB +7 -0
  4. data/LICENSE-QuackProtocol +21 -0
  5. data/LICENSE-Rust-dependencies.json +2057 -0
  6. data/README.md +229 -0
  7. data/ext/quack/Cargo.lock +1803 -0
  8. data/ext/quack/Cargo.toml +17 -0
  9. data/ext/quack/extconf.rb +4 -0
  10. data/ext/quack/src/lib.rs +647 -0
  11. data/ext/quack/vendor/quack_protocol/Cargo.lock +1907 -0
  12. data/ext/quack/vendor/quack_protocol/Cargo.toml +41 -0
  13. data/ext/quack/vendor/quack_protocol/LICENSE +21 -0
  14. data/ext/quack/vendor/quack_protocol/PATCHES.md +16 -0
  15. data/ext/quack/vendor/quack_protocol/README.md +272 -0
  16. data/ext/quack/vendor/quack_protocol/src/arrow/columns.rs +399 -0
  17. data/ext/quack/vendor/quack_protocol/src/arrow/mod.rs +134 -0
  18. data/ext/quack/vendor/quack_protocol/src/arrow/schema.rs +103 -0
  19. data/ext/quack/vendor/quack_protocol/src/arrow/tests.rs +851 -0
  20. data/ext/quack/vendor/quack_protocol/src/arrow/values.rs +150 -0
  21. data/ext/quack/vendor/quack_protocol/src/binary.rs +504 -0
  22. data/ext/quack/vendor/quack_protocol/src/builders.rs +217 -0
  23. data/ext/quack/vendor/quack_protocol/src/client.rs +1486 -0
  24. data/ext/quack/vendor/quack_protocol/src/constants.rs +11 -0
  25. data/ext/quack/vendor/quack_protocol/src/errors.rs +103 -0
  26. data/ext/quack/vendor/quack_protocol/src/inspection.rs +250 -0
  27. data/ext/quack/vendor/quack_protocol/src/json.rs +225 -0
  28. data/ext/quack/vendor/quack_protocol/src/lib.rs +54 -0
  29. data/ext/quack/vendor/quack_protocol/src/logical_types.rs +977 -0
  30. data/ext/quack/vendor/quack_protocol/src/messages.rs +843 -0
  31. data/ext/quack/vendor/quack_protocol/src/pool.rs +741 -0
  32. data/ext/quack/vendor/quack_protocol/src/protocol_tests.rs +759 -0
  33. data/ext/quack/vendor/quack_protocol/src/proxy_error.rs +77 -0
  34. data/ext/quack/vendor/quack_protocol/src/sql.rs +359 -0
  35. data/ext/quack/vendor/quack_protocol/src/tls.rs +354 -0
  36. data/ext/quack/vendor/quack_protocol/src/values.rs +77 -0
  37. data/ext/quack/vendor/quack_protocol/src/vector.rs +1364 -0
  38. data/ext/quack/vendor/quack_protocol/testdata/tls/key.der +0 -0
  39. data/ext/quack/vendor/quack_protocol/testdata/tls/spki.der +0 -0
  40. data/ext/quack/vendor/quack_protocol/testdata/tls/v1-cert.der +0 -0
  41. data/ext/quack/vendor/quack_protocol/testdata/tls/v3-cert.der +0 -0
  42. data/lib/quack/codec.rb +263 -0
  43. data/lib/quack/connection.rb +131 -0
  44. data/lib/quack/logical_types.rb +72 -0
  45. data/lib/quack/result.rb +64 -0
  46. data/lib/quack/sql.rb +183 -0
  47. data/lib/quack/types.rb +40 -0
  48. data/lib/quack/variant.rb +298 -0
  49. data/lib/quack.rb +34 -0
  50. metadata +136 -0
@@ -0,0 +1,843 @@
1
+ use crate::binary::{BinaryReader, BinaryWriter, HugeIntParts};
2
+ use crate::constants::{OPTIONAL_INDEX_INVALID, QUACK_V1, QUACK_V3};
3
+ use crate::errors::{QuackError, Result};
4
+ use crate::logical_types::{LogicalType, decode_logical_type, encode_logical_type};
5
+ use crate::vector::{
6
+ DataChunk, decode_data_chunk, decode_data_chunk_wrapper, encode_data_chunk,
7
+ encode_data_chunk_wrapper,
8
+ };
9
+
10
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
11
+ #[repr(u64)]
12
+ pub enum MessageType {
13
+ Invalid = 0,
14
+ ConnectionRequest = 1,
15
+ ConnectionResponse = 2,
16
+ PrepareRequest = 3,
17
+ PrepareResponse = 4,
18
+ FetchRequest = 7,
19
+ FetchResponse = 8,
20
+ SendDataRequest = 9,
21
+ SuccessResponse = 10,
22
+ DisconnectMessage = 11,
23
+ CancelRequest = 12,
24
+ SendDataResponse = 14,
25
+ Acknowledgement = 15,
26
+ HeartbeatRequest = 16,
27
+ ErrorResponse = 100,
28
+ }
29
+
30
+ impl TryFrom<u64> for MessageType {
31
+ type Error = QuackError;
32
+
33
+ fn try_from(value: u64) -> Result<Self> {
34
+ Ok(match value {
35
+ 0 => Self::Invalid,
36
+ 1 => Self::ConnectionRequest,
37
+ 2 => Self::ConnectionResponse,
38
+ 3 => Self::PrepareRequest,
39
+ 4 => Self::PrepareResponse,
40
+ 7 => Self::FetchRequest,
41
+ 8 => Self::FetchResponse,
42
+ 9 => Self::SendDataRequest,
43
+ 10 => Self::SuccessResponse,
44
+ 11 => Self::DisconnectMessage,
45
+ 12 => Self::CancelRequest,
46
+ 14 => Self::SendDataResponse,
47
+ 15 => Self::Acknowledgement,
48
+ 16 => Self::HeartbeatRequest,
49
+ 100 => Self::ErrorResponse,
50
+ _ => {
51
+ return Err(QuackError::protocol(format!(
52
+ "unknown message type {value}"
53
+ )));
54
+ }
55
+ })
56
+ }
57
+ }
58
+
59
+ #[derive(Clone, Debug, PartialEq, Eq)]
60
+ pub(crate) struct MessageHeader {
61
+ pub(crate) message_type: MessageType,
62
+ pub(crate) connection_id: Option<String>,
63
+ pub(crate) client_query_id: Option<u64>,
64
+ }
65
+
66
+ impl MessageHeader {
67
+ pub(crate) fn new(message_type: MessageType) -> Self {
68
+ Self {
69
+ message_type,
70
+ connection_id: None,
71
+ client_query_id: None,
72
+ }
73
+ }
74
+
75
+ pub(crate) fn with_connection(mut self, connection_id: impl Into<String>) -> Self {
76
+ self.connection_id = Some(connection_id.into());
77
+ self
78
+ }
79
+
80
+ pub(crate) fn with_client_query_id(mut self, client_query_id: u64) -> Self {
81
+ self.client_query_id = Some(client_query_id);
82
+ self
83
+ }
84
+ }
85
+
86
+ #[derive(Clone, Debug, PartialEq)]
87
+ pub(crate) enum QuackMessage {
88
+ ConnectionRequest {
89
+ header: MessageHeader,
90
+ auth_string: Option<String>,
91
+ client_duckdb_version: Option<String>,
92
+ client_platform: Option<String>,
93
+ min_supported_quack_version: u64,
94
+ max_supported_quack_version: u64,
95
+ client_id: Option<String>,
96
+ heartbeat_timeout_seconds: u64,
97
+ },
98
+ ConnectionResponse {
99
+ header: MessageHeader,
100
+ server_duckdb_version: Option<String>,
101
+ server_platform: Option<String>,
102
+ quack_version: Option<u64>,
103
+ heartbeat_timeout_seconds: Option<u64>,
104
+ },
105
+ PrepareRequest {
106
+ header: MessageHeader,
107
+ sql: String,
108
+ query_uuid: Option<HugeIntParts>,
109
+ inline_rows: Option<u64>,
110
+ },
111
+ PrepareResponse {
112
+ header: MessageHeader,
113
+ result_types: Vec<LogicalType>,
114
+ result_names: Vec<String>,
115
+ needs_more_fetch: bool,
116
+ results: Vec<DataChunk>,
117
+ result_uuid: HugeIntParts,
118
+ },
119
+ FetchRequest {
120
+ header: MessageHeader,
121
+ result_uuid: HugeIntParts,
122
+ batch_index: Option<u64>,
123
+ ack_index: Option<u64>,
124
+ },
125
+ FetchResponse {
126
+ header: MessageHeader,
127
+ results: Vec<DataChunk>,
128
+ total_batches: Option<u64>,
129
+ batch_index: Option<u64>,
130
+ },
131
+ HeartbeatRequest {
132
+ header: MessageHeader,
133
+ },
134
+ AppendRequest {
135
+ header: MessageHeader,
136
+ schema_name: Option<String>,
137
+ table_name: String,
138
+ append_chunk: DataChunk,
139
+ },
140
+ SuccessResponse {
141
+ header: MessageHeader,
142
+ },
143
+ Disconnect {
144
+ header: MessageHeader,
145
+ },
146
+ ErrorResponse {
147
+ header: MessageHeader,
148
+ message: String,
149
+ },
150
+ }
151
+
152
+ impl QuackMessage {
153
+ pub(crate) fn header(&self) -> &MessageHeader {
154
+ match self {
155
+ Self::ConnectionRequest { header, .. }
156
+ | Self::ConnectionResponse { header, .. }
157
+ | Self::PrepareRequest { header, .. }
158
+ | Self::PrepareResponse { header, .. }
159
+ | Self::FetchRequest { header, .. }
160
+ | Self::FetchResponse { header, .. }
161
+ | Self::HeartbeatRequest { header }
162
+ | Self::AppendRequest { header, .. }
163
+ | Self::SuccessResponse { header }
164
+ | Self::Disconnect { header }
165
+ | Self::ErrorResponse { header, .. } => header,
166
+ }
167
+ }
168
+
169
+ pub(crate) fn message_type(&self) -> MessageType {
170
+ self.header().message_type
171
+ }
172
+ }
173
+
174
+ #[cfg(test)]
175
+ pub(crate) fn encode_message(message: &QuackMessage) -> Result<Vec<u8>> {
176
+ encode_message_for_version(message, QUACK_V3)
177
+ }
178
+
179
+ pub(crate) fn encode_message_for_version(
180
+ message: &QuackMessage,
181
+ quack_version: u64,
182
+ ) -> Result<Vec<u8>> {
183
+ let mut writer = BinaryWriter::new();
184
+ encode_header(&mut writer, message.header())?;
185
+ match quack_version {
186
+ QUACK_V1 => encode_body_v1(&mut writer, message)?,
187
+ QUACK_V3 => encode_body_v3(&mut writer, message)?,
188
+ other => {
189
+ return Err(QuackError::protocol(format!(
190
+ "unsupported Quack protocol version {other}"
191
+ )));
192
+ }
193
+ }
194
+ Ok(writer.into_bytes())
195
+ }
196
+
197
+ #[cfg(test)]
198
+ pub(crate) fn decode_message(bytes: &[u8]) -> Result<QuackMessage> {
199
+ decode_message_for_version(bytes, QUACK_V3)
200
+ }
201
+
202
+ pub(crate) fn decode_message_for_version(bytes: &[u8], quack_version: u64) -> Result<QuackMessage> {
203
+ let mut reader = BinaryReader::new(bytes);
204
+ let header = decode_header(&mut reader)?;
205
+ let message = match quack_version {
206
+ QUACK_V1 => decode_body_v1(&mut reader, header)?,
207
+ QUACK_V3 => decode_body_v3(&mut reader, header)?,
208
+ other => {
209
+ return Err(QuackError::protocol(format!(
210
+ "unsupported Quack protocol version {other}"
211
+ )));
212
+ }
213
+ };
214
+ reader.assert_eof()?;
215
+ Ok(message)
216
+ }
217
+
218
+ pub(crate) fn encode_header(writer: &mut BinaryWriter, header: &MessageHeader) -> Result<()> {
219
+ writer.write_object(|object| {
220
+ object.write_field(1, |object| object.write_uleb(header.message_type as u64))?;
221
+ if let Some(connection_id) = header
222
+ .connection_id
223
+ .as_deref()
224
+ .filter(|value| !value.is_empty())
225
+ {
226
+ object.write_field(2, |object| object.write_string(connection_id))?;
227
+ }
228
+ object.write_field(3, |object| {
229
+ object.write_uleb(header.client_query_id.unwrap_or(OPTIONAL_INDEX_INVALID))
230
+ })?;
231
+ Ok(())
232
+ })
233
+ }
234
+
235
+ pub(crate) fn decode_header(reader: &mut BinaryReader<'_>) -> Result<MessageHeader> {
236
+ reader.read_object(|object| {
237
+ let message_type =
238
+ MessageType::try_from(object.read_required_field(1, |object| object.read_uleb_u64())?)?;
239
+ let connection_id = object.read_optional_field(
240
+ 2,
241
+ |object| Ok(Some(object.read_string()?)),
242
+ None::<String>,
243
+ )?;
244
+ let client_query_id_raw = object.read_required_field(3, |object| object.read_uleb_u64())?;
245
+ Ok(MessageHeader {
246
+ message_type,
247
+ connection_id: connection_id.filter(|value| !value.is_empty()),
248
+ client_query_id: (client_query_id_raw != OPTIONAL_INDEX_INVALID)
249
+ .then_some(client_query_id_raw),
250
+ })
251
+ })
252
+ }
253
+
254
+ fn encode_body_v1(writer: &mut BinaryWriter, message: &QuackMessage) -> Result<()> {
255
+ match message {
256
+ QuackMessage::ConnectionRequest {
257
+ auth_string,
258
+ client_duckdb_version,
259
+ client_platform,
260
+ min_supported_quack_version,
261
+ max_supported_quack_version,
262
+ ..
263
+ } => writer.write_object(|object| {
264
+ write_optional_string(object, 1, auth_string.as_deref())?;
265
+ write_optional_string(object, 2, client_duckdb_version.as_deref())?;
266
+ write_optional_string(object, 3, client_platform.as_deref())?;
267
+ write_optional_index_default_zero(object, 4, *min_supported_quack_version)?;
268
+ write_optional_index_default_zero(object, 5, *max_supported_quack_version)?;
269
+ Ok(())
270
+ }),
271
+ QuackMessage::ConnectionResponse {
272
+ server_duckdb_version,
273
+ server_platform,
274
+ quack_version,
275
+ ..
276
+ } => writer.write_object(|object| {
277
+ write_optional_string(object, 1, server_duckdb_version.as_deref())?;
278
+ write_optional_string(object, 2, server_platform.as_deref())?;
279
+ if let Some(quack_version) = quack_version {
280
+ object.write_field(3, |object| object.write_uleb(*quack_version))?;
281
+ }
282
+ Ok(())
283
+ }),
284
+ QuackMessage::PrepareRequest { sql, .. } => writer.write_object(|object| {
285
+ write_optional_string(object, 1, Some(sql))?;
286
+ Ok(())
287
+ }),
288
+ QuackMessage::PrepareResponse {
289
+ result_types,
290
+ result_names,
291
+ needs_more_fetch,
292
+ results,
293
+ result_uuid,
294
+ ..
295
+ } => writer.write_object(|object| {
296
+ if !result_types.is_empty() {
297
+ object.write_field(1, |object| {
298
+ object.write_list(result_types, |object, logical_type, _| {
299
+ encode_logical_type(object, logical_type)
300
+ })
301
+ })?;
302
+ }
303
+ if !result_names.is_empty() {
304
+ object.write_field(2, |object| {
305
+ object.write_list(result_names, |object, name, _| object.write_string(name))
306
+ })?;
307
+ }
308
+ if *needs_more_fetch {
309
+ object.write_field(3, |object| object.write_bool(true))?;
310
+ }
311
+ if !results.is_empty() {
312
+ object.write_field(4, |object| write_chunk_pointer_list(object, results))?;
313
+ }
314
+ object.write_field(5, |object| object.write_huge_int_parts(*result_uuid))?;
315
+ Ok(())
316
+ }),
317
+ QuackMessage::FetchRequest { result_uuid, .. } => writer.write_object(|object| {
318
+ object.write_field(1, |object| object.write_huge_int_parts(*result_uuid))?;
319
+ Ok(())
320
+ }),
321
+ QuackMessage::FetchResponse {
322
+ results,
323
+ batch_index,
324
+ ..
325
+ } => writer.write_object(|object| {
326
+ if !results.is_empty() {
327
+ object.write_field(1, |object| write_chunk_pointer_list(object, results))?;
328
+ }
329
+ object.write_field(2, |object| {
330
+ object.write_uleb(batch_index.unwrap_or(OPTIONAL_INDEX_INVALID))
331
+ })?;
332
+ Ok(())
333
+ }),
334
+ QuackMessage::AppendRequest {
335
+ schema_name,
336
+ table_name,
337
+ append_chunk,
338
+ ..
339
+ } => writer.write_object(|object| {
340
+ write_optional_string(object, 1, schema_name.as_deref())?;
341
+ write_optional_string(object, 2, Some(table_name))?;
342
+ object.write_field(3, |object| {
343
+ object.write_nullable(Some(append_chunk), encode_data_chunk_wrapper)
344
+ })?;
345
+ Ok(())
346
+ }),
347
+ QuackMessage::HeartbeatRequest { .. } => Err(QuackError::protocol(
348
+ "HEARTBEAT_REQUEST is not supported by Quack protocol v1",
349
+ )),
350
+ QuackMessage::SuccessResponse { .. } | QuackMessage::Disconnect { .. } => {
351
+ writer.write_object(|_| Ok(()))
352
+ }
353
+ QuackMessage::ErrorResponse { message, .. } => writer.write_object(|object| {
354
+ write_optional_string(object, 1, Some(message))?;
355
+ Ok(())
356
+ }),
357
+ }
358
+ }
359
+
360
+ fn encode_body_v3(writer: &mut BinaryWriter, message: &QuackMessage) -> Result<()> {
361
+ match message {
362
+ QuackMessage::ConnectionRequest {
363
+ auth_string,
364
+ client_duckdb_version,
365
+ client_platform,
366
+ min_supported_quack_version,
367
+ max_supported_quack_version,
368
+ client_id,
369
+ heartbeat_timeout_seconds,
370
+ ..
371
+ } => writer.write_object(|object| {
372
+ write_optional_string(object, 1, auth_string.as_deref())?;
373
+ write_optional_string(object, 2, client_duckdb_version.as_deref())?;
374
+ write_optional_string(object, 3, client_platform.as_deref())?;
375
+ write_optional_index_default_zero(object, 4, *min_supported_quack_version)?;
376
+ write_optional_index_default_zero(object, 5, *max_supported_quack_version)?;
377
+ write_optional_string(object, 6, client_id.as_deref())?;
378
+ write_optional_index_default_zero(object, 7, *heartbeat_timeout_seconds)?;
379
+ Ok(())
380
+ }),
381
+ QuackMessage::ConnectionResponse {
382
+ server_duckdb_version,
383
+ server_platform,
384
+ quack_version,
385
+ heartbeat_timeout_seconds,
386
+ ..
387
+ } => writer.write_object(|object| {
388
+ write_optional_string(object, 1, server_duckdb_version.as_deref())?;
389
+ write_optional_string(object, 2, server_platform.as_deref())?;
390
+ if let Some(quack_version) = quack_version {
391
+ object.write_field(3, |object| object.write_uleb(*quack_version))?;
392
+ }
393
+ if let Some(heartbeat_timeout_seconds) = heartbeat_timeout_seconds {
394
+ object.write_field(4, |object| object.write_uleb(*heartbeat_timeout_seconds))?;
395
+ }
396
+ Ok(())
397
+ }),
398
+ QuackMessage::PrepareRequest {
399
+ sql,
400
+ query_uuid,
401
+ inline_rows,
402
+ ..
403
+ } => {
404
+ let query_uuid = query_uuid.ok_or_else(|| {
405
+ QuackError::protocol("protocol v3 PREPARE_REQUEST requires query_uuid")
406
+ })?;
407
+ writer.write_object(|object| {
408
+ write_optional_string(object, 1, Some(sql))?;
409
+ object.write_field(2, |object| object.write_huge_int_parts(query_uuid))?;
410
+ if let Some(inline_rows) = inline_rows {
411
+ object.write_field(3, |object| object.write_uleb(*inline_rows))?;
412
+ }
413
+ Ok(())
414
+ })
415
+ }
416
+ QuackMessage::PrepareResponse {
417
+ result_types,
418
+ result_names,
419
+ needs_more_fetch,
420
+ results,
421
+ result_uuid,
422
+ ..
423
+ } => writer.write_object(|object| {
424
+ if !result_types.is_empty() {
425
+ object.write_field(1, |object| {
426
+ object.write_list(result_types, |object, logical_type, _| {
427
+ encode_logical_type(object, logical_type)
428
+ })
429
+ })?;
430
+ }
431
+ if !result_names.is_empty() {
432
+ object.write_field(2, |object| {
433
+ object.write_list(result_names, |object, name, _| object.write_string(name))
434
+ })?;
435
+ }
436
+ if *needs_more_fetch {
437
+ object.write_field(3, |object| object.write_bool(true))?;
438
+ }
439
+ if !results.is_empty() {
440
+ object.write_field(4, |object| write_chunk_pointer_list(object, results))?;
441
+ }
442
+ object.write_field(5, |object| object.write_huge_int_parts(*result_uuid))?;
443
+ Ok(())
444
+ }),
445
+ QuackMessage::FetchRequest {
446
+ result_uuid,
447
+ batch_index,
448
+ ack_index,
449
+ ..
450
+ } => {
451
+ let batch_index = batch_index.ok_or_else(|| {
452
+ QuackError::protocol("protocol v3 FETCH_REQUEST requires batch_index")
453
+ })?;
454
+ writer.write_object(|object| {
455
+ object.write_field(1, |object| object.write_huge_int_parts(*result_uuid))?;
456
+ write_optional_index_default_zero(object, 2, batch_index)?;
457
+ write_optional_index_default_zero(object, 3, ack_index.unwrap_or(0))?;
458
+ Ok(())
459
+ })
460
+ }
461
+ QuackMessage::FetchResponse {
462
+ results,
463
+ total_batches,
464
+ batch_index,
465
+ ..
466
+ } => {
467
+ writer.write_object(|object| {
468
+ write_optional_index_default_zero(object, 1, results.len() as u64)?;
469
+ write_optional_index(object, 2, *total_batches)?;
470
+ write_optional_index(object, 3, *batch_index)?;
471
+ Ok(())
472
+ })?;
473
+ for result in results {
474
+ encode_data_chunk(writer, result)?;
475
+ }
476
+ Ok(())
477
+ }
478
+ QuackMessage::AppendRequest { .. } => Err(QuackError::protocol(
479
+ "APPEND_REQUEST is not supported by Quack protocol v3",
480
+ )),
481
+ QuackMessage::SuccessResponse { .. }
482
+ | QuackMessage::Disconnect { .. }
483
+ | QuackMessage::HeartbeatRequest { .. } => writer.write_object(|_| Ok(())),
484
+ QuackMessage::ErrorResponse { message, .. } => writer.write_object(|object| {
485
+ write_optional_string(object, 1, Some(message))?;
486
+ Ok(())
487
+ }),
488
+ }
489
+ }
490
+
491
+ fn decode_body_v1(reader: &mut BinaryReader<'_>, header: MessageHeader) -> Result<QuackMessage> {
492
+ match header.message_type {
493
+ MessageType::ConnectionRequest => reader.read_object(|object| {
494
+ Ok(QuackMessage::ConnectionRequest {
495
+ header,
496
+ auth_string: read_optional_string(object, 1)?,
497
+ client_duckdb_version: read_optional_string(object, 2)?,
498
+ client_platform: read_optional_string(object, 3)?,
499
+ min_supported_quack_version: object.read_optional_field(
500
+ 4,
501
+ |object| object.read_uleb_u64(),
502
+ 0,
503
+ )?,
504
+ max_supported_quack_version: object.read_optional_field(
505
+ 5,
506
+ |object| object.read_uleb_u64(),
507
+ 0,
508
+ )?,
509
+ client_id: None,
510
+ heartbeat_timeout_seconds: 0,
511
+ })
512
+ }),
513
+ MessageType::ConnectionResponse => reader.read_object(|object| {
514
+ Ok(QuackMessage::ConnectionResponse {
515
+ header,
516
+ server_duckdb_version: read_optional_string(object, 1)?,
517
+ server_platform: read_optional_string(object, 2)?,
518
+ quack_version: object.read_optional_field(
519
+ 3,
520
+ |object| Ok(Some(object.read_uleb_u64()?)),
521
+ None::<u64>,
522
+ )?,
523
+ heartbeat_timeout_seconds: None,
524
+ })
525
+ }),
526
+ MessageType::PrepareRequest => reader.read_object(|object| {
527
+ Ok(QuackMessage::PrepareRequest {
528
+ header,
529
+ sql: object.read_optional_field(1, |object| object.read_string(), String::new())?,
530
+ query_uuid: None,
531
+ inline_rows: None,
532
+ })
533
+ }),
534
+ MessageType::PrepareResponse => reader.read_object(|object| {
535
+ Ok(QuackMessage::PrepareResponse {
536
+ header,
537
+ result_types: object.read_optional_field(
538
+ 1,
539
+ |object| object.read_list(|object, _| decode_logical_type(object)),
540
+ Vec::new(),
541
+ )?,
542
+ result_names: object.read_optional_field(
543
+ 2,
544
+ |object| object.read_list(|object, _| object.read_string()),
545
+ Vec::new(),
546
+ )?,
547
+ needs_more_fetch: object.read_optional_field(
548
+ 3,
549
+ |object| object.read_bool(),
550
+ false,
551
+ )?,
552
+ results: object.read_optional_field(4, read_chunk_pointer_list, Vec::new())?,
553
+ result_uuid: object
554
+ .read_required_field(5, |object| object.read_huge_int_parts())?,
555
+ })
556
+ }),
557
+ MessageType::FetchRequest => reader.read_object(|object| {
558
+ Ok(QuackMessage::FetchRequest {
559
+ header,
560
+ result_uuid: object
561
+ .read_required_field(1, |object| object.read_huge_int_parts())?,
562
+ batch_index: None,
563
+ ack_index: None,
564
+ })
565
+ }),
566
+ MessageType::FetchResponse => reader.read_object(|object| {
567
+ let results = object.read_optional_field(1, read_chunk_pointer_list, Vec::new())?;
568
+ let batch_index = object.read_required_field(2, |object| object.read_uleb_u64())?;
569
+ Ok(QuackMessage::FetchResponse {
570
+ header,
571
+ results,
572
+ total_batches: None,
573
+ batch_index: (batch_index != OPTIONAL_INDEX_INVALID).then_some(batch_index),
574
+ })
575
+ }),
576
+ MessageType::SendDataRequest => reader.read_object(|object| {
577
+ let schema_name = read_optional_string(object, 1)?;
578
+ let table_name =
579
+ object.read_optional_field(2, |object| object.read_string(), String::new())?;
580
+ let append_chunk = object.read_optional_field(
581
+ 3,
582
+ |object| object.read_nullable(decode_data_chunk_wrapper),
583
+ None,
584
+ )?;
585
+ Ok(QuackMessage::AppendRequest {
586
+ header,
587
+ schema_name,
588
+ table_name,
589
+ append_chunk: append_chunk.ok_or_else(|| {
590
+ QuackError::protocol("APPEND_REQUEST is missing append_chunk")
591
+ })?,
592
+ })
593
+ }),
594
+ MessageType::SuccessResponse => {
595
+ reader.read_object(|_| Ok(QuackMessage::SuccessResponse { header }))
596
+ }
597
+ MessageType::DisconnectMessage => {
598
+ reader.read_object(|_| Ok(QuackMessage::Disconnect { header }))
599
+ }
600
+ MessageType::ErrorResponse => reader.read_object(|object| {
601
+ Ok(QuackMessage::ErrorResponse {
602
+ header,
603
+ message: read_error_message(object)?,
604
+ })
605
+ }),
606
+ other => Err(QuackError::protocol(format!(
607
+ "cannot decode unsupported protocol-v1 message type {other:?}"
608
+ ))),
609
+ }
610
+ }
611
+
612
+ fn decode_body_v3(reader: &mut BinaryReader<'_>, header: MessageHeader) -> Result<QuackMessage> {
613
+ match header.message_type {
614
+ MessageType::ConnectionRequest => reader.read_object(|object| {
615
+ Ok(QuackMessage::ConnectionRequest {
616
+ header,
617
+ auth_string: read_optional_string(object, 1)?,
618
+ client_duckdb_version: read_optional_string(object, 2)?,
619
+ client_platform: read_optional_string(object, 3)?,
620
+ min_supported_quack_version: object.read_optional_field(
621
+ 4,
622
+ |object| object.read_uleb_u64(),
623
+ 0,
624
+ )?,
625
+ max_supported_quack_version: object.read_optional_field(
626
+ 5,
627
+ |object| object.read_uleb_u64(),
628
+ 0,
629
+ )?,
630
+ client_id: read_optional_string(object, 6)?,
631
+ heartbeat_timeout_seconds: object.read_optional_field(
632
+ 7,
633
+ |object| object.read_uleb_u64(),
634
+ 0,
635
+ )?,
636
+ })
637
+ }),
638
+ MessageType::ConnectionResponse => reader.read_object(|object| {
639
+ Ok(QuackMessage::ConnectionResponse {
640
+ header,
641
+ server_duckdb_version: read_optional_string(object, 1)?,
642
+ server_platform: read_optional_string(object, 2)?,
643
+ quack_version: object.read_optional_field(
644
+ 3,
645
+ |object| Ok(Some(object.read_uleb_u64()?)),
646
+ None::<u64>,
647
+ )?,
648
+ heartbeat_timeout_seconds: object.read_optional_field(
649
+ 4,
650
+ |object| Ok(Some(object.read_uleb_u64()?)),
651
+ None::<u64>,
652
+ )?,
653
+ })
654
+ }),
655
+ MessageType::PrepareRequest => reader.read_object(|object| {
656
+ Ok(QuackMessage::PrepareRequest {
657
+ header,
658
+ sql: object.read_optional_field(1, |object| object.read_string(), String::new())?,
659
+ query_uuid: Some(
660
+ object.read_required_field(2, |object| object.read_huge_int_parts())?,
661
+ ),
662
+ inline_rows: read_optional_index(object, 3)?,
663
+ })
664
+ }),
665
+ MessageType::PrepareResponse => reader.read_object(|object| {
666
+ Ok(QuackMessage::PrepareResponse {
667
+ header,
668
+ result_types: object.read_optional_field(
669
+ 1,
670
+ |object| object.read_list(|object, _| decode_logical_type(object)),
671
+ Vec::new(),
672
+ )?,
673
+ result_names: object.read_optional_field(
674
+ 2,
675
+ |object| object.read_list(|object, _| object.read_string()),
676
+ Vec::new(),
677
+ )?,
678
+ needs_more_fetch: object.read_optional_field(
679
+ 3,
680
+ |object| object.read_bool(),
681
+ false,
682
+ )?,
683
+ results: object.read_optional_field(4, read_chunk_pointer_list, Vec::new())?,
684
+ result_uuid: object
685
+ .read_required_field(5, |object| object.read_huge_int_parts())?,
686
+ })
687
+ }),
688
+ MessageType::FetchRequest => reader.read_object(|object| {
689
+ Ok(QuackMessage::FetchRequest {
690
+ header,
691
+ result_uuid: object
692
+ .read_required_field(1, |object| object.read_huge_int_parts())?,
693
+ batch_index: read_optional_index(object, 2)?,
694
+ ack_index: Some(object.read_optional_field(
695
+ 3,
696
+ |object| object.read_uleb_u64(),
697
+ 0,
698
+ )?),
699
+ })
700
+ }),
701
+ MessageType::FetchResponse => {
702
+ let (chunk_count, total_batches, batch_index) = reader.read_object(|object| {
703
+ Ok((
704
+ object.read_optional_field(1, |object| object.read_uleb_u64(), 0)?,
705
+ read_optional_index(object, 2)?,
706
+ read_optional_index(object, 3)?,
707
+ ))
708
+ })?;
709
+ let chunk_count = usize::try_from(chunk_count)
710
+ .map_err(|_| QuackError::protocol("FETCH_RESPONSE chunk_count is too large"))?;
711
+ let mut results = Vec::with_capacity(chunk_count.min(1024));
712
+ for _ in 0..chunk_count {
713
+ results.push(decode_data_chunk(reader)?);
714
+ }
715
+ Ok(QuackMessage::FetchResponse {
716
+ header,
717
+ results,
718
+ total_batches,
719
+ batch_index,
720
+ })
721
+ }
722
+ MessageType::SuccessResponse => {
723
+ reader.read_object(|_| Ok(QuackMessage::SuccessResponse { header }))
724
+ }
725
+ MessageType::DisconnectMessage => {
726
+ reader.read_object(|_| Ok(QuackMessage::Disconnect { header }))
727
+ }
728
+ MessageType::HeartbeatRequest => {
729
+ reader.read_object(|_| Ok(QuackMessage::HeartbeatRequest { header }))
730
+ }
731
+ MessageType::ErrorResponse => reader.read_object(|object| {
732
+ Ok(QuackMessage::ErrorResponse {
733
+ header,
734
+ message: read_error_message(object)?,
735
+ })
736
+ }),
737
+ other => Err(QuackError::protocol(format!(
738
+ "cannot decode unsupported message type {other:?}"
739
+ ))),
740
+ }
741
+ }
742
+
743
+ fn read_chunk_pointer_list(reader: &mut BinaryReader<'_>) -> Result<Vec<DataChunk>> {
744
+ reader.read_list(|reader, _| {
745
+ reader
746
+ .read_nullable(decode_data_chunk_wrapper)?
747
+ .ok_or_else(|| {
748
+ QuackError::protocol("encountered null DataChunk pointer in result list")
749
+ })
750
+ })
751
+ }
752
+
753
+ fn write_chunk_pointer_list(writer: &mut BinaryWriter, chunks: &[DataChunk]) -> Result<()> {
754
+ writer.write_list(chunks, |writer, chunk, _| {
755
+ writer.write_nullable(Some(chunk), encode_data_chunk_wrapper)
756
+ })
757
+ }
758
+
759
+ fn write_optional_string(
760
+ writer: &mut BinaryWriter,
761
+ field_id: u16,
762
+ value: Option<&str>,
763
+ ) -> Result<()> {
764
+ if let Some(value) = value.filter(|value| !value.is_empty()) {
765
+ writer.write_field(field_id, |writer| writer.write_string(value))?;
766
+ }
767
+ Ok(())
768
+ }
769
+
770
+ fn write_optional_index_default_zero(
771
+ writer: &mut BinaryWriter,
772
+ field_id: u16,
773
+ value: u64,
774
+ ) -> Result<()> {
775
+ if value != 0 {
776
+ writer.write_field(field_id, |writer| writer.write_uleb(value))?;
777
+ }
778
+ Ok(())
779
+ }
780
+
781
+ fn write_optional_index(
782
+ writer: &mut BinaryWriter,
783
+ field_id: u16,
784
+ value: Option<u64>,
785
+ ) -> Result<()> {
786
+ if let Some(value) = value {
787
+ writer.write_field(field_id, |writer| writer.write_uleb(value))?;
788
+ }
789
+ Ok(())
790
+ }
791
+
792
+ fn read_optional_index(reader: &mut BinaryReader<'_>, field_id: u16) -> Result<Option<u64>> {
793
+ let value = reader.read_optional_field(
794
+ field_id,
795
+ |reader| Ok(Some(reader.read_uleb_u64()?)),
796
+ None::<u64>,
797
+ )?;
798
+ Ok(value.filter(|value| *value != OPTIONAL_INDEX_INVALID))
799
+ }
800
+
801
+ fn read_optional_string(reader: &mut BinaryReader<'_>, field_id: u16) -> Result<Option<String>> {
802
+ reader.read_optional_field(
803
+ field_id,
804
+ |reader| Ok(Some(reader.read_string()?)),
805
+ None::<String>,
806
+ )
807
+ }
808
+
809
+ /// Reads an ERROR_RESPONSE body. Servers since DuckDB 2.0 follow the message with
810
+ /// the exception type (2), extra info (3) and a must-invalidate flag (4); the
811
+ /// client reports only the message, so the rest is read past.
812
+ fn read_error_message(object: &mut BinaryReader<'_>) -> Result<String> {
813
+ read_error_fields(object).map(|(message, _)| message)
814
+ }
815
+
816
+ // Share error decoding with bounded proxy inspection. Do not preallocate using
817
+ // the untrusted extra-info count: malformed input must fail before allocation.
818
+ pub(crate) fn read_error_fields(object: &mut BinaryReader<'_>) -> Result<(String, bool)> {
819
+ let message = object.read_optional_field(1, |object| object.read_string(), String::new())?;
820
+ let exception = object.read_optional_field(2, |object| object.read_string(), String::new())?;
821
+ object.read_optional_field(
822
+ 3,
823
+ |object| {
824
+ let length = object.read_uleb_usize()?;
825
+ for _ in 0..length {
826
+ object.read_object(|entry| {
827
+ entry.read_required_field(0, |entry| entry.read_string())?;
828
+ entry.read_required_field(1, |entry| entry.read_string())?;
829
+ Ok(())
830
+ })?;
831
+ }
832
+ Ok(())
833
+ },
834
+ (),
835
+ )?;
836
+ let invalidated = object.read_optional_field(4, |object| object.read_bool(), false)?;
837
+ let fatal = invalidated
838
+ || matches!(
839
+ exception.as_str(),
840
+ "FATAL" | "Fatal" | "INTERNAL" | "Internal"
841
+ );
842
+ Ok((message, fatal))
843
+ }