diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..0ef02745 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,77 @@ +version: 2.1 + +executors: + java17: + docker: + - image: 'cimg/openjdk:17.0' + resource_class: small + +orbs: + slack: circleci/slack@4.10.1 + +commands: + run_maven: + parameters: + maven_subcommand: + default: test + type: string + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "pom.xml" }} + - run: + name: Run Maven + command: | + mvn -s .circleci/mvn-settings.xml << parameters.maven_subcommand >> + - run: + name: Save test results + command: | + mkdir -p ~/test-results/junit/ + find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} ~/test-results/junit/ \; + when: always + - store_test_results: + path: ~/test-results + - save_cache: + paths: + - ~/.m2 + key: v1-dependencies-{{ checksum "pom.xml" }} + +jobs: + mvn_test: + executor: java17 + steps: + - run_maven: + maven_subcommand: test + - slack/notify: + event: fail + + mvn_deploy: + executor: java17 + steps: + - run_maven: + maven_subcommand: deploy + - slack/notify: + event: always + +workflows: + test_only: + jobs: + - mvn_test: + context: [ qqq-maven-registry-credentials, kingsrook-slack ] + filters: + branches: + ignore: /dev/ + tags: + ignore: /(version|snapshot)-.*/ + + deploy: + jobs: + - mvn_deploy: + context: [ qqq-maven-registry-credentials, kingsrook-slack ] + filters: + branches: + only: /dev/ + tags: + only: /(version|snapshot)-.*/ + diff --git a/.circleci/mvn-settings.xml b/.circleci/mvn-settings.xml new file mode 100644 index 00000000..b2a345f0 --- /dev/null +++ b/.circleci/mvn-settings.xml @@ -0,0 +1,9 @@ + + + + github-qqq-maven-registry + ${env.QQQ_MAVEN_REGISTRY_USERNAME} + ${env.QQQ_MAVEN_REGISTRY_PASSWORD} + + + diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index 8c794698..00000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,37 +0,0 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -name: Java CI with Maven - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - branches: [ main ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - java-version: '17' - distribution: 'adopt' - cache: maven - - name: maven-settings-xml-action - uses: whelk-io/maven-settings-xml-action@v20 - with: - repositories: '[{ "id": "github-qqq-maven-registry", "url": "https://maven.pkg.github.com/Kingsrook/qqq-maven-registry", "snapshots": { "enabled": "true" }}]' - servers: '[{ "id": "github-qqq-maven-registry", "username": "${{ secrets.QQQ_MAVEN_REGISTRY_USERNAME }}", "password": "${{ secrets.QQQ_MAVEN_REGISTRY_PASSWORD }}" }]' - - name: Build with Maven - run: mvn -B package --file pom.xml - - name: Publish to GitHub Packages Apache Maven - run: mvn deploy - env: - GITHUB_TOKEN: ${{ github.token }} diff --git a/README.md b/README.md index f64a1d0c..36d0831b 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@ This is a backend-module for the qqq framework - specifically, one that works wi QQQ - Low-code Application Framework for Engineers. \ Copyright (C) 2022. Kingsrook, LLC \ 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States \ -contact@kingsrook.com -https://github.com/Kingsrook/intellij-commentator-plugin +contact@kingsrook.com | https://github.com/Kingsrook/ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as diff --git a/checkstyle.xml b/checkstyle.xml index dbaa3479..76f872ed 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -46,6 +46,7 @@ --> + @@ -171,7 +172,7 @@ - + - + 4.0.0 com.kingsrook.qqq qqq-backend-module-rdbms - 0.0-SNAPSHOT + 0.0.0 + + + scm:git:git@github.com:Kingsrook/qqq-backend-module-rdbms.git + scm:git:git@github.com:Kingsrook/qqq-backend-module-rdbms.git + HEAD + @@ -47,7 +51,7 @@ com.kingsrook.qqq qqq-backend-core - 0.0-SNAPSHOT + 0.0.0 @@ -59,7 +63,7 @@ com.h2database h2 - 1.4.197 + 2.1.210 test @@ -72,12 +76,12 @@ org.apache.logging.log4j log4j-api - 2.15.0 + 2.17.1 org.apache.logging.log4j log4j-core - 2.15.0 + 2.17.1 org.junit.jupiter @@ -85,6 +89,12 @@ 5.8.1 test + + org.assertj + assertj-core + 3.23.1 + test + @@ -138,12 +148,29 @@ + + com.amashchenko.maven.plugin + gitflow-maven-plugin + 1.18.0 + + + main + dev + version- + + true + install + true + 1 + + + - github + github-qqq-maven-registry GitHub QQQ Maven Registry https://maven.pkg.github.com/Kingsrook/qqq-maven-registry diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java index 9146e1e4..752b53c9 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendModule.java @@ -22,6 +22,8 @@ package com.kingsrook.qqq.backend.module.rdbms; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QTableBackendDetails; import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface; import com.kingsrook.qqq.backend.core.modules.interfaces.QBackendModuleInterface; @@ -31,21 +33,54 @@ import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSDeleteAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSInsertAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSQueryAction; import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSUpdateAction; +import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData; +import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails; /******************************************************************************* - ** RDBMSBackendModule - * + ** QQQ Backend module for working with Relational Databases (RDBMS's). *******************************************************************************/ public class RDBMSBackendModule implements QBackendModuleInterface { + /******************************************************************************* + ** Method where a backend module must be able to provide its type (name). + *******************************************************************************/ + public String getBackendType() + { + return ("rdbms"); + } + + + + /******************************************************************************* + ** Method to identify the class used for backend meta data for this module. + *******************************************************************************/ + @Override + public Class getBackendMetaDataClass() + { + return (RDBMSBackendMetaData.class); + } + + + + /******************************************************************************* + ** Method to identify the class used for table-backend details for this module. + *******************************************************************************/ + @Override + public Class getTableBackendDetailsClass() + { + return (RDBMSTableBackendDetails.class); + } + + + /******************************************************************************* ** *******************************************************************************/ @Override public QueryInterface getQueryInterface() { - return new RDBMSQueryAction(); + return (new RDBMSQueryAction()); } @@ -60,6 +95,7 @@ public class RDBMSBackendModule implements QBackendModuleInterface } + /******************************************************************************* ** *******************************************************************************/ @@ -79,4 +115,5 @@ public class RDBMSBackendModule implements QBackendModuleInterface { return (new RDBMSDeleteAction()); } + } diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java index 94abade1..0f1c7110 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java @@ -22,17 +22,49 @@ package com.kingsrook.qqq.backend.module.rdbms.actions; +import java.io.Serializable; +import java.sql.Connection; +import java.sql.SQLException; +import java.time.OffsetDateTime; +import com.kingsrook.qqq.backend.core.model.actions.AbstractQTableRequest; import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; +import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData; +import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails; /******************************************************************************* - ** + ** Base class for all core actions in the RDBMS module. *******************************************************************************/ public abstract class AbstractRDBMSAction { /******************************************************************************* + ** Get the table name to use in the RDBMS from a QTableMetaData. ** + ** That is, table.backendDetails.tableName if set -- else, table.name + *******************************************************************************/ + protected String getTableName(QTableMetaData table) + { + if(table.getBackendDetails() instanceof RDBMSTableBackendDetails details) + { + if(StringUtils.hasContent(details.getTableName())) + { + return (details.getTableName()); + } + } + return (table.getName()); + } + + + + /******************************************************************************* + ** Get the column name to use for a field in the RDBMS, from the fieldMetaData. + ** + ** That is, field.backendName if set -- else, field.name *******************************************************************************/ protected String getColumnName(QFieldMetaData field) { @@ -43,4 +75,42 @@ public abstract class AbstractRDBMSAction return (field.getName()); } + + + /******************************************************************************* + ** Get a database connection, per the backend in the request. + *******************************************************************************/ + protected Connection getConnection(AbstractQTableRequest qTableRequest) throws SQLException + { + ConnectionManager connectionManager = new ConnectionManager(); + return connectionManager.getConnection((RDBMSBackendMetaData) qTableRequest.getBackend()); + } + + + + /******************************************************************************* + ** Handle obvious problems with values - like empty string for integer should be null. + ** + *******************************************************************************/ + protected Serializable scrubValue(QFieldMetaData field, Serializable value) + { + if("".equals(value)) + { + QFieldType type = field.getType(); + if(type.equals(QFieldType.INTEGER) || type.equals(QFieldType.DECIMAL) || type.equals(QFieldType.DATE) || type.equals(QFieldType.DATE_TIME)) + { + value = null; + } + } + + ////////////////////////////////////////////////////// + // todo - let this come from something in the field // + ////////////////////////////////////////////////////// + if(value == null && (field.getName().equals("createDate") || field.getName().equals("modifyDate"))) + { + value = OffsetDateTime.now(); + } + + return (value); + } } diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteAction.java index 5c4254ec..3495a621 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteAction.java @@ -31,11 +31,8 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteRequest; import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteResult; import com.kingsrook.qqq.backend.core.model.data.QRecord; -import com.kingsrook.qqq.backend.core.model.data.QRecordWithStatus; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.modules.interfaces.DeleteInterface; -import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData; -import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; @@ -55,7 +52,7 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte DeleteResult rs = new DeleteResult(); QTableMetaData table = deleteRequest.getTable(); - String tableName = table.getName(); + String tableName = getTableName(table); String primaryKeyName = getColumnName(table.getField(table.getPrimaryKeyField())); String sql = "DELETE FROM " + tableName @@ -68,18 +65,16 @@ public class RDBMSDeleteAction extends AbstractRDBMSAction implements DeleteInte // todo sql customization - can edit sql and/or param list - ConnectionManager connectionManager = new ConnectionManager(); - Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(deleteRequest.getBackend())); - + Connection connection = getConnection(deleteRequest); QueryManager.executeUpdateForRowCount(connection, sql, params); - List recordsWithStatus = new ArrayList<>(); - rs.setRecords(recordsWithStatus); + List outputRecords = new ArrayList<>(); + rs.setRecords(outputRecords); for(Serializable primaryKey : deleteRequest.getPrimaryKeys()) { QRecord qRecord = new QRecord().withTableName(deleteRequest.getTableName()).withValue("id", primaryKey); // todo uh, identify any errors? - QRecordWithStatus recordWithStatus = new QRecordWithStatus(qRecord); - recordsWithStatus.add(recordWithStatus); + QRecord outputRecord = new QRecord(qRecord); + outputRecords.add(outputRecord); } return rs; diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java index 73173920..c55fff9f 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertAction.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.module.rdbms.actions; +import java.io.Serializable; import java.sql.Connection; import java.util.ArrayList; import java.util.List; @@ -30,12 +31,10 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.insert.InsertRequest; import com.kingsrook.qqq.backend.core.model.actions.insert.InsertResult; import com.kingsrook.qqq.backend.core.model.data.QRecord; -import com.kingsrook.qqq.backend.core.model.data.QRecordWithStatus; import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.modules.interfaces.InsertInterface; -import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData; -import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; +import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; @@ -50,9 +49,14 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte *******************************************************************************/ public InsertResult execute(InsertRequest insertRequest) throws QException { + if(CollectionUtils.nullSafeIsEmpty(insertRequest.getRecords())) + { + throw (new QException("Request to insert 0 records.")); + } + try { - InsertResult rs = new InsertResult(); + InsertResult rs = new InsertResult(); QTableMetaData table = insertRequest.getTable(); List insertableFields = table.getFields().values().stream() @@ -66,9 +70,9 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte .map(x -> "?") .collect(Collectors.joining(", ")); - String tableName = table.getName(); - StringBuilder sql = new StringBuilder("INSERT INTO ").append(tableName).append("(").append(columns).append(") VALUES"); - List params = new ArrayList<>(); + String tableName = getTableName(table); + StringBuilder sql = new StringBuilder("INSERT INTO ").append(tableName).append("(").append(columns).append(") VALUES"); + List params = new ArrayList<>(); int recordIndex = 0; for(QRecord record : insertRequest.getRecords()) @@ -80,31 +84,32 @@ public class RDBMSInsertAction extends AbstractRDBMSAction implements InsertInte sql.append("(").append(questionMarks).append(")"); for(QFieldMetaData field : insertableFields) { - params.add(record.getValue(field.getName())); + Serializable value = record.getValue(field.getName()); + value = scrubValue(field, value); + + params.add(value); } } // todo sql customization - can edit sql and/or param list - ConnectionManager connectionManager = new ConnectionManager(); - Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(insertRequest.getBackend())); - // QueryResult rs = new QueryResult(); // List records = new ArrayList<>(); // rs.setRecords(records); // todo - non-serial-id style tables // todo - other generated values, e.g., createDate... maybe need to re-select? - List idList = QueryManager.executeInsertForGeneratedIds(connection, sql.toString(), params); - List recordsWithStatus = new ArrayList<>(); - rs.setRecords(recordsWithStatus); + Connection connection = getConnection(insertRequest); + List idList = QueryManager.executeInsertForGeneratedIds(connection, sql.toString(), params); + List outputRecords = new ArrayList<>(); + rs.setRecords(outputRecords); int index = 0; for(QRecord record : insertRequest.getRecords()) { - Integer id = idList.get(index++); - QRecordWithStatus recordWithStatus = new QRecordWithStatus(record); - recordWithStatus.setValue(table.getPrimaryKeyField(), id); - recordsWithStatus.add(recordWithStatus); + Integer id = idList.get(index++); + QRecord outputRecord = new QRecord(record); + outputRecord.setValue(table.getPrimaryKeyField(), id); + outputRecords.add(outputRecord); } return rs; diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java index b1a58837..6c892ee8 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java @@ -44,9 +44,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.QFieldType; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.modules.interfaces.QueryInterface; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; -import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData; -import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /******************************************************************************* @@ -54,6 +54,7 @@ import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; *******************************************************************************/ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterface { + private static final Logger LOG = LogManager.getLogger(RDBMSQueryAction.class); /******************************************************************************* ** @@ -63,7 +64,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf try { QTableMetaData table = queryRequest.getTable(); - String tableName = table.getName(); + String tableName = getTableName(table); List fieldList = new ArrayList<>(table.getFields().values()); String columns = fieldList.stream() @@ -97,13 +98,11 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf // todo sql customization - can edit sql and/or param list - ConnectionManager connectionManager = new ConnectionManager(); - Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(queryRequest.getBackend())); - QueryResult rs = new QueryResult(); List records = new ArrayList<>(); rs.setRecords(records); + Connection connection = getConnection(queryRequest); QueryManager.executeStatement(connection, sql, ((ResultSet resultSet) -> { ResultSetMetaData metaData = resultSet.getMetaData(); @@ -131,7 +130,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf } catch(Exception e) { - e.printStackTrace(); + LOG.warn("Error executing query", e); throw new QException("Error executing query", e); } } diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java index 3cad765f..c9bdb280 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateAction.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.backend.module.rdbms.actions; +import java.io.Serializable; import java.sql.Connection; import java.util.ArrayList; import java.util.List; @@ -30,12 +31,9 @@ import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest; import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult; import com.kingsrook.qqq.backend.core.model.data.QRecord; -import com.kingsrook.qqq.backend.core.model.data.QRecordWithStatus; import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.modules.interfaces.UpdateInterface; -import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData; -import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; @@ -52,15 +50,16 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte { try { - UpdateResult rs = new UpdateResult(); + UpdateResult rs = new UpdateResult(); QTableMetaData table = updateRequest.getTable(); - List recordsWithStatus = new ArrayList<>(); - rs.setRecords(recordsWithStatus); + List outputRecords = new ArrayList<>(); + rs.setRecords(outputRecords); // todo - sql batch for performance // todo - if setting a bunch of records to have the same value, a single update where id IN? - int recordIndex = 0; + Connection connection = getConnection(updateRequest); + int recordIndex = 0; for(QRecord record : updateRequest.getRecords()) { List updateableFields = table.getFields().values().stream() @@ -72,33 +71,34 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte .map(f -> this.getColumnName(f) + " = ?") .collect(Collectors.joining(", ")); - String tableName = table.getName(); + String tableName = getTableName(table); StringBuilder sql = new StringBuilder("UPDATE ").append(tableName) .append(" SET ").append(columns) .append(" WHERE ").append(getColumnName(table.getField(table.getPrimaryKeyField()))).append(" = ?"); // todo sql customization - can edit sql and/or param list - ConnectionManager connectionManager = new ConnectionManager(); - Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(updateRequest.getBackend())); - - QRecordWithStatus recordWithStatus = new QRecordWithStatus(record); - recordsWithStatus.add(recordWithStatus); + QRecord outputRecord = new QRecord(record); + outputRecords.add(outputRecord); try { List params = new ArrayList<>(); for(QFieldMetaData field : updateableFields) { - params.add(record.getValue(field.getName())); + Serializable value = record.getValue(field.getName()); + value = scrubValue(field, value); + params.add(value); } params.add(record.getValue(table.getPrimaryKeyField())); + QueryManager.executeUpdate(connection, sql.toString(), params); // todo - auto-updated values, e.g., modifyDate... maybe need to re-select? } catch(Exception e) { - recordWithStatus.setErrors(new ArrayList<>(List.of(e))); + // todo - how to communicate errors??? outputRecord.setErrors(new ArrayList<>(List.of(e))); + throw new QException("Error executing update: " + e.getMessage(), e); } } diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/ConnectionManager.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/ConnectionManager.java index b6ffbe7b..43c6a1bc 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/ConnectionManager.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/ConnectionManager.java @@ -25,7 +25,7 @@ package com.kingsrook.qqq.backend.module.rdbms.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; -import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData; +import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData; /******************************************************************************* diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java index bc07bf75..6fa85207 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java @@ -652,6 +652,12 @@ public class QueryManager statement.setTimestamp(index, timestamp); return (1); } + else if(value instanceof OffsetDateTime) + { + Timestamp timestamp = new Timestamp(((OffsetDateTime) value).toEpochSecond() * MS_PER_SEC); + statement.setTimestamp(index, timestamp); + return (1); + } else if(value instanceof LocalDateTime) { Timestamp timestamp = new Timestamp(((LocalDateTime) value).toEpochSecond(ZoneOffset.UTC) * MS_PER_SEC); diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSBackendMetaData.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSBackendMetaData.java new file mode 100644 index 00000000..627c7557 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSBackendMetaData.java @@ -0,0 +1,272 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.module.rdbms.model.metadata; + + +import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule; + + +/******************************************************************************* + ** Meta-data to provide details of an RDBMS backend (e.g., connection params) + *******************************************************************************/ +public class RDBMSBackendMetaData extends QBackendMetaData +{ + private String vendor; + private String hostName; + private Integer port; + private String databaseName; + private String username; + private String password; + + + + /******************************************************************************* + ** Default Constructor. + *******************************************************************************/ + public RDBMSBackendMetaData() + { + super(); + setBackendType(RDBMSBackendModule.class); + } + + + + /******************************************************************************* + ** Getter for vendor + ** + *******************************************************************************/ + public String getVendor() + { + return vendor; + } + + + + /******************************************************************************* + ** Setter for vendor + ** + *******************************************************************************/ + public void setVendor(String vendor) + { + this.vendor = vendor; + } + + + + /******************************************************************************* + ** Fluent Setter for vendor + ** + *******************************************************************************/ + public RDBMSBackendMetaData withVendor(String vendor) + { + this.vendor = vendor; + return (this); + } + + + + /******************************************************************************* + ** Getter for hostName + ** + *******************************************************************************/ + public String getHostName() + { + return hostName; + } + + + + /******************************************************************************* + ** Setter for hostName + ** + *******************************************************************************/ + public void setHostName(String hostName) + { + this.hostName = hostName; + } + + + + /******************************************************************************* + ** Fluent Setter for hostName + ** + *******************************************************************************/ + public RDBMSBackendMetaData withHostName(String hostName) + { + this.hostName = hostName; + return (this); + } + + + + /******************************************************************************* + ** Getter for port + ** + *******************************************************************************/ + public Integer getPort() + { + return port; + } + + + + /******************************************************************************* + ** Setter for port + ** + *******************************************************************************/ + public void setPort(Integer port) + { + this.port = port; + } + + + + /******************************************************************************* + ** Fluent Setter for port + ** + *******************************************************************************/ + public RDBMSBackendMetaData withPort(Integer port) + { + this.port = port; + return (this); + } + + + + /******************************************************************************* + ** Getter for databaseName + ** + *******************************************************************************/ + public String getDatabaseName() + { + return databaseName; + } + + + + /******************************************************************************* + ** Setter for databaseName + ** + *******************************************************************************/ + public void setDatabaseName(String databaseName) + { + this.databaseName = databaseName; + } + + + + /******************************************************************************* + ** Fluent Setter for databaseName + ** + *******************************************************************************/ + public RDBMSBackendMetaData withDatabaseName(String databaseName) + { + this.databaseName = databaseName; + return (this); + } + + + + /******************************************************************************* + ** Getter for username + ** + *******************************************************************************/ + public String getUsername() + { + return username; + } + + + + /******************************************************************************* + ** Setter for username + ** + *******************************************************************************/ + public void setUsername(String username) + { + this.username = username; + } + + + + /******************************************************************************* + ** Fluent Setter for username + ** + *******************************************************************************/ + public RDBMSBackendMetaData withUsername(String username) + { + this.username = username; + return (this); + } + + + + /******************************************************************************* + ** Getter for password + ** + *******************************************************************************/ + public String getPassword() + { + return password; + } + + + + /******************************************************************************* + ** Setter for password + ** + *******************************************************************************/ + public void setPassword(String password) + { + this.password = password; + } + + + + /******************************************************************************* + ** Fluent Setter for password + ** + *******************************************************************************/ + public RDBMSBackendMetaData withPassword(String password) + { + this.password = password; + return (this); + } + + + + /******************************************************************************* + ** Called by the QInstanceEnricher - to do backend-type-specific enrichments. + ** Original use case is: reading secrets into fields (e.g., passwords). + *******************************************************************************/ + @Override + public void enrich() + { + super.enrich(); + QMetaDataVariableInterpreter interpreter = new QMetaDataVariableInterpreter(); + username = interpreter.interpret(username); + password = interpreter.interpret(password); + } + +} diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendMetaData.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSTableBackendDetails.java similarity index 58% rename from src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendMetaData.java rename to src/main/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSTableBackendDetails.java index 9fb425a8..67c4b155 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/RDBMSBackendMetaData.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSTableBackendDetails.java @@ -19,87 +19,63 @@ * along with this program. If not, see . */ -package com.kingsrook.qqq.backend.module.rdbms; +package com.kingsrook.qqq.backend.module.rdbms.model.metadata; -import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QTableBackendDetails; +import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule; /******************************************************************************* - ** RDBMSBackendMetaData - * + ** Extension of QTableBackendDetails, with details specific to an RDBMS table. *******************************************************************************/ -public class RDBMSBackendMetaData extends QBackendMetaData +public class RDBMSTableBackendDetails extends QTableBackendDetails { + private String tableName; + + /******************************************************************************* - ** + ** Default Constructor. *******************************************************************************/ - public RDBMSBackendMetaData(QBackendMetaData source) + public RDBMSTableBackendDetails() { super(); - setName(source.getName()); - setValues(source.getValues()); + setBackendType(RDBMSBackendModule.class); } /******************************************************************************* + ** Getter for tableName ** *******************************************************************************/ - public String getVendor() + public String getTableName() { - return getValue("vendor"); + return tableName; } /******************************************************************************* + ** Setter for tableName ** *******************************************************************************/ - public String getHostName() + public void setTableName(String tableName) { - return getValue("hostName"); + this.tableName = tableName; } /******************************************************************************* + ** Fluent Setter for tableName ** *******************************************************************************/ - public String getPort() + public RDBMSTableBackendDetails withTableName(String tableName) { - return getValue("port"); - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - public String getDatabaseName() - { - return getValue("databaseName"); - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - public String getUsername() - { - return getValue("username"); - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - public String getPassword() - { - return getValue("password"); + this.tableName = tableName; + return (this); } } diff --git a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/TestUtils.java b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/TestUtils.java new file mode 100644 index 00000000..1e9596db --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/TestUtils.java @@ -0,0 +1,90 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.module.rdbms; + + +import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; +import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData; +import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TestUtils +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public static QInstance defineInstance() + { + QInstance qInstance = new QInstance(); + qInstance.addBackend(defineBackend()); + qInstance.addTable(defineTablePerson()); + return (qInstance); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static RDBMSBackendMetaData defineBackend() + { + RDBMSBackendMetaData rdbmsBackendMetaData = new RDBMSBackendMetaData() + .withVendor("h2") + .withHostName("mem") + .withDatabaseName("test_database") + .withUsername("sa") + .withPassword(""); + rdbmsBackendMetaData.setName("default"); + return (rdbmsBackendMetaData); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QTableMetaData defineTablePerson() + { + return new QTableMetaData() + .withName("a-person") // use this name, so it isn't the same as the actual database-table name (which must come from the backend details) + .withLabel("Person") + .withBackendName(defineBackend().getName()) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date")) + .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date")) + .withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name")) + .withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name")) + .withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date")) + .withField(new QFieldMetaData("email", QFieldType.STRING)) + .withBackendDetails(new RDBMSTableBackendDetails() + .withTableName("person")); + } + +} diff --git a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSActionTest.java b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSActionTest.java index 2964835c..a70632bf 100644 --- a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSActionTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSActionTest.java @@ -25,12 +25,7 @@ package com.kingsrook.qqq.backend.module.rdbms.actions; import java.io.InputStream; import java.sql.Connection; import java.util.List; -import com.kingsrook.qqq.backend.core.model.metadata.QInstance; -import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; -import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; -import com.kingsrook.qqq.backend.core.model.metadata.QFieldType; -import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; -import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData; +import com.kingsrook.qqq.backend.module.rdbms.TestUtils; import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; import org.apache.commons.io.IOUtils; @@ -43,56 +38,6 @@ import static junit.framework.Assert.assertNotNull; public class RDBMSActionTest { - /******************************************************************************* - ** - *******************************************************************************/ - protected QInstance defineInstance() - { - QInstance qInstance = new QInstance(); - qInstance.addBackend(defineBackend()); - qInstance.addTable(defineTablePerson()); - return (qInstance); - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - protected QBackendMetaData defineBackend() - { - return new QBackendMetaData() - .withName("default") - .withType("rdbms") - .withValue("vendor", "h2") - .withValue("hostName", "mem") - .withValue("databaseName", "test_database") - .withValue("username", "sa") - .withValue("password", ""); - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - public QTableMetaData defineTablePerson() - { - return new QTableMetaData() - .withName("person") - .withLabel("Person") - .withBackendName(defineBackend().getName()) - .withPrimaryKeyField("id") - .withField(new QFieldMetaData("id", QFieldType.INTEGER)) - .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date")) - .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date")) - .withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name")) - .withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name")) - .withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date")) - .withField(new QFieldMetaData("email", QFieldType.STRING)); - } - - /******************************************************************************* ** @@ -100,11 +45,12 @@ public class RDBMSActionTest @SuppressWarnings("unchecked") protected void primeTestDatabase() throws Exception { - ConnectionManager connectionManager = new ConnectionManager(); - Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(defineBackend())); - InputStream primeTestDatabaseSqlStream = RDBMSActionTest.class.getResourceAsStream("/prime-test-database.sql"); + ConnectionManager connectionManager = new ConnectionManager(); + Connection connection = connectionManager.getConnection(TestUtils.defineBackend()); + InputStream primeTestDatabaseSqlStream = RDBMSActionTest.class.getResourceAsStream("/prime-test-database.sql"); assertNotNull(primeTestDatabaseSqlStream); List lines = (List) IOUtils.readLines(primeTestDatabaseSqlStream); + lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList(); String joinedSQL = String.join("\n", lines); for(String sql : joinedSQL.split(";")) { @@ -120,7 +66,7 @@ public class RDBMSActionTest protected void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception { ConnectionManager connectionManager = new ConnectionManager(); - Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(defineBackend())); + Connection connection = connectionManager.getConnection(TestUtils.defineBackend()); QueryManager.executeStatement(connection, sql, resultSetProcessor); } } diff --git a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteActionTest.java b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteActionTest.java index ff31bfad..8547040c 100644 --- a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteActionTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSDeleteActionTest.java @@ -25,7 +25,7 @@ package com.kingsrook.qqq.backend.module.rdbms.actions; import java.util.List; import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteRequest; import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteResult; -import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.module.rdbms.TestUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -60,7 +60,7 @@ public class RDBMSDeleteActionTest extends RDBMSActionTest deleteRequest.setPrimaryKeys(List.of(1, 2, 3, 4, 5)); DeleteResult deleteResult = new RDBMSDeleteAction().execute(deleteRequest); assertEquals(5, deleteResult.getRecords().size(), "Unfiltered delete should return all rows"); - assertTrue(deleteResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); + // todo - add errors to QRecord? assertTrue(deleteResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); runTestSql("SELECT id FROM person", (rs -> assertFalse(rs.next()))); } @@ -76,7 +76,7 @@ public class RDBMSDeleteActionTest extends RDBMSActionTest deleteRequest.setPrimaryKeys(List.of(1)); DeleteResult deleteResult = new RDBMSDeleteAction().execute(deleteRequest); assertEquals(1, deleteResult.getRecords().size(), "Should delete one row"); - assertTrue(deleteResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); + // todo - add errors to QRecord? assertTrue(deleteResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); runTestSql("SELECT id FROM person WHERE id = 1", (rs -> assertFalse(rs.next()))); } @@ -92,7 +92,7 @@ public class RDBMSDeleteActionTest extends RDBMSActionTest deleteRequest.setPrimaryKeys(List.of(1, 3, 5)); DeleteResult deleteResult = new RDBMSDeleteAction().execute(deleteRequest); assertEquals(3, deleteResult.getRecords().size(), "Should delete one row"); - assertTrue(deleteResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); + // todo - add errors to QRecord? assertTrue(deleteResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); runTestSql("SELECT id FROM person", (rs -> { int rowsFound = 0; while(rs.next()) @@ -113,8 +113,8 @@ public class RDBMSDeleteActionTest extends RDBMSActionTest private DeleteRequest initDeleteRequest() { DeleteRequest deleteRequest = new DeleteRequest(); - deleteRequest.setInstance(defineInstance()); - deleteRequest.setTableName(defineTablePerson().getName()); + deleteRequest.setInstance(TestUtils.defineInstance()); + deleteRequest.setTableName(TestUtils.defineTablePerson().getName()); return deleteRequest; } diff --git a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java index e4dc9751..852d7a51 100644 --- a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSInsertActionTest.java @@ -26,12 +26,11 @@ import java.util.List; import com.kingsrook.qqq.backend.core.model.actions.insert.InsertRequest; import com.kingsrook.qqq.backend.core.model.actions.insert.InsertResult; import com.kingsrook.qqq.backend.core.model.data.QRecord; -import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.module.rdbms.TestUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; /******************************************************************************* @@ -67,7 +66,7 @@ public class RDBMSInsertActionTest extends RDBMSActionTest InsertResult insertResult = new RDBMSInsertAction().execute(insertRequest); assertEquals(1, insertResult.getRecords().size(), "Should return 1 row"); assertNotNull(insertResult.getRecords().get(0).getValue("id"), "Should have an id in the row"); - assertTrue(insertResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); + // todo - add errors to QRecord? assertTrue(insertResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); runTestSql("SELECT * FROM person WHERE last_name = 'Kirk'", (rs -> { int rowsFound = 0; while(rs.next()) @@ -75,6 +74,7 @@ public class RDBMSInsertActionTest extends RDBMSActionTest rowsFound++; assertEquals(6, rs.getInt("id")); assertEquals("James", rs.getString("first_name")); + assertNotNull(rs.getString("create_date")); } assertEquals(1, rowsFound); })); @@ -104,7 +104,7 @@ public class RDBMSInsertActionTest extends RDBMSActionTest assertEquals(2, insertResult.getRecords().size(), "Should return 1 row"); assertEquals(6, insertResult.getRecords().get(0).getValue("id"), "Should have next id in the row"); assertEquals(7, insertResult.getRecords().get(1).getValue("id"), "Should have next id in the row"); - assertTrue(insertResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); + // todo - add errors to QRecord? assertTrue(insertResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); runTestSql("SELECT * FROM person WHERE last_name = 'Picard'", (rs -> { int rowsFound = 0; while(rs.next()) @@ -135,8 +135,8 @@ public class RDBMSInsertActionTest extends RDBMSActionTest private InsertRequest initInsertRequest() { InsertRequest insertRequest = new InsertRequest(); - insertRequest.setInstance(defineInstance()); - insertRequest.setTableName(defineTablePerson().getName()); + insertRequest.setInstance(TestUtils.defineInstance()); + insertRequest.setTableName(TestUtils.defineTablePerson().getName()); return insertRequest; } diff --git a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java index 2e34a82b..0067b59a 100644 --- a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java @@ -29,6 +29,7 @@ import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria; import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.query.QueryRequest; import com.kingsrook.qqq.backend.core.model.actions.query.QueryResult; +import com.kingsrook.qqq.backend.module.rdbms.TestUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -413,8 +414,8 @@ public class RDBMSQueryActionTest extends RDBMSActionTest private QueryRequest initQueryRequest() { QueryRequest queryRequest = new QueryRequest(); - queryRequest.setInstance(defineInstance()); - queryRequest.setTableName(defineTablePerson().getName()); + queryRequest.setInstance(TestUtils.defineInstance()); + queryRequest.setTableName(TestUtils.defineTablePerson().getName()); return queryRequest; } diff --git a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateActionTest.java b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateActionTest.java index fe3158f8..0cbe75a2 100644 --- a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateActionTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSUpdateActionTest.java @@ -26,12 +26,11 @@ import java.util.List; import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest; import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult; import com.kingsrook.qqq.backend.core.model.data.QRecord; -import com.kingsrook.qqq.backend.core.utils.CollectionUtils; +import com.kingsrook.qqq.backend.module.rdbms.TestUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -69,7 +68,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest UpdateResult updateResult = new RDBMSUpdateAction().execute(updateRequest); assertEquals(1, updateResult.getRecords().size(), "Should return 1 row"); assertEquals(2, updateResult.getRecords().get(0).getValue("id"), "Should have id=2 in the row"); - assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); + // todo - add errors to QRecord? assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); runTestSql("SELECT * FROM person WHERE last_name = 'Kirk'", (rs -> { int rowsFound = 0; while(rs.next()) @@ -114,7 +113,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest assertEquals(2, updateResult.getRecords().size(), "Should return 2 rows"); assertEquals(1, updateResult.getRecords().get(0).getValue("id"), "Should have expected ids in the row"); assertEquals(3, updateResult.getRecords().get(1).getValue("id"), "Should have expected ids in the row"); - assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); + // todo - add errors to QRecord? assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors"); runTestSql("SELECT * FROM person WHERE last_name = 'From Bewitched'", (rs -> { int rowsFound = 0; while(rs.next()) @@ -148,8 +147,8 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest private UpdateRequest initUpdateRequest() { UpdateRequest updateRequest = new UpdateRequest(); - updateRequest.setInstance(defineInstance()); - updateRequest.setTableName(defineTablePerson().getName()); + updateRequest.setInstance(TestUtils.defineInstance()); + updateRequest.setTableName(TestUtils.defineTablePerson().getName()); return updateRequest; } diff --git a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSBackendMetaDataTest.java b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSBackendMetaDataTest.java new file mode 100644 index 00000000..028e8081 --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/model/metadata/RDBMSBackendMetaDataTest.java @@ -0,0 +1,73 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.module.rdbms.model.metadata; + + +import java.io.IOException; +import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.module.rdbms.TestUtils; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/******************************************************************************* + ** Unit test for RDBMSBackendMetaData + *******************************************************************************/ +class RDBMSBackendMetaDataTest +{ + + /******************************************************************************* + ** Test that an instance can be serialized as expected + *******************************************************************************/ + @Test + public void testSerializingToJson() + { + QInstance qInstance = TestUtils.defineInstance(); + String json = new QInstanceAdapter().qInstanceToJsonIncludingBackend(qInstance); + System.out.println(JsonUtils.prettyPrint(json)); + System.out.println(json); + String expectToContain = """ + "backends":{"default":{"hostName":"mem","password":"","databaseName":"test_database\""""; + assertTrue(json.contains(expectToContain)); + } + + + + /******************************************************************************* + ** Test that an instance can be deserialized as expected + *******************************************************************************/ + @Test + public void testDeserializingFromJson() throws IOException + { + QInstanceAdapter qInstanceAdapter = new QInstanceAdapter(); + + QInstance qInstance = TestUtils.defineInstance(); + String json = qInstanceAdapter.qInstanceToJsonIncludingBackend(qInstance); + + QInstance deserialized = qInstanceAdapter.jsonToQInstanceIncludingBackends(json); + assertThat(deserialized).usingRecursiveComparison().isEqualTo(qInstance); + } + +} \ No newline at end of file diff --git a/src/test/resources/prime-test-database.sql b/src/test/resources/prime-test-database.sql index b314c28a..d95d660b 100644 --- a/src/test/resources/prime-test-database.sql +++ b/src/test/resources/prime-test-database.sql @@ -1,28 +1,28 @@ -/* - * QQQ - Low-code Application Framework for Engineers. - * Copyright (C) 2021-2022. Kingsrook, LLC - * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States - * contact@kingsrook.com - * https://github.com/Kingsrook/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ +-- +-- QQQ - Low-code Application Framework for Engineers. +-- Copyright (C) 2021-2022. Kingsrook, LLC +-- 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States +-- contact@kingsrook.com +-- https://github.com/Kingsrook/ +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . +-- DROP TABLE IF EXISTS person; CREATE TABLE person ( - id SERIAL, + id INT AUTO_INCREMENT, create_date TIMESTAMP DEFAULT now(), modify_date TIMESTAMP DEFAULT now(),