Skip to content

Commit cc2559c

Browse files
authored
hive: add create function syntax (#496)
Signed-off-by: Maciej Obuchowski <[email protected]>
1 parent 0fa812b commit cc2559c

File tree

4 files changed

+128
-2
lines changed

4 files changed

+128
-2
lines changed

src/ast/mod.rs

+44
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,15 @@ pub enum Statement {
981981
location: Option<String>,
982982
managed_location: Option<String>,
983983
},
984+
/// CREATE FUNCTION
985+
///
986+
/// Hive: https://cwiki.apache.org/confluence/display/hive/languagemanual+ddl#LanguageManualDDL-Create/Drop/ReloadFunction
987+
CreateFunction {
988+
temporary: bool,
989+
name: ObjectName,
990+
class_name: String,
991+
using: Option<CreateFunctionUsing>,
992+
},
984993
/// `ASSERT <condition> [AS <message>]`
985994
Assert {
986995
condition: Expr,
@@ -1320,6 +1329,22 @@ impl fmt::Display for Statement {
13201329
}
13211330
Ok(())
13221331
}
1332+
Statement::CreateFunction {
1333+
temporary,
1334+
name,
1335+
class_name,
1336+
using,
1337+
} => {
1338+
write!(
1339+
f,
1340+
"CREATE {temp}FUNCTION {name} AS '{class_name}'",
1341+
temp = if *temporary { "TEMPORARY " } else { "" },
1342+
)?;
1343+
if let Some(u) = using {
1344+
write!(f, " {}", u)?;
1345+
}
1346+
Ok(())
1347+
}
13231348
Statement::CreateView {
13241349
name,
13251350
or_replace,
@@ -2568,6 +2593,25 @@ impl fmt::Display for DiscardObject {
25682593
}
25692594
}
25702595

2596+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2597+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2598+
pub enum CreateFunctionUsing {
2599+
Jar(String),
2600+
File(String),
2601+
Archive(String),
2602+
}
2603+
2604+
impl fmt::Display for CreateFunctionUsing {
2605+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2606+
write!(f, "USING ")?;
2607+
match self {
2608+
CreateFunctionUsing::Jar(uri) => write!(f, "JAR '{uri}'"),
2609+
CreateFunctionUsing::File(uri) => write!(f, "FILE '{uri}'"),
2610+
CreateFunctionUsing::Archive(uri) => write!(f, "ARCHIVE '{uri}'"),
2611+
}
2612+
}
2613+
}
2614+
25712615
#[cfg(test)]
25722616
mod tests {
25732617
use super::*;

src/keywords.rs

+3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ define_keywords!(
7676
AND,
7777
ANY,
7878
APPLY,
79+
ARCHIVE,
7980
ARE,
8081
ARRAY,
8182
ARRAY_AGG,
@@ -223,6 +224,7 @@ define_keywords!(
223224
FALSE,
224225
FETCH,
225226
FIELDS,
227+
FILE,
226228
FILTER,
227229
FIRST,
228230
FIRST_VALUE,
@@ -277,6 +279,7 @@ define_keywords!(
277279
ISODOW,
278280
ISOLATION,
279281
ISOYEAR,
282+
JAR,
280283
JOIN,
281284
JSONFILE,
282285
JULIAN,

src/parser.rs

+38
Original file line numberDiff line numberDiff line change
@@ -1615,6 +1615,8 @@ impl<'a> Parser<'a> {
16151615
self.parse_create_schema()
16161616
} else if self.parse_keyword(Keyword::DATABASE) {
16171617
self.parse_create_database()
1618+
} else if dialect_of!(self is HiveDialect) && self.parse_keyword(Keyword::FUNCTION) {
1619+
self.parse_create_function(temporary)
16181620
} else {
16191621
self.expected("an object type after CREATE", self.peek_token())
16201622
}
@@ -1671,6 +1673,42 @@ impl<'a> Parser<'a> {
16711673
})
16721674
}
16731675

1676+
pub fn parse_optional_create_function_using(
1677+
&mut self,
1678+
) -> Result<Option<CreateFunctionUsing>, ParserError> {
1679+
if !self.parse_keyword(Keyword::USING) {
1680+
return Ok(None);
1681+
};
1682+
let keyword =
1683+
self.expect_one_of_keywords(&[Keyword::JAR, Keyword::FILE, Keyword::ARCHIVE])?;
1684+
1685+
let uri = self.parse_literal_string()?;
1686+
1687+
match keyword {
1688+
Keyword::JAR => Ok(Some(CreateFunctionUsing::Jar(uri))),
1689+
Keyword::FILE => Ok(Some(CreateFunctionUsing::File(uri))),
1690+
Keyword::ARCHIVE => Ok(Some(CreateFunctionUsing::Archive(uri))),
1691+
_ => self.expected(
1692+
"JAR, FILE or ARCHIVE, got {:?}",
1693+
Token::make_keyword(format!("{:?}", keyword).as_str()),
1694+
),
1695+
}
1696+
}
1697+
1698+
pub fn parse_create_function(&mut self, temporary: bool) -> Result<Statement, ParserError> {
1699+
let name = self.parse_object_name()?;
1700+
self.expect_keyword(Keyword::AS)?;
1701+
let class_name = self.parse_literal_string()?;
1702+
let using = self.parse_optional_create_function_using()?;
1703+
1704+
Ok(Statement::CreateFunction {
1705+
temporary,
1706+
name,
1707+
class_name,
1708+
using,
1709+
})
1710+
}
1711+
16741712
pub fn parse_create_external_table(
16751713
&mut self,
16761714
or_replace: bool,

tests/sqlparser_hive.rs

+43-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
//! Test SQL syntax specific to Hive. The parser based on the generic dialect
1616
//! is also tested (on the inputs it can handle).
1717
18-
use sqlparser::ast::{Ident, ObjectName, SetVariableValue, Statement};
19-
use sqlparser::dialect::HiveDialect;
18+
use sqlparser::ast::{CreateFunctionUsing, Ident, ObjectName, SetVariableValue, Statement};
19+
use sqlparser::dialect::{GenericDialect, HiveDialect};
2020
use sqlparser::parser::ParserError;
2121
use sqlparser::test_utils::*;
2222

@@ -232,6 +232,47 @@ fn set_statement_with_minus() {
232232
)
233233
}
234234

235+
#[test]
236+
fn parse_create_function() {
237+
let sql = "CREATE TEMPORARY FUNCTION mydb.myfunc AS 'org.random.class.Name' USING JAR 'hdfs://somewhere.com:8020/very/far'";
238+
match hive().verified_stmt(sql) {
239+
Statement::CreateFunction {
240+
temporary,
241+
name,
242+
class_name,
243+
using,
244+
} => {
245+
assert!(temporary);
246+
assert_eq!("mydb.myfunc", name.to_string());
247+
assert_eq!("org.random.class.Name", class_name);
248+
assert_eq!(
249+
using,
250+
Some(CreateFunctionUsing::Jar(
251+
"hdfs://somewhere.com:8020/very/far".to_string()
252+
))
253+
)
254+
}
255+
_ => unreachable!(),
256+
}
257+
258+
let generic = TestedDialects {
259+
dialects: vec![Box::new(GenericDialect {})],
260+
};
261+
262+
assert_eq!(
263+
generic.parse_sql_statements(sql).unwrap_err(),
264+
ParserError::ParserError(
265+
"Expected an object type after CREATE, found: FUNCTION".to_string()
266+
)
267+
);
268+
269+
let sql = "CREATE TEMPORARY FUNCTION mydb.myfunc AS 'org.random.class.Name' USING JAR";
270+
assert_eq!(
271+
hive().parse_sql_statements(sql).unwrap_err(),
272+
ParserError::ParserError("Expected literal string, found: EOF".to_string()),
273+
);
274+
}
275+
235276
fn hive() -> TestedDialects {
236277
TestedDialects {
237278
dialects: vec![Box::new(HiveDialect {})],

0 commit comments

Comments
 (0)