-
Notifications
You must be signed in to change notification settings - Fork 561
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parse Postgres's LOCK TABLE statement #1614
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -3341,16 +3341,13 @@ pub enum Statement { | |||
value: Option<Value>, | ||||
is_eq: bool, | ||||
}, | ||||
/// ```sql | ||||
/// LOCK TABLES <table_name> [READ [LOCAL] | [LOW_PRIORITY] WRITE] | ||||
/// ``` | ||||
/// Note: this is a MySQL-specific statement. See <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html> | ||||
LockTables { tables: Vec<LockTable> }, | ||||
/// See [`LockTables`]. | ||||
LockTables(LockTables), | ||||
/// ```sql | ||||
/// UNLOCK TABLES | ||||
/// ``` | ||||
/// Note: this is a MySQL-specific statement. See <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html> | ||||
UnlockTables, | ||||
UnlockTables(bool), | ||||
/// ```sql | ||||
/// UNLOAD(statement) TO <destination> [ WITH options ] | ||||
/// ``` | ||||
|
@@ -4925,11 +4922,15 @@ impl fmt::Display for Statement { | |||
} | ||||
Ok(()) | ||||
} | ||||
Statement::LockTables { tables } => { | ||||
write!(f, "LOCK TABLES {}", display_comma_separated(tables)) | ||||
Statement::LockTables(lock_tables) => { | ||||
write!(f, "{}", lock_tables) | ||||
} | ||||
Statement::UnlockTables => { | ||||
write!(f, "UNLOCK TABLES") | ||||
Statement::UnlockTables(pluralized) => { | ||||
if *pluralized { | ||||
write!(f, "UNLOCK TABLES") | ||||
} else { | ||||
write!(f, "UNLOCK TABLE") | ||||
} | ||||
} | ||||
Statement::Unload { query, to, with } => { | ||||
write!(f, "UNLOAD({query}) TO {to}")?; | ||||
|
@@ -7278,16 +7279,126 @@ impl fmt::Display for SearchModifier { | |||
} | ||||
} | ||||
|
||||
/// A `LOCK TABLE ..` statement. MySQL and Postgres variants are supported. | ||||
/// | ||||
/// The MySQL and Postgres syntax variants are significant enough that they | ||||
/// are explicitly represented as enum variants. In order to support additional | ||||
/// databases in the future, this enum is marked as `#[non_exhaustive]`. | ||||
/// | ||||
/// In MySQL, when multiple tables are mentioned in the statement the lock mode | ||||
/// can vary per table. | ||||
/// | ||||
/// In contrast, Postgres only allows specifying a single mode which is applied | ||||
/// to all mentioned tables. | ||||
/// | ||||
/// MySQL: see <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html> | ||||
/// | ||||
/// ```sql | ||||
/// LOCK [TABLE | TABLES] name [[AS] alias] locktype [,name [[AS] alias] locktype] | ||||
/// ```` | ||||
/// | ||||
/// Where *locktype* is: | ||||
/// ```sql | ||||
/// READ [LOCAL] | [LOW_PRIORITY] WRITE | ||||
/// ``` | ||||
/// | ||||
/// Postgres: See <https://www.postgresql.org/docs/current/sql-lock.html> | ||||
/// | ||||
/// ```sql | ||||
/// LOCK [ TABLE ] [ ONLY ] name [, ...] [ IN lockmode MODE ] [ NOWAIT ] | ||||
/// ``` | ||||
/// Where *lockmode* is one of: | ||||
/// | ||||
/// ```sql | ||||
/// ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE | ||||
/// | SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE | ||||
/// `````` | ||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||
pub struct LockTable { | ||||
pub table: Ident, | ||||
#[non_exhaustive] | ||||
pub enum LockTables { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we want to avoid variants that are specific to dialects, those tend to make it more difficult to reuse the parser code and ast representations across dialects. Representation wise, I think both variants can be merged into a struct with something like the following? struct TableLock {
pub table: ObjectName,
pub alias: Option<Ident>,
pub lock_type: Option<LockTableType>,
}
struct LockTable {
pluralized_table_keyword: Option<bool>, // If None, then no table keyword was provided
locks: Vec<TableLock>,
lock_mode: Option<LockTableType>,
only: bool,
no_wait: bool
} similarly, the parser uses the same impl to create the struct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
MySQL's & Postgres's
I agree that explicit database-specific AST fragments make parser reuse more difficult but in the case of The db-specific AST pieces do make implementing It also makes it (almost) impossible be able to represent invalid AST (e.g. a mix of PG & MySQL) except I didn't take a hard stance on this for None of this is a hill I will die on, but handling the burden of grammar differences in the parser and AST design means consuming the AST correctly in downstream projects will be easier. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What I mean by this: When pieces of dialect-specific syntax are mixed in the same AST struct/enum variant the consumers have to understand the dialect-specific differences in order to know which fields they can safely ignore. At CipherStash I wrote a type-inferencer for SQL statements in order to determine if specific transformations can be performed safely. It uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I agree that downstream crates targeting a single dialect would be easier to implement by essentially having dialect specific AST representations (on the other extreme there are downstream crates that would like to process the AST in a dialect agnostic manner,, we also have custom dialects in other downstream crates that need support). I think there are pros/cons to this approach vs the current one followed by the parser which puts some of the responsibility on the downstream crate. I'm thinking in any case ideally we would want to keep to the current approach for the PR while shift in approaches could be tackled as its own dedicated proposal. |
||||
/// The MySQL syntax variant | ||||
MySql { | ||||
/// Whether the `TABLE` or `TABLES` keyword was used. | ||||
pluralized_table_keyword: bool, | ||||
/// The tables to lock and their per-table lock mode. | ||||
tables: Vec<MySqlTableLock>, | ||||
}, | ||||
|
||||
/// The Postgres syntax variant. | ||||
Postgres { | ||||
/// One or more optionally schema-qualified table names to lock. | ||||
tables: Vec<ObjectName>, | ||||
/// The lock type applied to all mentioned tables. | ||||
lock_mode: Option<LockTableType>, | ||||
/// Whether the optional `TABLE` keyword was present (to support round-trip parse & render) | ||||
has_table_keyword: bool, | ||||
/// Whether the `ONLY` option was specified. | ||||
only: bool, | ||||
/// Whether the `NOWAIT` option was specified. | ||||
no_wait: bool, | ||||
}, | ||||
} | ||||
|
||||
impl Display for LockTables { | ||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
match self { | ||||
LockTables::MySql { | ||||
pluralized_table_keyword, | ||||
tables, | ||||
} => { | ||||
write!( | ||||
f, | ||||
"LOCK {tbl_kwd} ", | ||||
tbl_kwd = if *pluralized_table_keyword { | ||||
"TABLES" | ||||
} else { | ||||
"TABLE" | ||||
} | ||||
)?; | ||||
write!(f, "{}", display_comma_separated(tables))?; | ||||
Ok(()) | ||||
} | ||||
LockTables::Postgres { | ||||
tables, | ||||
lock_mode, | ||||
has_table_keyword, | ||||
only, | ||||
no_wait, | ||||
} => { | ||||
write!( | ||||
f, | ||||
"LOCK{tbl_kwd}", | ||||
tbl_kwd = if *has_table_keyword { " TABLE" } else { "" } | ||||
)?; | ||||
if *only { | ||||
write!(f, " ONLY")?; | ||||
} | ||||
write!(f, " {}", display_comma_separated(tables))?; | ||||
if let Some(lock_mode) = lock_mode { | ||||
write!(f, " IN {} MODE", lock_mode)?; | ||||
} | ||||
if *no_wait { | ||||
write!(f, " NOWAIT")?; | ||||
} | ||||
Ok(()) | ||||
} | ||||
} | ||||
} | ||||
} | ||||
|
||||
/// A locked table from a MySQL `LOCK TABLE` statement. | ||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||
pub struct MySqlTableLock { | ||||
pub table: ObjectName, | ||||
pub alias: Option<Ident>, | ||||
pub lock_type: LockTableType, | ||||
pub lock_type: Option<LockTableType>, | ||||
} | ||||
|
||||
impl fmt::Display for LockTable { | ||||
impl fmt::Display for MySqlTableLock { | ||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
let Self { | ||||
table: tbl_name, | ||||
|
@@ -7299,17 +7410,34 @@ impl fmt::Display for LockTable { | |||
if let Some(alias) = alias { | ||||
write!(f, "AS {alias} ")?; | ||||
} | ||||
write!(f, "{lock_type}")?; | ||||
if let Some(lock_type) = lock_type { | ||||
write!(f, "{lock_type}")?; | ||||
} | ||||
Ok(()) | ||||
} | ||||
} | ||||
|
||||
/// Table lock types. | ||||
/// | ||||
/// `Read` & `Write` are MySQL-specfic. | ||||
/// | ||||
/// AccessShare, RowShare, RowExclusive, ShareUpdateExclusive, Share, | ||||
/// ShareRowExclusive, Exclusive, AccessExclusive are Postgres-specific. | ||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||
#[non_exhaustive] | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think we tend to not use this attribute, I think there are pros/cons with using it but better to keep with the existing convention in this PR |
||||
pub enum LockTableType { | ||||
Read { local: bool }, | ||||
Write { low_priority: bool }, | ||||
AccessShare, | ||||
RowShare, | ||||
RowExclusive, | ||||
ShareUpdateExclusive, | ||||
Share, | ||||
ShareRowExclusive, | ||||
Exclusive, | ||||
AccessExclusive, | ||||
} | ||||
|
||||
impl fmt::Display for LockTableType { | ||||
|
@@ -7327,6 +7455,30 @@ impl fmt::Display for LockTableType { | |||
} | ||||
write!(f, "WRITE")?; | ||||
} | ||||
Self::AccessShare => { | ||||
write!(f, "ACCESS SHARE")?; | ||||
} | ||||
Self::RowShare => { | ||||
write!(f, "ROW SHARE")?; | ||||
} | ||||
Self::RowExclusive => { | ||||
write!(f, "ROW EXCLUSIVE")?; | ||||
} | ||||
Self::ShareUpdateExclusive => { | ||||
write!(f, "SHARE UPDATE EXCLUSIVE")?; | ||||
} | ||||
Self::Share => { | ||||
write!(f, "SHARE")?; | ||||
} | ||||
Self::ShareRowExclusive => { | ||||
write!(f, "SHARE ROW EXCLUSIVE")?; | ||||
} | ||||
Self::Exclusive => { | ||||
write!(f, "EXCLUSIVE")?; | ||||
} | ||||
Self::AccessExclusive => { | ||||
write!(f, "ACCESS EXCLUSIVE")?; | ||||
} | ||||
} | ||||
|
||||
Ok(()) | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we use a dedicated struct here as well? Its not clear what the bool property implies otherwise