From 23a5eae0eb9bef946126f010d31d6707f3fe3972 Mon Sep 17 00:00:00 2001 From: "gao.dong" Date: Thu, 5 Sep 2024 10:32:44 +0800 Subject: [PATCH] parse create function --- src/alter.rs | 2 +- src/create.rs | 105 +++++++--------------------------------------- src/delete.rs | 2 +- src/drop.rs | 2 +- src/issue.rs | 5 ++- src/lexer.rs | 2 +- src/lib.rs | 23 ++++++++++ src/select.rs | 2 +- src/statement.rs | 68 ++++++++++++++++++++++++++++++ src/with_query.rs | 2 +- 10 files changed, 116 insertions(+), 97 deletions(-) diff --git a/src/alter.rs b/src/alter.rs index 9de4896..64a95db 100644 --- a/src/alter.rs +++ b/src/alter.rs @@ -17,7 +17,7 @@ use crate::{ lexer::Token, parser::{ParseError, Parser}, qualified_name::parse_qualified_name, - DataType, Identifier, Issue, QualifiedName, SString, Span, Spanned, Statement, + DataType, Identifier, QualifiedName, SString, Span, Spanned, Statement, }; /// Option on an index diff --git a/src/create.rs b/src/create.rs index 4da317f..be8fffa 100644 --- a/src/create.rs +++ b/src/create.rs @@ -20,7 +20,7 @@ use crate::{ qualified_name::parse_qualified_name, select::{parse_select, Select}, statement::parse_statement, - DataType, Expression, Identifier, Issue, QualifiedName, SString, Span, Spanned, Statement, + DataType, Expression, Identifier, QualifiedName, SString, Span, Spanned, Statement, }; /// Options on created table @@ -505,7 +505,7 @@ impl Spanned for FunctionParamDirection { /// /// This is not fully implemented yet /// -/// ```ignore +/// ``` /// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, CreateFunction, Statement}; /// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB); /// # let mut issues = Vec::new(); @@ -515,12 +515,13 @@ impl Spanned for FunctionParamDirection { /// BEGIN /// SET c = 100; /// RETURN a + b; -/// END; +/// END /// $$ /// DELIMITER ;"; /// let mut stmts = parse_statements(sql, &mut issues, &options); /// -/// assert!(issues.is_empty()); +/// +/// assert!(issues.is_empty(),"Issues: {:#?}", issues); /// # /// let create: CreateFunction = match stmts.pop() { /// Some(Statement::CreateFunction(c)) => c, @@ -528,7 +529,7 @@ impl Spanned for FunctionParamDirection { /// }; /// /// assert!(create.name.as_str() == "add_func3"); -/// println!("{:#?}", create.return_) +/// /// ``` #[derive(Clone, Debug)] pub struct CreateFunction<'a> { @@ -548,10 +549,8 @@ pub struct CreateFunction<'a> { pub returns_span: Span, /// Type of return value pub return_type: DataType<'a>, - /// Characteristics of created function - pub characteristics: Vec>, - /// Statement computing return value - pub return_: Option>>, + /// Statement of function body + pub statement: Box>, } impl<'a> Spanned for CreateFunction<'a> { @@ -562,8 +561,7 @@ impl<'a> Spanned for CreateFunction<'a> { .join_span(&self.if_not_exists) .join_span(&self.name) .join_span(&self.return_type) - .join_span(&self.characteristics) - .join_span(&self.return_) + .join_span(&self.statement) } } @@ -607,10 +605,6 @@ fn parse_create_function<'a>( _ => None, }; - if parser.options.dialect.is_maria() && direction.is_none() { - parser.expected_error("'IN', 'OUT' or 'INOUT'"); - } - let name = parser.consume_plain_identifier()?; let type_ = parse_data_type(parser, false)?; params.push((direction, name, type_)); @@ -638,78 +632,12 @@ fn parse_create_function<'a>( } } - let mut characteristics = Vec::new(); - loop { - let f = match &parser.token { - Token::Ident(_, Keyword::LANGUAGE) => { - let lg = parser.consume(); - match &parser.token { - Token::Ident(_, Keyword::SQL) => { - FunctionCharacteristic::LanguageSql(lg.join_span(&parser.consume())) - } - Token::Ident(_, Keyword::PLPGSQL) => { - FunctionCharacteristic::LanguagePlpgsql(lg.join_span(&parser.consume())) - } - _ => parser.expected_failure("language name")?, - } - } - Token::Ident(_, Keyword::NOT) => FunctionCharacteristic::NotDeterministic( - parser.consume_keywords(&[Keyword::NOT, Keyword::DETERMINISTIC])?, - ), - Token::Ident(_, Keyword::DETERMINISTIC) => FunctionCharacteristic::Deterministic( - parser.consume_keyword(Keyword::DETERMINISTIC)?, - ), - Token::Ident(_, Keyword::CONTAINS) => FunctionCharacteristic::ContainsSql( - parser.consume_keywords(&[Keyword::CONTAINS, Keyword::SQL])?, - ), - Token::Ident(_, Keyword::NO) => FunctionCharacteristic::NoSql( - parser.consume_keywords(&[Keyword::NO, Keyword::SQL])?, - ), - Token::Ident(_, Keyword::READS) => { - FunctionCharacteristic::ReadsSqlData(parser.consume_keywords(&[ - Keyword::READS, - Keyword::SQL, - Keyword::DATA, - ])?) - } - Token::Ident(_, Keyword::MODIFIES) => { - FunctionCharacteristic::ModifiesSqlData(parser.consume_keywords(&[ - Keyword::MODIFIES, - Keyword::SQL, - Keyword::DATA, - ])?) - } - Token::Ident(_, Keyword::COMMENT) => { - parser.consume_keyword(Keyword::COMMENT)?; - FunctionCharacteristic::Comment(parser.consume_string()?) - } - Token::Ident(_, Keyword::SQL) => { - let span = parser.consume_keywords(&[Keyword::SQL, Keyword::SECURITY])?; - match &parser.token { - Token::Ident(_, Keyword::DEFINER) => { - FunctionCharacteristic::SqlSecurityDefiner( - parser.consume_keyword(Keyword::DEFINER)?.join_span(&span), - ) - } - Token::Ident(_, Keyword::USER) => FunctionCharacteristic::SqlSecurityUser( - parser.consume_keyword(Keyword::USER)?.join_span(&span), - ), - _ => parser.expected_failure("'DEFINER' or 'USER'")?, - } - } - _ => break, - }; - characteristics.push(f); - } - - let return_ = if parser.options.dialect.is_maria() { - match parse_statement(parser)? { - Some(v) => Some(Box::new(v)), - None => parser.expected_failure("statement")?, - } - } else { - None + let old = core::mem::replace(&mut parser.permit_compound_statements, true); + let statement = match parse_statement(parser)? { + Some(v) => v, + None => parser.expected_failure("statement")?, }; + parser.permit_compound_statements = old; Ok(Statement::CreateFunction(CreateFunction { create_span, @@ -718,10 +646,9 @@ fn parse_create_function<'a>( if_not_exists, name, params, - return_type, - characteristics, - return_, returns_span, + return_type, + statement: Box::new(statement), })) } diff --git a/src/delete.rs b/src/delete.rs index b3c4c3d..df42519 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -19,7 +19,7 @@ use crate::{ parser::{ParseError, Parser}, qualified_name::parse_qualified_name, select::{parse_select_expr, parse_table_reference}, - Issue, QualifiedName, SelectExpr, Span, Spanned, TableReference, + QualifiedName, SelectExpr, Span, Spanned, TableReference, }; /// Flags for deletion diff --git a/src/drop.rs b/src/drop.rs index 5da71d5..415100b 100644 --- a/src/drop.rs +++ b/src/drop.rs @@ -17,7 +17,7 @@ use crate::{ lexer::Token, parser::{ParseError, Parser}, qualified_name::parse_qualified_name, - Identifier, Issue, QualifiedName, Span, Spanned, Statement, + Identifier, QualifiedName, Span, Spanned, Statement, }; /// Represent a drop table statement diff --git a/src/issue.rs b/src/issue.rs index 2304960..81f81eb 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -12,7 +12,7 @@ use alloc::{string::String, vec::Vec}; -use crate::{SString, Span, Spanned}; +use crate::{Span, Spanned}; /// Level of an issues #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] @@ -66,7 +66,8 @@ impl<'a> Issue<'a> { span: &impl Spanned, sql_segment: &'a str, ) -> Self { - self.fragments.push((message.into(), span.span(),sql_segment)); + self.fragments + .push((message.into(), span.span(), sql_segment)); self } } diff --git a/src/lexer.rs b/src/lexer.rs index 8b02894..1a6311c 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -445,7 +445,7 @@ impl<'a> Lexer<'a> { '`' => { while matches!( self.chars.peek(), - Some((_, '_' | 'a'..='z' | 'A'..='Z' | '0'..='9' | '-')) + Some((_, '_' | 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '%')) ) { self.chars.next(); } diff --git a/src/lib.rs b/src/lib.rs index bbf81a8..f33bbf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -397,3 +397,26 @@ pub fn parse_use_index() { let _result = parse_statement(sql, &mut issues, &options); assert!(issues.is_empty(), "Issues: {:#?}", issues); } + +#[test] +pub fn parse_create_function_sql_with_schema() { + let sql = " + DELIMITER $$ + CREATE DEFINER=`root`@`%` FUNCTION `name_for_id`(id INT(11)) + RETURNS VARCHAR(20) + COMMENT '' + BEGIN + RETURN(SELECT name FROM student tb WHERE tb.id = id); + END + $$ + DELIMITER ;"; + let options = ParseOptions::new() + .dialect(SQLDialect::MariaDB) + .arguments(SQLArguments::QuestionMark) + .warn_unquoted_identifiers(false); + + let mut issues = Vec::new(); + let _result = parse_statements(sql, &mut issues, &options); + // assert!(result.len() <= 0, "result: {:#?}", &result); + assert!(issues.is_empty(), "Issues: {:#?}", issues); +} diff --git a/src/select.rs b/src/select.rs index 3c5e5d7..6b8a5db 100644 --- a/src/select.rs +++ b/src/select.rs @@ -12,6 +12,7 @@ use alloc::{boxed::Box, vec::Vec}; use crate::qualified_name::parse_qualified_name; +use crate::QualifiedName; use crate::{ expression::{parse_expression, Expression}, keywords::Keyword, @@ -21,7 +22,6 @@ use crate::{ statement::parse_compound_query, Identifier, Span, Spanned, Statement, }; -use crate::{Issue, QualifiedName}; /// Value in select #[derive(Debug, Clone)] diff --git a/src/statement.rs b/src/statement.rs index 6982059..b6dbfdb 100644 --- a/src/statement.rs +++ b/src/statement.rs @@ -277,6 +277,7 @@ pub enum Statement<'a> { TruncateTable(TruncateTable<'a>), RenameTable(RenameTable<'a>), WithQuery(WithQuery<'a>), + Return(Return<'a>), } impl<'a> Spanned for Statement<'a> { @@ -318,6 +319,7 @@ impl<'a> Spanned for Statement<'a> { Statement::TruncateTable(v) => v.span(), Statement::RenameTable(v) => v.span(), Statement::WithQuery(v) => v.span(), + Statement::Return(v) => v.span(), } } } @@ -366,6 +368,7 @@ pub(crate) fn parse_statement<'a>( Some(Statement::RenameTable(parse_rename_table(parser)?)) } Token::Ident(_, Keyword::WITH) => Some(Statement::WithQuery(parse_with_query(parser)?)), + Token::Ident(_, Keyword::RETURN) => Some(Statement::Return(parse_return(parser)?)), _ => None, }) } @@ -742,3 +745,68 @@ pub(crate) fn parse_statements<'a>(parser: &mut Parser<'a, '_>) -> Vec { + Expression(Box>), + Select(Box>), +} + +impl<'a> Spanned for ReturnExpression<'a> { + fn span(&self) -> Span { + match &self { + ReturnExpression::Expression(v) => v.span(), + ReturnExpression::Select(v) => v.span(), + } + } +} + +#[derive(Clone, Debug)] +pub struct Return<'a> { + pub return_span: Span, + pub statement: Option>, +} + +impl<'a> Spanned for Return<'a> { + fn span(&self) -> Span { + match &self.statement { + Some(v) => v.join_span(&self.return_span), + None => self.return_span.span(), + } + } +} + +fn parse_return<'a>(parser: &mut Parser<'a, '_>) -> Result, ParseError> { + let return_span = parser.consume_keyword(Keyword::RETURN)?; + loop { + if Token::LParen == parser.token { + let _ = parser.skip_token(Token::LParen); + } else { + break; + } + } + let statement = match &parser.token { + Token::Ident(_, Keyword::SELECT) => { + Some(ReturnExpression::Select(Box::new(parse_select(parser)?))) + } + Token::Eof => None, + _ => Some(ReturnExpression::Expression(Box::new(parse_expression( + parser, false, + )?))), + }; + loop { + match &parser.token { + Token::RParen => { + parser.skip_token(Token::RParen); + } + // Token::SemiColon => { + // parser.skip_token(Token::SemiColon); + // } + _ => break, + } + } + Ok(Return { + return_span, + statement, + }) +} diff --git a/src/with_query.rs b/src/with_query.rs index 39c77d3..7f9ff52 100644 --- a/src/with_query.rs +++ b/src/with_query.rs @@ -5,7 +5,7 @@ use crate::{ lexer::Token, parser::{ParseError, Parser}, statement::parse_statement, - Identifier, Issue, Span, Spanned, Statement, + Identifier, Span, Spanned, Statement, }; #[derive(Clone, Debug)]