Skip to content

Commit 4dfcf28

Browse files
SQLite: parse the full PRAGMA value grammar
1 parent c0054b7 commit 4dfcf28

3 files changed

Lines changed: 107 additions & 32 deletions

File tree

src/ast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4783,8 +4783,8 @@ pub enum Statement {
47834783
Pragma {
47844784
/// Pragma name (possibly qualified).
47854785
name: ObjectName,
4786-
/// Optional pragma value.
4787-
value: Option<ValueWithSpan>,
4786+
/// Optional pragma value (`signed-number`, `name`, or `signed-literal`).
4787+
value: Option<Expr>,
47884788
/// Whether the pragma used `=`.
47894789
is_eq: bool,
47904790
},

src/parser/mod.rs

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20166,44 +20166,40 @@ impl<'a> Parser<'a> {
2016620166
})
2016720167
}
2016820168

20169-
fn parse_pragma_value(&mut self) -> Result<ValueWithSpan, ParserError> {
20170-
let v = self.parse_value()?;
20171-
match &v.value {
20172-
Value::SingleQuotedString(_) => Ok(v),
20173-
Value::DoubleQuotedString(_) => Ok(v),
20174-
Value::Number(_, _) => Ok(v),
20175-
Value::Placeholder(_) => Ok(v),
20176-
_ => {
20177-
self.prev_token();
20178-
self.expected_ref("number or string or ? placeholder", self.peek_token_ref())
20179-
}
20169+
/// Parse a SQLite `pragma-value`: `signed-number | name | signed-literal`
20170+
/// (<https://www.sqlite.org/pragma.html>). SQLite performs no per-pragma type
20171+
/// validation at parse time, so a bare `name` such as `WAL` is kept as an
20172+
/// identifier and left for the caller to interpret.
20173+
fn parse_pragma_value(&mut self) -> Result<Expr, ParserError> {
20174+
if matches!(self.peek_token_ref().token, Token::Plus | Token::Minus) {
20175+
let op = match self.next_token().token {
20176+
Token::Plus => UnaryOperator::Plus,
20177+
_ => UnaryOperator::Minus,
20178+
};
20179+
return Ok(Expr::UnaryOp {
20180+
op,
20181+
expr: Box::new(Expr::Value(self.parse_value()?)),
20182+
});
20183+
}
20184+
match self.maybe_parse(|parser| parser.parse_value())? {
20185+
Some(value) => Ok(Expr::Value(value)),
20186+
None => Ok(Expr::Identifier(self.parse_identifier()?)),
2018020187
}
2018120188
}
2018220189

2018320190
/// PRAGMA [schema-name '.'] pragma-name [('=' pragma-value) | '(' pragma-value ')']
2018420191
pub fn parse_pragma(&mut self) -> Result<Statement, ParserError> {
2018520192
let name = self.parse_object_name(false)?;
20186-
if self.consume_token(&Token::LParen) {
20193+
let (value, is_eq) = if self.consume_token(&Token::LParen) {
2018720194
let value = self.parse_pragma_value()?;
2018820195
self.expect_token(&Token::RParen)?;
20189-
Ok(Statement::Pragma {
20190-
name,
20191-
value: Some(value),
20192-
is_eq: false,
20193-
})
20196+
(Some(value), false)
2019420197
} else if self.consume_token(&Token::Eq) {
20195-
Ok(Statement::Pragma {
20196-
name,
20197-
value: Some(self.parse_pragma_value()?),
20198-
is_eq: true,
20199-
})
20198+
(Some(self.parse_pragma_value()?), true)
2020020199
} else {
20201-
Ok(Statement::Pragma {
20202-
name,
20203-
value: None,
20204-
is_eq: false,
20205-
})
20206-
}
20200+
(None, false)
20201+
};
20202+
Ok(Statement::Pragma { name, value, is_eq })
2020720203
}
2020820204

2020920205
/// `INSTALL [extension_name]`

tests/sqlparser_sqlite.rs

Lines changed: 81 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() {
@@ -971,3 +971,82 @@ fn sqlite_and_generic() -> TestedDialects {
971971
Box::new(GenericDialect {}),
972972
])
973973
}
974+
975+
#[test]
976+
fn pragma_values() {
977+
// `signed-literal`: boolean and numeric literals keep their `Value`.
978+
let statement = sqlite_and_generic().verified_stmt("PRAGMA case_sensitive_like = true");
979+
assert!(matches!(
980+
statement,
981+
Statement::Pragma {
982+
value: Some(Expr::Value(ValueWithSpan {
983+
value: Value::Boolean(true),
984+
..
985+
})),
986+
is_eq: true,
987+
..
988+
}
989+
));
990+
991+
// `name`: bare keywords are kept verbatim as identifiers. SQLite assigns
992+
// their meaning at execution time, so the parser does not interpret them.
993+
for spelling in [
994+
"ON", "OFF", "YES", "NO", "WAL", "DELETE", "NORMAL", "FULL", "MEMORY",
995+
] {
996+
let sql = format!("PRAGMA case_sensitive_like = {spelling}");
997+
let Statement::Pragma {
998+
value: Some(Expr::Identifier(ident)),
999+
is_eq: true,
1000+
..
1001+
} = sqlite_and_generic().verified_stmt(&sql)
1002+
else {
1003+
panic!("expected identifier pragma value for {spelling}");
1004+
};
1005+
assert_eq!(spelling, ident.value);
1006+
assert_eq!(None, ident.quote_style);
1007+
}
1008+
1009+
// The identifier value keeps its source span.
1010+
let statements =
1011+
Parser::parse_sql(&SQLiteDialect {}, "PRAGMA case_sensitive_like = oN").unwrap();
1012+
let [Statement::Pragma {
1013+
value: Some(Expr::Identifier(ident)),
1014+
is_eq: true,
1015+
..
1016+
}] = statements.as_slice()
1017+
else {
1018+
panic!("Expected equality-form PRAGMA")
1019+
};
1020+
assert_eq!("oN", ident.value);
1021+
assert_eq!(Span::new((1, 30).into(), (1, 32).into()), ident.span);
1022+
1023+
// `signed-number`: a leading sign becomes a unary operation.
1024+
let statement = sqlite_and_generic().verified_stmt("PRAGMA cache_size = -2000");
1025+
assert!(matches!(
1026+
statement,
1027+
Statement::Pragma {
1028+
value: Some(Expr::UnaryOp {
1029+
op: UnaryOperator::Minus,
1030+
..
1031+
}),
1032+
is_eq: true,
1033+
..
1034+
}
1035+
));
1036+
sqlite_and_generic().verified_stmt("PRAGMA cache_size = +2000");
1037+
1038+
// Hex integer literals tokenize to a hex string literal.
1039+
sqlite_and_generic()
1040+
.one_statement_parses_to("PRAGMA optimize = 0x10002", "PRAGMA optimize = X'10002'");
1041+
1042+
// The function-call form accepts a `name` argument too.
1043+
let statement = sqlite_and_generic().verified_stmt("PRAGMA wal_checkpoint(TRUNCATE)");
1044+
assert!(matches!(
1045+
statement,
1046+
Statement::Pragma {
1047+
value: Some(Expr::Identifier(_)),
1048+
is_eq: false,
1049+
..
1050+
}
1051+
));
1052+
}

0 commit comments

Comments
 (0)