mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
Initial checkin of sqlite module
This commit is contained in:
1
pom.xml
1
pom.xml
@ -34,6 +34,7 @@
|
||||
<module>qqq-backend-module-api</module>
|
||||
<module>qqq-backend-module-filesystem</module>
|
||||
<module>qqq-backend-module-rdbms</module>
|
||||
<module>qqq-backend-module-sqlite</module>
|
||||
<module>qqq-backend-module-mongodb</module>
|
||||
<module>qqq-language-support-javascript</module>
|
||||
<module>qqq-openapi</module>
|
||||
|
113
qqq-backend-module-sqlite/pom.xml
Normal file
113
qqq-backend-module-sqlite/pom.xml
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
<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>
|
||||
|
||||
<artifactId>qqq-backend-module-sqlite</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-parent-project</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<!-- props specifically to this module -->
|
||||
<!-- none at this time -->
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- other qqq modules deps -->
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-backend-module-rdbms</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 3rd party deps specifically for this module -->
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.47.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Common deps for all qqq modules -->
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.4.3</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>${plugin.shade.phase}</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,57 @@
|
||||
package com.kingsrook.qqq.backend.module.sqlite;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendModule;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.model.metadata.SQLiteBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.model.metadata.SQLiteTableBackendDetails;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SQLiteBackendModule extends RDBMSBackendModule
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(SQLiteBackendModule.class);
|
||||
|
||||
private static final String NAME = "sqlite";
|
||||
|
||||
static
|
||||
{
|
||||
QBackendModuleDispatcher.registerBackendModule(new SQLiteBackendModule());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Method where a backend module must be able to provide its type (name).
|
||||
*******************************************************************************/
|
||||
public String getBackendType()
|
||||
{
|
||||
return NAME;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Method to identify the class used for backend meta data for this module.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Class<? extends QBackendMetaData> getBackendMetaDataClass()
|
||||
{
|
||||
return (SQLiteBackendMetaData.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Method to identify the class used for table-backend details for this module.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Class<? extends QTableBackendDetails> getTableBackendDetailsClass()
|
||||
{
|
||||
return (SQLiteTableBackendDetails.class);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package com.kingsrook.qqq.backend.module.sqlite.model.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.strategy.RDBMSActionStrategyInterface;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.SQLiteBackendModule;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.strategy.SQLiteRDBMSActionStrategy;
|
||||
import org.sqlite.JDBC;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data to provide details of an SQLite backend (e.g., path to the database file)
|
||||
*******************************************************************************/
|
||||
public class SQLiteBackendMetaData extends RDBMSBackendMetaData
|
||||
{
|
||||
private String path;
|
||||
|
||||
// todo - overrides to setters for unsupported fields?
|
||||
// todo - or - change rdbms connection manager to not require an RDBMSBackendMetaData?
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default Constructor.
|
||||
*******************************************************************************/
|
||||
public SQLiteBackendMetaData()
|
||||
{
|
||||
super();
|
||||
setVendor("sqlite");
|
||||
setBackendType(SQLiteBackendModule.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String buildConnectionString()
|
||||
{
|
||||
return "jdbc:sqlite:" + this.path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String getJdbcDriverClassName()
|
||||
{
|
||||
return (JDBC.class.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public SQLiteBackendMetaData withName(String name)
|
||||
{
|
||||
setName(name);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for path
|
||||
*******************************************************************************/
|
||||
public String getPath()
|
||||
{
|
||||
return (this.path);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for path
|
||||
*******************************************************************************/
|
||||
public void setPath(String path)
|
||||
{
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for path
|
||||
*******************************************************************************/
|
||||
public SQLiteBackendMetaData withPath(String path)
|
||||
{
|
||||
this.path = path;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public RDBMSActionStrategyInterface getActionStrategy()
|
||||
{
|
||||
if(getActionStrategyField() == null)
|
||||
{
|
||||
if(getActionStrategyCodeReference() != null)
|
||||
{
|
||||
setActionStrategyField(QCodeLoader.getAdHoc(RDBMSActionStrategyInterface.class, getActionStrategyCodeReference()));
|
||||
}
|
||||
else
|
||||
{
|
||||
setActionStrategyField(new SQLiteRDBMSActionStrategy());
|
||||
}
|
||||
}
|
||||
|
||||
return (getActionStrategyField());
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.kingsrook.qqq.backend.module.sqlite.model.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SQLiteTableBackendDetails extends QTableBackendDetails
|
||||
{
|
||||
private String tableName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return (this.tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
*******************************************************************************/
|
||||
public SQLiteTableBackendDetails withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package com.kingsrook.qqq.backend.module.sqlite.strategy;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.strategy.BaseRDBMSActionStrategy;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** SQLite specialization of the default RDBMS/JDBC action strategy
|
||||
*******************************************************************************/
|
||||
public class SQLiteRDBMSActionStrategy extends BaseRDBMSActionStrategy
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
** deal with sqlite not having temporal types... so temporal values
|
||||
** i guess are stored as strings, as that's how they come back to us - so
|
||||
** the JDBC methods fail trying to getDate or whatever from them - but
|
||||
** getting the values as strings, they parse nicely, so do that.
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public Serializable getFieldValueFromResultSet(QFieldType type, ResultSet resultSet, int i) throws SQLException
|
||||
{
|
||||
return switch(type)
|
||||
{
|
||||
case DATE ->
|
||||
{
|
||||
try
|
||||
{
|
||||
yield parseString(s -> LocalDate.parse(s), resultSet, i);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// handle the case of, the value we got back is actually a date-time -- so -- //
|
||||
// let's parse it as such, and then map into a LocalDate in the session zoneId //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
Instant instant = (Instant) parseString(s -> Instant.parse(s), resultSet, i);
|
||||
if(instant == null)
|
||||
{
|
||||
yield null;
|
||||
}
|
||||
ZoneId zoneId = ValueUtils.getSessionOrInstanceZoneId();
|
||||
yield instant.atZone(zoneId).toLocalDate();
|
||||
}
|
||||
}
|
||||
case TIME -> parseString(s -> LocalTime.parse(s), resultSet, i);
|
||||
case DATE_TIME -> parseString(s -> Instant.parse(s), resultSet, i);
|
||||
default -> super.getFieldValueFromResultSet(type, resultSet, i);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** helper method for getFieldValueFromResultSet
|
||||
***************************************************************************/
|
||||
private Serializable parseString(Function<String, Serializable> parser, ResultSet resultSet, int i) throws SQLException
|
||||
{
|
||||
String valueString = QueryManager.getString(resultSet, i);
|
||||
if(valueString == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
else
|
||||
{
|
||||
return parser.apply(valueString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* bind temporal types as strings (see above comment re: sqlite temporal types)
|
||||
***************************************************************************/
|
||||
@Override
|
||||
protected int bindParamObject(PreparedStatement statement, int index, Object value) throws SQLException
|
||||
{
|
||||
if(value instanceof Instant || value instanceof LocalTime || value instanceof LocalDate)
|
||||
{
|
||||
bindParam(statement, index, value.toString());
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.bindParamObject(statement, index, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** per discussion (and rejected PR mentioned) on https://github.com/prrvchr/sqlite-jdbc
|
||||
** sqlite jdbc by default will only return the latest generated serial. but we can get
|
||||
** them all by appending this "RETURNING id" to the query, and then calling execute()
|
||||
** (instead of executeUpdate()) and getResultSet (instead of getGeneratedKeys())
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<Serializable> executeInsertForGeneratedIds(Connection connection, String sql, List<Object> params, QFieldMetaData primaryKeyField) throws SQLException
|
||||
{
|
||||
sql = sql + " RETURNING " + getColumnName(primaryKeyField);
|
||||
|
||||
try(PreparedStatement statement = connection.prepareStatement(sql))
|
||||
{
|
||||
bindParams(params.toArray(), statement);
|
||||
incrementStatistic(STAT_QUERIES_RAN);
|
||||
statement.execute();
|
||||
|
||||
ResultSet generatedKeys = statement.getResultSet();
|
||||
List<Serializable> rs = new ArrayList<>();
|
||||
while(generatedKeys.next())
|
||||
{
|
||||
rs.add(getFieldValueFromResultSet(primaryKeyField.getType(), generatedKeys, 1));
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.sqlite;
|
||||
|
||||
|
||||
import java.sql.Connection;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.strategy.BaseRDBMSActionStrategy;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class BaseTest
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(BaseTest.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
void baseBeforeEach() throws Exception
|
||||
{
|
||||
QContext.init(TestUtils.defineInstance(), new QSession());
|
||||
TestUtils.primeTestDatabase("prime-test-database.sql");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@AfterEach
|
||||
void baseAfterEach()
|
||||
{
|
||||
BaseRDBMSActionStrategy actionStrategy = getBaseRDBMSActionStrategy();
|
||||
actionStrategy.setPageSize(BaseRDBMSActionStrategy.DEFAULT_PAGE_SIZE);
|
||||
actionStrategy.resetStatistics();
|
||||
actionStrategy.setCollectStatistics(false);
|
||||
|
||||
QContext.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
protected static BaseRDBMSActionStrategy getBaseRDBMSActionStrategy()
|
||||
{
|
||||
RDBMSBackendMetaData backend = (RDBMSBackendMetaData) QContext.getQInstance().getBackend(TestUtils.DEFAULT_BACKEND_NAME);
|
||||
BaseRDBMSActionStrategy actionStrategy = (BaseRDBMSActionStrategy) backend.getActionStrategy();
|
||||
return actionStrategy;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
protected static BaseRDBMSActionStrategy getBaseRDBMSActionStrategyAndActivateCollectingStatistics()
|
||||
{
|
||||
BaseRDBMSActionStrategy actionStrategy = getBaseRDBMSActionStrategy();
|
||||
actionStrategy.setCollectStatistics(true);
|
||||
actionStrategy.resetStatistics();
|
||||
return actionStrategy;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected static void reInitInstanceInContext(QInstance qInstance)
|
||||
{
|
||||
if(qInstance.equals(QContext.getQInstance()))
|
||||
{
|
||||
LOG.warn("Unexpected condition - the same qInstance that is already in the QContext was passed into reInit. You probably want a new QInstance object instance.");
|
||||
}
|
||||
QContext.init(qInstance, new QSession());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception
|
||||
{
|
||||
ConnectionManager connectionManager = new ConnectionManager();
|
||||
Connection connection = connectionManager.getConnection(TestUtils.defineBackend());
|
||||
QueryManager.executeStatement(connection, sql, resultSetProcessor);
|
||||
connection.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,470 @@
|
||||
/*
|
||||
* 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.sqlite;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.C3P0PooledConnectionProvider;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.model.metadata.SQLiteBackendMetaData;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class TestUtils
|
||||
{
|
||||
public static final String DEFAULT_BACKEND_NAME = "default";
|
||||
public static final String MEMORY_BACKEND_NAME = "memory";
|
||||
|
||||
public static final String TABLE_NAME_PERSON = "personTable";
|
||||
public static final String TABLE_NAME_PERSONAL_ID_CARD = "personalIdCard";
|
||||
public static final String TABLE_NAME_STORE = "store";
|
||||
public static final String TABLE_NAME_ORDER = "order";
|
||||
public static final String TABLE_NAME_ORDER_INSTRUCTIONS = "orderInstructions";
|
||||
public static final String TABLE_NAME_ITEM = "item";
|
||||
public static final String TABLE_NAME_ORDER_LINE = "orderLine";
|
||||
public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic";
|
||||
public static final String TABLE_NAME_WAREHOUSE = "warehouse";
|
||||
public static final String TABLE_NAME_WAREHOUSE_STORE_INT = "warehouseStoreInt";
|
||||
|
||||
public static final String SECURITY_KEY_STORE_ALL_ACCESS = "storeAllAccess";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void primeTestDatabase(String sqlFileName) throws Exception
|
||||
{
|
||||
SQLiteBackendMetaData backend = TestUtils.defineBackend();
|
||||
|
||||
File file = new File(backend.getPath());
|
||||
|
||||
/*
|
||||
if(file.exists())
|
||||
{
|
||||
if(!file.delete())
|
||||
{
|
||||
throw (new Exception("SQLite database at [" + file.getAbsolutePath() + "] exists, and could not be deleted before (re)priming the database."));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
file.getParentFile().mkdirs();
|
||||
|
||||
try(Connection connection = ConnectionManager.getConnection(backend))
|
||||
{
|
||||
InputStream primeTestDatabaseSqlStream = SQLiteBackendModule.class.getResourceAsStream("/" + sqlFileName);
|
||||
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(";"))
|
||||
{
|
||||
if(sql.matches("(?s).*[a-zA-Z0-9_].*"))
|
||||
{
|
||||
QueryManager.executeUpdate(connection, sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QInstance defineInstance()
|
||||
{
|
||||
QInstance qInstance = new QInstance();
|
||||
qInstance.addBackend(defineBackend());
|
||||
qInstance.addBackend(defineMemoryBackend());
|
||||
qInstance.addTable(defineTablePerson());
|
||||
qInstance.addPossibleValueSource(definePvsPerson());
|
||||
qInstance.addTable(defineTablePersonalIdCard());
|
||||
qInstance.addJoin(defineJoinPersonAndPersonalIdCard());
|
||||
addOmsTablesAndJoins(qInstance);
|
||||
qInstance.setAuthentication(defineAuthentication());
|
||||
return (qInstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the in-memory backend used in standard tests
|
||||
*******************************************************************************/
|
||||
public static QBackendMetaData defineMemoryBackend()
|
||||
{
|
||||
return new QBackendMetaData()
|
||||
.withName(MEMORY_BACKEND_NAME)
|
||||
.withBackendType(MemoryBackendModule.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the authentication used in standard tests - using 'mock' type.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QAuthenticationMetaData defineAuthentication()
|
||||
{
|
||||
return new QAuthenticationMetaData()
|
||||
.withName("mock")
|
||||
.withType(QAuthenticationType.MOCK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static SQLiteBackendMetaData defineBackend()
|
||||
{
|
||||
SQLiteBackendMetaData sqLiteBackendMetaData = new SQLiteBackendMetaData()
|
||||
.withName(DEFAULT_BACKEND_NAME)
|
||||
.withPath("/tmp/sqlite/test.db");
|
||||
|
||||
sqLiteBackendMetaData.setQueriesForNewConnections(List.of(
|
||||
"PRAGMA foreign_keys = ON"
|
||||
));
|
||||
|
||||
sqLiteBackendMetaData.setConnectionProvider(new QCodeReference(C3P0PooledConnectionProvider.class));
|
||||
|
||||
return sqLiteBackendMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QTableMetaData defineTablePerson()
|
||||
{
|
||||
return new QTableMetaData()
|
||||
.withName(TABLE_NAME_PERSON)
|
||||
.withLabel("Person")
|
||||
.withRecordLabelFormat("%s %s")
|
||||
.withRecordLabelFields("firstName", "lastName")
|
||||
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||
.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).withBackendName("email"))
|
||||
.withField(new QFieldMetaData("isEmployed", QFieldType.BOOLEAN).withBackendName("is_employed"))
|
||||
.withField(new QFieldMetaData("annualSalary", QFieldType.DECIMAL).withBackendName("annual_salary"))
|
||||
.withField(new QFieldMetaData("daysWorked", QFieldType.INTEGER).withBackendName("days_worked"))
|
||||
.withField(new QFieldMetaData("homeTown", QFieldType.STRING).withBackendName("home_town"))
|
||||
.withField(new QFieldMetaData("startTime", QFieldType.TIME).withBackendName("start_time"))
|
||||
.withBackendDetails(new RDBMSTableBackendDetails()
|
||||
.withTableName("person"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QPossibleValueSource definePvsPerson()
|
||||
{
|
||||
return (new QPossibleValueSource()
|
||||
.withName(TABLE_NAME_PERSON)
|
||||
.withType(QPossibleValueSourceType.TABLE)
|
||||
.withTableName(TABLE_NAME_PERSON)
|
||||
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define a 1:1 table with Person.
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QTableMetaData defineTablePersonalIdCard()
|
||||
{
|
||||
return new QTableMetaData()
|
||||
.withName(TABLE_NAME_PERSONAL_ID_CARD)
|
||||
.withLabel("Personal Id Card")
|
||||
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||
.withBackendDetails(new RDBMSTableBackendDetails()
|
||||
.withTableName("personal_id_card"))
|
||||
.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("personId", QFieldType.INTEGER).withBackendName("person_id"))
|
||||
.withField(new QFieldMetaData("idNumber", QFieldType.STRING).withBackendName("id_number"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QJoinMetaData defineJoinPersonAndPersonalIdCard()
|
||||
{
|
||||
return new QJoinMetaData()
|
||||
.withLeftTable(TABLE_NAME_PERSON)
|
||||
.withRightTable(TABLE_NAME_PERSONAL_ID_CARD)
|
||||
.withInferredName()
|
||||
.withType(JoinType.ONE_TO_ONE)
|
||||
.withJoinOn(new JoinOn("id", "personId"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void addOmsTablesAndJoins(QInstance qInstance)
|
||||
{
|
||||
qInstance.addTable(defineBaseTable(TABLE_NAME_STORE, "store")
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("name")
|
||||
.withRecordSecurityLock(new RecordSecurityLock().withSecurityKeyType(TABLE_NAME_STORE).withFieldName("id"))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||
);
|
||||
|
||||
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER, "order")
|
||||
.withRecordSecurityLock(new RecordSecurityLock().withSecurityKeyType(TABLE_NAME_STORE).withFieldName("storeId"))
|
||||
.withAssociation(new Association().withName("orderLine").withAssociatedTableName(TABLE_NAME_ORDER_LINE).withJoinName("orderJoinOrderLine"))
|
||||
.withExposedJoin(new ExposedJoin().withJoinTable(TABLE_NAME_ITEM).withJoinPath(List.of("orderJoinOrderLine", "orderLineJoinItem")))
|
||||
.withExposedJoin(new ExposedJoin().withJoinTable(TABLE_NAME_ORDER_INSTRUCTIONS).withJoinPath(List.of("orderJoinCurrentOrderInstructions")).withLabel("Current Order Instructions"))
|
||||
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id").withPossibleValueSourceName(TABLE_NAME_STORE))
|
||||
.withField(new QFieldMetaData("billToPersonId", QFieldType.INTEGER).withBackendName("bill_to_person_id").withPossibleValueSourceName(TABLE_NAME_PERSON))
|
||||
.withField(new QFieldMetaData("shipToPersonId", QFieldType.INTEGER).withBackendName("ship_to_person_id").withPossibleValueSourceName(TABLE_NAME_PERSON))
|
||||
.withField(new QFieldMetaData("currentOrderInstructionsId", QFieldType.INTEGER).withBackendName("current_order_instructions_id").withPossibleValueSourceName(TABLE_NAME_PERSON))
|
||||
);
|
||||
|
||||
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER_INSTRUCTIONS, "order_instructions")
|
||||
.withRecordSecurityLock(new RecordSecurityLock()
|
||||
.withSecurityKeyType(TABLE_NAME_STORE)
|
||||
.withFieldName("order.storeId")
|
||||
.withJoinNameChain(List.of("orderInstructionsJoinOrder")))
|
||||
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER).withBackendName("order_id"))
|
||||
.withField(new QFieldMetaData("instructions", QFieldType.STRING))
|
||||
.withExposedJoin(new ExposedJoin().withJoinTable(TABLE_NAME_ORDER).withJoinPath(List.of("orderInstructionsJoinOrder")))
|
||||
);
|
||||
|
||||
qInstance.addTable(defineBaseTable(TABLE_NAME_ITEM, "item")
|
||||
.withRecordSecurityLock(new RecordSecurityLock().withSecurityKeyType(TABLE_NAME_STORE).withFieldName("storeId"))
|
||||
.withExposedJoin(new ExposedJoin().withJoinTable(TABLE_NAME_ORDER).withJoinPath(List.of("orderLineJoinItem", "orderJoinOrderLine")))
|
||||
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("description", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id").withPossibleValueSourceName(TABLE_NAME_STORE))
|
||||
);
|
||||
|
||||
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER_LINE, "order_line")
|
||||
.withRecordSecurityLock(new RecordSecurityLock()
|
||||
.withSecurityKeyType(TABLE_NAME_STORE)
|
||||
.withFieldName("order.storeId")
|
||||
.withJoinNameChain(List.of("orderJoinOrderLine")))
|
||||
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_LINE_ITEM_EXTRINSIC).withJoinName("orderLineJoinLineItemExtrinsic"))
|
||||
.withField(new QFieldMetaData("orderId", QFieldType.INTEGER).withBackendName("order_id"))
|
||||
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id").withPossibleValueSourceName(TABLE_NAME_STORE))
|
||||
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER))
|
||||
);
|
||||
|
||||
qInstance.addTable(defineBaseTable(TABLE_NAME_LINE_ITEM_EXTRINSIC, "line_item_extrinsic")
|
||||
.withRecordSecurityLock(new RecordSecurityLock()
|
||||
.withSecurityKeyType(TABLE_NAME_STORE)
|
||||
.withFieldName("order.storeId")
|
||||
.withJoinNameChain(List.of("orderJoinOrderLine", "orderLineJoinLineItemExtrinsic")))
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("orderLineId", QFieldType.INTEGER).withBackendName("order_line_id"))
|
||||
.withField(new QFieldMetaData("key", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("value", QFieldType.STRING))
|
||||
);
|
||||
|
||||
qInstance.addTable(defineBaseTable(TABLE_NAME_WAREHOUSE_STORE_INT, "warehouse_store_int")
|
||||
.withField(new QFieldMetaData("warehouseId", QFieldType.INTEGER).withBackendName("warehouse_id"))
|
||||
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withBackendName("store_id"))
|
||||
);
|
||||
|
||||
qInstance.addTable(defineBaseTable(TABLE_NAME_WAREHOUSE, "warehouse")
|
||||
.withRecordSecurityLock(new RecordSecurityLock()
|
||||
.withSecurityKeyType(TABLE_NAME_STORE)
|
||||
.withFieldName(TABLE_NAME_WAREHOUSE_STORE_INT + ".storeId")
|
||||
.withJoinNameChain(List.of(QJoinMetaData.makeInferredJoinName(TestUtils.TABLE_NAME_WAREHOUSE, TestUtils.TABLE_NAME_WAREHOUSE_STORE_INT)))
|
||||
)
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING).withBackendName("name"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withType(JoinType.ONE_TO_MANY)
|
||||
.withLeftTable(TestUtils.TABLE_NAME_WAREHOUSE)
|
||||
.withRightTable(TestUtils.TABLE_NAME_WAREHOUSE_STORE_INT)
|
||||
.withInferredName()
|
||||
.withJoinOn(new JoinOn("id", "warehouseId"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withName("orderJoinStore")
|
||||
.withLeftTable(TABLE_NAME_ORDER)
|
||||
.withRightTable(TABLE_NAME_STORE)
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("storeId", "id"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withName("orderJoinBillToPerson")
|
||||
.withLeftTable(TABLE_NAME_ORDER)
|
||||
.withRightTable(TABLE_NAME_PERSON)
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("billToPersonId", "id"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withName("orderJoinShipToPerson")
|
||||
.withLeftTable(TABLE_NAME_ORDER)
|
||||
.withRightTable(TABLE_NAME_PERSON)
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("shipToPersonId", "id"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withName("itemJoinStore")
|
||||
.withLeftTable(TABLE_NAME_ITEM)
|
||||
.withRightTable(TABLE_NAME_STORE)
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("storeId", "id"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withName("orderJoinOrderLine")
|
||||
.withLeftTable(TABLE_NAME_ORDER)
|
||||
.withRightTable(TABLE_NAME_ORDER_LINE)
|
||||
.withType(JoinType.ONE_TO_MANY)
|
||||
.withJoinOn(new JoinOn("id", "orderId"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withName("orderLineJoinItem")
|
||||
.withLeftTable(TABLE_NAME_ORDER_LINE)
|
||||
.withRightTable(TABLE_NAME_ITEM)
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("sku", "sku"))
|
||||
.withJoinOn(new JoinOn("storeId", "storeId"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withName("orderLineJoinLineItemExtrinsic")
|
||||
.withLeftTable(TABLE_NAME_ORDER_LINE)
|
||||
.withRightTable(TABLE_NAME_LINE_ITEM_EXTRINSIC)
|
||||
.withType(JoinType.ONE_TO_MANY)
|
||||
.withJoinOn(new JoinOn("id", "orderLineId"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withName("orderJoinCurrentOrderInstructions")
|
||||
.withLeftTable(TABLE_NAME_ORDER)
|
||||
.withRightTable(TABLE_NAME_ORDER_INSTRUCTIONS)
|
||||
.withType(JoinType.ONE_TO_ONE)
|
||||
.withJoinOn(new JoinOn("currentOrderInstructionsId", "id"))
|
||||
);
|
||||
|
||||
qInstance.addJoin(new QJoinMetaData()
|
||||
.withName("orderInstructionsJoinOrder")
|
||||
.withRightTable(TABLE_NAME_ORDER_INSTRUCTIONS)
|
||||
.withLeftTable(TABLE_NAME_ORDER)
|
||||
.withType(JoinType.MANY_TO_ONE)
|
||||
.withJoinOn(new JoinOn("id", "orderId"))
|
||||
);
|
||||
|
||||
qInstance.addPossibleValueSource(new QPossibleValueSource()
|
||||
.withName("store")
|
||||
.withType(QPossibleValueSourceType.TABLE)
|
||||
.withTableName(TABLE_NAME_STORE)
|
||||
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY)
|
||||
);
|
||||
|
||||
qInstance.addSecurityKeyType(new QSecurityKeyType()
|
||||
.withName(TABLE_NAME_STORE)
|
||||
.withAllAccessKeyName(SECURITY_KEY_STORE_ALL_ACCESS)
|
||||
.withPossibleValueSourceName(TABLE_NAME_STORE));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QTableMetaData defineBaseTable(String tableName, String backendTableName)
|
||||
{
|
||||
return new QTableMetaData()
|
||||
.withName(tableName)
|
||||
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||
.withBackendDetails(new RDBMSTableBackendDetails().withTableName(backendTableName))
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QRecord> queryTable(String tableName) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(tableName);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
return (queryOutput.getRecords());
|
||||
}
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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.sqlite.actions;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.BaseTest;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SQLiteCountActionTest extends BaseTest
|
||||
{
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testUnfilteredCount() throws QException
|
||||
{
|
||||
CountInput countInput = initCountRequest();
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
assertEquals(5, countOutput.getCount(), "Unfiltered query should find all rows");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testEqualsQueryCount() throws QException
|
||||
{
|
||||
String email = "darin.kelkhoff@gmail.com";
|
||||
|
||||
CountInput countInput = initCountRequest();
|
||||
countInput.setFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria()
|
||||
.withFieldName("email")
|
||||
.withOperator(QCriteriaOperator.EQUALS)
|
||||
.withValues(List.of(email)))
|
||||
);
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
assertEquals(1, countOutput.getCount(), "Expected # of rows");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testNotEqualsQuery() throws QException
|
||||
{
|
||||
String email = "darin.kelkhoff@gmail.com";
|
||||
|
||||
CountInput countInput = initCountRequest();
|
||||
countInput.setFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria()
|
||||
.withFieldName("email")
|
||||
.withOperator(QCriteriaOperator.NOT_EQUALS)
|
||||
.withValues(List.of(email)))
|
||||
);
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
assertEquals(4, countOutput.getCount(), "Expected # of rows");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private CountInput initCountRequest()
|
||||
{
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(TestUtils.defineTablePerson().getName());
|
||||
return countInput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testOneToOneInnerJoinWithoutWhere() throws QException
|
||||
{
|
||||
CountInput countInput = initCountRequest();
|
||||
countInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD));
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
assertEquals(3, countOutput.getCount(), "Join count should find 3 rows");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testOneToOneLeftJoinWithoutWhere() throws QException
|
||||
{
|
||||
CountInput countInput = initCountRequest();
|
||||
countInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withType(QueryJoin.Type.LEFT));
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
assertEquals(5, countOutput.getCount(), "Left Join count should find 5 rows");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testOneToOneRightJoinWithoutWhere() throws QException
|
||||
{
|
||||
CountInput countInput = initCountRequest();
|
||||
countInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withType(QueryJoin.Type.RIGHT));
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
assertEquals(6, countOutput.getCount(), "Right Join count should find 6 rows");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testOneToOneInnerJoinWithWhere() throws QException
|
||||
{
|
||||
CountInput countInput = initCountRequest();
|
||||
countInput.withQueryJoin(new QueryJoin(TestUtils.TABLE_NAME_PERSON, TestUtils.TABLE_NAME_PERSONAL_ID_CARD).withSelect(true));
|
||||
countInput.setFilter(new QQueryFilter(new QFilterCriteria(TestUtils.TABLE_NAME_PERSONAL_ID_CARD + ".idNumber", QCriteriaOperator.STARTS_WITH, "1980")));
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
assertEquals(2, countOutput.getCount(), "Right Join count should find 2 rows");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testRecordSecurity() throws QException
|
||||
{
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||
|
||||
QContext.setQSession(new QSession());
|
||||
assertThat(new CountAction().execute(countInput).getCount()).isEqualTo(0);
|
||||
|
||||
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true));
|
||||
assertThat(new CountAction().execute(countInput).getCount()).isEqualTo(8);
|
||||
|
||||
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 2).withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 3));
|
||||
assertThat(new CountAction().execute(countInput).getCount()).isEqualTo(5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testRecordSecurityWithLockFromJoinTableWhereTheKeyIsOnTheManySide() throws QException
|
||||
{
|
||||
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true));
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(TestUtils.TABLE_NAME_WAREHOUSE);
|
||||
|
||||
assertThat(new CountAction().execute(countInput).getCount()).isEqualTo(4);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
/*
|
||||
* 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.sqlite.actions;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.actions.RDBMSDeleteAction;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.strategy.BaseRDBMSActionStrategy;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.BaseTest;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SQLiteDeleteActionTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testDeleteAll() throws Exception
|
||||
{
|
||||
DeleteInput deleteInput = initStandardPersonDeleteRequest();
|
||||
deleteInput.setPrimaryKeys(List.of(1, 2, 3, 4, 5));
|
||||
DeleteOutput deleteResult = new DeleteAction().execute(deleteInput);
|
||||
assertEquals(5, deleteResult.getDeletedRecordCount(), "Unfiltered delete should return all rows");
|
||||
assertEquals(0, deleteResult.getRecordsWithErrors().size(), "should have no errors");
|
||||
runTestSql("SELECT id FROM person", (rs -> assertFalse(rs.next())));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testDeleteOne() throws Exception
|
||||
{
|
||||
DeleteInput deleteInput = initStandardPersonDeleteRequest();
|
||||
deleteInput.setPrimaryKeys(List.of(1));
|
||||
DeleteOutput deleteResult = new DeleteAction().execute(deleteInput);
|
||||
assertEquals(1, deleteResult.getDeletedRecordCount(), "Should delete one row");
|
||||
assertEquals(0, deleteResult.getRecordsWithErrors().size(), "should have no errors");
|
||||
runTestSql("SELECT id FROM person WHERE id = 1", (rs -> assertFalse(rs.next())));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testDeleteSome() throws Exception
|
||||
{
|
||||
DeleteInput deleteInput = initStandardPersonDeleteRequest();
|
||||
deleteInput.setPrimaryKeys(List.of(1, 3, 5));
|
||||
DeleteOutput deleteResult = new DeleteAction().execute(deleteInput);
|
||||
assertEquals(3, deleteResult.getDeletedRecordCount(), "Should delete one row");
|
||||
assertEquals(0, deleteResult.getRecordsWithErrors().size(), "should have no errors");
|
||||
runTestSql("SELECT id FROM person", (rs ->
|
||||
{
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
{
|
||||
rowsFound++;
|
||||
assertTrue(rs.getInt(1) == 2 || rs.getInt(1) == 4);
|
||||
}
|
||||
assertEquals(2, rowsFound);
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDeleteSomeIdsThatExistAndSomeThatDoNot() throws Exception
|
||||
{
|
||||
DeleteInput deleteInput = initStandardPersonDeleteRequest();
|
||||
deleteInput.setPrimaryKeys(List.of(1, -1));
|
||||
DeleteOutput deleteResult = new RDBMSDeleteAction().execute(deleteInput);
|
||||
assertEquals(1, deleteResult.getDeletedRecordCount(), "Should delete one row");
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// note - that if we went to the top-level DeleteAction, then it would have pre- //
|
||||
// checked that the ids existed, and it WOULD give us an error for the -1 row here //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
assertEquals(0, deleteResult.getRecordsWithErrors().size(), "should have no errors (the one not found is just noop)");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private DeleteInput initStandardPersonDeleteRequest()
|
||||
{
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(TestUtils.defineTablePerson().getName());
|
||||
return deleteInput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testDeleteWhereForeignKeyBlocksSome() throws Exception
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// load the parent-child tables, with foreign keys and instance //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
TestUtils.primeTestDatabase("prime-test-database-parent-child-tables.sql");
|
||||
DeleteInput deleteInput = initChildTableInstanceAndDeleteRequest();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// try to delete all of the child records - 2 should fail, because they are referenced by parent_table.child_id //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
deleteInput.setPrimaryKeys(List.of(1, 2, 3, 4, 5));
|
||||
|
||||
BaseRDBMSActionStrategy actionStrategy = getBaseRDBMSActionStrategyAndActivateCollectingStatistics();
|
||||
DeleteOutput deleteResult = new RDBMSDeleteAction().execute(deleteInput);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// assert that 6 queries ran - the initial delete (which failed), then 5 more deletes //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
actionStrategy.setCollectStatistics(false);
|
||||
Map<String, Integer> queryStats = actionStrategy.getStatistics();
|
||||
assertEquals(6, queryStats.get(BaseRDBMSActionStrategy.STAT_QUERIES_RAN), "Number of queries ran");
|
||||
|
||||
assertEquals(2, deleteResult.getRecordsWithErrors().size(), "Should get back the 2 records with errors");
|
||||
assertTrue(deleteResult.getRecordsWithErrors().stream().noneMatch(r -> r.getErrors().isEmpty()), "All we got back should have errors");
|
||||
assertEquals(3, deleteResult.getDeletedRecordCount(), "Should get back that 3 were deleted");
|
||||
|
||||
runTestSql("SELECT id FROM child_table", (rs ->
|
||||
{
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
{
|
||||
rowsFound++;
|
||||
///////////////////////////////////////////
|
||||
// child_table rows 1 & 3 should survive //
|
||||
///////////////////////////////////////////
|
||||
assertTrue(rs.getInt(1) == 1 || rs.getInt(1) == 3);
|
||||
}
|
||||
assertEquals(2, rowsFound);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testDeleteByFilterThatJustWorks() throws Exception
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// load the parent-child tables, with foreign keys and instance //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
TestUtils.primeTestDatabase("prime-test-database-parent-child-tables.sql");
|
||||
DeleteInput deleteInput = initChildTableInstanceAndDeleteRequest();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// try to delete the records without a foreign key that'll block them //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
deleteInput.setQueryFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, List.of(2, 4, 5))));
|
||||
|
||||
BaseRDBMSActionStrategy actionStrategy = getBaseRDBMSActionStrategyAndActivateCollectingStatistics();
|
||||
DeleteOutput deleteResult = new RDBMSDeleteAction().execute(deleteInput);
|
||||
|
||||
//////////////////////////////////
|
||||
// assert that just 1 query ran //
|
||||
//////////////////////////////////
|
||||
actionStrategy.setCollectStatistics(false);
|
||||
Map<String, Integer> queryStats = actionStrategy.getStatistics();
|
||||
assertEquals(1, queryStats.get(BaseRDBMSActionStrategy.STAT_QUERIES_RAN), "Number of queries ran");
|
||||
assertEquals(3, deleteResult.getDeletedRecordCount(), "Should get back that 3 were deleted");
|
||||
|
||||
runTestSql("SELECT id FROM child_table", (rs ->
|
||||
{
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
{
|
||||
rowsFound++;
|
||||
///////////////////////////////////////////
|
||||
// child_table rows 1 & 3 should survive //
|
||||
///////////////////////////////////////////
|
||||
assertTrue(rs.getInt(1) == 1 || rs.getInt(1) == 3);
|
||||
}
|
||||
assertEquals(2, rowsFound);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testDeleteByFilterWhereForeignKeyBlocksSome() throws Exception
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// load the parent-child tables, with foreign keys and instance //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
TestUtils.primeTestDatabase("prime-test-database-parent-child-tables.sql");
|
||||
DeleteInput deleteInput = initChildTableInstanceAndDeleteRequest();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// try to delete all of the child records - 2 should fail, because they are referenced by parent_table.child_id //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
deleteInput.setQueryFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, List.of(1, 2, 3, 4, 5))));
|
||||
|
||||
BaseRDBMSActionStrategy actionStrategy = getBaseRDBMSActionStrategyAndActivateCollectingStatistics();
|
||||
DeleteOutput deleteResult = new RDBMSDeleteAction().execute(deleteInput);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// assert that 8 queries ran - the initial delete (which failed), then 1 to look up the ids //
|
||||
// from that query, another to try to delete all those ids (also fails), and finally 5 deletes by id //
|
||||
// todo - maybe we shouldn't do that 2nd "try to delete 'em all by id"... why would it ever work, //
|
||||
// but the original filter query didn't (other than malformed SQL)? //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
actionStrategy.setCollectStatistics(false);
|
||||
Map<String, Integer> queryStats = actionStrategy.getStatistics();
|
||||
assertEquals(8, queryStats.get(BaseRDBMSActionStrategy.STAT_QUERIES_RAN), "Number of queries ran");
|
||||
|
||||
assertEquals(2, deleteResult.getRecordsWithErrors().size(), "Should get back the 2 records with errors");
|
||||
assertTrue(deleteResult.getRecordsWithErrors().stream().noneMatch(r -> r.getErrors().isEmpty()), "All we got back should have errors");
|
||||
assertEquals(3, deleteResult.getDeletedRecordCount(), "Should get back that 3 were deleted");
|
||||
|
||||
runTestSql("SELECT id FROM child_table", (rs ->
|
||||
{
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
{
|
||||
rowsFound++;
|
||||
///////////////////////////////////////////
|
||||
// child_table rows 1 & 3 should survive //
|
||||
///////////////////////////////////////////
|
||||
assertTrue(rs.getInt(1) == 1 || rs.getInt(1) == 3);
|
||||
}
|
||||
assertEquals(2, rowsFound);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private DeleteInput initChildTableInstanceAndDeleteRequest()
|
||||
{
|
||||
QInstance qInstance = TestUtils.defineInstance();
|
||||
|
||||
String childTableName = "childTable";
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName(childTableName)
|
||||
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||
.withBackendDetails(new RDBMSTableBackendDetails()
|
||||
.withTableName("child_table")));
|
||||
|
||||
qInstance.addTable(new QTableMetaData()
|
||||
.withName("parentTable")
|
||||
.withBackendName(TestUtils.DEFAULT_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("childId", QFieldType.INTEGER).withBackendName("child_id"))
|
||||
.withBackendDetails(new RDBMSTableBackendDetails()
|
||||
.withTableName("parent_table")));
|
||||
|
||||
reInitInstanceInContext(qInstance);
|
||||
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(childTableName);
|
||||
|
||||
return deleteInput;
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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.sqlite.actions;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.strategy.BaseRDBMSActionStrategy;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.BaseTest;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.TestUtils;
|
||||
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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SQLiteInsertActionTest extends BaseTest
|
||||
{
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testInsertNullList() throws QException
|
||||
{
|
||||
InsertInput insertInput = initInsertRequest();
|
||||
insertInput.setRecords(null);
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
assertEquals(0, insertOutput.getRecords().size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testInsertEmptyList() throws QException
|
||||
{
|
||||
InsertInput insertInput = initInsertRequest();
|
||||
insertInput.setRecords(Collections.emptyList());
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
assertEquals(0, insertOutput.getRecords().size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testInsertOne() throws Exception
|
||||
{
|
||||
InsertInput insertInput = initInsertRequest();
|
||||
QRecord record = new QRecord().withTableName("person")
|
||||
.withValue("firstName", "James")
|
||||
.withValue("lastName", "Kirk")
|
||||
.withValue("email", "jamestk@starfleet.net")
|
||||
.withValue("birthDate", "2210-05-20");
|
||||
insertInput.setRecords(List.of(record));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
assertEquals(1, insertOutput.getRecords().size(), "Should return 1 row");
|
||||
assertNotNull(insertOutput.getRecords().get(0).getValue("id"), "Should have an id in the row");
|
||||
// todo - add errors to QRecord? assertTrue(insertResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors");
|
||||
assertAnInsertedPersonRecord("James", "Kirk", 6);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testInsertMany() throws Exception
|
||||
{
|
||||
getBaseRDBMSActionStrategyAndActivateCollectingStatistics()
|
||||
.setPageSize(2);
|
||||
|
||||
InsertInput insertInput = initInsertRequest();
|
||||
QRecord record1 = new QRecord().withTableName("person")
|
||||
.withValue("firstName", "Jean-Luc")
|
||||
.withValue("lastName", "Picard")
|
||||
.withValue("email", "jl@starfleet.net")
|
||||
.withValue("birthDate", "2310-05-20");
|
||||
QRecord record2 = new QRecord().withTableName("person")
|
||||
.withValue("firstName", "William")
|
||||
.withValue("lastName", "Riker")
|
||||
.withValue("email", "notthomas@starfleet.net")
|
||||
.withValue("birthDate", "2320-05-20");
|
||||
QRecord record3 = new QRecord().withTableName("person")
|
||||
.withValue("firstName", "Beverly")
|
||||
.withValue("lastName", "Crusher")
|
||||
.withValue("email", "doctor@starfleet.net")
|
||||
.withValue("birthDate", "2320-06-26");
|
||||
insertInput.setRecords(List.of(record1, record2, record3));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
assertEquals(3, insertOutput.getRecords().size(), "Should return right # of rows");
|
||||
assertEquals(6, insertOutput.getRecords().get(0).getValue("id"), "Should have next id in the row");
|
||||
assertEquals(7, insertOutput.getRecords().get(1).getValue("id"), "Should have next id in the row");
|
||||
assertEquals(8, insertOutput.getRecords().get(2).getValue("id"), "Should have next id in the row");
|
||||
|
||||
Map<String, Integer> statistics = getBaseRDBMSActionStrategy().getStatistics();
|
||||
assertEquals(2, statistics.get(BaseRDBMSActionStrategy.STAT_QUERIES_RAN));
|
||||
|
||||
assertAnInsertedPersonRecord("Jean-Luc", "Picard", 6);
|
||||
assertAnInsertedPersonRecord("William", "Riker", 7);
|
||||
assertAnInsertedPersonRecord("Beverly", "Crusher", 8);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInsertAssociations() throws QException
|
||||
{
|
||||
QContext.getQSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 1);
|
||||
|
||||
int originalNoOfOrderLineExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC).size();
|
||||
int originalNoOfOrderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_LINE).size();
|
||||
int originalNoOfOrders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER).size();
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||
insertInput.setRecords(List.of(
|
||||
new QRecord().withValue("storeId", 1).withValue("billToPersonId", 100).withValue("shipToPersonId", 200)
|
||||
|
||||
.withAssociatedRecord("orderLine", new QRecord().withValue("storeId", 1).withValue("sku", "BASIC1").withValue("quantity", 1)
|
||||
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "LINE-EXT-1.1").withValue("value", "LINE-VAL-1")))
|
||||
|
||||
.withAssociatedRecord("orderLine", new QRecord().withValue("storeId", 1).withValue("sku", "BASIC2").withValue("quantity", 2)
|
||||
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "LINE-EXT-2.1").withValue("value", "LINE-VAL-2"))
|
||||
.withAssociatedRecord("extrinsics", new QRecord().withValue("key", "LINE-EXT-2.2").withValue("value", "LINE-VAL-3")))
|
||||
));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
List<QRecord> orders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER);
|
||||
assertEquals(originalNoOfOrders + 1, orders.size());
|
||||
assertTrue(orders.stream().anyMatch(r -> Objects.equals(r.getValue("billToPersonId"), 100) && Objects.equals(r.getValue("shipToPersonId"), 200)));
|
||||
|
||||
List<QRecord> orderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_LINE);
|
||||
assertEquals(originalNoOfOrderLines + 2, orderLines.size());
|
||||
assertTrue(orderLines.stream().anyMatch(r -> Objects.equals(r.getValue("sku"), "BASIC1") && Objects.equals(r.getValue("quantity"), 1)));
|
||||
assertTrue(orderLines.stream().anyMatch(r -> Objects.equals(r.getValue("sku"), "BASIC2") && Objects.equals(r.getValue("quantity"), 2)));
|
||||
|
||||
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
|
||||
assertEquals(originalNoOfOrderLineExtrinsics + 3, lineItemExtrinsics.size());
|
||||
assertTrue(lineItemExtrinsics.stream().anyMatch(r -> Objects.equals(r.getValue("key"), "LINE-EXT-1.1") && Objects.equals(r.getValue("value"), "LINE-VAL-1")));
|
||||
assertTrue(lineItemExtrinsics.stream().anyMatch(r -> Objects.equals(r.getValue("key"), "LINE-EXT-2.1") && Objects.equals(r.getValue("value"), "LINE-VAL-2")));
|
||||
assertTrue(lineItemExtrinsics.stream().anyMatch(r -> Objects.equals(r.getValue("key"), "LINE-EXT-2.2") && Objects.equals(r.getValue("value"), "LINE-VAL-3")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private void assertAnInsertedPersonRecord(String firstName, String lastName, Integer id) throws Exception
|
||||
{
|
||||
runTestSql("SELECT * FROM person WHERE last_name = '" + lastName + "'", (rs ->
|
||||
{
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
{
|
||||
rowsFound++;
|
||||
assertEquals(id, rs.getInt("id"));
|
||||
assertEquals(firstName, rs.getString("first_name"));
|
||||
assertNotNull(rs.getString("create_date"));
|
||||
assertNotNull(rs.getString("modify_date"));
|
||||
}
|
||||
assertEquals(1, rowsFound);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private InsertInput initInsertRequest()
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
return insertInput;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,443 @@
|
||||
/*
|
||||
* 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.sqlite.actions;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.strategy.BaseRDBMSActionStrategy;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.BaseTest;
|
||||
import com.kingsrook.qqq.backend.module.sqlite.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.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SQLiteUpdateActionTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
public void beforeEach() throws Exception
|
||||
{
|
||||
getBaseRDBMSActionStrategyAndActivateCollectingStatistics();
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testUpdateNullList() throws QException
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
updateInput.setRecords(null);
|
||||
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
|
||||
assertEquals(0, updateResult.getRecords().size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testUpdateEmptyList() throws QException
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
updateInput.setRecords(Collections.emptyList());
|
||||
new UpdateAction().execute(updateInput);
|
||||
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
|
||||
assertEquals(0, updateResult.getRecords().size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testUpdateOne() throws Exception
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
QRecord record = new QRecord()
|
||||
.withValue("id", 2)
|
||||
.withValue("firstName", "James")
|
||||
.withValue("lastName", "Kirk")
|
||||
.withValue("email", "jamestk@starfleet.net")
|
||||
.withValue("birthDate", "2210-05-20");
|
||||
updateInput.setRecords(List.of(record));
|
||||
|
||||
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
|
||||
Map<String, Integer> statistics = getBaseRDBMSActionStrategy().getStatistics();
|
||||
assertEquals(2, statistics.get(BaseRDBMSActionStrategy.STAT_QUERIES_RAN));
|
||||
|
||||
assertEquals(1, updateResult.getRecords().size(), "Should return 1 row");
|
||||
assertEquals(2, updateResult.getRecords().get(0).getValue("id"), "Should have id=2 in the row");
|
||||
// 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())
|
||||
{
|
||||
rowsFound++;
|
||||
assertEquals(2, rs.getInt("id"));
|
||||
assertEquals("James", rs.getString("first_name"));
|
||||
assertEquals("2210-05-20", rs.getString("birth_date"));
|
||||
}
|
||||
assertEquals(1, rowsFound);
|
||||
}));
|
||||
runTestSql("SELECT * FROM person WHERE last_name = 'Maes'", (rs ->
|
||||
{
|
||||
if(rs.next())
|
||||
{
|
||||
fail("Should not have found Maes any more.");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testUpdateManyWithDifferentColumnsAndValues() throws Exception
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
QRecord record1 = new QRecord()
|
||||
.withValue("id", 1)
|
||||
.withValue("firstName", "Darren")
|
||||
.withValue("lastName", "From Bewitched")
|
||||
.withValue("birthDate", "1900-01-01");
|
||||
|
||||
QRecord record2 = new QRecord()
|
||||
.withValue("id", 3)
|
||||
.withValue("firstName", "Wilt")
|
||||
.withValue("birthDate", null);
|
||||
|
||||
QRecord record3 = new QRecord()
|
||||
.withValue("id", 5)
|
||||
.withValue("firstName", "Richard")
|
||||
.withValue("birthDate", null);
|
||||
|
||||
updateInput.setRecords(List.of(record1, record2, record3));
|
||||
|
||||
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
|
||||
|
||||
// this test runs one batch and one regular query
|
||||
Map<String, Integer> statistics = getBaseRDBMSActionStrategy().getStatistics();
|
||||
assertEquals(1, statistics.get(BaseRDBMSActionStrategy.STAT_BATCHES_RAN));
|
||||
assertEquals(2, statistics.get(BaseRDBMSActionStrategy.STAT_QUERIES_RAN));
|
||||
|
||||
assertEquals(3, updateResult.getRecords().size(), "Should return 3 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");
|
||||
assertEquals(5, updateResult.getRecords().get(2).getValue("id"), "Should have expected ids in the row");
|
||||
// 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())
|
||||
{
|
||||
rowsFound++;
|
||||
assertEquals(1, rs.getInt("id"));
|
||||
assertEquals("Darren", rs.getString("first_name"));
|
||||
assertEquals("From Bewitched", rs.getString("last_name"));
|
||||
assertEquals("1900-01-01", rs.getString("birth_date"));
|
||||
}
|
||||
assertEquals(1, rowsFound);
|
||||
}));
|
||||
runTestSql("SELECT * FROM person WHERE last_name = 'Chamberlain'", (rs ->
|
||||
{
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
{
|
||||
rowsFound++;
|
||||
assertEquals(3, rs.getInt("id"));
|
||||
assertEquals("Wilt", rs.getString("first_name"));
|
||||
assertNull(rs.getString("birth_date"));
|
||||
}
|
||||
assertEquals(1, rowsFound);
|
||||
}));
|
||||
runTestSql("SELECT * FROM person WHERE last_name = 'Richardson'", (rs ->
|
||||
{
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
{
|
||||
rowsFound++;
|
||||
assertEquals(5, rs.getInt("id"));
|
||||
assertEquals("Richard", rs.getString("first_name"));
|
||||
assertNull(rs.getString("birth_date"));
|
||||
}
|
||||
assertEquals(1, rowsFound);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testUpdateManyWithSameColumnsDifferentValues() throws Exception
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
QRecord record1 = new QRecord()
|
||||
.withValue("id", 1)
|
||||
.withValue("firstName", "Darren")
|
||||
.withValue("lastName", "From Bewitched")
|
||||
.withValue("birthDate", "1900-01-01");
|
||||
|
||||
QRecord record2 = new QRecord()
|
||||
.withValue("id", 3)
|
||||
.withValue("firstName", "Wilt")
|
||||
.withValue("lastName", "Tim's Uncle")
|
||||
.withValue("birthDate", null);
|
||||
|
||||
updateInput.setRecords(List.of(record1, record2));
|
||||
|
||||
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
|
||||
Map<String, Integer> statistics = getBaseRDBMSActionStrategy().getStatistics();
|
||||
assertEquals(1, statistics.get(BaseRDBMSActionStrategy.STAT_BATCHES_RAN));
|
||||
|
||||
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");
|
||||
// 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())
|
||||
{
|
||||
rowsFound++;
|
||||
assertEquals(1, rs.getInt("id"));
|
||||
assertEquals("Darren", rs.getString("first_name"));
|
||||
assertEquals("From Bewitched", rs.getString("last_name"));
|
||||
assertEquals("1900-01-01", rs.getString("birth_date"));
|
||||
}
|
||||
assertEquals(1, rowsFound);
|
||||
}));
|
||||
runTestSql("SELECT * FROM person WHERE last_name = 'Tim''s Uncle'", (rs ->
|
||||
{
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
{
|
||||
rowsFound++;
|
||||
assertEquals(3, rs.getInt("id"));
|
||||
assertEquals("Wilt", rs.getString("first_name"));
|
||||
assertEquals("Tim's Uncle", rs.getString("last_name"));
|
||||
assertNull(rs.getString("birth_date"));
|
||||
}
|
||||
assertEquals(1, rowsFound);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
public void testUpdateManyWithSameColumnsSameValues() throws Exception
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
List<QRecord> records = new ArrayList<>();
|
||||
for(int i = 1; i <= 5; i++)
|
||||
{
|
||||
records.add(new QRecord()
|
||||
.withValue("id", i)
|
||||
.withValue("birthDate", "1999-09-09"));
|
||||
}
|
||||
|
||||
updateInput.setRecords(records);
|
||||
|
||||
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
|
||||
Map<String, Integer> statistics = getBaseRDBMSActionStrategy().getStatistics();
|
||||
assertEquals(2, statistics.get(BaseRDBMSActionStrategy.STAT_QUERIES_RAN));
|
||||
|
||||
assertEquals(5, updateResult.getRecords().size(), "Should return 5 rows");
|
||||
// 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 id <= 5", (rs ->
|
||||
{
|
||||
int rowsFound = 0;
|
||||
while(rs.next())
|
||||
{
|
||||
rowsFound++;
|
||||
assertEquals("1999-09-09", rs.getString("birth_date"));
|
||||
}
|
||||
assertEquals(5, rowsFound);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testModifyDateGetsUpdated() throws Exception
|
||||
{
|
||||
String originalModifyDate = selectModifyDate(1);
|
||||
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
List<QRecord> records = new ArrayList<>();
|
||||
records.add(new QRecord()
|
||||
.withValue("id", 1)
|
||||
.withValue("firstName", "Johnny Updated"));
|
||||
updateInput.setRecords(records);
|
||||
new UpdateAction().execute(updateInput);
|
||||
|
||||
String updatedModifyDate = selectModifyDate(1);
|
||||
|
||||
assertTrue(StringUtils.hasContent(originalModifyDate));
|
||||
assertTrue(StringUtils.hasContent(updatedModifyDate));
|
||||
assertNotEquals(originalModifyDate, updatedModifyDate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** This situation - fails in a real mysql, but not in h2... anyway, because mysql
|
||||
** didn't want to convert the date-time string format to a date-time.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDateTimesCanBeModifiedFromIsoStrings() throws Exception
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
List<QRecord> records = new ArrayList<>();
|
||||
records.add(new QRecord()
|
||||
.withValue("id", 1)
|
||||
.withValue("createDate", "2022-10-03T10:29:35Z")
|
||||
.withValue("firstName", "Johnny Updated"));
|
||||
updateInput.setRecords(records);
|
||||
new UpdateAction().execute(updateInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Make sure that records without a primary key come back with error.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testWithoutPrimaryKeyErrors() throws Exception
|
||||
{
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
List<QRecord> records = new ArrayList<>();
|
||||
records.add(new QRecord()
|
||||
.withValue("firstName", "Johnny Updated"));
|
||||
updateInput.setRecords(records);
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertFalse(updateOutput.getRecords().get(0).getErrors().isEmpty());
|
||||
assertEquals("Missing value in primary key field", updateOutput.getRecords().get(0).getErrors().get(0).getMessage());
|
||||
}
|
||||
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
List<QRecord> records = new ArrayList<>();
|
||||
records.add(new QRecord()
|
||||
.withValue("id", null)
|
||||
.withValue("firstName", "Johnny Updated"));
|
||||
updateInput.setRecords(records);
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertFalse(updateOutput.getRecords().get(0).getErrors().isEmpty());
|
||||
assertEquals("Missing value in primary key field", updateOutput.getRecords().get(0).getErrors().get(0).getMessage());
|
||||
}
|
||||
|
||||
{
|
||||
UpdateInput updateInput = initUpdateRequest();
|
||||
List<QRecord> records = new ArrayList<>();
|
||||
records.add(new QRecord()
|
||||
.withValue("id", null)
|
||||
.withValue("firstName", "Johnny Not Updated"));
|
||||
records.add(new QRecord()
|
||||
.withValue("id", 2)
|
||||
.withValue("firstName", "Johnny Updated"));
|
||||
updateInput.setRecords(records);
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
|
||||
assertFalse(updateOutput.getRecords().get(0).getErrors().isEmpty());
|
||||
assertEquals("Missing value in primary key field", updateOutput.getRecords().get(0).getErrors().get(0).getMessage());
|
||||
|
||||
assertTrue(updateOutput.getRecords().get(1).getErrors().isEmpty());
|
||||
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
getInput.setPrimaryKey(2);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
assertEquals("Johnny Updated", getOutput.getRecord().getValueString("firstName"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String selectModifyDate(Integer id) throws Exception
|
||||
{
|
||||
StringBuilder modifyDate = new StringBuilder();
|
||||
runTestSql("SELECT modify_date FROM person WHERE id = " + id, (rs ->
|
||||
{
|
||||
if(rs.next())
|
||||
{
|
||||
modifyDate.append(rs.getString("modify_date"));
|
||||
}
|
||||
}));
|
||||
return (modifyDate.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private UpdateInput initUpdateRequest()
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
return updateInput;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
--
|
||||
-- QQQ - Low-code Application Framework for Engineers.
|
||||
-- Copyright (C) 2021-2025. 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 parent_table;
|
||||
DROP TABLE IF EXISTS child_table;
|
||||
|
||||
CREATE TABLE child_table
|
||||
(
|
||||
id INT AUTO_INCREMENT primary key,
|
||||
name VARCHAR(80) NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO child_table (id, name) VALUES (1, 'Timmy');
|
||||
INSERT INTO child_table (id, name) VALUES (2, 'Jimmy');
|
||||
INSERT INTO child_table (id, name) VALUES (3, 'Johnny');
|
||||
INSERT INTO child_table (id, name) VALUES (4, 'Gracie');
|
||||
INSERT INTO child_table (id, name) VALUES (5, 'Suzie');
|
||||
|
||||
CREATE TABLE parent_table
|
||||
(
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(80) NOT NULL,
|
||||
child_id INT,
|
||||
foreign key (child_id) references child_table(id)
|
||||
);
|
||||
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (1, 'Tim''s Dad', 1);
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (2, 'Tim''s Mom', 1);
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (3, 'Childless Man', null);
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (4, 'Childless Woman', null);
|
||||
INSERT INTO parent_table (id, name, child_id) VALUES (5, 'Johny''s Single Dad', 3);
|
@ -0,0 +1,215 @@
|
||||
--
|
||||
-- QQQ - Low-code Application Framework for Engineers.
|
||||
-- Copyright (C) 2021-2025. 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 INTEGER PRIMARY KEY,
|
||||
create_date TIMESTAMP, -- DEFAULT datetime('now'), -- can't get this to work!
|
||||
modify_date TIMESTAMP, -- DEFAULT datetime('now'),
|
||||
|
||||
first_name VARCHAR(80) NOT NULL,
|
||||
last_name VARCHAR(80) NOT NULL,
|
||||
birth_date DATE,
|
||||
email VARCHAR(250) NOT NULL,
|
||||
is_employed BOOLEAN,
|
||||
annual_salary DECIMAL(12,2),
|
||||
days_worked INTEGER,
|
||||
home_town VARCHAR(80),
|
||||
start_time TIME
|
||||
);
|
||||
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com', 1, 25000, 27, 'Chester');
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com', 1, 26000, 124, 'Chester');
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com', 0, null, 0, 'Decatur');
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com', 1, 30000, 99, 'Texas');
|
||||
INSERT INTO person (id, first_name, last_name, birth_date, email, is_employed, annual_salary, days_worked, home_town) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com', 1, 1000000, 232, null);
|
||||
|
||||
DROP TABLE IF EXISTS personal_id_card;
|
||||
CREATE TABLE personal_id_card
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
create_date TIMESTAMP, -- DEFAULT date(),
|
||||
modify_date TIMESTAMP, -- DEFAULT date(),
|
||||
person_id INTEGER,
|
||||
id_number VARCHAR(250)
|
||||
);
|
||||
|
||||
INSERT INTO personal_id_card (person_id, id_number) VALUES (1, '19800531');
|
||||
INSERT INTO personal_id_card (person_id, id_number) VALUES (2, '19800515');
|
||||
INSERT INTO personal_id_card (person_id, id_number) VALUES (3, '19760528');
|
||||
INSERT INTO personal_id_card (person_id, id_number) VALUES (6, '123123123');
|
||||
INSERT INTO personal_id_card (person_id, id_number) VALUES (null, '987987987');
|
||||
INSERT INTO personal_id_card (person_id, id_number) VALUES (null, '456456456');
|
||||
|
||||
DROP TABLE IF EXISTS carrier;
|
||||
CREATE TABLE carrier
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR(80) NOT NULL,
|
||||
company_code VARCHAR(80) NOT NULL,
|
||||
service_level VARCHAR(80) NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (1, 'UPS Ground', 'UPS', 'G');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (2, 'UPS 2Day', 'UPS', '2');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (3, 'UPS International', 'UPS', 'I');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (4, 'Fedex Ground', 'FEDEX', 'G');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (5, 'Fedex Next Day', 'UPS', '1');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (6, 'Will Call', 'WILL_CALL', 'W');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (7, 'USPS Priority', 'USPS', '1');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (8, 'USPS Super Slow', 'USPS', '4');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (9, 'USPS Super Fast', 'USPS', '0');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (10, 'DHL International', 'DHL', 'I');
|
||||
INSERT INTO carrier (id, name, company_code, service_level) VALUES (11, 'GSO', 'GSO', 'G');
|
||||
|
||||
DROP TABLE IF EXISTS line_item_extrinsic;
|
||||
DROP TABLE IF EXISTS order_line;
|
||||
DROP TABLE IF EXISTS item;
|
||||
DROP TABLE IF EXISTS `order`;
|
||||
DROP TABLE IF EXISTS order_instructions;
|
||||
DROP TABLE IF EXISTS warehouse_store_int;
|
||||
DROP TABLE IF EXISTS store;
|
||||
DROP TABLE IF EXISTS warehouse;
|
||||
|
||||
CREATE TABLE store
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR(80) NOT NULL
|
||||
);
|
||||
|
||||
-- define 3 stores
|
||||
INSERT INTO store (id, name) VALUES (1, 'Q-Mart');
|
||||
INSERT INTO store (id, name) VALUES (2, 'QQQ ''R'' Us');
|
||||
INSERT INTO store (id, name) VALUES (3, 'QDepot');
|
||||
|
||||
CREATE TABLE item
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
sku VARCHAR(80) NOT NULL,
|
||||
description VARCHAR(80),
|
||||
store_id INT NOT NULL REFERENCES store
|
||||
);
|
||||
|
||||
-- three items for each store
|
||||
INSERT INTO item (id, sku, description, store_id) VALUES (1, 'QM-1', 'Q-Mart Item 1', 1);
|
||||
INSERT INTO item (id, sku, description, store_id) VALUES (2, 'QM-2', 'Q-Mart Item 2', 1);
|
||||
INSERT INTO item (id, sku, description, store_id) VALUES (3, 'QM-3', 'Q-Mart Item 3', 1);
|
||||
INSERT INTO item (id, sku, description, store_id) VALUES (4, 'QRU-1', 'QQQ R Us Item 4', 2);
|
||||
INSERT INTO item (id, sku, description, store_id) VALUES (5, 'QRU-2', 'QQQ R Us Item 5', 2);
|
||||
INSERT INTO item (id, sku, description, store_id) VALUES (6, 'QRU-3', 'QQQ R Us Item 6', 2);
|
||||
INSERT INTO item (id, sku, description, store_id) VALUES (7, 'QD-1', 'QDepot Item 7', 3);
|
||||
INSERT INTO item (id, sku, description, store_id) VALUES (8, 'QD-2', 'QDepot Item 8', 3);
|
||||
INSERT INTO item (id, sku, description, store_id) VALUES (9, 'QD-3', 'QDepot Item 9', 3);
|
||||
|
||||
CREATE TABLE `order`
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
store_id INT REFERENCES store,
|
||||
bill_to_person_id INT,
|
||||
ship_to_person_id INT,
|
||||
current_order_instructions_id INT -- f-key to order_instructions, which also has an f-key back here!
|
||||
);
|
||||
|
||||
-- variable orders
|
||||
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (1, 1, 1, 1);
|
||||
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (2, 1, 1, 2);
|
||||
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (3, 1, 2, 3);
|
||||
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (4, 2, 4, 5);
|
||||
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (5, 2, 5, 4);
|
||||
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (6, 3, 5, null);
|
||||
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (7, 3, null, 5);
|
||||
INSERT INTO `order` (id, store_id, bill_to_person_id, ship_to_person_id) VALUES (8, 3, null, 5);
|
||||
|
||||
CREATE TABLE order_instructions
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
order_id INT,
|
||||
instructions VARCHAR(250)
|
||||
);
|
||||
|
||||
-- give orders 1 & 2 multiple versions of the instruction record
|
||||
INSERT INTO order_instructions (id, order_id, instructions) VALUES (1, 1, 'order 1 v1');
|
||||
INSERT INTO order_instructions (id, order_id, instructions) VALUES (2, 1, 'order 1 v2');
|
||||
UPDATE `order` SET current_order_instructions_id = 2 WHERE id=1;
|
||||
|
||||
INSERT INTO order_instructions (id, order_id, instructions) VALUES (3, 2, 'order 2 v1');
|
||||
INSERT INTO order_instructions (id, order_id, instructions) VALUES (4, 2, 'order 2 v2');
|
||||
INSERT INTO order_instructions (id, order_id, instructions) VALUES (5, 2, 'order 2 v3');
|
||||
UPDATE `order` SET current_order_instructions_id = 5 WHERE id=2;
|
||||
|
||||
-- give all other orders just 1 instruction
|
||||
INSERT INTO order_instructions (order_id, instructions) SELECT id, concat('order ', id, ' v1') FROM `order` WHERE current_order_instructions_id IS NULL;
|
||||
UPDATE `order` SET current_order_instructions_id = (SELECT MIN(id) FROM order_instructions WHERE order_id = `order`.id) WHERE current_order_instructions_id is null;
|
||||
|
||||
CREATE TABLE order_line
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
order_id INT REFERENCES `order`,
|
||||
sku VARCHAR(80),
|
||||
store_id INT REFERENCES store,
|
||||
quantity INT
|
||||
);
|
||||
|
||||
-- various lines
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (1, 'QM-1', 1, 10);
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (1, 'QM-2', 1, 1);
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (1, 'QM-3', 1, 1);
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (2, 'QRU-1', 2, 1); -- this line has an item from a different store than its order.
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (3, 'QM-1', 1, 20);
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (4, 'QRU-1', 2, 1);
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (4, 'QRU-2', 2, 2);
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (5, 'QRU-1', 2, 1);
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (6, 'QD-1', 3, 1);
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (7, 'QD-1', 3, 2);
|
||||
INSERT INTO order_line (order_id, sku, store_id, quantity) VALUES (8, 'QD-1', 3, 3);
|
||||
|
||||
|
||||
CREATE TABLE warehouse
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR(80)
|
||||
);
|
||||
|
||||
INSERT INTO warehouse (name) VALUES ('Patterson');
|
||||
INSERT INTO warehouse (name) VALUES ('Edison');
|
||||
INSERT INTO warehouse (name) VALUES ('Stockton');
|
||||
INSERT INTO warehouse (name) VALUES ('Somewhere in Texas');
|
||||
|
||||
CREATE TABLE warehouse_store_int
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
warehouse_id INT REFERENCES `warehouse`,
|
||||
store_id INT REFERENCES `store`
|
||||
);
|
||||
|
||||
INSERT INTO warehouse_store_int (warehouse_id, store_id) VALUES (1, 1);
|
||||
INSERT INTO warehouse_store_int (warehouse_id, store_id) VALUES (1, 2);
|
||||
INSERT INTO warehouse_store_int (warehouse_id, store_id) VALUES (1, 3);
|
||||
|
||||
CREATE TABLE line_item_extrinsic
|
||||
(
|
||||
id INTEGER PRIMARY KEY,
|
||||
order_line_id INT REFERENCES order_line,
|
||||
`key` VARCHAR(80),
|
||||
`value` VARCHAR(80)
|
||||
);
|
||||
|
Reference in New Issue
Block a user