mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-17 09:17:14 +00:00
Generalize locking clause (#759)
Postgres supports more generalized locking clauses, for example: FOR UPDATE OF <table_name> SKIP LOCKED also, multiple locking clauses. Generalize the parser to support these. Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
parent
6c545195e1
commit
fb02344131
7 changed files with 187 additions and 36 deletions
|
@ -32,9 +32,9 @@ pub use self::ddl::{
|
||||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||||
pub use self::query::{
|
pub use self::query::{
|
||||||
Cte, ExceptSelectItem, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator,
|
Cte, ExceptSelectItem, ExcludeSelectItem, Fetch, Join, JoinConstraint, JoinOperator,
|
||||||
LateralView, LockType, Offset, OffsetRows, OrderByExpr, Query, Select, SelectInto, SelectItem,
|
LateralView, LockClause, LockType, NonBlock, Offset, OffsetRows, OrderByExpr, Query, Select,
|
||||||
SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor, TableWithJoins, Top,
|
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor,
|
||||||
Values, WildcardAdditionalOptions, With,
|
TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
|
||||||
};
|
};
|
||||||
pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value};
|
pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value};
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@ pub struct Query {
|
||||||
pub offset: Option<Offset>,
|
pub offset: Option<Offset>,
|
||||||
/// `FETCH { FIRST | NEXT } <N> [ PERCENT ] { ROW | ROWS } | { ONLY | WITH TIES }`
|
/// `FETCH { FIRST | NEXT } <N> [ PERCENT ] { ROW | ROWS } | { ONLY | WITH TIES }`
|
||||||
pub fetch: Option<Fetch>,
|
pub fetch: Option<Fetch>,
|
||||||
/// `FOR { UPDATE | SHARE }`
|
/// `FOR { UPDATE | SHARE } [ OF table_name ] [ SKIP LOCKED | NOWAIT ]`
|
||||||
pub lock: Option<LockType>,
|
pub locks: Vec<LockClause>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Query {
|
impl fmt::Display for Query {
|
||||||
|
@ -57,8 +57,8 @@ impl fmt::Display for Query {
|
||||||
if let Some(ref fetch) = self.fetch {
|
if let Some(ref fetch) = self.fetch {
|
||||||
write!(f, " {}", fetch)?;
|
write!(f, " {}", fetch)?;
|
||||||
}
|
}
|
||||||
if let Some(ref lock) = self.lock {
|
if !self.locks.is_empty() {
|
||||||
write!(f, " {}", lock)?;
|
write!(f, " {}", display_separated(&self.locks, " "))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -833,6 +833,27 @@ impl fmt::Display for Fetch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct LockClause {
|
||||||
|
pub lock_type: LockType,
|
||||||
|
pub of: Option<ObjectName>,
|
||||||
|
pub nonblock: Option<NonBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LockClause {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "FOR {}", &self.lock_type)?;
|
||||||
|
if let Some(ref of) = self.of {
|
||||||
|
write!(f, " OF {}", of)?;
|
||||||
|
}
|
||||||
|
if let Some(ref nb) = self.nonblock {
|
||||||
|
write!(f, " {}", nb)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum LockType {
|
pub enum LockType {
|
||||||
|
@ -843,13 +864,30 @@ pub enum LockType {
|
||||||
impl fmt::Display for LockType {
|
impl fmt::Display for LockType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let select_lock = match self {
|
let select_lock = match self {
|
||||||
LockType::Share => "FOR SHARE",
|
LockType::Share => "SHARE",
|
||||||
LockType::Update => "FOR UPDATE",
|
LockType::Update => "UPDATE",
|
||||||
};
|
};
|
||||||
write!(f, "{}", select_lock)
|
write!(f, "{}", select_lock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum NonBlock {
|
||||||
|
Nowait,
|
||||||
|
SkipLocked,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NonBlock {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let nonblock = match self {
|
||||||
|
NonBlock::Nowait => "NOWAIT",
|
||||||
|
NonBlock::SkipLocked => "SKIP LOCKED",
|
||||||
|
};
|
||||||
|
write!(f, "{}", nonblock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct Top {
|
pub struct Top {
|
||||||
|
|
|
@ -331,6 +331,7 @@ define_keywords!(
|
||||||
LOCALTIME,
|
LOCALTIME,
|
||||||
LOCALTIMESTAMP,
|
LOCALTIMESTAMP,
|
||||||
LOCATION,
|
LOCATION,
|
||||||
|
LOCKED,
|
||||||
LOGIN,
|
LOGIN,
|
||||||
LOWER,
|
LOWER,
|
||||||
MANAGEDLOCATION,
|
MANAGEDLOCATION,
|
||||||
|
@ -382,6 +383,7 @@ define_keywords!(
|
||||||
NOSUPERUSER,
|
NOSUPERUSER,
|
||||||
NOT,
|
NOT,
|
||||||
NOTHING,
|
NOTHING,
|
||||||
|
NOWAIT,
|
||||||
NTH_VALUE,
|
NTH_VALUE,
|
||||||
NTILE,
|
NTILE,
|
||||||
NULL,
|
NULL,
|
||||||
|
@ -509,6 +511,7 @@ define_keywords!(
|
||||||
SHARE,
|
SHARE,
|
||||||
SHOW,
|
SHOW,
|
||||||
SIMILAR,
|
SIMILAR,
|
||||||
|
SKIP,
|
||||||
SMALLINT,
|
SMALLINT,
|
||||||
SNAPSHOT,
|
SNAPSHOT,
|
||||||
SOME,
|
SOME,
|
||||||
|
|
|
@ -4505,11 +4505,10 @@ impl<'a> Parser<'a> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let lock = if self.parse_keyword(Keyword::FOR) {
|
let mut locks = Vec::new();
|
||||||
Some(self.parse_lock()?)
|
while self.parse_keyword(Keyword::FOR) {
|
||||||
} else {
|
locks.push(self.parse_lock()?);
|
||||||
None
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Query {
|
Ok(Query {
|
||||||
with,
|
with,
|
||||||
|
@ -4518,7 +4517,7 @@ impl<'a> Parser<'a> {
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
fetch,
|
fetch,
|
||||||
lock,
|
locks,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let insert = self.parse_insert()?;
|
let insert = self.parse_insert()?;
|
||||||
|
@ -4530,7 +4529,7 @@ impl<'a> Parser<'a> {
|
||||||
order_by: vec![],
|
order_by: vec![],
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5945,12 +5944,29 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a FOR UPDATE/FOR SHARE clause
|
/// Parse a FOR UPDATE/FOR SHARE clause
|
||||||
pub fn parse_lock(&mut self) -> Result<LockType, ParserError> {
|
pub fn parse_lock(&mut self) -> Result<LockClause, ParserError> {
|
||||||
match self.expect_one_of_keywords(&[Keyword::UPDATE, Keyword::SHARE])? {
|
let lock_type = match self.expect_one_of_keywords(&[Keyword::UPDATE, Keyword::SHARE])? {
|
||||||
Keyword::UPDATE => Ok(LockType::Update),
|
Keyword::UPDATE => LockType::Update,
|
||||||
Keyword::SHARE => Ok(LockType::Share),
|
Keyword::SHARE => LockType::Share,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
};
|
||||||
|
let of = if self.parse_keyword(Keyword::OF) {
|
||||||
|
Some(self.parse_object_name()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let nonblock = if self.parse_keyword(Keyword::NOWAIT) {
|
||||||
|
Some(NonBlock::Nowait)
|
||||||
|
} else if self.parse_keywords(&[Keyword::SKIP, Keyword::LOCKED]) {
|
||||||
|
Some(NonBlock::SkipLocked)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(LockClause {
|
||||||
|
lock_type,
|
||||||
|
of,
|
||||||
|
nonblock,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_values(&mut self) -> Result<Values, ParserError> {
|
pub fn parse_values(&mut self) -> Result<Values, ParserError> {
|
||||||
|
|
|
@ -253,7 +253,7 @@ fn parse_update_set_from() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
}),
|
}),
|
||||||
alias: Some(TableAlias {
|
alias: Some(TableAlias {
|
||||||
name: Ident::new("t2"),
|
name: Ident::new("t2"),
|
||||||
|
@ -2296,7 +2296,7 @@ fn parse_create_table_as_table() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
});
|
});
|
||||||
|
|
||||||
match verified_stmt(sql1) {
|
match verified_stmt(sql1) {
|
||||||
|
@ -2319,7 +2319,7 @@ fn parse_create_table_as_table() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
});
|
});
|
||||||
|
|
||||||
match verified_stmt(sql2) {
|
match verified_stmt(sql2) {
|
||||||
|
@ -3456,7 +3456,7 @@ fn parse_interval_and_or_xor() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
}))];
|
}))];
|
||||||
|
|
||||||
assert_eq!(actual_ast, expected_ast);
|
assert_eq!(actual_ast, expected_ast);
|
||||||
|
@ -5604,7 +5604,7 @@ fn parse_merge() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
}),
|
}),
|
||||||
alias: Some(TableAlias {
|
alias: Some(TableAlias {
|
||||||
name: Ident {
|
name: Ident {
|
||||||
|
@ -5729,12 +5729,106 @@ fn test_merge_with_delimiter() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lock() {
|
fn test_lock() {
|
||||||
let sql = "SELECT * FROM student WHERE id = '1' FOR UPDATE";
|
let sql = "SELECT * FROM student WHERE id = '1' FOR UPDATE";
|
||||||
let ast = verified_query(sql);
|
let mut ast = verified_query(sql);
|
||||||
assert_eq!(ast.lock.unwrap(), LockType::Update);
|
assert_eq!(ast.locks.len(), 1);
|
||||||
|
let lock = ast.locks.pop().unwrap();
|
||||||
|
assert_eq!(lock.lock_type, LockType::Update);
|
||||||
|
assert!(lock.of.is_none());
|
||||||
|
assert!(lock.nonblock.is_none());
|
||||||
|
|
||||||
let sql = "SELECT * FROM student WHERE id = '1' FOR SHARE";
|
let sql = "SELECT * FROM student WHERE id = '1' FOR SHARE";
|
||||||
let ast = verified_query(sql);
|
let mut ast = verified_query(sql);
|
||||||
assert_eq!(ast.lock.unwrap(), LockType::Share);
|
assert_eq!(ast.locks.len(), 1);
|
||||||
|
let lock = ast.locks.pop().unwrap();
|
||||||
|
assert_eq!(lock.lock_type, LockType::Share);
|
||||||
|
assert!(lock.of.is_none());
|
||||||
|
assert!(lock.nonblock.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lock_table() {
|
||||||
|
let sql = "SELECT * FROM student WHERE id = '1' FOR UPDATE OF school";
|
||||||
|
let mut ast = verified_query(sql);
|
||||||
|
assert_eq!(ast.locks.len(), 1);
|
||||||
|
let lock = ast.locks.pop().unwrap();
|
||||||
|
assert_eq!(lock.lock_type, LockType::Update);
|
||||||
|
assert_eq!(
|
||||||
|
lock.of.unwrap().0,
|
||||||
|
vec![Ident {
|
||||||
|
value: "school".to_string(),
|
||||||
|
quote_style: None
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
assert!(lock.nonblock.is_none());
|
||||||
|
|
||||||
|
let sql = "SELECT * FROM student WHERE id = '1' FOR SHARE OF school";
|
||||||
|
let mut ast = verified_query(sql);
|
||||||
|
assert_eq!(ast.locks.len(), 1);
|
||||||
|
let lock = ast.locks.pop().unwrap();
|
||||||
|
assert_eq!(lock.lock_type, LockType::Share);
|
||||||
|
assert_eq!(
|
||||||
|
lock.of.unwrap().0,
|
||||||
|
vec![Ident {
|
||||||
|
value: "school".to_string(),
|
||||||
|
quote_style: None
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
assert!(lock.nonblock.is_none());
|
||||||
|
|
||||||
|
let sql = "SELECT * FROM student WHERE id = '1' FOR SHARE OF school FOR UPDATE OF student";
|
||||||
|
let mut ast = verified_query(sql);
|
||||||
|
assert_eq!(ast.locks.len(), 2);
|
||||||
|
let lock = ast.locks.remove(0);
|
||||||
|
assert_eq!(lock.lock_type, LockType::Share);
|
||||||
|
assert_eq!(
|
||||||
|
lock.of.unwrap().0,
|
||||||
|
vec![Ident {
|
||||||
|
value: "school".to_string(),
|
||||||
|
quote_style: None
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
assert!(lock.nonblock.is_none());
|
||||||
|
let lock = ast.locks.remove(0);
|
||||||
|
assert_eq!(lock.lock_type, LockType::Update);
|
||||||
|
assert_eq!(
|
||||||
|
lock.of.unwrap().0,
|
||||||
|
vec![Ident {
|
||||||
|
value: "student".to_string(),
|
||||||
|
quote_style: None
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
assert!(lock.nonblock.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lock_nonblock() {
|
||||||
|
let sql = "SELECT * FROM student WHERE id = '1' FOR UPDATE OF school SKIP LOCKED";
|
||||||
|
let mut ast = verified_query(sql);
|
||||||
|
assert_eq!(ast.locks.len(), 1);
|
||||||
|
let lock = ast.locks.pop().unwrap();
|
||||||
|
assert_eq!(lock.lock_type, LockType::Update);
|
||||||
|
assert_eq!(
|
||||||
|
lock.of.unwrap().0,
|
||||||
|
vec![Ident {
|
||||||
|
value: "school".to_string(),
|
||||||
|
quote_style: None
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
assert_eq!(lock.nonblock.unwrap(), NonBlock::SkipLocked);
|
||||||
|
|
||||||
|
let sql = "SELECT * FROM student WHERE id = '1' FOR SHARE OF school NOWAIT";
|
||||||
|
let mut ast = verified_query(sql);
|
||||||
|
assert_eq!(ast.locks.len(), 1);
|
||||||
|
let lock = ast.locks.pop().unwrap();
|
||||||
|
assert_eq!(lock.lock_type, LockType::Share);
|
||||||
|
assert_eq!(
|
||||||
|
lock.of.unwrap().0,
|
||||||
|
vec![Ident {
|
||||||
|
value: "school".to_string(),
|
||||||
|
quote_style: None
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
assert_eq!(lock.nonblock.unwrap(), NonBlock::Nowait);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -466,7 +466,7 @@ fn parse_quote_identifiers_2() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -500,7 +500,7 @@ fn parse_quote_identifiers_3() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -683,7 +683,7 @@ fn parse_simple_insert() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
}),
|
}),
|
||||||
source
|
source
|
||||||
);
|
);
|
||||||
|
@ -741,7 +741,7 @@ fn parse_insert_with_on_duplicate_update() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
}),
|
}),
|
||||||
source
|
source
|
||||||
);
|
);
|
||||||
|
@ -983,7 +983,7 @@ fn parse_substring_in_select() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
}),
|
}),
|
||||||
query
|
query
|
||||||
);
|
);
|
||||||
|
|
|
@ -1512,7 +1512,7 @@ fn parse_array_subquery_expr() {
|
||||||
limit: None,
|
limit: None,
|
||||||
offset: None,
|
offset: None,
|
||||||
fetch: None,
|
fetch: None,
|
||||||
lock: None,
|
locks: vec![],
|
||||||
})),
|
})),
|
||||||
expr_from_projection(only(&select.projection)),
|
expr_from_projection(only(&select.projection)),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue