From f4ef5bb60d9cd743ffd7635da524d62072c638aa Mon Sep 17 00:00:00 2001 From: Takahiro Nakayama Date: Tue, 14 Feb 2023 17:44:52 +0900 Subject: [PATCH 1/4] Upgrade Gradle 6.1 -> 7.6 Signed-off-by: Takahiro Nakayama --- gradle/wrapper/gradle-wrapper.jar | Bin 58695 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 269 ++++++++++++++--------- gradlew.bat | 38 ++-- 4 files changed, 182 insertions(+), 128 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf2fc91d853cd5d4242b5547257070..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 39233 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 XzlJ0PkH9a_Rv8E<4FsY;_p9`ODYye< delta 36344 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�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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_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # 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 + # Determine the Java command to use to start the JVM. 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 @@ -97,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 @@ -105,79 +140,105 @@ 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*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + 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 24467a1..6689b85 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,10 +25,14 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,38 +65,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :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 From 25bf4be184806d66e9d5bd91392aa722a31257ee Mon Sep 17 00:00:00 2001 From: Takahiro Nakayama Date: Tue, 14 Feb 2023 17:45:53 +0900 Subject: [PATCH 2/4] Use embulk-plugins Gradle Plugin Signed-off-by: Takahiro Nakayama --- LICENSE => LICENSE.txt | 2 +- build.gradle | 126 ++++++++++++++++------------------------- 2 files changed, 50 insertions(+), 78 deletions(-) rename LICENSE => LICENSE.txt (95%) diff --git a/LICENSE b/LICENSE.txt similarity index 95% rename from LICENSE rename to LICENSE.txt index 9c97600..2790bec 100644 --- a/LICENSE +++ b/LICENSE.txt @@ -1,6 +1,7 @@ The MIT License (MIT) Copyright (c) 2015 Daisuke Higashi +Copyright (c) 2020 Takahiro Nakayama (civitaspo) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,4 +20,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/build.gradle b/build.gradle index 1e375f0..50ce3c8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,110 +1,82 @@ plugins { - id "com.jfrog.bintray" version "1.1" - id "com.github.jruby-gradle.base" version "1.5.0" id "scala" - id "com.diffplug.gradle.spotless" version "3.27.1" + id "maven-publish" + id "org.embulk.embulk-plugins" version "0.5.5" + id "com.diffplug.spotless" version "6.15.0" + // note: We cannot use the latest version because of the following error. + // > org/eclipse/jgit/lib/Repository has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0 + // id "com.palantir.git-version" version "0.15.0" + id "com.palantir.git-version" version "0.12.3" } - -import com.github.jrubygradle.JRubyExec repositories { mavenCentral() - jcenter() -} -configurations { - provided } - -version = "0.3.1" +group = "pro.civitaspo" +description = "Loads records from Dynamodb." +version = { + def vd = versionDetails() + if (vd.commitDistance == 0 && vd.lastTag ==~ /^[0-9]+\.[0-9]+\.[0-9]+(\.[a-zA-Z0-9]+)?/) { + vd.lastTag + } else { + "0.0.0.${vd.gitHash}" + } +}() sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { - compile "org.scala-lang:scala-library:2.13.1" + compileOnly "org.scala-lang:scala-library:2.13.1" - compile "org.embulk:embulk-core:0.9.23" - provided "org.embulk:embulk-core:0.9.23" + implementation "org.embulk:embulk-core:0.9.23" - compile "com.amazonaws:aws-java-sdk-dynamodb:1.11.711" - compile "com.amazonaws:aws-java-sdk-sts:1.11.711" + implementation "com.amazonaws:aws-java-sdk-dynamodb:1.11.711" + implementation "com.amazonaws:aws-java-sdk-sts:1.11.711" // For @delegate macro. - compile "dev.zio:zio-macros-core_2.13:0.6.2" + implementation "dev.zio:zio-macros-core_2.13:0.6.2" - testCompile "junit:junit:4.+" - testCompile "org.embulk:embulk-core:0.9.23:tests" - testCompile "org.embulk:embulk-standards:0.9.23" - testCompile "org.embulk:embulk-deps-buffer:0.9.23" - testCompile "org.embulk:embulk-deps-config:0.9.23" + testImplementation "junit:junit:4.+" + testImplementation "org.embulk:embulk-core:0.9.23:tests" + testImplementation "org.embulk:embulk-standards:0.9.23" + testImplementation "org.embulk:embulk-deps-buffer:0.9.23" + testImplementation "org.embulk:embulk-deps-config:0.9.23" } - compileScala { scalaCompileOptions.additionalParameters = [ "-Ymacro-annotations" ] } - test { jvmArgs '-Xms4g', '-Xmx4g', '-XX:MaxMetaspaceSize=1g' maxHeapSize = "4g" } - spotless { scala { - scalafmt('2.3.2').configFile('.scalafmt.conf') + scalafmt('3.7.1').configFile('.scalafmt.conf') } } - -task classpath(type: Copy, dependsOn: ["jar"]) { - doFirst { file("classpath").deleteDir() } - from (configurations.runtime - configurations.provided + files(jar.archivePath)) - into "classpath" -} -clean { delete "classpath" } - -task gem(type: JRubyExec, dependsOn: ["gemspec", "classpath"]) { - jrubyArgs "-S" - script "gem" - scriptArgs "build", "${project.name}.gemspec" - doLast { ant.move(file: "${project.name}-${project.version}.gem", todir: "pkg") } +embulkPlugin { + mainClass = "org.embulk.input.dynamodb.DynamodbInputPlugin" + category = "input" + type = "dynamodb" } - -task gemPush(type: JRubyExec, dependsOn: ["gem"]) { - jrubyArgs "-S" - script "gem" - scriptArgs "push", "pkg/${project.name}-${project.version}.gem" -} - -task "package"(dependsOn: ["gemspec", "classpath"]) { - doLast { - println "> Build succeeded." - println "> You can run embulk with '-L ${file(".").absolutePath}' argument." +publishing { + publications { + embulkPluginMaven(MavenPublication) { + from components.java + } } -} - -task gemspec { - ext.gemspecFile = file("${project.name}.gemspec") - inputs.file "build.gradle" - outputs.file gemspecFile - doLast { gemspecFile.write($/ -Gem::Specification.new do |spec| - spec.name = "${project.name}" - spec.version = "${project.version}" - spec.authors = ["Daisuke Higashi", "Civitaspo"] - spec.summary = %[Dynamodb input plugin for Embulk] - spec.description = %["Loads records from Dynamodb."] - spec.email = ["daisuke.develop@gmail.com", "civitaspo@gmail.com"] - spec.licenses = ["MIT"] - spec.homepage = "https://github.com/lulichn/embulk-input-dynamodb" - - spec.files = `git ls-files`.split("\n") + Dir["classpath/*.jar"] - spec.test_files = spec.files.grep(%r"^(test|spec)/") - spec.require_paths = ["lib"] - - spec.add_development_dependency 'bundler', ['~> 1.0'] - spec.add_development_dependency 'rake', ['~> 12.0'] -end -/$) + repositories { + maven { + url = "${project.buildDir}/mavenPublishLocal" + } } } -clean { delete "${project.name}.gemspec" } - +gem { + from("LICENSE.txt") + authors = ["Daisuke Higashi", "Civitaspo"] + email = ["daisuke.develop@gmail.com", "civitaspo@gmail.com"] + summary = "An Embulk plugin to ingest data from Dynamodb." + homepage = "https://github.com/lulichn/embulk-input-dynamodb" + licenses = [ "MIT" ] +} From 356f10f3c71d6dd994acd706056fb513748042be Mon Sep 17 00:00:00 2001 From: Takahiro Nakayama Date: Tue, 14 Feb 2023 18:54:21 +0900 Subject: [PATCH 3/4] Remove deprecated features Signed-off-by: Takahiro Nakayama --- README.md | 21 -- example/config-deprecated.yml | 20 - .../DeprecatedDynamodbInputPlugin.scala | 73 ---- .../input/dynamodb/DynamodbInputPlugin.scala | 33 -- .../embulk/input/dynamodb/PluginTask.scala | 116 ------ .../input/dynamodb/aws/AwsCredentials.scala | 66 +--- .../aws/AwsEndpointConfiguration.scala | 27 +- .../deprecated/AttributeValueHelper.scala | 72 ---- .../input/dynamodb/deprecated/Filter.scala | 32 -- .../dynamodb/deprecated/FilterConfig.scala | 55 --- .../deprecated/ope/AbstractOperation.scala | 119 ------ .../deprecated/ope/QueryOperation.scala | 58 --- .../deprecated/ope/ScanOperation.scala | 58 --- .../dynamodb/item/DynamodbItemSchema.scala | 37 +- .../dynamodb/AttributeValueHelperTest.scala | 342 ------------------ .../input/dynamodb/AwsCredentialsTest.scala | 51 --- ...ryOperationBackwardCompatibilityTest.scala | 50 --- ...anOperationBackwardCompatibilityTest.scala | 44 --- 18 files changed, 28 insertions(+), 1246 deletions(-) delete mode 100644 example/config-deprecated.yml delete mode 100644 src/main/scala/org/embulk/input/dynamodb/DeprecatedDynamodbInputPlugin.scala delete mode 100644 src/main/scala/org/embulk/input/dynamodb/deprecated/AttributeValueHelper.scala delete mode 100644 src/main/scala/org/embulk/input/dynamodb/deprecated/Filter.scala delete mode 100644 src/main/scala/org/embulk/input/dynamodb/deprecated/FilterConfig.scala delete mode 100644 src/main/scala/org/embulk/input/dynamodb/deprecated/ope/AbstractOperation.scala delete mode 100644 src/main/scala/org/embulk/input/dynamodb/deprecated/ope/QueryOperation.scala delete mode 100644 src/main/scala/org/embulk/input/dynamodb/deprecated/ope/ScanOperation.scala delete mode 100644 src/test/scala/org/embulk/input/dynamodb/AttributeValueHelperTest.scala diff --git a/README.md b/README.md index 806ebe6..fb81be9 100644 --- a/README.md +++ b/README.md @@ -123,27 +123,6 @@ This type of `DynamodbAttributeValue` is one that can express Dynamodb `Attribut - **NULL**: null or not. (boolean, optional) - **BOOL**: `true` or `false`. (boolean, optional) -### Deprecated Configuration - -You can use the below options yet for the backward compatibility before `v0.3.0`. However, these are already deprecated, so please use new options instead. - -- **access_key**: *[Deprecated: Use **access_key** instead]* aws access key id. this is required when **auth_method** is `"basic"` or `"session"`. (string, optional) -- **secret_key**: *[Deprecated: Use **secret_access_key** instead]* aws secret access key. this is required when **auth_method** is `"basic"` or `"session"`. (string, optional) -- **end_point**: *[Deprecated: Use **endpoint** instead]* The AWS Service endpoint (string, optional) -- **operation**: *[Deprecated: Use **scan** or **query** option instead]* Operation Type (`"scan"` or `"query"`, required) -- **filters**: *[Deprecated: Use **query.filter_expression** option or **query.filter_expression** instead]* Query Filters. (Required if **operation** is `"query"`, optional if **operation** is `"scan"`) - - **name**: Column name. - - **type**: Column type. - - **condition**: Comparison Operator. - - **value(s)**: Attribute Value(s). -- **limit**: *[Deprecated: Use **query.batch_size** option or **query.batch_size** instead]* DynamoDB 1-time Scan/Query Operation size limit (int, optional) -- **scan_limit**: *[Deprecated: Use **query.batch_size** option or **query.batch_size** instead]* DynamoDB 1-time Scan Query size limit (int, optional) -- **record_limit**: *[Deprecated: Use **query.limit** option or **query.limit** instead]* Max Record Search limit (long, optional) -- **columns**: *[Deprecated: This **columns** option for the deprecated operation. See the above **columns** option when using a new operation.]* a key-value pairs where key is a column name and value is options for the column (required) - - **name**: Column name. (string, required) - - **type**: Column values are converted to this embulk type. (`"boolean"`, `"long"`, `"double"`, `"string"`, `"json"`, required) - - NOTE: Be careful that storing values is skipped when you specify `"timestamp"`. - ## Example - Scan Operation diff --git a/example/config-deprecated.yml b/example/config-deprecated.yml deleted file mode 100644 index 1a64aea..0000000 --- a/example/config-deprecated.yml +++ /dev/null @@ -1,20 +0,0 @@ -in: - type: dynamodb - region: us-east-1 - endpoint: http://localhost:8000 - operation: scan - table: embulk-input-dynamodb_example - auth_method: basic - access_key_id: dummy - secret_access_key: dummy - columns: - - {name: primary-key, type: string} - - {name: sort-key, type: long} - - {name: doubleValue, type: double} - - {name: boolValue, type: boolean} - - {name: listValue, type: json} - - {name: mapValue, type: json} - -out: - type: stdout - diff --git a/src/main/scala/org/embulk/input/dynamodb/DeprecatedDynamodbInputPlugin.scala b/src/main/scala/org/embulk/input/dynamodb/DeprecatedDynamodbInputPlugin.scala deleted file mode 100644 index 17411f9..0000000 --- a/src/main/scala/org/embulk/input/dynamodb/DeprecatedDynamodbInputPlugin.scala +++ /dev/null @@ -1,73 +0,0 @@ -package org.embulk.input.dynamodb - -import java.util.{List => JList} - -import org.embulk.config.{ - ConfigDiff, - ConfigException, - ConfigSource, - TaskReport, - TaskSource -} -import org.embulk.input.dynamodb.aws.Aws -import org.embulk.input.dynamodb.deprecated.ope.{QueryOperation, ScanOperation} -import org.embulk.spi.{Exec, InputPlugin, PageOutput, Schema} - -@deprecated(since = "0.3.0") -object DeprecatedDynamodbInputPlugin extends InputPlugin { - - override def transaction( - config: ConfigSource, - control: InputPlugin.Control - ): ConfigDiff = { - val task: PluginTask = PluginTask.load(config) - val schema: Schema = task.getColumns.toSchema - if (schema.isEmpty) - throw new ConfigException("\"columns\" option must be set.") - val taskCount: Int = 1 - - control.run(task.dump(), schema, taskCount) - Exec.newConfigDiff() - } - - override def resume( - taskSource: TaskSource, - schema: Schema, - taskCount: Int, - control: InputPlugin.Control - ): ConfigDiff = { - throw new UnsupportedOperationException - } - - override def run( - taskSource: TaskSource, - schema: Schema, - taskIndex: Int, - output: PageOutput - ): TaskReport = { - val task: PluginTask = PluginTask.load(taskSource) - - Aws(task).withDynamodb { dynamodb => - task.getOperation.ifPresent { ope => - val o = ope.toLowerCase match { - case "scan" => new ScanOperation(dynamodb) - case "query" => new QueryOperation(dynamodb) - } - o.execute(task, schema, output) - } - } - - Exec.newTaskReport() - } - - override def cleanup( - taskSource: TaskSource, - schema: Schema, - taskCount: Int, - successTaskReports: JList[TaskReport] - ): Unit = {} - - override def guess(config: ConfigSource): ConfigDiff = { - throw new UnsupportedOperationException - } -} diff --git a/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala b/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala index 545a340..c783639 100644 --- a/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala +++ b/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala @@ -15,9 +15,6 @@ class DynamodbInputPlugin extends InputPlugin { control: InputPlugin.Control ): ConfigDiff = { val task: PluginTask = PluginTask.load(config) - if (isDeprecatedOperationRequired(task)) - return DeprecatedDynamodbInputPlugin.transaction(config, control) - val schema: Schema = DynamodbItemSchema(task).getEmbulkSchema val taskCount: Int = DynamodbOperationProxy(task).getEmbulkTaskCount @@ -31,14 +28,6 @@ class DynamodbInputPlugin extends InputPlugin { taskCount: Int, control: InputPlugin.Control ): ConfigDiff = { - val task: PluginTask = PluginTask.load(taskSource) - if (isDeprecatedOperationRequired(task)) - return DeprecatedDynamodbInputPlugin.resume( - taskSource, - schema, - taskCount, - control - ) throw new UnsupportedOperationException } @@ -49,14 +38,6 @@ class DynamodbInputPlugin extends InputPlugin { output: PageOutput ): TaskReport = { val task: PluginTask = PluginTask.load(taskSource) - if (isDeprecatedOperationRequired(task)) - return DeprecatedDynamodbInputPlugin.run( - taskSource, - schema, - taskIndex, - output - ) - val pageBuilder = new PageBuilder(task.getBufferAllocator, schema, output) Aws(task).withDynamodb { dynamodb => @@ -76,23 +57,9 @@ class DynamodbInputPlugin extends InputPlugin { taskCount: Int, successTaskReports: JList[TaskReport] ): Unit = { - val task: PluginTask = PluginTask.load(taskSource) - if (isDeprecatedOperationRequired(task)) - DeprecatedDynamodbInputPlugin.cleanup( - taskSource, - schema, - taskCount, - successTaskReports - ) } override def guess(config: ConfigSource): ConfigDiff = { - val task: PluginTask = PluginTask.load(config) - if (isDeprecatedOperationRequired(task)) - return DeprecatedDynamodbInputPlugin.guess(config) throw new UnsupportedOperationException } - - private def isDeprecatedOperationRequired(task: PluginTask): Boolean = - task.getOperation.isPresent } diff --git a/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala b/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala index e387369..1c4e6af 100644 --- a/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala +++ b/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala @@ -12,7 +12,6 @@ import org.embulk.config.{ TaskSource } import org.embulk.input.dynamodb.aws.Aws -import org.embulk.input.dynamodb.deprecated.Filter import org.embulk.input.dynamodb.item.DynamodbItemSchema import org.embulk.input.dynamodb.operation.DynamodbOperationProxy import org.embulk.spi.BufferAllocator @@ -26,134 +25,19 @@ trait PluginTask with DynamodbItemSchema.Task with DynamodbOperationProxy.Task { - @deprecated( - message = "Use #getScan() or #getQuery() instead.", - since = "0.3.0" - ) - @Config("operation") - @ConfigDefault("null") - def getOperation: Optional[String] - - @deprecated( - message = - "Use DynamodbQueryOperation.Task#getBatchSize() or DynamodbScanOperation.Task#getBatchSize() instead.", - since = "0.3.0" - ) - @Config("limit") - @ConfigDefault("0") - def getLimit: Long - - @deprecated( - message = - "Use DynamodbQueryOperation.Task#getBatchSize() or DynamodbScanOperation.Task#getBatchSize() instead.", - since = "0.3.0" - ) - @Config("scan_limit") - @ConfigDefault("0") - def getScanLimit: Long - - @deprecated( - message = - "Use DynamodbQueryOperation.Task#getLimit() or DynamodbScanOperation.Task#getLimit() instead.", - since = "0.3.0" - ) - @Config("record_limit") - @ConfigDefault("0") - def getRecordLimit: Long - - @deprecated( - message = - "Use DynamodbQueryOperation.Task#getFilterExpression() or DynamodbScanOperation.Task#getFilterExpression() instead.", - since = "0.3.0" - ) - @Config("filters") - @ConfigDefault("null") - def getFilters: Optional[Filter] - @ConfigInject def getBufferAllocator: BufferAllocator } -case class PluginTaskCompat(@delegate task: PluginTask) extends PluginTask { - - override def getOperation: Optional[String] = { - task.getOperation.ifPresent { op => - logger.warn( - "[Deprecated] The option \"operation\" is deprecated. Use \"scan\" or \"query\" option instead." - ) - - op.toLowerCase match { - case "scan" | "query" => // do nothing - case x => - throw new ConfigException( - s"Operation '$x' is unsupported. Available values are 'scan' or 'query'." - ) - } - - if (getScan.isPresent || getQuery.isPresent) - throw new ConfigException( - "The option \"operation\" must not be used together with either \"scan\" or \"query\" options." - ) - } - task.getOperation - } - - override def getLimit: Long = { - logger.warn( - "[Deprecated] The option \"limit\" is deprecated. Use \"query.batch_size\" or \"scan.batch_size\" instead." - ) - task.getLimit - } - - override def getScanLimit: Long = { - logger.warn( - "[Deprecated] The option \"scan_limit\" is deprecated. Use \"query.batch_size\" or \"scan.batch_size\" instead." - ) - task.getScanLimit - } - - override def getRecordLimit: Long = { - logger.warn( - "[Deprecated] The option \"record_limit\" is deprecated. Use \"query.limit\" or \"scan.limit\" instead." - ) - task.getRecordLimit - } - - override def getFilters: Optional[Filter] = { - logger.warn( - "[Deprecated] The option \"filters\" is deprecated. Use \"query.filter_expression\" or \"scan.filter_expression\" instead." - ) - task.getFilters - } -} - object PluginTask { def load(configSource: ConfigSource): PluginTask = { configSource .loadConfig(classOf[PluginTask]) - .pipe(PluginTaskCompat) - .tap(configure) } def load(taskSource: TaskSource): PluginTask = { taskSource .loadTask(classOf[PluginTask]) - .pipe(PluginTaskCompat) - .tap(configure) - } - - private def configure(task: PluginTask): Unit = { - if (!task.getOperation.isPresent && !task.getScan.isPresent && !task.getQuery.isPresent) { - // NOTE: "operation" option is deprecated, so this is not shown the message. - throw new ConfigException( - "Either \"scan\" or \"query\" option is required." - ) - } - if (task.getOperation.isPresent && (task.getScan.isPresent || task.getQuery.isPresent)) { - throw new ConfigException( - "[Deprecated] You must not use \"scan\" or \"query\" option with \"operation\" option." - ) - } } } diff --git a/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala b/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala index d19b363..4efbd34 100644 --- a/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala +++ b/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala @@ -33,20 +33,10 @@ object AwsCredentials { @ConfigDefault("\"default\"") def getAuthMethod: String - @deprecated(message = "Use #getAccessKeyId() instead.", since = "0.3.0") - @Config("access_key") - @ConfigDefault("null") - def getAccessKey: Optional[String] - @Config("access_key_id") @ConfigDefault("null") def getAccessKeyId: Optional[String] - @deprecated(message = "Use #getSecretAccessKey() instead.", since = "0.3.0") - @Config("secret_key") - @ConfigDefault("null") - def getSecretKey: Optional[String] - @Config("secret_access_key") @ConfigDefault("null") def getSecretAccessKey: Optional[String] @@ -89,61 +79,7 @@ object AwsCredentials { } def apply(task: Task): AwsCredentials = { - new AwsCredentials(AwsCredentialsTaskCompat(task)) - } -} - -case class AwsCredentialsTaskCompat(@delegate task: Task) extends Task { - - override def getAccessKey: Optional[String] = { - throw new NotImplementedError() - } - - override def getSecretKey: Optional[String] = { - throw new NotImplementedError() - } - - override def getAuthMethod: String = { - if (getAccessKeyId.isPresent && getSecretAccessKey.isPresent) { - if (task.getAuthMethod != "basic") { - logger.warn( - "[Deprecated] The default value of \"auth_method\" option is \"default\", " + - "but currently use \"basic\" auth_method for backward compatibility " + - "because you set \"access_key_id\" and \"secret_access_key\" options. " + - "Please set \"basic\" to \"auth_method\" option expressly." - ) - return "basic" - } - } - task.getAuthMethod - } - - override def getAccessKeyId: Optional[String] = { - if (task.getAccessKeyId.isPresent && task.getAccessKey.isPresent) - throw new ConfigException( - "You cannot use both \"access_key_id\" option and \"access_key\" option. Use \"access_key_id\" option." - ) - if (task.getAccessKey.isPresent) { - logger.warn( - "[Deprecated] \"access_key\" option is deprecated. Use \"access_key_id\" option instead." - ) - return task.getAccessKey - } - task.getAccessKeyId - } - - override def getSecretAccessKey: Optional[String] = { - if (task.getSecretAccessKey.isPresent && task.getSecretKey.isPresent) - throw new ConfigException( - "You cannot use both \"secret_access_key\" option and \"secret_key\" option. Use \"secret_access_key\" option." - ) - if (task.getSecretKey.isPresent) { - logger.warn( - "[Deprecated] \"secret_key\" option is deprecated. Use \"secret_access_key\" option instead." - ) - return task.getSecretKey - } - task.getSecretAccessKey + new AwsCredentials(task) } } diff --git a/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala b/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala index cf68533..bc497cb 100644 --- a/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala +++ b/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala @@ -15,12 +15,6 @@ import scala.util.Try object AwsEndpointConfiguration { trait Task { - - @deprecated(message = "Use #getEndpoint() instead.", since = "0.3.0") - @Config("end_point") - @ConfigDefault("null") - def getEndPoint: Optional[String] - @Config("endpoint") @ConfigDefault("null") def getEndpoint: Optional[String] @@ -31,26 +25,7 @@ object AwsEndpointConfiguration { } def apply(task: Task): AwsEndpointConfiguration = { - new AwsEndpointConfiguration(AwsEndpointConfigurationTaskCompat(task)) - } -} - -case class AwsEndpointConfigurationTaskCompat(@delegate task: Task) - extends Task { - override def getEndPoint: Optional[String] = throw new NotImplementedError() - - override def getEndpoint: Optional[String] = { - if (task.getEndpoint.isPresent && task.getEndPoint.isPresent) - throw new ConfigException( - "You cannot use both \"endpoint\" option and \"end_point\" option. Use \"endpoint\" option." - ) - if (task.getEndPoint.isPresent) { - logger.warn( - "[Deprecated] \"end_point\" option is deprecated. Use \"endpoint\" option instead." - ) - return task.getEndPoint - } - task.getEndpoint + new AwsEndpointConfiguration(task) } } diff --git a/src/main/scala/org/embulk/input/dynamodb/deprecated/AttributeValueHelper.scala b/src/main/scala/org/embulk/input/dynamodb/deprecated/AttributeValueHelper.scala deleted file mode 100644 index 72bed20..0000000 --- a/src/main/scala/org/embulk/input/dynamodb/deprecated/AttributeValueHelper.scala +++ /dev/null @@ -1,72 +0,0 @@ -package org.embulk.input.dynamodb.deprecated - -import com.amazonaws.services.dynamodbv2.model.AttributeValue -import org.msgpack.value.{Value, ValueFactory} - -import scala.util.Try - -object AttributeValueHelper { - - // referring aws-scala - def decodeToValue(value: AttributeValue): Value = { - import scala.jdk.CollectionConverters._ - - // FIXME: Need Encode? - lazy val _bin = Option(value.getB).map(v => ValueFactory.newBinary(v.array)) - lazy val _bool = Option(value.getBOOL).map(v => ValueFactory.newBoolean(v)) - lazy val _num = Option(value.getN).map(v => - Try(v.toLong) - .map(ValueFactory.newInteger) - .getOrElse(ValueFactory.newFloat(v.toDouble)) - ) - lazy val _str = Option(value.getS).map(v => ValueFactory.newString(v)) - lazy val _nil = Option(value.getNULL).map(v => ValueFactory.newNil) - - lazy val _list = Option(value.getL).map(l => - ValueFactory.newArray(l.asScala.map(v => decodeToValue(v)).asJava) - ) - lazy val _ss = Option(value.getSS).map(l => - ValueFactory.newArray( - l.asScala.map(v => ValueFactory.newString(v)).asJava - ) - ) - lazy val _ns = Option(value.getNS).map(l => - ValueFactory.newArray( - l.asScala - .map(v => - Try(v.toLong) - .map(ValueFactory.newInteger) - .getOrElse(ValueFactory.newFloat(v.toDouble)) - ) - .asJava - ) - ) - // FIXME: Need Encode? - lazy val _bs = Option(value.getBS).map(l => - ValueFactory.newArray( - l.asScala.map(v => ValueFactory.newBinary(v.array)).asJava - ) - ) - lazy val _map = Option(value.getM).map(m => - ValueFactory.newMap( - m.asScala - .map(v => ValueFactory.newString(v._1) -> decodeToValue(v._2)) - .asJava - ) - ) - - _bin - .orElse(_bool) - .orElse(_num) - .orElse(_str) - .orElse(_nil) - .orElse(_list) - .orElse(_ss) - .orElse(_ns) - .orElse(_bs) - .orElse(_map) match { - case None => ValueFactory.newNil - case Some(j) => j - } - } -} diff --git a/src/main/scala/org/embulk/input/dynamodb/deprecated/Filter.scala b/src/main/scala/org/embulk/input/dynamodb/deprecated/Filter.scala deleted file mode 100644 index 32e9140..0000000 --- a/src/main/scala/org/embulk/input/dynamodb/deprecated/Filter.scala +++ /dev/null @@ -1,32 +0,0 @@ -package org.embulk.input.dynamodb.deprecated - -import java.util.{List => JList} - -import com.fasterxml.jackson.annotation.{JsonCreator, JsonValue} -import com.google.common.base.Objects - -class Filter { - private var filters: JList[FilterConfig] = _ - - @JsonCreator - def this(filters: JList[FilterConfig]) { - this() - this.filters = filters - } - - @JsonValue - def getFilters: JList[FilterConfig] = filters - - override def equals(obj: Any): Boolean = { - if (this == obj) return true - - if (!obj.isInstanceOf[Filter]) return false - - val other: Filter = obj.asInstanceOf[Filter] - Objects.equal(filters, other.filters) - } - - override def hashCode: Int = { - Objects.hashCode(filters) - } -} diff --git a/src/main/scala/org/embulk/input/dynamodb/deprecated/FilterConfig.scala b/src/main/scala/org/embulk/input/dynamodb/deprecated/FilterConfig.scala deleted file mode 100644 index ab20290..0000000 --- a/src/main/scala/org/embulk/input/dynamodb/deprecated/FilterConfig.scala +++ /dev/null @@ -1,55 +0,0 @@ -package org.embulk.input.dynamodb.deprecated - -import com.fasterxml.jackson.annotation.JsonProperty -import com.google.common.base.Objects - -class FilterConfig { - private var _name: String = _ - private var _type: String = _ - private var _condition: String = _ - private var _value: String = _ - private var _value2: String = _ - - def this( - @JsonProperty("name") _name: String, - @JsonProperty("type") _type: String, - @JsonProperty("condition") _condition: String, - @JsonProperty("value") _value: String, - @JsonProperty("value2") _value2: String - ) { - this() - this._name = _name - this._type = _type - this._condition = _condition - this._value = _value - this._value2 = _value2 - } - - @JsonProperty("name") - def getName = _name - - @JsonProperty("type") - def getType = _type - - @JsonProperty("condition") - def getCondition = _condition - - @JsonProperty("value") - def getValue = _value - - @JsonProperty("value2") - def getValue2 = _value2 - - override def equals(obj: Any): Boolean = { - if (this == obj) return true - - if (!obj.isInstanceOf[FilterConfig]) return false - - val other: FilterConfig = obj.asInstanceOf[FilterConfig] - Objects.equal(this._name, other._name) && - Objects.equal(this._type, other._type) && - Objects.equal(this._condition, other._condition) && - Objects.equal(this._value, other._value) && - Objects.equal(this._value2, other._value2) - } -} diff --git a/src/main/scala/org/embulk/input/dynamodb/deprecated/ope/AbstractOperation.scala b/src/main/scala/org/embulk/input/dynamodb/deprecated/ope/AbstractOperation.scala deleted file mode 100644 index ed3cd38..0000000 --- a/src/main/scala/org/embulk/input/dynamodb/deprecated/ope/AbstractOperation.scala +++ /dev/null @@ -1,119 +0,0 @@ -package org.embulk.input.dynamodb.deprecated.ope - -import com.amazonaws.services.dynamodbv2.model.{AttributeValue, Condition} -import org.embulk.input.dynamodb.PluginTask -import org.embulk.input.dynamodb.deprecated.AttributeValueHelper -import org.embulk.spi._ -import org.embulk.spi.`type`.Types -import org.msgpack.value.{Value, ValueFactory} - -import scala.jdk.CollectionConverters._ - -abstract class AbstractOperation { - def execute(task: PluginTask, schema: Schema, output: PageOutput): Unit - - def getLimit(limit: Long, recordLimit: Long, recordCount: Long): Int = { - if (limit > 0 && recordLimit > 0) { - math.min(limit, recordLimit - recordCount).toInt - } - else if (limit > 0 || recordLimit > 0) { - math.max(limit, recordLimit).toInt - } - else { - 0 - } - } - - def createFilters(task: PluginTask): Map[String, Condition] = { - val filterMap = collection.mutable.HashMap[String, Condition]() - - Option(task.getFilters.orElse(null)).map { filters => - filters.getFilters.asScala.map { filter => - val attributeValueList = - collection.mutable.ArrayBuffer[AttributeValue]() - attributeValueList += createAttributeValue( - filter.getType, - filter.getValue - ) - Option(filter.getValue2).map { value2 => - attributeValueList += createAttributeValue(filter.getType, value2) - } - - filterMap += filter.getName -> new Condition() - .withComparisonOperator(filter.getCondition) - .withAttributeValueList(attributeValueList.asJava) - } - } - - filterMap.toMap - } - - def createAttributeValue(t: String, v: String): AttributeValue = { - t match { - case "string" => - new AttributeValue().withS(v) - case "long" | "double" => - new AttributeValue().withN(v) - case "boolean" => - new AttributeValue().withBOOL(v.toBoolean) - } - } - - def write( - pageBuilder: PageBuilder, - schema: Schema, - items: Seq[Map[String, AttributeValue]] - ): Long = { - var count = 0 - - items.foreach { item => - schema.getColumns.asScala.foreach { column => - val value = item.get(column.getName) - column.getType match { - case Types.STRING => - convert(column, value, pageBuilder.setString) - case Types.LONG => - convert(column, value, pageBuilder.setLong) - case Types.DOUBLE => - convert(column, value, pageBuilder.setDouble) - case Types.BOOLEAN => - convert(column, value, pageBuilder.setBoolean) - case Types.JSON => - convert(column, value, pageBuilder.setJson) - case _ => /* Do nothing */ - } - } - pageBuilder.addRecord() - count += 1 - } - - count - } - - def convert[A]( - column: Column, - value: Option[AttributeValue], - f: (Column, A) => Unit - )(implicit f1: Option[AttributeValue] => A): Unit = - f(column, f1(value)) - - implicit def StringConvert(value: Option[AttributeValue]): String = - value.map(_.getS).getOrElse("") - - implicit def LongConvert(value: Option[AttributeValue]): Long = - value.map(_.getN).flatMap(Option(_)).map(_.toLong).getOrElse(0L) - - implicit def DoubleConvert(value: Option[AttributeValue]): Double = - value.map(_.getN).flatMap(Option(_)).map(_.toDouble).getOrElse(0d) - - implicit def BooleanConvert(value: Option[AttributeValue]): Boolean = - value.exists(_.getBOOL) - - implicit def JsonConvert(value: Option[AttributeValue]): Value = { - value - .map { attr => - AttributeValueHelper.decodeToValue(attr) - } - .getOrElse(ValueFactory.newNil()) - } -} diff --git a/src/main/scala/org/embulk/input/dynamodb/deprecated/ope/QueryOperation.scala b/src/main/scala/org/embulk/input/dynamodb/deprecated/ope/QueryOperation.scala deleted file mode 100644 index 4dea80d..0000000 --- a/src/main/scala/org/embulk/input/dynamodb/deprecated/ope/QueryOperation.scala +++ /dev/null @@ -1,58 +0,0 @@ -package org.embulk.input.dynamodb.deprecated.ope - -import java.util.{List => JList, Map => JMap} - -import com.amazonaws.services.dynamodbv2.{AmazonDynamoDB, AmazonDynamoDBClient} -import com.amazonaws.services.dynamodbv2.model.{ - AttributeValue, - Condition, - QueryRequest, - QueryResult -} -import org.embulk.input.dynamodb.PluginTask -import org.embulk.spi.{BufferAllocator, PageBuilder, PageOutput, Schema} - -import scala.jdk.CollectionConverters._ - -class QueryOperation(client: AmazonDynamoDB) extends AbstractOperation { - - override def execute( - task: PluginTask, - schema: Schema, - output: PageOutput - ): Unit = { - val allocator: BufferAllocator = task.getBufferAllocator - val pageBuilder: PageBuilder = new PageBuilder(allocator, schema, output) - - val attributes: JList[String] = - schema.getColumns.asScala.map(_.getName).asJava - val conditions: JMap[String, Condition] = createFilters(task).asJava - var evaluateKey: JMap[String, AttributeValue] = null - - val limit: Long = math.max(task.getScanLimit, task.getLimit) - val recordLimit: Long = task.getRecordLimit - var recordCount: Long = 0 - - do { - val batchSize = getLimit(limit, recordLimit, recordCount) - - val request: QueryRequest = new QueryRequest() - .withTableName(task.getTable) - .withAttributesToGet(attributes) - .withKeyConditions(conditions) - .withExclusiveStartKey(evaluateKey) - - if (batchSize > 0) { - request.setLimit(batchSize) - } - - val result: QueryResult = client.query(request) - evaluateKey = result.getLastEvaluatedKey - - val items = result.getItems.asScala.map(_.asScala.toMap).toSeq - recordCount += write(pageBuilder, schema, items) - } while (evaluateKey != null && (recordLimit == 0 || recordLimit > recordCount)) - - pageBuilder.finish() - } -} diff --git a/src/main/scala/org/embulk/input/dynamodb/deprecated/ope/ScanOperation.scala b/src/main/scala/org/embulk/input/dynamodb/deprecated/ope/ScanOperation.scala deleted file mode 100644 index 383004c..0000000 --- a/src/main/scala/org/embulk/input/dynamodb/deprecated/ope/ScanOperation.scala +++ /dev/null @@ -1,58 +0,0 @@ -package org.embulk.input.dynamodb.deprecated.ope - -import java.util.{List => JList, Map => JMap} - -import com.amazonaws.services.dynamodbv2.{AmazonDynamoDB, AmazonDynamoDBClient} -import com.amazonaws.services.dynamodbv2.model.{ - AttributeValue, - Condition, - ScanRequest, - ScanResult -} -import org.embulk.input.dynamodb.PluginTask -import org.embulk.spi.{BufferAllocator, PageBuilder, PageOutput, Schema} - -import scala.jdk.CollectionConverters._ - -class ScanOperation(client: AmazonDynamoDB) extends AbstractOperation { - - override def execute( - task: PluginTask, - schema: Schema, - output: PageOutput - ): Unit = { - val allocator: BufferAllocator = task.getBufferAllocator - val pageBuilder: PageBuilder = new PageBuilder(allocator, schema, output) - - val attributes: JList[String] = - schema.getColumns.asScala.map(_.getName).asJava - val scanFilter: JMap[String, Condition] = createFilters(task).asJava - var evaluateKey: JMap[String, AttributeValue] = null - - val scanLimit: Long = task.getScanLimit - val recordLimit: Long = task.getRecordLimit - var recordCount: Long = 0 - - do { - val batchSize = getLimit(scanLimit, recordLimit, recordCount) - - val request: ScanRequest = new ScanRequest() - .withTableName(task.getTable) - .withAttributesToGet(attributes) - .withScanFilter(scanFilter) - .withExclusiveStartKey(evaluateKey) - - if (batchSize > 0) { - request.setLimit(batchSize) - } - - val result: ScanResult = client.scan(request) - evaluateKey = result.getLastEvaluatedKey - - val items = result.getItems.asScala.map(_.asScala.toMap).toSeq - recordCount += write(pageBuilder, schema, items) - } while (evaluateKey != null && (recordLimit == 0 || recordLimit > recordCount)) - - pageBuilder.finish() - } -} diff --git a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala index 624ec90..24f00b5 100644 --- a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala +++ b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala @@ -63,29 +63,44 @@ object DynamodbItemSchema { @Config("columns") @ConfigDefault("[]") - def getColumns: SchemaConfigCompat + def getColumns: Jlist[ColumnTask] + + @Config("default_timezone") + @ConfigDefault("\"UTC\"") + def getDefaultTimeZoneId: String + + @Config("default_timestamp_format") + @ConfigDefault("\"%Y-%m-%d %H:%M:%S.%6N %z\"") + def getDefaultTimestampFormat: String + + @Config("default_date") + @ConfigDefault("\"1970-01-01\"") + def getDefaultDate: String } } case class DynamodbItemSchema(task: DynamodbItemSchema.Task) { - // TODO: build in this class after removing SchemaConfigCompat. private lazy val embulkSchema: Schema = - if (!isItemAsJson) task.getColumns.toSchema - else - Schema - .builder() - .add(task.getJsonColumnName, Types.JSON) - .build() + Schema + .builder() + .tap { b => + if (isItemAsJson) b.add(task.getJsonColumnName, Types.JSON) + else + task.getColumns.toSeq.foreach { t => + b.add(t.getName, t.getType) + } + } + .build() private lazy val timestampParsers: Map[String, TimestampParser] = - task.getColumns.columnTasks.map { columnTask => + task.getColumns.toSeq.map { columnTask => columnTask.getName -> TimestampParser.of(task, columnTask) }.toMap private lazy val attributeTypes: Map[String, DynamodbAttributeValueType] = - task.getColumns.columnTasks + task.getColumns.toSeq .filter(_.getAttributeType.isPresent) .map { columnTask => columnTask.getName -> DynamodbAttributeValueType( @@ -121,7 +136,7 @@ case class DynamodbItemSchema(task: DynamodbItemSchema.Task) { def getEmbulkColumn(columnIndex: Int): Option[Column] = Try(getEmbulkSchema.getColumn(columnIndex)).toOption - def isItemAsJson: Boolean = task.getColumns.isEmpty + def isItemAsJson: Boolean = task.getColumns.toSeq.isEmpty def visitColumns(visitor: DynamodbItemColumnVisitor): Unit = getEmbulkSchema.visitColumns(visitor) diff --git a/src/test/scala/org/embulk/input/dynamodb/AttributeValueHelperTest.scala b/src/test/scala/org/embulk/input/dynamodb/AttributeValueHelperTest.scala deleted file mode 100644 index 75a6ba8..0000000 --- a/src/test/scala/org/embulk/input/dynamodb/AttributeValueHelperTest.scala +++ /dev/null @@ -1,342 +0,0 @@ -package org.embulk.input.dynamodb - -import java.io.File -import java.{util => JUtil} - -import com.amazonaws.services.dynamodbv2.model.AttributeValue -import com.fasterxml.jackson.databind.ObjectMapper -import org.embulk.input.dynamodb.deprecated.AttributeValueHelper._ -import org.hamcrest.CoreMatchers._ -import org.hamcrest.MatcherAssert.assertThat -import org.junit.Assert._ -import org.junit.Test -import org.msgpack.value.ValueFactory - -import scala.jdk.CollectionConverters._ - -class AttributeValueHelperTest { - - @Test - def decodeTest(): Unit = { - val stringValue = decodeToValue(new AttributeValue().withS("STR")) - assertEquals(stringValue.asStringValue.asString, "STR") - - val intValue = decodeToValue(new AttributeValue().withN("123456789")) - assertEquals(intValue.asNumberValue().toInt, 123456789) - - val doubleValue = decodeToValue( - new AttributeValue().withN("-98765432.00000001") - ) - assertEquals(doubleValue.asNumberValue().toDouble, -98765432.00000001, 0.0) - - val trueValue = decodeToValue(new AttributeValue().withBOOL(true)) - assertEquals(trueValue.asBooleanValue().getBoolean, true) - - val falseValue = decodeToValue(new AttributeValue().withBOOL(false)) - assertEquals(falseValue.asBooleanValue().getBoolean, false) - - val nilValue = decodeToValue(new AttributeValue().withNULL(true)) - assertEquals(nilValue.isNilValue, true) - } - - @Test - def listDecodeTest(): Unit = { - val stringListValue = decodeToValue( - new AttributeValue().withL( - new AttributeValue().withS("ValueA"), - new AttributeValue().withS("ValueB"), - new AttributeValue().withS("ValueC") - ) - ) - - assertTrue(stringListValue.isArrayValue) - assertEquals(stringListValue.asArrayValue().size(), 3) - - assertTrue(stringListValue.asArrayValue().get(0).isStringValue) - assertEquals( - stringListValue.asArrayValue().get(0).asStringValue().asString(), - "ValueA" - ) - assertEquals( - stringListValue.asArrayValue().get(1).asStringValue().asString(), - "ValueB" - ) - assertEquals( - stringListValue.asArrayValue().get(2).asStringValue().asString(), - "ValueC" - ) - - val numberListValue = decodeToValue( - new AttributeValue().withL( - new AttributeValue().withN("123"), - new AttributeValue().withN("-456"), - new AttributeValue().withN("0.0000045679"), - new AttributeValue().withN("-1234567890.123") - ) - ) - - assertTrue(numberListValue.isArrayValue) - assertEquals(numberListValue.asArrayValue().size(), 4) - - assertTrue(numberListValue.asArrayValue().get(0).isIntegerValue) - assertEquals( - numberListValue.asArrayValue().get(0).asNumberValue().toInt, - 123 - ) - assertEquals( - numberListValue.asArrayValue().get(1).asNumberValue().toInt, - -456 - ) - - assertTrue(numberListValue.asArrayValue().get(2).isFloatValue) - assertEquals( - numberListValue.asArrayValue().get(2).asNumberValue().toDouble, - 0.0000045679, - 0.0 - ) - assertEquals( - numberListValue.asArrayValue().get(3).asNumberValue().toDouble, - -1234567890.123, - 0.0 - ) - - val stringSetValue = decodeToValue( - new AttributeValue().withSS(new JUtil.HashSet[String]() { - add("ValueA") - add("ValueB") - add("ValueC") - }) - ) - - assertTrue(stringSetValue.isArrayValue) - assertEquals(stringSetValue.asArrayValue().size(), 3) - - assertThat( - List("ValueA", "ValueB", "ValueC").asJava, - hasItems( - equalTo(stringSetValue.asArrayValue().get(0).asStringValue().asString), - equalTo(stringSetValue.asArrayValue().get(1).asStringValue().asString), - equalTo(stringSetValue.asArrayValue().get(2).asStringValue().asString) - ) - ) - - val numberSetValue = decodeToValue( - new AttributeValue().withNS(new JUtil.HashSet[String]() { - add("123") - add("-456") - add("0.0000045679") - add("-1234567890.123") - }) - ) - - assertTrue(numberSetValue.isArrayValue) - assertEquals(numberSetValue.asArrayValue().size(), 4) - } - - @Test - def mapDecodeTest(): Unit = { - val stringMap = decodeToValue( - new AttributeValue().withM(new JUtil.HashMap[String, AttributeValue]() { - put("KeyA", new AttributeValue().withS("ValueA")) - put("KeyB", new AttributeValue().withS("ValueB")) - put("KeyC", new AttributeValue().withS("ValueC")) - }) - ) - - assertTrue(stringMap.isMapValue) - assertEquals(stringMap.asMapValue().size(), 3) - assertEquals( - stringMap - .asMapValue() - .map() - .get(ValueFactory.newString("KeyA")) - .asStringValue() - .asString(), - "ValueA" - ) - assertEquals( - stringMap - .asMapValue() - .map() - .get(ValueFactory.newString("KeyB")) - .asStringValue() - .asString(), - "ValueB" - ) - assertEquals( - stringMap - .asMapValue() - .map() - .get(ValueFactory.newString("KeyC")) - .asStringValue() - .asString(), - "ValueC" - ) - - val numberMap = decodeToValue( - new AttributeValue().withM(new JUtil.HashMap[String, AttributeValue]() { - put("KeyA", new AttributeValue().withN("123")) - put("KeyB", new AttributeValue().withN("-456")) - put("KeyC", new AttributeValue().withN("0.0000045679")) - put("KeyD", new AttributeValue().withN("-1234567890.123")) - }) - ) - - assertTrue(numberMap.isMapValue) - assertEquals(numberMap.asMapValue().size(), 4) - - assertTrue( - numberMap - .asMapValue() - .map() - .get(ValueFactory.newString("KeyA")) - .isIntegerValue - ) - assertEquals( - numberMap - .asMapValue() - .map() - .get(ValueFactory.newString("KeyA")) - .asNumberValue() - .toInt, - 123 - ) - assertEquals( - numberMap - .asMapValue() - .map() - .get(ValueFactory.newString("KeyB")) - .asNumberValue() - .toInt, - -456 - ) - - assertTrue( - numberMap - .asMapValue() - .map() - .get(ValueFactory.newString("KeyC")) - .isFloatValue - ) - assertEquals( - numberMap - .asMapValue() - .map() - .get(ValueFactory.newString("KeyC")) - .asFloatValue() - .toDouble, - 0.0000045679, - 0.0 - ) - assertEquals( - numberMap - .asMapValue() - .map() - .get(ValueFactory.newString("KeyD")) - .asFloatValue() - .toDouble, - -1234567890.123, - 0.0 - ) - } - - def attr[A](value: A)(implicit f: A => AttributeValue): AttributeValue = - f(value) - - implicit def StringAttributeValue(value: String): AttributeValue = - new AttributeValue().withS(value) - - implicit def IntegerAttributeValue(value: Int): AttributeValue = - new AttributeValue().withN(value.toString) - - implicit def LongAttributeValue(value: Long): AttributeValue = - new AttributeValue().withN(value.toString) - - implicit def FloatAttributeValue(value: Float): AttributeValue = - new AttributeValue().withN(value.toString) - - implicit def DoubleAttributeValue(value: Double): AttributeValue = - new AttributeValue().withN(value.toString) - - implicit def BooleanAttributeValue(value: Boolean): AttributeValue = - new AttributeValue().withBOOL(value) - - implicit def MapAttributeValue( - value: Map[String, AttributeValue] - ): AttributeValue = new AttributeValue().withM(value.asJava) - - implicit def ListAttributeValue(value: List[AttributeValue]): AttributeValue = - new AttributeValue().withL(value.asJava) - - @Test - def nestedDecodeTest(): Unit = { - // TODO: Json -> AttributeValue... - val testData = decodeToValue( - attr( - Map( - "_id" -> attr("56d8e1377a72374918f73bd2"), - "index" -> attr(0), - "guid" -> attr("5309640c-499a-43f6-801d-3076c810892b"), - "isActive" -> attr(true), - "age" -> attr(37), - "name" -> attr("Battle Lancaster"), - "email" -> attr("battlelancaster@zytrac.com"), - "registered" -> attr("2014-07-16T04:40:58 -09:00"), - "latitude" -> attr(45.574906), - "longitude" -> attr(36.596302), - "tags" -> attr( - List( - attr("veniam"), - attr("exercitation"), - attr("velit"), - attr("pariatur"), - attr("sit"), - attr("non"), - attr("dolore") - ) - ), - "friends" -> attr( - List( - attr( - Map( - "id" -> attr(0), - "name" -> attr("Mejia Montgomery"), - "tags" -> attr( - List(attr("duis"), attr("proident"), attr("et")) - ) - ) - ), - attr( - Map( - "id" -> attr(1), - "name" -> attr("Carpenter Reed"), - "tags" -> attr( - List(attr("labore"), attr("nisi"), attr("ipsum")) - ) - ) - ), - attr( - Map( - "id" -> attr(2), - "name" -> attr("Gamble Watts"), - "tags" -> attr( - List(attr("occaecat"), attr("voluptate"), attr("eu")) - ) - ) - ) - ) - ) - ) - ) - ) - - val testA = new ObjectMapper() - .readValue(testData.toJson, classOf[JUtil.Map[String, Any]]) - val testB = new ObjectMapper().readValue( - new File("src/test/resources/json/test.json"), - classOf[JUtil.Map[String, Any]] - ) - - assertThat(testA, is(testB)) - } -} diff --git a/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala b/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala index b9110b0..9c7c29b 100644 --- a/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala +++ b/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala @@ -56,17 +56,6 @@ class AwsCredentialsTest extends EmbulkTestBase { |""".stripMargin) } - @deprecated(since = "0.3.0") - @Test - def notSetAuthMethod_SetCredentials_deprecated(): Unit = - if (runAwsCredentialsTest) { - val inConfig: ConfigSource = defaultInConfig - .set("access_key", EMBULK_DYNAMODB_TEST_ACCESS_KEY) - .set("secret_key", EMBULK_DYNAMODB_TEST_SECRET_KEY) - - doTest(inConfig) - } - @Test def notSetAuthMethod_SetCredentials(): Unit = if (runAwsCredentialsTest) { val inConfig: ConfigSource = defaultInConfig @@ -76,17 +65,6 @@ class AwsCredentialsTest extends EmbulkTestBase { doTest(inConfig) } - @deprecated(since = "0.3.0") - @Test - def setAuthMethod_Basic_deprecated(): Unit = if (runAwsCredentialsTest) { - val inConfig: ConfigSource = defaultInConfig - .set("auth_method", "basic") - .set("access_key", EMBULK_DYNAMODB_TEST_ACCESS_KEY) - .set("secret_key", EMBULK_DYNAMODB_TEST_SECRET_KEY) - - doTest(inConfig) - } - @Test def setAuthMethod_Basic(): Unit = if (runAwsCredentialsTest) { val inConfig: ConfigSource = defaultInConfig @@ -97,35 +75,6 @@ class AwsCredentialsTest extends EmbulkTestBase { doTest(inConfig) } - @deprecated(since = "0.3.0") - @Test - def throwIfSetAccessKeyAndAccessKeyId(): Unit = if (runAwsCredentialsTest) { - val inConfig: ConfigSource = defaultInConfig - .set("auth_method", "basic") - .set("access_key", EMBULK_DYNAMODB_TEST_ACCESS_KEY) - .set("access_key_id", EMBULK_DYNAMODB_TEST_ACCESS_KEY) - .set("secret_key", EMBULK_DYNAMODB_TEST_SECRET_KEY) - - Assert.assertThrows(classOf[ConfigException], () => { - doTest(inConfig) - }) - } - - @deprecated(since = "0.3.0") - @Test - def throwIfSetSecretKeyAndSecretAccessKeyId(): Unit = - if (runAwsCredentialsTest) { - val inConfig: ConfigSource = defaultInConfig - .set("auth_method", "basic") - .set("access_key", EMBULK_DYNAMODB_TEST_ACCESS_KEY) - .set("secret_key", EMBULK_DYNAMODB_TEST_SECRET_KEY) - .set("secret_access_key", EMBULK_DYNAMODB_TEST_SECRET_KEY) - - Assert.assertThrows(classOf[ConfigException], () => { - doTest(inConfig) - }) - } - @Test def setAuthMethod_Basic_NotSet(): Unit = { val inConfig: ConfigSource = defaultInConfig diff --git a/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationBackwardCompatibilityTest.scala b/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationBackwardCompatibilityTest.scala index d911911..3db620e 100644 --- a/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationBackwardCompatibilityTest.scala +++ b/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationBackwardCompatibilityTest.scala @@ -128,54 +128,4 @@ class DynamodbQueryOperationBackwardCompatibilityTest extends EmbulkTestBase { ) } - @Test - def deprecatedQueryOperationTest(): Unit = { - val inConfig: ConfigSource = loadConfigSourceFromYamlString(s""" - |type: dynamodb - |end_point: http://${dynamoDBHost}:${dynamoDBPort}/ - |table: EMBULK_DYNAMODB_TEST_TABLE - |auth_method: basic - |access_key: dummy - |secret_key: dummy - |operation: query - |filters: - | - {name: pri-key, type: string, condition: EQ, value: key-1} - |columns: - | - {name: pri-key, type: string} - | - {name: sort-key, type: long} - | - {name: doubleValue, type: double} - | - {name: boolValue, type: boolean} - | - {name: listValue, type: json} - | - {name: mapValue, type: json} - |""".stripMargin) - - testBackwardCompatibility(inConfig) - } - - @Test - def keepTheSameBehaviourAsDeprecatedQueryOperationTest(): Unit = { - val inConfig: ConfigSource = loadConfigSourceFromYamlString(s""" - |type: dynamodb - |endpoint: http://${dynamoDBHost}:${dynamoDBPort}/ - |table: EMBULK_DYNAMODB_TEST_TABLE - |auth_method: basic - |access_key: dummy - |secret_key: dummy - |query: - | key_condition_expression: "#x = :v" - | expression_attribute_names: - | "#x": pri-key - | expression_attribute_values: - | ":v": {S: key-1} - |columns: - | - {name: pri-key, type: string} - | - {name: sort-key, type: long} - | - {name: doubleValue, type: double} - | - {name: boolValue, type: boolean} - | - {name: listValue, type: json} - | - {name: mapValue, type: json} - |""".stripMargin) - - testBackwardCompatibility(inConfig) - } } diff --git a/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationBackwardCompatibilityTest.scala b/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationBackwardCompatibilityTest.scala index a248f67..2bab719 100644 --- a/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationBackwardCompatibilityTest.scala +++ b/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationBackwardCompatibilityTest.scala @@ -128,48 +128,4 @@ class DynamodbScanOperationBackwardCompatibilityTest extends EmbulkTestBase { ) } - @Test - def deprecatedScanOperationTest(): Unit = { - - val inConfig: ConfigSource = loadConfigSourceFromYamlString(s""" - |type: dynamodb - |end_point: http://${dynamoDBHost}:${dynamoDBPort}/ - |table: EMBULK_DYNAMODB_TEST_TABLE - |auth_method: basic - |access_key: dummy - |secret_key: dummy - |operation: scan - |columns: - | - {name: pri-key, type: string} - | - {name: sort-key, type: long} - | - {name: doubleValue, type: double} - | - {name: boolValue, type: boolean} - | - {name: listValue, type: json} - | - {name: mapValue, type: json} - |""".stripMargin) - - testBackwardCompatibility(inConfig) - } - - @Test - def keepTheSameBehaviourAsDeprecatedScanOperationTest(): Unit = { - val inConfig: ConfigSource = loadConfigSourceFromYamlString(s""" - |type: dynamodb - |endpoint: http://${dynamoDBHost}:${dynamoDBPort}/ - |table: EMBULK_DYNAMODB_TEST_TABLE - |auth_method: basic - |access_key: dummy - |secret_key: dummy - |scan: {} - |columns: - | - {name: pri-key, type: string} - | - {name: sort-key, type: long} - | - {name: doubleValue, type: double} - | - {name: boolValue, type: boolean} - | - {name: listValue, type: json} - | - {name: mapValue, type: json} - |""".stripMargin) - - testBackwardCompatibility(inConfig) - } } From 084c57947441d3664545fc89f93ba23e4329ecd7 Mon Sep 17 00:00:00 2001 From: Takahiro Nakayama Date: Tue, 14 Feb 2023 19:52:57 +0900 Subject: [PATCH 4/4] Fix deprecation against embulk 0.10.41 upgrade Signed-off-by: Takahiro Nakayama --- .scalafmt.conf | 4 +- README.md | 8 +- build.gradle | 32 ++++---- .../input/dynamodb/DynamodbInputPlugin.scala | 11 ++- .../embulk/input/dynamodb/PluginTask.scala | 36 +++++---- .../org/embulk/input/dynamodb/aws/Aws.scala | 4 +- .../dynamodb/aws/AwsClientConfiguration.scala | 4 +- .../input/dynamodb/aws/AwsCredentials.scala | 8 +- .../aws/AwsDynamodbConfiguration.scala | 4 +- .../aws/AwsEndpointConfiguration.scala | 6 +- .../embulk/input/dynamodb/aws/HttpProxy.scala | 9 ++- .../item/DynamodbAttributeValue.scala | 10 +-- ...ttributeValueEmbulkTypeTransformable.scala | 40 ++++++---- .../dynamodb/item/DynamodbItemReader.scala | 5 +- .../dynamodb/item/DynamodbItemSchema.scala | 76 +++++++++---------- .../operation/AbstractDynamodbOperation.scala | 12 ++- .../operation/DynamodbOperationProxy.scala | 11 +-- .../operation/DynamodbQueryOperation.scala | 2 +- .../operation/DynamodbScanOperation.scala | 3 +- .../input/dynamodb/AwsCredentialsTest.scala | 31 +++++--- .../dynamodb/DynamodbOperationTest.scala | 18 +++-- ...ryOperationBackwardCompatibilityTest.scala | 3 +- ...anOperationBackwardCompatibilityTest.scala | 3 +- .../dynamodb/testutil/EmbulkTestBase.scala | 27 ++++--- 24 files changed, 200 insertions(+), 167 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 7a9fe74..b564ec8 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,5 +1,5 @@ # https://scalameta.org/scalafmt/#Configuration -version = "2.3.2" +version = "3.7.1" +runner.dialect = "scala3" newlines.alwaysBeforeElseAfterCurlyIf = true -newlines.alwaysBeforeTopLevelStatements = true diff --git a/README.md b/README.md index fb81be9..af2c10b 100644 --- a/README.md +++ b/README.md @@ -174,9 +174,11 @@ You can see more examples [here](./example). ```shell $ ./run_dynamodb_local.sh +# Set dummy credentials (access_key_id=dummy and secret_access_key=dummy) +$ aws configure $ ./example/prepare_dynamodb_table.sh -$ ./gradlew classpath -$ embulk run example/config-query.yml -Ilib +$ ./gradlew gem +$ embulk run example/config-query.yml -Ibuild/gemContents/lib ``` ### Run tests @@ -230,4 +232,4 @@ $ ./gradlew gemPush ## License -[MIT LICENSE](./LICENSE) +[MIT LICENSE](./LICENSE.txt) diff --git a/build.gradle b/build.gradle index 50ce3c8..410dee4 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,10 @@ plugins { id "scala" id "maven-publish" id "org.embulk.embulk-plugins" version "0.5.5" - id "com.diffplug.spotless" version "6.15.0" + // note: Incompatible because this component declares an API of a component compatible with Java 11 and the consumer needed a runtime of a component compatible with Java 8 + // id "com.diffplug.spotless" version "6.15.0" + // https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md#6130---2023-01-14 + id "com.diffplug.spotless" version "6.13.0" // note: We cannot use the latest version because of the following error. // > org/eclipse/jgit/lib/Repository has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0 // id "com.palantir.git-version" version "0.15.0" @@ -26,25 +29,24 @@ sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { - compileOnly "org.scala-lang:scala-library:2.13.1" - - implementation "org.embulk:embulk-core:0.9.23" + // note: The upper versions of embulk includes Guava Dependency removal, + // so we need to specify the version of embulk to avoid some errors. + // We may need to update the version of embulk in the future. + def embulkVersion = "0.10.41" + compileOnly "org.embulk:embulk-api:${embulkVersion}" + compileOnly "org.embulk:embulk-spi:${embulkVersion}" + implementation "org.scala-lang:scala-library:2.13.1" + implementation "org.embulk:embulk-util-config:0.3.2" + implementation "org.embulk:embulk-util-json:0.1.1" + implementation "org.embulk:embulk-util-timestamp:0.2.1" implementation "com.amazonaws:aws-java-sdk-dynamodb:1.11.711" implementation "com.amazonaws:aws-java-sdk-sts:1.11.711" - // For @delegate macro. - implementation "dev.zio:zio-macros-core_2.13:0.6.2" testImplementation "junit:junit:4.+" - testImplementation "org.embulk:embulk-core:0.9.23:tests" - testImplementation "org.embulk:embulk-standards:0.9.23" - testImplementation "org.embulk:embulk-deps-buffer:0.9.23" - testImplementation "org.embulk:embulk-deps-config:0.9.23" -} -compileScala { - scalaCompileOptions.additionalParameters = [ - "-Ymacro-annotations" - ] + testImplementation "org.embulk:embulk-junit4:${embulkVersion}" + testImplementation "org.embulk:embulk-deps:${embulkVersion}" + testImplementation "org.embulk:embulk-input-config:0.10.36" } test { jvmArgs '-Xms4g', '-Xmx4g', '-XX:MaxMetaspaceSize=1g' diff --git a/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala b/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala index c783639..4f49f0d 100644 --- a/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala +++ b/src/main/scala/org/embulk/input/dynamodb/DynamodbInputPlugin.scala @@ -18,8 +18,8 @@ class DynamodbInputPlugin extends InputPlugin { val schema: Schema = DynamodbItemSchema(task).getEmbulkSchema val taskCount: Int = DynamodbOperationProxy(task).getEmbulkTaskCount - control.run(task.dump(), schema, taskCount) - Exec.newConfigDiff() + control.run(task.toTaskSource(), schema, taskCount) + PluginTask.newConfigDiff() } override def resume( @@ -38,7 +38,7 @@ class DynamodbInputPlugin extends InputPlugin { output: PageOutput ): TaskReport = { val task: PluginTask = PluginTask.load(taskSource) - val pageBuilder = new PageBuilder(task.getBufferAllocator, schema, output) + val pageBuilder = new PageBuilder(Exec.getBufferAllocator(), schema, output) Aws(task).withDynamodb { dynamodb => DynamodbOperationProxy(task).run( @@ -48,7 +48,7 @@ class DynamodbInputPlugin extends InputPlugin { ) } pageBuilder.finish() - Exec.newTaskReport() + PluginTask.newTaskReport() } override def cleanup( @@ -56,8 +56,7 @@ class DynamodbInputPlugin extends InputPlugin { schema: Schema, taskCount: Int, successTaskReports: JList[TaskReport] - ): Unit = { - } + ): Unit = {} override def guess(config: ConfigSource): ConfigDiff = { throw new UnsupportedOperationException diff --git a/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala b/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala index 1c4e6af..c2862de 100644 --- a/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala +++ b/src/main/scala/org/embulk/input/dynamodb/PluginTask.scala @@ -3,41 +3,45 @@ package org.embulk.input.dynamodb import java.util.Optional import org.embulk.config.{ - Config, - ConfigDefault, + ConfigDiff, ConfigException, - ConfigInject, ConfigSource, - Task, - TaskSource + TaskSource, + TaskReport +} +import org.embulk.util.config.{ + Config, + ConfigDefault, + ConfigMapperFactory, + Task => EmbulkTask } import org.embulk.input.dynamodb.aws.Aws import org.embulk.input.dynamodb.item.DynamodbItemSchema import org.embulk.input.dynamodb.operation.DynamodbOperationProxy import org.embulk.spi.BufferAllocator -import zio.macros.annotation.delegate import scala.util.chaining._ trait PluginTask - extends Task + extends EmbulkTask with Aws.Task with DynamodbItemSchema.Task - with DynamodbOperationProxy.Task { - - @ConfigInject - def getBufferAllocator: BufferAllocator -} + with DynamodbOperationProxy.Task {} object PluginTask { + private val configMapperFactory: ConfigMapperFactory = + ConfigMapperFactory.builder().addDefaultModules().build() def load(configSource: ConfigSource): PluginTask = { - configSource - .loadConfig(classOf[PluginTask]) + configMapperFactory + .createConfigMapper() + .map(configSource, classOf[PluginTask]) } def load(taskSource: TaskSource): PluginTask = { - taskSource - .loadTask(classOf[PluginTask]) + configMapperFactory.createTaskMapper().map(taskSource, classOf[PluginTask]) } + + def newConfigDiff(): ConfigDiff = configMapperFactory.newConfigDiff() + def newTaskReport(): TaskReport = configMapperFactory.newTaskReport() } diff --git a/src/main/scala/org/embulk/input/dynamodb/aws/Aws.scala b/src/main/scala/org/embulk/input/dynamodb/aws/Aws.scala index 9703410..6a9cca1 100644 --- a/src/main/scala/org/embulk/input/dynamodb/aws/Aws.scala +++ b/src/main/scala/org/embulk/input/dynamodb/aws/Aws.scala @@ -5,11 +5,13 @@ import com.amazonaws.services.dynamodbv2.{ AmazonDynamoDB, AmazonDynamoDBClientBuilder } +import org.embulk.util.config.{Task => EmbulkTask} object Aws { trait Task - extends AwsCredentials.Task + extends EmbulkTask + with AwsCredentials.Task with AwsEndpointConfiguration.Task with AwsClientConfiguration.Task with AwsDynamodbConfiguration.Task diff --git a/src/main/scala/org/embulk/input/dynamodb/aws/AwsClientConfiguration.scala b/src/main/scala/org/embulk/input/dynamodb/aws/AwsClientConfiguration.scala index a43bfdc..2e07a23 100644 --- a/src/main/scala/org/embulk/input/dynamodb/aws/AwsClientConfiguration.scala +++ b/src/main/scala/org/embulk/input/dynamodb/aws/AwsClientConfiguration.scala @@ -4,12 +4,12 @@ import java.util.Optional import com.amazonaws.ClientConfiguration import com.amazonaws.client.builder.AwsClientBuilder -import org.embulk.config.{Config, ConfigDefault} +import org.embulk.util.config.{Config, ConfigDefault, Task => EmbulkTask} import org.embulk.input.dynamodb.aws.AwsClientConfiguration.Task object AwsClientConfiguration { - trait Task { + trait Task extends EmbulkTask { @Config("http_proxy") @ConfigDefault("null") diff --git a/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala b/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala index 4efbd34..eab934c 100644 --- a/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala +++ b/src/main/scala/org/embulk/input/dynamodb/aws/AwsCredentials.scala @@ -19,15 +19,15 @@ import com.amazonaws.auth.profile.{ ProfileCredentialsProvider, ProfilesConfigFile } -import org.embulk.config.{Config, ConfigDefault, ConfigException} +import org.embulk.config.ConfigException +import org.embulk.util.config.{Config, ConfigDefault, Task => EmbulkTask} import org.embulk.input.dynamodb.aws.AwsCredentials.Task import org.embulk.input.dynamodb.logger -import org.embulk.spi.unit.LocalFile -import zio.macros.annotation.delegate +import org.embulk.util.config.units.LocalFile object AwsCredentials { - trait Task { + trait Task extends EmbulkTask { @Config("auth_method") @ConfigDefault("\"default\"") diff --git a/src/main/scala/org/embulk/input/dynamodb/aws/AwsDynamodbConfiguration.scala b/src/main/scala/org/embulk/input/dynamodb/aws/AwsDynamodbConfiguration.scala index ed91b52..7adcfd8 100644 --- a/src/main/scala/org/embulk/input/dynamodb/aws/AwsDynamodbConfiguration.scala +++ b/src/main/scala/org/embulk/input/dynamodb/aws/AwsDynamodbConfiguration.scala @@ -3,12 +3,12 @@ package org.embulk.input.dynamodb.aws import java.util.Optional import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder -import org.embulk.config.{Config, ConfigDefault} +import org.embulk.util.config.{Config, ConfigDefault, Task => EmbulkTask} import org.embulk.input.dynamodb.aws.AwsDynamodbConfiguration.Task object AwsDynamodbConfiguration { - trait Task { + trait Task extends EmbulkTask { @Config("enable_endpoint_discovery") @ConfigDefault("null") diff --git a/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala b/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala index bc497cb..bef8f91 100644 --- a/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala +++ b/src/main/scala/org/embulk/input/dynamodb/aws/AwsEndpointConfiguration.scala @@ -5,16 +5,16 @@ import java.util.Optional import com.amazonaws.client.builder.AwsClientBuilder import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration import com.amazonaws.regions.{DefaultAwsRegionProviderChain, Regions} -import org.embulk.config.{Config, ConfigDefault, ConfigException} +import org.embulk.config.ConfigException +import org.embulk.util.config.{Task => EmbulkTask, Config, ConfigDefault} import org.embulk.input.dynamodb.aws.AwsEndpointConfiguration.Task import org.embulk.input.dynamodb.logger -import zio.macros.annotation.delegate import scala.util.Try object AwsEndpointConfiguration { - trait Task { + trait Task extends EmbulkTask { @Config("endpoint") @ConfigDefault("null") def getEndpoint: Optional[String] diff --git a/src/main/scala/org/embulk/input/dynamodb/aws/HttpProxy.scala b/src/main/scala/org/embulk/input/dynamodb/aws/HttpProxy.scala index edd1fb0..c986f38 100644 --- a/src/main/scala/org/embulk/input/dynamodb/aws/HttpProxy.scala +++ b/src/main/scala/org/embulk/input/dynamodb/aws/HttpProxy.scala @@ -3,12 +3,13 @@ package org.embulk.input.dynamodb.aws import java.util.Optional import com.amazonaws.{ClientConfiguration, Protocol} -import org.embulk.config.{Config, ConfigDefault, ConfigException} +import org.embulk.config.ConfigException +import org.embulk.util.config.{Task => EmbulkTask, Config, ConfigDefault} import org.embulk.input.dynamodb.aws.HttpProxy.Task object HttpProxy { - trait Task { + trait Task extends EmbulkTask { @Config("host") @ConfigDefault("null") @@ -50,8 +51,8 @@ class HttpProxy(task: Task) { case None => throw new ConfigException( s"'${task.getProtocol}' is unsupported: `protocol` must be one of [${Protocol.values - .map(v => s"'$v'") - .mkString(", ")}]." + .map(v => s"'$v'") + .mkString(", ")}]." ) } diff --git a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValue.scala b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValue.scala index 8d4449a..2345022 100644 --- a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValue.scala +++ b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValue.scala @@ -5,15 +5,15 @@ import java.nio.charset.StandardCharsets import java.util.{Optional, List => JList, Map => JMap} import com.amazonaws.services.dynamodbv2.model.AttributeValue -import org.embulk.config.{Config, ConfigDefault, Task => EmbulkTask} +import org.embulk.util.config.{Config, ConfigDefault, Task => EmbulkTask} import scala.jdk.CollectionConverters._ import scala.util.chaining._ -/** - * TODO: I want to bind directly `org.embulk.config.Config`` to `com.amazonaws.services.dynamodbv2.model.AttributeValue`. - * Should I implement `com.amazonaws.transform.JsonUnmarshallerContext`? - **/ +/** TODO: I want to bind directly `org.embulk.util.config.Config`` to + * `com.amazonaws.services.dynamodbv2.model.AttributeValue`. Should I implement + * `com.amazonaws.transform.JsonUnmarshallerContext`? + */ object DynamodbAttributeValue { trait Task extends EmbulkTask { diff --git a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValueEmbulkTypeTransformable.scala b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValueEmbulkTypeTransformable.scala index 67c44f1..188e497 100644 --- a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValueEmbulkTypeTransformable.scala +++ b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbAttributeValueEmbulkTypeTransformable.scala @@ -4,11 +4,13 @@ import java.nio.ByteBuffer import java.nio.charset.StandardCharsets import org.embulk.input.dynamodb.logger -import org.embulk.spi.time.{Timestamp, TimestampParser} +import org.embulk.spi.time.Timestamp +import org.embulk.util.timestamp.TimestampFormatter import org.msgpack.value.{Value, ValueFactory} import scala.jdk.CollectionConverters._ import scala.util.chaining._ +import java.time.Instant object DynamodbAttributeValueEmbulkTypeTransformable { @@ -50,7 +52,7 @@ object DynamodbAttributeValueEmbulkTypeTransformable { case class DynamodbAttributeValueEmbulkTypeTransformable( attributeValue: DynamodbAttributeValue, typeEnforcer: Option[DynamodbAttributeValueType] = None, - timestampParser: Option[TimestampParser] = None + timestampFormatter: Option[TimestampFormatter] = None ) { private def fromAttributeValueType: DynamodbAttributeValueType = @@ -142,10 +144,14 @@ case class DynamodbAttributeValueEmbulkTypeTransformable( Option(fromAttributeValueType match { case DynamodbAttributeValueType.S => - if (DynamodbAttributeValueEmbulkTypeTransformable.TRUTHY_STRINGS - .contains(attributeValue.getS)) true - else if (DynamodbAttributeValueEmbulkTypeTransformable.FALSY_STRINGS - .contains(attributeValue.getS)) false + if ( + DynamodbAttributeValueEmbulkTypeTransformable.TRUTHY_STRINGS + .contains(attributeValue.getS) + ) true + else if ( + DynamodbAttributeValueEmbulkTypeTransformable.FALSY_STRINGS + .contains(attributeValue.getS) + ) false else return None case DynamodbAttributeValueType.N => convertNAsLongOrDouble(attributeValue.getN) match { @@ -154,11 +160,15 @@ case class DynamodbAttributeValueEmbulkTypeTransformable( } case DynamodbAttributeValueType.B => val s = convertBAsString(attributeValue.getB) - if (DynamodbAttributeValueEmbulkTypeTransformable.TRUTHY_STRINGS - .contains(s)) + if ( + DynamodbAttributeValueEmbulkTypeTransformable.TRUTHY_STRINGS + .contains(s) + ) true - else if (DynamodbAttributeValueEmbulkTypeTransformable.FALSY_STRINGS - .contains(s)) false + else if ( + DynamodbAttributeValueEmbulkTypeTransformable.FALSY_STRINGS + .contains(s) + ) false else return None case DynamodbAttributeValueType.BOOL => attributeValue.getBOOL case unsupported => @@ -221,9 +231,9 @@ case class DynamodbAttributeValueEmbulkTypeTransformable( if (attributeValue.isNull) return None Option(fromAttributeValueType match { - case DynamodbAttributeValueType.S => attributeValue.getS - case DynamodbAttributeValueType.N => attributeValue.getN - case DynamodbAttributeValueType.B => convertBAsString(attributeValue.getB) + case DynamodbAttributeValueType.S => attributeValue.getS + case DynamodbAttributeValueType.N => attributeValue.getN + case DynamodbAttributeValueType.B => convertBAsString(attributeValue.getB) case DynamodbAttributeValueType.SS => asMessagePack.map(_.toJson).get case DynamodbAttributeValueType.NS => asMessagePack.map(_.toJson).get case DynamodbAttributeValueType.BS => asMessagePack.map(_.toJson).get @@ -238,8 +248,8 @@ case class DynamodbAttributeValueEmbulkTypeTransformable( }) } - def asTimestamp: Option[Timestamp] = { - timestampParser.flatMap(p => asString.map(p.parse)) + def asTimestamp: Option[Instant] = { + timestampFormatter.flatMap(p => asString.map(p.parse)) } } diff --git a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemReader.scala b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemReader.scala index 96dd77f..34081fe 100644 --- a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemReader.scala +++ b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemReader.scala @@ -3,6 +3,7 @@ package org.embulk.input.dynamodb.item import org.embulk.spi.Column import org.embulk.spi.time.Timestamp import org.msgpack.value.Value +import java.time.Instant case class DynamodbItemReader( private val schema: DynamodbItemSchema, @@ -28,7 +29,7 @@ case class DynamodbItemReader( DynamodbAttributeValueEmbulkTypeTransformable( value, typeEnforcer = schema.getAttributeType(name), - timestampParser = schema.getTimestampParser(name) + timestampFormatter = schema.getTimestampFormatter(name) ) } @@ -52,7 +53,7 @@ case class DynamodbItemReader( .get(column.getName) .flatMap(v => getTransformable(column.getName, v).asDouble) - def getTimestamp(column: Column): Option[Timestamp] = + def getTimestamp(column: Column): Option[Instant] = currentItem .get(column.getName) .flatMap(v => getTransformable(column.getName, v).asTimestamp) diff --git a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala index 24f00b5..b994ad1 100644 --- a/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala +++ b/src/main/scala/org/embulk/input/dynamodb/item/DynamodbItemSchema.scala @@ -4,10 +4,10 @@ import java.util.{Optional, List => JList} import com.amazonaws.services.dynamodbv2.model.AttributeValue import com.fasterxml.jackson.annotation.{JsonCreator, JsonValue} -import org.embulk.config.{Config, ConfigDefault, Task => EmbulkTask} +import org.embulk.util.config.{Config, ConfigDefault, Task => EmbulkTask} import org.embulk.spi.{Column, PageBuilder, Schema} import org.embulk.spi.`type`.{Type, Types} -import org.embulk.spi.time.TimestampParser +import org.embulk.util.timestamp.TimestampFormatter import scala.jdk.CollectionConverters._ import scala.util.chaining._ @@ -15,9 +15,7 @@ import scala.util.Try object DynamodbItemSchema { - trait ColumnTask - extends EmbulkTask - with TimestampParser.TimestampColumnOption { + trait ColumnTask extends EmbulkTask { @Config("name") def getName: String @@ -28,34 +26,21 @@ object DynamodbItemSchema { @Config("attribute_type") @ConfigDefault("null") def getAttributeType: Optional[String] - } - @deprecated( - message = "for DeprecatedDynamodbInputPlugin", - since = "0.3.0" - ) - case class SchemaConfigCompat(columnTasks: Seq[ColumnTask]) { - @JsonCreator - def this(columnTasks: JList[ColumnTask]) = - this(columnTasks.asScala.toSeq) - - @JsonValue - def getColumnTasks: JList[ColumnTask] = columnTasks.asJava - - def toSchema: Schema = - Schema - .builder() - .tap { b => - columnTasks.foreach { t => - b.add(t.getName, t.getType) - } - } - .build() + @Config("timezone") + @ConfigDefault("null") + def getTimeZoneId: Optional[String] + + @Config("format") + @ConfigDefault("null") + def getFormat: Optional[String] - def isEmpty: Boolean = columnTasks.isEmpty + @Config("date") + @ConfigDefault("null") + def getDate: Optional[String] } - trait Task extends EmbulkTask with TimestampParser.Task { + trait Task extends EmbulkTask { @Config("json_column_name") @ConfigDefault("\"record\"") @@ -63,7 +48,7 @@ object DynamodbItemSchema { @Config("columns") @ConfigDefault("[]") - def getColumns: Jlist[ColumnTask] + def getColumns: JList[ColumnTask] @Config("default_timezone") @ConfigDefault("\"UTC\"") @@ -88,19 +73,30 @@ case class DynamodbItemSchema(task: DynamodbItemSchema.Task) { .tap { b => if (isItemAsJson) b.add(task.getJsonColumnName, Types.JSON) else - task.getColumns.toSeq.foreach { t => + task.getColumns.asScala.foreach { t => b.add(t.getName, t.getType) } } .build() - private lazy val timestampParsers: Map[String, TimestampParser] = - task.getColumns.toSeq.map { columnTask => - columnTask.getName -> TimestampParser.of(task, columnTask) + private lazy val timestampFormatters: Map[String, TimestampFormatter] = + task.getColumns.asScala.map { columnTask => + columnTask.getName -> TimestampFormatter + .builder( + columnTask.getFormat.orElse(task.getDefaultTimestampFormat), + true + ) + .setDefaultZoneFromString( + columnTask.getTimeZoneId.orElse(task.getDefaultTimeZoneId) + ) + .setDefaultDateFromString( + columnTask.getDate.orElse(task.getDefaultDate) + ) + .build() }.toMap private lazy val attributeTypes: Map[String, DynamodbAttributeValueType] = - task.getColumns.toSeq + task.getColumns.asScala .filter(_.getAttributeType.isPresent) .map { columnTask => columnTask.getName -> DynamodbAttributeValueType( @@ -116,11 +112,11 @@ case class DynamodbItemSchema(task: DynamodbItemSchema.Task) { def getEmbulkSchema: Schema = embulkSchema - def getTimestampParser(column: Column): Option[TimestampParser] = - timestampParsers.get(column.getName) + def getTimestampFormatter(column: Column): Option[TimestampFormatter] = + timestampFormatters.get(column.getName) - def getTimestampParser(columnName: String): Option[TimestampParser] = - getEmbulkColumn(columnName).flatMap(getTimestampParser) + def getTimestampFormatter(columnName: String): Option[TimestampFormatter] = + getEmbulkColumn(columnName).flatMap(getTimestampFormatter) def getAttributeType(column: Column): Option[DynamodbAttributeValueType] = attributeTypes.get(column.getName) @@ -136,7 +132,7 @@ case class DynamodbItemSchema(task: DynamodbItemSchema.Task) { def getEmbulkColumn(columnIndex: Int): Option[Column] = Try(getEmbulkSchema.getColumn(columnIndex)).toOption - def isItemAsJson: Boolean = task.getColumns.toSeq.isEmpty + def isItemAsJson: Boolean = task.getColumns.asScala.isEmpty def visitColumns(visitor: DynamodbItemColumnVisitor): Unit = getEmbulkSchema.visitColumns(visitor) diff --git a/src/main/scala/org/embulk/input/dynamodb/operation/AbstractDynamodbOperation.scala b/src/main/scala/org/embulk/input/dynamodb/operation/AbstractDynamodbOperation.scala index 9a34352..9dcda78 100644 --- a/src/main/scala/org/embulk/input/dynamodb/operation/AbstractDynamodbOperation.scala +++ b/src/main/scala/org/embulk/input/dynamodb/operation/AbstractDynamodbOperation.scala @@ -13,12 +13,8 @@ import com.amazonaws.services.dynamodbv2.model.{ ReturnConsumedCapacity, Select } -import org.embulk.config.{ - Config, - ConfigDefault, - ConfigException, - Task => EmbulkTask -} +import org.embulk.config.ConfigException +import org.embulk.util.config.{Config, ConfigDefault, Task => EmbulkTask} import org.embulk.input.dynamodb.item.DynamodbAttributeValue import scala.jdk.CollectionConverters._ @@ -159,7 +155,9 @@ abstract class AbstractDynamodbOperation( throw new ConfigException( "\"batch_size\" must be greater than or equal to 1." ) - req.setLimit(JInteger.valueOf(v)) // Note: Use BatchSize for the limit per a request. + req.setLimit( + JInteger.valueOf(v) + ) // Note: Use BatchSize for the limit per a request. } task.getProjectionExpression.ifPresent(req.setProjectionExpression) task.getReturnConsumedCapacity.ifPresent(req.setReturnConsumedCapacity) diff --git a/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbOperationProxy.scala b/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbOperationProxy.scala index 7b5edb7..8863b27 100644 --- a/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbOperationProxy.scala +++ b/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbOperationProxy.scala @@ -4,11 +4,12 @@ import java.util.Optional import com.amazonaws.services.dynamodbv2.model.AttributeValue import com.amazonaws.services.dynamodbv2.AmazonDynamoDB -import org.embulk.config.{ - Config, - ConfigDefault, - ConfigException, - Task => EmbulkTask +import org.embulk.config.ConfigException +import org.embulk.util.config.{Config, ConfigDefault, Task => EmbulkTask} +import org.embulk.input.dynamodb.operation.{ + DynamodbQueryOperation, + DynamodbScanOperation, + EmbulkDynamodbOperation } object DynamodbOperationProxy { diff --git a/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbQueryOperation.scala b/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbQueryOperation.scala index 3667880..b4c99b3 100644 --- a/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbQueryOperation.scala +++ b/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbQueryOperation.scala @@ -2,7 +2,7 @@ package org.embulk.input.dynamodb.operation import com.amazonaws.services.dynamodbv2.model.{AttributeValue, QueryRequest} import com.amazonaws.services.dynamodbv2.AmazonDynamoDB -import org.embulk.config.{Config, ConfigDefault} +import org.embulk.util.config.{Config, ConfigDefault} import org.embulk.input.dynamodb.logger import scala.jdk.CollectionConverters._ diff --git a/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbScanOperation.scala b/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbScanOperation.scala index ca57916..cc76d1d 100644 --- a/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbScanOperation.scala +++ b/src/main/scala/org/embulk/input/dynamodb/operation/DynamodbScanOperation.scala @@ -4,7 +4,8 @@ import java.util.Optional import com.amazonaws.services.dynamodbv2.model.{AttributeValue, ScanRequest} import com.amazonaws.services.dynamodbv2.AmazonDynamoDB -import org.embulk.config.{Config, ConfigDefault, ConfigException} +import org.embulk.config.ConfigException +import org.embulk.util.config.{Config, ConfigDefault} import org.embulk.input.dynamodb.logger import scala.jdk.CollectionConverters._ diff --git a/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala b/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala index 9c7c29b..6bea047 100644 --- a/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala +++ b/src/test/scala/org/embulk/input/dynamodb/AwsCredentialsTest.scala @@ -1,7 +1,7 @@ package org.embulk.input.dynamodb import com.amazonaws.services.securitytoken.model.AWSSecurityTokenServiceException -import org.embulk.config.{ConfigException, ConfigSource} +import org.embulk.config.{ConfigSource, ConfigException} import org.embulk.input.dynamodb.aws.AwsCredentials import org.embulk.input.dynamodb.testutil.EmbulkTestBase import org.hamcrest.CoreMatchers._ @@ -36,7 +36,7 @@ class AwsCredentialsTest extends EmbulkTestBase { ) def doTest(inConfig: ConfigSource): Unit = { - val task: PluginTask = inConfig.loadConfig(classOf[PluginTask]) + val task: PluginTask = PluginTask.load(inConfig) val provider = AwsCredentials(task).createAwsCredentialsProvider val cred = provider.getCredentials assertThat(cred.getAWSAccessKeyId, notNullValue()) @@ -80,9 +80,12 @@ class AwsCredentialsTest extends EmbulkTestBase { val inConfig: ConfigSource = defaultInConfig .set("auth_method", "basic") - Assert.assertThrows(classOf[ConfigException], () => { - doTest(inConfig) - }) + Assert.assertThrows( + classOf[ConfigException], + () => { + doTest(inConfig) + } + ) } @Test @@ -110,9 +113,12 @@ class AwsCredentialsTest extends EmbulkTestBase { .set("auth_method", "profile") .set("profile_name", "DO_NOT_EXIST") - Assert.assertThrows(classOf[IllegalArgumentException], () => { - doTest(inConfig) - }) + Assert.assertThrows( + classOf[IllegalArgumentException], + () => { + doTest(inConfig) + } + ) } @Test @@ -133,8 +139,11 @@ class AwsCredentialsTest extends EmbulkTestBase { .set("role_arn", "DO_NOT_EXIST") .set("role_session_name", "dummy") - Assert.assertThrows(classOf[AWSSecurityTokenServiceException], () => { - doTest(inConfig) - }) + Assert.assertThrows( + classOf[AWSSecurityTokenServiceException], + () => { + doTest(inConfig) + } + ) } } diff --git a/src/test/scala/org/embulk/input/dynamodb/DynamodbOperationTest.scala b/src/test/scala/org/embulk/input/dynamodb/DynamodbOperationTest.scala index 3452a10..6005112 100644 --- a/src/test/scala/org/embulk/input/dynamodb/DynamodbOperationTest.scala +++ b/src/test/scala/org/embulk/input/dynamodb/DynamodbOperationTest.scala @@ -105,9 +105,12 @@ class DynamodbOperationTest extends EmbulkTestBase { |scan: | limit: 1 |""".stripMargin) - runInput(inScanConfig, { result => - assert(result.size.equals(1)) - }) + runInput( + inScanConfig, + { result => + assert(result.size.equals(1)) + } + ) val inQueryConfig: ConfigSource = loadConfigSourceFromYamlString(s""" |type: dynamodb @@ -124,8 +127,11 @@ class DynamodbOperationTest extends EmbulkTestBase { | ":v": {S: a} | limit: 1 |""".stripMargin) - runInput(inQueryConfig, { result => - assert(result.size.equals(1)) - }) + runInput( + inQueryConfig, + { result => + assert(result.size.equals(1)) + } + ) } } diff --git a/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationBackwardCompatibilityTest.scala b/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationBackwardCompatibilityTest.scala index 3db620e..d314c0c 100644 --- a/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationBackwardCompatibilityTest.scala +++ b/src/test/scala/org/embulk/input/dynamodb/DynamodbQueryOperationBackwardCompatibilityTest.scala @@ -88,7 +88,8 @@ class DynamodbQueryOperationBackwardCompatibilityTest extends EmbulkTestBase { } runInput( - embulkInConfig, { result: Seq[Seq[AnyRef]] => + embulkInConfig, + { result: Seq[Seq[AnyRef]] => val head = result.head assertThat(head(0).toString, is("key-1")) assertThat(head(1).asInstanceOf[Long], is(0L)) diff --git a/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationBackwardCompatibilityTest.scala b/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationBackwardCompatibilityTest.scala index 2bab719..a007329 100644 --- a/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationBackwardCompatibilityTest.scala +++ b/src/test/scala/org/embulk/input/dynamodb/DynamodbScanOperationBackwardCompatibilityTest.scala @@ -88,7 +88,8 @@ class DynamodbScanOperationBackwardCompatibilityTest extends EmbulkTestBase { } runInput( - embulkInConfig, { result: Seq[Seq[AnyRef]] => + embulkInConfig, + { result: Seq[Seq[AnyRef]] => val head = result.head assertThat(head(0).toString, is("key-1")) assertThat(head(1).asInstanceOf[Long], is(0L)) diff --git a/src/test/scala/org/embulk/input/dynamodb/testutil/EmbulkTestBase.scala b/src/test/scala/org/embulk/input/dynamodb/testutil/EmbulkTestBase.scala index 0355666..a45e031 100644 --- a/src/test/scala/org/embulk/input/dynamodb/testutil/EmbulkTestBase.scala +++ b/src/test/scala/org/embulk/input/dynamodb/testutil/EmbulkTestBase.scala @@ -75,22 +75,21 @@ trait EmbulkTestBase { } def runInput(inConfig: ConfigSource, test: Seq[Seq[AnyRef]] => Unit): Unit = { - runtime.getInstance(classOf[DynamodbInputPlugin]).tap { plugin => - plugin.transaction( - inConfig, - (taskSource: TaskSource, schema: Schema, taskCount: Int) => { - val output: MockPageOutput = new MockPageOutput() - val reports: Seq[TaskReport] = 0.until(taskCount).map { taskIndex => - plugin.run(taskSource, schema, taskIndex, output) - } - output.finish() + val plugin = new DynamodbInputPlugin() + plugin.transaction( + inConfig, + (taskSource: TaskSource, schema: Schema, taskCount: Int) => { + val output: MockPageOutput = new MockPageOutput() + val reports: Seq[TaskReport] = 0.until(taskCount).map { taskIndex => + plugin.run(taskSource, schema, taskIndex, output) + } + output.finish() - test(Pages.toObjects(schema, output.pages).asScala.toSeq.map(_.toSeq)) + test(Pages.toObjects(schema, output.pages).asScala.toSeq.map(_.toSeq)) - reports.asJava - } - ) - } + reports.asJava + } + ) } def loadConfigSourceFromYamlString(yaml: String): ConfigSource = {