mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00
Support CREATE FUNCTION for BigQuery (#1253)
This commit is contained in:
parent
d5faf3c54b
commit
792e389baa
6 changed files with 557 additions and 173 deletions
213
src/ast/mod.rs
213
src/ast/mod.rs
|
@ -2454,14 +2454,64 @@ pub enum Statement {
|
|||
/// Supported variants:
|
||||
/// 1. [Hive](https://cwiki.apache.org/confluence/display/hive/languagemanual+ddl#LanguageManualDDL-Create/Drop/ReloadFunction)
|
||||
/// 2. [Postgres](https://www.postgresql.org/docs/15/sql-createfunction.html)
|
||||
/// 3. [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement)
|
||||
CreateFunction {
|
||||
or_replace: bool,
|
||||
temporary: bool,
|
||||
if_not_exists: bool,
|
||||
name: ObjectName,
|
||||
args: Option<Vec<OperateFunctionArg>>,
|
||||
return_type: Option<DataType>,
|
||||
/// Optional parameters.
|
||||
params: CreateFunctionBody,
|
||||
/// The expression that defines the function.
|
||||
///
|
||||
/// Examples:
|
||||
/// ```sql
|
||||
/// AS ((SELECT 1))
|
||||
/// AS "console.log();"
|
||||
/// ```
|
||||
function_body: Option<CreateFunctionBody>,
|
||||
/// Behavior attribute for the function
|
||||
///
|
||||
/// IMMUTABLE | STABLE | VOLATILE
|
||||
///
|
||||
/// [Postgres](https://www.postgresql.org/docs/current/sql-createfunction.html)
|
||||
behavior: Option<FunctionBehavior>,
|
||||
/// CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
|
||||
///
|
||||
/// [Postgres](https://www.postgresql.org/docs/current/sql-createfunction.html)
|
||||
called_on_null: Option<FunctionCalledOnNull>,
|
||||
/// PARALLEL { UNSAFE | RESTRICTED | SAFE }
|
||||
///
|
||||
/// [Postgres](https://www.postgresql.org/docs/current/sql-createfunction.html)
|
||||
parallel: Option<FunctionParallel>,
|
||||
/// USING ... (Hive only)
|
||||
using: Option<CreateFunctionUsing>,
|
||||
/// Language used in a UDF definition.
|
||||
///
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// CREATE FUNCTION foo() LANGUAGE js AS "console.log();"
|
||||
/// ```
|
||||
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_a_javascript_udf)
|
||||
language: Option<Ident>,
|
||||
/// Determinism keyword used for non-sql UDF definitions.
|
||||
///
|
||||
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11)
|
||||
determinism_specifier: Option<FunctionDeterminismSpecifier>,
|
||||
/// List of options for creating the function.
|
||||
///
|
||||
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11)
|
||||
options: Option<Vec<SqlOption>>,
|
||||
/// Connection resource for a remote function.
|
||||
///
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// CREATE FUNCTION foo()
|
||||
/// RETURNS FLOAT64
|
||||
/// REMOTE WITH CONNECTION us.myconnection
|
||||
/// ```
|
||||
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_a_remote_function)
|
||||
remote_connection: Option<ObjectName>,
|
||||
},
|
||||
/// ```sql
|
||||
/// CREATE PROCEDURE
|
||||
|
@ -3152,16 +3202,26 @@ impl fmt::Display for Statement {
|
|||
Statement::CreateFunction {
|
||||
or_replace,
|
||||
temporary,
|
||||
if_not_exists,
|
||||
name,
|
||||
args,
|
||||
return_type,
|
||||
params,
|
||||
function_body,
|
||||
language,
|
||||
behavior,
|
||||
called_on_null,
|
||||
parallel,
|
||||
using,
|
||||
determinism_specifier,
|
||||
options,
|
||||
remote_connection,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"CREATE {or_replace}{temp}FUNCTION {name}",
|
||||
"CREATE {or_replace}{temp}FUNCTION {if_not_exists}{name}",
|
||||
temp = if *temporary { "TEMPORARY " } else { "" },
|
||||
or_replace = if *or_replace { "OR REPLACE " } else { "" },
|
||||
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
|
||||
)?;
|
||||
if let Some(args) = args {
|
||||
write!(f, "({})", display_comma_separated(args))?;
|
||||
|
@ -3169,7 +3229,43 @@ impl fmt::Display for Statement {
|
|||
if let Some(return_type) = return_type {
|
||||
write!(f, " RETURNS {return_type}")?;
|
||||
}
|
||||
write!(f, "{params}")?;
|
||||
if let Some(determinism_specifier) = determinism_specifier {
|
||||
write!(f, " {determinism_specifier}")?;
|
||||
}
|
||||
if let Some(language) = language {
|
||||
write!(f, " LANGUAGE {language}")?;
|
||||
}
|
||||
if let Some(behavior) = behavior {
|
||||
write!(f, " {behavior}")?;
|
||||
}
|
||||
if let Some(called_on_null) = called_on_null {
|
||||
write!(f, " {called_on_null}")?;
|
||||
}
|
||||
if let Some(parallel) = parallel {
|
||||
write!(f, " {parallel}")?;
|
||||
}
|
||||
if let Some(remote_connection) = remote_connection {
|
||||
write!(f, " REMOTE WITH CONNECTION {remote_connection}")?;
|
||||
}
|
||||
if let Some(CreateFunctionBody::AsBeforeOptions(function_body)) = function_body {
|
||||
write!(f, " AS {function_body}")?;
|
||||
}
|
||||
if let Some(CreateFunctionBody::Return(function_body)) = function_body {
|
||||
write!(f, " RETURN {function_body}")?;
|
||||
}
|
||||
if let Some(using) = using {
|
||||
write!(f, " {using}")?;
|
||||
}
|
||||
if let Some(options) = options {
|
||||
write!(
|
||||
f,
|
||||
" OPTIONS({})",
|
||||
display_comma_separated(options.as_slice())
|
||||
)?;
|
||||
}
|
||||
if let Some(CreateFunctionBody::AsAfterOptions(function_body)) = function_body {
|
||||
write!(f, " AS {function_body}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Statement::CreateProcedure {
|
||||
|
@ -6143,75 +6239,74 @@ impl fmt::Display for FunctionParallel {
|
|||
}
|
||||
}
|
||||
|
||||
/// [BigQuery] Determinism specifier used in a UDF definition.
|
||||
///
|
||||
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum FunctionDefinition {
|
||||
SingleQuotedDef(String),
|
||||
DoubleDollarDef(String),
|
||||
pub enum FunctionDeterminismSpecifier {
|
||||
Deterministic,
|
||||
NotDeterministic,
|
||||
}
|
||||
|
||||
impl fmt::Display for FunctionDefinition {
|
||||
impl fmt::Display for FunctionDeterminismSpecifier {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
FunctionDefinition::SingleQuotedDef(s) => write!(f, "'{s}'")?,
|
||||
FunctionDefinition::DoubleDollarDef(s) => write!(f, "$${s}$$")?,
|
||||
FunctionDeterminismSpecifier::Deterministic => {
|
||||
write!(f, "DETERMINISTIC")
|
||||
}
|
||||
FunctionDeterminismSpecifier::NotDeterministic => {
|
||||
write!(f, "NOT DETERMINISTIC")
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Postgres specific feature.
|
||||
/// Represent the expression body of a `CREATE FUNCTION` statement as well as
|
||||
/// where within the statement, the body shows up.
|
||||
///
|
||||
/// See [Postgres docs](https://www.postgresql.org/docs/15/sql-createfunction.html)
|
||||
/// for more details
|
||||
#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
|
||||
/// [Postgres]: https://www.postgresql.org/docs/15/sql-createfunction.html
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct CreateFunctionBody {
|
||||
/// LANGUAGE lang_name
|
||||
pub language: Option<Ident>,
|
||||
/// IMMUTABLE | STABLE | VOLATILE
|
||||
pub behavior: Option<FunctionBehavior>,
|
||||
/// CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
|
||||
pub called_on_null: Option<FunctionCalledOnNull>,
|
||||
/// PARALLEL { UNSAFE | RESTRICTED | SAFE }
|
||||
pub parallel: Option<FunctionParallel>,
|
||||
/// AS 'definition'
|
||||
pub enum CreateFunctionBody {
|
||||
/// A function body expression using the 'AS' keyword and shows up
|
||||
/// before any `OPTIONS` clause.
|
||||
///
|
||||
/// Note that Hive's `AS class_name` is also parsed here.
|
||||
pub as_: Option<FunctionDefinition>,
|
||||
/// RETURN expression
|
||||
pub return_: Option<Expr>,
|
||||
/// USING ... (Hive only)
|
||||
pub using: Option<CreateFunctionUsing>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CreateFunctionBody {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(language) = &self.language {
|
||||
write!(f, " LANGUAGE {language}")?;
|
||||
}
|
||||
if let Some(behavior) = &self.behavior {
|
||||
write!(f, " {behavior}")?;
|
||||
}
|
||||
if let Some(called_on_null) = &self.called_on_null {
|
||||
write!(f, " {called_on_null}")?;
|
||||
}
|
||||
if let Some(parallel) = &self.parallel {
|
||||
write!(f, " {parallel}")?;
|
||||
}
|
||||
if let Some(definition) = &self.as_ {
|
||||
write!(f, " AS {definition}")?;
|
||||
}
|
||||
if let Some(expr) = &self.return_ {
|
||||
write!(f, " RETURN {expr}")?;
|
||||
}
|
||||
if let Some(using) = &self.using {
|
||||
write!(f, " {using}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// CREATE FUNCTION myfunc(x FLOAT64, y FLOAT64) RETURNS FLOAT64
|
||||
/// AS (x * y)
|
||||
/// OPTIONS(description="desc");
|
||||
/// ```
|
||||
///
|
||||
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
|
||||
AsBeforeOptions(Expr),
|
||||
/// A function body expression using the 'AS' keyword and shows up
|
||||
/// after any `OPTIONS` clause.
|
||||
///
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// CREATE FUNCTION myfunc(x FLOAT64, y FLOAT64) RETURNS FLOAT64
|
||||
/// OPTIONS(description="desc")
|
||||
/// AS (x * y);
|
||||
/// ```
|
||||
///
|
||||
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
|
||||
AsAfterOptions(Expr),
|
||||
/// Function body expression using the 'RETURN' keyword.
|
||||
///
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// CREATE FUNCTION myfunc(a INTEGER, IN b INTEGER = 1) RETURNS INTEGER
|
||||
/// LANGUAGE SQL
|
||||
/// RETURN a + b;
|
||||
/// ```
|
||||
///
|
||||
/// [Postgres]: https://www.postgresql.org/docs/current/sql-createfunction.html
|
||||
Return(Expr),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
|
|
|
@ -572,6 +572,7 @@ define_keywords!(
|
|||
RELATIVE,
|
||||
RELAY,
|
||||
RELEASE,
|
||||
REMOTE,
|
||||
RENAME,
|
||||
REORG,
|
||||
REPAIR,
|
||||
|
|
|
@ -3590,95 +3590,53 @@ impl<'a> Parser<'a> {
|
|||
temporary: bool,
|
||||
) -> Result<Statement, ParserError> {
|
||||
if dialect_of!(self is HiveDialect) {
|
||||
let name = self.parse_object_name(false)?;
|
||||
self.expect_keyword(Keyword::AS)?;
|
||||
let class_name = self.parse_function_definition()?;
|
||||
let params = CreateFunctionBody {
|
||||
as_: Some(class_name),
|
||||
using: self.parse_optional_create_function_using()?,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(Statement::CreateFunction {
|
||||
or_replace,
|
||||
temporary,
|
||||
name,
|
||||
args: None,
|
||||
return_type: None,
|
||||
params,
|
||||
})
|
||||
self.parse_hive_create_function(or_replace, temporary)
|
||||
} else if dialect_of!(self is PostgreSqlDialect | GenericDialect) {
|
||||
let name = self.parse_object_name(false)?;
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let args = if self.consume_token(&Token::RParen) {
|
||||
self.prev_token();
|
||||
None
|
||||
} else {
|
||||
Some(self.parse_comma_separated(Parser::parse_function_arg)?)
|
||||
};
|
||||
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
||||
let return_type = if self.parse_keyword(Keyword::RETURNS) {
|
||||
Some(self.parse_data_type()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let params = self.parse_create_function_body()?;
|
||||
|
||||
Ok(Statement::CreateFunction {
|
||||
or_replace,
|
||||
temporary,
|
||||
name,
|
||||
args,
|
||||
return_type,
|
||||
params,
|
||||
})
|
||||
self.parse_postgres_create_function(or_replace, temporary)
|
||||
} else if dialect_of!(self is DuckDbDialect) {
|
||||
self.parse_create_macro(or_replace, temporary)
|
||||
} else if dialect_of!(self is BigQueryDialect) {
|
||||
self.parse_bigquery_create_function(or_replace, temporary)
|
||||
} else {
|
||||
self.prev_token();
|
||||
self.expected("an object type after CREATE", self.peek_token())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_function_arg(&mut self) -> Result<OperateFunctionArg, ParserError> {
|
||||
let mode = if self.parse_keyword(Keyword::IN) {
|
||||
Some(ArgMode::In)
|
||||
} else if self.parse_keyword(Keyword::OUT) {
|
||||
Some(ArgMode::Out)
|
||||
} else if self.parse_keyword(Keyword::INOUT) {
|
||||
Some(ArgMode::InOut)
|
||||
/// Parse `CREATE FUNCTION` for [Postgres]
|
||||
///
|
||||
/// [Postgres]: https://www.postgresql.org/docs/15/sql-createfunction.html
|
||||
fn parse_postgres_create_function(
|
||||
&mut self,
|
||||
or_replace: bool,
|
||||
temporary: bool,
|
||||
) -> Result<Statement, ParserError> {
|
||||
let name = self.parse_object_name(false)?;
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let args = if self.consume_token(&Token::RParen) {
|
||||
self.prev_token();
|
||||
None
|
||||
} else {
|
||||
Some(self.parse_comma_separated(Parser::parse_function_arg)?)
|
||||
};
|
||||
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
||||
let return_type = if self.parse_keyword(Keyword::RETURNS) {
|
||||
Some(self.parse_data_type()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// parse: [ argname ] argtype
|
||||
let mut name = None;
|
||||
let mut data_type = self.parse_data_type()?;
|
||||
if let DataType::Custom(n, _) = &data_type {
|
||||
// the first token is actually a name
|
||||
name = Some(n.0[0].clone());
|
||||
data_type = self.parse_data_type()?;
|
||||
#[derive(Default)]
|
||||
struct Body {
|
||||
language: Option<Ident>,
|
||||
behavior: Option<FunctionBehavior>,
|
||||
function_body: Option<CreateFunctionBody>,
|
||||
called_on_null: Option<FunctionCalledOnNull>,
|
||||
parallel: Option<FunctionParallel>,
|
||||
}
|
||||
|
||||
let default_expr = if self.parse_keyword(Keyword::DEFAULT) || self.consume_token(&Token::Eq)
|
||||
{
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(OperateFunctionArg {
|
||||
mode,
|
||||
name,
|
||||
data_type,
|
||||
default_expr,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_create_function_body(&mut self) -> Result<CreateFunctionBody, ParserError> {
|
||||
let mut body = CreateFunctionBody::default();
|
||||
let mut body = Body::default();
|
||||
loop {
|
||||
fn ensure_not_set<T>(field: &Option<T>, name: &str) -> Result<(), ParserError> {
|
||||
if field.is_some() {
|
||||
|
@ -3689,8 +3647,10 @@ impl<'a> Parser<'a> {
|
|||
Ok(())
|
||||
}
|
||||
if self.parse_keyword(Keyword::AS) {
|
||||
ensure_not_set(&body.as_, "AS")?;
|
||||
body.as_ = Some(self.parse_function_definition()?);
|
||||
ensure_not_set(&body.function_body, "AS")?;
|
||||
body.function_body = Some(CreateFunctionBody::AsBeforeOptions(
|
||||
self.parse_create_function_body_string()?,
|
||||
));
|
||||
} else if self.parse_keyword(Keyword::LANGUAGE) {
|
||||
ensure_not_set(&body.language, "LANGUAGE")?;
|
||||
body.language = Some(self.parse_identifier(false)?);
|
||||
|
@ -3744,12 +3704,186 @@ impl<'a> Parser<'a> {
|
|||
return self.expected("one of UNSAFE | RESTRICTED | SAFE", self.peek_token());
|
||||
}
|
||||
} else if self.parse_keyword(Keyword::RETURN) {
|
||||
ensure_not_set(&body.return_, "RETURN")?;
|
||||
body.return_ = Some(self.parse_expr()?);
|
||||
ensure_not_set(&body.function_body, "RETURN")?;
|
||||
body.function_body = Some(CreateFunctionBody::Return(self.parse_expr()?));
|
||||
} else {
|
||||
return Ok(body);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Statement::CreateFunction {
|
||||
or_replace,
|
||||
temporary,
|
||||
name,
|
||||
args,
|
||||
return_type,
|
||||
behavior: body.behavior,
|
||||
called_on_null: body.called_on_null,
|
||||
parallel: body.parallel,
|
||||
language: body.language,
|
||||
function_body: body.function_body,
|
||||
if_not_exists: false,
|
||||
using: None,
|
||||
determinism_specifier: None,
|
||||
options: None,
|
||||
remote_connection: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse `CREATE FUNCTION` for [Hive]
|
||||
///
|
||||
/// [Hive]: https://cwiki.apache.org/confluence/display/hive/languagemanual+ddl#LanguageManualDDL-Create/Drop/ReloadFunction
|
||||
fn parse_hive_create_function(
|
||||
&mut self,
|
||||
or_replace: bool,
|
||||
temporary: bool,
|
||||
) -> Result<Statement, ParserError> {
|
||||
let name = self.parse_object_name(false)?;
|
||||
self.expect_keyword(Keyword::AS)?;
|
||||
|
||||
let as_ = self.parse_create_function_body_string()?;
|
||||
let using = self.parse_optional_create_function_using()?;
|
||||
|
||||
Ok(Statement::CreateFunction {
|
||||
or_replace,
|
||||
temporary,
|
||||
name,
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions(as_)),
|
||||
using,
|
||||
if_not_exists: false,
|
||||
args: None,
|
||||
return_type: None,
|
||||
behavior: None,
|
||||
called_on_null: None,
|
||||
parallel: None,
|
||||
language: None,
|
||||
determinism_specifier: None,
|
||||
options: None,
|
||||
remote_connection: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse `CREATE FUNCTION` for [BigQuery]
|
||||
///
|
||||
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement
|
||||
fn parse_bigquery_create_function(
|
||||
&mut self,
|
||||
or_replace: bool,
|
||||
temporary: bool,
|
||||
) -> Result<Statement, ParserError> {
|
||||
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||
let name = self.parse_object_name(false)?;
|
||||
|
||||
let parse_function_param =
|
||||
|parser: &mut Parser| -> Result<OperateFunctionArg, ParserError> {
|
||||
let name = parser.parse_identifier(false)?;
|
||||
let data_type = parser.parse_data_type()?;
|
||||
Ok(OperateFunctionArg {
|
||||
mode: None,
|
||||
name: Some(name),
|
||||
data_type,
|
||||
default_expr: None,
|
||||
})
|
||||
};
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let args = self.parse_comma_separated0(parse_function_param)?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
||||
let return_type = if self.parse_keyword(Keyword::RETURNS) {
|
||||
Some(self.parse_data_type()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let determinism_specifier = if self.parse_keyword(Keyword::DETERMINISTIC) {
|
||||
Some(FunctionDeterminismSpecifier::Deterministic)
|
||||
} else if self.parse_keywords(&[Keyword::NOT, Keyword::DETERMINISTIC]) {
|
||||
Some(FunctionDeterminismSpecifier::NotDeterministic)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let language = if self.parse_keyword(Keyword::LANGUAGE) {
|
||||
Some(self.parse_identifier(false)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let remote_connection =
|
||||
if self.parse_keywords(&[Keyword::REMOTE, Keyword::WITH, Keyword::CONNECTION]) {
|
||||
Some(self.parse_object_name(false)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// `OPTIONS` may come before of after the function body but
|
||||
// may be specified at most once.
|
||||
let mut options = self.maybe_parse_options(Keyword::OPTIONS)?;
|
||||
|
||||
let function_body = if remote_connection.is_none() {
|
||||
self.expect_keyword(Keyword::AS)?;
|
||||
let expr = self.parse_expr()?;
|
||||
if options.is_none() {
|
||||
options = self.maybe_parse_options(Keyword::OPTIONS)?;
|
||||
Some(CreateFunctionBody::AsBeforeOptions(expr))
|
||||
} else {
|
||||
Some(CreateFunctionBody::AsAfterOptions(expr))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Statement::CreateFunction {
|
||||
or_replace,
|
||||
temporary,
|
||||
if_not_exists,
|
||||
name,
|
||||
args: Some(args),
|
||||
return_type,
|
||||
function_body,
|
||||
language,
|
||||
determinism_specifier,
|
||||
options,
|
||||
remote_connection,
|
||||
using: None,
|
||||
behavior: None,
|
||||
called_on_null: None,
|
||||
parallel: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_function_arg(&mut self) -> Result<OperateFunctionArg, ParserError> {
|
||||
let mode = if self.parse_keyword(Keyword::IN) {
|
||||
Some(ArgMode::In)
|
||||
} else if self.parse_keyword(Keyword::OUT) {
|
||||
Some(ArgMode::Out)
|
||||
} else if self.parse_keyword(Keyword::INOUT) {
|
||||
Some(ArgMode::InOut)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// parse: [ argname ] argtype
|
||||
let mut name = None;
|
||||
let mut data_type = self.parse_data_type()?;
|
||||
if let DataType::Custom(n, _) = &data_type {
|
||||
// the first token is actually a name
|
||||
name = Some(n.0[0].clone());
|
||||
data_type = self.parse_data_type()?;
|
||||
}
|
||||
|
||||
let default_expr = if self.parse_keyword(Keyword::DEFAULT) || self.consume_token(&Token::Eq)
|
||||
{
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(OperateFunctionArg {
|
||||
mode,
|
||||
name,
|
||||
data_type,
|
||||
default_expr,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_create_macro(
|
||||
|
@ -3893,12 +4027,9 @@ impl<'a> Parser<'a> {
|
|||
};
|
||||
|
||||
if dialect_of!(self is BigQueryDialect | GenericDialect) {
|
||||
if let Token::Word(word) = self.peek_token().token {
|
||||
if word.keyword == Keyword::OPTIONS {
|
||||
let opts = self.parse_options(Keyword::OPTIONS)?;
|
||||
if !opts.is_empty() {
|
||||
options = CreateTableOptions::Options(opts);
|
||||
}
|
||||
if let Some(opts) = self.maybe_parse_options(Keyword::OPTIONS)? {
|
||||
if !opts.is_empty() {
|
||||
options = CreateTableOptions::Options(opts);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -5680,6 +5811,18 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn maybe_parse_options(
|
||||
&mut self,
|
||||
keyword: Keyword,
|
||||
) -> Result<Option<Vec<SqlOption>>, ParserError> {
|
||||
if let Token::Word(word) = self.peek_token().token {
|
||||
if word.keyword == keyword {
|
||||
return Ok(Some(self.parse_options(keyword)?));
|
||||
}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn parse_options(&mut self, keyword: Keyword) -> Result<Vec<SqlOption>, ParserError> {
|
||||
if self.parse_keyword(keyword) {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
|
@ -6521,19 +6664,22 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_function_definition(&mut self) -> Result<FunctionDefinition, ParserError> {
|
||||
/// Parse the body of a `CREATE FUNCTION` specified as a string.
|
||||
/// e.g. `CREATE FUNCTION ... AS $$ body $$`.
|
||||
fn parse_create_function_body_string(&mut self) -> Result<Expr, ParserError> {
|
||||
let peek_token = self.peek_token();
|
||||
match peek_token.token {
|
||||
Token::DollarQuotedString(value) if dialect_of!(self is PostgreSqlDialect | GenericDialect) =>
|
||||
Token::DollarQuotedString(s) if dialect_of!(self is PostgreSqlDialect | GenericDialect) =>
|
||||
{
|
||||
self.next_token();
|
||||
Ok(FunctionDefinition::DoubleDollarDef(value.value))
|
||||
Ok(Expr::Value(Value::DollarQuotedString(s)))
|
||||
}
|
||||
_ => Ok(FunctionDefinition::SingleQuotedDef(
|
||||
_ => Ok(Expr::Value(Value::SingleQuotedString(
|
||||
self.parse_literal_string()?,
|
||||
)),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a literal string
|
||||
pub fn parse_literal_string(&mut self) -> Result<String, ParserError> {
|
||||
let next_token = self.next_token();
|
||||
|
|
|
@ -1950,6 +1950,145 @@ fn parse_map_access_expr() {
|
|||
bigquery().verified_only_select(sql);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bigquery_create_function() {
|
||||
let sql = concat!(
|
||||
"CREATE OR REPLACE TEMPORARY FUNCTION ",
|
||||
"project1.mydataset.myfunction(x FLOAT64) ",
|
||||
"RETURNS FLOAT64 ",
|
||||
"OPTIONS(x = 'y') ",
|
||||
"AS 42"
|
||||
);
|
||||
|
||||
let stmt = bigquery().verified_stmt(sql);
|
||||
assert_eq!(
|
||||
stmt,
|
||||
Statement::CreateFunction {
|
||||
or_replace: true,
|
||||
temporary: true,
|
||||
if_not_exists: false,
|
||||
name: ObjectName(vec![
|
||||
Ident::new("project1"),
|
||||
Ident::new("mydataset"),
|
||||
Ident::new("myfunction"),
|
||||
]),
|
||||
args: Some(vec![OperateFunctionArg::with_name("x", DataType::Float64),]),
|
||||
return_type: Some(DataType::Float64),
|
||||
function_body: Some(CreateFunctionBody::AsAfterOptions(Expr::Value(number(
|
||||
"42"
|
||||
)))),
|
||||
options: Some(vec![SqlOption {
|
||||
name: Ident::new("x"),
|
||||
value: Expr::Value(Value::SingleQuotedString("y".into())),
|
||||
}]),
|
||||
behavior: None,
|
||||
using: None,
|
||||
language: None,
|
||||
determinism_specifier: None,
|
||||
remote_connection: None,
|
||||
called_on_null: None,
|
||||
parallel: None,
|
||||
}
|
||||
);
|
||||
|
||||
let sqls = [
|
||||
// Arbitrary Options expressions.
|
||||
concat!(
|
||||
"CREATE OR REPLACE TEMPORARY FUNCTION ",
|
||||
"myfunction(a FLOAT64, b INT64, c STRING) ",
|
||||
"RETURNS ARRAY<FLOAT64> ",
|
||||
"OPTIONS(a = [1, 2], b = 'two', c = [('k1', 'v1'), ('k2', 'v2')]) ",
|
||||
"AS ((SELECT 1 FROM mytable))"
|
||||
),
|
||||
// Options after body.
|
||||
concat!(
|
||||
"CREATE OR REPLACE TEMPORARY FUNCTION ",
|
||||
"myfunction(a FLOAT64, b INT64, c STRING) ",
|
||||
"RETURNS ARRAY<FLOAT64> ",
|
||||
"AS ((SELECT 1 FROM mytable)) ",
|
||||
"OPTIONS(a = [1, 2], b = 'two', c = [('k1', 'v1'), ('k2', 'v2')])",
|
||||
),
|
||||
// IF NOT EXISTS
|
||||
concat!(
|
||||
"CREATE OR REPLACE TEMPORARY FUNCTION IF NOT EXISTS ",
|
||||
"myfunction(a FLOAT64, b INT64, c STRING) ",
|
||||
"RETURNS ARRAY<FLOAT64> ",
|
||||
"OPTIONS(a = [1, 2]) ",
|
||||
"AS ((SELECT 1 FROM mytable))"
|
||||
),
|
||||
// No return type.
|
||||
concat!(
|
||||
"CREATE OR REPLACE TEMPORARY FUNCTION ",
|
||||
"myfunction(a FLOAT64, b INT64, c STRING) ",
|
||||
"OPTIONS(a = [1, 2]) ",
|
||||
"AS ((SELECT 1 FROM mytable))"
|
||||
),
|
||||
// With language - body after options
|
||||
concat!(
|
||||
"CREATE OR REPLACE TEMPORARY FUNCTION ",
|
||||
"myfunction(a FLOAT64, b INT64, c STRING) ",
|
||||
"DETERMINISTIC ",
|
||||
"LANGUAGE js ",
|
||||
"OPTIONS(a = [1, 2]) ",
|
||||
"AS \"console.log('hello');\""
|
||||
),
|
||||
// With language - body before options
|
||||
concat!(
|
||||
"CREATE OR REPLACE TEMPORARY FUNCTION ",
|
||||
"myfunction(a FLOAT64, b INT64, c STRING) ",
|
||||
"NOT DETERMINISTIC ",
|
||||
"LANGUAGE js ",
|
||||
"AS \"console.log('hello');\" ",
|
||||
"OPTIONS(a = [1, 2])",
|
||||
),
|
||||
// Remote
|
||||
concat!(
|
||||
"CREATE OR REPLACE TEMPORARY FUNCTION ",
|
||||
"myfunction(a FLOAT64, b INT64, c STRING) ",
|
||||
"RETURNS INT64 ",
|
||||
"REMOTE WITH CONNECTION us.myconnection ",
|
||||
"OPTIONS(a = [1, 2])",
|
||||
),
|
||||
];
|
||||
for sql in sqls {
|
||||
bigquery().verified_stmt(sql);
|
||||
}
|
||||
|
||||
let error_sqls = [
|
||||
(
|
||||
concat!(
|
||||
"CREATE TEMPORARY FUNCTION myfunction() ",
|
||||
"OPTIONS(a = [1, 2]) ",
|
||||
"AS ((SELECT 1 FROM mytable)) ",
|
||||
"OPTIONS(a = [1, 2])",
|
||||
),
|
||||
"Expected end of statement, found: OPTIONS",
|
||||
),
|
||||
(
|
||||
concat!(
|
||||
"CREATE TEMPORARY FUNCTION myfunction() ",
|
||||
"IMMUTABLE ",
|
||||
"AS ((SELECT 1 FROM mytable)) ",
|
||||
),
|
||||
"Expected AS, found: IMMUTABLE",
|
||||
),
|
||||
(
|
||||
concat!(
|
||||
"CREATE TEMPORARY FUNCTION myfunction() ",
|
||||
"AS \"console.log('hello');\" ",
|
||||
"LANGUAGE js ",
|
||||
),
|
||||
"Expected end of statement, found: LANGUAGE",
|
||||
),
|
||||
];
|
||||
for (sql, error) in error_sqls {
|
||||
assert_eq!(
|
||||
ParserError::ParserError(error.to_owned()),
|
||||
bigquery().parse_sql_statements(sql).unwrap_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bigquery_trim() {
|
||||
let real_sql = r#"SELECT customer_id, TRIM(item_price_id, '"', "a") AS item_price_id FROM models_staging.subscriptions"#;
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
use sqlparser::ast::{
|
||||
CreateFunctionBody, CreateFunctionUsing, Expr, Function, FunctionArgumentList,
|
||||
FunctionArguments, FunctionDefinition, Ident, ObjectName, OneOrManyWithParens, SelectItem,
|
||||
Statement, TableFactor, UnaryOperator,
|
||||
FunctionArguments, Ident, ObjectName, OneOrManyWithParens, SelectItem, Statement, TableFactor,
|
||||
UnaryOperator, Value,
|
||||
};
|
||||
use sqlparser::dialect::{GenericDialect, HiveDialect, MsSqlDialect};
|
||||
use sqlparser::parser::{ParserError, ParserOptions};
|
||||
|
@ -296,22 +296,23 @@ fn parse_create_function() {
|
|||
Statement::CreateFunction {
|
||||
temporary,
|
||||
name,
|
||||
params,
|
||||
function_body,
|
||||
using,
|
||||
..
|
||||
} => {
|
||||
assert!(temporary);
|
||||
assert_eq!(name.to_string(), "mydb.myfunc");
|
||||
assert_eq!(
|
||||
params,
|
||||
CreateFunctionBody {
|
||||
as_: Some(FunctionDefinition::SingleQuotedDef(
|
||||
"org.random.class.Name".to_string()
|
||||
)),
|
||||
using: Some(CreateFunctionUsing::Jar(
|
||||
"hdfs://somewhere.com:8020/very/far".to_string()
|
||||
)),
|
||||
..Default::default()
|
||||
}
|
||||
function_body,
|
||||
Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
|
||||
Value::SingleQuotedString("org.random.class.Name".to_string())
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
using,
|
||||
Some(CreateFunctionUsing::Jar(
|
||||
"hdfs://somewhere.com:8020/very/far".to_string()
|
||||
)),
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
|
|
@ -3285,16 +3285,18 @@ fn parse_create_function() {
|
|||
OperateFunctionArg::unnamed(DataType::Integer(None)),
|
||||
]),
|
||||
return_type: Some(DataType::Integer(None)),
|
||||
params: CreateFunctionBody {
|
||||
language: Some("SQL".into()),
|
||||
behavior: Some(FunctionBehavior::Immutable),
|
||||
called_on_null: Some(FunctionCalledOnNull::Strict),
|
||||
parallel: Some(FunctionParallel::Safe),
|
||||
as_: Some(FunctionDefinition::SingleQuotedDef(
|
||||
"select $1 + $2;".into()
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
language: Some("SQL".into()),
|
||||
behavior: Some(FunctionBehavior::Immutable),
|
||||
called_on_null: Some(FunctionCalledOnNull::Strict),
|
||||
parallel: Some(FunctionParallel::Safe),
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
|
||||
Value::SingleQuotedString("select $1 + $2;".into())
|
||||
))),
|
||||
if_not_exists: false,
|
||||
using: None,
|
||||
determinism_specifier: None,
|
||||
options: None,
|
||||
remote_connection: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue