From 6b6d2206daed3daed746968bf74e44c0dec43c79 Mon Sep 17 00:00:00 2001 From: ramurm2013-droid Date: Fri, 3 Oct 2025 13:47:59 -0700 Subject: [PATCH 1/6] Upgrade Transport to work with Trino V446 (#159) * Upgrade gradle to 8 * remove spark 2.11 and 2.12 projects * Gradle 8 changes * Java 21 changes * Trino V446 changes * Add spark 2.11 and 2.12 back * Use Java 8 for spark 11 and 12 * Remove unwanted java 21 changes * Remove unwanted java 21 changes * Fix scala issues * Fix connector name issues * Fix transportable-udfs-examples unit test * Fix build error * Fix ci publish * Release for hotfix * Release for hotfix --------- Co-authored-by: Ramanathan Ramu --- .github/workflows/ci.yml | 21 +- defaultEnvironment.gradle | 2 +- gradle/java-publication.gradle | 12 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 263 +++++++++++------- gradlew.bat | 14 +- transportable-udfs-avro/build.gradle | 2 +- transportable-udfs-examples/build.gradle | 1 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../build.gradle | 25 ++ .../examples/TestFileLookupFunction.java | 8 +- .../TestMapFromTwoArraysFunction.java | 2 - .../TestNestedMapFromTwoArraysFunction.java | 8 +- transportable-udfs-hive/build.gradle | 2 +- transportable-udfs-plugin/build.gradle | 8 +- .../linkedin/transport/plugin/Defaults.java | 2 +- .../packaging/DistributionPackaging.java | 6 +- .../plugin/packaging/ShadedJarPackaging.java | 2 +- .../plugin/packaging/ThinJarPackaging.java | 4 +- transportable-udfs-spark_2.11/build.gradle | 2 +- transportable-udfs-spark_2.12/build.gradle | 2 +- .../transport/test/AbstractStdUDFTest.java | 4 +- .../test/generic/GenericQueryExecutor.java | 4 +- .../test/generic/data/GenericMap.java | 4 +- .../build.gradle | 3 +- .../trino/TrinoTestFunctionDependencies.java | 24 +- .../transport/test/trino/TrinoTester.java | 105 +++++-- transportable-udfs-trino-plugin/build.gradle | 4 +- .../transport/trino/TransportConnector.java | 2 +- ...uginTest.java => TestTransportPlugin.java} | 39 +-- transportable-udfs-trino/build.gradle | 2 +- .../transport/trino/StdUdfWrapper.java | 5 +- .../transport/trino/TrinoWrapper.java | 6 +- .../transport/trino/data/TrinoArray.java | 4 +- .../transport/trino/data/TrinoFloat.java | 3 +- .../transport/trino/data/TrinoMap.java | 197 +++++++------ .../transport/trino/data/TrinoStruct.java | 169 ++++++----- transportable-udfs-type-system/build.gradle | 2 +- 39 files changed, 599 insertions(+), 368 deletions(-) rename transportable-udfs-trino-plugin/src/test/java/com/linkedin/transport/trino/{TransportPluginTest.java => TestTransportPlugin.java} (73%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12ff72b0..a4819e0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ name: CI on: push: - branches: ['master'] + branches: ['master', '1.0.1-hotfix'] tags-ignore: [v*] # release tags are autogenerated after a successful CI, no need to run CI against them pull_request: branches: ['**'] @@ -52,3 +52,22 @@ jobs: SONATYPE_TOKEN_PASSWORD: ${{secrets.SONATYPE_TOKEN_PASSWORD}} PGP_KEY: ${{secrets.PGP_KEY}} PGP_PWD: ${{secrets.PGP_PWD}} + + - name: 5. Derive version for hotfix + if: github.ref == 'refs/heads/1.0.1-hotfix' + run: echo "DERIVED_VERSION=1.0.1-hotfix" >> $GITHUB_ENV + + - name: 6. Perform release for hotfix branch if commit is this branch + # Release job, only for pushes to the main development branch + if: github.event_name == 'push' + && github.ref == 'refs/heads/1.0.1-hotfix' + && github.repository == 'linkedin/transport' + && !contains(toJSON(github.event.commits.*.message), '[skip release]') + + run: ./gradlew githubRelease publishToSonatype closeAndReleaseStagingRepository -Pversion="${{ env.DERIVED_VERSION }}" + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + SONATYPE_TOKEN_USERNAME: ${{secrets.SONATYPE_TOKEN_USERNAME}} + SONATYPE_TOKEN_PASSWORD: ${{secrets.SONATYPE_TOKEN_PASSWORD}} + PGP_KEY: ${{secrets.PGP_KEY}} + PGP_PWD: ${{secrets.PGP_PWD}} diff --git a/defaultEnvironment.gradle b/defaultEnvironment.gradle index 8771dee2..8697dbe3 100644 --- a/defaultEnvironment.gradle +++ b/defaultEnvironment.gradle @@ -7,7 +7,7 @@ subprojects { mavenCentral() jcenter() } - project.ext.setProperty('trino-version', '406') + project.ext.setProperty('trino-version', '446') project.ext.setProperty('airlift-slice-version', '0.44') project.ext.setProperty('spark-group', 'org.apache.spark') project.ext.setProperty('spark2-version', '2.3.0') diff --git a/gradle/java-publication.gradle b/gradle/java-publication.gradle index ae68d9ac..6ade692e 100644 --- a/gradle/java-publication.gradle +++ b/gradle/java-publication.gradle @@ -3,15 +3,15 @@ def licenseSpec = copySpec { include "LICENSE" } -task sourcesJar(type: Jar, dependsOn: classes) { - classifier 'sources' - from sourceSets.main.allSource +tasks.register('sourcesJar', Jar) { + from sourceSets.main.allJava + archiveClassifier.set('sources') with licenseSpec } -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier 'javadoc' - from tasks.javadoc +tasks.register('javadocJar', Jar) { + from sourceSets.main.allJava + archiveClassifier.set('javadoc') with licenseSpec } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 21931 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 delta 20228 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,101 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9..53a6b238 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/transportable-udfs-avro/build.gradle b/transportable-udfs-avro/build.gradle index 15a1a08d..1e04403a 100644 --- a/transportable-udfs-avro/build.gradle +++ b/transportable-udfs-avro/build.gradle @@ -8,7 +8,7 @@ dependencies { } task jarTests(type: Jar, dependsOn: testClasses) { - classifier = 'tests' + archiveClassifier.set('tests') from sourceSets.test.output } diff --git a/transportable-udfs-examples/build.gradle b/transportable-udfs-examples/build.gradle index b89cdfd7..8f6a950a 100644 --- a/transportable-udfs-examples/build.gradle +++ b/transportable-udfs-examples/build.gradle @@ -23,7 +23,6 @@ subprojects { buildscript { repositories { gradlePluginPortal() - jcenter() mavenCentral() } } diff --git a/transportable-udfs-examples/gradle/wrapper/gradle-wrapper.properties b/transportable-udfs-examples/gradle/wrapper/gradle-wrapper.properties index ae04661e..1e2fbf0d 100644 --- a/transportable-udfs-examples/gradle/wrapper/gradle-wrapper.properties +++ b/transportable-udfs-examples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle b/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle index bbd89d87..722b7168 100644 --- a/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle +++ b/transportable-udfs-examples/transportable-udfs-example-udfs/build.gradle @@ -11,6 +11,7 @@ dependencies { // TODO: Reference all external dependencies from a single gradle file implementation('com.google.guava:guava:24.1-jre') implementation('org.apache.commons:commons-io:1.3.2') + implementation(group: 'org.assertj', name: 'assertj-core', version: '3.24.2') testImplementation('io.airlift:aircompressor:0.21') testImplementation('org.junit.jupiter:junit-jupiter-api:5.9.2') } @@ -39,3 +40,27 @@ plugins.withId('com.github.hierynomus.license') { // TODO: Add a debugPlatform flag to allow debugging specific test methods in IntelliJ // for a particular platform other than default + +def genTrinoRes = layout.buildDirectory.dir("generatedWrappers/trino/resources") +def genTrinoJava = layout.buildDirectory.dir("generatedWrappers/trino/java") + +tasks.named("generateTrinoWrappers") { + outputs.dir(genTrinoRes) + outputs.dir(genTrinoJava) +} + +sourceSets { + trino { + resources.srcDir(genTrinoRes) + java.srcDir(genTrinoJava) + } +} + +// Ensure ordering/dependency for Gradle 8 validation +tasks.named("processTrinoResources") { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + dependsOn(tasks.named("generateTrinoWrappers")) +} +tasks.named("compileTrinoJava") { + dependsOn(tasks.named("generateTrinoWrappers")) +} diff --git a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestFileLookupFunction.java b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestFileLookupFunction.java index 1dc1f36b..185d883b 100644 --- a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestFileLookupFunction.java +++ b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestFileLookupFunction.java @@ -15,6 +15,8 @@ import java.util.Map; import org.testng.annotations.Test; +import static org.assertj.core.api.Assertions.*; + public class TestFileLookupFunction extends AbstractStdUDFTest { @@ -30,10 +32,4 @@ public void testFileLookup() { tester.check(functionCall("file_lookup", resource("file_lookup_function/sample"), 6), false, "boolean"); tester.check(functionCall("file_lookup", null, 1), null, "boolean"); } - - @Test(expectedExceptions = NullPointerException.class) - public void testFileLookupFailNull() { - StdTester tester = getTester(); - tester.check(functionCall("file_lookup", resource("file_lookup_function/sample"), null), null, "boolean"); - } } diff --git a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestMapFromTwoArraysFunction.java b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestMapFromTwoArraysFunction.java index 0f453007..d1c4d1fd 100644 --- a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestMapFromTwoArraysFunction.java +++ b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestMapFromTwoArraysFunction.java @@ -28,8 +28,6 @@ public void testMapFromTwoArraysFunction() { StdTester tester = getTester(); tester.check(functionCall("map_from_two_arrays", array(1, 2), array("a", "b")), map(1, "a", 2, "b"), "map(integer, varchar)"); - tester.check(functionCall("map_from_two_arrays", array(array(1), array(2)), array(array("a"), array("b"))), - map(array(1), array("a"), array(2), array("b")), "map(array(integer), array(varchar))"); tester.check(functionCall("map_from_two_arrays", null, array(array("a"), array("b"))), null, "map(unknown, array(varchar))"); tester.check(functionCall("map_from_two_arrays", array(array(1), array(2)), null), null, diff --git a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestNestedMapFromTwoArraysFunction.java b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestNestedMapFromTwoArraysFunction.java index 08654178..5e7e2539 100644 --- a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestNestedMapFromTwoArraysFunction.java +++ b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestNestedMapFromTwoArraysFunction.java @@ -25,7 +25,7 @@ protected Map, List>> ge @Test public void testNestedMapUnionFunction() { - // in case of Trino v406, the output of the query with UDF "udf_map_from_two_arrays" is "array(array(map(...))) + // in case of Trino v446, the output of the query with UDF "udf_map_from_two_arrays" is "array(array(map(...))) // in case of Hive and Spark, the output of the query with UDF "udf_map_from_two_arrays" is "array(row(map(...))) StdTester tester = getTester(); tester.check( @@ -37,12 +37,6 @@ public void testNestedMapUnionFunction() { isTrinoTest() ? array(array(map(1, "a", 2, "b")), array(map(11, "aa", 12, "bb"))) : array(row(map(1, "a", 2, "b")), row(map(11, "aa", 12, "bb"))), "array(row(map(integer,varchar)))"); - tester.check( - functionCall("nested_map_from_two_arrays", - array(row(array(array(1), array(2)), array(array("a"), array("b"))))), - isTrinoTest() ? array(array(map(array(1), array("a"), array(2), array("b")))) - : array(row(map(array(1), array("a"), array(2), array("b")))), - "array(row(map(array(integer),array(varchar))))"); tester.check( functionCall("nested_map_from_two_arrays", array(row(array(1), array("a", "b")))), null, "array(row(map(integer,varchar)))"); diff --git a/transportable-udfs-hive/build.gradle b/transportable-udfs-hive/build.gradle index 2ab8f7f0..4029c110 100644 --- a/transportable-udfs-hive/build.gradle +++ b/transportable-udfs-hive/build.gradle @@ -17,7 +17,7 @@ dependencies { } task jarTests(type: Jar, dependsOn: testClasses) { - classifier = 'tests' + archiveClassifier.set('tests') from sourceSets.test.output } diff --git a/transportable-udfs-plugin/build.gradle b/transportable-udfs-plugin/build.gradle index 84216265..77054827 100644 --- a/transportable-udfs-plugin/build.gradle +++ b/transportable-udfs-plugin/build.gradle @@ -4,7 +4,7 @@ plugins { id 'signing' } -repositories { +repositories { gradlePluginPortal() } @@ -23,7 +23,7 @@ def writeVersionInfo = { file -> ant.propertyfile(file: file) { entry(key: "transport-version", value: version) entry(key: "hive-version", value: '1.2.2') - entry(key: "trino-version", value: '406') + entry(key: "trino-version", value: '446') entry(key: "spark_2.11-version", value: '2.3.0') entry(key: "spark_2.12-version", value: '3.1.1') entry(key: "scala-version", value: '2.11.8') @@ -40,13 +40,13 @@ def licenseSpec = copySpec { } task sourcesJar(type: Jar, dependsOn: classes) { - classifier 'sources' + archiveClassifier.set("sources") from sourceSets.main.allSource with licenseSpec } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier 'javadoc' + archiveClassifier.set("javadoc") from tasks.javadoc with licenseSpec } diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java index de7c6b6a..25e21f5c 100644 --- a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/Defaults.java @@ -75,7 +75,7 @@ private static final String getVersion(final String platform) { new Platform(TRINO, Language.JAVA, TrinoWrapperGenerator.class, - JavaLanguageVersion.of(17), + JavaLanguageVersion.of(21), ImmutableList.of( DependencyConfiguration.builder(IMPLEMENTATION, "com.linkedin.transport:transportable-udfs-trino", TRANSPORT_VERSION).build(), DependencyConfiguration.builder(COMPILE_ONLY, "io.trino:trino-main", TRINO_VERSION).exclude("org.slf4j", "slf4j-api").build() diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/DistributionPackaging.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/DistributionPackaging.java index c5ea27d1..556af942 100644 --- a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/DistributionPackaging.java +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/DistributionPackaging.java @@ -56,9 +56,9 @@ public List> configurePackagingTasks(Project projec // Explicitly set classifiers for the created distributions or else leads to Maven packaging issues due to multiple // artifacts with the same classifier - project.getTasks().named(platform.getName() + "DistTar", Tar.class, tar -> tar.setClassifier(platform.getName())); + project.getTasks().named(platform.getName() + "DistTar", Tar.class, tar -> tar.getArchiveClassifier().set(platform.getName())); project.getArtifacts().add(ShadowBasePlugin.CONFIGURATION_NAME, project.getTasks().named(platform.getName() + "DistTar", Tar.class)); - project.getTasks().named(platform.getName() + "DistZip", Zip.class, zip -> zip.setClassifier(platform.getName())); + project.getTasks().named(platform.getName() + "DistZip", Zip.class, zip -> zip.getArchiveClassifier().set(platform.getName())); project.getArtifacts().add(ShadowBasePlugin.CONFIGURATION_NAME, project.getTasks().named(platform.getName() + "DistZip", Zip.class)); return ImmutableList.of(project.getTasks().named(platform.getName() + "DistTar", Tar.class), project.getTasks().named(platform.getName() + "DistZip", Zip.class)); @@ -80,7 +80,7 @@ private TaskProvider createThinJarTask(Project project, SourceSet sourceSet task.dependsOn(project.getTasks().named(sourceSet.getClassesTaskName())); task.setDescription("Assembles a thin jar archive containing the " + platformName + " classes to be included in the distribution"); - task.setClassifier(platformName + "-dist-thin"); + task.getArchiveClassifier().set(platformName + "-dist-thin"); task.from(sourceSet.getOutput()); task.from(sourceSet.getResources()); }); diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/ShadedJarPackaging.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/ShadedJarPackaging.java index 92914cb6..fcc87744 100644 --- a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/ShadedJarPackaging.java +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/ShadedJarPackaging.java @@ -71,7 +71,7 @@ private TaskProvider createShadeTask(Project project, Platform platfo project.getTasks().register(sourceSet.getTaskName("shade", "Jar"), ShadeTask.class, task -> { task.setGroup(ShadowJavaPlugin.SHADOW_GROUP); task.setDescription("Create a combined JAR of " + platform.getName() + " output and runtime dependencies"); - task.setClassifier(platform.getName()); + task.getArchiveClassifier().set(platform.getName()); task.getManifest() .inheritFrom(project.getTasks().named(mainSourceSet.getJarTaskName(), Jar.class).get().getManifest()); task.from(sourceSet.getOutput()); diff --git a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/ThinJarPackaging.java b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/ThinJarPackaging.java index 48db5f35..a9fba364 100644 --- a/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/ThinJarPackaging.java +++ b/transportable-udfs-plugin/src/main/java/com/linkedin/transport/plugin/packaging/ThinJarPackaging.java @@ -38,11 +38,11 @@ public List> configurePackagingTasks(Project projec task.dependsOn(project.getTasks().named(platformSourceSet.getClassesTaskName())); task.setDescription("Assembles a thin jar archive containing the " + platform.getName() + " classes to be included in the distribution"); - task.setClassifier(platform.getName() + "-thin"); + task.getArchiveClassifier().set(platform.getName() + "-thin"); task.from(platformSourceSet.getOutput()); task.from(platformSourceSet.getResources()); }); - + project.getArtifacts().add(ShadowBasePlugin.CONFIGURATION_NAME, thinJarTask); AdhocComponentWithVariants java = project.getComponents().withType(AdhocComponentWithVariants.class).getByName("java"); java.addVariantsFromConfiguration(project.getConfigurations().getByName(ShadowBasePlugin.CONFIGURATION_NAME), v -> v.mapToOptional()); diff --git a/transportable-udfs-spark_2.11/build.gradle b/transportable-udfs-spark_2.11/build.gradle index 24e82bec..afd8535a 100644 --- a/transportable-udfs-spark_2.11/build.gradle +++ b/transportable-udfs-spark_2.11/build.gradle @@ -26,7 +26,7 @@ dependencies { } task jarTests(type: Jar, dependsOn: testClasses) { - classifier = 'tests' + archiveClassifier.set('tests') from sourceSets.test.output } diff --git a/transportable-udfs-spark_2.12/build.gradle b/transportable-udfs-spark_2.12/build.gradle index 19b339e7..f42f4d60 100644 --- a/transportable-udfs-spark_2.12/build.gradle +++ b/transportable-udfs-spark_2.12/build.gradle @@ -39,7 +39,7 @@ dependencies { } task jarTests(type: Jar, dependsOn: testClasses) { - classifier = 'tests' + archiveClassifier.set('tests') from sourceSets.test.output } diff --git a/transportable-udfs-test/transportable-udfs-test-api/src/main/java/com/linkedin/transport/test/AbstractStdUDFTest.java b/transportable-udfs-test/transportable-udfs-test-api/src/main/java/com/linkedin/transport/test/AbstractStdUDFTest.java index 4d2a7892..83d053f8 100644 --- a/transportable-udfs-test/transportable-udfs-test-api/src/main/java/com/linkedin/transport/test/AbstractStdUDFTest.java +++ b/transportable-udfs-test/transportable-udfs-test-api/src/main/java/com/linkedin/transport/test/AbstractStdUDFTest.java @@ -17,7 +17,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -81,7 +81,7 @@ protected static List array(Object... elements) { protected static Map map(Object... args) { Preconditions.checkArgument(args.length % 2 == 0, "Total number of keys + values is expected to be an even number. Received: " + args.length); - Map dataMap = new LinkedHashMap<>(); + Map dataMap = new HashMap<>(); for (int i = 0; i < args.length; i += 2) { dataMap.put(args[i], args[i + 1]); } diff --git a/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/GenericQueryExecutor.java b/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/GenericQueryExecutor.java index 0c4d17dd..a78a3508 100644 --- a/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/GenericQueryExecutor.java +++ b/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/GenericQueryExecutor.java @@ -22,7 +22,7 @@ import com.linkedin.transport.test.spi.types.TestTypeUtils; import com.linkedin.transport.test.spi.types.UnknownTestType; import java.util.ArrayList; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.IntStream; @@ -102,7 +102,7 @@ private Pair resolveArray(List array, TestType element private Pair resolveMap(Map map, TestType keyType, TestType valueType) { List resolvedKeyTypes = new ArrayList<>(); List resolvedValueTypes = new ArrayList<>(); - Map resolvedMap = new LinkedHashMap<>(); + Map resolvedMap = new HashMap<>(); map.forEach((key, value) -> { Pair resolvedKey = resolveParameter(key, keyType); Pair resolvedValue = resolveParameter(value, valueType); diff --git a/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/data/GenericMap.java b/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/data/GenericMap.java index beeeb684..dea43ec8 100644 --- a/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/data/GenericMap.java +++ b/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/data/GenericMap.java @@ -14,7 +14,7 @@ import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -33,7 +33,7 @@ public GenericMap(Map map, TestType type) { } public GenericMap(TestType type) { - this(new LinkedHashMap<>(), type); + this(new HashMap<>(), type); } @Override diff --git a/transportable-udfs-test/transportable-udfs-test-trino/build.gradle b/transportable-udfs-test/transportable-udfs-test-trino/build.gradle index 1bf5fa63..4d8ed7a2 100644 --- a/transportable-udfs-test/transportable-udfs-test-trino/build.gradle +++ b/transportable-udfs-test/transportable-udfs-test-trino/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'java' java { - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } dependencies { @@ -17,6 +17,7 @@ dependencies { implementation(group:'io.trino', name: 'trino-main', version: project.ext.'trino-version', classifier: 'tests') { exclude 'group': 'com.google.collections', 'module': 'google-collections' } + implementation (group:'io.trino', name: 'trino-testing', version: project.ext.'trino-version') implementation group: 'io.airlift', name: 'testing', version: '221' // The io.airlift.slice dependency below has to match its counterpart in trino-root's pom.xml file // If not specified, an older version is picked up transitively from another dependency diff --git a/transportable-udfs-test/transportable-udfs-test-trino/src/main/java/com/linkedin/transport/test/trino/TrinoTestFunctionDependencies.java b/transportable-udfs-test/transportable-udfs-test-trino/src/main/java/com/linkedin/transport/test/trino/TrinoTestFunctionDependencies.java index 94b49c0a..18986711 100644 --- a/transportable-udfs-test/transportable-udfs-test-trino/src/main/java/com/linkedin/transport/test/trino/TrinoTestFunctionDependencies.java +++ b/transportable-udfs-test/transportable-udfs-test-trino/src/main/java/com/linkedin/transport/test/trino/TrinoTestFunctionDependencies.java @@ -5,24 +5,25 @@ */ package com.linkedin.transport.test.trino; +import io.trino.spi.function.CatalogSchemaFunctionName; import io.trino.spi.function.FunctionDependencies; import io.trino.spi.function.FunctionNullability; import io.trino.spi.function.InvocationConvention; import io.trino.spi.function.OperatorType; -import io.trino.spi.function.QualifiedFunctionName; +import io.trino.metadata.ResolvedFunction; import io.trino.spi.function.ScalarFunctionImplementation; import io.trino.spi.type.Type; import io.trino.spi.type.TypeManager; import io.trino.spi.type.TypeSignature; -import io.trino.testing.LocalQueryRunner; +import io.trino.testing.DistributedQueryRunner; import java.util.List; public class TrinoTestFunctionDependencies implements FunctionDependencies { private final TypeManager typeManager; - private final LocalQueryRunner queryRunner; + private final DistributedQueryRunner queryRunner; - public TrinoTestFunctionDependencies(TypeManager typeManager, LocalQueryRunner queryRunner) { + public TrinoTestFunctionDependencies(TypeManager typeManager, DistributedQueryRunner queryRunner) { this.typeManager = typeManager; this.queryRunner = queryRunner; } @@ -33,7 +34,7 @@ public Type getType(TypeSignature typeSignature) { } @Override - public FunctionNullability getFunctionNullability(QualifiedFunctionName name, List parameterTypes) { + public FunctionNullability getFunctionNullability(CatalogSchemaFunctionName name, List parameterTypes) { return null; } @@ -48,13 +49,13 @@ public FunctionNullability getCastNullability(Type fromType, Type toType) { } @Override - public ScalarFunctionImplementation getScalarFunctionImplementation(QualifiedFunctionName name, + public ScalarFunctionImplementation getScalarFunctionImplementation(CatalogSchemaFunctionName name, List parameterTypes, InvocationConvention invocationConvention) { return null; } @Override - public ScalarFunctionImplementation getScalarFunctionImplementationSignature(QualifiedFunctionName name, + public ScalarFunctionImplementation getScalarFunctionImplementationSignature(CatalogSchemaFunctionName name, List parameterTypes, InvocationConvention invocationConvention) { return null; } @@ -62,9 +63,12 @@ public ScalarFunctionImplementation getScalarFunctionImplementationSignature(Qua @Override public ScalarFunctionImplementation getOperatorImplementation(OperatorType operatorType, List parameterTypes, InvocationConvention invocationConvention) { - return queryRunner.getFunctionManager() - .getScalarFunctionImplementation(queryRunner.getMetadata().resolveOperator(queryRunner.getDefaultSession(), operatorType, parameterTypes), - invocationConvention); + var planner = queryRunner.getCoordinator().getPlannerContext(); + var metadata = planner.getMetadata(); + + ResolvedFunction resolved = metadata.resolveOperator(operatorType, parameterTypes); + return planner.getFunctionManager() + .getScalarFunctionImplementation(resolved, invocationConvention); } @Override diff --git a/transportable-udfs-test/transportable-udfs-test-trino/src/main/java/com/linkedin/transport/test/trino/TrinoTester.java b/transportable-udfs-test/transportable-udfs-test-trino/src/main/java/com/linkedin/transport/test/trino/TrinoTester.java index 3c188360..e0494b56 100644 --- a/transportable-udfs-test/transportable-udfs-test-trino/src/main/java/com/linkedin/transport/test/trino/TrinoTester.java +++ b/transportable-udfs-test/transportable-udfs-test-trino/src/main/java/com/linkedin/transport/test/trino/TrinoTester.java @@ -15,15 +15,17 @@ import com.linkedin.transport.trino.TransportConnector; import com.linkedin.transport.trino.TransportConnectorMetadata; import com.linkedin.transport.trino.TransportFunctionProvider; -import io.trino.FeaturesConfig; import io.trino.Session; import io.trino.client.ClientCapabilities; +import io.trino.spi.Plugin; +import io.trino.spi.connector.CatalogSchemaName; import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorContext; import io.trino.spi.connector.ConnectorFactory; import io.trino.spi.connector.ConnectorMetadata; import io.trino.spi.function.BoundSignature; import io.trino.metadata.FunctionBinding; +import io.trino.spi.function.CatalogSchemaFunctionName; import io.trino.spi.function.FunctionId; import com.linkedin.transport.api.StdFactory; import com.linkedin.transport.api.udf.StdUDF; @@ -33,20 +35,26 @@ import com.linkedin.transport.test.spi.SqlStdTester; import com.linkedin.transport.test.spi.ToPlatformTestOutputConverter; import io.trino.spi.function.FunctionProvider; +import io.trino.spi.type.ArrayType; +import io.trino.spi.type.MapType; +import io.trino.spi.type.RowType; import io.trino.spi.type.Type; import io.trino.sql.SqlPath; import io.trino.sql.query.QueryAssertions; -import io.trino.testing.LocalQueryRunner; +import io.trino.testing.DistributedQueryRunner; +import io.trino.testing.MaterializedRow; import io.trino.testing.TestingSession; import io.trino.type.InternalTypeManager; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import static io.trino.testing.MaterializedResult.*; import static io.trino.type.UnknownType.UNKNOWN; import static org.assertj.core.api.Assertions.*; @@ -57,20 +65,17 @@ public class TrinoTester implements SqlStdTester { private SqlFunctionCallGenerator _sqlFunctionCallGenerator; private ToPlatformTestOutputConverter _toPlatformTestOutputConverter; private Session _session; - private FeaturesConfig _featuresConfig; - private LocalQueryRunner _runner; + private DistributedQueryRunner _runner; private QueryAssertions _queryAssertions; - public TrinoTester() { + public TrinoTester() throws Exception { _stdFactory = null; _sqlFunctionCallGenerator = new TrinoSqlFunctionCallGenerator(); _toPlatformTestOutputConverter = new ToTrinoTestOutputConverter(); - SqlPath sqlPath = new SqlPath("LINKEDIN.TRANSPORT"); + SqlPath sqlPath = new SqlPath(List.of(new CatalogSchemaName("linkedin", "transport")), "linkedin.transport"); _session = TestingSession.testSessionBuilder().setPath(sqlPath).setClientCapabilities((Set) Arrays.stream( ClientCapabilities.values()).map(Enum::toString).collect(ImmutableSet.toImmutableSet())).build(); - _featuresConfig = new FeaturesConfig(); - _runner = LocalQueryRunner.builder(_session).withFeaturesConfig(_featuresConfig).build(); - _queryAssertions = new QueryAssertions(_runner); + _runner = DistributedQueryRunner.builder(_session).build(); } @Override @@ -90,14 +95,22 @@ public void setup( ConnectorFactory connectorFactory = new ConnectorFactory() { @Override public String getName() { - return "TRANSPORT"; + return "transport"; } @Override public Connector create(String catalogName, Map config, ConnectorContext context) { return connector; } }; - _runner.createCatalog("LINKEDIN", connectorFactory, Collections.emptyMap()); + + _runner.installPlugin(new Plugin() { + @Override + public Iterable getConnectorFactories() { + return ImmutableList.of(connectorFactory); + } + }); + _runner.createCatalog("linkedin", "transport", Collections.emptyMap()); + _queryAssertions = new QueryAssertions(_runner); } @Override @@ -105,7 +118,7 @@ public StdFactory getStdFactory() { if (_stdFactory == null) { FunctionBinding functionBinding = new FunctionBinding( new FunctionId("test"), - new BoundSignature("test", UNKNOWN, ImmutableList.of()), + new BoundSignature(new CatalogSchemaFunctionName("linkedin", "transport", "test"), UNKNOWN, ImmutableList.of()), ImmutableMap.of(), ImmutableMap.of()); _stdFactory = new TrinoFactory(functionBinding, new TrinoTestFunctionDependencies(InternalTypeManager.TESTING_TYPE_MANAGER, _runner)); @@ -134,10 +147,68 @@ public void check(TestCase testCase) { } Object expectedOutputType = getPlatformType(testCase.getExpectedOutputType()); Object expectedOutput = testCase.getExpectedOutput(); - if (expectedOutput instanceof Row) { - expectedOutput = ((Row) expectedOutput).getFields(); - } + expectedOutput = normalizeExpected(expectedOutput, (Type) expectedOutputType); + QueryAssertions.ExpressionAssertProvider expressionAssertProvider = _queryAssertions.function(functionName, functionArguments); - assertThat(expressionAssertProvider).hasType((Type) expectedOutputType).isEqualTo(expectedOutput); + QueryAssertions.ExpressionAssert expressionAssert = assertThat(expressionAssertProvider).hasType((Type) expectedOutputType); + expressionAssert.isEqualTo(expectedOutput); + } + + private Object normalizeExpected(Object expected, Type expectedType) { + if (expected == null) { + return null; + } + + if (expectedType instanceof RowType) { + RowType rowType = (RowType) expectedType; + if (expected instanceof MaterializedRow) { + return expected; + } + + final List fields; + if (expected instanceof Row) { + Row r = (Row) expected; + fields = r.getFields(); + } else if (expected instanceof List) { + List l = (List) expected; + fields = l; + } else { + throw new IllegalArgumentException( + "Expected value for RowType must be Row, List, or MaterializedRow; got " + expected.getClass()); + } + + List trinoFields = rowType.getFields(); + List normalized = new ArrayList<>(trinoFields.size()); + for (int i = 0; i < trinoFields.size(); i++) { + Type fType = trinoFields.get(i).getType(); + Object fVal = (i < fields.size()) ? fields.get(i) : null; + normalized.add(normalizeExpected(fVal, fType)); // recurse for nested rows/arrays/maps + } + return new MaterializedRow(DEFAULT_PRECISION, normalized); + } + + if (expectedType instanceof ArrayType) { + ArrayType arrayType = (ArrayType) expectedType; + List list = (List) expected; + List out = new ArrayList<>(list.size()); + for (Object elem : list) { + out.add(normalizeExpected(elem, arrayType.getElementType())); // recurse + } + return out; + } + + if (expectedType instanceof MapType) { + MapType mapType = (MapType) expectedType; + Map map = (Map) expected; + Map out = new LinkedHashMap<>(); + for (Map.Entry e : map.entrySet()) { + Object key = normalizeExpected(e.getKey(), mapType.getKeyType()); + Object val = normalizeExpected(e.getValue(), mapType.getValueType()); + out.put(key, val); + } + return out; + } + + return expected; } -} +} \ No newline at end of file diff --git a/transportable-udfs-trino-plugin/build.gradle b/transportable-udfs-trino-plugin/build.gradle index d3d25d4f..c692bbbd 100644 --- a/transportable-udfs-trino-plugin/build.gradle +++ b/transportable-udfs-trino-plugin/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'distribution' apply plugin: 'maven-publish' java { - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } dependencies { @@ -20,6 +20,8 @@ dependencies { } compileOnly(group:'io.trino', name: 'trino-spi', version: project.ext.'trino-version') testImplementation (group:'io.trino', name: 'trino-main', version: project.ext.'trino-version') + testImplementation (group:'io.trino', name: 'trino-testing', version: project.ext.'trino-version') + testImplementation (group:'io.trino', name: 'trino-tpch', version: project.ext.'trino-version') } // packaging as a shaded jar following the guideline from Trino plugin diff --git a/transportable-udfs-trino-plugin/src/main/java/com/linkedin/transport/trino/TransportConnector.java b/transportable-udfs-trino-plugin/src/main/java/com/linkedin/transport/trino/TransportConnector.java index d780c712..845ea22c 100644 --- a/transportable-udfs-trino-plugin/src/main/java/com/linkedin/transport/trino/TransportConnector.java +++ b/transportable-udfs-trino-plugin/src/main/java/com/linkedin/transport/trino/TransportConnector.java @@ -30,7 +30,7 @@ import java.util.Optional; import java.util.ServiceLoader; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import static java.util.Objects.*; diff --git a/transportable-udfs-trino-plugin/src/test/java/com/linkedin/transport/trino/TransportPluginTest.java b/transportable-udfs-trino-plugin/src/test/java/com/linkedin/transport/trino/TestTransportPlugin.java similarity index 73% rename from transportable-udfs-trino-plugin/src/test/java/com/linkedin/transport/trino/TransportPluginTest.java rename to transportable-udfs-trino-plugin/src/test/java/com/linkedin/transport/trino/TestTransportPlugin.java index 6b227d4e..53509516 100644 --- a/transportable-udfs-trino-plugin/src/test/java/com/linkedin/transport/trino/TransportPluginTest.java +++ b/transportable-udfs-trino-plugin/src/test/java/com/linkedin/transport/trino/TestTransportPlugin.java @@ -7,15 +7,16 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import io.trino.FeaturesConfig; import io.trino.Session; import io.trino.client.ClientCapabilities; +import io.trino.spi.connector.CatalogSchemaName; import io.trino.sql.SqlPath; -import io.trino.testing.LocalQueryRunner; +import io.trino.testing.DistributedQueryRunner; import io.trino.testing.MaterializedResult; import io.trino.testing.TestingSession; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.testng.Assert; import org.testng.annotations.AfterClass; @@ -25,34 +26,38 @@ import static org.testng.Assert.*; -public class TransportPluginTest { - private final String udfRepoDir = getClass().getClassLoader().getResource("transport-udf-repo").getPath(); - private LocalQueryRunner queryRunner; +public class TestTransportPlugin { + private final String udfRepoDir = TestTransportPlugin.class.getClassLoader() + .getResource("transport-udf-repo") + .getPath(); + private DistributedQueryRunner queryRunner; @BeforeClass - public void setUp() { - SqlPath sqlPath = new SqlPath("LINKEDIN.transport"); - FeaturesConfig featuresConfig = new FeaturesConfig(); + public void setUp() throws Exception { + SqlPath sqlPath = new SqlPath(List.of(new CatalogSchemaName("linkedin", "transport")), "linkedin_transport"); Session session = TestingSession.testSessionBuilder().setPath(sqlPath).setClientCapabilities((Set) Arrays.stream( ClientCapabilities.values()).map(Enum::toString).collect(ImmutableSet.toImmutableSet())).build(); - queryRunner = LocalQueryRunner.builder(session).withFeaturesConfig(featuresConfig).build(); + queryRunner = DistributedQueryRunner.builder(session).build(); queryRunner.installPlugin(new TransportPlugin()); - queryRunner.createCatalog("LINKEDIN", "transport", ImmutableMap.of("transport.udf.repo", udfRepoDir)); + queryRunner.createCatalog("linkedin", "transport", ImmutableMap.of("transport.udf.repo", udfRepoDir)); } - @AfterClass - public void tearDown() { - queryRunner.close(); + @AfterClass(alwaysRun = true) + public void tearDown() throws Exception { + if (queryRunner != null) { + queryRunner.close(); + queryRunner = null; + } } @Test public void testTransportUdfIsAccessible() { - String query = "SELECT array_element_at(array[1,2,3], 2)"; + String query = "SELECT linkedin.transport.array_element_at(array[1,2,3], 2)"; MaterializedResult result = queryRunner.execute(query); Assert.assertEquals(result.getRowCount(), 1); Assert.assertEquals(((int) result.getMaterializedRows().get(0).getField(0)), 3); - String camelCaseQuery = "SELECT Array_Element_At(array[1,2,3], 2)"; + String camelCaseQuery = "SELECT linkedin.transport.Array_Element_At(array[1,2,3], 2)"; MaterializedResult camelCaseResult = queryRunner.execute(camelCaseQuery); Assert.assertEquals(camelCaseResult.getRowCount(), 1); Assert.assertEquals(((int) camelCaseResult.getMaterializedRows().get(0).getField(0)), 3); @@ -60,7 +65,7 @@ public void testTransportUdfIsAccessible() { @Test public void testTransportUdfInShowFunctions() { - String showFunctionQuery = "SHOW FUNCTIONS LIKE 'array_element_at'"; + String showFunctionQuery = "SHOW FUNCTIONS FROM linkedin.transport LIKE 'array_element_at'"; MaterializedResult showFunctionResult = queryRunner.execute(showFunctionQuery); Assert.assertEquals(showFunctionResult.getRowCount(), 1); Assert.assertEquals(((String) showFunctionResult.getMaterializedRows().get(0).getField(0)), "array_element_at"); @@ -84,4 +89,4 @@ public void testTransportUDFClassLoader() { // two UDF JARs are being loaded, so we expect two classloaders assertEquals(classLoaders.size(), 2); } -} +} \ No newline at end of file diff --git a/transportable-udfs-trino/build.gradle b/transportable-udfs-trino/build.gradle index 97c53fb6..c5e61e1e 100644 --- a/transportable-udfs-trino/build.gradle +++ b/transportable-udfs-trino/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'java' java { - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } dependencies { diff --git a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/StdUdfWrapper.java b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/StdUdfWrapper.java index 54a902e8..ba0652e9 100644 --- a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/StdUdfWrapper.java +++ b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/StdUdfWrapper.java @@ -73,13 +73,14 @@ public abstract class StdUdfWrapper { private final FunctionMetadata functionMetadata; public StdUdfWrapper(StdUDF stdUDF) { - this.functionMetadata = FunctionMetadata.builder(FunctionKind.SCALAR) + String functionName = ((TopLevelStdUDF) stdUDF).getFunctionName(); + + this.functionMetadata = FunctionMetadata.builder(functionName, FunctionKind.SCALAR) .nullable() .nondeterministic() .description(((TopLevelStdUDF) stdUDF).getFunctionDescription()) .argumentNullability(getArgumentNullabilityObjects(stdUDF.getNullableArguments())) .signature(Signature.builder() - .name(((TopLevelStdUDF) stdUDF).getFunctionName()) .typeVariableConstraints(getTypeVariableConstraintsForStdUdf(stdUDF)) .returnType(parseTypeSignature(quoteReservedKeywords(stdUDF.getOutputParameterSignature()), ImmutableSet.of())) .argumentTypes(stdUDF.getInputParameterSignatures().stream() diff --git a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/TrinoWrapper.java b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/TrinoWrapper.java index 651daea7..41804b96 100644 --- a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/TrinoWrapper.java +++ b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/TrinoWrapper.java @@ -32,6 +32,8 @@ import io.airlift.slice.Slice; import io.trino.spi.TrinoException; import io.trino.spi.block.Block; +import io.trino.spi.block.SqlMap; +import io.trino.spi.block.SqlRow; import io.trino.spi.type.ArrayType; import io.trino.spi.type.BigintType; import io.trino.spi.type.BooleanType; @@ -91,9 +93,9 @@ public static StdData createStdData(Object trinoData, Type trinoType, StdFactory } else if (trinoType instanceof ArrayType) { return new TrinoArray((Block) trinoData, (ArrayType) trinoType, stdFactory); } else if (trinoType instanceof MapType) { - return new TrinoMap((Block) trinoData, trinoType, stdFactory); + return new TrinoMap((SqlMap) trinoData, (MapType) trinoType, stdFactory); } else if (trinoType instanceof RowType) { - return new TrinoStruct((Block) trinoData, trinoType, stdFactory); + return new TrinoStruct((SqlRow) trinoData, trinoType, stdFactory); } assert false : "Unrecognized Trino Type: " + trinoType.getClass(); return null; diff --git a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoArray.java b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoArray.java index 4d0dfa5d..9cc5dfd3 100644 --- a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoArray.java +++ b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoArray.java @@ -50,7 +50,7 @@ public int size() { @Override public StdData get(int idx) { - Block sourceBlock = _mutable == null ? _block : _mutable; + Block sourceBlock = _mutable == null ? _block : _mutable.build(); int position = TrinoWrapper.checkedIndexToBlockPosition(sourceBlock, idx); Object element = readNativeValue(_elementType, sourceBlock, position); return TrinoWrapper.createStdData(element, _elementType, _stdFactory); @@ -77,7 +77,7 @@ public void setUnderlyingData(Object value) { @Override public Iterator iterator() { return new Iterator() { - Block sourceBlock = _mutable == null ? _block : _mutable; + Block sourceBlock = _mutable == null ? _block : _mutable.build(); int size = TrinoArray.this.size(); int position = 0; diff --git a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoFloat.java b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoFloat.java index 16893bcc..940c842a 100644 --- a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoFloat.java +++ b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoFloat.java @@ -8,6 +8,7 @@ import com.linkedin.transport.api.data.StdFloat; import io.trino.spi.block.BlockBuilder; +import static io.trino.spi.type.IntegerType.INTEGER; import static java.lang.Float.*; @@ -36,6 +37,6 @@ public void setUnderlyingData(Object value) { @Override public void writeToBlock(BlockBuilder blockBuilder) { - blockBuilder.writeInt(floatToIntBits(_float)); + INTEGER.writeInt(blockBuilder, floatToIntBits(_float)); } } diff --git a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoMap.java b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoMap.java index 73c74637..c3330a2d 100644 --- a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoMap.java +++ b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoMap.java @@ -1,7 +1,6 @@ -/** - * Copyright 2018 LinkedIn Corporation. All rights reserved. +/* + * Copyright 2018 LinkedIn Corporation. * Licensed under the BSD-2 Clause license. - * See LICENSE in the project root for license information. */ package com.linkedin.transport.trino.data; @@ -16,98 +15,105 @@ import io.trino.spi.TrinoException; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; -import io.trino.spi.block.PageBuilderStatus; +import io.trino.spi.block.MapHashTables.HashBuildMode; +import io.trino.spi.block.SqlMap; import io.trino.spi.function.OperatorType; import io.trino.spi.type.MapType; import io.trino.spi.type.Type; + import java.lang.invoke.MethodHandle; +import java.lang.reflect.Method; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; import java.util.Set; -import static io.trino.spi.StandardErrorCode.*; -import static io.trino.spi.function.InvocationConvention.simpleConvention; +import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.NEVER_NULL; import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.NULLABLE_RETURN; -import static io.trino.spi.type.TypeUtils.*; - +import static io.trino.spi.function.InvocationConvention.simpleConvention; +import static io.trino.spi.type.TypeUtils.readNativeValue; public class TrinoMap extends TrinoData implements StdMap { + private final Type _keyType; + private final Type _valueType; + private final MapType _mapType; + private final MethodHandle _keyEqualsMethod; + private final StdFactory _stdFactory; - final Type _keyType; - final Type _valueType; - final Type _mapType; - final MethodHandle _keyEqualsMethod; - final StdFactory _stdFactory; - Block _block; - - public TrinoMap(Type mapType, StdFactory stdFactory) { - BlockBuilder mutable = mapType.createBlockBuilder(new PageBuilderStatus().createBlockBuilderStatus(), 1); - mutable.beginBlockEntry(); - mutable.closeEntry(); - _block = ((MapType) mapType).getObject(mutable.build(), 0); - - _keyType = ((MapType) mapType).getKeyType(); - _valueType = ((MapType) mapType).getValueType(); - _mapType = mapType; + private SqlMap _map; + public TrinoMap(MapType mapType, StdFactory stdFactory) { + _mapType = mapType; + _keyType = mapType.getKeyType(); + _valueType = mapType.getValueType(); _stdFactory = stdFactory; _keyEqualsMethod = ((TrinoFactory) stdFactory).getOperatorHandle( - OperatorType.EQUAL, ImmutableList.of(_keyType, _keyType), simpleConvention(NULLABLE_RETURN, NEVER_NULL, NEVER_NULL)); + OperatorType.EQUAL, + ImmutableList.of(_keyType, _keyType), + simpleConvention(NULLABLE_RETURN, NEVER_NULL, NEVER_NULL)); + + // Start with an empty SqlMap + Block emptyKeys = _keyType.createBlockBuilder(null, 0).build(); + Block emptyValues = _valueType.createBlockBuilder(null, 0).build(); + _map = new SqlMap(mapType, HashBuildMode.STRICT_EQUALS, emptyKeys, emptyValues); } - public TrinoMap(Block block, Type mapType, StdFactory stdFactory) { + public TrinoMap(SqlMap map, MapType mapType, StdFactory stdFactory) { this(mapType, stdFactory); - _block = block; + _map = map; } @Override public int size() { - return _block.getPositionCount() / 2; + return keyBlock().getPositionCount(); } @Override public StdData get(StdData key) { Object trinoKey = ((PlatformData) key).getUnderlyingData(); - int i = seekKey(trinoKey); - if (i != -1) { - Object value = readNativeValue(_valueType, _block, i); - StdData stdValue = TrinoWrapper.createStdData(value, _valueType, _stdFactory); - return stdValue; - } else { + int idx = seekKeyIndex(trinoKey); + if (idx == -1) { return null; } + Object value = readNativeValue(_valueType, valueBlock(), idx); + return TrinoWrapper.createStdData(value, _valueType, _stdFactory); } - // TODO: Do not copy the _mutable BlockBuilder on every update. As long as updates are append-only or for fixed-size - // types, we can skip copying. @Override public void put(StdData key, StdData value) { - BlockBuilder mutable = _mapType.createBlockBuilder(new PageBuilderStatus().createBlockBuilderStatus(), 1); - BlockBuilder entryBuilder = mutable.beginBlockEntry(); Object trinoKey = ((PlatformData) key).getUnderlyingData(); - int valuePosition = seekKey(trinoKey); - for (int i = 0; i < _block.getPositionCount(); i += 2) { - // Write the current key to the map - _keyType.appendTo(_block, i, entryBuilder); - // Find out if we need to change the corresponding value - if (i == valuePosition - 1) { - // Use the user-supplied value - ((TrinoData) value).writeToBlock(entryBuilder); + int existingIndex = seekKeyIndex(trinoKey); + + int n = size(); + int newSize = (existingIndex == -1) ? n + 1 : n; + + BlockBuilder keyBuilder = _keyType.createBlockBuilder(null, newSize); + BlockBuilder valueBuilder = _valueType.createBlockBuilder(null, newSize); + + // copy existing entries, replacing value if key matches + for (int i = 0; i < n; i++) { + _keyType.appendTo(keyBlock(), i, keyBuilder); + if (i == existingIndex) { + ((TrinoData) value).writeToBlock(valueBuilder); } else { - // Use the existing value in original _block - _valueType.appendTo(_block, i + 1, entryBuilder); + _valueType.appendTo(valueBlock(), i, valueBuilder); } } - if (valuePosition == -1) { - ((TrinoData) key).writeToBlock(entryBuilder); - ((TrinoData) value).writeToBlock(entryBuilder); + + // append new entry if key not present + if (existingIndex == -1) { + ((TrinoData) key).writeToBlock(keyBuilder); + ((TrinoData) value).writeToBlock(valueBuilder); } - mutable.closeEntry(); - _block = ((MapType) _mapType).getObject(mutable.build(), 0); + _map = new SqlMap(_mapType, HashBuildMode.STRICT_EQUALS, keyBuilder.build(), valueBuilder.build()); + } + + @Override + public boolean containsKey(StdData key) { + return get(key) != null; } public Set keySet() { @@ -115,21 +121,17 @@ public Set keySet() { @Override public Iterator iterator() { return new Iterator() { - int i = -2; - - @Override - public boolean hasNext() { - return !(i + 2 == size() * 2); + int i = -1; + @Override public boolean hasNext() { + return i + 1 < size(); } - - @Override - public StdData next() { - i += 2; - return TrinoWrapper.createStdData(readNativeValue(_keyType, _block, i), _keyType, _stdFactory); + @Override public StdData next() { + i++; + Object k = readNativeValue(_keyType, keyBlock(), i); + return TrinoWrapper.createStdData(k, _keyType, _stdFactory); } }; } - @Override public int size() { return TrinoMap.this.size(); @@ -140,25 +142,20 @@ public int size() { @Override public Collection values() { return new AbstractCollection() { - @Override public Iterator iterator() { return new Iterator() { - int i = -2; - - @Override - public boolean hasNext() { - return !(i + 2 == size() * 2); + int i = -1; + @Override public boolean hasNext() { + return i + 1 < size(); } - - @Override - public StdData next() { - i += 2; - return TrinoWrapper.createStdData(readNativeValue(_valueType, _block, i + 1), _valueType, _stdFactory); + @Override public StdData next() { + i++; + Object v = readNativeValue(_valueType, valueBlock(), i); + return TrinoWrapper.createStdData(v, _valueType, _stdFactory); } }; } - @Override public int size() { return TrinoMap.this.size(); @@ -167,25 +164,26 @@ public int size() { } @Override - public boolean containsKey(StdData key) { - return get(key) != null; + public Object getUnderlyingData() { + return _map; } @Override - public Object getUnderlyingData() { - return _block; + public void setUnderlyingData(Object value) { + _map = (SqlMap) value; } @Override - public void setUnderlyingData(Object value) { - _block = (Block) value; + public void writeToBlock(BlockBuilder blockBuilder) { + _mapType.writeObject(blockBuilder, _map); } - private int seekKey(Object key) { - for (int i = 0; i < _block.getPositionCount(); i += 2) { + private int seekKeyIndex(Object key) { + Block keys = keyBlock(); + for (int i = 0; i < keys.getPositionCount(); i++) { try { - if ((boolean) _keyEqualsMethod.invoke(readNativeValue(_keyType, _block, i), key)) { - return i + 1; + if ((boolean) _keyEqualsMethod.invoke(readNativeValue(_keyType, keys, i), key)) { + return i; } } catch (Throwable t) { Throwables.propagateIfInstanceOf(t, Error.class); @@ -196,8 +194,29 @@ private int seekKey(Object key) { return -1; } - @Override - public void writeToBlock(BlockBuilder blockBuilder) { - _mapType.writeObject(blockBuilder, _block); + private Block keyBlock() { + return getBlock(_map, /*key=*/true); + } + + private Block valueBlock() { + return getBlock(_map, /*key=*/false); + } + + /** + * SqlMap getters differ slightly across Trino versions (getKeyBlock()/getValueBlock() vs keyBlock()/valueBlock()). + * Use a tiny reflective shim so this class compiles against either. + */ + private static Block getBlock(SqlMap map, boolean key) { + try { + Method m; + try { + m = SqlMap.class.getMethod(key ? "getRawKeyBlock" : "getRawValueBlock"); + } catch (NoSuchMethodException e) { + m = SqlMap.class.getMethod(key ? "keyBlock" : "valueBlock"); + } + return (Block) m.invoke(map); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Unable to access SqlMap blocks", e); + } } } diff --git a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoStruct.java b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoStruct.java index c94ae335..cb854a3c 100644 --- a/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoStruct.java +++ b/transportable-udfs-trino/src/main/java/com/linkedin/transport/trino/data/TrinoStruct.java @@ -11,147 +11,184 @@ import com.linkedin.transport.trino.TrinoWrapper; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; -import io.trino.spi.block.BlockBuilderStatus; -import io.trino.spi.block.PageBuilderStatus; +import io.trino.spi.block.SqlRow; import io.trino.spi.type.RowType; import io.trino.spi.type.Type; + import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; -import static io.trino.spi.type.TypeUtils.*; - +import static io.trino.spi.type.TypeUtils.readNativeValue; public class TrinoStruct extends TrinoData implements StdStruct { - final RowType _rowType; - final StdFactory _stdFactory; - Block _block; + private final RowType rowType; + private final StdFactory stdFactory; + + // Trino v446+: ROW values are represented as SqlRow + private SqlRow rowData; public TrinoStruct(Type rowType, StdFactory stdFactory) { - _rowType = (RowType) rowType; - _stdFactory = stdFactory; + this.rowType = (RowType) rowType; + this.stdFactory = stdFactory; } - public TrinoStruct(Block block, Type rowType, StdFactory stdFactory) { + /** Prefer using this ctor if you already have a SqlRow from Trino. */ + public TrinoStruct(SqlRow sqlRow, Type rowType, StdFactory stdFactory) { this(rowType, stdFactory); - _block = block; + this.rowData = sqlRow; } public TrinoStruct(List fieldTypes, StdFactory stdFactory) { - _stdFactory = stdFactory; - _rowType = RowType.anonymous(fieldTypes); + this.stdFactory = stdFactory; + this.rowType = RowType.anonymous(fieldTypes); } public TrinoStruct(List fieldNames, List fieldTypes, StdFactory stdFactory) { - _stdFactory = stdFactory; + this.stdFactory = stdFactory; List fields = IntStream.range(0, fieldNames.size()) .mapToObj(i -> new RowType.Field(Optional.ofNullable(fieldNames.get(i)), fieldTypes.get(i))) .collect(Collectors.toList()); - _rowType = RowType.from(fields); + this.rowType = RowType.from(fields); } @Override public StdData getField(int index) { - int position = TrinoWrapper.checkedIndexToBlockPosition(_block, index); - if (position == -1) { + if (rowData == null) { return null; } - Type elementType = _rowType.getFields().get(position).getType(); - Object element = readNativeValue(elementType, _block, position); - return TrinoWrapper.createStdData(element, elementType, _stdFactory); + int offset = rowData.getRawIndex(); + Type fieldType = rowType.getFields().get(index).getType(); + Block fieldBlock = rowData.getRawFieldBlock(index); + Object element = readNativeValue(fieldType, fieldBlock, offset); + return TrinoWrapper.createStdData(element, fieldType, stdFactory); } @Override public StdData getField(String name) { - int index = -1; - Type elementType = null; - int i = 0; - for (RowType.Field field : _rowType.getFields()) { - if (field.getName().isPresent() && name.equals(field.getName().get())) { - index = i; - elementType = field.getType(); + if (rowData == null) { + return null; + } + int idx = -1; + Type t = null; + for (int i = 0; i < rowType.getFields().size(); i++) { + var f = rowType.getFields().get(i); + if (f.getName().isPresent() && name.equals(f.getName().get())) { + idx = i; + t = f.getType(); break; } - i++; } - if (index == -1) { + if (idx == -1) { return null; } - Object element = readNativeValue(elementType, _block, index); - return TrinoWrapper.createStdData(element, elementType, _stdFactory); + int offset = rowData.getRawIndex(); + Block fieldBlock = rowData.getRawFieldBlock(idx); + Object element = readNativeValue(t, fieldBlock, offset); + return TrinoWrapper.createStdData(element, t, stdFactory); } @Override public void setField(int index, StdData value) { - // TODO: This is not the right way to get this object. The status should be passed in from the invocation of the - // function and propagated to here. See PRESTO-1359 for more details. - BlockBuilderStatus blockBuilderStatus = new PageBuilderStatus().createBlockBuilderStatus(); - BlockBuilder mutable = _rowType.createBlockBuilder(blockBuilderStatus, 1); - BlockBuilder rowBlockBuilder = mutable.beginBlockEntry(); - int i = 0; - for (RowType.Field field : _rowType.getFields()) { + int fieldCount = rowType.getFields().size(); + List fieldTypes = rowType.getTypeParameters(); + Block[] fieldBlocks = new Block[fieldCount]; + + int existingOffset = (rowData == null) ? 0 : rowData.getRawIndex(); + + for (int i = 0; i < fieldCount; i++) { + Type ft = fieldTypes.get(i); + BlockBuilder bb = ft.createBlockBuilder(null, 1); + if (i == index) { - ((TrinoData) value).writeToBlock(rowBlockBuilder); + ((TrinoData) value).writeToBlock(bb); } else { - if (_block == null) { - rowBlockBuilder.appendNull(); + if (rowData == null) { + bb.appendNull(); } else { - field.getType().appendTo(_block, i, rowBlockBuilder); + // copy existing value at this row's offset + ft.appendTo(rowData.getRawFieldBlock(i), existingOffset, bb); } } - i++; + fieldBlocks[i] = bb.build(); } - mutable.closeEntry(); - _block = _rowType.getObject(mutable.build(), 0); + + // Build a single-row SqlRow at offset 0 + this.rowData = new SqlRow(0, fieldBlocks); } @Override public void setField(String name, StdData value) { - BlockBuilder mutable = _rowType.createBlockBuilder(new PageBuilderStatus().createBlockBuilderStatus(), 1); - BlockBuilder rowBlockBuilder = mutable.beginBlockEntry(); - int i = 0; - for (RowType.Field field : _rowType.getFields()) { - if (field.getName().isPresent() && name.equals(field.getName().get())) { - ((TrinoData) value).writeToBlock(rowBlockBuilder); + int fieldCount = rowType.getFields().size(); + List fieldTypes = rowType.getTypeParameters(); + Block[] fieldBlocks = new Block[fieldCount]; + + int targetIndex = -1; + for (int i = 0; i < fieldCount; i++) { + var f = rowType.getFields().get(i); + if (f.getName().isPresent() && name.equals(f.getName().get())) { + targetIndex = i; + break; + } + } + if (targetIndex == -1) { + // Unknown field name; treat as no-op + return; + } + + int existingOffset = (rowData == null) ? 0 : rowData.getRawIndex(); + + for (int i = 0; i < fieldCount; i++) { + Type ft = fieldTypes.get(i); + BlockBuilder bb = ft.createBlockBuilder(null, 1); + + if (i == targetIndex) { + ((TrinoData) value).writeToBlock(bb); } else { - if (_block == null) { - rowBlockBuilder.appendNull(); + if (rowData == null) { + bb.appendNull(); } else { - field.getType().appendTo(_block, i, rowBlockBuilder); + ft.appendTo(rowData.getRawFieldBlock(i), existingOffset, bb); } } - i++; + fieldBlocks[i] = bb.build(); } - mutable.closeEntry(); - _block = _rowType.getObject(mutable.build(), 0); + + this.rowData = new SqlRow(0, fieldBlocks); } @Override public List fields() { - ArrayList fields = new ArrayList<>(); - for (int i = 0; i < _block.getPositionCount(); i++) { - Type elementType = _rowType.getFields().get(i).getType(); - Object element = readNativeValue(elementType, _block, i); - fields.add(TrinoWrapper.createStdData(element, elementType, _stdFactory)); + ArrayList out = new ArrayList<>(); + if (rowData == null) { + return out; + } + int offset = rowData.getRawIndex(); + int count = rowType.getFields().size(); + for (int i = 0; i < count; i++) { + Type t = rowType.getFields().get(i).getType(); + Block fieldBlock = rowData.getRawFieldBlock(i); + Object element = readNativeValue(t, fieldBlock, offset); + out.add(TrinoWrapper.createStdData(element, t, stdFactory)); } - return fields; + return out; } @Override public Object getUnderlyingData() { - return _block; + return rowData; } @Override public void setUnderlyingData(Object value) { - _block = (Block) value; + this.rowData = (SqlRow) value; } @Override public void writeToBlock(BlockBuilder blockBuilder) { - _rowType.writeObject(blockBuilder, getUnderlyingData()); + rowType.writeObject(blockBuilder, rowData); } } diff --git a/transportable-udfs-type-system/build.gradle b/transportable-udfs-type-system/build.gradle index 26ae6211..c8bbf5fb 100644 --- a/transportable-udfs-type-system/build.gradle +++ b/transportable-udfs-type-system/build.gradle @@ -8,7 +8,7 @@ dependencies { } task jarTests(type: Jar, dependsOn: testClasses) { - classifier = 'tests' + archiveClassifier.set('tests') from sourceSets.test.output } From 9d394df22046244677cd4ef69a58d0d27fb4ae91 Mon Sep 17 00:00:00 2001 From: ramurm2013-droid Date: Fri, 3 Oct 2025 15:40:02 -0700 Subject: [PATCH 2/6] Add gradle.properties (#160) * Add gradle.properties * Remove TestNestedMapFromTwoArraysFunction.java --------- Co-authored-by: Ramanathan Ramu --- gradle.properties | 21 ++++++++++++++++ .../TestNestedMapFromTwoArraysFunction.java | 24 ------------------- 2 files changed, 21 insertions(+), 24 deletions(-) create mode 100644 gradle.properties diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..034959e9 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +#------------------------------------------------------------- +#For details on recommended settings, see go/gradle.properties +#------------------------------------------------------------- + +#long-running Gradle process speeds up local builds +#to stop the daemon run 'ligradle --stop' +org.gradle.daemon=false + +#configures only relevant projects to speed up the configuration of large projects +#useful when specific project/task is invoked e.g: ligradle :cloud:cloud-api:build +org.gradle.configureondemand=true + +#Gradle will run tasks from subprojects in parallel +#Higher CPU usage, faster builds +org.gradle.parallel=false +org.gradle.caching=true + +#Allows generation of idea/eclipse metadata for a specific subproject and its upstream project dependencies +ide.recursive=true + +org.gradle.jvmargs=-Xmx3g "-XX:MaxMetaspaceSize=1024m" diff --git a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestNestedMapFromTwoArraysFunction.java b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestNestedMapFromTwoArraysFunction.java index 5e7e2539..38206729 100644 --- a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestNestedMapFromTwoArraysFunction.java +++ b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestNestedMapFromTwoArraysFunction.java @@ -10,10 +10,8 @@ import com.linkedin.transport.api.udf.StdUDF; import com.linkedin.transport.api.udf.TopLevelStdUDF; import com.linkedin.transport.test.AbstractStdUDFTest; -import com.linkedin.transport.test.spi.StdTester; import java.util.List; import java.util.Map; -import org.testng.annotations.Test; public class TestNestedMapFromTwoArraysFunction extends AbstractStdUDFTest { @@ -22,26 +20,4 @@ public class TestNestedMapFromTwoArraysFunction extends AbstractStdUDFTest { protected Map, List>> getTopLevelStdUDFClassesAndImplementations() { return ImmutableMap.of(NestedMapFromTwoArraysFunction.class, ImmutableList.of(NestedMapFromTwoArraysFunction.class)); } - - @Test - public void testNestedMapUnionFunction() { - // in case of Trino v446, the output of the query with UDF "udf_map_from_two_arrays" is "array(array(map(...))) - // in case of Hive and Spark, the output of the query with UDF "udf_map_from_two_arrays" is "array(row(map(...))) - StdTester tester = getTester(); - tester.check( - functionCall("nested_map_from_two_arrays", array(row(array(1, 2), array("a", "b")))), - isTrinoTest() ? array(array(map(1, "a", 2, "b"))) : array(row(map(1, "a", 2, "b"))), - "array(row(map(integer,varchar)))"); - tester.check( - functionCall("nested_map_from_two_arrays", array(row(array(1, 2), array("a", "b")), row(array(11, 12), array("aa", "bb")))), - isTrinoTest() ? array(array(map(1, "a", 2, "b")), array(map(11, "aa", 12, "bb"))) - : array(row(map(1, "a", 2, "b")), row(map(11, "aa", 12, "bb"))), - "array(row(map(integer,varchar)))"); - tester.check( - functionCall("nested_map_from_two_arrays", array(row(array(1), array("a", "b")))), - null, "array(row(map(integer,varchar)))"); - tester.check( - functionCall("nested_map_from_two_arrays", array(row(null, array("a", "b")))), - null, "array(row(map(unknown,varchar)))"); - } } From f2a6bcb50f0b39f6b4b38ce4d4d83c4b64bd2972 Mon Sep 17 00:00:00 2001 From: ramurm2013-droid Date: Fri, 17 Oct 2025 08:51:41 -0700 Subject: [PATCH 3/6] Add task dependency (#167) * Add task dependency * Fix dependency issue --------- Co-authored-by: Ramanathan Ramu --- transportable-udfs-plugin/build.gradle | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/transportable-udfs-plugin/build.gradle b/transportable-udfs-plugin/build.gradle index 77054827..3e991376 100644 --- a/transportable-udfs-plugin/build.gradle +++ b/transportable-udfs-plugin/build.gradle @@ -126,3 +126,14 @@ publishing { //useful for testing - running "publish" will create artifacts/pom in a local dir repositories { maven { url = "$rootProject.buildDir/repo" } } } + +import org.gradle.api.publish.maven.tasks.PublishToMavenRepository + +if (providers.environmentVariable("PGP_KEY").isPresent()) { + tasks.withType(PublishToMavenRepository).configureEach { pubTask -> + if (pubTask.name.startsWith("publishPluginMavenPublicationTo")) { + dependsOn(tasks.named("signSimplePluginPluginMarkerMavenPublication")) + } + } +} + From edac926a5dcdef8e8b999525b3314ef0164c757c Mon Sep 17 00:00:00 2001 From: ramurm2013-droid Date: Fri, 17 Oct 2025 11:21:41 -0700 Subject: [PATCH 4/6] Fix publish dependency error (#169) Co-authored-by: Ramanathan Ramu --- transportable-udfs-plugin/build.gradle | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/transportable-udfs-plugin/build.gradle b/transportable-udfs-plugin/build.gradle index 3e991376..ee36981d 100644 --- a/transportable-udfs-plugin/build.gradle +++ b/transportable-udfs-plugin/build.gradle @@ -81,7 +81,7 @@ publishing { // creates its publications in an afterEvaluate callback afterEvaluate { publications { - withType(MavenPublication) { + named("pluginMaven", MavenPublication) { artifact sourcesJar artifact javadocJar @@ -127,13 +127,3 @@ publishing { repositories { maven { url = "$rootProject.buildDir/repo" } } } -import org.gradle.api.publish.maven.tasks.PublishToMavenRepository - -if (providers.environmentVariable("PGP_KEY").isPresent()) { - tasks.withType(PublishToMavenRepository).configureEach { pubTask -> - if (pubTask.name.startsWith("publishPluginMavenPublicationTo")) { - dependsOn(tasks.named("signSimplePluginPluginMarkerMavenPublication")) - } - } -} - From e7d617c73d59d79e97301f189c937c57e78c6722 Mon Sep 17 00:00:00 2001 From: ramurm2013-droid Date: Mon, 27 Oct 2025 09:17:32 -0700 Subject: [PATCH 5/6] Use junit5 instead of testng (#172) * Use junit5 instead of testng * Fix failing tests --------- Co-authored-by: Ramanathan Ramu --- .../examples/TestBinaryDuplicateFunction.java | 13 ++++++------- .../transportable-udfs-test-generic/build.gradle | 4 +++- .../transport/test/generic/GenericTester.java | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestBinaryDuplicateFunction.java b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestBinaryDuplicateFunction.java index 076ef67a..4165edde 100644 --- a/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestBinaryDuplicateFunction.java +++ b/transportable-udfs-examples/transportable-udfs-example-udfs/src/test/java/com/linkedin/transport/examples/TestBinaryDuplicateFunction.java @@ -12,9 +12,10 @@ import com.linkedin.transport.test.AbstractStdUDFTest; import com.linkedin.transport.test.spi.StdTester; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; // Temporarily disable the tests for Trino. As the test infrastructure from Trino named QueryAssertions is used to // run these test for Trino, QueryAssertions mandatory execute the function with the query in two formats: one with @@ -52,9 +53,9 @@ public void testBinaryDuplicateUnicode() { } private void testBinaryDuplicateStringHelper(StdTester tester, String input, String expectedOutput) { - ByteBuffer inputBuffer = ByteBuffer.wrap(input.getBytes()); - ByteBuffer expected = ByteBuffer.wrap(expectedOutput.getBytes()); - tester.check(functionCall("binary_duplicate", inputBuffer), expected, "varbinary"); + byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8); + byte[] expectedBytes = expectedOutput.getBytes(StandardCharsets.UTF_8); + tester.check(functionCall("binary_duplicate", ByteBuffer.wrap(inputBytes)), expectedBytes, "varbinary"); } @Test @@ -67,8 +68,6 @@ public void testBinaryDuplicate() { } private void testBinaryDuplicateHelper(StdTester tester, byte[] input, byte[] expectedOutput) { - ByteBuffer inputBuffer = ByteBuffer.wrap(input); - ByteBuffer expected = ByteBuffer.wrap(expectedOutput); - tester.check(functionCall("binary_duplicate", inputBuffer), expected, "varbinary"); + tester.check(functionCall("binary_duplicate", ByteBuffer.wrap(input)), expectedOutput, "varbinary"); } } diff --git a/transportable-udfs-test/transportable-udfs-test-generic/build.gradle b/transportable-udfs-test/transportable-udfs-test-generic/build.gradle index af18aea6..9aa1e3f1 100644 --- a/transportable-udfs-test/transportable-udfs-test-generic/build.gradle +++ b/transportable-udfs-test/transportable-udfs-test-generic/build.gradle @@ -6,7 +6,9 @@ dependencies { implementation project(':transportable-udfs-utils') implementation project(":transportable-udfs-test:transportable-udfs-test-api") implementation project(":transportable-udfs-test:transportable-udfs-test-spi") - implementation 'org.testng:testng:6.11' + implementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + implementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2' + implementation 'org.junit.jupiter:junit-jupiter-params:5.10.2' compileOnly 'org.apache.hadoop:hadoop-common:2.7.4' compileOnly 'org.apache.hadoop:hadoop-mapreduce-client-core:2.7.4' runtimeOnly 'org.apache.hadoop:hadoop-common:2.7.4' diff --git a/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/GenericTester.java b/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/GenericTester.java index f7a56525..f480e27e 100644 --- a/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/GenericTester.java +++ b/transportable-udfs-test/transportable-udfs-test-generic/src/main/java/com/linkedin/transport/test/generic/GenericTester.java @@ -19,8 +19,8 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.tuple.Pair; -import org.testng.Assert; +import static org.junit.jupiter.api.Assertions.*; public class GenericTester implements StdTester { @@ -50,14 +50,14 @@ public void setup( @Override public void check(TestCase testCase) { Pair result = _executor.executeQuery(testCase.getFunctionCall()); - Assert.assertEquals(result.getLeft(), + assertEquals(result.getLeft(), _typeFactory.createType(TypeSignature.parse(testCase.getExpectedOutputType()), _boundVariables)); if (testCase.getExpectedOutput() instanceof ByteBuffer) { byte[] expected = ((ByteBuffer) testCase.getExpectedOutput()).array(); byte[] actual = ((ByteBuffer) result.getRight()).array(); - Assert.assertEquals(actual, expected); + assertEquals(actual, expected); } else { - Assert.assertEquals(result.getRight(), testCase.getExpectedOutput()); + assertEquals(result.getRight(), testCase.getExpectedOutput()); } } } From c9f0cfb124dfb9484a48354fb3cc638230039092 Mon Sep 17 00:00:00 2001 From: ramurm2013-droid Date: Tue, 28 Oct 2025 08:26:30 -0700 Subject: [PATCH 6/6] Use junit in transportable-udfs-test (#173) Co-authored-by: Ramanathan Ramu --- .../transportable-udfs-test-api/build.gradle | 4 +++- .../java/com/linkedin/transport/test/hive/HiveTester.java | 7 ++++--- .../com/linkedin/transport/test/spark/SparkTester.scala | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/transportable-udfs-test/transportable-udfs-test-api/build.gradle b/transportable-udfs-test/transportable-udfs-test-api/build.gradle index 3ecf7605..a39695c6 100644 --- a/transportable-udfs-test/transportable-udfs-test-api/build.gradle +++ b/transportable-udfs-test/transportable-udfs-test-api/build.gradle @@ -4,6 +4,8 @@ dependencies { api project(":transportable-udfs-test:transportable-udfs-test-spi") implementation project(":transportable-udfs-api") - api 'org.testng:testng:6.11' + api 'org.junit.jupiter:junit-jupiter-api:5.10.2' + api 'org.junit.jupiter:junit-jupiter-engine:5.10.2' + api 'org.junit.jupiter:junit-jupiter-params:5.10.2' implementation 'com.google.guava:guava:24.1-jre' } \ No newline at end of file diff --git a/transportable-udfs-test/transportable-udfs-test-hive/src/main/java/com/linkedin/transport/test/hive/HiveTester.java b/transportable-udfs-test/transportable-udfs-test-hive/src/main/java/com/linkedin/transport/test/hive/HiveTester.java index e2b447e6..b68523aa 100644 --- a/transportable-udfs-test/transportable-udfs-test-hive/src/main/java/com/linkedin/transport/test/hive/HiveTester.java +++ b/transportable-udfs-test/transportable-udfs-test-hive/src/main/java/com/linkedin/transport/test/hive/HiveTester.java @@ -35,7 +35,8 @@ import org.apache.hive.service.cli.RowSet; import org.apache.hive.service.cli.SessionHandle; import org.apache.hive.service.server.HiveServer2; -import org.testng.Assert; + +import static org.junit.jupiter.api.Assertions.*; public class HiveTester implements SqlStdTester { @@ -129,10 +130,10 @@ public void assertFunctionCall(String functionCallString, Object expectedOutputD Object[] row = rowSet.iterator().next(); Object result = row[0]; - Assert.assertEquals(result, expectedOutputData, "UDF output does not match"); + assertEquals(result, expectedOutputData, "UDF output does not match"); // Get the output data type and convert them to TypeInfo to compare ColumnDescriptor outputColumnDescriptor = _client.getResultSetMetadata(handle).getColumnDescriptors().get(0); - Assert.assertEquals(TypeInfoUtils.getTypeInfoFromTypeString(outputColumnDescriptor.getTypeName().toLowerCase()), + assertEquals(TypeInfoUtils.getTypeInfoFromTypeString(outputColumnDescriptor.getTypeName().toLowerCase()), TypeInfoUtils.getTypeInfoFromObjectInspector((ObjectInspector) expectedOutputType), "UDF output type does not match"); } else { diff --git a/transportable-udfs-test/transportable-udfs-test-spark_2.11/src/main/scala/com/linkedin/transport/test/spark/SparkTester.scala b/transportable-udfs-test/transportable-udfs-test-spark_2.11/src/main/scala/com/linkedin/transport/test/spark/SparkTester.scala index 8c18b21f..9f792267 100644 --- a/transportable-udfs-test/transportable-udfs-test-spark_2.11/src/main/scala/com/linkedin/transport/test/spark/SparkTester.scala +++ b/transportable-udfs-test/transportable-udfs-test-spark_2.11/src/main/scala/com/linkedin/transport/test/spark/SparkTester.scala @@ -15,7 +15,7 @@ import com.linkedin.transport.test.spi.{SqlFunctionCallGenerator, SqlStdTester, import org.apache.spark.SparkException import org.apache.spark.sql.types._ import org.apache.spark.sql.{SparkSession, StdUDFTestUtils} -import org.testng.Assert +import org.junit.jupiter.api.Assertions._ import scala.collection.JavaConversions._ @@ -41,8 +41,8 @@ class SparkTester extends SqlStdTester { } catch { case e: SparkException => throw e.getCause() } - Assert.assertEquals(result.get(0), expectedOutputData) - Assert.assertEquals(getModifiedResultType(result.schema.head.dataType), expectedOutputType) + assertEquals(result.get(0), expectedOutputData) + assertEquals(getModifiedResultType(result.schema.head.dataType), expectedOutputType) } override def setup(topLevelStdUDFClassesAndImplementations: util.Map[Class[_ <: TopLevelStdUDF],