Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4786,8 +4786,8 @@ pub enum Statement {
Pragma {
/// Pragma name (possibly qualified).
name: ObjectName,
/// Optional pragma value.
value: Option<ValueWithSpan>,
/// Optional pragma value (`signed-number`, `name`, or `signed-literal`).
value: Option<Expr>,
/// Whether the pragma used `=`.
is_eq: bool,
},
Expand Down
49 changes: 21 additions & 28 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20157,44 +20157,37 @@ impl<'a> Parser<'a> {
})
}

fn parse_pragma_value(&mut self) -> Result<ValueWithSpan, ParserError> {
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<Expr, ParserError> {
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()?)),
}
Comment on lines +20162 to 20175

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm can this be replaced by a call to parse_expr()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of accepting things, yes it would work, but parse_expr() would over-extend the grammar accepting arbitrary expressions like PRAGMA p = 1 + 2, foo(bar), a AND b or (SELECT 1).

SQLite defines a pragma value as signed-number | name | signed-literal, so parse_pragma_value mirrors that production exactly while still leaving the per-pragma value validation to execution.

I understand I tend to lean much more strongly on the strict parser approach, but here I really believe allowing any expr would be excessively loose.

}

/// PRAGMA [schema-name '.'] pragma-name [('=' pragma-value) | '(' pragma-value ')']
pub fn parse_pragma(&mut self) -> Result<Statement, ParserError> {
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]`
Expand Down
82 changes: 80 additions & 2 deletions tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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,
..
}
));
}
Loading