Merge branch 'release/0.0.0'

This commit is contained in:
2022-07-01 11:21:40 -05:00
24 changed files with 797 additions and 251 deletions

77
.circleci/config.yml Normal file
View File

@ -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)-.*/

View File

@ -0,0 +1,9 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>github-qqq-maven-registry</id>
<username>${env.QQQ_MAVEN_REGISTRY_USERNAME}</username>
<password>${env.QQQ_MAVEN_REGISTRY_PASSWORD}</password>
</server>
</servers>
</settings>

View File

@ -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 }}

View File

@ -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

View File

@ -46,6 +46,7 @@
-->
<module name="TreeWalker">
<module name="SuppressWarningsHolder"/>
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
@ -171,7 +172,7 @@
<property name="caseIndent" value="3"/>
<property name="throwsIndent" value="6"/>
<property name="lineWrappingIndentation" value="3"/>
<property name="arrayInitIndent" value="2"/>
<property name="arrayInitIndent" value="6"/>
</module>
<!--
<module name="AbbreviationAsWordInName">
@ -260,4 +261,5 @@
<module name="MissingOverride"/>
</module>
<module name="SuppressWarningsFilter"/>
</module>

45
pom.xml
View File

@ -20,14 +20,18 @@
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-rdbms</artifactId>
<version>0.0-SNAPSHOT</version>
<version>0.0.0</version>
<scm>
<connection>scm:git:git@github.com:Kingsrook/qqq-backend-module-rdbms.git</connection>
<developerConnection>scm:git:git@github.com:Kingsrook/qqq-backend-module-rdbms.git</developerConnection>
<tag>HEAD</tag>
</scm>
<properties>
<!-- props specifically to this module -->
@ -47,7 +51,7 @@
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-core</artifactId>
<version>0.0-SNAPSHOT</version>
<version>0.0.0</version>
</dependency>
<!-- 3rd party deps specifically for this module -->
@ -59,7 +63,7 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
<version>2.1.210</version>
<scope>test</scope>
</dependency>
@ -72,12 +76,12 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.15.0</version>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.15.0</version>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
@ -85,6 +89,12 @@
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.23.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@ -138,12 +148,29 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.amashchenko.maven.plugin</groupId>
<artifactId>gitflow-maven-plugin</artifactId>
<version>1.18.0</version>
<configuration>
<gitFlowConfig>
<productionBranch>main</productionBranch>
<developmentBranch>dev</developmentBranch>
<versionTagPrefix>version-</versionTagPrefix>
</gitFlowConfig>
<skipFeatureVersion>true</skipFeatureVersion> <!-- Keep feature names out of versions -->
<postReleaseGoals>install</postReleaseGoals> <!-- Let CI run deploys -->
<commitDevelopmentVersionAtStart>true</commitDevelopmentVersionAtStart>
<versionDigitToIncrement>1</versionDigitToIncrement> <!-- In general, we update the minor -->
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>github</id>
<id>github-qqq-maven-registry</id>
<name>GitHub QQQ Maven Registry</name>
<url>https://maven.pkg.github.com/Kingsrook/qqq-maven-registry</url>
</repository>

View File

@ -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<? extends QBackendMetaData> getBackendMetaDataClass()
{
return (RDBMSBackendMetaData.class);
}
/*******************************************************************************
** Method to identify the class used for table-backend details for this module.
*******************************************************************************/
@Override
public Class<? extends QTableBackendDetails> 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());
}
}

View File

@ -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);
}
}

View File

@ -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<QRecordWithStatus> recordsWithStatus = new ArrayList<>();
rs.setRecords(recordsWithStatus);
List<QRecord> 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;

View File

@ -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<QFieldMetaData> 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<Object> params = new ArrayList<>();
String tableName = getTableName(table);
StringBuilder sql = new StringBuilder("INSERT INTO ").append(tableName).append("(").append(columns).append(") VALUES");
List<Object> 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<QRecord> 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<Integer> idList = QueryManager.executeInsertForGeneratedIds(connection, sql.toString(), params);
List<QRecordWithStatus> recordsWithStatus = new ArrayList<>();
rs.setRecords(recordsWithStatus);
Connection connection = getConnection(insertRequest);
List<Integer> idList = QueryManager.executeInsertForGeneratedIds(connection, sql.toString(), params);
List<QRecord> 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;

View File

@ -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<QFieldMetaData> 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<QRecord> 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);
}
}

View File

@ -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<QRecordWithStatus> recordsWithStatus = new ArrayList<>();
rs.setRecords(recordsWithStatus);
List<QRecord> 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<QFieldMetaData> 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<Object> 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);
}
}

View File

@ -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;
/*******************************************************************************

View File

@ -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);

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -19,87 +19,63 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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"));
}
}

View File

@ -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<String> lines = (List<String>) 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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
--
-- 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 <https://www.gnu.org/licenses/>.
--
DROP TABLE IF EXISTS person;
CREATE TABLE person
(
id SERIAL,
id INT AUTO_INCREMENT,
create_date TIMESTAMP DEFAULT now(),
modify_date TIMESTAMP DEFAULT now(),