From 56df74802c57f21bca645ca6fdf9be888d92befb Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Tue, 20 Sep 2022 10:51:30 +1000 Subject: [PATCH] feat: Add `DELETE` and `DROP MEASUREMENT` statements (#5656) * chore: Refactor `FROM` clause parser to be generic This will allow us to use it for `DELETE` statements, which has a more restrictive requirement, only allowing regular expressions or single part identifiers. * feat: Add `DELETE` series statement * chore: Add test case for insignificant whitespace between operators NOTE: Added a skipped test until #5663 is implemented * feat: Add `DROP MEASUREMENT` statement * chore: Add DropMeasurementStatement struct * chore: `Statement` enum contains only `Box`ed types Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- influxdb_influxql_parser/src/delete.rs | 96 +++++++ influxdb_influxql_parser/src/drop.rs | 75 +++++ influxdb_influxql_parser/src/expression.rs | 11 +- influxdb_influxql_parser/src/lib.rs | 3 + influxdb_influxql_parser/src/show.rs | 152 ++-------- .../src/show_field_keys.rs | 7 +- .../src/show_retention_policies.rs | 24 +- influxdb_influxql_parser/src/show_tag_keys.rs | 7 +- .../src/show_tag_values.rs | 7 +- .../src/simple_from_clause.rs | 267 ++++++++++++++++++ influxdb_influxql_parser/src/statement.rs | 64 +++-- 11 files changed, 555 insertions(+), 158 deletions(-) create mode 100644 influxdb_influxql_parser/src/delete.rs create mode 100644 influxdb_influxql_parser/src/drop.rs create mode 100644 influxdb_influxql_parser/src/simple_from_clause.rs diff --git a/influxdb_influxql_parser/src/delete.rs b/influxdb_influxql_parser/src/delete.rs new file mode 100644 index 0000000000..a18159019d --- /dev/null +++ b/influxdb_influxql_parser/src/delete.rs @@ -0,0 +1,96 @@ +use crate::common::where_clause; +use crate::expression::Expr; +use crate::internal::{expect, ParseResult}; +use crate::simple_from_clause::{delete_from_clause, DeleteFromClause}; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::character::complete::{multispace0, multispace1}; +use nom::combinator::{map, opt}; +use nom::sequence::{pair, preceded}; +use std::fmt::{Display, Formatter}; + +#[derive(Clone, Debug, PartialEq)] +pub enum DeleteStatement { + /// A DELETE with a measurement or measurements and an optional conditional expression + /// to restrict which series are deleted. + FromWhere { + from: DeleteFromClause, + condition: Option, + }, + + /// A `DELETE` with a conditional expression to restrict which series are deleted. + Where(Expr), +} + +impl Display for DeleteStatement { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DELETE")?; + + match self { + Self::FromWhere { from, condition } => { + write!(f, " FROM {}", from)?; + if let Some(condition) = condition { + write!(f, " WHERE {}", condition)?; + } + } + Self::Where(condition) => write!(f, " WHERE {}", condition)?, + }; + + Ok(()) + } +} + +/// Parse a `DELETE` statement. +pub fn delete_statement(i: &str) -> ParseResult<&str, DeleteStatement> { + // delete ::= "DELETE" ( from_clause where_clause? | where_clause ) + preceded( + tag_no_case("DELETE"), + expect( + "invalid DELETE statement, must be followed by FROM or WHERE", + preceded( + multispace1, + alt(( + // delete ::= from_clause where_clause? + map( + pair(delete_from_clause, opt(preceded(multispace0, where_clause))), + |(from, condition)| DeleteStatement::FromWhere { from, condition }, + ), + // delete ::= where_clause + map(where_clause, DeleteStatement::Where), + )), + ), + ), + )(i) +} + +#[cfg(test)] +mod test { + use crate::assert_expect_error; + use crate::delete::delete_statement; + + #[test] + fn test_delete() { + // Validate via the Display trait, as we don't need to validate the contents of the + // FROM and / or WHERE clauses, given they are tested in their on modules. + + let (_, got) = delete_statement("DELETE FROM foo").unwrap(); + assert_eq!(format!("{}", got), "DELETE FROM foo"); + + let (_, got) = delete_statement("DELETE FROM foo WHERE time > 10").unwrap(); + assert_eq!(format!("{}", got), "DELETE FROM foo WHERE time > 10"); + + let (_, got) = delete_statement("DELETE WHERE time > 10").unwrap(); + assert_eq!(format!("{}", got), "DELETE WHERE time > 10"); + + // Fallible cases + assert_expect_error!( + delete_statement("DELETE"), + "invalid DELETE statement, must be followed by FROM or WHERE" + ); + + assert_expect_error!( + delete_statement("DELETE FOO"), + "invalid DELETE statement, must be followed by FROM or WHERE" + ); + } +} diff --git a/influxdb_influxql_parser/src/drop.rs b/influxdb_influxql_parser/src/drop.rs new file mode 100644 index 0000000000..0db5d3fedf --- /dev/null +++ b/influxdb_influxql_parser/src/drop.rs @@ -0,0 +1,75 @@ +use crate::identifier::{identifier, Identifier}; +use crate::internal::{expect, ParseResult}; +use nom::bytes::complete::tag_no_case; +use nom::character::complete::multispace1; +use nom::combinator::map; +use nom::sequence::{pair, preceded}; +use std::fmt::{Display, Formatter}; + +/// Represents a `DROP MEASUREMENT` statement. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DropMeasurementStatement { + /// The name of the measurement to delete. + name: Identifier, +} + +impl Display for DropMeasurementStatement { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "DROP MEASUREMENT {}", self.name)?; + Ok(()) + } +} + +pub fn drop_statement(i: &str) -> ParseResult<&str, DropMeasurementStatement> { + preceded( + pair(tag_no_case("DROP"), multispace1), + expect( + "invalid DROP statement, must be followed by MEASUREMENT", + drop_measurement, + ), + )(i) +} + +fn drop_measurement(i: &str) -> ParseResult<&str, DropMeasurementStatement> { + preceded( + pair(tag_no_case("MEASUREMENT"), multispace1), + map( + expect( + "invalid DROP MEASUREMENT statement, expected identifier", + identifier, + ), + |name| DropMeasurementStatement { name }, + ), + )(i) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::assert_expect_error; + + #[test] + fn test_drop_statement() { + drop_statement("DROP MEASUREMENT foo").unwrap(); + + // Fallible cases + assert_expect_error!( + drop_statement("DROP foo"), + "invalid DROP statement, must be followed by MEASUREMENT" + ); + } + + #[test] + fn test_drop_measurement() { + let (_, got) = drop_measurement("MEASUREMENT \"foo\"").unwrap(); + assert_eq!(got, DropMeasurementStatement { name: "foo".into() }); + // validate Display + assert_eq!(format!("{}", got), "DROP MEASUREMENT foo"); + + // Fallible cases + assert_expect_error!( + drop_measurement("MEASUREMENT 'foo'"), + "invalid DROP MEASUREMENT statement, expected identifier" + ); + } +} diff --git a/influxdb_influxql_parser/src/expression.rs b/influxdb_influxql_parser/src/expression.rs index 70dc3a73fa..87cbd5adfd 100644 --- a/influxdb_influxql_parser/src/expression.rs +++ b/influxdb_influxql_parser/src/expression.rs @@ -468,16 +468,25 @@ mod test { let (_, got) = conditional_expression("5+$foo").unwrap(); assert_eq!(got, *binary_op!(5, Add, param!("foo"))); - let (_, got) = conditional_expression("5--(3|2)").unwrap(); + let (_, got) = conditional_expression("5- -(3|2)").unwrap(); assert_eq!( got, *binary_op!(5, Sub, unary!(-nested!(binary_op!(3, BitwiseOr, 2)))) ); + // whitespace is not significant between unary operators + let (_, got) = conditional_expression("5+-(3|2)").unwrap(); + assert_eq!( + got, + *binary_op!(5, Add, unary!(-nested!(binary_op!(3, BitwiseOr, 2)))) + ); + // Fallible cases // invalid operator / incomplete expression assert_failure!(conditional_expression("5 || 3")); + // TODO: skip until https://github.com/influxdata/influxdb_iox/issues/5663 is implemented + // assert_failure!(conditional_expression("5+--(3|2)")); } #[test] diff --git a/influxdb_influxql_parser/src/lib.rs b/influxdb_influxql_parser/src/lib.rs index b60b850960..3198f2d3ea 100644 --- a/influxdb_influxql_parser/src/lib.rs +++ b/influxdb_influxql_parser/src/lib.rs @@ -25,6 +25,8 @@ use std::fmt::{Debug, Display, Formatter}; mod test_util; mod common; +mod delete; +mod drop; mod expression; mod identifier; mod internal; @@ -37,6 +39,7 @@ mod show_measurements; mod show_retention_policies; mod show_tag_keys; mod show_tag_values; +mod simple_from_clause; mod statement; mod string; diff --git a/influxdb_influxql_parser/src/show.rs b/influxdb_influxql_parser/src/show.rs index 2a5bba48aa..2cbf33764a 100644 --- a/influxdb_influxql_parser/src/show.rs +++ b/influxdb_influxql_parser/src/show.rs @@ -1,4 +1,3 @@ -use crate::common::{measurement_name_expression, MeasurementNameExpression}; use crate::identifier::{identifier, Identifier}; use crate::internal::{expect, ParseResult}; use crate::show_field_keys::show_field_keys; @@ -6,35 +5,33 @@ use crate::show_measurements::show_measurements; use crate::show_retention_policies::show_retention_policies; use crate::show_tag_keys::show_tag_keys; use crate::show_tag_values::show_tag_values; -use crate::string::{regex, Regex}; use crate::Statement; use nom::branch::alt; use nom::bytes::complete::tag_no_case; -use nom::character::complete::{char, multispace0, multispace1}; -use nom::combinator::{map, opt, value}; -use nom::multi::separated_list1; -use nom::sequence::{pair, preceded, tuple}; -use std::fmt; -use std::fmt::Formatter; +use nom::character::complete::multispace1; +use nom::combinator::{map, value}; +use nom::sequence::{pair, preceded}; +use std::fmt::{Display, Formatter}; /// Parse a SHOW statement. pub fn show_statement(i: &str) -> ParseResult<&str, Statement> { preceded( pair(tag_no_case("SHOW"), multispace1), expect( - "invalid SHOW statement, expected MEASUREMENTS, TAG following SHOW", - // NOTE: This will become an alt(()) once more statements are added + "invalid SHOW statement, expected DATABASES, FIELD, MEASUREMENTS, TAG, or RETENTION following SHOW", alt(( // SHOW DATABASES - show_databases, + map(show_databases, |s| Statement::ShowDatabases(Box::new(s))), // SHOW FIELD KEYS - map(show_field_keys, |v| Statement::ShowFieldKeys(Box::new(v))), + map(show_field_keys, |s| Statement::ShowFieldKeys(Box::new(s))), // SHOW MEASUREMENTS - map(show_measurements, |v| { - Statement::ShowMeasurements(Box::new(v)) + map(show_measurements, |s| { + Statement::ShowMeasurements(Box::new(s)) }), // SHOW RETENTION POLICIES - show_retention_policies, + map(show_retention_policies, |s| { + Statement::ShowRetentionPolicies(Box::new(s)) + }), // SHOW TAG show_tag, )), @@ -42,113 +39,20 @@ pub fn show_statement(i: &str) -> ParseResult<&str, Statement> { )(i) } +/// Represents a `SHOW DATABASES` statement. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ShowDatabasesStatement; + +impl Display for ShowDatabasesStatement { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("SHOW DATABASES")?; + Ok(()) + } +} + /// Parse a `SHOW DATABASES` statement. -fn show_databases(i: &str) -> ParseResult<&str, Statement> { - value(Statement::ShowDatabases, tag_no_case("DATABASES"))(i) -} - -/// Represents a single measurement selection found in a `FROM` measurement clause. -/// -/// A `FROM` measurement clause is used by various `SHOW` statements. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum MeasurementSelection { - Name(MeasurementNameExpression), - Regex(Regex), -} - -impl fmt::Display for MeasurementSelection { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::Name(ref name) => fmt::Display::fmt(name, f)?, - Self::Regex(ref re) => fmt::Display::fmt(re, f)?, - }; - - Ok(()) - } -} - -/// Represents a `FROM` clause used by various `SHOW` statements. -/// -/// A `FROM` clause for a `SHOW` statements differs from a `FROM` in a -/// `SELECT`, as it can only contain measurement name or regular expressions. -/// -/// It is defined by the following EBNF notation: -/// -/// ```text -/// from_clause ::= "FROM" measurement_selection ("," measurement_selection)* -/// measurement_selection ::= measurement -/// -/// measurement ::= measurement_name | -/// ( policy_name "." measurement_name ) | -/// ( db_name "." policy_name? "." measurement_name ) -/// -/// db_name ::= identifier -/// measurement_name ::= identifier | regex_lit -/// policy_name ::= identifier -/// ``` -/// -/// A minimal `FROM` clause would be a single identifier -/// -/// ```text -/// FROM foo -/// ``` -/// -/// A more complicated example may include a variety of fully-qualified -/// identifiers and regular expressions -/// -/// ```text -/// FROM foo, /bar/, some_database..foo, some_retention_policy.foobar -/// ``` -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FromMeasurementClause { - first: MeasurementSelection, - rest: Option>, -} - -impl fmt::Display for FromMeasurementClause { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.first, f)?; - if let Some(ref rest) = self.rest { - for arg in rest { - write!(f, ", {}", arg)?; - } - } - - Ok(()) - } -} - -fn measurement_selection(i: &str) -> ParseResult<&str, MeasurementSelection> { - alt(( - map(measurement_name_expression, MeasurementSelection::Name), - map(regex, MeasurementSelection::Regex), - ))(i) -} - -pub fn from_clause(i: &str) -> ParseResult<&str, FromMeasurementClause> { - // NOTE: This combinator is optimised to parse - map( - preceded( - pair(tag_no_case("FROM"), multispace1), - expect( - "invalid FROM clause, expected one or more identifiers or regexes", - tuple(( - measurement_selection, - opt(preceded( - pair(multispace0, char(',')), - expect( - "invalid FROM clause, expected identifier after ,", - separated_list1( - preceded(multispace0, char(',')), - preceded(multispace0, measurement_selection), - ), - ), - )), - )), - ), - ), - |(first, rest)| FromMeasurementClause { first, rest }, - )(i) +fn show_databases(i: &str) -> ParseResult<&str, ShowDatabasesStatement> { + value(ShowDatabasesStatement, tag_no_case("DATABASES"))(i) } /// Parse an `ON` clause for `SHOW TAG KEYS`, `SHOW TAG VALUES` and `SHOW FIELD KEYS` @@ -167,8 +71,8 @@ fn show_tag(i: &str) -> ParseResult<&str, Statement> { expect( "invalid SHOW TAG statement, expected KEYS or VALUES following TAG", alt(( - map(show_tag_keys, |v| Statement::ShowTagKeys(Box::new(v))), - map(show_tag_values, |v| Statement::ShowTagValues(Box::new(v))), + map(show_tag_keys, |s| Statement::ShowTagKeys(Box::new(s))), + map(show_tag_values, |s| Statement::ShowTagValues(Box::new(s))), )), ), )(i) @@ -211,7 +115,7 @@ mod test { // Unsupported SHOW assert_expect_error!( show_statement("SHOW FOO"), - "invalid SHOW statement, expected MEASUREMENTS, TAG following SHOW" + "invalid SHOW statement, expected DATABASES, FIELD, MEASUREMENTS, TAG, or RETENTION following SHOW" ); } } diff --git a/influxdb_influxql_parser/src/show_field_keys.rs b/influxdb_influxql_parser/src/show_field_keys.rs index 9efa698334..14e3b66ec3 100644 --- a/influxdb_influxql_parser/src/show_field_keys.rs +++ b/influxdb_influxql_parser/src/show_field_keys.rs @@ -1,7 +1,8 @@ use crate::common::{limit_clause, offset_clause}; use crate::identifier::Identifier; use crate::internal::{expect, ParseResult}; -use crate::show::{from_clause, on_clause, FromMeasurementClause}; +use crate::show::on_clause; +use crate::simple_from_clause::{show_from_clause, ShowFromClause}; use nom::bytes::complete::tag_no_case; use nom::character::complete::multispace1; use nom::combinator::opt; @@ -18,7 +19,7 @@ pub struct ShowFieldKeysStatement { /// The measurement or measurements to restrict which field keys /// are retrieved. - pub from: Option, + pub from: Option, /// A value to restrict the number of field keys returned. pub limit: Option, @@ -72,7 +73,7 @@ pub fn show_field_keys(i: &str) -> ParseResult<&str, ShowFieldKeysStatement> { tag_no_case("KEYS"), ), opt(preceded(multispace1, on_clause)), - opt(preceded(multispace1, from_clause)), + opt(preceded(multispace1, show_from_clause)), opt(preceded(multispace1, limit_clause)), opt(preceded(multispace1, offset_clause)), ))(i)?; diff --git a/influxdb_influxql_parser/src/show_retention_policies.rs b/influxdb_influxql_parser/src/show_retention_policies.rs index 5326605387..2778e3630e 100644 --- a/influxdb_influxql_parser/src/show_retention_policies.rs +++ b/influxdb_influxql_parser/src/show_retention_policies.rs @@ -1,11 +1,27 @@ use crate::identifier::{identifier, Identifier}; use crate::internal::{expect, ParseResult}; -use crate::Statement; -use crate::Statement::ShowRetentionPolicies; use nom::bytes::complete::tag_no_case; use nom::character::complete::multispace1; use nom::combinator::opt; use nom::sequence::{pair, preceded, tuple}; +use std::fmt::{Display, Formatter}; + +/// Represents a `SHOW RETENTION POLICIES` statement. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ShowRetentionPoliciesStatement { + /// Name of the database to list the retention policies, or all if this is `None`. + database: Option, +} + +impl Display for ShowRetentionPoliciesStatement { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "SHOW RETENTION POLICIES")?; + if let Some(ref database) = self.database { + write!(f, " ON {}", database)?; + } + Ok(()) + } +} fn on_clause(i: &str) -> ParseResult<&str, Identifier> { preceded( @@ -14,7 +30,7 @@ fn on_clause(i: &str) -> ParseResult<&str, Identifier> { )(i) } -pub fn show_retention_policies(i: &str) -> ParseResult<&str, Statement> { +pub fn show_retention_policies(i: &str) -> ParseResult<&str, ShowRetentionPoliciesStatement> { let (remaining, (_, _, _, database)) = tuple(( tag_no_case("RETENTION"), multispace1, @@ -25,7 +41,7 @@ pub fn show_retention_policies(i: &str) -> ParseResult<&str, Statement> { opt(preceded(multispace1, on_clause)), ))(i)?; - Ok((remaining, ShowRetentionPolicies { database })) + Ok((remaining, ShowRetentionPoliciesStatement { database })) } #[cfg(test)] diff --git a/influxdb_influxql_parser/src/show_tag_keys.rs b/influxdb_influxql_parser/src/show_tag_keys.rs index 8be7239556..ef1efa6531 100644 --- a/influxdb_influxql_parser/src/show_tag_keys.rs +++ b/influxdb_influxql_parser/src/show_tag_keys.rs @@ -2,7 +2,8 @@ use crate::common::{limit_clause, offset_clause, where_clause}; use crate::expression::Expr; use crate::identifier::Identifier; use crate::internal::ParseResult; -use crate::show::{from_clause, on_clause, FromMeasurementClause}; +use crate::show::on_clause; +use crate::simple_from_clause::{show_from_clause, ShowFromClause}; use nom::bytes::complete::tag_no_case; use nom::character::complete::multispace1; use nom::combinator::opt; @@ -19,7 +20,7 @@ pub struct ShowTagKeysStatement { /// The measurement or measurements to restrict which tag keys /// are retrieved. - pub from: Option, + pub from: Option, /// A conditional expression to filter the tag keys. pub condition: Option, @@ -74,7 +75,7 @@ pub fn show_tag_keys(i: &str) -> ParseResult<&str, ShowTagKeysStatement> { ) = tuple(( tag_no_case("KEYS"), opt(preceded(multispace1, on_clause)), - opt(preceded(multispace1, from_clause)), + opt(preceded(multispace1, show_from_clause)), opt(preceded(multispace1, where_clause)), opt(preceded(multispace1, limit_clause)), opt(preceded(multispace1, offset_clause)), diff --git a/influxdb_influxql_parser/src/show_tag_values.rs b/influxdb_influxql_parser/src/show_tag_values.rs index 6fcbb62b85..36cd58aaab 100644 --- a/influxdb_influxql_parser/src/show_tag_values.rs +++ b/influxdb_influxql_parser/src/show_tag_values.rs @@ -2,7 +2,8 @@ use crate::common::{limit_clause, offset_clause, where_clause}; use crate::expression::Expr; use crate::identifier::{identifier, Identifier}; use crate::internal::{expect, ParseResult}; -use crate::show::{from_clause, on_clause, FromMeasurementClause}; +use crate::show::on_clause; +use crate::simple_from_clause::{show_from_clause, ShowFromClause}; use crate::string::{regex, Regex}; use nom::branch::alt; use nom::bytes::complete::{tag, tag_no_case}; @@ -22,7 +23,7 @@ pub struct ShowTagValuesStatement { /// The measurement or measurements to restrict which tag keys /// are retrieved. - pub from: Option, + pub from: Option, /// `WITH KEY` expression, to limit the values retrieved to /// the matching tag keys. @@ -84,7 +85,7 @@ pub fn show_tag_values(i: &str) -> ParseResult<&str, ShowTagValuesStatement> { ) = tuple(( tag_no_case("VALUES"), opt(preceded(multispace1, on_clause)), - opt(preceded(multispace1, from_clause)), + opt(preceded(multispace1, show_from_clause)), expect( "invalid SHOW TAG VALUES statement, expect WITH KEY clause", preceded(multispace1, with_key_clause), diff --git a/influxdb_influxql_parser/src/simple_from_clause.rs b/influxdb_influxql_parser/src/simple_from_clause.rs new file mode 100644 index 0000000000..7fa5813695 --- /dev/null +++ b/influxdb_influxql_parser/src/simple_from_clause.rs @@ -0,0 +1,267 @@ +use crate::common::{measurement_name_expression, MeasurementNameExpression}; +use crate::identifier::{identifier, Identifier}; +use crate::internal::{expect, ParseResult}; +use crate::string::{regex, Regex}; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::character::complete::{char, multispace0, multispace1}; +use nom::combinator::{map, opt}; +use nom::multi::separated_list1; +use nom::sequence::{pair, preceded, tuple}; +use std::fmt; +use std::fmt::Formatter; + +pub trait Parser: Sized { + fn parse(i: &str) -> ParseResult<&str, Self>; +} + +/// Represents a single measurement selection found in a `FROM` measurement clause. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum MeasurementSelection { + Name(T), + Regex(Regex), +} + +impl fmt::Display for MeasurementSelection { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Name(ref name) => fmt::Display::fmt(name, f)?, + Self::Regex(ref re) => fmt::Display::fmt(re, f)?, + }; + + Ok(()) + } +} + +/// Represents a `FROM` clause of a `DELETE` or `SHOW` statement. +/// +/// A `FROM` clause for a `DELETE` can only accept [`Identifier`] or regular expressions +/// for measurements names. +/// +/// A `FROM` clause for a number of `SHOW` statements can accept a 3-part measurement name or +/// regular expression. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FromMeasurementClause { + pub first: MeasurementSelection, + pub rest: Option>>, +} + +impl fmt::Display for FromMeasurementClause { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.first, f)?; + if let Some(ref rest) = self.rest { + for arg in rest { + write!(f, ", {}", arg)?; + } + } + + Ok(()) + } +} + +fn measurement_selection(i: &str) -> ParseResult<&str, MeasurementSelection> { + alt(( + map(T::parse, MeasurementSelection::Name), + map(regex, MeasurementSelection::Regex), + ))(i) +} + +fn from_clause(i: &str) -> ParseResult<&str, FromMeasurementClause> { + // NOTE: This combinator is optimised to parse + map( + preceded( + pair(tag_no_case("FROM"), multispace1), + expect( + "invalid FROM clause, expected one or more identifiers or regexes", + tuple(( + measurement_selection, + opt(preceded( + pair(multispace0, char(',')), + expect( + "invalid FROM clause, expected identifier after ,", + separated_list1( + preceded(multispace0, char(',')), + preceded(multispace0, measurement_selection), + ), + ), + )), + )), + ), + ), + |(first, rest)| FromMeasurementClause { first, rest }, + )(i) +} + +impl Parser for MeasurementNameExpression { + fn parse(i: &str) -> ParseResult<&str, Self> { + measurement_name_expression(i) + } +} + +/// Represents a `FROM` clause used by various `SHOW` statements. +/// +/// A `FROM` clause for a `SHOW` statements differs from a `FROM` in a +/// `SELECT`, as it can only contain measurement name or regular expressions. +/// +/// It is defined by the following EBNF notation: +/// +/// ```text +/// from_clause ::= "FROM" measurement_selection ("," measurement_selection)* +/// measurement_selection ::= measurement +/// +/// measurement ::= measurement_name | +/// ( policy_name "." measurement_name ) | +/// ( db_name "." policy_name? "." measurement_name ) +/// +/// db_name ::= identifier +/// measurement_name ::= identifier | regex_lit +/// policy_name ::= identifier +/// ``` +/// +/// A minimal `FROM` clause would be a single identifier +/// +/// ```text +/// FROM foo +/// ``` +/// +/// A more complicated example may include a variety of fully-qualified +/// identifiers and regular expressions +/// +/// ```text +/// FROM foo, /bar/, some_database..foo, some_retention_policy.foobar +/// ``` +pub type ShowFromClause = FromMeasurementClause; + +/// Parse a `FROM` clause for various `SHOW` statements. +pub fn show_from_clause(i: &str) -> ParseResult<&str, ShowFromClause> { + from_clause(i) +} + +impl Parser for Identifier { + fn parse(i: &str) -> ParseResult<&str, Self> { + identifier(i) + } +} + +/// Represents a `FROM` clause for a `DELETE` statement. +pub type DeleteFromClause = FromMeasurementClause; + +/// Parse a `FROM` clause for a `DELETE` statement. +pub fn delete_from_clause(i: &str) -> ParseResult<&str, DeleteFromClause> { + from_clause(i) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_show_from_clause() { + use crate::simple_from_clause::MeasurementSelection::*; + + let (_, from) = show_from_clause("FROM c").unwrap(); + assert_eq!( + from, + ShowFromClause { + first: Name(MeasurementNameExpression { + database: None, + retention_policy: None, + name: "c".into() + }), + rest: None + } + ); + + let (_, from) = show_from_clause("FROM a..c").unwrap(); + assert_eq!( + from, + ShowFromClause { + first: Name(MeasurementNameExpression { + database: Some("a".into()), + retention_policy: None, + name: "c".into() + }), + rest: None + } + ); + + let (_, from) = show_from_clause("FROM a.b.c").unwrap(); + assert_eq!( + from, + ShowFromClause { + first: Name(MeasurementNameExpression { + database: Some("a".into()), + retention_policy: Some("b".into()), + name: "c".into() + }), + rest: None + } + ); + + let (_, from) = show_from_clause("FROM /reg/").unwrap(); + assert_eq!( + from, + ShowFromClause { + first: Regex("reg".into()), + rest: None + } + ); + + let (_, from) = show_from_clause("FROM c, /reg/").unwrap(); + assert_eq!( + from, + ShowFromClause { + first: Name(MeasurementNameExpression { + database: None, + retention_policy: None, + name: "c".into() + }), + rest: Some(vec![Regex("reg".into())]), + } + ); + } + + #[test] + fn test_delete_from_clause() { + use crate::simple_from_clause::MeasurementSelection::*; + + let (_, from) = delete_from_clause("FROM c").unwrap(); + assert_eq!( + from, + DeleteFromClause { + first: Name("c".into()), + rest: None + } + ); + + let (_, from) = delete_from_clause("FROM /reg/").unwrap(); + assert_eq!( + from, + DeleteFromClause { + first: Regex("reg".into()), + rest: None + } + ); + + let (_, from) = delete_from_clause("FROM c, /reg/").unwrap(); + assert_eq!( + from, + DeleteFromClause { + first: Name("c".into()), + rest: Some(vec![Regex("reg".into())]), + } + ); + + // Demonstrate that the 3-part name is not parsed + let (i, from) = delete_from_clause("FROM a.b.c").unwrap(); + assert_eq!( + from, + DeleteFromClause { + first: Name("a".into()), + rest: None, + } + ); + // The remaining input will fail in a later parser + assert_eq!(i, ".b.c"); + } +} diff --git a/influxdb_influxql_parser/src/statement.rs b/influxdb_influxql_parser/src/statement.rs index 82cf828c15..411617a9dc 100644 --- a/influxdb_influxql_parser/src/statement.rs +++ b/influxdb_influxql_parser/src/statement.rs @@ -1,24 +1,29 @@ -use crate::identifier::Identifier; +use crate::delete::{delete_statement, DeleteStatement}; +use crate::drop::{drop_statement, DropMeasurementStatement}; use crate::internal::ParseResult; -use crate::show::show_statement; +use crate::show::{show_statement, ShowDatabasesStatement}; use crate::show_field_keys::ShowFieldKeysStatement; use crate::show_measurements::ShowMeasurementsStatement; +use crate::show_retention_policies::ShowRetentionPoliciesStatement; use crate::show_tag_keys::ShowTagKeysStatement; use crate::show_tag_values::ShowTagValuesStatement; +use nom::branch::alt; +use nom::combinator::map; use std::fmt::{Display, Formatter}; /// An InfluxQL statement. #[derive(Debug, Clone, PartialEq)] pub enum Statement { + /// Represents a `DELETE` statement. + Delete(Box), + /// Represents a `DROP MEASUREMENT` statement. + DropMeasurement(Box), /// Represents a `SHOW DATABASES` statement. - ShowDatabases, + ShowDatabases(Box), /// Represents a `SHOW MEASUREMENTS` statement. ShowMeasurements(Box), /// Represents a `SHOW RETENTION POLICIES` statement. - ShowRetentionPolicies { - /// Name of the database to list the retention policies, or all if this is `None`. - database: Option, - }, + ShowRetentionPolicies(Box), /// Represents a `SHOW TAG KEYS` statement. ShowTagKeys(Box), /// Represents a `SHOW TAG VALUES` statement. @@ -30,17 +35,14 @@ pub enum Statement { impl Display for Statement { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Self::ShowDatabases => f.write_str("SHOW DATABASES")?, - Self::ShowMeasurements(s) => write!(f, "{}", s)?, - Self::ShowRetentionPolicies { database } => { - write!(f, "SHOW RETENTION POLICIES")?; - if let Some(database) = database { - write!(f, " ON {}", database)?; - } - } - Self::ShowTagKeys(s) => write!(f, "{}", s)?, - Self::ShowTagValues(s) => write!(f, "{}", s)?, - Self::ShowFieldKeys(s) => write!(f, "{}", s)?, + Self::Delete(s) => Display::fmt(s, f)?, + Self::DropMeasurement(s) => Display::fmt(s, f)?, + Self::ShowDatabases(s) => Display::fmt(s, f)?, + Self::ShowMeasurements(s) => Display::fmt(s, f)?, + Self::ShowRetentionPolicies(s) => Display::fmt(s, f)?, + Self::ShowTagKeys(s) => Display::fmt(s, f)?, + Self::ShowTagValues(s) => Display::fmt(s, f)?, + Self::ShowFieldKeys(s) => Display::fmt(s, f)?, }; Ok(()) @@ -49,6 +51,28 @@ impl Display for Statement { /// Parse a single InfluxQL statement. pub fn statement(i: &str) -> ParseResult<&str, Statement> { - // NOTE: This will become an alt(()) once more statements are added - show_statement(i) + alt(( + map(delete_statement, |s| Statement::Delete(Box::new(s))), + map(drop_statement, |s| Statement::DropMeasurement(Box::new(s))), + show_statement, + ))(i) +} + +#[cfg(test)] +mod test { + use crate::statement; + + #[test] + fn test_statement() { + // validate one of each statement parser is accepted + + // delete_statement combinator + statement("DELETE FROM foo").unwrap(); + + // drop_statement combinator + statement("DROP MEASUREMENT foo").unwrap(); + + // show_statement combinator + statement("SHOW TAG KEYS").unwrap(); + } }