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

Support hive CREATE FUNCTION syntax #496

Merged
merged 1 commit into from May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/ast/mod.rs
Expand Up @@ -981,6 +981,15 @@ pub enum Statement {
location: Option<String>,
managed_location: Option<String>,
},
/// CREATE FUNCTION
///
/// Hive: https://cwiki.apache.org/confluence/display/hive/languagemanual+ddl#LanguageManualDDL-Create/Drop/ReloadFunction
CreateFunction {
temporary: bool,
name: ObjectName,
class_name: String,
using: Option<CreateFunctionUsing>,
},
/// `ASSERT <condition> [AS <message>]`
Assert {
condition: Expr,
Expand Down Expand Up @@ -1320,6 +1329,22 @@ impl fmt::Display for Statement {
}
Ok(())
}
Statement::CreateFunction {
temporary,
name,
class_name,
using,
} => {
write!(
f,
"CREATE {temp}FUNCTION {name} AS '{class_name}'",
temp = if *temporary { "TEMPORARY " } else { "" },
)?;
if let Some(u) = using {
write!(f, " {}", u)?;
}
Ok(())
}
Statement::CreateView {
name,
or_replace,
Expand Down Expand Up @@ -2568,6 +2593,25 @@ impl fmt::Display for DiscardObject {
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CreateFunctionUsing {
Jar(String),
File(String),
Archive(String),
}

impl fmt::Display for CreateFunctionUsing {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "USING ")?;
match self {
CreateFunctionUsing::Jar(uri) => write!(f, "JAR '{uri}'"),
CreateFunctionUsing::File(uri) => write!(f, "FILE '{uri}'"),
CreateFunctionUsing::Archive(uri) => write!(f, "ARCHIVE '{uri}'"),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
3 changes: 3 additions & 0 deletions src/keywords.rs
Expand Up @@ -76,6 +76,7 @@ define_keywords!(
AND,
ANY,
APPLY,
ARCHIVE,
ARE,
ARRAY,
ARRAY_AGG,
Expand Down Expand Up @@ -223,6 +224,7 @@ define_keywords!(
FALSE,
FETCH,
FIELDS,
FILE,
FILTER,
FIRST,
FIRST_VALUE,
Expand Down Expand Up @@ -277,6 +279,7 @@ define_keywords!(
ISODOW,
ISOLATION,
ISOYEAR,
JAR,
JOIN,
JSONFILE,
JULIAN,
Expand Down
38 changes: 38 additions & 0 deletions src/parser.rs
Expand Up @@ -1615,6 +1615,8 @@ impl<'a> Parser<'a> {
self.parse_create_schema()
} else if self.parse_keyword(Keyword::DATABASE) {
self.parse_create_database()
} else if dialect_of!(self is HiveDialect) && self.parse_keyword(Keyword::FUNCTION) {
self.parse_create_function(temporary)
} else {
self.expected("an object type after CREATE", self.peek_token())
}
Expand Down Expand Up @@ -1671,6 +1673,42 @@ impl<'a> Parser<'a> {
})
}

pub fn parse_optional_create_function_using(
&mut self,
) -> Result<Option<CreateFunctionUsing>, ParserError> {
if !self.parse_keyword(Keyword::USING) {
return Ok(None);
};
let keyword =
self.expect_one_of_keywords(&[Keyword::JAR, Keyword::FILE, Keyword::ARCHIVE])?;

let uri = self.parse_literal_string()?;

match keyword {
Keyword::JAR => Ok(Some(CreateFunctionUsing::Jar(uri))),
Keyword::FILE => Ok(Some(CreateFunctionUsing::File(uri))),
Keyword::ARCHIVE => Ok(Some(CreateFunctionUsing::Archive(uri))),
_ => self.expected(
"JAR, FILE or ARCHIVE, got {:?}",
Token::make_keyword(format!("{:?}", keyword).as_str()),
),
}
}

pub fn parse_create_function(&mut self, temporary: bool) -> Result<Statement, ParserError> {
let name = self.parse_object_name()?;
self.expect_keyword(Keyword::AS)?;
let class_name = self.parse_literal_string()?;
let using = self.parse_optional_create_function_using()?;

Ok(Statement::CreateFunction {
temporary,
name,
class_name,
using,
})
}

pub fn parse_create_external_table(
&mut self,
or_replace: bool,
Expand Down
45 changes: 43 additions & 2 deletions tests/sqlparser_hive.rs
Expand Up @@ -15,8 +15,8 @@
//! Test SQL syntax specific to Hive. The parser based on the generic dialect
//! is also tested (on the inputs it can handle).

use sqlparser::ast::{Ident, ObjectName, SetVariableValue, Statement};
use sqlparser::dialect::HiveDialect;
use sqlparser::ast::{CreateFunctionUsing, Ident, ObjectName, SetVariableValue, Statement};
use sqlparser::dialect::{GenericDialect, HiveDialect};
use sqlparser::parser::ParserError;
use sqlparser::test_utils::*;

Expand Down Expand Up @@ -232,6 +232,47 @@ fn set_statement_with_minus() {
)
}

#[test]
fn parse_create_function() {
let sql = "CREATE TEMPORARY FUNCTION mydb.myfunc AS 'org.random.class.Name' USING JAR 'hdfs://somewhere.com:8020/very/far'";
match hive().verified_stmt(sql) {
Statement::CreateFunction {
temporary,
name,
class_name,
using,
} => {
assert!(temporary);
assert_eq!("mydb.myfunc", name.to_string());
assert_eq!("org.random.class.Name", class_name);
assert_eq!(
using,
Some(CreateFunctionUsing::Jar(
"hdfs://somewhere.com:8020/very/far".to_string()
))
)
}
_ => unreachable!(),
}

let generic = TestedDialects {
dialects: vec![Box::new(GenericDialect {})],
};

assert_eq!(
generic.parse_sql_statements(sql).unwrap_err(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

ParserError::ParserError(
"Expected an object type after CREATE, found: FUNCTION".to_string()
)
);

let sql = "CREATE TEMPORARY FUNCTION mydb.myfunc AS 'org.random.class.Name' USING JAR";
assert_eq!(
hive().parse_sql_statements(sql).unwrap_err(),
ParserError::ParserError("Expected literal string, found: EOF".to_string()),
);
}

fn hive() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(HiveDialect {})],
Expand Down