From 582c4e430997389a223f734270547e2260200802 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Mon, 31 Aug 2026 12:33:54 +0200 Subject: [PATCH] SQLite: parse the full PRAGMA value grammar --- src/ast/mod.rs | 4 +- src/parser/mod.rs | 49 ++++++++++------------- tests/sqlparser_sqlite.rs | 82 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 32 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 4d86dd6a6..63e2971a6 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -4783,8 +4783,8 @@ pub enum Statement { Pragma { /// Pragma name (possibly qualified). name: ObjectName, - /// Optional pragma value. - value: Option, + /// Optional pragma value (`signed-number`, `name`, or `signed-literal`). + value: Option, /// Whether the pragma used `=`. is_eq: bool, }, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6af0fb776..278a0f118 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -20166,44 +20166,37 @@ impl<'a> Parser<'a> { }) } - fn parse_pragma_value(&mut self) -> Result { - let v = self.parse_value()?; - match &v.value { - Value::SingleQuotedString(_) => Ok(v), - Value::DoubleQuotedString(_) => Ok(v), - Value::Number(_, _) => Ok(v), - Value::Placeholder(_) => Ok(v), - _ => { - self.prev_token(); - self.expected_ref("number or string or ? placeholder", self.peek_token_ref()) - } + /// Parse a SQLite `pragma-value`: `signed-number | name | signed-literal`. + fn parse_pragma_value(&mut self) -> Result { + if matches!(self.peek_token_ref().token, Token::Plus | Token::Minus) { + let op = match self.next_token().token { + Token::Plus => UnaryOperator::Plus, + _ => UnaryOperator::Minus, + }; + return Ok(Expr::UnaryOp { + op, + expr: Box::new(Expr::Value(self.parse_value()?)), + }); + } + match self.maybe_parse(|parser| parser.parse_value())? { + Some(value) => Ok(Expr::Value(value)), + None => Ok(Expr::Identifier(self.parse_identifier()?)), } } /// PRAGMA [schema-name '.'] pragma-name [('=' pragma-value) | '(' pragma-value ')'] pub fn parse_pragma(&mut self) -> Result { let name = self.parse_object_name(false)?; - if self.consume_token(&Token::LParen) { + let (value, is_eq) = if self.consume_token(&Token::LParen) { let value = self.parse_pragma_value()?; self.expect_token(&Token::RParen)?; - Ok(Statement::Pragma { - name, - value: Some(value), - is_eq: false, - }) + (Some(value), false) } else if self.consume_token(&Token::Eq) { - Ok(Statement::Pragma { - name, - value: Some(self.parse_pragma_value()?), - is_eq: true, - }) + (Some(self.parse_pragma_value()?), true) } else { - Ok(Statement::Pragma { - name, - value: None, - is_eq: false, - }) - } + (None, false) + }; + Ok(Statement::Pragma { name, value, is_eq }) } /// `INSTALL [extension_name]` diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index a017562df..d4dc08fff 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -30,8 +30,8 @@ use sqlparser::ast::SelectItem::UnnamedExpr; use sqlparser::ast::Value::Placeholder; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, SQLiteDialect}; -use sqlparser::parser::{ParserError, ParserOptions}; -use sqlparser::tokenizer::Token; +use sqlparser::parser::{Parser, ParserError, ParserOptions}; +use sqlparser::tokenizer::{Span, Token}; #[test] fn pragma_no_value() { @@ -971,3 +971,81 @@ fn sqlite_and_generic() -> TestedDialects { Box::new(GenericDialect {}), ]) } + +#[test] +fn pragma_values() { + // signed-literal + let statement = sqlite_and_generic().verified_stmt("PRAGMA case_sensitive_like = true"); + assert!(matches!( + statement, + Statement::Pragma { + value: Some(Expr::Value(ValueWithSpan { + value: Value::Boolean(true), + .. + })), + is_eq: true, + .. + } + )); + + // name: kept verbatim as an identifier + for spelling in [ + "ON", "OFF", "YES", "NO", "WAL", "DELETE", "NORMAL", "FULL", "MEMORY", + ] { + let sql = format!("PRAGMA case_sensitive_like = {spelling}"); + let Statement::Pragma { + value: Some(Expr::Identifier(ident)), + is_eq: true, + .. + } = sqlite_and_generic().verified_stmt(&sql) + else { + panic!("expected identifier pragma value for {spelling}"); + }; + assert_eq!(spelling, ident.value); + assert_eq!(None, ident.quote_style); + } + + // identifier value keeps its span + let statements = + Parser::parse_sql(&SQLiteDialect {}, "PRAGMA case_sensitive_like = oN").unwrap(); + let [Statement::Pragma { + value: Some(Expr::Identifier(ident)), + is_eq: true, + .. + }] = statements.as_slice() + else { + panic!("Expected equality-form PRAGMA") + }; + assert_eq!("oN", ident.value); + assert_eq!(Span::new((1, 30).into(), (1, 32).into()), ident.span); + + // signed-number + let statement = sqlite_and_generic().verified_stmt("PRAGMA cache_size = -2000"); + assert!(matches!( + statement, + Statement::Pragma { + value: Some(Expr::UnaryOp { + op: UnaryOperator::Minus, + .. + }), + is_eq: true, + .. + } + )); + sqlite_and_generic().verified_stmt("PRAGMA cache_size = +2000"); + + // hex integer tokenizes to a hex string literal + sqlite_and_generic() + .one_statement_parses_to("PRAGMA optimize = 0x10002", "PRAGMA optimize = X'10002'"); + + // function-call form with a name argument + let statement = sqlite_and_generic().verified_stmt("PRAGMA wal_checkpoint(TRUNCATE)"); + assert!(matches!( + statement, + Statement::Pragma { + value: Some(Expr::Identifier(_)), + is_eq: false, + .. + } + )); +}