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()?)),
}
}

/// 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