Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ case class SQLFunction(
* Convert the SQL function to a [[CatalogFunction]].
*/
def toCatalogFunction: CatalogFunction = {
val props = sqlFunctionToProps ++ properties
// Persist function metadata (owner, createTime) alongside the SQL function
// body so the values survive a session restart and can be rendered by
// DESCRIBE FUNCTION EXTENDED.
val props = sqlFunctionToProps ++ functionMetadataToProps ++ properties
CatalogFunction(
identifier = name,
className = SQL_FUNCTION_PREFIX,
Expand Down Expand Up @@ -187,6 +190,9 @@ case class SQLFunction(

object SQLFunction {

val SCALAR = "SCALAR"
val TABLE = "TABLE"

/**
* Persisted frozen PATH for SQL function bodies when created with [[SQLConf.PATH_ENABLED]].
* Serialized as a JSON array of path entries (same format as
Expand Down Expand Up @@ -227,21 +233,7 @@ object SQLFunction {
}
val blob = parts.sortBy(_._1).map(_._2).mkString
val props = mapper.readValue(blob, classOf[Map[String, String]])
val isTableFunc = props(IS_TABLE_FUNC).toBoolean
val collation = props.get(COLLATION)
val returnType = parseReturnTypeText(props(RETURN_TYPE), isTableFunc, parser, collation)
SQLFunction(
name = function.identifier,
inputParam = props.get(INPUT_PARAM).map(parseRoutineParam(_, parser, collation)),
returnType = returnType.get,
exprText = props.get(EXPRESSION),
queryText = props.get(QUERY),
comment = props.get(COMMENT),
collation = collation,
deterministic = props.get(DETERMINISTIC).map(_.toBoolean),
containsSQL = props.get(CONTAINS_SQL).map(_.toBoolean),
isTableFunc = isTableFunc,
props.filterNot(_._1.startsWith(SQL_FUNCTION_PREFIX)))
fromProps(props, function.identifier, parser)
} catch {
case e: Exception =>
throw new AnalysisException(
Expand All @@ -253,6 +245,56 @@ object SQLFunction {
}
}

/**
* Convert an [[ExpressionInfo]] into a SQL function.
*/
def fromExpressionInfo(info: ExpressionInfo, parser: ParserInterface): SQLFunction = {
Comment thread
srielau marked this conversation as resolved.
try {
val props = mapper.readValue(info.getUsage, classOf[Map[String, String]])
fromProps(props, FunctionIdentifier(info.getName, Option(info.getDb)), parser)
} catch {
case e: Exception =>
throw new AnalysisException(
errorClass = "CORRUPTED_CATALOG_FUNCTION",
messageParameters = Map(
"identifier" -> s"${info.getDb}.${info.getName}",
"className" -> s"${info.getClassName}"), cause = Some(e)
)
}
}

/**
* Build a [[SQLFunction]] from a deserialized property map and a function identifier.
* Shared by both [[fromCatalogFunction]] and [[fromExpressionInfo]] so all readers
* stay in sync as new properties are added.
*
* `OWNER` is optional and defaults to `None` when missing; `CREATE_TIME` falls back
* to the current wall-clock time so functions persisted before metadata was added
* to the catalog payload still load.
*/
private def fromProps(
props: Map[String, String],
identifier: FunctionIdentifier,
parser: ParserInterface): SQLFunction = {
val isTableFunc = props(IS_TABLE_FUNC).toBoolean
val collation = props.get(COLLATION)
val returnType = parseReturnTypeText(props(RETURN_TYPE), isTableFunc, parser, collation)
SQLFunction(
name = identifier,
inputParam = props.get(INPUT_PARAM).map(parseRoutineParam(_, parser, collation)),
returnType = returnType.get,
exprText = props.get(EXPRESSION),
queryText = props.get(QUERY),
comment = props.get(COMMENT),
collation = collation,
deterministic = props.get(DETERMINISTIC).map(_.toBoolean),
containsSQL = props.get(CONTAINS_SQL).map(_.toBoolean),
isTableFunc = isTableFunc,
properties = props.filterNot(_._1.startsWith(SQL_FUNCTION_PREFIX)),
owner = props.get(OWNER),
createTimeMs = props.get(CREATE_TIME).map(_.toLong).getOrElse(System.currentTimeMillis))
}

def parseDefault(text: String, parser: ParserInterface): Expression = {
parser.parseExpression(text)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2108,14 +2108,15 @@ class SessionCatalog(
overrideIfExists: Boolean,
functionBuilder: Option[FunctionBuilder] = None): Unit = {
val builder = functionBuilder.getOrElse(makeFunctionBuilder(funcDefinition))
registerFunction(funcDefinition, overrideIfExists, functionRegistry, builder)
registerFunction(funcDefinition, overrideIfExists, functionRegistry, builder, info = None)
}

private def registerFunction[T](
funcDefinition: CatalogFunction,
overrideIfExists: Boolean,
registry: FunctionRegistryBase[T],
functionBuilder: FunctionRegistryBase[T]#FunctionBuilder): Unit = {
functionBuilder: FunctionRegistryBase[T]#FunctionBuilder,
info: Option[ExpressionInfo]): Unit = {
val func = funcDefinition.identifier

// Determine the key to use for registration:
Expand Down Expand Up @@ -2146,8 +2147,18 @@ class SessionCatalog(
if (registry.functionExists(identToRegister) && !overrideIfExists) {
throw QueryCompilationErrors.functionAlreadyExistsError(func)
}
val info = makeExprInfoForHiveFunction(funcDefinition)
registry.registerFunction(identToRegister, info, functionBuilder)
// Prefer caller-supplied info (the freshly-registered SQL UDF path passes a
// structured ExpressionInfo). Otherwise reconstruct one: SQL UDFs need the
// structured `usage` blob so DESCRIBE FUNCTION can rehydrate them; hive-style
// functions get the legacy info with `usage = null`.
val resolvedInfo = info.getOrElse {
if (funcDefinition.isUserDefinedFunction) {
UserDefinedFunction.fromCatalogFunction(funcDefinition, parser).toExpressionInfo
} else {
makeExprInfoForHiveFunction(funcDefinition)
}
}
registry.registerFunction(identToRegister, resolvedInfo, functionBuilder)
}

private def makeExprInfoForHiveFunction(func: CatalogFunction): ExpressionInfo = {
Expand Down Expand Up @@ -2279,11 +2290,16 @@ class SessionCatalog(
val info = function.toExpressionInfo
registry.registerFunction(tempIdentifier, info, functionBuilder)
} else {
// We already have the UserDefinedFunction in hand, so skip the
// CatalogFunction -> ExpressionInfo round trip inside `registerFunction`
// and pass the structured ExpressionInfo (with owner/createTime preserved
// at CREATE-time values) directly to the registry.
registerFunction(
function.toCatalogFunction,
overrideIfExists,
registry,
functionBuilder)
functionBuilder,
info = Some(function.toExpressionInfo))
}
}

Expand Down Expand Up @@ -2644,7 +2660,8 @@ class SessionCatalog(
funcMetadata,
overrideIfExists = false,
functionRegistry,
makeFunctionBuilder(funcMetadata))
makeFunctionBuilder(funcMetadata),
info = None)
}
functionRegistry.lookupFunctionBuilder(qualifiedIdent).get
}
Expand Down

This file was deleted.

Loading