Skip to main content

alopex_sql/
nim_bridge.rs

1use crate::ast::ddl::CreateContinuousAggregate;
2use crate::ast::dml::{FromItem, Select, SelectItem, SetOperation, SetOperator};
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
46/// Exact Select wire adapter for the continuous-aggregate payload.
47///
48/// Existing top-level Select statements encode their variant through
49/// `StatementKind`. The nested continuous-aggregate query is a named Select
50/// payload in its own right, so it carries and validates an explicit
51/// `variant: Select` field.
52pub(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: &'a Option<Vec<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        SelectWireRef {
103            variant: "Select",
104            distinct: select.distinct,
105            projection: &select.projection,
106            from: &select.from,
107            selection: &select.selection,
108            group_by: &select.group_by,
109            having: &select.having,
110            set_operations: &select.set_operations,
111            order_by: &select.order_by,
112            limit: &select.limit,
113            offset: &select.offset,
114            span: select.span,
115        }
116        .serialize(serializer)
117    }
118
119    pub(crate) fn deserialize<'de, D>(deserializer: D) -> std::result::Result<Select, D::Error>
120    where
121        D: Deserializer<'de>,
122    {
123        let wire = SelectWire::deserialize(deserializer)?;
124        if wire.variant != "Select" {
125            return Err(D::Error::custom(format!(
126                "expected nested query variant `Select`, found `{}`",
127                wire.variant
128            )));
129        }
130        Ok(Select {
131            with: wire.with,
132            distinct: wire.distinct,
133            projection: wire.projection,
134            from: wire.from,
135            selection: wire.selection,
136            group_by: wire.group_by,
137            having: wire.having,
138            set_operations: wire.set_operations,
139            order_by: wire.order_by,
140            limit: wire.limit,
141            offset: wire.offset,
142            span: wire.span,
143        })
144    }
145}
146
147impl CreateContinuousAggregate {
148    /// Decode the staged 0.4 wire shape without adding it to `StatementKind`.
149    ///
150    /// This narrow seam lets the contract shape be proven while the checked-in
151    /// producer remains on 0.3. The linked-version check is the same one used
152    /// by the production parser path and always runs before payload preflight.
153    #[doc(hidden)]
154    pub fn decode_staged_messagepack(linked_parser_contract: &str, payload: &[u8]) -> Result<Self> {
155        ensure_linked_parser_contract(linked_parser_contract)?;
156        validate_bounded_messagepack(payload).map_err(messagepack_preflight_error)?;
157        let decoded = rmp_serde::from_slice::<StagedContinuousAggregateStatement>(payload)
158            .map_err(messagepack_decode_error)?;
159        let StagedContinuousAggregateKind::CreateContinuousAggregate(statement) = decoded.kind;
160        if decoded.span != statement.span {
161            return Err(ParserError::UnexpectedToken {
162                line: 0,
163                column: 0,
164                expected: "matching outer and kind spans in MessagePack AST".to_string(),
165                found: "continuous aggregate outer span differs from kind span".to_string(),
166            });
167        }
168        Ok(statement)
169    }
170}
171
172/// Return the SQL/PromQL MessagePack wire contract version exported by Nim.
173pub fn parser_contract_version() -> String {
174    nim_ffi::parser_contract_version()
175}
176
177pub fn parse_sql(sql: &str) -> Result<Vec<Statement>> {
178    preflight_input(sql, 0).map_err(parser_error_from_preflight)?;
179    parse_sql_preflighted(sql)
180}
181
182fn parse_sql_preflighted(sql: &str) -> Result<Vec<Statement>> {
183    let tokens = scan_top_level_tokens(sql);
184    let ranges = top_level_statement_ranges(sql, &tokens);
185    let contains_set_operation = ranges.iter().any(|(start, end)| {
186        tokens.iter().any(|token| {
187            token.start >= *start
188                && token.end <= *end
189                && matches!(token.kind, TopLevelTokenKind::Word)
190                && set_operator(&sql[token.start..token.end]).is_some()
191        })
192    });
193    if contains_set_operation
194        && let Ok(statements) = parse_sql_via_ffi(sql)
195        && statements.len() == ranges.len()
196    {
197        return Ok(statements);
198    }
199    if let Some(statements) = parse_set_operation_batch(sql)? {
200        return Ok(statements);
201    }
202    parse_sql_via_ffi(sql)
203}
204
205fn parse_sql_via_ffi(sql: &str) -> Result<Vec<Statement>> {
206    ensure_linked_parser_contract(&nim_ffi::parser_contract_version())?;
207    let natural_join_markers = natural_join_markers(sql);
208    // Option (a): double-quoted tokens are identifiers under SQL standard and
209    // PostgreSQL rules. The currently deployed Nim lexer predates that contract
210    // and emits them as string literals, so normalize the FFI input until every
211    // parser binary has the corrected token kind.
212    let normalized_sql = normalize_quoted_identifiers(sql);
213    let result = nim_ffi::parse_sql(&normalized_sql).map_err(parser_error_from_ffi_input)?;
214    match result.kind {
215        ParseResultKind::Ok => {
216            let buffer = OwnedBuffer::new(result.buffer_ptr, result.buffer_len);
217            // 正常時の payload は最低でも MessagePack の配列ヘッダ 1 バイトを
218            // 含む。空 payload はゼロ初期化された CParseResult、つまり Nim 側
219            // から例外が漏れた事故 (issue #40 の desync 経路) を意味するため、
220            // 汎用の decode エラーではなく原因が特定できるエラーにする。
221            if buffer.as_slice().is_empty() {
222                return Err(ParserError::UnexpectedToken {
223                    line: 0,
224                    column: 0,
225                    expected: "MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
226                    found: "empty payload from Nim parser (leaked exception at FFI boundary; \
227                            see issue #40)"
228                        .to_string(),
229                });
230            }
231            validate_bounded_messagepack(buffer.as_slice()).map_err(messagepack_preflight_error)?;
232            let mut statements = rmp_serde::from_slice::<Vec<Statement>>(buffer.as_slice())
233                .map_err(messagepack_decode_error)?;
234            annotate_natural_joins(&mut statements, natural_join_markers)?;
235            Ok(statements)
236        }
237        ParseResultKind::Error => {
238            let buffer = OwnedBuffer::new(result.error_ptr.cast(), result.error_len);
239            Err(parser_error_from_nim(
240                String::from_utf8_lossy(buffer.as_slice()).as_ref(),
241            ))
242        }
243    }
244}
245
246#[derive(Debug, Clone, Copy)]
247enum TopLevelTokenKind {
248    Word,
249    Semicolon,
250}
251
252#[derive(Debug, Clone, Copy)]
253struct TopLevelToken {
254    kind: TopLevelTokenKind,
255    start: usize,
256    end: usize,
257}
258
259#[derive(Debug, Clone, Copy)]
260struct SetOperationSpec {
261    operator: SetOperator,
262    all: bool,
263    span: Span,
264}
265
266fn parse_set_operation_batch(sql: &str) -> Result<Option<Vec<Statement>>> {
267    let tokens = scan_top_level_tokens(sql);
268    let ranges = top_level_statement_ranges(sql, &tokens);
269
270    let contains_set_operation = ranges.iter().any(|(start, end)| {
271        tokens.iter().any(|token| {
272            token.start >= *start
273                && token.end <= *end
274                && matches!(token.kind, TopLevelTokenKind::Word)
275                && set_operator(&sql[token.start..token.end]).is_some()
276        })
277    });
278    if !contains_set_operation {
279        return Ok(None);
280    }
281
282    let mut statements = Vec::new();
283    for (start, end) in ranges {
284        let words = tokens
285            .iter()
286            .copied()
287            .filter(|token| {
288                token.start >= start
289                    && token.end <= end
290                    && matches!(token.kind, TopLevelTokenKind::Word)
291            })
292            .collect::<Vec<_>>();
293        if words
294            .iter()
295            .any(|word| set_operator(&sql[word.start..word.end]).is_some())
296        {
297            statements.push(parse_set_operation_statement(sql, start, end, &words)?);
298        } else {
299            statements.extend(parse_sql_via_ffi(&sql[start..end])?);
300        }
301    }
302    Ok(Some(statements))
303}
304
305fn top_level_statement_ranges(sql: &str, tokens: &[TopLevelToken]) -> Vec<(usize, usize)> {
306    let mut ranges = Vec::new();
307    let mut start = 0;
308    for token in tokens {
309        if matches!(token.kind, TopLevelTokenKind::Semicolon) {
310            if !sql[start..token.start].trim().is_empty() {
311                ranges.push((start, token.start));
312            }
313            start = token.end;
314        }
315    }
316    if !sql[start..].trim().is_empty() {
317        ranges.push((start, sql.len()));
318    }
319    ranges
320}
321
322fn parse_set_operation_statement(
323    sql: &str,
324    statement_start: usize,
325    statement_end: usize,
326    words: &[TopLevelToken],
327) -> Result<Statement> {
328    let mut branch_ranges = Vec::new();
329    let mut operations = Vec::new();
330    let mut branch_start = statement_start;
331
332    for (index, word) in words.iter().enumerate() {
333        let Some(operator) = set_operator(&sql[word.start..word.end]) else {
334            continue;
335        };
336        branch_ranges.push((branch_start, word.start));
337        let all_word = words
338            .get(index + 1)
339            .filter(|next| sql[next.start..next.end].eq_ignore_ascii_case("all"));
340        let all = all_word.is_some();
341        branch_start = all_word.map_or(word.end, |token| token.end);
342        operations.push(SetOperationSpec {
343            operator,
344            all,
345            span: span_for_offsets(sql, word.start, word.end),
346        });
347    }
348    branch_ranges.push((branch_start, statement_end));
349
350    if branch_ranges.len() != operations.len() + 1 {
351        return Err(set_operation_parser_error(
352            "a SELECT query on both sides of every set operator",
353            "malformed set-operation chain",
354        ));
355    }
356
357    let mut branches = branch_ranges
358        .into_iter()
359        .map(|(start, end)| parse_select_fragment(&sql[start..end]))
360        .collect::<Result<Vec<_>>>()?;
361    let final_branch = branches.last_mut().ok_or_else(|| {
362        set_operation_parser_error("at least one SELECT query", "empty set-operation chain")
363    })?;
364    let order_by = std::mem::take(&mut final_branch.order_by);
365    let limit = final_branch.limit.take();
366    let offset = final_branch.offset.take();
367
368    let mut terms = Vec::new();
369    let mut outer_operations = Vec::new();
370    let mut current = branches.remove(0);
371    for (operation, right) in operations.into_iter().zip(branches) {
372        if operation.operator == SetOperator::Intersect {
373            current.set_operations.push(SetOperation {
374                operator: operation.operator,
375                all: operation.all,
376                right: Box::new(right),
377                span: operation.span,
378            });
379        } else {
380            terms.push(current);
381            outer_operations.push(operation);
382            current = right;
383        }
384    }
385    terms.push(current);
386
387    let mut root = terms.remove(0);
388    for (operation, right) in outer_operations.into_iter().zip(terms) {
389        root.set_operations.push(SetOperation {
390            operator: operation.operator,
391            all: operation.all,
392            right: Box::new(right),
393            span: operation.span,
394        });
395    }
396    root.order_by = order_by;
397    root.limit = limit;
398    root.offset = offset;
399    let span = root.span;
400    Ok(Statement {
401        kind: StatementKind::Select(root),
402        span,
403    })
404}
405
406fn parse_select_fragment(sql: &str) -> Result<Select> {
407    let mut statements = parse_sql_via_ffi(sql.trim())?;
408    if statements.len() != 1 {
409        return Err(set_operation_parser_error(
410            "exactly one SELECT query per set-operation input",
411            format!("{} statements", statements.len()),
412        ));
413    }
414    match statements.remove(0).kind {
415        StatementKind::Select(select) => Ok(select),
416        _ => Err(set_operation_parser_error(
417            "SELECT query in set operation",
418            "non-SELECT statement",
419        )),
420    }
421}
422
423fn set_operator(operator: &str) -> Option<SetOperator> {
424    if operator.eq_ignore_ascii_case("union") {
425        Some(SetOperator::Union)
426    } else if operator.eq_ignore_ascii_case("intersect") {
427        Some(SetOperator::Intersect)
428    } else if operator.eq_ignore_ascii_case("except") {
429        Some(SetOperator::Except)
430    } else {
431        None
432    }
433}
434
435fn set_operation_parser_error(
436    expected: impl Into<String>,
437    found: impl Into<String>,
438) -> ParserError {
439    ParserError::UnexpectedToken {
440        line: 0,
441        column: 0,
442        expected: expected.into(),
443        found: found.into(),
444    }
445}
446
447fn span_for_offsets(sql: &str, start: usize, end: usize) -> Span {
448    fn location(sql: &str, offset: usize) -> Location {
449        let prefix = &sql[..offset.min(sql.len())];
450        let line = prefix.bytes().filter(|byte| *byte == b'\n').count() as u64 + 1;
451        let column = prefix
452            .rsplit_once('\n')
453            .map_or(prefix.len(), |(_, tail)| tail.len()) as u64
454            + 1;
455        Location::new(line, column)
456    }
457    Span::new(location(sql, start), location(sql, end.saturating_sub(1)))
458}
459
460fn scan_top_level_tokens(sql: &str) -> Vec<TopLevelToken> {
461    let bytes = sql.as_bytes();
462    let mut tokens = Vec::new();
463    let mut index = 0;
464    let mut depth = 0_u32;
465
466    while index < bytes.len() {
467        match bytes[index] {
468            b'\'' | b'"' => {
469                let quote = bytes[index];
470                index += 1;
471                while index < bytes.len() {
472                    if bytes[index] == quote {
473                        if bytes.get(index + 1) == Some(&quote) {
474                            index += 2;
475                        } else {
476                            index += 1;
477                            break;
478                        }
479                    } else {
480                        index += 1;
481                    }
482                }
483            }
484            b'-' if bytes.get(index + 1) == Some(&b'-') => {
485                index += 2;
486                while index < bytes.len() && bytes[index] != b'\n' {
487                    index += 1;
488                }
489            }
490            b'/' if bytes.get(index + 1) == Some(&b'*') => {
491                index += 2;
492                while index + 1 < bytes.len() && !(bytes[index] == b'*' && bytes[index + 1] == b'/')
493                {
494                    index += 1;
495                }
496                index = (index + 2).min(bytes.len());
497            }
498            b'(' => {
499                depth += 1;
500                index += 1;
501            }
502            b')' => {
503                depth = depth.saturating_sub(1);
504                index += 1;
505            }
506            b';' if depth == 0 => {
507                tokens.push(TopLevelToken {
508                    kind: TopLevelTokenKind::Semicolon,
509                    start: index,
510                    end: index + 1,
511                });
512                index += 1;
513            }
514            byte if depth == 0 && (byte.is_ascii_alphabetic() || byte == b'_') => {
515                let start = index;
516                index += 1;
517                while index < bytes.len()
518                    && (bytes[index].is_ascii_alphanumeric() || bytes[index] == b'_')
519                {
520                    index += 1;
521                }
522                tokens.push(TopLevelToken {
523                    kind: TopLevelTokenKind::Word,
524                    start,
525                    end: index,
526                });
527            }
528            _ => index += 1,
529        }
530    }
531    tokens
532}
533
534fn expected_parser_contract() -> &'static str {
535    PARSER_CONTRACT_DESCRIPTOR.trim()
536}
537
538fn ensure_linked_parser_contract(linked_parser_contract: &str) -> Result<()> {
539    let expected = expected_parser_contract();
540    if linked_parser_contract == expected {
541        return Ok(());
542    }
543    Err(ParserError::UnexpectedToken {
544        line: 0,
545        column: 0,
546        expected: format!("linked Nim parser contract {expected}"),
547        found: format!("linked Nim parser contract {linked_parser_contract}"),
548    })
549}
550
551fn messagepack_decode_error(error: rmp_serde::decode::Error) -> ParserError {
552    ParserError::UnexpectedToken {
553        line: 0,
554        column: 0,
555        expected: "bounded MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
556        found: error.to_string(),
557    }
558}
559
560fn messagepack_preflight_error(error: MessagePackPreflightError) -> ParserError {
561    let found = match error {
562        MessagePackPreflightError::TooLarge => {
563            format!("MessagePack payload exceeds {MAX_MESSAGEPACK_PAYLOAD_BYTES} bytes")
564        }
565        MessagePackPreflightError::TooDeep => {
566            format!("MessagePack nesting exceeds {MAX_MESSAGEPACK_DEPTH} levels")
567        }
568        MessagePackPreflightError::TooManyValues => {
569            format!("MessagePack collection limit of {MAX_MESSAGEPACK_VALUES} values exceeded")
570        }
571        MessagePackPreflightError::Truncated => "truncated MessagePack payload".to_string(),
572        MessagePackPreflightError::ReservedMarker => "reserved MessagePack marker 0xc1".to_string(),
573        MessagePackPreflightError::TrailingBytes => {
574            "trailing bytes after MessagePack payload".to_string()
575        }
576    };
577    ParserError::UnexpectedToken {
578        line: 0,
579        column: 0,
580        expected: "bounded MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
581        found,
582    }
583}
584
585fn validate_bounded_messagepack(
586    payload: &[u8],
587) -> std::result::Result<(), MessagePackPreflightError> {
588    if payload.len() > MAX_MESSAGEPACK_PAYLOAD_BYTES {
589        return Err(MessagePackPreflightError::TooLarge);
590    }
591    let mut scanner = MessagePackScanner {
592        payload,
593        position: 0,
594        values: 0,
595    };
596    scanner.scan_value(1)?;
597    if scanner.position != payload.len() {
598        return Err(MessagePackPreflightError::TrailingBytes);
599    }
600    Ok(())
601}
602
603struct MessagePackScanner<'a> {
604    payload: &'a [u8],
605    position: usize,
606    values: usize,
607}
608
609impl MessagePackScanner<'_> {
610    fn scan_value(&mut self, depth: usize) -> std::result::Result<(), MessagePackPreflightError> {
611        if depth > MAX_MESSAGEPACK_DEPTH {
612            return Err(MessagePackPreflightError::TooDeep);
613        }
614        self.values = self
615            .values
616            .checked_add(1)
617            .ok_or(MessagePackPreflightError::TooManyValues)?;
618        if self.values > MAX_MESSAGEPACK_VALUES {
619            return Err(MessagePackPreflightError::TooManyValues);
620        }
621
622        let marker = self.read_byte()?;
623        match marker {
624            0x00..=0x7f | 0xc0 | 0xc2 | 0xc3 | 0xe0..=0xff => Ok(()),
625            0x80..=0x8f => self.scan_map(usize::from(marker & 0x0f), depth),
626            0x90..=0x9f => self.scan_children(usize::from(marker & 0x0f), depth),
627            0xa0..=0xbf => self.skip(usize::from(marker & 0x1f)),
628            0xc1 => Err(MessagePackPreflightError::ReservedMarker),
629            0xc4 | 0xd9 => {
630                let length = usize::from(self.read_byte()?);
631                self.skip(length)
632            }
633            0xc5 | 0xda => {
634                let length = usize::from(self.read_u16()?);
635                self.skip(length)
636            }
637            0xc6 | 0xdb => {
638                let length = usize::try_from(self.read_u32()?)
639                    .map_err(|_| MessagePackPreflightError::TooLarge)?;
640                self.skip(length)
641            }
642            0xc7 => {
643                let length = usize::from(self.read_byte()?);
644                self.skip_ext(length)
645            }
646            0xc8 => {
647                let length = usize::from(self.read_u16()?);
648                self.skip_ext(length)
649            }
650            0xc9 => {
651                let length = usize::try_from(self.read_u32()?)
652                    .map_err(|_| MessagePackPreflightError::TooLarge)?;
653                self.skip_ext(length)
654            }
655            0xca => self.skip(4),
656            0xcb => self.skip(8),
657            0xcc | 0xd0 => self.skip(1),
658            0xcd | 0xd1 => self.skip(2),
659            0xce | 0xd2 => self.skip(4),
660            0xcf | 0xd3 => self.skip(8),
661            0xd4 => self.skip_ext(1),
662            0xd5 => self.skip_ext(2),
663            0xd6 => self.skip_ext(4),
664            0xd7 => self.skip_ext(8),
665            0xd8 => self.skip_ext(16),
666            0xdc => {
667                let count = usize::from(self.read_u16()?);
668                self.scan_children(count, depth)
669            }
670            0xdd => {
671                let count = usize::try_from(self.read_u32()?)
672                    .map_err(|_| MessagePackPreflightError::TooManyValues)?;
673                self.scan_children(count, depth)
674            }
675            0xde => {
676                let count = usize::from(self.read_u16()?);
677                self.scan_map(count, depth)
678            }
679            0xdf => {
680                let count = usize::try_from(self.read_u32()?)
681                    .map_err(|_| MessagePackPreflightError::TooManyValues)?;
682                self.scan_map(count, depth)
683            }
684        }
685    }
686
687    fn scan_map(
688        &mut self,
689        entries: usize,
690        depth: usize,
691    ) -> std::result::Result<(), MessagePackPreflightError> {
692        let children = entries
693            .checked_mul(2)
694            .ok_or(MessagePackPreflightError::TooManyValues)?;
695        self.scan_children(children, depth)
696    }
697
698    fn scan_children(
699        &mut self,
700        children: usize,
701        depth: usize,
702    ) -> std::result::Result<(), MessagePackPreflightError> {
703        if children > MAX_MESSAGEPACK_VALUES {
704            return Err(MessagePackPreflightError::TooManyValues);
705        }
706        for _ in 0..children {
707            self.scan_value(depth + 1)?;
708        }
709        Ok(())
710    }
711
712    fn skip_ext(
713        &mut self,
714        payload_length: usize,
715    ) -> std::result::Result<(), MessagePackPreflightError> {
716        let total = payload_length
717            .checked_add(1)
718            .ok_or(MessagePackPreflightError::TooLarge)?;
719        self.skip(total)
720    }
721
722    fn read_byte(&mut self) -> std::result::Result<u8, MessagePackPreflightError> {
723        let byte = *self
724            .payload
725            .get(self.position)
726            .ok_or(MessagePackPreflightError::Truncated)?;
727        self.position += 1;
728        Ok(byte)
729    }
730
731    fn read_u16(&mut self) -> std::result::Result<u16, MessagePackPreflightError> {
732        let bytes = self.take(2)?;
733        Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
734    }
735
736    fn read_u32(&mut self) -> std::result::Result<u32, MessagePackPreflightError> {
737        let bytes = self.take(4)?;
738        Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
739    }
740
741    fn skip(&mut self, length: usize) -> std::result::Result<(), MessagePackPreflightError> {
742        self.take(length).map(|_| ())
743    }
744
745    fn take(&mut self, length: usize) -> std::result::Result<&[u8], MessagePackPreflightError> {
746        let end = self
747            .position
748            .checked_add(length)
749            .ok_or(MessagePackPreflightError::Truncated)?;
750        let bytes = self
751            .payload
752            .get(self.position..end)
753            .ok_or(MessagePackPreflightError::Truncated)?;
754        self.position = end;
755        Ok(bytes)
756    }
757}
758
759fn normalize_quoted_identifiers(sql: &str) -> String {
760    let mut normalized = String::with_capacity(sql.len());
761    let mut chars = sql.chars().peekable();
762    while let Some(ch) = chars.next() {
763        match ch {
764            '\'' => {
765                normalized.push(ch);
766                while let Some(string_ch) = chars.next() {
767                    normalized.push(string_ch);
768                    if string_ch == '\'' {
769                        if chars.peek() == Some(&'\'') {
770                            normalized.push(chars.next().expect("peeked quote"));
771                        } else {
772                            break;
773                        }
774                    }
775                }
776            }
777            '"' => {
778                // Replace each quote with a space rather than removing it, so
779                // every later token keeps its original offset and diagnostics
780                // point into the SQL the caller actually wrote.
781                normalized.push(' ');
782                while let Some(identifier_ch) = chars.next() {
783                    if identifier_ch == '"' {
784                        if chars.peek() == Some(&'"') {
785                            // An escaped quote is two characters in the input
786                            // and one in the identifier; pad to keep the width.
787                            normalized.push(chars.next().expect("peeked quote"));
788                            normalized.push(' ');
789                        } else {
790                            normalized.push(' ');
791                            break;
792                        }
793                    } else {
794                        normalized.push(identifier_ch);
795                    }
796                }
797            }
798            '-' if chars.peek() == Some(&'-') => {
799                normalized.push(ch);
800                normalized.push(chars.next().expect("peeked comment dash"));
801                for comment_ch in chars.by_ref() {
802                    normalized.push(comment_ch);
803                    if comment_ch == '\n' {
804                        break;
805                    }
806                }
807            }
808            '/' if chars.peek() == Some(&'*') => {
809                normalized.push(ch);
810                normalized.push(chars.next().expect("peeked comment star"));
811                let mut previous = '\0';
812                for comment_ch in chars.by_ref() {
813                    normalized.push(comment_ch);
814                    if previous == '*' && comment_ch == '/' {
815                        break;
816                    }
817                    previous = comment_ch;
818                }
819            }
820            ch if ch.is_ascii_alphabetic() || ch == '_' => {
821                let mut identifier = String::from(ch);
822                while chars
823                    .peek()
824                    .is_some_and(|next| next.is_ascii_alphanumeric() || *next == '_')
825                {
826                    identifier.push(chars.next().expect("peeked identifier character"));
827                }
828                // PostgreSQL folds bare identifiers to lowercase. Delimited
829                // identifiers take the `\"` branch above and keep their exact
830                // spelling for case-sensitive resolution.
831                normalized.push_str(&identifier.to_ascii_lowercase());
832            }
833            _ => normalized.push(ch),
834        }
835    }
836    normalized
837}
838
839fn natural_join_markers(sql: &str) -> Vec<bool> {
840    let mut markers = Vec::new();
841    let mut saw_natural = false;
842    let mut chars = sql.chars().peekable();
843    while let Some(ch) = chars.next() {
844        match ch {
845            '\'' | '"' => skip_quoted(&mut chars, ch),
846            '-' if chars.peek() == Some(&'-') => {
847                chars.next();
848                for comment_ch in chars.by_ref() {
849                    if comment_ch == '\n' {
850                        break;
851                    }
852                }
853            }
854            '/' if chars.peek() == Some(&'*') => {
855                chars.next();
856                let mut previous = '\0';
857                for comment_ch in chars.by_ref() {
858                    if previous == '*' && comment_ch == '/' {
859                        break;
860                    }
861                    previous = comment_ch;
862                }
863            }
864            ';' => saw_natural = false,
865            c if c.is_ascii_alphabetic() || c == '_' => {
866                let mut word = String::from(c);
867                while chars
868                    .peek()
869                    .is_some_and(|next| next.is_ascii_alphanumeric() || *next == '_')
870                {
871                    word.push(chars.next().expect("peeked identifier character"));
872                }
873                match word.to_ascii_lowercase().as_str() {
874                    "natural" => saw_natural = true,
875                    "join" => {
876                        markers.push(saw_natural);
877                        saw_natural = false;
878                    }
879                    _ => {}
880                }
881            }
882            _ => {}
883        }
884    }
885    markers
886}
887
888fn skip_quoted(chars: &mut std::iter::Peekable<std::str::Chars<'_>>, quote: char) {
889    while let Some(ch) = chars.next() {
890        if ch == quote {
891            if chars.peek() == Some(&quote) {
892                chars.next();
893            } else {
894                break;
895            }
896        }
897    }
898}
899
900/// Apply the parser's NATURAL markers to the joins they belong to.
901///
902/// The markers arrive as a flat list alongside the AST, so they only line up
903/// while both sides walk the joins in the same order. A mismatch used to leave
904/// the remaining joins as plain joins, turning `NATURAL JOIN` into a cross
905/// product without any diagnostic. Treat it as the contract violation it is.
906fn annotate_natural_joins(statements: &mut [Statement], natural_markers: Vec<bool>) -> Result<()> {
907    let supplied = natural_markers.len();
908    let mut natural_markers = natural_markers.into_iter();
909    let mut consumed = 0usize;
910    for statement in statements {
911        if let StatementKind::Select(select) = &mut statement.kind {
912            annotate_select_natural_joins(select, &mut natural_markers, &mut consumed);
913        }
914    }
915
916    if consumed != supplied {
917        return Err(ParserError::UnexpectedToken {
918            line: 0,
919            column: 0,
920            expected: format!("{supplied} NATURAL join markers, one per join"),
921            found: format!("{consumed} joins in the AST"),
922        });
923    }
924    Ok(())
925}
926
927fn annotate_select_natural_joins(
928    select: &mut Select,
929    natural_markers: &mut impl Iterator<Item = bool>,
930    consumed: &mut usize,
931) {
932    for item in &mut select.projection {
933        if let SelectItem::Expr { expr, .. } = item {
934            annotate_expr_natural_joins(expr, natural_markers, consumed);
935        }
936    }
937    for from in &mut select.from {
938        annotate_from_natural_joins(from, natural_markers, consumed);
939    }
940    if let Some(selection) = &mut select.selection {
941        annotate_expr_natural_joins(selection, natural_markers, consumed);
942    }
943    if let Some(group_by) = &mut select.group_by {
944        for expression in group_by {
945            annotate_expr_natural_joins(expression, natural_markers, consumed);
946        }
947    }
948    if let Some(having) = &mut select.having {
949        annotate_expr_natural_joins(having, natural_markers, consumed);
950    }
951    for order_by in &mut select.order_by {
952        annotate_expr_natural_joins(&mut order_by.expr, natural_markers, consumed);
953    }
954    if let Some(limit) = &mut select.limit {
955        annotate_expr_natural_joins(limit, natural_markers, consumed);
956    }
957    if let Some(offset) = &mut select.offset {
958        annotate_expr_natural_joins(offset, natural_markers, consumed);
959    }
960}
961
962fn annotate_from_natural_joins(
963    from: &mut FromItem,
964    natural_markers: &mut impl Iterator<Item = bool>,
965    consumed: &mut usize,
966) {
967    match from {
968        FromItem::Join {
969            left,
970            right,
971            natural,
972            ..
973        } => {
974            annotate_from_natural_joins(left, natural_markers, consumed);
975            if let Some(marker) = natural_markers.next() {
976                *natural |= marker;
977                *consumed += 1;
978            }
979            annotate_from_natural_joins(right, natural_markers, consumed);
980        }
981        FromItem::Derived { subquery, .. } => {
982            if let StatementKind::Select(select) = &mut subquery.kind {
983                annotate_select_natural_joins(select, natural_markers, consumed);
984            }
985        }
986        FromItem::Table { .. } => {}
987    }
988}
989
990fn annotate_expr_natural_joins(
991    expr: &mut Expr,
992    natural_markers: &mut impl Iterator<Item = bool>,
993    consumed: &mut usize,
994) {
995    match &mut expr.kind {
996        ExprKind::ScalarSubquery { subquery } | ExprKind::Exists { subquery, .. } => {
997            if let StatementKind::Select(select) = &mut subquery.kind {
998                annotate_select_natural_joins(select, natural_markers, consumed);
999            }
1000        }
1001        ExprKind::InSubquery { expr, subquery, .. }
1002        | ExprKind::Quantified { expr, subquery, .. } => {
1003            annotate_expr_natural_joins(expr, natural_markers, consumed);
1004            if let StatementKind::Select(select) = &mut subquery.kind {
1005                annotate_select_natural_joins(select, natural_markers, consumed);
1006            }
1007        }
1008        ExprKind::BinaryOp { left, right, .. } => {
1009            annotate_expr_natural_joins(left, natural_markers, consumed);
1010            annotate_expr_natural_joins(right, natural_markers, consumed);
1011        }
1012        ExprKind::UnaryOp { operand, .. } | ExprKind::IsNull { expr: operand, .. } => {
1013            annotate_expr_natural_joins(operand, natural_markers, consumed);
1014        }
1015        ExprKind::Case {
1016            operand,
1017            branches,
1018            else_expr,
1019        } => {
1020            if let Some(operand) = operand {
1021                annotate_expr_natural_joins(operand, natural_markers, consumed);
1022            }
1023            for branch in branches {
1024                annotate_expr_natural_joins(&mut branch.when, natural_markers, consumed);
1025                annotate_expr_natural_joins(&mut branch.then, natural_markers, consumed);
1026            }
1027            if let Some(else_expr) = else_expr {
1028                annotate_expr_natural_joins(else_expr, natural_markers, consumed);
1029            }
1030        }
1031        ExprKind::FunctionCall { args, .. } => {
1032            for argument in args {
1033                annotate_expr_natural_joins(argument, natural_markers, consumed);
1034            }
1035        }
1036        ExprKind::Between {
1037            expr, low, high, ..
1038        } => {
1039            annotate_expr_natural_joins(expr, natural_markers, consumed);
1040            annotate_expr_natural_joins(low, natural_markers, consumed);
1041            annotate_expr_natural_joins(high, natural_markers, consumed);
1042        }
1043        ExprKind::Like {
1044            expr,
1045            pattern,
1046            escape,
1047            ..
1048        } => {
1049            annotate_expr_natural_joins(expr, natural_markers, consumed);
1050            annotate_expr_natural_joins(pattern, natural_markers, consumed);
1051            if let Some(escape) = escape {
1052                annotate_expr_natural_joins(escape, natural_markers, consumed);
1053            }
1054        }
1055        ExprKind::InList { expr, list, .. } => {
1056            annotate_expr_natural_joins(expr, natural_markers, consumed);
1057            for item in list {
1058                annotate_expr_natural_joins(item, natural_markers, consumed);
1059            }
1060        }
1061        ExprKind::Cast { expr, .. } => {
1062            annotate_expr_natural_joins(expr, natural_markers, consumed);
1063        }
1064        ExprKind::Literal { .. } | ExprKind::ColumnRef { .. } | ExprKind::VectorLiteral { .. } => {}
1065    }
1066}
1067
1068pub fn parse_expression_sql(sql: &str) -> Result<crate::ast::Expr> {
1069    let wrapped_len =
1070        preflight_input(sql, SELECT_WRAPPER_PREFIX.len()).map_err(parser_error_from_preflight)?;
1071    let mut wrapped = String::with_capacity(wrapped_len);
1072    wrapped.push_str(SELECT_WRAPPER_PREFIX);
1073    wrapped.push_str(sql);
1074    let statements = parse_sql_preflighted(&wrapped)?;
1075    let Some(statement) = statements.into_iter().next() else {
1076        return Err(empty_expression_error());
1077    };
1078    let StatementKind::Select(select) = statement.kind else {
1079        return Err(empty_expression_error());
1080    };
1081    let Some(crate::ast::SelectItem::Expr { expr, .. }) = select.projection.into_iter().next()
1082    else {
1083        return Err(empty_expression_error());
1084    };
1085    Ok(expr)
1086}
1087
1088fn empty_expression_error() -> ParserError {
1089    ParserError::UnexpectedToken {
1090        line: 0,
1091        column: 0,
1092        expected: "expression".to_string(),
1093        found: "empty parser result".to_string(),
1094    }
1095}
1096
1097fn checked_total_input_len(
1098    input_len: usize,
1099    wrapper_len: usize,
1100) -> std::result::Result<usize, InputPreflightError> {
1101    let total_len = input_len
1102        .checked_add(wrapper_len)
1103        .ok_or(InputPreflightError::LengthOverflow)?;
1104    if total_len > MAX_SQL_INPUT_BYTES {
1105        return Err(InputPreflightError::TooLarge);
1106    }
1107    Ok(total_len)
1108}
1109
1110fn preflight_input(
1111    sql: &str,
1112    wrapper_len: usize,
1113) -> std::result::Result<usize, InputPreflightError> {
1114    let total_len = checked_total_input_len(sql.len(), wrapper_len)?;
1115    if sql.as_bytes().contains(&0) {
1116        return Err(InputPreflightError::InteriorNul);
1117    }
1118    Ok(total_len)
1119}
1120
1121fn parser_error_from_preflight(error: InputPreflightError) -> ParserError {
1122    match error {
1123        InputPreflightError::TooLarge | InputPreflightError::LengthOverflow => {
1124            input_too_large_error()
1125        }
1126        InputPreflightError::InteriorNul => interior_nul_error(),
1127    }
1128}
1129
1130fn parser_error_from_ffi_input(error: nim_ffi::ParseInputError) -> ParserError {
1131    match error {
1132        nim_ffi::ParseInputError::LengthOutOfRange => input_too_large_error(),
1133        nim_ffi::ParseInputError::InteriorNul => interior_nul_error(),
1134    }
1135}
1136
1137fn input_too_large_error() -> ParserError {
1138    ParserError::UnexpectedToken {
1139        line: 0,
1140        column: 0,
1141        expected: "SQL input at most 1048576 UTF-8 bytes".to_string(),
1142        found: "SQL input exceeds byte limit".to_string(),
1143    }
1144}
1145
1146fn interior_nul_error() -> ParserError {
1147    ParserError::UnexpectedToken {
1148        line: 0,
1149        column: 0,
1150        expected: "valid SQL without interior NUL bytes".to_string(),
1151        found: "interior NUL byte".to_string(),
1152    }
1153}
1154
1155// nim-sql-parser/src/alopex_sql_parser.nim の `internalDefectPrefix` と
1156// 一致させる。Nim 側の `except Defect` 節が付与する接頭辞で、パーサー
1157// 内部の不変条件違反 (通常の構文エラーではない) を機械的に区別するための
1158// マーカー。ワイヤ契約 (MessagePack AST) には影響しない、エラー文言のみの
1159// 合意。
1160const INTERNAL_DEFECT_PREFIX: &str =
1161    "internal parser defect (this is a parser bug, not invalid SQL): ";
1162
1163fn parser_error_from_nim(message: &str) -> ParserError {
1164    if let Some(defect_message) = message.strip_prefix(INTERNAL_DEFECT_PREFIX) {
1165        return ParserError::InternalParserDefect {
1166            message: defect_message.to_string(),
1167        };
1168    }
1169    let (line, column) = parse_nim_line_col(message).unwrap_or((0, 0));
1170    ParserError::UnexpectedToken {
1171        line,
1172        column,
1173        expected: "valid SQL".to_string(),
1174        found: message.to_string(),
1175    }
1176}
1177
1178fn parse_nim_line_col(message: &str) -> Option<(u64, u64)> {
1179    let after_line = message.strip_prefix("Parse error at line ")?;
1180    let (line, rest) = after_line.split_once(", col ")?;
1181    let (col, _) = rest.split_once(':')?;
1182    Some((line.parse().ok()?, col.parse().ok()?))
1183}
1184
1185#[cfg(test)]
1186mod input_preflight_tests {
1187    use super::*;
1188
1189    #[test]
1190    fn raw_sql_guard_accepts_boundary_minus_and_exact_but_rejects_plus() {
1191        assert_eq!(
1192            checked_total_input_len(MAX_SQL_INPUT_BYTES - 1, 0),
1193            Ok(MAX_SQL_INPUT_BYTES - 1)
1194        );
1195        assert_eq!(
1196            checked_total_input_len(MAX_SQL_INPUT_BYTES, 0),
1197            Ok(MAX_SQL_INPUT_BYTES)
1198        );
1199        assert_eq!(
1200            checked_total_input_len(MAX_SQL_INPUT_BYTES + 1, 0),
1201            Err(InputPreflightError::TooLarge)
1202        );
1203    }
1204
1205    #[test]
1206    fn guard_counts_utf8_bytes_instead_of_characters() {
1207        let exact = "é".repeat(MAX_SQL_INPUT_BYTES / "é".len());
1208        assert_eq!(exact.chars().count(), MAX_SQL_INPUT_BYTES / 2);
1209        assert_eq!(exact.len(), MAX_SQL_INPUT_BYTES);
1210        assert_eq!(preflight_input(&exact, 0), Ok(MAX_SQL_INPUT_BYTES));
1211
1212        let plus = format!("{exact}é");
1213        assert_eq!(
1214            preflight_input(&plus, 0),
1215            Err(InputPreflightError::TooLarge)
1216        );
1217    }
1218
1219    #[test]
1220    fn expression_guard_includes_wrapper_and_detects_length_overflow() {
1221        assert_eq!(
1222            checked_total_input_len(
1223                MAX_SQL_INPUT_BYTES - SELECT_WRAPPER_PREFIX.len(),
1224                SELECT_WRAPPER_PREFIX.len(),
1225            ),
1226            Ok(MAX_SQL_INPUT_BYTES)
1227        );
1228        assert_eq!(
1229            checked_total_input_len(
1230                MAX_SQL_INPUT_BYTES - SELECT_WRAPPER_PREFIX.len() + 1,
1231                SELECT_WRAPPER_PREFIX.len(),
1232            ),
1233            Err(InputPreflightError::TooLarge)
1234        );
1235        assert_eq!(
1236            checked_total_input_len(usize::MAX, SELECT_WRAPPER_PREFIX.len()),
1237            Err(InputPreflightError::LengthOverflow)
1238        );
1239    }
1240
1241    #[test]
1242    fn preflight_rejects_nul_before_any_ffi_work() {
1243        assert_eq!(
1244            preflight_input("SELECT \0 1", 0),
1245            Err(InputPreflightError::InteriorNul)
1246        );
1247        assert_eq!(
1248            preflight_input("1 \0 2", SELECT_WRAPPER_PREFIX.len()),
1249            Err(InputPreflightError::InteriorNul)
1250        );
1251    }
1252}