diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d4eb56..92c9819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # JDBC Component + +## 2.4.0 (March 12, 2020) + +* Add Firebird RDBMS support + ## 2.3.2 (October 21, 2019) * Add rebound mechanism in case of deadlocks for actions: Insert, UpsertByPK, DeleteByPK diff --git a/README.md b/README.md index db3e765..62f6ee7 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,13 @@ For integration-testing is needed to specify following environment variables: - ``CONN_DBNAME_POSTGRESQL`` - DataBase name - ``CONN_HOST_POSTGRESQL`` - DataBase host - ``CONN_PORT_POSTGRESQL`` - DataBase port + 5. Connection to Firebird: + - ``CONN_USER_FIREBIRD`` - user login + - ``CONN_PASSWORD_FIREBIRD`` - user password + - ``CONN_DBNAME_FIREBIRD`` - DataBase name + - ``CONN_HOST_FIREBIRD`` - DataBase host + - ``CONN_PORT_FIREBIRD`` - DataBase port + - ``CONN_PORT_FIREBIRD`` - DataBase port #### Others ## Credentials You need to use following properties to configure credentials: @@ -74,6 +81,7 @@ Optional field. Provide port of the server instance, as by default: - ``5432`` - PostgreSQL - ``1521`` - Oracle - ``1433`` - MSSQL +- ``3050`` - Firebird ### Database Name Provide name of database at the instance that you want to interact with. ### User @@ -341,15 +349,17 @@ Please use [**Upsert row by primary key**](#upsert-row-by-primary-key-action) in - ``PostgreSQL`` - compatible with PostgreSQL 8.2 and higher - ``Oracle`` - compatible with Oracle Database 8.1.7 - 12.1.0.2 - ``MSSQL`` - compatible with Microsoft SQL Server 2008 R2 and higher +- ``Firebird`` - compatible with Firebird 2.0 and higher 3. The current implementation of the action ``Upsert By Primary Key`` doesn't mark non-nullable fields as required fields at a dynamic metadata. In case of updating such fields with an empty value you will get SQL Exception ``Cannot insert the value NULL into...``. You should manually fill in all non-nullable fields with previous data, if you want to update part of columns in a row, even if data in that fields doesn't change. 4. The current implementation of the action ``Execute stored procedure`` doesn't support ResultSet MSSQL output. 5. The current implementation of the action ``Execute stored procedure`` doesn't support any array types parameters. (MySQL does not have schemas by definition) 6. Rebound mechanism only works for this SQL State: - - ``MySQL``: 40001, XA102 - - ``Oracle``: 61000 - - ``MSSQL``: 40001 - - ``PostgreSQL``: 40P01 +- ``MySQL``: 40001, XA102 +- ``Oracle``: 61000 +- ``MSSQL``: 40001 +- ``PostgreSQL``: 40P01 +- ``Firebird``: 10054, 10038 ## License Apache-2.0 © [elastic.io GmbH](https://www.elastic.io "elastic.io GmbH") diff --git a/build.gradle b/build.gradle index 3f969b2..c5da106 100755 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group = 'io.elastic' -version = '2.4.0' +version = '6.3' apply plugin: 'java' apply plugin: 'idea' apply plugin: 'eclipse' @@ -28,11 +28,12 @@ test { } task integrationTest(type: Test) { + maxParallelForks = 1 testLogging { showStandardStreams = true } filter() { - includeTestsMatching "io.elastic.jdbc.integration.*" + includeTestsMatching 'io.elastic.jdbc.integration.*' } } @@ -48,16 +49,17 @@ targetCompatibility = 1.8 repositories { maven { - url "https://oss.sonatype.org/content/repositories/snapshots" + url 'https://oss.sonatype.org/content/repositories/snapshots' } mavenCentral() mavenLocal() } dependencies { - compile "io.elastic:sailor-jvm:2.1.0" - compile "mysql:mysql-connector-java:8.0.11" + compile 'io.elastic:sailor-jvm:2.1.3' + compile 'mysql:mysql-connector-java:8.0.11' compile group: 'org.postgresql', name: 'postgresql', version: '42.2.4' + compile group: 'org.firebirdsql.jdbc', name: 'jaybird-jdk18', version: '3.0.8' compile files("./lib/ojdbc6.jar") compile files("./lib/sqljdbc4.jar") compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5' @@ -67,7 +69,7 @@ dependencies { compile 'com.fasterxml.jackson.core:jackson-databind:2.7.3' testCompile group: 'io.github.cdimascio', name: 'java-dotenv', version: '5.1.0' - testCompile "org.spockframework:spock-core:1.1-groovy-2.4" + testCompile 'org.spockframework:spock-core:1.1-groovy-2.4' testCompile 'org.hsqldb:hsqldb:2.0.0' } diff --git a/component.json b/component.json index 54a8280..69e7fee 100755 --- a/component.json +++ b/component.json @@ -13,7 +13,8 @@ "mysql": "MySQL", "postgresql": "PostgreSQL", "oracle": "Oracle", - "mssql": "MSSQL" + "mssql": "MSSQL", + "firebirdsql": "Firebird" }, "prompt": "Please Select Database Type" }, diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index b761216..758de96 100755 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 79004d9..fab963f 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Aug 13 13:58:37 EEST 2019 +#Thu Apr 02 13:29:34 EEST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 4bf48f6..cccdd3d 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +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 @@ -101,7 +100,7 @@ if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else - warn "Could not queryOld maximum file descriptor limit: $MAX_FD_LIMIT" + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi @@ -114,6 +113,7 @@ fi if $cygwin ; 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` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# 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" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..f955316 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@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= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@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= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/src/main/java/io/elastic/jdbc/actions/DeleteRowByPrimaryKey.java b/src/main/java/io/elastic/jdbc/actions/DeleteRowByPrimaryKey.java index ed86a44..7db5692 100644 --- a/src/main/java/io/elastic/jdbc/actions/DeleteRowByPrimaryKey.java +++ b/src/main/java/io/elastic/jdbc/actions/DeleteRowByPrimaryKey.java @@ -35,27 +35,11 @@ public void execute(ExecutionParameters parameters) { StringBuilder primaryKey = new StringBuilder(); StringBuilder primaryValue = new StringBuilder(); Integer primaryKeysCount = 0; - String tableName = ""; - String dbEngine = ""; Boolean nullableResult = false; - - if (configuration.containsKey(PROPERTY_TABLE_NAME) - && Utils.getNonNullString(configuration, PROPERTY_TABLE_NAME).length() != 0) { - tableName = configuration.getString(PROPERTY_TABLE_NAME); - } else if (snapshot.containsKey(PROPERTY_TABLE_NAME) - && Utils.getNonNullString(snapshot, PROPERTY_TABLE_NAME).length() != 0) { - tableName = snapshot.getString(PROPERTY_TABLE_NAME); - } else { - throw new RuntimeException("Table name is required field"); - } - - if (Utils.getNonNullString(configuration, PROPERTY_DB_ENGINE).length() != 0) { - dbEngine = configuration.getString(PROPERTY_DB_ENGINE); - } else if (Utils.getNonNullString(snapshot, PROPERTY_DB_ENGINE).length() != 0) { - dbEngine = snapshot.getString(PROPERTY_DB_ENGINE); - } else { - throw new RuntimeException("DB Engine is required field"); - } + final String dbEngine = Utils.getDbEngine(configuration); + final boolean isOracle = dbEngine.equals(Engines.ORACLE.name().toLowerCase()); + final boolean isFirebird = dbEngine.equals(Engines.FIREBIRDSQL.name().toLowerCase()); + final String tableName = Utils.getTableName(configuration, (isOracle || isFirebird)); if (Utils.getNonNullString(configuration, PROPERTY_NULLABLE_RESULT).equals("true")) { nullableResult = true; @@ -73,8 +57,7 @@ public void execute(ExecutionParameters parameters) { if (primaryKeysCount == 1) { try (Connection connection = Utils.getConnection(configuration)) { LOGGER.info("Executing delete row by primary key action"); - boolean isOracle = dbEngine.equals(Engines.ORACLE.name().toLowerCase()); - Utils.columnTypes = Utils.getColumnTypes(connection, isOracle, tableName); + Utils.columnTypes = Utils.getColumnTypes(connection, tableName); LOGGER.info("Detected column types: " + Utils.columnTypes); try { QueryFactory queryFactory = new QueryFactory(); diff --git a/src/main/java/io/elastic/jdbc/actions/InsertAction.java b/src/main/java/io/elastic/jdbc/actions/InsertAction.java index 3a8a111..6f41c66 100644 --- a/src/main/java/io/elastic/jdbc/actions/InsertAction.java +++ b/src/main/java/io/elastic/jdbc/actions/InsertAction.java @@ -30,10 +30,11 @@ public void execute(ExecutionParameters parameters) { final JsonObject body = parameters.getMessage().getBody(); final String dbEngine = Utils.getDbEngine(configuration); final boolean isOracle = dbEngine.equals(Engines.ORACLE.name().toLowerCase()); - final String tableName = Utils.getTableName(configuration, isOracle); + final boolean isFirebird = dbEngine.equals(Engines.FIREBIRDSQL.name().toLowerCase()); + final String tableName = Utils.getTableName(configuration, (isOracle || isFirebird)); LOGGER.info("Found dbEngine: '{}' and tableName: '{}'", dbEngine, tableName); try (Connection connection = Utils.getConnection(configuration)) { - Utils.columnTypes = Utils.getColumnTypes(connection, isOracle, tableName); + Utils.columnTypes = Utils.getColumnTypes(connection, tableName); LOGGER.info("Detected column types: " + Utils.columnTypes); LOGGER.info("Inserting in table '{}' values '{}'", tableName, body); QueryFactory queryFactory = new QueryFactory(); diff --git a/src/main/java/io/elastic/jdbc/actions/LookupRowByPrimaryKey.java b/src/main/java/io/elastic/jdbc/actions/LookupRowByPrimaryKey.java index 52f1fc9..24058e9 100644 --- a/src/main/java/io/elastic/jdbc/actions/LookupRowByPrimaryKey.java +++ b/src/main/java/io/elastic/jdbc/actions/LookupRowByPrimaryKey.java @@ -35,10 +35,10 @@ public void execute(ExecutionParameters parameters) { JsonObject snapshot = parameters.getSnapshot(); StringBuilder primaryKey = new StringBuilder(); StringBuilder primaryValue = new StringBuilder(); - Integer primaryKeysCount = 0; + int primaryKeysCount = 0; String tableName = ""; String dbEngine = ""; - Boolean nullableResult = false; + boolean nullableResult = false; if (configuration.containsKey(PROPERTY_TABLE_NAME) && Utils.getNonNullString(configuration, PROPERTY_TABLE_NAME).length() != 0) { @@ -64,8 +64,6 @@ public void execute(ExecutionParameters parameters) { nullableResult = true; } - boolean isOracle = dbEngine.equals(Engines.ORACLE.name().toLowerCase()); - for (Map.Entry entry : body.entrySet()) { LOGGER.info("{} = {}", entry.getKey(), entry.getValue()); primaryKey.append(entry.getKey()); @@ -77,7 +75,7 @@ public void execute(ExecutionParameters parameters) { try (Connection connection = Utils.getConnection(configuration)) { LOGGER.info("Executing lookup row by primary key action"); - Utils.columnTypes = Utils.getColumnTypes(connection, isOracle, tableName); + Utils.columnTypes = Utils.getColumnTypes(connection, tableName); LOGGER.info("Detected column types: " + Utils.columnTypes); try { QueryFactory queryFactory = new QueryFactory(); diff --git a/src/main/java/io/elastic/jdbc/actions/UpsertRowByPrimaryKey.java b/src/main/java/io/elastic/jdbc/actions/UpsertRowByPrimaryKey.java index 890ba84..da66fb6 100644 --- a/src/main/java/io/elastic/jdbc/actions/UpsertRowByPrimaryKey.java +++ b/src/main/java/io/elastic/jdbc/actions/UpsertRowByPrimaryKey.java @@ -31,7 +31,7 @@ public void execute(ExecutionParameters parameters) { JsonObject resultRow; String tableName; String dbEngine; - String catalog = null; + String catalog = ""; String schemaName = ""; String primaryKey = ""; int primaryKeysCount = 0; @@ -55,14 +55,18 @@ public void execute(ExecutionParameters parameters) { } LOGGER.info("Executing lookup primary key"); - boolean isOracle = dbEngine.equals(Engines.ORACLE.name().toLowerCase()); - Boolean isMysql = configuration.getString("dbEngine").equals("mysql"); + final boolean isOracle = dbEngine.equals(Engines.ORACLE.name().toLowerCase()); + final boolean isMysql = dbEngine.equals(Engines.MYSQL.name().toLowerCase()); + final boolean isFirebird = dbEngine.equals(Engines.FIREBIRDSQL.name().toLowerCase()); try (Connection connection = Utils.getConnection(configuration)) { DatabaseMetaData dbMetaData = connection.getMetaData(); if (isMysql) { catalog = configuration.getString("databaseName"); } + if (isFirebird) { + tableName = tableName.toUpperCase(); + } if (tableName.contains(".")) { schemaName = (isOracle) ? tableName.split("\\.")[0].toUpperCase() : tableName.split("\\.")[0]; @@ -78,7 +82,7 @@ public void execute(ExecutionParameters parameters) { } if (primaryKeysCount == 1) { LOGGER.info("Executing upsert row by primary key action"); - Utils.columnTypes = Utils.getColumnTypes(connection, isOracle, tableName); + Utils.columnTypes = Utils.getColumnTypes(connection, tableName); LOGGER.info("Detected column types: " + Utils.columnTypes); QueryFactory queryFactory = new QueryFactory(); Query query = queryFactory.getQuery(dbEngine); diff --git a/src/main/java/io/elastic/jdbc/providers/ColumnNamesForInsertProvider.java b/src/main/java/io/elastic/jdbc/providers/ColumnNamesForInsertProvider.java index 6c22e62..d7c0297 100644 --- a/src/main/java/io/elastic/jdbc/providers/ColumnNamesForInsertProvider.java +++ b/src/main/java/io/elastic/jdbc/providers/ColumnNamesForInsertProvider.java @@ -54,7 +54,8 @@ private JsonObject getInputMetaData(JsonObject configuration) { LOGGER.info("Getting input metadata..."); final String dbEngine = Utils.getDbEngine(configuration); final boolean isOracle = dbEngine.equals(Engines.ORACLE.name().toLowerCase()); - final String tableName = Utils.getTableName(configuration, isOracle); + final boolean isFirebird = dbEngine.equals(Engines.FIREBIRDSQL.name().toLowerCase()); + final String tableName = Utils.getTableName(configuration, (isOracle || isFirebird)); JsonObjectBuilder propertiesIn = Json.createObjectBuilder(); boolean isEmpty = true; @@ -89,7 +90,7 @@ private JsonObject getInputMetaData(JsonObject configuration) { final boolean isPrimaryKey = Utils.isPrimaryKey(primaryKeysNames, fieldName); final boolean isNotNull = Utils.isNotNull(resultSet); - final boolean isAutoincrement = Utils.isAutoincrement(resultSet, isOracle); + final boolean isAutoincrement = Utils.isAutoincrement(resultSet, (isOracle || isFirebird)); final boolean isCalculated = Utils.isCalculated(resultSet, dbEngine); LOGGER .info( diff --git a/src/main/java/io/elastic/jdbc/query_builders/Firebird.java b/src/main/java/io/elastic/jdbc/query_builders/Firebird.java new file mode 100644 index 0000000..f5b7602 --- /dev/null +++ b/src/main/java/io/elastic/jdbc/query_builders/Firebird.java @@ -0,0 +1,190 @@ +package io.elastic.jdbc.query_builders; + +import io.elastic.jdbc.providers.ProcedureFieldsNameProvider; +import io.elastic.jdbc.utils.ProcedureParameter; +import io.elastic.jdbc.utils.ProcedureParameter.Direction; +import io.elastic.jdbc.utils.Utils; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonValue; + +public class Firebird extends Query { + + public ArrayList executePolling(Connection connection) throws SQLException { + validateQuery(); + + /* workaround to set FIRST operation on the 1st position in the statement */ + StringBuilder sql = new StringBuilder("WITH d AS (SELECT * FROM "); + sql.append(tableName); + sql.append(" WHERE "); + sql.append(pollingField); + sql.append(" > ?) "); + sql.append("SELECT FIRST ? * FROM d"); + if (orderField != null) { + sql.append(" ORDER BY ").append(orderField).append(" ASC"); + } + + return getRowsExecutePolling(connection, sql.toString()); + } + + public JsonObject executeLookup(Connection connection, JsonObject body) throws SQLException { + validateQuery(); + + /* workaround to set FIRST operation on the 1st position in the statement */ + StringBuilder sql = new StringBuilder("SELECT * FROM "); + sql.append(tableName); + sql.append(" WHERE "); + sql.append(lookupField); + sql.append(" = ?"); + sql.append(" ORDER BY ").append(lookupField); + sql.append(" ASC ROWS ? TO ?"); + + return getLookupRow(connection, body, sql.toString(), 1, skipNumber += countNumber); + } + + public int executeDelete(Connection connection, JsonObject body) throws SQLException { + String sql = "DELETE" + + " FROM " + tableName + + " WHERE " + lookupField + " = ?"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + stmt.setString(1, lookupValue); + return stmt.executeUpdate(); + } + } + + public void executeInsert(Connection connection, String tableName, JsonObject body) + throws SQLException { + validateQuery(); + StringBuilder keys = new StringBuilder(); + StringBuilder values = new StringBuilder(); + for (Map.Entry entry : body.entrySet()) { + if (keys.length() > 0) { + keys.append(","); + } + keys.append(entry.getKey()); + if (values.length() > 0) { + values.append(","); + } + values.append("?"); + } + String sql = "INSERT INTO " + tableName + + " (" + keys.toString() + ")" + + " VALUES (" + values.toString() + ")"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + int i = 1; + for (String key : body.keySet()) { + Utils.setStatementParam(stmt, i, key, body); + i++; + } + stmt.execute(); + } + } + + public void executeUpdate(Connection connection, String tableName, String idColumn, + String idValue, JsonObject body) throws SQLException { + validateQuery(); + StringBuilder setString = new StringBuilder(); + for (Map.Entry entry : body.entrySet()) { + if (setString.length() > 0) { + setString.append(","); + } + setString.append(entry.getKey()).append(" = ?"); + } + String sql = "UPDATE " + tableName + + " SET " + setString.toString() + + " WHERE " + idColumn + " = ?"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + int i = 1; + for (String key : body.keySet()) { + Utils.setStatementParam(stmt, i, key, body); + i++; + } + Utils.setStatementParam(stmt, i, idColumn, body); + stmt.execute(); + } + } + + @Override + protected CallableStatement prepareCallableStatement(Connection connection, String procedureName, + Map procedureParams, JsonObject messageBody) + throws SQLException { + CallableStatement stmt = connection.prepareCall( + String.format("{call %s%s}", procedureName, + generateStatementWildcardMask(procedureParams))); + + for (int inc = 1; inc <= procedureParams.size(); inc++) { + final int order = inc; + ProcedureParameter parameter = procedureParams.values() + .stream() + .filter(p -> p.getOrder() == order) + .findFirst().orElseThrow(() -> new IllegalStateException("Can't find parameter by order")); + + if (parameter.getDirection() == Direction.IN || parameter.getDirection() == Direction.INOUT) { + if (parameter.getDirection() == Direction.INOUT) { + stmt.registerOutParameter(inc, parameter.getType()); + } + + String type = Utils.cleanJsonType(Utils.detectColumnType(parameter.getType(), "")); + switch (type) { + case ("number"): + stmt.setObject(inc, + messageBody.getJsonNumber(parameter.getName()).toString(), + parameter.getType()); + break; + case ("boolean"): + stmt.setObject(inc, messageBody.getBoolean(parameter.getName()), + parameter.getType()); + break; + default: + stmt.setObject(inc, messageBody.getString(parameter.getName()), + parameter.getType()); + } + } else if (parameter.getDirection() == Direction.OUT) { + stmt.registerOutParameter(inc, parameter.getType()); + } + } + + return stmt; + } + + @Override + public JsonObject callProcedure(Connection connection, JsonObject body, JsonObject configuration) + throws SQLException { + + Map procedureParams = ProcedureFieldsNameProvider + .getProcedureMetadata(configuration).stream() + .collect(Collectors.toMap(ProcedureParameter::getName, Function.identity())); + + CallableStatement stmt = prepareCallableStatement(connection, + configuration.getString("procedureName"), procedureParams, body); + + stmt.execute(); + + JsonObjectBuilder resultBuilder = Json.createObjectBuilder(); + + procedureParams.values().stream() + .filter(param -> param.getDirection() == Direction.OUT + || param.getDirection() == Direction.INOUT) + .forEach(param -> { + try { + addValueToResultJson(resultBuilder, stmt, procedureParams, param.getName()); + } catch (SQLException e) { + e.printStackTrace(); + } + }); + + stmt.close(); + + return resultBuilder.build(); + } + +} diff --git a/src/main/java/io/elastic/jdbc/query_builders/MySQL.java b/src/main/java/io/elastic/jdbc/query_builders/MySQL.java index 380bd19..705a1fe 100644 --- a/src/main/java/io/elastic/jdbc/query_builders/MySQL.java +++ b/src/main/java/io/elastic/jdbc/query_builders/MySQL.java @@ -51,7 +51,9 @@ public int executeDelete(Connection connection, JsonObject body) throws SQLExcep " FROM " + tableName + " WHERE " + lookupField + " = ?"; try (PreparedStatement stmt = connection.prepareStatement(sql)) { - stmt.setString(1, lookupValue); + for (Map.Entry entry : body.entrySet()) { + Utils.setStatementParam(stmt, 1, entry.getKey(), body); + } return stmt.executeUpdate(); } } diff --git a/src/main/java/io/elastic/jdbc/utils/Engines.java b/src/main/java/io/elastic/jdbc/utils/Engines.java index e511753..7e9d2f3 100644 --- a/src/main/java/io/elastic/jdbc/utils/Engines.java +++ b/src/main/java/io/elastic/jdbc/utils/Engines.java @@ -1,6 +1,18 @@ package io.elastic.jdbc.utils; public enum Engines { + FIREBIRDSQL("org.firebirdsql.jdbc.FBDriver", 3050) { + @Override + protected String getSubprotocol(String host, Integer port, String db) { + return "firebirdsql"; + } + + @Override + protected String getSubname(String host, Integer port, String db) { + return String.format("//%s:%s/%s", host, port, db); + } + }, + MYSQL("com.mysql.cj.jdbc.Driver", 3306) { @Override protected String getSubprotocol(String host, Integer port, String db) { diff --git a/src/main/java/io/elastic/jdbc/utils/QueryFactory.java b/src/main/java/io/elastic/jdbc/utils/QueryFactory.java index 67695dc..08bd2a0 100644 --- a/src/main/java/io/elastic/jdbc/utils/QueryFactory.java +++ b/src/main/java/io/elastic/jdbc/utils/QueryFactory.java @@ -2,6 +2,7 @@ import io.elastic.jdbc.query_builders.MSSQL; import io.elastic.jdbc.query_builders.MySQL; +import io.elastic.jdbc.query_builders.Firebird; import io.elastic.jdbc.query_builders.Oracle; import io.elastic.jdbc.query_builders.PostgreSQL; import io.elastic.jdbc.query_builders.Query; @@ -21,6 +22,9 @@ public Query getQuery(String dbEngine) { if (dbEngine.toLowerCase().equals("mysql")) { return new MySQL(); } + if (dbEngine.toLowerCase().equals("firebirdsql")) { + return new Firebird(); + } return null; } } diff --git a/src/main/java/io/elastic/jdbc/utils/Utils.java b/src/main/java/io/elastic/jdbc/utils/Utils.java index 7f6c070..2a6f4cb 100644 --- a/src/main/java/io/elastic/jdbc/utils/Utils.java +++ b/src/main/java/io/elastic/jdbc/utils/Utils.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.StringReader; +import java.math.BigDecimal; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.Date; @@ -53,6 +54,7 @@ public class Utils { reboundDbState.put(Engines.MSSQL.name(), Collections.singletonList("40001")); reboundDbState.put(Engines.MYSQL.name(), Arrays.asList("40001", "XA102")); reboundDbState.put(Engines.ORACLE.name(), Collections.singletonList("61000")); + reboundDbState.put(Engines.FIREBIRDSQL.name(), Arrays.asList("10054", "10038")); } public static Connection getConnection(final JsonObject config) throws SQLException { @@ -135,11 +137,16 @@ public static void setStatementParam(PreparedStatement statement, int paramNumbe JsonObject body) throws SQLException { try { if (isNumeric(colName)) { - if ((body.get(colName) != null) && (body.get(colName) != JsonValue.NULL)) { - statement.setBigDecimal(paramNumber, body.getJsonNumber(colName).bigDecimalValue()); - } else { - statement.setBigDecimal(paramNumber, null); - } + if ((body.get(colName) != null) && (body.get(colName) != JsonValue.NULL)) { + // workaround for Firebase -> isNumeric=true, but value = "true" or "false" + if (body.get(colName).toString().equals("true") || body.get(colName).toString().equals("false")) { + statement.setBoolean(paramNumber, body.getBoolean(colName)); + } else { + statement.setBigDecimal(paramNumber, body.getJsonNumber(colName).bigDecimalValue()); + } + } else { + statement.setBigDecimal(paramNumber, null); + } } else if (isTimestamp(colName)) { if ((body.get(colName) != null) && (body.get(colName) != JsonValue.NULL)) { statement.setTimestamp(paramNumber, Timestamp.valueOf(body.getString(colName))); @@ -165,7 +172,7 @@ public static void setStatementParam(PreparedStatement statement, int paramNumbe statement.setNull(paramNumber, Types.VARCHAR); } } - } catch (java.lang.NumberFormatException e) { + } catch (java.lang.NumberFormatException | java.lang.ClassCastException e) { String message = String .format("Provided data: %s can't be cast to the column %s datatype", body.get(colName), colName); @@ -223,8 +230,7 @@ private static boolean isBoolean(String columnName) { return type != null && type.equals("boolean"); } - public static Map getColumnTypes(Connection connection, Boolean isOracle, - String tableName) { + public static Map getColumnTypes(Connection connection, String tableName) { DatabaseMetaData md; ResultSet rs = null; Map columnTypes = new HashMap<>(); @@ -236,6 +242,10 @@ public static Map getColumnTypes(Connection connection, Boolean tableName = tableName.split("\\.")[1]; } rs = md.getColumns(null, schemaName, tableName, "%"); + if (!rs.isBeforeFirst()){ + // ResultSet is empty, maybe we need to use null as Catalog? + rs = md.getColumns(null, schemaName, tableName, "%"); + } while (rs.next()) { String name = rs.getString("COLUMN_NAME").toLowerCase(); String type = detectColumnType(rs.getInt("DATA_TYPE"), rs.getString("TYPE_NAME")); @@ -366,14 +376,16 @@ public static String cleanJsonType(String rawType) { * Converts table name according Engine Type * * @param configuration should contains tableName - * @param isOracle flag is Engine type equals `oracle` + * @param isOracleOrFirebird flag is Engine type equals `oracle` */ - public static String getTableName(JsonObject configuration, boolean isOracle) { + public static String getTableName(JsonObject configuration, boolean isOracleOrFirebird) { if (configuration.containsKey(PROPERTY_TABLE_NAME) && getNonNullString(configuration, PROPERTY_TABLE_NAME).length() != 0) { String tableName = configuration.getString(PROPERTY_TABLE_NAME); if (tableName.contains(".")) { - tableName = isOracle ? tableName.split("\\.")[1].toUpperCase() : tableName.split("\\.")[1]; + tableName = isOracleOrFirebird ? tableName.split("\\.")[1].toUpperCase() : tableName.split("\\.")[1]; + } else { + tableName = isOracleOrFirebird ? tableName.toUpperCase() : tableName; } return tableName; } else { diff --git a/src/test/groovy/io/elastic/jdbc/TestUtils.java b/src/test/groovy/io/elastic/jdbc/TestUtils.java index 73979aa..7e151ba 100644 --- a/src/test/groovy/io/elastic/jdbc/TestUtils.java +++ b/src/test/groovy/io/elastic/jdbc/TestUtils.java @@ -12,6 +12,8 @@ public class TestUtils { public static final String TEST_TABLE_NAME = "stars"; private static final String SQL_DELETE_TABLE = " DROP TABLE IF EXISTS " + TEST_TABLE_NAME; + private static final String FIREBIRD_DELETE_TABLE = + " DROP TABLE " + TEST_TABLE_NAME; private static final String ORACLE_DELETE_TABLE = "BEGIN" + " EXECUTE IMMEDIATE 'DROP TABLE " + TEST_TABLE_NAME + "';" @@ -55,6 +57,14 @@ public class TestUtils { + "visible bit, " + "createdat DATETIME, " + "diameter INT GENERATED ALWAYS AS (radius * 2));"; + private static final String FIREBIRD_CREATE_TABLE = "RECREATE TABLE " + + TEST_TABLE_NAME + + " (ID int, " + + "NAME varchar(255) NOT NULL, " + + "RADIUS int NOT NULL," + + "DESTINATION float," + + "VISIBLE smallint, " + + "createdat TIMESTAMP)"; private static Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load(); public static JsonObjectBuilder getMssqlConfigurationBuilder() { @@ -73,12 +83,37 @@ public static JsonObjectBuilder getMssqlConfigurationBuilder() { .add("connectionString", connectionString); } + public static JsonObjectBuilder getFirebirdConfigurationBuilder() { + final String host = dotenv.get("CONN_HOST_FIREBIRD"); + final String port = dotenv.get("CONN_PORT_FIREBIRD"); + final String databaseName = dotenv.get("CONN_DBNAME_FIREBIRD"); + String configProperties = ""; + if ((!dotenv.get("CONN_CONFIG_PROP_FIREBIRD", "").equals(""))) { + configProperties = dotenv.get("CONN_CONFIG_PROP_FIREBIRD"); + } + final String connectionString = + "jdbc:firebirdsql://" + host + ":" + port + "/" + databaseName + "?" + configProperties; + return Json.createObjectBuilder() + .add("dbEngine", "firebirdsql") + .add("host", host) + .add("port", port) + .add("databaseName", databaseName) + .add("user", dotenv.get("CONN_USER_FIREBIRD")) + .add("password", dotenv.get("CONN_PASSWORD_FIREBIRD")) + .add("configurationProperties", configProperties) + .add("connectionString", connectionString); + } + public static JsonObjectBuilder getMysqlConfigurationBuilder() { final String host = dotenv.get("CONN_HOST_MYSQL"); final String port = dotenv.get("CONN_PORT_MYSQL"); final String databaseName = dotenv.get("CONN_DBNAME_MYSQL"); + String configProperties = ""; + if (!dotenv.get("CONN_CONFIG_PROP_MYSQL", "").equals("")) { + configProperties = dotenv.get("CONN_CONFIG_PROP_MYSQL"); + } final String connectionString = - "jdbc:mysql://" + host + ":" + port + "/" + databaseName; + "jdbc:mysql://" + host + ":" + port + "/" + databaseName + "?" + configProperties; return Json.createObjectBuilder() .add("dbEngine", "mysql") .add("host", host) @@ -86,6 +121,7 @@ public static JsonObjectBuilder getMysqlConfigurationBuilder() { .add("databaseName", databaseName) .add("user", dotenv.get("CONN_USER_MYSQL")) .add("password", dotenv.get("CONN_PASSWORD_MYSQL")) + .add("configurationProperties", configProperties) .add("connectionString", connectionString); } @@ -146,6 +182,9 @@ public static void createTestTable(Connection connection, String dbEngine) case "postgresql": connection.createStatement().execute(POSTGRESQL_CREATE_TABLE); break; + case "firebirdsql": + connection.createStatement().execute(FIREBIRD_CREATE_TABLE); + break; default: throw new RuntimeException("Unsupported dbEngine" + dbEngine); } @@ -155,6 +194,8 @@ public static void deleteTestTable(Connection connection, String dbEngine) throws SQLException { if (dbEngine.toLowerCase().equals("oracle")){ connection.createStatement().execute(ORACLE_DELETE_TABLE); + } else if (dbEngine.toLowerCase().equals("firebirdsql")){ + connection.createStatement().execute(FIREBIRD_DELETE_TABLE); } else { connection.createStatement().execute(SQL_DELETE_TABLE); } diff --git a/src/test/groovy/io/elastic/jdbc/integration/JdbcCredentialsVerifierSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/JdbcCredentialsVerifierSpec.groovy index 663b0b1..3c2803e 100644 --- a/src/test/groovy/io/elastic/jdbc/integration/JdbcCredentialsVerifierSpec.groovy +++ b/src/test/groovy/io/elastic/jdbc/integration/JdbcCredentialsVerifierSpec.groovy @@ -22,6 +22,16 @@ class JdbcCredentialsVerifierSpec extends Specification { notThrown(Throwable.class) } + def "should verify successfully Firebird"() { + setup: + JsonObject firebirdConfig = TestUtils.getFirebirdConfigurationBuilder().build() + when: + new JdbcCredentialsVerifier().verify(firebirdConfig) + + then: + notThrown(Throwable.class) + } + def "should verify successfully MySql"() { setup: JsonObject mysqlConfig = TestUtils.getMysqlConfigurationBuilder() diff --git a/src/test/groovy/io/elastic/jdbc/integration/actions/custom_query_action/CustomQueryFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/actions/custom_query_action/CustomQueryFirebirdSpec.groovy new file mode 100644 index 0000000..fa1a579 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/actions/custom_query_action/CustomQueryFirebirdSpec.groovy @@ -0,0 +1,151 @@ +package io.elastic.jdbc.integration.actions.custom_query_action + +import io.elastic.api.EventEmitter +import io.elastic.api.ExecutionParameters +import io.elastic.api.Message +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.actions.CustomQuery +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.Json +import javax.json.JsonObject +import java.sql.Connection +import java.sql.DriverManager +import java.sql.ResultSet + +class CustomQueryFirebirdSpec extends Specification { + @Shared + EventEmitter.Callback errorCallback + @Shared + EventEmitter.Callback snapshotCallback + @Shared + EventEmitter.Callback dataCallback + @Shared + EventEmitter.Callback reboundCallback + @Shared + EventEmitter.Callback httpReplyCallback + @Shared + EventEmitter emitter + @Shared + CustomQuery action + @Shared + String sqlCreateTable = "RECREATE TABLE STARS (ID int, NAME varchar(255) NOT NULL, DATET timestamp, RADIUS int, DESTINATION int, VISIBLE smallint, VISIBLEDATE date)" + @Shared + String sqlDropTable = "DROP TABLE STARS" + + + def cleanupSpec() { + JsonObject config = getConfig() + Connection connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlDropTable) + connection.close() + } + + def setup() { + errorCallback = Mock(EventEmitter.Callback) + snapshotCallback = Mock(EventEmitter.Callback) + dataCallback = Mock(EventEmitter.Callback) + reboundCallback = Mock(EventEmitter.Callback) + httpReplyCallback = Mock(EventEmitter.Callback) + emitter = new EventEmitter.Builder().onData(dataCallback).onSnapshot(snapshotCallback).onError(errorCallback) + .onRebound(reboundCallback).onHttpReplyCallback(httpReplyCallback).build() + action = new CustomQuery() + prepareStarsTable() + } + + def runAction(JsonObject config, JsonObject body, JsonObject snapshot) { + Message msg = new Message.Builder().body(body).build() + ExecutionParameters params = new ExecutionParameters(msg, emitter, config, snapshot) + action.execute(params); + + } + + def prepareStarsTable() { + JsonObject config = getConfig() + Connection connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlCreateTable) + connection.createStatement().execute("INSERT INTO stars values (1,'Taurus', '2015-02-19 10:10:10.0', 123, 5, 0, '2015-02-19')") + connection.createStatement().execute("INSERT INTO stars values (2,'Eridanus', '2017-02-19 10:10:10.0', 852, 5, 0, '2015-07-19')") + connection.close() + } + + + def getRecords(tableName) { + ArrayList records = new ArrayList(); + Connection connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + ResultSet rs = connection.createStatement().executeQuery("SELECT * FROM " + tableName + ";"); + while (rs.next()) { + records.add(rs.toRowResult().toString()); + } + rs.close(); + connection.close() + return records; + } + + def "make insert"() { + JsonObject snapshot = Json.createObjectBuilder().build() + JsonObject body = Json.createObjectBuilder() + .add("query", "INSERT INTO stars values (3, 'Rastaban', '2015-02-19 10:10:10.0', 123, 5, 1, '2018-02-19');") + .build(); + + when: + runAction(getConfig(), body, snapshot) + then: + 0 * errorCallback.receive(_) + 1 * dataCallback.receive({ it.getBody().getInt("updated") == 1 }) + + int records = getRecords("STARS").size() + expect: + records == 3 + } + + def "make select"() { + JsonObject snapshot = Json.createObjectBuilder().build() + + JsonObject body = Json.createObjectBuilder() + .add("query", "SELECT * FROM STARS;") + .build(); + + when: + runAction(getConfig(), body, snapshot) + then: + 0 * errorCallback.receive(_) + 2 * dataCallback.receive({ + JsonObject msgBody = it.getBody() + if (msgBody.getJsonArray("result") != null) { + msgBody.getJsonArray("result").size() == 2 + } else { + msgBody.getInt("updated") == 0 + } + } + ) + + } + + def "make delete"() { + JsonObject snapshot = Json.createObjectBuilder().build() + + JsonObject body = Json.createObjectBuilder() + .add("query", "DELETE FROM STARS WHERE ID = 1;") + .build(); + + when: + runAction(getConfig(), body, snapshot) + then: + 0 * errorCallback.receive(_) + 1 * dataCallback.receive({ it.getBody().getInt("updated") == 1 }) + + int records = getRecords("STARS").size() + expect: + records == 1 + } + + def getConfig() { + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", "STARS") + .add("nullableResult", "true") + .build(); + return config; + } +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/actions/delete_row_by_primary_key/DeleteActionFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/actions/delete_row_by_primary_key/DeleteActionFirebirdSpec.groovy new file mode 100644 index 0000000..faa0fd6 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/actions/delete_row_by_primary_key/DeleteActionFirebirdSpec.groovy @@ -0,0 +1,115 @@ +package io.elastic.jdbc.integration.actions.delete_row_by_primary_key + +import io.elastic.api.EventEmitter +import io.elastic.api.ExecutionParameters +import io.elastic.api.Message +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.actions.DeleteRowByPrimaryKey +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.Json +import javax.json.JsonObject +import java.sql.Connection +import java.sql.DriverManager +import java.sql.ResultSet + +class DeleteActionFirebirdSpec extends Specification { + @Shared + JsonObject config = getStarsConfig() + + @Shared + EventEmitter.Callback errorCallback + @Shared + EventEmitter.Callback snapshotCallback + @Shared + EventEmitter.Callback dataCallback + @Shared + EventEmitter.Callback reboundCallback + @Shared + EventEmitter.Callback httpReplyCallback + @Shared + EventEmitter emitter + @Shared + DeleteRowByPrimaryKey action + @Shared + String sqlCreateTable = "RECREATE TABLE STARS (ID int, NAME varchar(255) NOT NULL, DATET timestamp, RADIUS int, DESTINATION int, VISIBLE smallint, VISIBLEDATE date)" + @Shared + String sqlDropTable = "DROP TABLE STARS" + + def setup() { + createAction() + } + + def createAction() { + errorCallback = Mock(EventEmitter.Callback) + snapshotCallback = Mock(EventEmitter.Callback) + dataCallback = Mock(EventEmitter.Callback) + reboundCallback = Mock(EventEmitter.Callback) + httpReplyCallback = Mock(EventEmitter.Callback) + emitter = new EventEmitter.Builder().onData(dataCallback).onSnapshot(snapshotCallback).onError(errorCallback) + .onRebound(reboundCallback).onHttpReplyCallback(httpReplyCallback).build() + action = new DeleteRowByPrimaryKey() + } + + def runAction(JsonObject config, JsonObject body, JsonObject snapshot) { + Message msg = new Message.Builder().body(body).build() + ExecutionParameters params = new ExecutionParameters(msg, emitter, config, snapshot) + action.execute(params); + } + + def getStarsConfig() { + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", "STARS") + .add("nullableResult", "true") + .build(); + return config; + } + + def prepareStarsTable() { + Connection connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlCreateTable) + connection.createStatement().execute("INSERT INTO stars values (1,'Taurus', '2015-02-19 10:10:10.0', 123, 5, 0, '2015-02-19')") + connection.createStatement().execute("INSERT INTO stars values (2,'Eridanus', '2017-02-19 10:10:10.0', 852, 5, 0, '2015-07-19')") + connection.close() + } + + def getRecords(tableName) { + Connection connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + ArrayList records = new ArrayList(); + String sql = "SELECT * FROM " + tableName; + ResultSet rs = connection.createStatement().executeQuery(sql); + while (rs.next()) { + records.add(rs.toRowResult().toString()); + } + rs.close() + connection.close() + return records + } + + def cleanupSpec() { + Connection connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlDropTable) + connection.close() + } + + def "one delete"() { + prepareStarsTable(); + JsonObject snapshot = Json.createObjectBuilder().build() + JsonObject body = Json.createObjectBuilder() + .add("ID", 1) + .build(); + + runAction(getStarsConfig(), body, snapshot) + int first = getRecords("STARS").size() + JsonObject body2 = Json.createObjectBuilder() + .add("ID", 2) + .build() + runAction(getStarsConfig(), body2, snapshot) + int second = getRecords("STARS").size() + + expect: + first == 1 + second == 0 + } +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/actions/insert_action/InsertActionFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/actions/insert_action/InsertActionFirebirdSpec.groovy new file mode 100644 index 0000000..7751614 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/actions/insert_action/InsertActionFirebirdSpec.groovy @@ -0,0 +1,75 @@ +package io.elastic.jdbc.integration.actions.insert_action + +import io.elastic.api.EventEmitter +import io.elastic.api.ExecutionParameters +import io.elastic.api.Message +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.actions.InsertAction +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.Json +import javax.json.JsonObject +import java.sql.Connection +import java.sql.DriverManager +import java.sql.ResultSet + +class InsertActionFirebirdSpec extends Specification { + @Shared + Connection connection + @Shared + EventEmitter emitter = TestUtils.getFakeEventEmitter(Mock(EventEmitter.Callback)) + @Shared + InsertAction action = new InsertAction() + @Shared + String dbEngine = "firebirdsql" + @Shared + JsonObject configuration = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", TestUtils.TEST_TABLE_NAME) + .build() + + def setupSpec() { + connection = DriverManager.getConnection(configuration.getString("connectionString"), configuration.getString("user"), configuration.getString("password")); + TestUtils.createTestTable(connection, dbEngine) + } + + def cleanupSpec() { + connection.close() + + connection = DriverManager.getConnection(configuration.getString("connectionString"), configuration.getString("user"), configuration.getString("password")); + TestUtils.deleteTestTable(connection, dbEngine) + connection.close() + } + + def getRecords(tableName) { + ArrayList records = new ArrayList(); + String sql = "SELECT * FROM " + tableName; + ResultSet rs = connection.createStatement().executeQuery(sql); + while (rs.next()) { + records.add(rs.toRowResult().toString()); + rs.toRowResult() + } + rs.close(); + return records; + } + + def "one insert"() { + JsonObject body = Json.createObjectBuilder() + .add("id", 1) + .add("name", "Taurus") + .add("radius", 12) + .add("visible", true) + .add("createdat", "2015-02-19 10:10:10.0") + .build(); + Message msg = new Message.Builder().body(body).build() + ExecutionParameters params = new ExecutionParameters(msg, emitter, configuration, Json.createObjectBuilder().build()) + + action.execute(params); + + ArrayList records = getRecords(TestUtils.TEST_TABLE_NAME) + + expect: + records.size() == 1 + records.get(0) == '{ID=1, NAME=Taurus, RADIUS=12, DESTINATION=null, VISIBLE=1, CREATEDAT=2015-02-19 10:10:10.0}' + } +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/actions/select_action/SelectFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/actions/select_action/SelectFirebirdSpec.groovy new file mode 100644 index 0000000..d4d3941 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/actions/select_action/SelectFirebirdSpec.groovy @@ -0,0 +1,94 @@ +package io.elastic.jdbc.integration.actions.select_action + +import io.elastic.api.EventEmitter +import io.elastic.api.ExecutionParameters +import io.elastic.api.Message +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.actions.SelectAction +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.Json +import javax.json.JsonObject +import java.sql.Connection +import java.sql.DriverManager + +class SelectFirebirdSpec extends Specification { + + @Shared + String dbEngine = "firebirdsql" + @Shared + Connection connection + @Shared + JsonObject configuration = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", TestUtils.TEST_TABLE_NAME) + .build() + @Shared + EventEmitter.Callback errorCallback + @Shared + EventEmitter.Callback snapshotCallback + @Shared + EventEmitter.Callback dataCallback + @Shared + EventEmitter.Callback onHttpReplyCallback + @Shared + EventEmitter.Callback reboundCallback + @Shared + EventEmitter emitter + @Shared + SelectAction action + + def setupSpec() { + connection = DriverManager.getConnection(configuration.getString("connectionString"), configuration.getString("user"), configuration.getString("password")) + TestUtils.createTestTable(connection, dbEngine) + connection.createStatement().execute("INSERT INTO stars (id, name, radius, DESTINATION, visible, createdat) VALUES (1,'Hello', 1, 20, 0, '2015-02-19 10:10:10.0')"); + connection.createStatement().execute("INSERT INTO stars (id, name, radius, DESTINATION, visible, createdat) VALUES (2,'World', 1, 30, 1, '2015-02-19 10:10:10.0')"); + } + + def setup() { + action = new SelectAction() + } + + def runAction(JsonObject config, JsonObject body, JsonObject snapshot) { + Message msg = new Message.Builder().body(body).build() + errorCallback = Mock(EventEmitter.Callback) + snapshotCallback = Mock(EventEmitter.Callback) + dataCallback = Mock(EventEmitter.Callback) + reboundCallback = Mock(EventEmitter.Callback) + onHttpReplyCallback = Mock(EventEmitter.Callback) + emitter = new EventEmitter.Builder() + .onData(dataCallback) + .onSnapshot(snapshotCallback) + .onError(errorCallback) + .onRebound(reboundCallback) + .onHttpReplyCallback(onHttpReplyCallback).build() + ExecutionParameters params = new ExecutionParameters(msg, emitter, config, snapshot) + action.execute(params); + } + + def getStarsConfig() { + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("sqlQuery", "SELECT * FROM STARS WHERE @id:number =id AND name=@name") + .build() + return config; + } + + def cleanupSpec() { + connection.close() + Connection deleteCon = DriverManager.getConnection(configuration.getString("connectionString"), configuration.getString("user"), configuration.getString("password")) + TestUtils.deleteTestTable(deleteCon, dbEngine) + } + + def "one select"() { + JsonObject snapshot = Json.createObjectBuilder().build(); + JsonObject body = Json.createObjectBuilder() + .add("ID", 1) + .add("NAME", "Hello") + .build() + when: + runAction(getStarsConfig(), body, snapshot) + then: + 0 * errorCallback.receive(_) + } + +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/actions/upsert_row_by_primary_key/UpsertRowByPrimaryFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/actions/upsert_row_by_primary_key/UpsertRowByPrimaryFirebirdSpec.groovy new file mode 100644 index 0000000..8169147 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/actions/upsert_row_by_primary_key/UpsertRowByPrimaryFirebirdSpec.groovy @@ -0,0 +1,251 @@ +package io.elastic.jdbc.integration.actions.upsert_row_by_primary_key + +import io.elastic.api.EventEmitter +import io.elastic.api.ExecutionParameters +import io.elastic.api.Message +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.actions.UpsertRowByPrimaryKey +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.Json +import javax.json.JsonObject +import java.sql.Connection +import java.sql.DriverManager +import java.sql.ResultSet + +class UpsertRowByPrimaryFirebirdSpec extends Specification { + + @Shared + Connection connection + + @Shared + EventEmitter.Callback errorCallback + @Shared + EventEmitter.Callback snapshotCallback + @Shared + EventEmitter.Callback dataCallback + @Shared + EventEmitter.Callback reboundCallback + @Shared + EventEmitter.Callback httpReplyCallback + @Shared + EventEmitter emitter + @Shared + UpsertRowByPrimaryKey action + @Shared + JsonObject config = getStarsConfig(); + @Shared + String sqlDropStarsTable = "DROP TABLE STARS" + @Shared + String sqlCreateStarsTable = "RECREATE TABLE stars (ID int PRIMARY KEY, NAME varchar(255) NOT NULL, DATET timestamp, RADIUS int, DESTINATION int, VISIBLE smallint, VISIBLEDATE date)" + @Shared + String sqlDropPersonsTable = "DROP TABLE PERSONS" + @Shared + String sqlCreatePersonsTable = "RECREATE TABLE PERSONS (ID int, NAME varchar(255) NOT NULL, EMAIL varchar(255) NOT NULL PRIMARY KEY)" + + def cleanupSpec() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlDropStarsTable); + connection.createStatement().execute(sqlDropPersonsTable); + connection.close() + } + + def setup() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlCreateStarsTable); + connection.createStatement().execute(sqlCreatePersonsTable); + createAction() + } + def cleanup() { + connection.close() + } + + def createAction() { + errorCallback = Mock(EventEmitter.Callback) + snapshotCallback = Mock(EventEmitter.Callback) + dataCallback = Mock(EventEmitter.Callback) + reboundCallback = Mock(EventEmitter.Callback) + httpReplyCallback = Mock(EventEmitter.Callback) + emitter = new EventEmitter.Builder().onData(dataCallback).onSnapshot(snapshotCallback).onError(errorCallback) + .onRebound(reboundCallback).onHttpReplyCallback(httpReplyCallback).build() + action = new UpsertRowByPrimaryKey() + } + + def runAction(JsonObject config, JsonObject body, JsonObject snapshot) { + Message msg = new Message.Builder().body(body).build() + ExecutionParameters params = new ExecutionParameters(msg, emitter, config, snapshot) + action.execute(params); + } + + def getStarsConfig() { + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", "STARS") + .build(); + return config; + } + + def getRecords(tableName) { + ArrayList records = new ArrayList(); + String sql = "SELECT * FROM " + tableName; + ResultSet rs = connection.createStatement().executeQuery(sql); + while (rs.next()) { + records.add(rs.toRowResult().toString()); + } + rs.close(); + return records; + } + + def "one insert"() { + + JsonObject snapshot = Json.createObjectBuilder().build() + + JsonObject body = Json.createObjectBuilder() + .add("ID", 1) + .add("NAME", "Taurus") + .add("DATET", "2015-02-19 10:10:10.0") + .add("RADIUS", 123) + .add("VISIBLE", 1) + .add("VISIBLEDATE", "2015-02-19") + .build(); + + runAction(getStarsConfig(), body, snapshot) + + ArrayList records = getRecords("STARS") + + expect: + records.size() == 1 + records.get(0) == '{ID=1, NAME=Taurus, DATET=2015-02-19 10:10:10.0, RADIUS=123, DESTINATION=null, VISIBLE=1, VISIBLEDATE=2015-02-19}' + } + + def "one insert, incorrect value: string in integer field"() { + + JsonObject snapshot = Json.createObjectBuilder().build() + + JsonObject body = Json.createObjectBuilder() + .add("ID", 1) + .add("NAME", "Taurus") + .add("RADIUS", "test") + .build() + String exceptionClass = ""; + + try { + runAction(getStarsConfig(), body, snapshot) + } catch (Exception e) { + exceptionClass = e.getClass().getName(); + } + + expect: + exceptionClass.contains("Exception") + } + + def "two inserts"() { + + JsonObject snapshot = Json.createObjectBuilder().build() + + JsonObject body1 = Json.createObjectBuilder() + .add("ID", 1) + .add("NAME", "Taurus") + .add("RADIUS", 123) + .build() + runAction(getStarsConfig(), body1, snapshot) + + JsonObject body2 = Json.createObjectBuilder() + .add("ID", 2) + .add("NAME", "Eridanus") + .add("RADIUS", 456) + .build() + + runAction(getStarsConfig(), body2, snapshot) + + ArrayList records = getRecords("stars") + + expect: + records.size() == 2 + records.get(0) == '{ID=1, NAME=Taurus, DATET=null, RADIUS=123, DESTINATION=null, VISIBLE=null, VISIBLEDATE=null}' + records.get(1) == '{ID=2, NAME=Eridanus, DATET=null, RADIUS=456, DESTINATION=null, VISIBLE=null, VISIBLEDATE=null}' + } + + def "one insert, one update by ID"() { + + JsonObject snapshot = Json.createObjectBuilder().build() + + JsonObject body1 = Json.createObjectBuilder() + .add("ID", 1) + .add("NAME", "Taurus") + .add("RADIUS", 123) + .build() + runAction(getStarsConfig(), body1, snapshot) + + JsonObject body2 = Json.createObjectBuilder() + .add("ID", 1) + .add("NAME", "Eridanus") + .build() + runAction(getStarsConfig(), body2, snapshot) + + ArrayList records = getRecords("stars") + + expect: + records.size() == 1 + records.get(0) == '{ID=1, NAME=Eridanus, DATET=null, RADIUS=123, DESTINATION=null, VISIBLE=null, VISIBLEDATE=null}' + } + + + def getPersonsConfig() { + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", "PERSONS") + .build() + return config + } + + def "one insert, name with quote"() { + + JsonObject snapshot = Json.createObjectBuilder().build() + + JsonObject body1 = Json.createObjectBuilder() + .add("ID", 1) + .add("NAME", "O'Henry") + .add("EMAIL", "ohenry@elastic.io") + .build() + runAction(getPersonsConfig(), body1, snapshot) + + ArrayList records = getRecords("PERSONS") + + expect: + records.size() == 1 + records.get(0) == '{ID=1, NAME=O\'Henry, EMAIL=ohenry@elastic.io}' + } + + def "two inserts, one update by email"() { + + JsonObject snapshot = Json.createObjectBuilder().build() + + JsonObject body1 = Json.createObjectBuilder() + .add("ID", 1) + .add("NAME", "User1") + .add("EMAIL", "user1@elastic.io") + .build() + runAction(getPersonsConfig(), body1, snapshot) + + JsonObject body2 = Json.createObjectBuilder() + .add("ID", 2) + .add("NAME", "User2") + .add("EMAIL", "user2@elastic.io") + .build() + runAction(getPersonsConfig(), body2, snapshot) + + JsonObject body3 = Json.createObjectBuilder() + .add("ID", 3) + .add("NAME", "User3") + .add("EMAIL", "user2@elastic.io") + .build() + runAction(getPersonsConfig(), body3, snapshot) + + ArrayList records = getRecords("PERSONS") + + expect: + records.size() == 2 + records.get(0) == '{ID=1, NAME=User1, EMAIL=user1@elastic.io}' + records.get(1) == '{ID=3, NAME=User3, EMAIL=user2@elastic.io}' + } +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/providers/column_names_for_insert_provider/ColumnNamesForInsertProviderFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/providers/column_names_for_insert_provider/ColumnNamesForInsertProviderFirebirdSpec.groovy new file mode 100644 index 0000000..e616076 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/providers/column_names_for_insert_provider/ColumnNamesForInsertProviderFirebirdSpec.groovy @@ -0,0 +1,51 @@ +package io.elastic.jdbc.integration.providers.column_names_for_insert_provider + +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.providers.ColumnNamesForInsertProvider +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.Json +import javax.json.JsonObject +import javax.json.JsonReader +import java.sql.Connection +import java.sql.DriverManager + +class ColumnNamesForInsertProviderFirebirdSpec extends Specification { + + @Shared + Connection connection + @Shared + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", "STARS") + .build() + @Shared + String sqlDropTable = "DROP TABLE STARS" + @Shared + String sqlCreateTable = "RECREATE TABLE STARS (id INT PRIMARY KEY, name VARCHAR(255) NOT NULL, radius INT NOT NULL, destination FLOAT, createdat TIMESTAMP, diameter INT GENERATED ALWAYS AS (radius * 2))" + + def setup() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")); + connection.createStatement().execute(sqlCreateTable) + } + + def cleanupSpec() { + connection.createStatement().execute(sqlDropTable) + connection.close() + } + + def "get metadata model, given table name"() { + ColumnNamesForInsertProvider provider = new ColumnNamesForInsertProvider() + JsonObject meta = provider.getMetaModel(config) + InputStream fis = new FileInputStream("src/test/resources/GeneratedMetadata/columnNameFirebird.json"); + JsonReader reader = Json.createReader(fis); + JsonObject expectedMetadata = reader.readObject(); + reader.close(); + print meta + expect: + meta.containsKey("in") + meta.containsKey("out") + meta.getJsonObject("in") == expectedMetadata.getJsonObject("in") + meta.getJsonObject("out") == expectedMetadata.getJsonObject("out") + } +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/providers/column_names_provider/ColumnNamesProviderFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/providers/column_names_provider/ColumnNamesProviderFirebirdSpec.groovy new file mode 100644 index 0000000..5542010 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/providers/column_names_provider/ColumnNamesProviderFirebirdSpec.groovy @@ -0,0 +1,42 @@ +package io.elastic.jdbc.integration.providers.column_names_provider + +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.providers.ColumnNamesProvider +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.JsonObject +import java.sql.Connection +import java.sql.DriverManager + +class ColumnNamesProviderFirebirdSpec extends Specification { + @Shared + Connection connection + @Shared + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", "STARS") + .build() + @Shared + String sqlDropTable = "DROP TABLE STARS" + @Shared + String sqlCreateTable = "RECREATE TABLE STARS (ID int, NAME varchar(255) NOT NULL, RADIUS int, DESTINATION float, CREATEDAT TIMESTAMP)" + + def setup() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")); + connection.createStatement().execute(sqlCreateTable) + } + + def cleanupSpec() { + connection.createStatement().execute(sqlDropTable) + connection.close() + } + + def "get metadata model, given table name"() { + ColumnNamesProvider provider = new ColumnNamesProvider() + JsonObject meta = provider.getMetaModel(config) + print meta + expect: + meta.toString() == "{\"out\":{\"type\":\"object\",\"properties\":{\"ID\":{\"required\":false,\"title\":\"ID\",\"type\":\"number\"},\"NAME\":{\"required\":true,\"title\":\"NAME\",\"type\":\"string\"},\"RADIUS\":{\"required\":false,\"title\":\"RADIUS\",\"type\":\"number\"},\"DESTINATION\":{\"required\":false,\"title\":\"DESTINATION\",\"type\":\"number\"},\"CREATEDAT\":{\"required\":false,\"title\":\"CREATEDAT\",\"type\":\"string\"}}},\"in\":{\"type\":\"object\",\"properties\":{\"ID\":{\"required\":false,\"title\":\"ID\",\"type\":\"number\"},\"NAME\":{\"required\":true,\"title\":\"NAME\",\"type\":\"string\"},\"RADIUS\":{\"required\":false,\"title\":\"RADIUS\",\"type\":\"number\"},\"DESTINATION\":{\"required\":false,\"title\":\"DESTINATION\",\"type\":\"number\"},\"CREATEDAT\":{\"required\":false,\"title\":\"CREATEDAT\",\"type\":\"string\"}}}}" + } + +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/providers/column_names_with_primary_key_provider/ColumnNamesWithPrimaryKeyProviderFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/providers/column_names_with_primary_key_provider/ColumnNamesWithPrimaryKeyProviderFirebirdSpec.groovy new file mode 100644 index 0000000..026b332 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/providers/column_names_with_primary_key_provider/ColumnNamesWithPrimaryKeyProviderFirebirdSpec.groovy @@ -0,0 +1,42 @@ +package io.elastic.jdbc.integration.providers.column_names_with_primary_key_provider + +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.providers.ColumnNamesWithPrimaryKeyProvider +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.JsonObject +import java.sql.Connection +import java.sql.DriverManager + +class ColumnNamesWithPrimaryKeyProviderFirebirdSpec extends Specification { + + @Shared + Connection connection + @Shared + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", "STARS") + .build() + @Shared + String sqlDropTable = "DROP TABLE STARS" + @Shared + String sqlCreateTable = "RECREATE TABLE STARS (ID int, ISDEAD smallint, NAME varchar(255) NOT NULL, RADIUS int, DESTINATION float, CREATEDAT timestamp, PRIMARY KEY (ID))" + + def setup() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlCreateTable) + } + + def cleanupSpec() { + connection.createStatement().execute(sqlDropTable) + connection.close() + } + + def "get metadata model, given table name"() { + ColumnNamesWithPrimaryKeyProvider provider = new ColumnNamesWithPrimaryKeyProvider() + JsonObject meta = provider.getMetaModel(config) + print meta + expect: + meta.getJsonObject("in").toString() == "{\"type\":\"object\",\"properties\":{\"ID\":{\"required\":true,\"title\":\"ID\",\"type\":\"number\"},\"ISDEAD\":{\"required\":false,\"title\":\"ISDEAD\",\"type\":\"number\"},\"NAME\":{\"required\":false,\"title\":\"NAME\",\"type\":\"string\"},\"RADIUS\":{\"required\":false,\"title\":\"RADIUS\",\"type\":\"number\"},\"DESTINATION\":{\"required\":false,\"title\":\"DESTINATION\",\"type\":\"number\"},\"CREATEDAT\":{\"required\":false,\"title\":\"CREATEDAT\",\"type\":\"string\"}}}" + } +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/providers/primary_column_names_provider/PrimaryColumnNamesProviderFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/providers/primary_column_names_provider/PrimaryColumnNamesProviderFirebirdSpec.groovy new file mode 100644 index 0000000..0298977 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/providers/primary_column_names_provider/PrimaryColumnNamesProviderFirebirdSpec.groovy @@ -0,0 +1,44 @@ +package io.elastic.jdbc.integration.providers.primary_column_names_provider + +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.providers.PrimaryColumnNamesProvider +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.JsonObject +import javax.json.JsonObjectBuilder +import java.sql.Connection +import java.sql.DriverManager + +class PrimaryColumnNamesProviderFirebirdSpec extends Specification { + + @Shared + Connection connection + @Shared + JsonObject config = TestUtils.getFirebirdConfigurationBuilder().build() + @Shared + String sqlDropTable = "DROP TABLE STARS" + @Shared + String sqlCreateTable = "RECREATE TABLE STARS (ID int NOT NULL, name varchar(255) NOT NULL, RADIUS int, DESTINATION float, CREATEDAT TIMESTAMP, PRIMARY KEY (ID))" + + def setup() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")); + connection.createStatement().execute(sqlCreateTable) + } + + def cleanupSpec() { + connection.createStatement().execute(sqlDropTable) + connection.close() + } + + def "get metadata model, given table name"() { + + JsonObjectBuilder config = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", "STARS") + PrimaryColumnNamesProvider provider = new PrimaryColumnNamesProvider() + JsonObject meta = provider.getMetaModel(config.build()); + print meta + expect: + meta.toString() == "{\"out\":{\"type\":\"object\",\"properties\":{\"ID\":{\"required\":true,\"title\":\"ID\",\"type\":\"number\"}}},\"in\":{\"type\":\"object\",\"properties\":{\"ID\":{\"required\":true,\"title\":\"ID\",\"type\":\"number\"}}}}" + } +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/providers/table_name_provider/TableNameProviderFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/providers/table_name_provider/TableNameProviderFirebirdSpec.groovy new file mode 100644 index 0000000..4db3723 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/providers/table_name_provider/TableNameProviderFirebirdSpec.groovy @@ -0,0 +1,59 @@ +package io.elastic.jdbc.integration.providers.table_name_provider + +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.providers.TableNameProvider +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.JsonObject +import javax.json.JsonObjectBuilder +import java.sql.Connection +import java.sql.DriverManager + +class TableNameProviderFirebirdSpec extends Specification { + + @Shared + Connection connection + @Shared + JsonObject config = TestUtils.getFirebirdConfigurationBuilder().build() + @Shared + String sqlDropUsersTable = "DROP TABLE USERS" + @Shared + String sqlDropOrdersTable = "DROP TABLE ORDERS" + @Shared + String sqlDropProductsTable = "DROP TABLE PRODUCTS" + @Shared + String sqlCreateUsersTable = "RECREATE TABLE USERS (ID int, NAME varchar(255) NOT NULL, RADIUS int, DESTINATION int)" + @Shared + String sqlCreateOrdersTable = "RECREATE TABLE ORDERS (ID int, NAME varchar(255) NOT NULL, RADIUS int, DESTINATION int)" + @Shared + String sqlCreateProductsTable = "RECREATE TABLE PRODUCTS (ID int, NAME varchar(255) NOT NULL, RADIUS int, DESTINATION int)" + + def setupSpec() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlCreateUsersTable); + connection.createStatement().execute(sqlCreateProductsTable); + connection.createStatement().execute(sqlCreateOrdersTable); + } + + def cleanupSpec() { + connection.createStatement().execute(sqlDropUsersTable); + connection.createStatement().execute(sqlDropProductsTable); + connection.createStatement().execute(sqlDropOrdersTable); + connection.close(); + } + + def "create tables, successful"() { + JsonObjectBuilder config = TestUtils.getFirebirdConfigurationBuilder() + TableNameProvider provider = new TableNameProvider(); + + when: + JsonObject model = provider.getSelectModel(config.build()); + + then: + print model + model.getString("ORDERS").equals("ORDERS") + model.getString("PRODUCTS").equals("PRODUCTS") + model.getString("USERS").equals("USERS") + } +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/providers/timestamp_column_names_provider/column_names_provider/TimeStampColumnNamesProviderFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/providers/timestamp_column_names_provider/column_names_provider/TimeStampColumnNamesProviderFirebirdSpec.groovy new file mode 100644 index 0000000..1639113 --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/providers/timestamp_column_names_provider/column_names_provider/TimeStampColumnNamesProviderFirebirdSpec.groovy @@ -0,0 +1,42 @@ +package io.elastic.jdbc.integration.providers.timestamp_column_names_provider.column_names_provider + +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.providers.TimeStampColumnNamesProvider +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.JsonObject +import java.sql.Connection +import java.sql.DriverManager + +class TimeStampColumnNamesProviderFirebirdSpec extends Specification { + @Shared + Connection connection + @Shared + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", "STARS") + .build() + @Shared + String sqlDropTable = "DROP TABLE STARS" + @Shared + String sqlCreateTable = "RECREATE TABLE STARS (ID int, NAME varchar(255) NOT NULL, RADIUS int, DESTINATION float, CREATEDAT timestamp)" + + def setup() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlCreateTable); + } + + def cleanupSpec() { + connection.createStatement().execute(sqlDropTable); + connection.close() + } + + def "get select model, given table name"() { + TimeStampColumnNamesProvider provider = new TimeStampColumnNamesProvider() + JsonObject meta = provider.getSelectModel(config) + print meta + expect: + meta.toString() == "{\"CREATEDAT\":\"CREATEDAT\"}" + } + +} diff --git a/src/test/groovy/io/elastic/jdbc/integration/triggers/get_rows_polling_trigger/GetRowsPollingTriggerFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/triggers/get_rows_polling_trigger/GetRowsPollingTriggerFirebirdSpec.groovy new file mode 100644 index 0000000..a1b5f2d --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/triggers/get_rows_polling_trigger/GetRowsPollingTriggerFirebirdSpec.groovy @@ -0,0 +1,86 @@ +package io.elastic.jdbc.integration.triggers.get_rows_polling_trigger + +import io.elastic.api.EventEmitter +import io.elastic.api.EventEmitter.Callback +import io.elastic.api.ExecutionParameters +import io.elastic.api.Message +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.triggers.GetRowsPollingTrigger +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.Json +import javax.json.JsonObject +import javax.json.JsonObjectBuilder +import java.sql.Connection +import java.sql.DriverManager + +class GetRowsPollingTriggerFirebirdSpec extends Specification { + + + @Shared + Connection connection; + @Shared + JsonObject config = TestUtils.getFirebirdConfigurationBuilder().build() + @Shared + String sqlDropTable = "DROP TABLE STARS" + @Shared + String sqlCreateTable = "RECREATE TABLE STARS (ID int, ISDEAD smallint, NAME varchar(255) NOT NULL, RADIUS int, DESTINATION float, CREATEDAT timestamp)" + @Shared + String sqlInsertTable = "INSERT INTO STARS (ID, ISDEAD, NAME, RADIUS, DESTINATION, CREATEDAT) VALUES (1, 0, 'Sun', 50, 170, '2018-06-14 10:00:00')" + + def setup() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlCreateTable) + connection.createStatement().execute(sqlInsertTable) + connection.close() + } + + def cleanupSpec() { + connection = DriverManager.getConnection(config.getString("connectionString"), config.getString("user"), config.getString("password")) + connection.createStatement().execute(sqlDropTable) + connection.close() + } + + def "make a SELECT request"() { + + Callback errorCallback = Mock(Callback) + Callback snapshotCallback = Mock(Callback) + Callback dataCallback = Mock(Callback) + Callback onreboundCallback = Mock(Callback) + Callback onHttpCallback = Mock(Callback) + + EventEmitter emitter = new EventEmitter.Builder() + .onData(dataCallback) + .onSnapshot(snapshotCallback) + .onError(errorCallback) + .onHttpReplyCallback(onHttpCallback) + .onRebound(onreboundCallback).build(); + + GetRowsPollingTrigger getRowsPollingTrigger = new GetRowsPollingTrigger(); + + given: + Message msg = new Message.Builder().build(); + + JsonObjectBuilder config = TestUtils.getFirebirdConfigurationBuilder() + config.add("pollingField", "CREATEDAT") + .add("pollingValue", "2018-06-14 00:00:00") + .add("tableName", "STARS") + + JsonObjectBuilder snapshot = Json.createObjectBuilder() + snapshot.add("skipNumber", 0) + + when: + ExecutionParameters params = new ExecutionParameters(msg, emitter, config.build(), snapshot.build()) + getRowsPollingTrigger.execute(params) + + then: + 0 * errorCallback.receive(_) + dataCallback.receive({ + it.body.getInt("ID").equals(1) + it.body.getBoolean("ISDEAD").equals(0) + it.body.getString("NAME").equals("Sun") + it.body.getString("CREATEDAT").equals("2018-06-14 13:00:00.0") + }) + } +} \ No newline at end of file diff --git a/src/test/groovy/io/elastic/jdbc/integration/triggers/select_trigger/SelectTriggerFirebirdSpec.groovy b/src/test/groovy/io/elastic/jdbc/integration/triggers/select_trigger/SelectTriggerFirebirdSpec.groovy new file mode 100644 index 0000000..a129d9a --- /dev/null +++ b/src/test/groovy/io/elastic/jdbc/integration/triggers/select_trigger/SelectTriggerFirebirdSpec.groovy @@ -0,0 +1,86 @@ +package io.elastic.jdbc.integration.triggers.select_trigger + +import io.elastic.api.EventEmitter +import io.elastic.api.ExecutionParameters +import io.elastic.api.Message +import io.elastic.jdbc.TestUtils +import io.elastic.jdbc.triggers.SelectTrigger +import spock.lang.Shared +import spock.lang.Specification + +import javax.json.Json +import javax.json.JsonObject +import java.sql.Connection +import java.sql.DriverManager + +class SelectTriggerFirebirdSpec extends Specification { + + @Shared + Connection connection + @Shared + JsonObject configuration = TestUtils.getFirebirdConfigurationBuilder() + .add("tableName", TestUtils.TEST_TABLE_NAME) + .build() + + @Shared + EventEmitter.Callback errorCallback + @Shared + EventEmitter.Callback snapshotCallback + @Shared + EventEmitter.Callback dataCallback + @Shared + EventEmitter.Callback reboundCallback + @Shared + EventEmitter.Callback onHttpReplyCallback + @Shared + EventEmitter emitter + @Shared + SelectTrigger trigger + @Shared + String sqlCreateTable = "RECREATE TABLE STARS (ID int, NAME varchar(255) NOT NULL, DATET timestamp, RADIUS int, DESTINATION int)" + @Shared + String sqlInsertTable = "INSERT INTO STARS (ID, NAME) VALUES (1, 'Hello')" + + def setupSpec() { + connection = DriverManager.getConnection(configuration.getString("connectionString"), configuration.getString("user"), configuration.getString("password")); + connection.createStatement().execute(sqlCreateTable); + connection.createStatement().execute(sqlInsertTable); + connection.close() + } + + + def runTrigger(JsonObject config, JsonObject body, JsonObject snapshot) { + Message msg = new Message.Builder().body(body).build() + errorCallback = Mock(EventEmitter.Callback) + snapshotCallback = Mock(EventEmitter.Callback) + dataCallback = Mock(EventEmitter.Callback) + reboundCallback = Mock(EventEmitter.Callback) + onHttpReplyCallback = Mock(EventEmitter.Callback) + emitter = new EventEmitter.Builder() + .onData(dataCallback) + .onSnapshot(snapshotCallback) + .onError(errorCallback) + .onRebound(reboundCallback) + .onHttpReplyCallback(onHttpReplyCallback).build() + ExecutionParameters params = new ExecutionParameters(msg, emitter, config, snapshot) + trigger = new SelectTrigger() + trigger.execute(params); + } + + def getStarsConfig() { + JsonObject config = TestUtils.getFirebirdConfigurationBuilder() + .add("sqlQuery", "SELECT * from STARS where ID = 1") + .build(); + return config; + } + + def "one select"() { + JsonObject snapshot = Json.createObjectBuilder().build(); + JsonObject body = Json.createObjectBuilder().build() + when: + runTrigger(getStarsConfig(), body, snapshot) + then: + 0 * errorCallback.receive(_) + + } +} diff --git a/src/test/resources/GeneratedMetadata/columnNameFirebird.json b/src/test/resources/GeneratedMetadata/columnNameFirebird.json new file mode 100644 index 0000000..c2f373d --- /dev/null +++ b/src/test/resources/GeneratedMetadata/columnNameFirebird.json @@ -0,0 +1,47 @@ +{ + "out": { + "type": "object", + "properties": { + "result": { + "required": true, + "title": "result", + "type": "boolean" + } + } + }, + "in": { + "type": "object", + "properties": { + "ID": { + "required": true, + "title": "ID", + "type": "number" + }, + "NAME": { + "required": true, + "title": "NAME", + "type": "string" + }, + "RADIUS": { + "required": true, + "title": "RADIUS", + "type": "number" + }, + "DESTINATION": { + "required": false, + "title": "DESTINATION", + "type": "number" + }, + "CREATEDAT": { + "required": false, + "title": "CREATEDAT", + "type": "string" + }, + "DIAMETER": { + "required": false, + "title": "DIAMETER", + "type": "number" + } + } + } +} \ No newline at end of file