Support CREATE FUNCTION for BigQuery (#1253)

This commit is contained in:
Ifeanyi Ubah 2024-05-30 18:18:41 +02:00 committed by GitHub
parent d5faf3c54b
commit 792e389baa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 557 additions and 173 deletions

View file

@ -2454,14 +2454,64 @@ pub enum Statement {
/// Supported variants: /// Supported variants:
/// 1. [Hive](https://cwiki.apache.org/confluence/display/hive/languagemanual+ddl#LanguageManualDDL-Create/Drop/ReloadFunction) /// 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) /// 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 { CreateFunction {
or_replace: bool, or_replace: bool,
temporary: bool, temporary: bool,
if_not_exists: bool,
name: ObjectName, name: ObjectName,
args: Option<Vec<OperateFunctionArg>>, args: Option<Vec<OperateFunctionArg>>,
return_type: Option<DataType>, return_type: Option<DataType>,
/// Optional parameters. /// The expression that defines the function.
params: CreateFunctionBody, ///
/// 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 /// ```sql
/// CREATE PROCEDURE /// CREATE PROCEDURE
@ -3152,16 +3202,26 @@ impl fmt::Display for Statement {
Statement::CreateFunction { Statement::CreateFunction {
or_replace, or_replace,
temporary, temporary,
if_not_exists,
name, name,
args, args,
return_type, return_type,
params, function_body,
language,
behavior,
called_on_null,
parallel,
using,
determinism_specifier,
options,
remote_connection,
} => { } => {
write!( write!(
f, f,
"CREATE {or_replace}{temp}FUNCTION {name}", "CREATE {or_replace}{temp}FUNCTION {if_not_exists}{name}",
temp = if *temporary { "TEMPORARY " } else { "" }, temp = if *temporary { "TEMPORARY " } else { "" },
or_replace = if *or_replace { "OR REPLACE " } 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 { if let Some(args) = args {
write!(f, "({})", display_comma_separated(args))?; write!(f, "({})", display_comma_separated(args))?;
@ -3169,7 +3229,43 @@ impl fmt::Display for Statement {
if let Some(return_type) = return_type { if let Some(return_type) = return_type {
write!(f, " RETURNS {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(()) Ok(())
} }
Statement::CreateProcedure { 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)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum FunctionDefinition { pub enum FunctionDeterminismSpecifier {
SingleQuotedDef(String), Deterministic,
DoubleDollarDef(String), NotDeterministic,
} }
impl fmt::Display for FunctionDefinition { impl fmt::Display for FunctionDeterminismSpecifier {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
FunctionDefinition::SingleQuotedDef(s) => write!(f, "'{s}'")?, FunctionDeterminismSpecifier::Deterministic => {
FunctionDefinition::DoubleDollarDef(s) => write!(f, "$${s}$$")?, 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) /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
/// for more details /// [Postgres]: https://www.postgresql.org/docs/15/sql-createfunction.html
#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct CreateFunctionBody { pub enum CreateFunctionBody {
/// LANGUAGE lang_name /// A function body expression using the 'AS' keyword and shows up
pub language: Option<Ident>, /// before any `OPTIONS` clause.
/// 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'
/// ///
/// Note that Hive's `AS class_name` is also parsed here. /// Example:
pub as_: Option<FunctionDefinition>, /// ```sql
/// RETURN expression /// CREATE FUNCTION myfunc(x FLOAT64, y FLOAT64) RETURNS FLOAT64
pub return_: Option<Expr>, /// AS (x * y)
/// USING ... (Hive only) /// OPTIONS(description="desc");
pub using: Option<CreateFunctionUsing>, /// ```
} ///
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
impl fmt::Display for CreateFunctionBody { AsBeforeOptions(Expr),
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { /// A function body expression using the 'AS' keyword and shows up
if let Some(language) = &self.language { /// after any `OPTIONS` clause.
write!(f, " LANGUAGE {language}")?; ///
} /// Example:
if let Some(behavior) = &self.behavior { /// ```sql
write!(f, " {behavior}")?; /// CREATE FUNCTION myfunc(x FLOAT64, y FLOAT64) RETURNS FLOAT64
} /// OPTIONS(description="desc")
if let Some(called_on_null) = &self.called_on_null { /// AS (x * y);
write!(f, " {called_on_null}")?; /// ```
} ///
if let Some(parallel) = &self.parallel { /// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
write!(f, " {parallel}")?; AsAfterOptions(Expr),
} /// Function body expression using the 'RETURN' keyword.
if let Some(definition) = &self.as_ { ///
write!(f, " AS {definition}")?; /// Example:
} /// ```sql
if let Some(expr) = &self.return_ { /// CREATE FUNCTION myfunc(a INTEGER, IN b INTEGER = 1) RETURNS INTEGER
write!(f, " RETURN {expr}")?; /// LANGUAGE SQL
} /// RETURN a + b;
if let Some(using) = &self.using { /// ```
write!(f, " {using}")?; ///
} /// [Postgres]: https://www.postgresql.org/docs/current/sql-createfunction.html
Ok(()) Return(Expr),
}
} }
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]

View file

@ -572,6 +572,7 @@ define_keywords!(
RELATIVE, RELATIVE,
RELAY, RELAY,
RELEASE, RELEASE,
REMOTE,
RENAME, RENAME,
REORG, REORG,
REPAIR, REPAIR,

View file

@ -3590,95 +3590,53 @@ impl<'a> Parser<'a> {
temporary: bool, temporary: bool,
) -> Result<Statement, ParserError> { ) -> Result<Statement, ParserError> {
if dialect_of!(self is HiveDialect) { if dialect_of!(self is HiveDialect) {
let name = self.parse_object_name(false)?; self.parse_hive_create_function(or_replace, temporary)
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,
})
} else if dialect_of!(self is PostgreSqlDialect | GenericDialect) { } else if dialect_of!(self is PostgreSqlDialect | GenericDialect) {
let name = self.parse_object_name(false)?; self.parse_postgres_create_function(or_replace, temporary)
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,
})
} else if dialect_of!(self is DuckDbDialect) { } else if dialect_of!(self is DuckDbDialect) {
self.parse_create_macro(or_replace, temporary) self.parse_create_macro(or_replace, temporary)
} else if dialect_of!(self is BigQueryDialect) {
self.parse_bigquery_create_function(or_replace, temporary)
} else { } else {
self.prev_token(); self.prev_token();
self.expected("an object type after CREATE", self.peek_token()) self.expected("an object type after CREATE", self.peek_token())
} }
} }
fn parse_function_arg(&mut self) -> Result<OperateFunctionArg, ParserError> { /// Parse `CREATE FUNCTION` for [Postgres]
let mode = if self.parse_keyword(Keyword::IN) { ///
Some(ArgMode::In) /// [Postgres]: https://www.postgresql.org/docs/15/sql-createfunction.html
} else if self.parse_keyword(Keyword::OUT) { fn parse_postgres_create_function(
Some(ArgMode::Out) &mut self,
} else if self.parse_keyword(Keyword::INOUT) { or_replace: bool,
Some(ArgMode::InOut) 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 { } else {
None None
}; };
// parse: [ argname ] argtype #[derive(Default)]
let mut name = None; struct Body {
let mut data_type = self.parse_data_type()?; language: Option<Ident>,
if let DataType::Custom(n, _) = &data_type { behavior: Option<FunctionBehavior>,
// the first token is actually a name function_body: Option<CreateFunctionBody>,
name = Some(n.0[0].clone()); called_on_null: Option<FunctionCalledOnNull>,
data_type = self.parse_data_type()?; parallel: Option<FunctionParallel>,
} }
let mut body = Body::default();
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();
loop { loop {
fn ensure_not_set<T>(field: &Option<T>, name: &str) -> Result<(), ParserError> { fn ensure_not_set<T>(field: &Option<T>, name: &str) -> Result<(), ParserError> {
if field.is_some() { if field.is_some() {
@ -3689,8 +3647,10 @@ impl<'a> Parser<'a> {
Ok(()) Ok(())
} }
if self.parse_keyword(Keyword::AS) { if self.parse_keyword(Keyword::AS) {
ensure_not_set(&body.as_, "AS")?; ensure_not_set(&body.function_body, "AS")?;
body.as_ = Some(self.parse_function_definition()?); body.function_body = Some(CreateFunctionBody::AsBeforeOptions(
self.parse_create_function_body_string()?,
));
} else if self.parse_keyword(Keyword::LANGUAGE) { } else if self.parse_keyword(Keyword::LANGUAGE) {
ensure_not_set(&body.language, "LANGUAGE")?; ensure_not_set(&body.language, "LANGUAGE")?;
body.language = Some(self.parse_identifier(false)?); 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()); return self.expected("one of UNSAFE | RESTRICTED | SAFE", self.peek_token());
} }
} else if self.parse_keyword(Keyword::RETURN) { } else if self.parse_keyword(Keyword::RETURN) {
ensure_not_set(&body.return_, "RETURN")?; ensure_not_set(&body.function_body, "RETURN")?;
body.return_ = Some(self.parse_expr()?); body.function_body = Some(CreateFunctionBody::Return(self.parse_expr()?));
} else { } 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( pub fn parse_create_macro(
@ -3893,12 +4027,9 @@ impl<'a> Parser<'a> {
}; };
if dialect_of!(self is BigQueryDialect | GenericDialect) { if dialect_of!(self is BigQueryDialect | GenericDialect) {
if let Token::Word(word) = self.peek_token().token { if let Some(opts) = self.maybe_parse_options(Keyword::OPTIONS)? {
if word.keyword == Keyword::OPTIONS { if !opts.is_empty() {
let opts = self.parse_options(Keyword::OPTIONS)?; options = CreateTableOptions::Options(opts);
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> { pub fn parse_options(&mut self, keyword: Keyword) -> Result<Vec<SqlOption>, ParserError> {
if self.parse_keyword(keyword) { if self.parse_keyword(keyword) {
self.expect_token(&Token::LParen)?; 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(); let peek_token = self.peek_token();
match peek_token.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(); 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()?, self.parse_literal_string()?,
)), ))),
} }
} }
/// Parse a literal string /// Parse a literal string
pub fn parse_literal_string(&mut self) -> Result<String, ParserError> { pub fn parse_literal_string(&mut self) -> Result<String, ParserError> {
let next_token = self.next_token(); let next_token = self.next_token();

View file

@ -1950,6 +1950,145 @@ fn parse_map_access_expr() {
bigquery().verified_only_select(sql); 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] #[test]
fn test_bigquery_trim() { fn test_bigquery_trim() {
let real_sql = r#"SELECT customer_id, TRIM(item_price_id, '"', "a") AS item_price_id FROM models_staging.subscriptions"#; let real_sql = r#"SELECT customer_id, TRIM(item_price_id, '"', "a") AS item_price_id FROM models_staging.subscriptions"#;

View file

@ -17,8 +17,8 @@
use sqlparser::ast::{ use sqlparser::ast::{
CreateFunctionBody, CreateFunctionUsing, Expr, Function, FunctionArgumentList, CreateFunctionBody, CreateFunctionUsing, Expr, Function, FunctionArgumentList,
FunctionArguments, FunctionDefinition, Ident, ObjectName, OneOrManyWithParens, SelectItem, FunctionArguments, Ident, ObjectName, OneOrManyWithParens, SelectItem, Statement, TableFactor,
Statement, TableFactor, UnaryOperator, UnaryOperator, Value,
}; };
use sqlparser::dialect::{GenericDialect, HiveDialect, MsSqlDialect}; use sqlparser::dialect::{GenericDialect, HiveDialect, MsSqlDialect};
use sqlparser::parser::{ParserError, ParserOptions}; use sqlparser::parser::{ParserError, ParserOptions};
@ -296,22 +296,23 @@ fn parse_create_function() {
Statement::CreateFunction { Statement::CreateFunction {
temporary, temporary,
name, name,
params, function_body,
using,
.. ..
} => { } => {
assert!(temporary); assert!(temporary);
assert_eq!(name.to_string(), "mydb.myfunc"); assert_eq!(name.to_string(), "mydb.myfunc");
assert_eq!( assert_eq!(
params, function_body,
CreateFunctionBody { Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
as_: Some(FunctionDefinition::SingleQuotedDef( Value::SingleQuotedString("org.random.class.Name".to_string())
"org.random.class.Name".to_string() )))
)), );
using: Some(CreateFunctionUsing::Jar( assert_eq!(
"hdfs://somewhere.com:8020/very/far".to_string() using,
)), Some(CreateFunctionUsing::Jar(
..Default::default() "hdfs://somewhere.com:8020/very/far".to_string()
} )),
) )
} }
_ => unreachable!(), _ => unreachable!(),

View file

@ -3285,16 +3285,18 @@ fn parse_create_function() {
OperateFunctionArg::unnamed(DataType::Integer(None)), OperateFunctionArg::unnamed(DataType::Integer(None)),
]), ]),
return_type: Some(DataType::Integer(None)), return_type: Some(DataType::Integer(None)),
params: CreateFunctionBody { language: Some("SQL".into()),
language: Some("SQL".into()), behavior: Some(FunctionBehavior::Immutable),
behavior: Some(FunctionBehavior::Immutable), called_on_null: Some(FunctionCalledOnNull::Strict),
called_on_null: Some(FunctionCalledOnNull::Strict), parallel: Some(FunctionParallel::Safe),
parallel: Some(FunctionParallel::Safe), function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
as_: Some(FunctionDefinition::SingleQuotedDef( Value::SingleQuotedString("select $1 + $2;".into())
"select $1 + $2;".into() ))),
)), if_not_exists: false,
..Default::default() using: None,
}, determinism_specifier: None,
options: None,
remote_connection: None,
} }
); );
} }