Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sqlglot/expressions/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,11 @@ class StorageHandlerProperty(Property):
arg_types = {"this": True}


class UsingProperty(Property):
# kind: JAR, FILE, or ARCHIVE; this: the resource path (string literal)
arg_types = {"this": True, "kind": True}


class TemporaryProperty(Property):
arg_types = {"this": False}

Expand Down
1 change: 1 addition & 0 deletions sqlglot/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ class Generator:
exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,
exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,
exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,
exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION,
exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,
exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,
exp.VirtualProperty: exp.Properties.Location.POST_CREATE,
Expand Down
4 changes: 4 additions & 0 deletions sqlglot/generators/hive.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,10 @@ def timetostr_sql(self, expression: exp.TimeToStr) -> str:

return self.func("DATE_FORMAT", this, self.format_time(expression))

def usingproperty_sql(self, expression: exp.UsingProperty) -> str:
kind = expression.args.get("kind")
return f"USING {kind} {self.sql(expression, 'this')}"

Comment on lines +534 to +537

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This should be moved to generator.py, otherwise UsingProperty.sql() will crash.

def fileformatproperty_sql(self, expression: exp.FileFormatProperty) -> str:
if isinstance(expression.this, exp.InputOutputFormat):
this = self.sql(expression, "this")
Expand Down
8 changes: 8 additions & 0 deletions sqlglot/parsers/hive.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class HiveParser(parser.Parser):
"SERDEPROPERTIES": lambda self: exp.SerdeProperties(
expressions=self._parse_wrapped_csv(self._parse_property)
),
"USING": lambda self: self._parse_using_property(),
}

ALTER_PARSERS = {
Expand Down Expand Up @@ -237,6 +238,13 @@ def _parse_alter_table_change(self) -> t.Optional[exp.Expr]:
exp.AlterColumn(this=this, rename_to=column_new, dtype=dtype, comment=comment)
)

def _parse_using_property(self) -> exp.Property:
if self._match_texts(("JAR", "FILE", "ARCHIVE")):
kind = self._prev.text.upper()
return exp.UsingProperty(this=self._parse_string(), kind=kind)

return self._parse_property_assignment(exp.FileFormatProperty)
Comment on lines +241 to +246

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

FWIW, I'm not sure if this is complete, as per Hive's docs:

CREATE FUNCTION [db_name.]function_name AS class_name
  [USING JAR|FILE|ARCHIVE 'file_uri' [, JAR|FILE|ARCHIVE 'file_uri'] ];


def _parse_partition_and_order(
self,
) -> t.Tuple[t.List[exp.Expr], t.Optional[exp.Expr]]:
Expand Down
58 changes: 58 additions & 0 deletions tests/dialects/test_hive.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,3 +1108,61 @@ def test_percentile(self):
quantile_expr.assert_is(exp.Quantile)
quantile_expr.this.assert_is(exp.Column)
quantile_expr.args.get("quantile").assert_is(exp.Literal)

def test_create_function_using(self):
# USING JAR
self.validate_identity(
"CREATE FUNCTION my_func AS 'com.example.MyFunc' USING JAR 'hdfs://path/to/my.jar'"
)

# OR REPLACE TEMPORARY with USING JAR
self.validate_identity(
"CREATE OR REPLACE TEMPORARY FUNCTION some_func AS 'my_jar.SomeFunctionUDF' USING JAR 's3://bucket/my.jar'"
)

# USING FILE
self.validate_identity(
"CREATE FUNCTION my_func AS 'com.example.MyFunc' USING FILE 'hdfs://path/to/file.py'"
)

# USING ARCHIVE
self.validate_identity(
"CREATE FUNCTION my_func AS 'com.example.MyFunc' USING ARCHIVE 'hdfs://path/to/archive.zip'"
)

# Verify the AST node is a Create with UsingProperty
expr = self.parse_one(
"CREATE FUNCTION my_func AS 'com.example.MyFunc' USING JAR 'hdfs://path/to/my.jar'"
)
self.assertIsInstance(expr, exp.Create)
using_prop = expr.find(exp.UsingProperty)
self.assertIsNotNone(using_prop)
self.assertEqual(using_prop.args["kind"], "JAR")
self.assertEqual(using_prop.this.this, "hdfs://path/to/my.jar")

# Verify programmatic construction
create = exp.Create(
this=exp.Table(this=exp.to_identifier("my_func")),
kind="FUNCTION",
expression=exp.Literal.string("com.example.MyFunc"),
properties=exp.Properties(
expressions=[
exp.UsingProperty(this=exp.Literal.string("s3://bucket/new.jar"), kind="JAR")
]
),
)
self.assertEqual(
create.sql(dialect="hive"),
"CREATE FUNCTION my_func AS 'com.example.MyFunc' USING JAR 's3://bucket/new.jar'",
)

# Verify programmatic modification of the JAR path
expr = self.parse_one(
"CREATE FUNCTION my_func AS 'com.example.MyFunc' USING JAR 'hdfs://old/path.jar'"
)
using_prop = expr.find(exp.UsingProperty)
using_prop.set("this", exp.Literal.string("hdfs://new/path.jar"))
self.assertEqual(
expr.sql(dialect="hive"),
"CREATE FUNCTION my_func AS 'com.example.MyFunc' USING JAR 'hdfs://new/path.jar'",
)
Loading