Skip to content

Commit 1c2fb43

Browse files
SQLite: parse the full PRAGMA value grammar
1 parent 88ffee6 commit 1c2fb43

3 files changed

Lines changed: 127 additions & 31 deletions

File tree

‎src/ast/mod.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4811,8 +4811,8 @@ pub enum Statement {
48114811
Pragma {
48124812
/// Pragma name (possibly qualified).
48134813
name: ObjectName,
4814-
/// Optional pragma value.
4815-
value: Option<ValueWithSpan>,
4814+
/// Optional pragma value (`signed-number`, `name`, or `signed-literal`).
4815+
value: Option<Expr>,
48164816
/// Whether the pragma used `=`.
48174817
is_eq: bool,
48184818
},

‎src/parser/mod.rs‎

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20199,44 +20199,50 @@ impl<'a> Parser<'a> {
2019920199
})
2020020200
}
2020120201

20202-
fn parse_pragma_value(&mut self) -> Result<ValueWithSpan, ParserError> {
20203-
let v = self.parse_value()?;
20204-
match &v.value {
20205-
Value::SingleQuotedString(_) => Ok(v),
20206-
Value::DoubleQuotedString(_) => Ok(v),
20207-
Value::Number(_, _) => Ok(v),
20208-
Value::Placeholder(_) => Ok(v),
20209-
_ => {
20210-
self.prev_token();
20211-
self.expected_ref("number or string or ? placeholder", self.peek_token_ref())
20202+
/// Parse a SQLite `pragma-value`: `signed-number | name | signed-literal`.
20203+
fn parse_pragma_value(&mut self) -> Result<Expr, ParserError> {
20204+
if matches!(self.peek_token_ref().token, Token::Plus | Token::Minus) {
20205+
let op = match self.next_token().token {
20206+
Token::Plus => UnaryOperator::Plus,
20207+
_ => UnaryOperator::Minus,
20208+
};
20209+
// SQLite only permits a sign before a number
20210+
if !matches!(
20211+
self.peek_token_ref().token,
20212+
Token::Number(..) | Token::HexStringLiteral(_)
20213+
) {
20214+
return self.expected_ref("number", self.peek_token_ref());
2021220215
}
20216+
return Ok(Expr::UnaryOp {
20217+
op,
20218+
expr: Box::new(Expr::Value(self.parse_value()?)),
20219+
});
20220+
}
20221+
// NULL is not a valid SQLite pragma value
20222+
if let Token::Word(w) = &self.peek_token_ref().token {
20223+
if w.keyword == Keyword::NULL {
20224+
return self.expected_ref("number, name or string", self.peek_token_ref());
20225+
}
20226+
}
20227+
match self.maybe_parse(|parser| parser.parse_value())? {
20228+
Some(value) => Ok(Expr::Value(value)),
20229+
None => Ok(Expr::Identifier(self.parse_identifier()?)),
2021320230
}
2021420231
}
2021520232

2021620233
/// PRAGMA [schema-name '.'] pragma-name [('=' pragma-value) | '(' pragma-value ')']
2021720234
pub fn parse_pragma(&mut self) -> Result<Statement, ParserError> {
2021820235
let name = self.parse_object_name(false)?;
20219-
if self.consume_token(&Token::LParen) {
20236+
let (value, is_eq) = if self.consume_token(&Token::LParen) {
2022020237
let value = self.parse_pragma_value()?;
2022120238
self.expect_token(&Token::RParen)?;
20222-
Ok(Statement::Pragma {
20223-
name,
20224-
value: Some(value),
20225-
is_eq: false,
20226-
})
20239+
(Some(value), false)
2022720240
} else if self.consume_token(&Token::Eq) {
20228-
Ok(Statement::Pragma {
20229-
name,
20230-
value: Some(self.parse_pragma_value()?),
20231-
is_eq: true,
20232-
})
20241+
(Some(self.parse_pragma_value()?), true)
2023320242
} else {
20234-
Ok(Statement::Pragma {
20235-
name,
20236-
value: None,
20237-
is_eq: false,
20238-
})
20239-
}
20243+
(None, false)
20244+
};
20245+
Ok(Statement::Pragma { name, value, is_eq })
2024020246
}
2024120247

2024220248
/// `INSTALL [extension_name]`

‎tests/sqlparser_sqlite.rs‎

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ use sqlparser::ast::SelectItem::UnnamedExpr;
3030
use sqlparser::ast::Value::Placeholder;
3131
use sqlparser::ast::*;
3232
use sqlparser::dialect::{GenericDialect, SQLiteDialect};
33-
use sqlparser::parser::{ParserError, ParserOptions};
34-
use sqlparser::tokenizer::Token;
33+
use sqlparser::parser::{Parser, ParserError, ParserOptions};
34+
use sqlparser::tokenizer::{Span, Token};
3535

3636
#[test]
3737
fn pragma_no_value() {
@@ -983,3 +983,93 @@ fn sqlite_and_generic() -> TestedDialects {
983983
Box::new(GenericDialect {}),
984984
])
985985
}
986+
987+
#[test]
988+
fn pragma_values() {
989+
// signed-literal
990+
let statement = sqlite_and_generic().verified_stmt("PRAGMA case_sensitive_like = true");
991+
assert!(matches!(
992+
statement,
993+
Statement::Pragma {
994+
value: Some(Expr::Value(ValueWithSpan {
995+
value: Value::Boolean(true),
996+
..
997+
})),
998+
is_eq: true,
999+
..
1000+
}
1001+
));
1002+
1003+
// name: kept verbatim as an identifier
1004+
for spelling in [
1005+
"ON", "OFF", "YES", "NO", "WAL", "DELETE", "NORMAL", "FULL", "MEMORY",
1006+
] {
1007+
let sql = format!("PRAGMA case_sensitive_like = {spelling}");
1008+
let Statement::Pragma {
1009+
value: Some(Expr::Identifier(ident)),
1010+
is_eq: true,
1011+
..
1012+
} = sqlite_and_generic().verified_stmt(&sql)
1013+
else {
1014+
panic!("expected identifier pragma value for {spelling}");
1015+
};
1016+
assert_eq!(spelling, ident.value);
1017+
assert_eq!(None, ident.quote_style);
1018+
}
1019+
1020+
// identifier value keeps its span
1021+
let statements =
1022+
Parser::parse_sql(&SQLiteDialect {}, "PRAGMA case_sensitive_like = oN").unwrap();
1023+
let [Statement::Pragma {
1024+
value: Some(Expr::Identifier(ident)),
1025+
is_eq: true,
1026+
..
1027+
}] = statements.as_slice()
1028+
else {
1029+
panic!("Expected equality-form PRAGMA")
1030+
};
1031+
assert_eq!("oN", ident.value);
1032+
assert_eq!(Span::new((1, 30).into(), (1, 32).into()), ident.span);
1033+
1034+
// signed-number
1035+
let statement = sqlite_and_generic().verified_stmt("PRAGMA cache_size = -2000");
1036+
assert!(matches!(
1037+
statement,
1038+
Statement::Pragma {
1039+
value: Some(Expr::UnaryOp {
1040+
op: UnaryOperator::Minus,
1041+
..
1042+
}),
1043+
is_eq: true,
1044+
..
1045+
}
1046+
));
1047+
sqlite_and_generic().verified_stmt("PRAGMA cache_size = +2000");
1048+
1049+
// hex integer tokenizes to a hex string literal
1050+
sqlite_and_generic()
1051+
.one_statement_parses_to("PRAGMA optimize = 0x10002", "PRAGMA optimize = X'10002'");
1052+
1053+
// function-call form with a name argument
1054+
let statement = sqlite_and_generic().verified_stmt("PRAGMA wal_checkpoint(TRUNCATE)");
1055+
assert!(matches!(
1056+
statement,
1057+
Statement::Pragma {
1058+
value: Some(Expr::Identifier(_)),
1059+
is_eq: false,
1060+
..
1061+
}
1062+
));
1063+
// values SQLite rejects
1064+
for (sql, error) in [
1065+
("PRAGMA x = -'str'", "Expected: number, found: 'str'"),
1066+
("PRAGMA x = +'str'", "Expected: number, found: 'str'"),
1067+
(
1068+
"PRAGMA x = NULL",
1069+
"Expected: number, name or string, found: NULL",
1070+
),
1071+
] {
1072+
let actual = sqlite_and_generic().parse_sql_statements(sql).unwrap_err();
1073+
assert!(actual.to_string().contains(error), "{sql}: {actual}");
1074+
}
1075+
}

0 commit comments

Comments
 (0)