Skip to content

Commit

Permalink
More work (#18)
Browse files Browse the repository at this point in the history
* format

* formatting

* working example

* remove example test

* move example to scala-2.12 profile

* no transfer progress

* working sop

* put

* comment

* format
  • Loading branch information
mgyucht committed Sep 1, 2023
1 parent 9f62162 commit ac8e18b
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ private object ProxyDBUtilsImpl {
threadLocal.get()
}

def toPrimitiveClass(clazz: Class[_]): Class[_] = clazz match {
case c if c == classOf[java.lang.Boolean] => java.lang.Boolean.TYPE
case c if c == classOf[java.lang.Byte] => java.lang.Byte.TYPE
case c if c == classOf[java.lang.Character] => java.lang.Character.TYPE
case c if c == classOf[java.lang.Double] => java.lang.Double.TYPE
case c if c == classOf[java.lang.Float] => java.lang.Float.TYPE
case c if c == classOf[java.lang.Integer] => java.lang.Integer.TYPE
case c if c == classOf[java.lang.Long] => java.lang.Long.TYPE
case c if c == classOf[java.lang.Short] => java.lang.Short.TYPE
case _ => clazz
}

def getProxyInstance[T: ClassTag](
backendInstance: AnyRef,
converters: Map[String, MethodCallAdapter] = Map.empty): T = {
Expand All @@ -64,16 +76,28 @@ private object ProxyDBUtilsImpl {
// CommandContext, RunId, FileInfo, MountInfo, etc.). To look up the correct method in DBR, we need to use
// the corresponding types in DBR. Conveniently, handleArgs maps these arguments to the corresponding types
// in DBR. However, getMethod requires exact types and not subtypes, so for methods that take Option[T],
// getMethod must be called with classOf[Option[T]] rather than classOf[Some[T]]. In this case, we fall back
// to the original types defined in the SDK DBUtils interfaces. This does mean that it is impossible to
// support methods with an Option[T] argument and a separate argument with a different type than defined in
// DBR.
val backendMethod =
try {
backendInstance.getClass.getMethod(method.getName, convertedArgs.map(_.getClass): _*)
} catch {
case _: NoSuchMethodException =>
backendInstance.getClass.getMethod(method.getName, method.getParameterTypes: _*)
// getMethod must be called with classOf[Option[T]] rather than classOf[Some[T]], or with primitive classes
// rather than boxed classes. For example:
// def put(path: String, contents: String, overwrite: Boolean = false): Unit
// has a method signature of put(String, String, boolean), but the last argument will be passed as a
// boxed java.lang.Boolean at runtime.
// So, we need to find the method that matches the name and number of arguments, and whose arguments are
// either the same type or a subtype as the arguments passed in, or whose arguments are boxed types and whose
// corresponding parameters are the corresponding primitive types.
val backendMethod = backendInstance.getClass.getMethods
.find { m =>
m.getName == method.getName && m.getParameterTypes.length == convertedArgs.length &&
m.getParameterTypes.zip(convertedArgs).forall { case (paramType, arg) =>
// Either arg's class is the same as paramType, or arg's class is a subtype of paramType, or arg's
// class is a boxed type and paramType is the corresponding primitive type. For example:
paramType.isAssignableFrom(arg.getClass) ||
(paramType.isPrimitive && paramType.isAssignableFrom(toPrimitiveClass(arg.getClass)))
}
}
.getOrElse {
throw new NoSuchMethodException(
s"Method ${method.getName} with arguments ${convertedArgs.mkString(", ")} not found in " +
s"backend instance ${backendInstance.getClass.getName}")
}
val result = backendMethod.invoke(backendInstance, convertedArgs: _*)
converter.convertResult(result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ class ProxyDbfsTest extends AnyFlatSpec {
verify(proxyFs).ls("/")
}

"dbutils.fs.put()" should "call put()" in {
val proxyFs = mock(classOf[DbfsUtils])
val proxyBackend = TestDBUtils(fs = proxyFs)
val proxyDbUtils = new ProxyDBUtilsImpl(proxyBackend)
proxyDbUtils.fs.put("/test", "test")
verify(proxyFs).put("/test", "test")
}

"dbutils.fs.mounts()" should "call mounts() and convert the response" in {
// We mock using the DbfsUtils type, but in DBR, the underlying type is defined
// in DBR.
Expand Down
7 changes: 7 additions & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
<artifactId>databricks-dbutils-scala-examples</artifactId>
<version>0.1.0</version>
<name>DBUtils for Scala Examples</name>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>scala-2.12</id>
Expand Down
13 changes: 13 additions & 0 deletions examples/src/main/resources/log4j.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
log4j.rootLogger=INFO, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{HH:mm} [%-5p] %m%n

# a more detailed PatternLayout: %d [%t] %-5p %c - %m%n

log4j.logger.com.databricks=TRACE

# Uncomment for debug logs from Apache HTTP client
# log4j.logger.org.apache.http=DEBUG
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@ object Example {
} else {
println("Failure!")
}
dbutils.fs.rm("/Volumes/main/default/scrap/file.txt")

val scopes = dbutils.secrets.listScopes()
val secrets = dbutils.secrets.list(scopes.head.name)
val secret = dbutils.secrets.get(scopes.head.name, secrets.head.key)
println(s"Got secret ${secrets.head.key} from scope ${scopes.head.name}")
}
}

0 comments on commit ac8e18b

Please sign in to comment.