hive: add create function syntax (#496)

Signed-off-by: Maciej Obuchowski <obuchowski.maciej@gmail.com>
This commit is contained in:
Maciej Obuchowski 2022-05-26 23:30:10 +02:00 committed by GitHub
parent 0fa812bd2b
commit cc2559c097
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 128 additions and 2 deletions

View file

@ -981,6 +981,15 @@ pub enum Statement {
location: Option<String>, location: Option<String>,
managed_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> [AS <message>]`
Assert { Assert {
condition: Expr, condition: Expr,
@ -1320,6 +1329,22 @@ impl fmt::Display for Statement {
} }
Ok(()) 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 { Statement::CreateView {
name, name,
or_replace, or_replace,
@ -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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -76,6 +76,7 @@ define_keywords!(
AND, AND,
ANY, ANY,
APPLY, APPLY,
ARCHIVE,
ARE, ARE,
ARRAY, ARRAY,
ARRAY_AGG, ARRAY_AGG,
@ -223,6 +224,7 @@ define_keywords!(
FALSE, FALSE,
FETCH, FETCH,
FIELDS, FIELDS,
FILE,
FILTER, FILTER,
FIRST, FIRST,
FIRST_VALUE, FIRST_VALUE,
@ -277,6 +279,7 @@ define_keywords!(
ISODOW, ISODOW,
ISOLATION, ISOLATION,
ISOYEAR, ISOYEAR,
JAR,
JOIN, JOIN,
JSONFILE, JSONFILE,
JULIAN, JULIAN,

View file

@ -1615,6 +1615,8 @@ impl<'a> Parser<'a> {
self.parse_create_schema() self.parse_create_schema()
} else if self.parse_keyword(Keyword::DATABASE) { } else if self.parse_keyword(Keyword::DATABASE) {
self.parse_create_database() self.parse_create_database()
} else if dialect_of!(self is HiveDialect) && self.parse_keyword(Keyword::FUNCTION) {
self.parse_create_function(temporary)
} else { } else {
self.expected("an object type after CREATE", self.peek_token()) self.expected("an object type after CREATE", self.peek_token())
} }
@ -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( pub fn parse_create_external_table(
&mut self, &mut self,
or_replace: bool, or_replace: bool,

View file

@ -15,8 +15,8 @@
//! Test SQL syntax specific to Hive. The parser based on the generic dialect //! Test SQL syntax specific to Hive. The parser based on the generic dialect
//! is also tested (on the inputs it can handle). //! is also tested (on the inputs it can handle).
use sqlparser::ast::{Ident, ObjectName, SetVariableValue, Statement}; use sqlparser::ast::{CreateFunctionUsing, Ident, ObjectName, SetVariableValue, Statement};
use sqlparser::dialect::HiveDialect; use sqlparser::dialect::{GenericDialect, HiveDialect};
use sqlparser::parser::ParserError; use sqlparser::parser::ParserError;
use sqlparser::test_utils::*; use sqlparser::test_utils::*;
@ -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 { fn hive() -> TestedDialects {
TestedDialects { TestedDialects {
dialects: vec![Box::new(HiveDialect {})], dialects: vec![Box::new(HiveDialect {})],