1use crate::ast::ddl::CreateContinuousAggregate;
2use crate::ast::dml::{FromItem, QueryBody, Select, SelectItem, SetOperation, SetOperator, Values};
3use crate::ast::expr::{Expr, ExprKind};
4use crate::ast::{Location, Span, Statement, StatementKind};
5use crate::error::{ParserError, Result};
6use crate::nim_ffi::{self, OwnedBuffer, ParseResultKind};
7use serde::Deserialize;
8
9const MAX_SQL_INPUT_BYTES: usize = 1_048_576;
10const MAX_MESSAGEPACK_PAYLOAD_BYTES: usize = 1_048_576;
11const MAX_MESSAGEPACK_DEPTH: usize = 128;
12const MAX_MESSAGEPACK_VALUES: usize = 65_536;
13const SELECT_WRAPPER_PREFIX: &str = "SELECT ";
14const PARSER_CONTRACT_DESCRIPTOR: &str = include_str!("../nim-sql-parser/PARSER_CONTRACT_VERSION");
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17enum InputPreflightError {
18 TooLarge,
19 LengthOverflow,
20 InteriorNul,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24enum MessagePackPreflightError {
25 TooLarge,
26 TooDeep,
27 TooManyValues,
28 Truncated,
29 ReservedMarker,
30 TrailingBytes,
31}
32
33#[derive(Deserialize)]
34#[serde(deny_unknown_fields)]
35struct StagedContinuousAggregateStatement {
36 kind: StagedContinuousAggregateKind,
37 span: Span,
38}
39
40#[derive(Deserialize)]
41#[serde(tag = "variant")]
42enum StagedContinuousAggregateKind {
43 CreateContinuousAggregate(CreateContinuousAggregate),
44}
45
46pub(crate) mod continuous_aggregate_select_wire {
53 use crate::ast::{
54 Expr, FromItem, OrderByExpr, Select, SelectItem, SetOperation, Span, WithClause,
55 };
56 use serde::de::Error as _;
57 use serde::{Deserialize, Deserializer, Serialize, Serializer};
58
59 #[derive(Serialize)]
60 struct SelectWireRef<'a> {
61 variant: &'static str,
62 distinct: bool,
63 projection: &'a [SelectItem],
64 from: &'a [FromItem],
65 selection: &'a Option<Expr>,
66 group_by: Option<Vec<&'a Expr>>,
67 having: &'a Option<Expr>,
68 set_operations: &'a [SetOperation],
69 order_by: &'a [OrderByExpr],
70 limit: &'a Option<Expr>,
71 offset: &'a Option<Expr>,
72 span: Span,
73 }
74
75 #[derive(Deserialize)]
76 #[serde(deny_unknown_fields)]
77 struct SelectWire {
78 variant: String,
79 #[serde(default)]
80 with: Option<WithClause>,
81 distinct: bool,
82 projection: Vec<SelectItem>,
83 from: Vec<FromItem>,
84 selection: Option<Expr>,
85 group_by: Option<Vec<Expr>>,
86 having: Option<Expr>,
87 #[serde(default)]
88 set_operations: Vec<SetOperation>,
89 order_by: Vec<OrderByExpr>,
90 limit: Option<Expr>,
91 offset: Option<Expr>,
92 span: Span,
93 }
94
95 pub(crate) fn serialize<S>(
96 select: &Select,
97 serializer: S,
98 ) -> std::result::Result<S::Ok, S::Error>
99 where
100 S: Serializer,
101 {
102 use serde::ser::Error as _;
103 let group_by = select
107 .group_by
108 .as_ref()
109 .map(|items| {
110 items
111 .iter()
112 .map(|item| match item {
113 crate::ast::GroupByItem::Expr { expr } => Ok(expr),
114 _ => Err(S::Error::custom(
115 "staged continuous-aggregate queries cannot carry \
116 ROLLUP/CUBE/GROUPING SETS",
117 )),
118 })
119 .collect::<std::result::Result<Vec<_>, S::Error>>()
120 })
121 .transpose()?;
122 SelectWireRef {
123 variant: "Select",
124 distinct: select.distinct,
125 projection: &select.projection,
126 from: &select.from,
127 selection: &select.selection,
128 group_by,
129 having: &select.having,
130 set_operations: &select.set_operations,
131 order_by: &select.order_by,
132 limit: &select.limit,
133 offset: &select.offset,
134 span: select.span,
135 }
136 .serialize(serializer)
137 }
138
139 pub(crate) fn deserialize<'de, D>(deserializer: D) -> std::result::Result<Select, D::Error>
140 where
141 D: Deserializer<'de>,
142 {
143 let wire = SelectWire::deserialize(deserializer)?;
144 if wire.variant != "Select" {
145 return Err(D::Error::custom(format!(
146 "expected nested query variant `Select`, found `{}`",
147 wire.variant
148 )));
149 }
150 Ok(Select {
151 with: wire.with,
152 distinct: wire.distinct,
153 distinct_on: Vec::new(),
156 projection: wire.projection,
157 from: wire.from,
158 selection: wire.selection,
159 group_by: wire.group_by.map(|items| {
162 items
163 .into_iter()
164 .map(|expr| crate::ast::GroupByItem::Expr { expr })
165 .collect()
166 }),
167 having: wire.having,
168 windows: Vec::new(),
169 qualify: None,
170 set_operations: wire.set_operations,
171 order_by: wire.order_by,
172 limit: wire.limit,
173 offset: wire.offset,
174 limit_with_ties: false,
177 span: wire.span,
178 })
179 }
180}
181
182impl CreateContinuousAggregate {
183 #[doc(hidden)]
188 pub fn decode_staged_messagepack(linked_parser_contract: &str, payload: &[u8]) -> Result<Self> {
189 ensure_linked_parser_contract(linked_parser_contract)?;
190 validate_bounded_messagepack(payload).map_err(messagepack_preflight_error)?;
191 let decoded = rmp_serde::from_slice::<StagedContinuousAggregateStatement>(payload)
192 .map_err(messagepack_decode_error)?;
193 let StagedContinuousAggregateKind::CreateContinuousAggregate(statement) = decoded.kind;
194 if decoded.span != statement.span {
195 return Err(ParserError::UnexpectedToken {
196 line: 0,
197 column: 0,
198 expected: "matching outer and kind spans in MessagePack AST".to_string(),
199 found: "continuous aggregate outer span differs from kind span".to_string(),
200 });
201 }
202 Ok(statement)
203 }
204}
205
206pub fn parser_contract_version() -> String {
208 nim_ffi::parser_contract_version()
209}
210
211pub fn parse_sql(sql: &str) -> Result<Vec<Statement>> {
212 preflight_input(sql, 0).map_err(parser_error_from_preflight)?;
213 parse_sql_preflighted(sql)
214}
215
216fn parse_sql_preflighted(sql: &str) -> Result<Vec<Statement>> {
217 let tokens = scan_top_level_tokens(sql);
218 let ranges = top_level_statement_ranges(sql, &tokens);
219 let contains_set_operation = ranges.iter().any(|(start, end)| {
220 tokens.iter().any(|token| {
221 token.start >= *start
222 && token.end <= *end
223 && matches!(token.kind, TopLevelTokenKind::Word)
224 && set_operator(&sql[token.start..token.end]).is_some()
225 })
226 });
227 if contains_set_operation
228 && let Ok(statements) = parse_sql_via_ffi(sql)
229 && statements.len() == ranges.len()
230 {
231 return Ok(statements);
232 }
233 if let Some(statements) = parse_set_operation_batch(sql)? {
234 return Ok(statements);
235 }
236 parse_sql_via_ffi(sql)
237}
238
239fn parse_sql_via_ffi(sql: &str) -> Result<Vec<Statement>> {
240 ensure_linked_parser_contract(&nim_ffi::parser_contract_version())?;
241 let natural_join_markers = natural_join_markers(sql);
242 let normalized_sql = normalize_quoted_identifiers(sql);
247 let result = nim_ffi::parse_sql(&normalized_sql).map_err(parser_error_from_ffi_input)?;
248 match result.kind {
249 ParseResultKind::Ok => {
250 let buffer = OwnedBuffer::new(result.buffer_ptr, result.buffer_len);
251 if buffer.as_slice().is_empty() {
256 return Err(ParserError::UnexpectedToken {
257 line: 0,
258 column: 0,
259 expected: "MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
260 found: "empty payload from Nim parser (leaked exception at FFI boundary; \
261 see issue #40)"
262 .to_string(),
263 });
264 }
265 validate_bounded_messagepack(buffer.as_slice()).map_err(messagepack_preflight_error)?;
266 let mut statements = rmp_serde::from_slice::<Vec<Statement>>(buffer.as_slice())
267 .map_err(messagepack_decode_error)?;
268 annotate_natural_joins(&mut statements, natural_join_markers)?;
269 Ok(statements)
270 }
271 ParseResultKind::Error => {
272 let buffer = OwnedBuffer::new(result.error_ptr.cast(), result.error_len);
273 Err(parser_error_from_nim(
274 String::from_utf8_lossy(buffer.as_slice()).as_ref(),
275 ))
276 }
277 }
278}
279
280#[derive(Debug, Clone, Copy)]
281enum TopLevelTokenKind {
282 Word,
283 Semicolon,
284}
285
286#[derive(Debug, Clone, Copy)]
287struct TopLevelToken {
288 kind: TopLevelTokenKind,
289 start: usize,
290 end: usize,
291}
292
293#[derive(Debug, Clone, Copy)]
294struct SetOperationSpec {
295 operator: SetOperator,
296 all: bool,
297 span: Span,
298}
299
300fn parse_set_operation_batch(sql: &str) -> Result<Option<Vec<Statement>>> {
301 let tokens = scan_top_level_tokens(sql);
302 let ranges = top_level_statement_ranges(sql, &tokens);
303
304 let contains_set_operation = ranges.iter().any(|(start, end)| {
305 tokens.iter().any(|token| {
306 token.start >= *start
307 && token.end <= *end
308 && matches!(token.kind, TopLevelTokenKind::Word)
309 && set_operator(&sql[token.start..token.end]).is_some()
310 })
311 });
312 if !contains_set_operation {
313 return Ok(None);
314 }
315
316 let mut statements = Vec::new();
317 for (start, end) in ranges {
318 let words = tokens
319 .iter()
320 .copied()
321 .filter(|token| {
322 token.start >= start
323 && token.end <= end
324 && matches!(token.kind, TopLevelTokenKind::Word)
325 })
326 .collect::<Vec<_>>();
327 if words
328 .iter()
329 .any(|word| set_operator(&sql[word.start..word.end]).is_some())
330 {
331 statements.push(parse_set_operation_statement(sql, start, end, &words)?);
332 } else {
333 statements.extend(parse_sql_via_ffi(&sql[start..end])?);
334 }
335 }
336 Ok(Some(statements))
337}
338
339fn top_level_statement_ranges(sql: &str, tokens: &[TopLevelToken]) -> Vec<(usize, usize)> {
340 let mut ranges = Vec::new();
341 let mut start = 0;
342 for token in tokens {
343 if matches!(token.kind, TopLevelTokenKind::Semicolon) {
344 if !sql[start..token.start].trim().is_empty() {
345 ranges.push((start, token.start));
346 }
347 start = token.end;
348 }
349 }
350 if !sql[start..].trim().is_empty() {
351 ranges.push((start, sql.len()));
352 }
353 ranges
354}
355
356fn parse_set_operation_statement(
357 sql: &str,
358 statement_start: usize,
359 statement_end: usize,
360 words: &[TopLevelToken],
361) -> Result<Statement> {
362 let mut branch_ranges = Vec::new();
363 let mut operations = Vec::new();
364 let mut branch_start = statement_start;
365
366 for (index, word) in words.iter().enumerate() {
367 let Some(operator) = set_operator(&sql[word.start..word.end]) else {
368 continue;
369 };
370 branch_ranges.push((branch_start, word.start));
371 let all_word = words
372 .get(index + 1)
373 .filter(|next| sql[next.start..next.end].eq_ignore_ascii_case("all"));
374 let all = all_word.is_some();
375 branch_start = all_word.map_or(word.end, |token| token.end);
376 operations.push(SetOperationSpec {
377 operator,
378 all,
379 span: span_for_offsets(sql, word.start, word.end),
380 });
381 }
382 branch_ranges.push((branch_start, statement_end));
383
384 if branch_ranges.len() != operations.len() + 1 {
385 return Err(set_operation_parser_error(
386 "a SELECT query on both sides of every set operator",
387 "malformed set-operation chain",
388 ));
389 }
390
391 let mut branches = branch_ranges
392 .into_iter()
393 .map(|(start, end)| parse_query_body_fragment(&sql[start..end]))
394 .collect::<Result<Vec<_>>>()?;
395 let final_branch = branches.last_mut().ok_or_else(|| {
396 set_operation_parser_error("at least one query body", "empty set-operation chain")
397 })?;
398 let tail = take_query_tail(final_branch);
399
400 let mut terms = Vec::new();
401 let mut outer_operations = Vec::new();
402 let mut current = branches.remove(0);
403 for (operation, right) in operations.into_iter().zip(branches) {
404 if operation.operator == SetOperator::Intersect {
405 query_set_operations_mut(&mut current).push(SetOperation {
406 operator: operation.operator,
407 all: operation.all,
408 right: Box::new(right),
409 span: operation.span,
410 });
411 } else {
412 terms.push(current);
413 outer_operations.push(operation);
414 current = right;
415 }
416 }
417 terms.push(current);
418
419 let mut root = terms.remove(0);
420 for (operation, right) in outer_operations.into_iter().zip(terms) {
421 query_set_operations_mut(&mut root).push(SetOperation {
422 operator: operation.operator,
423 all: operation.all,
424 right: Box::new(right),
425 span: operation.span,
426 });
427 }
428 set_query_tail(&mut root, tail);
429 let span = query_body_span(&root);
430 Ok(Statement {
431 kind: match root {
432 QueryBody::Select(select) => StatementKind::Select(select),
433 QueryBody::Values(values) => StatementKind::Values(values),
434 },
435 span,
436 })
437}
438
439fn parse_query_body_fragment(sql: &str) -> Result<QueryBody> {
440 let mut statements = parse_sql_via_ffi(sql.trim())?;
441 if statements.len() != 1 {
442 return Err(set_operation_parser_error(
443 "exactly one query body per set-operation input",
444 format!("{} statements", statements.len()),
445 ));
446 }
447 match statements.remove(0).kind {
448 StatementKind::Select(select) => Ok(QueryBody::Select(select)),
449 StatementKind::Values(values) => Ok(QueryBody::Values(values)),
450 _ => Err(set_operation_parser_error(
451 "SELECT or VALUES query in set operation",
452 "non-query statement",
453 )),
454 }
455}
456
457fn query_set_operations_mut(body: &mut QueryBody) -> &mut Vec<SetOperation> {
458 match body {
459 QueryBody::Select(select) => &mut select.set_operations,
460 QueryBody::Values(values) => &mut values.set_operations,
461 }
462}
463
464type QueryTail = (
465 Vec<crate::ast::OrderByExpr>,
466 Option<Expr>,
467 Option<Expr>,
468 bool,
469);
470
471fn take_query_tail(body: &mut QueryBody) -> QueryTail {
472 match body {
473 QueryBody::Select(select) => (
474 std::mem::take(&mut select.order_by),
475 select.limit.take(),
476 select.offset.take(),
477 std::mem::take(&mut select.limit_with_ties),
478 ),
479 QueryBody::Values(values) => (
480 std::mem::take(&mut values.order_by),
481 values.limit.take(),
482 values.offset.take(),
483 std::mem::take(&mut values.limit_with_ties),
484 ),
485 }
486}
487
488fn set_query_tail(body: &mut QueryBody, tail: QueryTail) {
489 let (order_by, limit, offset, limit_with_ties) = tail;
490 match body {
491 QueryBody::Select(select) => {
492 select.order_by = order_by;
493 select.limit = limit;
494 select.offset = offset;
495 select.limit_with_ties = limit_with_ties;
496 }
497 QueryBody::Values(values) => {
498 values.order_by = order_by;
499 values.limit = limit;
500 values.offset = offset;
501 values.limit_with_ties = limit_with_ties;
502 }
503 }
504}
505
506fn query_body_span(body: &QueryBody) -> Span {
507 match body {
508 QueryBody::Select(select) => select.span,
509 QueryBody::Values(values) => values.span,
510 }
511}
512
513fn set_operator(operator: &str) -> Option<SetOperator> {
514 if operator.eq_ignore_ascii_case("union") {
515 Some(SetOperator::Union)
516 } else if operator.eq_ignore_ascii_case("intersect") {
517 Some(SetOperator::Intersect)
518 } else if operator.eq_ignore_ascii_case("except") {
519 Some(SetOperator::Except)
520 } else {
521 None
522 }
523}
524
525fn set_operation_parser_error(
526 expected: impl Into<String>,
527 found: impl Into<String>,
528) -> ParserError {
529 ParserError::UnexpectedToken {
530 line: 0,
531 column: 0,
532 expected: expected.into(),
533 found: found.into(),
534 }
535}
536
537fn span_for_offsets(sql: &str, start: usize, end: usize) -> Span {
538 fn location(sql: &str, offset: usize) -> Location {
539 let prefix = &sql[..offset.min(sql.len())];
540 let line = prefix.bytes().filter(|byte| *byte == b'\n').count() as u64 + 1;
541 let column = prefix
542 .rsplit_once('\n')
543 .map_or(prefix.len(), |(_, tail)| tail.len()) as u64
544 + 1;
545 Location::new(line, column)
546 }
547 Span::new(location(sql, start), location(sql, end.saturating_sub(1)))
548}
549
550fn scan_top_level_tokens(sql: &str) -> Vec<TopLevelToken> {
551 let bytes = sql.as_bytes();
552 let mut tokens = Vec::new();
553 let mut index = 0;
554 let mut depth = 0_u32;
555
556 while index < bytes.len() {
557 match bytes[index] {
558 b'\'' | b'"' => {
559 let quote = bytes[index];
560 index += 1;
561 while index < bytes.len() {
562 if bytes[index] == quote {
563 if bytes.get(index + 1) == Some("e) {
564 index += 2;
565 } else {
566 index += 1;
567 break;
568 }
569 } else {
570 index += 1;
571 }
572 }
573 }
574 b'-' if bytes.get(index + 1) == Some(&b'-') => {
575 index += 2;
576 while index < bytes.len() && bytes[index] != b'\n' {
577 index += 1;
578 }
579 }
580 b'/' if bytes.get(index + 1) == Some(&b'*') => {
581 index += 2;
582 while index + 1 < bytes.len() && !(bytes[index] == b'*' && bytes[index + 1] == b'/')
583 {
584 index += 1;
585 }
586 index = (index + 2).min(bytes.len());
587 }
588 b'(' => {
589 depth += 1;
590 index += 1;
591 }
592 b')' => {
593 depth = depth.saturating_sub(1);
594 index += 1;
595 }
596 b';' if depth == 0 => {
597 tokens.push(TopLevelToken {
598 kind: TopLevelTokenKind::Semicolon,
599 start: index,
600 end: index + 1,
601 });
602 index += 1;
603 }
604 byte if depth == 0 && (byte.is_ascii_alphabetic() || byte == b'_') => {
605 let start = index;
606 index += 1;
607 while index < bytes.len()
608 && (bytes[index].is_ascii_alphanumeric() || bytes[index] == b'_')
609 {
610 index += 1;
611 }
612 tokens.push(TopLevelToken {
613 kind: TopLevelTokenKind::Word,
614 start,
615 end: index,
616 });
617 }
618 _ => index += 1,
619 }
620 }
621 tokens
622}
623
624fn expected_parser_contract() -> &'static str {
625 PARSER_CONTRACT_DESCRIPTOR.trim()
626}
627
628fn ensure_linked_parser_contract(linked_parser_contract: &str) -> Result<()> {
629 ensure_parser_contract(expected_parser_contract(), linked_parser_contract)
630}
631
632fn ensure_parser_contract(expected: &str, linked_parser_contract: &str) -> Result<()> {
633 if linked_parser_contract == expected {
634 return Ok(());
635 }
636 Err(ParserError::UnexpectedToken {
637 line: 0,
638 column: 0,
639 expected: format!("linked Nim parser contract {expected}"),
640 found: format!("linked Nim parser contract {linked_parser_contract}"),
641 })
642}
643
644fn messagepack_decode_error(error: rmp_serde::decode::Error) -> ParserError {
645 ParserError::UnexpectedToken {
646 line: 0,
647 column: 0,
648 expected: "bounded MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
649 found: error.to_string(),
650 }
651}
652
653fn messagepack_preflight_error(error: MessagePackPreflightError) -> ParserError {
654 let found = match error {
655 MessagePackPreflightError::TooLarge => {
656 format!("MessagePack payload exceeds {MAX_MESSAGEPACK_PAYLOAD_BYTES} bytes")
657 }
658 MessagePackPreflightError::TooDeep => {
659 format!("MessagePack nesting exceeds {MAX_MESSAGEPACK_DEPTH} levels")
660 }
661 MessagePackPreflightError::TooManyValues => {
662 format!("MessagePack collection limit of {MAX_MESSAGEPACK_VALUES} values exceeded")
663 }
664 MessagePackPreflightError::Truncated => "truncated MessagePack payload".to_string(),
665 MessagePackPreflightError::ReservedMarker => "reserved MessagePack marker 0xc1".to_string(),
666 MessagePackPreflightError::TrailingBytes => {
667 "trailing bytes after MessagePack payload".to_string()
668 }
669 };
670 ParserError::UnexpectedToken {
671 line: 0,
672 column: 0,
673 expected: "bounded MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
674 found,
675 }
676}
677
678fn validate_bounded_messagepack(
679 payload: &[u8],
680) -> std::result::Result<(), MessagePackPreflightError> {
681 if payload.len() > MAX_MESSAGEPACK_PAYLOAD_BYTES {
682 return Err(MessagePackPreflightError::TooLarge);
683 }
684 let mut scanner = MessagePackScanner {
685 payload,
686 position: 0,
687 values: 0,
688 };
689 scanner.scan_value(1)?;
690 if scanner.position != payload.len() {
691 return Err(MessagePackPreflightError::TrailingBytes);
692 }
693 Ok(())
694}
695
696struct MessagePackScanner<'a> {
697 payload: &'a [u8],
698 position: usize,
699 values: usize,
700}
701
702impl MessagePackScanner<'_> {
703 fn scan_value(&mut self, depth: usize) -> std::result::Result<(), MessagePackPreflightError> {
704 if depth > MAX_MESSAGEPACK_DEPTH {
705 return Err(MessagePackPreflightError::TooDeep);
706 }
707 self.values = self
708 .values
709 .checked_add(1)
710 .ok_or(MessagePackPreflightError::TooManyValues)?;
711 if self.values > MAX_MESSAGEPACK_VALUES {
712 return Err(MessagePackPreflightError::TooManyValues);
713 }
714
715 let marker = self.read_byte()?;
716 match marker {
717 0x00..=0x7f | 0xc0 | 0xc2 | 0xc3 | 0xe0..=0xff => Ok(()),
718 0x80..=0x8f => self.scan_map(usize::from(marker & 0x0f), depth),
719 0x90..=0x9f => self.scan_children(usize::from(marker & 0x0f), depth),
720 0xa0..=0xbf => self.skip(usize::from(marker & 0x1f)),
721 0xc1 => Err(MessagePackPreflightError::ReservedMarker),
722 0xc4 | 0xd9 => {
723 let length = usize::from(self.read_byte()?);
724 self.skip(length)
725 }
726 0xc5 | 0xda => {
727 let length = usize::from(self.read_u16()?);
728 self.skip(length)
729 }
730 0xc6 | 0xdb => {
731 let length = usize::try_from(self.read_u32()?)
732 .map_err(|_| MessagePackPreflightError::TooLarge)?;
733 self.skip(length)
734 }
735 0xc7 => {
736 let length = usize::from(self.read_byte()?);
737 self.skip_ext(length)
738 }
739 0xc8 => {
740 let length = usize::from(self.read_u16()?);
741 self.skip_ext(length)
742 }
743 0xc9 => {
744 let length = usize::try_from(self.read_u32()?)
745 .map_err(|_| MessagePackPreflightError::TooLarge)?;
746 self.skip_ext(length)
747 }
748 0xca => self.skip(4),
749 0xcb => self.skip(8),
750 0xcc | 0xd0 => self.skip(1),
751 0xcd | 0xd1 => self.skip(2),
752 0xce | 0xd2 => self.skip(4),
753 0xcf | 0xd3 => self.skip(8),
754 0xd4 => self.skip_ext(1),
755 0xd5 => self.skip_ext(2),
756 0xd6 => self.skip_ext(4),
757 0xd7 => self.skip_ext(8),
758 0xd8 => self.skip_ext(16),
759 0xdc => {
760 let count = usize::from(self.read_u16()?);
761 self.scan_children(count, depth)
762 }
763 0xdd => {
764 let count = usize::try_from(self.read_u32()?)
765 .map_err(|_| MessagePackPreflightError::TooManyValues)?;
766 self.scan_children(count, depth)
767 }
768 0xde => {
769 let count = usize::from(self.read_u16()?);
770 self.scan_map(count, depth)
771 }
772 0xdf => {
773 let count = usize::try_from(self.read_u32()?)
774 .map_err(|_| MessagePackPreflightError::TooManyValues)?;
775 self.scan_map(count, depth)
776 }
777 }
778 }
779
780 fn scan_map(
781 &mut self,
782 entries: usize,
783 depth: usize,
784 ) -> std::result::Result<(), MessagePackPreflightError> {
785 let children = entries
786 .checked_mul(2)
787 .ok_or(MessagePackPreflightError::TooManyValues)?;
788 self.scan_children(children, depth)
789 }
790
791 fn scan_children(
792 &mut self,
793 children: usize,
794 depth: usize,
795 ) -> std::result::Result<(), MessagePackPreflightError> {
796 if children > MAX_MESSAGEPACK_VALUES {
797 return Err(MessagePackPreflightError::TooManyValues);
798 }
799 for _ in 0..children {
800 self.scan_value(depth + 1)?;
801 }
802 Ok(())
803 }
804
805 fn skip_ext(
806 &mut self,
807 payload_length: usize,
808 ) -> std::result::Result<(), MessagePackPreflightError> {
809 let total = payload_length
810 .checked_add(1)
811 .ok_or(MessagePackPreflightError::TooLarge)?;
812 self.skip(total)
813 }
814
815 fn read_byte(&mut self) -> std::result::Result<u8, MessagePackPreflightError> {
816 let byte = *self
817 .payload
818 .get(self.position)
819 .ok_or(MessagePackPreflightError::Truncated)?;
820 self.position += 1;
821 Ok(byte)
822 }
823
824 fn read_u16(&mut self) -> std::result::Result<u16, MessagePackPreflightError> {
825 let bytes = self.take(2)?;
826 Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
827 }
828
829 fn read_u32(&mut self) -> std::result::Result<u32, MessagePackPreflightError> {
830 let bytes = self.take(4)?;
831 Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
832 }
833
834 fn skip(&mut self, length: usize) -> std::result::Result<(), MessagePackPreflightError> {
835 self.take(length).map(|_| ())
836 }
837
838 fn take(&mut self, length: usize) -> std::result::Result<&[u8], MessagePackPreflightError> {
839 let end = self
840 .position
841 .checked_add(length)
842 .ok_or(MessagePackPreflightError::Truncated)?;
843 let bytes = self
844 .payload
845 .get(self.position..end)
846 .ok_or(MessagePackPreflightError::Truncated)?;
847 self.position = end;
848 Ok(bytes)
849 }
850}
851
852fn normalize_quoted_identifiers(sql: &str) -> String {
853 let mut normalized = String::with_capacity(sql.len());
854 let mut chars = sql.chars().peekable();
855 while let Some(ch) = chars.next() {
856 match ch {
857 '\'' => {
858 normalized.push(ch);
859 while let Some(string_ch) = chars.next() {
860 normalized.push(string_ch);
861 if string_ch == '\'' {
862 if chars.peek() == Some(&'\'') {
863 normalized.push(chars.next().expect("peeked quote"));
864 } else {
865 break;
866 }
867 }
868 }
869 }
870 '"' => {
871 normalized.push(' ');
875 while let Some(identifier_ch) = chars.next() {
876 if identifier_ch == '"' {
877 if chars.peek() == Some(&'"') {
878 normalized.push(chars.next().expect("peeked quote"));
881 normalized.push(' ');
882 } else {
883 normalized.push(' ');
884 break;
885 }
886 } else {
887 normalized.push(identifier_ch);
888 }
889 }
890 }
891 '-' if chars.peek() == Some(&'-') => {
892 normalized.push(ch);
893 normalized.push(chars.next().expect("peeked comment dash"));
894 for comment_ch in chars.by_ref() {
895 normalized.push(comment_ch);
896 if comment_ch == '\n' {
897 break;
898 }
899 }
900 }
901 '/' if chars.peek() == Some(&'*') => {
902 normalized.push(ch);
903 normalized.push(chars.next().expect("peeked comment star"));
904 let mut previous = '\0';
905 for comment_ch in chars.by_ref() {
906 normalized.push(comment_ch);
907 if previous == '*' && comment_ch == '/' {
908 break;
909 }
910 previous = comment_ch;
911 }
912 }
913 ch if ch.is_ascii_alphabetic() || ch == '_' => {
914 let mut identifier = String::from(ch);
915 while chars
916 .peek()
917 .is_some_and(|next| next.is_ascii_alphanumeric() || *next == '_')
918 {
919 identifier.push(chars.next().expect("peeked identifier character"));
920 }
921 normalized.push_str(&identifier.to_ascii_lowercase());
925 }
926 _ => normalized.push(ch),
927 }
928 }
929 normalized
930}
931
932fn natural_join_markers(sql: &str) -> Vec<bool> {
933 let mut markers = Vec::new();
934 let mut saw_natural = false;
935 let mut chars = sql.chars().peekable();
936 while let Some(ch) = chars.next() {
937 match ch {
938 '\'' | '"' => skip_quoted(&mut chars, ch),
939 '-' if chars.peek() == Some(&'-') => {
940 chars.next();
941 for comment_ch in chars.by_ref() {
942 if comment_ch == '\n' {
943 break;
944 }
945 }
946 }
947 '/' if chars.peek() == Some(&'*') => {
948 chars.next();
949 let mut previous = '\0';
950 for comment_ch in chars.by_ref() {
951 if previous == '*' && comment_ch == '/' {
952 break;
953 }
954 previous = comment_ch;
955 }
956 }
957 ';' => saw_natural = false,
958 c if c.is_ascii_alphabetic() || c == '_' => {
959 let mut word = String::from(c);
960 while chars
961 .peek()
962 .is_some_and(|next| next.is_ascii_alphanumeric() || *next == '_')
963 {
964 word.push(chars.next().expect("peeked identifier character"));
965 }
966 match word.to_ascii_lowercase().as_str() {
967 "natural" => saw_natural = true,
968 "join" => {
969 markers.push(saw_natural);
970 saw_natural = false;
971 }
972 _ => {}
973 }
974 }
975 _ => {}
976 }
977 }
978 markers
979}
980
981fn skip_quoted(chars: &mut std::iter::Peekable<std::str::Chars<'_>>, quote: char) {
982 while let Some(ch) = chars.next() {
983 if ch == quote {
984 if chars.peek() == Some("e) {
985 chars.next();
986 } else {
987 break;
988 }
989 }
990 }
991}
992
993fn annotate_natural_joins(statements: &mut [Statement], natural_markers: Vec<bool>) -> Result<()> {
1000 let supplied = natural_markers.len();
1001 let mut natural_markers = natural_markers.into_iter();
1002 let mut consumed = 0usize;
1003 for statement in statements {
1004 match &mut statement.kind {
1005 StatementKind::Select(select) => {
1006 annotate_select_natural_joins(select, &mut natural_markers, &mut consumed);
1007 }
1008 StatementKind::Values(values) => {
1009 annotate_values_natural_joins(values, &mut natural_markers, &mut consumed);
1010 }
1011 _ => {}
1012 }
1013 }
1014
1015 if consumed != supplied {
1016 return Err(ParserError::UnexpectedToken {
1017 line: 0,
1018 column: 0,
1019 expected: format!("{supplied} NATURAL join markers, one per join"),
1020 found: format!("{consumed} joins in the AST"),
1021 });
1022 }
1023 Ok(())
1024}
1025
1026fn annotate_select_natural_joins(
1027 select: &mut Select,
1028 natural_markers: &mut impl Iterator<Item = bool>,
1029 consumed: &mut usize,
1030) {
1031 if let Some(with) = &mut select.with {
1032 for cte in &mut with.ctes {
1033 annotate_query_body_natural_joins(&mut cte.query, natural_markers, consumed);
1034 }
1035 }
1036 for item in &mut select.projection {
1037 if let SelectItem::Expr { expr, .. } = item {
1038 annotate_expr_natural_joins(expr, natural_markers, consumed);
1039 }
1040 }
1041 for from in &mut select.from {
1042 annotate_from_natural_joins(from, natural_markers, consumed);
1043 }
1044 if let Some(selection) = &mut select.selection {
1045 annotate_expr_natural_joins(selection, natural_markers, consumed);
1046 }
1047 if let Some(group_by) = &mut select.group_by {
1048 for item in group_by {
1049 for expression in item.exprs_mut() {
1050 annotate_expr_natural_joins(expression, natural_markers, consumed);
1051 }
1052 }
1053 }
1054 if let Some(having) = &mut select.having {
1055 annotate_expr_natural_joins(having, natural_markers, consumed);
1056 }
1057 for operation in &mut select.set_operations {
1058 annotate_query_body_natural_joins(&mut operation.right, natural_markers, consumed);
1059 }
1060 for order_by in &mut select.order_by {
1061 annotate_expr_natural_joins(&mut order_by.expr, natural_markers, consumed);
1062 }
1063 if let Some(limit) = &mut select.limit {
1064 annotate_expr_natural_joins(limit, natural_markers, consumed);
1065 }
1066 if let Some(offset) = &mut select.offset {
1067 annotate_expr_natural_joins(offset, natural_markers, consumed);
1068 }
1069}
1070
1071fn annotate_values_natural_joins(
1072 values: &mut Values,
1073 natural_markers: &mut impl Iterator<Item = bool>,
1074 consumed: &mut usize,
1075) {
1076 if let Some(with) = &mut values.with {
1077 for cte in &mut with.ctes {
1078 annotate_query_body_natural_joins(&mut cte.query, natural_markers, consumed);
1079 }
1080 }
1081 for row in &mut values.rows {
1082 for expr in row {
1083 annotate_expr_natural_joins(expr, natural_markers, consumed);
1084 }
1085 }
1086 for operation in &mut values.set_operations {
1087 annotate_query_body_natural_joins(&mut operation.right, natural_markers, consumed);
1088 }
1089 for order_by in &mut values.order_by {
1090 annotate_expr_natural_joins(&mut order_by.expr, natural_markers, consumed);
1091 }
1092 if let Some(limit) = &mut values.limit {
1093 annotate_expr_natural_joins(limit, natural_markers, consumed);
1094 }
1095 if let Some(offset) = &mut values.offset {
1096 annotate_expr_natural_joins(offset, natural_markers, consumed);
1097 }
1098}
1099
1100fn annotate_query_body_natural_joins(
1101 body: &mut QueryBody,
1102 natural_markers: &mut impl Iterator<Item = bool>,
1103 consumed: &mut usize,
1104) {
1105 match body {
1106 QueryBody::Select(select) => {
1107 annotate_select_natural_joins(select, natural_markers, consumed);
1108 }
1109 QueryBody::Values(values) => {
1110 annotate_values_natural_joins(values, natural_markers, consumed);
1111 }
1112 }
1113}
1114
1115fn annotate_from_natural_joins(
1116 from: &mut FromItem,
1117 natural_markers: &mut impl Iterator<Item = bool>,
1118 consumed: &mut usize,
1119) {
1120 match from {
1121 FromItem::Join {
1122 left,
1123 right,
1124 natural,
1125 ..
1126 } => {
1127 annotate_from_natural_joins(left, natural_markers, consumed);
1128 if let Some(marker) = natural_markers.next() {
1129 *natural |= marker;
1130 *consumed += 1;
1131 }
1132 annotate_from_natural_joins(right, natural_markers, consumed);
1133 }
1134 FromItem::Derived { subquery, .. } => {
1135 annotate_query_body_natural_joins(subquery, natural_markers, consumed);
1136 }
1137 FromItem::Function { args, .. } => {
1138 for arg in args {
1141 annotate_expr_natural_joins(arg, natural_markers, consumed);
1142 }
1143 }
1144 FromItem::Table { .. } => {}
1145 }
1146}
1147
1148fn annotate_expr_natural_joins(
1149 expr: &mut Expr,
1150 natural_markers: &mut impl Iterator<Item = bool>,
1151 consumed: &mut usize,
1152) {
1153 match &mut expr.kind {
1154 ExprKind::ScalarSubquery { subquery } | ExprKind::Exists { subquery, .. } => {
1155 if let StatementKind::Select(select) = &mut subquery.kind {
1156 annotate_select_natural_joins(select, natural_markers, consumed);
1157 }
1158 }
1159 ExprKind::InSubquery { expr, subquery, .. }
1160 | ExprKind::Quantified { expr, subquery, .. } => {
1161 annotate_expr_natural_joins(expr, natural_markers, consumed);
1162 if let StatementKind::Select(select) = &mut subquery.kind {
1163 annotate_select_natural_joins(select, natural_markers, consumed);
1164 }
1165 }
1166 ExprKind::BinaryOp { left, right, .. } => {
1167 annotate_expr_natural_joins(left, natural_markers, consumed);
1168 annotate_expr_natural_joins(right, natural_markers, consumed);
1169 }
1170 ExprKind::UnaryOp { operand, .. } | ExprKind::IsNull { expr: operand, .. } => {
1171 annotate_expr_natural_joins(operand, natural_markers, consumed);
1172 }
1173 ExprKind::TruthPredicate { expr, .. } => {
1174 annotate_expr_natural_joins(expr, natural_markers, consumed);
1175 }
1176 ExprKind::IsDistinctFrom { left, right, .. } => {
1177 annotate_expr_natural_joins(left, natural_markers, consumed);
1178 annotate_expr_natural_joins(right, natural_markers, consumed);
1179 }
1180 ExprKind::Row { items } => {
1181 for item in items {
1182 annotate_expr_natural_joins(item, natural_markers, consumed);
1183 }
1184 }
1185 ExprKind::Case {
1186 operand,
1187 branches,
1188 else_expr,
1189 } => {
1190 if let Some(operand) = operand {
1191 annotate_expr_natural_joins(operand, natural_markers, consumed);
1192 }
1193 for branch in branches {
1194 annotate_expr_natural_joins(&mut branch.when, natural_markers, consumed);
1195 annotate_expr_natural_joins(&mut branch.then, natural_markers, consumed);
1196 }
1197 if let Some(else_expr) = else_expr {
1198 annotate_expr_natural_joins(else_expr, natural_markers, consumed);
1199 }
1200 }
1201 ExprKind::FunctionCall {
1202 args,
1203 order_by,
1204 within_group,
1205 filter,
1206 ..
1207 } => {
1208 for argument in args {
1209 annotate_expr_natural_joins(argument, natural_markers, consumed);
1210 }
1211 for order in order_by {
1212 annotate_expr_natural_joins(&mut order.expr, natural_markers, consumed);
1213 }
1214 for order in within_group {
1215 annotate_expr_natural_joins(&mut order.expr, natural_markers, consumed);
1216 }
1217 if let Some(filter) = filter {
1218 annotate_expr_natural_joins(filter, natural_markers, consumed);
1219 }
1220 }
1221 ExprKind::Between {
1222 expr, low, high, ..
1223 } => {
1224 annotate_expr_natural_joins(expr, natural_markers, consumed);
1225 annotate_expr_natural_joins(low, natural_markers, consumed);
1226 annotate_expr_natural_joins(high, natural_markers, consumed);
1227 }
1228 ExprKind::Like {
1229 expr,
1230 pattern,
1231 escape,
1232 ..
1233 } => {
1234 annotate_expr_natural_joins(expr, natural_markers, consumed);
1235 annotate_expr_natural_joins(pattern, natural_markers, consumed);
1236 if let Some(escape) = escape {
1237 annotate_expr_natural_joins(escape, natural_markers, consumed);
1238 }
1239 }
1240 ExprKind::InList { expr, list, .. } => {
1241 annotate_expr_natural_joins(expr, natural_markers, consumed);
1242 for item in list {
1243 annotate_expr_natural_joins(item, natural_markers, consumed);
1244 }
1245 }
1246 ExprKind::Cast { expr, .. } | ExprKind::TryCast { expr, .. } => {
1247 annotate_expr_natural_joins(expr, natural_markers, consumed);
1248 }
1249 ExprKind::Literal { .. } | ExprKind::ColumnRef { .. } | ExprKind::VectorLiteral { .. } => {}
1250 }
1251}
1252
1253pub fn parse_expression_sql(sql: &str) -> Result<crate::ast::Expr> {
1254 let wrapped_len =
1255 preflight_input(sql, SELECT_WRAPPER_PREFIX.len()).map_err(parser_error_from_preflight)?;
1256 let mut wrapped = String::with_capacity(wrapped_len);
1257 wrapped.push_str(SELECT_WRAPPER_PREFIX);
1258 wrapped.push_str(sql);
1259 let statements = parse_sql_preflighted(&wrapped)?;
1260 let Some(statement) = statements.into_iter().next() else {
1261 return Err(empty_expression_error());
1262 };
1263 let StatementKind::Select(select) = statement.kind else {
1264 return Err(empty_expression_error());
1265 };
1266 let Some(crate::ast::SelectItem::Expr { expr, .. }) = select.projection.into_iter().next()
1267 else {
1268 return Err(empty_expression_error());
1269 };
1270 Ok(expr)
1271}
1272
1273fn empty_expression_error() -> ParserError {
1274 ParserError::UnexpectedToken {
1275 line: 0,
1276 column: 0,
1277 expected: "expression".to_string(),
1278 found: "empty parser result".to_string(),
1279 }
1280}
1281
1282fn checked_total_input_len(
1283 input_len: usize,
1284 wrapper_len: usize,
1285) -> std::result::Result<usize, InputPreflightError> {
1286 let total_len = input_len
1287 .checked_add(wrapper_len)
1288 .ok_or(InputPreflightError::LengthOverflow)?;
1289 if total_len > MAX_SQL_INPUT_BYTES {
1290 return Err(InputPreflightError::TooLarge);
1291 }
1292 Ok(total_len)
1293}
1294
1295fn preflight_input(
1296 sql: &str,
1297 wrapper_len: usize,
1298) -> std::result::Result<usize, InputPreflightError> {
1299 let total_len = checked_total_input_len(sql.len(), wrapper_len)?;
1300 if sql.as_bytes().contains(&0) {
1301 return Err(InputPreflightError::InteriorNul);
1302 }
1303 Ok(total_len)
1304}
1305
1306fn parser_error_from_preflight(error: InputPreflightError) -> ParserError {
1307 match error {
1308 InputPreflightError::TooLarge | InputPreflightError::LengthOverflow => {
1309 input_too_large_error()
1310 }
1311 InputPreflightError::InteriorNul => interior_nul_error(),
1312 }
1313}
1314
1315fn parser_error_from_ffi_input(error: nim_ffi::ParseInputError) -> ParserError {
1316 match error {
1317 nim_ffi::ParseInputError::LengthOutOfRange => input_too_large_error(),
1318 nim_ffi::ParseInputError::InteriorNul => interior_nul_error(),
1319 }
1320}
1321
1322fn input_too_large_error() -> ParserError {
1323 ParserError::UnexpectedToken {
1324 line: 0,
1325 column: 0,
1326 expected: "SQL input at most 1048576 UTF-8 bytes".to_string(),
1327 found: "SQL input exceeds byte limit".to_string(),
1328 }
1329}
1330
1331fn interior_nul_error() -> ParserError {
1332 ParserError::UnexpectedToken {
1333 line: 0,
1334 column: 0,
1335 expected: "valid SQL without interior NUL bytes".to_string(),
1336 found: "interior NUL byte".to_string(),
1337 }
1338}
1339
1340const INTERNAL_DEFECT_PREFIX: &str =
1346 "internal parser defect (this is a parser bug, not invalid SQL): ";
1347
1348fn parser_error_from_nim(message: &str) -> ParserError {
1349 if let Some(defect_message) = message.strip_prefix(INTERNAL_DEFECT_PREFIX) {
1350 return ParserError::InternalParserDefect {
1351 message: defect_message.to_string(),
1352 };
1353 }
1354 let (line, column) = parse_nim_line_col(message).unwrap_or((0, 0));
1355 ParserError::UnexpectedToken {
1356 line,
1357 column,
1358 expected: "valid SQL".to_string(),
1359 found: message.to_string(),
1360 }
1361}
1362
1363fn parse_nim_line_col(message: &str) -> Option<(u64, u64)> {
1364 let after_line = message.strip_prefix("Parse error at line ")?;
1365 let (line, rest) = after_line.split_once(", col ")?;
1366 let (col, _) = rest.split_once(':')?;
1367 Some((line.parse().ok()?, col.parse().ok()?))
1368}
1369
1370#[cfg(test)]
1371mod input_preflight_tests {
1372 use super::*;
1373
1374 #[test]
1375 fn relabeled_v040_parser_is_rejected_by_exported_contract_before_decode() {
1376 let error = ensure_linked_parser_contract("0.4.0")
1379 .expect_err("sidecar labels cannot make a pre-frame producer compatible");
1380 let rendered = error.to_string();
1381
1382 assert!(rendered.contains("linked Nim parser contract 0.14.0"));
1383 assert!(rendered.contains("linked Nim parser contract 0.4.0"));
1384 }
1385
1386 #[test]
1387 fn relabeled_v050_parser_is_rejected_by_exported_contract_before_decode() {
1388 let error = ensure_linked_parser_contract("0.5.0")
1389 .expect_err("a 0.5.0 producer cannot satisfy the current named-window contract");
1390 let rendered = error.to_string();
1391
1392 assert!(rendered.contains("linked Nim parser contract 0.14.0"));
1393 assert!(rendered.contains("linked Nim parser contract 0.5.0"));
1394 }
1395
1396 #[test]
1397 fn legacy_v040_consumer_rejects_the_current_producer_before_decode() {
1398 let linked_producer_contract = nim_ffi::parser_contract_version();
1399 assert_eq!(linked_producer_contract, "0.14.0");
1400 let error = ensure_parser_contract("0.4.0", &linked_producer_contract)
1401 .expect_err("legacy consumer must reject a producer with frame semantics");
1402 let rendered = error.to_string();
1403
1404 assert!(rendered.contains("linked Nim parser contract 0.4.0"));
1405 assert!(rendered.contains("linked Nim parser contract 0.14.0"));
1406 }
1407
1408 #[test]
1409 fn legacy_v050_consumer_rejects_a_v060_named_window_producer_before_decode() {
1410 let linked_producer_contract = nim_ffi::parser_contract_version();
1411 assert_eq!(linked_producer_contract, "0.14.0");
1412 let error = ensure_parser_contract("0.5.0", &linked_producer_contract)
1413 .expect_err("legacy consumer must not ignore QUALIFY or named-window fields");
1414 let rendered = error.to_string();
1415
1416 assert!(rendered.contains("linked Nim parser contract 0.5.0"));
1417 assert!(rendered.contains("linked Nim parser contract 0.14.0"));
1418 }
1419
1420 #[test]
1421 fn raw_sql_guard_accepts_boundary_minus_and_exact_but_rejects_plus() {
1422 assert_eq!(
1423 checked_total_input_len(MAX_SQL_INPUT_BYTES - 1, 0),
1424 Ok(MAX_SQL_INPUT_BYTES - 1)
1425 );
1426 assert_eq!(
1427 checked_total_input_len(MAX_SQL_INPUT_BYTES, 0),
1428 Ok(MAX_SQL_INPUT_BYTES)
1429 );
1430 assert_eq!(
1431 checked_total_input_len(MAX_SQL_INPUT_BYTES + 1, 0),
1432 Err(InputPreflightError::TooLarge)
1433 );
1434 }
1435
1436 #[test]
1437 fn guard_counts_utf8_bytes_instead_of_characters() {
1438 let exact = "é".repeat(MAX_SQL_INPUT_BYTES / "é".len());
1439 assert_eq!(exact.chars().count(), MAX_SQL_INPUT_BYTES / 2);
1440 assert_eq!(exact.len(), MAX_SQL_INPUT_BYTES);
1441 assert_eq!(preflight_input(&exact, 0), Ok(MAX_SQL_INPUT_BYTES));
1442
1443 let plus = format!("{exact}é");
1444 assert_eq!(
1445 preflight_input(&plus, 0),
1446 Err(InputPreflightError::TooLarge)
1447 );
1448 }
1449
1450 #[test]
1451 fn expression_guard_includes_wrapper_and_detects_length_overflow() {
1452 assert_eq!(
1453 checked_total_input_len(
1454 MAX_SQL_INPUT_BYTES - SELECT_WRAPPER_PREFIX.len(),
1455 SELECT_WRAPPER_PREFIX.len(),
1456 ),
1457 Ok(MAX_SQL_INPUT_BYTES)
1458 );
1459 assert_eq!(
1460 checked_total_input_len(
1461 MAX_SQL_INPUT_BYTES - SELECT_WRAPPER_PREFIX.len() + 1,
1462 SELECT_WRAPPER_PREFIX.len(),
1463 ),
1464 Err(InputPreflightError::TooLarge)
1465 );
1466 assert_eq!(
1467 checked_total_input_len(usize::MAX, SELECT_WRAPPER_PREFIX.len()),
1468 Err(InputPreflightError::LengthOverflow)
1469 );
1470 }
1471
1472 #[test]
1473 fn preflight_rejects_nul_before_any_ffi_work() {
1474 assert_eq!(
1475 preflight_input("SELECT \0 1", 0),
1476 Err(InputPreflightError::InteriorNul)
1477 );
1478 assert_eq!(
1479 preflight_input("1 \0 2", SELECT_WRAPPER_PREFIX.len()),
1480 Err(InputPreflightError::InteriorNul)
1481 );
1482 }
1483}