Skip to content

Commit

Permalink
hive: add create function syntax
Browse files Browse the repository at this point in the history
Signed-off-by: Maciej Obuchowski <obuchowski.maciej@gmail.com>
  • Loading branch information
mobuchowski committed May 26, 2022
1 parent 0fa812b commit 3613238
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 2 deletions.
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(),
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

0 comments on commit 3613238

Please sign in to comment.