Skip to content
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

feat(tql): add initial support for start,stop,step as sql functions #3507

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2cd9e39
feat(tql): add initial support for start,stop,step as sql functions
etolbakov Mar 13, 2024
bc1a549
fix(tql): remove unwraps, adjust fmt
etolbakov Mar 14, 2024
cd8b958
fix(tql): address taplo issue
etolbakov Mar 14, 2024
77cfa94
feat(tql): update parse_tql_query logic
etolbakov Mar 14, 2024
4d8711e
fix(tql): change query parsing logic to use parser instead of delimiter
etolbakov Mar 15, 2024
b665614
fix(tql): add timestamp function support, add sqlness tests
etolbakov Mar 17, 2024
26a4ab2
fix(tql): add lookback optional param for tql eval
etolbakov Mar 18, 2024
4bff8a5
fix(tql): adjust tests for now() function
etolbakov Mar 20, 2024
edafbe3
Merge branch 'main' into tql-start-stop-step-sql-functions-support
etolbakov Mar 20, 2024
d91c4e9
fix(tql): introduce the tqlerror to differentiate failures on parsing…
etolbakov Mar 21, 2024
b648e92
fix(tql): add tests for explain/analyze
etolbakov Mar 21, 2024
21f40ae
feat(tql): add lookback support for explain/analyze, update tests
etolbakov Mar 21, 2024
7d1d45b
Merge branch 'main' into tql-start-stop-step-sql-functions-support
etolbakov Mar 21, 2024
47a36de
feat(tql): add more sqlness tests
etolbakov Mar 22, 2024
bbac2f4
chore(tql): extract common logic for eval, analyze and explain into a…
etolbakov Mar 24, 2024
96fc4b9
feat(tql): address CR points
etolbakov Mar 26, 2024
fbdb7d0
Merge branch 'main' into tql-start-stop-step-sql-functions-support
etolbakov Mar 26, 2024
1281597
feat(tql): use snafu for tql errors, add more docs
etolbakov Mar 27, 2024
b59ffba
Merge branch 'main' into tql-start-stop-step-sql-functions-support
etolbakov Mar 27, 2024
368276b
feat(tql): address CR points
etolbakov Mar 27, 2024
1a611d8
Merge branch 'main' into tql-start-stop-step-sql-functions-support
etolbakov Mar 27, 2024
1abf153
Merge branch 'main' into tql-start-stop-step-sql-functions-support
etolbakov Mar 29, 2024
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
10 changes: 10 additions & 0 deletions src/sql/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use snafu::{Location, Snafu};
use sqlparser::parser::ParserError;

use crate::ast::{Expr, Value as SqlValue};
use crate::parsers::error::TQLError;

pub type Result<T> = std::result::Result<T, Error>;

Expand Down Expand Up @@ -66,6 +67,14 @@ pub enum Error {
location: Location,
},

// Syntax error from tql parser.
#[snafu(display(""))]
TQLSyntax {
#[snafu(source)]
error: TQLError,
location: Location,
},

#[snafu(display("Missing time index constraint"))]
MissingTimeIndex {},

Expand Down Expand Up @@ -170,6 +179,7 @@ impl ErrorExt for Error {
UnsupportedDefaultValue { .. } | Unsupported { .. } => StatusCode::Unsupported,
Unexpected { .. }
| Syntax { .. }
| TQLSyntax { .. }
| MissingTimeIndex { .. }
| InvalidTimeIndex { .. }
| InvalidSql { .. }
Expand Down
2 changes: 1 addition & 1 deletion src/sql/src/parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub(crate) mod create_parser;
pub(crate) mod delete_parser;
pub(crate) mod describe_parser;
pub(crate) mod drop_parser;
mod error;
pub(crate) mod error;
pub(crate) mod explain_parser;
pub(crate) mod insert_parser;
pub(crate) mod query_parser;
Expand Down
60 changes: 26 additions & 34 deletions src/sql/src/parsers/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,37 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use common_macro::stack_trace_debug;
use datafusion_common::DataFusionError;
use snafu::{Location, Snafu};
use sqlparser::parser::ParserError;

/// TQL parser & evaluation errors.
#[derive(Snafu)]
#[snafu(visibility(pub))]
#[stack_trace_debug]
pub enum TQLError {
etolbakov marked this conversation as resolved.
Show resolved Hide resolved
Parser(String),
Simplification(String),
Evaluation(String),
}
#[snafu(display("Failed to parse TQL expression"))]
Parser {
#[snafu(source)]
error: ParserError,
location: Location,
},

impl From<ParserError> for TQLError {
fn from(err: ParserError) -> Self {
TQLError::Parser(err.to_string())
}
}
#[snafu(display("Failed to convert to logical TQL expression"))]
ConvertToLogicalExpression {
#[snafu(source)]
error: DataFusionError,
location: Location,
},

impl From<DataFusionError> for TQLError {
fn from(err: DataFusionError) -> Self {
match err {
DataFusionError::SQL(parser_err) => TQLError::Parser(parser_err.to_string()),
DataFusionError::Plan(plan_err) => TQLError::Evaluation(plan_err),
unspecified => {
TQLError::Evaluation(format!("Failed to evaluate due to: {unspecified:?}"))
}
}
}
}
#[snafu(display("Failed to simplify TQL expression"))]
Simplification {
#[snafu(source)]
error: DataFusionError,
location: Location,
},

impl From<TQLError> for ParserError {
fn from(tql_err: TQLError) -> Self {
match tql_err {
TQLError::Parser(s) => {
ParserError::ParserError(format!("Failed to parse the query: {s}"))
}
TQLError::Simplification(s) => {
ParserError::ParserError(format!("Failed to simplify the query: {s}"))
}
TQLError::Evaluation(s) => {
ParserError::ParserError(format!("Failed to evaluate the query: {s}"))
}
}
}
#[snafu(display("Failed to evaluate TQL expression: {}", msg))]
Evaluation { msg: String },
}
66 changes: 41 additions & 25 deletions src/sql/src/parsers/tql_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use datafusion_common::{DFSchema, Result as DFResult, ScalarValue, TableReferenc
use datafusion_expr::{AggregateUDF, Expr, ScalarUDF, TableSource, WindowUDF};
use datafusion_physical_expr::execution_props::ExecutionProps;
use datafusion_sql::planner::{ContextProvider, SqlToRel};
use snafu::ResultExt;
use snafu::{OptionExt, ResultExt};
use sqlparser::keywords::Keyword;
use sqlparser::parser::ParserError;
use sqlparser::tokenizer::Token;
Expand All @@ -40,7 +40,9 @@ use datatypes::arrow::datatypes::DataType;
use sqlparser::parser::Parser;

use crate::dialect::GreptimeDbDialect;
use crate::parsers::error::TQLError;
use crate::parsers::error::{
ConvertToLogicalExpressionSnafu, EvaluationSnafu, ParserSnafu, SimplificationSnafu, TQLError,
};

/// TQL extension parser, including:
/// - `TQL EVAL <query>`
Expand All @@ -61,7 +63,7 @@ impl<'a> ParserContext<'a> {
{
self.parse_tql_params()
.map(|params| Statement::Tql(Tql::Eval(TqlEval::from(params))))
.context(error::SyntaxSnafu)
.context(error::TQLSyntaxSnafu)
}

Keyword::EXPLAIN => {
Expand All @@ -74,7 +76,7 @@ impl<'a> ParserContext<'a> {
params.is_verbose = is_verbose;
Statement::Tql(Tql::Explain(TqlExplain::from(params)))
})
.context(error::SyntaxSnafu)
.context(error::TQLSyntaxSnafu)
}

Keyword::ANALYZE => {
Expand All @@ -87,7 +89,7 @@ impl<'a> ParserContext<'a> {
params.is_verbose = is_verbose;
Statement::Tql(Tql::Analyze(TqlAnalyze::from(params)))
})
.context(error::SyntaxSnafu)
.context(error::TQLSyntaxSnafu)
}
_ => self.unsupported(self.peek_token_as_string()),
}
Expand All @@ -96,7 +98,7 @@ impl<'a> ParserContext<'a> {
}
}

fn parse_tql_params(&mut self) -> std::result::Result<TqlParameters, ParserError> {
fn parse_tql_params(&mut self) -> std::result::Result<TqlParameters, TQLError> {
let parser = &mut self.parser;
let (start, end, step, lookback) = match parser.peek_token().token {
Token::LParen => {
Expand All @@ -116,7 +118,7 @@ impl<'a> ParserContext<'a> {
}
_ => ("0".to_string(), "0".to_string(), "5m".to_string(), None),
};
let query = Self::parse_tql_query(parser, self.sql)?;
let query = Self::parse_tql_query(parser, self.sql).context(ParserSnafu {})?;
etolbakov marked this conversation as resolved.
Show resolved Hide resolved
Ok(TqlParameters::new(start, end, step, lookback, query))
}

Expand Down Expand Up @@ -163,23 +165,29 @@ impl<'a> ParserContext<'a> {
tokens.push(token.token);
}
let result = match tokens.len() {
0 => Err(TQLError::Parser("Expected tokens".to_string())),
0 => Err(ParserError::ParserError(
"Expected at least one token".to_string(),
))
.context(ParserSnafu {}),
1 => {
let value = match tokens[0].clone() {
Token::Number(n, _) => n,
Token::DoubleQuotedString(s) | Token::SingleQuotedString(s) => s,
Token::Word(_) => Self::parse_tokens(tokens)?,
unexpected => {
return Err(TQLError::Parser(format!(
"Expect number, string or word, but is {unexpected:?}"
)));
return Err(ParserError::ParserError(format!(
"Expected number, string or word, but have {unexpected:?}"
)))
.context(ParserSnafu {});
}
};
Ok(value)
}
_ => Self::parse_tokens(tokens),
};
parser.expect_token(&delimiter_token)?;
parser
.expect_token(&delimiter_token)
.context(ParserSnafu {})?;
result
}

Expand All @@ -194,16 +202,14 @@ impl<'a> ParserContext<'a> {
Parser::new(&GreptimeDbDialect {})
.with_tokens(tokens)
.parse_expr()
.map_err(|err| TQLError::Parser(format!("Failed to convert to expression: {err:?}")))
.context(ParserSnafu {})
}

fn parse_to_logical_expr(expr: sqlparser::ast::Expr) -> std::result::Result<Expr, TQLError> {
let empty_df_schema = DFSchema::empty();
SqlToRel::new(&StubContextProvider {})
.sql_to_expr(expr.into(), &empty_df_schema, &mut Default::default())
.map_err(|err| {
TQLError::Parser(format!("Failed to convert to logical expression {err:?}"))
})
.context(ConvertToLogicalExpressionSnafu {})
}

fn simplify_expr(logical_expr: Expr) -> std::result::Result<Expr, TQLError> {
Expand All @@ -212,21 +218,31 @@ impl<'a> ParserContext<'a> {
let info = SimplifyContext::new(&execution_props).with_schema(Arc::new(empty_df_schema));
ExprSimplifier::new(info)
.simplify(logical_expr)
.map_err(|err| {
TQLError::Simplification(format!("Failed to simplify expression {err:?}"))
})
.context(SimplificationSnafu {})
}

fn evaluate_expr(simplified_expr: Expr) -> std::result::Result<String, TQLError> {
match simplified_expr {
Expr::Literal(ScalarValue::TimestampNanosecond(v, _))
| Expr::Literal(ScalarValue::DurationNanosecond(v)) => v,
Expr::Literal(ScalarValue::TimestampNanosecond(ts_nanos, _))
| Expr::Literal(ScalarValue::DurationNanosecond(ts_nanos)) => {
ts_nanos.map(|v| v / 1_000_000_000)
}
Expr::Literal(ScalarValue::TimestampMicrosecond(ts_micros, _))
| Expr::Literal(ScalarValue::DurationMicrosecond(ts_micros)) => {
ts_micros.map(|v| v / 1_000_000)
}
Expr::Literal(ScalarValue::TimestampMillisecond(ts_millis, _))
| Expr::Literal(ScalarValue::DurationMillisecond(ts_millis)) => {
ts_millis.map(|v| v / 1_000)
}
Expr::Literal(ScalarValue::TimestampSecond(ts_secs, _))
| Expr::Literal(ScalarValue::DurationSecond(ts_secs)) => ts_secs,
_ => None,
}
.map(|timestamp_nanos| (timestamp_nanos / 1_000_000_000).to_string())
.ok_or(TQLError::Evaluation(format!(
"Failed to extract a timestamp value {simplified_expr:?}"
)))
.map(|ts| ts.to_string())
.context(EvaluationSnafu {
msg: format!("Failed to extract a timestamp value {simplified_expr:?}"),
})
}

fn parse_tql_query(parser: &mut Parser, sql: &str) -> std::result::Result<String, ParserError> {
Expand Down
3 changes: 3 additions & 0 deletions src/sql/src/statements/tql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub struct TqlAnalyze {
pub is_verbose: bool,
}

/// Intermediate structure used to unify parameter mappings for various TQL operations.
/// This struct serves as a common parameter container for parsing TQL queries
/// and constructing corresponding TQL operations: `TqlEval`, `TqlAnalyze` or `TqlExplain`.
#[derive(Debug)]
etolbakov marked this conversation as resolved.
Show resolved Hide resolved
pub struct TqlParameters {
start: String,
Expand Down