Checkpoint: Added Update (edit) action; Load definition from JSON

This commit is contained in:
Darin Kelkhoff
2022-03-01 18:29:59 -06:00
parent 200a0b7ccf
commit 154468bdf3
8 changed files with 807 additions and 186 deletions

14
pom.xml
View File

@ -79,7 +79,19 @@
<build>
<plugins>
<!-- plugins specifically for this module -->
<!-- none at this time -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.kingsrook.qqq.frontend.picocli.QPicoCliImplementation</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!-- Common plugins for all qqq modules -->
<plugin>

View File

@ -0,0 +1,253 @@
/*
* Copyright © 2021-2022. Kingsrook LLC <contact@kingsrook.com>. All Rights Reserved.
*/
package com.kingsrook.qqq.frontend.picocli;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import picocli.CommandLine;
/*******************************************************************************
** Helper class for QPicCliImplementation to build the Command
**
*******************************************************************************/
public class QCommandBuilder
{
private final QInstance qInstance;
/*******************************************************************************
** Constructor.
**
*******************************************************************************/
public QCommandBuilder(QInstance qInstance)
{
this.qInstance = qInstance;
}
/*******************************************************************************
**
*******************************************************************************/
CommandLine.Model.CommandSpec buildCommandSpec(String topCommandName)
{
//////////////////////////////////
// define the top-level command //
//////////////////////////////////
CommandLine.Model.CommandSpec topCommandSpec = CommandLine.Model.CommandSpec.create();
topCommandSpec.name(topCommandName);
topCommandSpec.version(topCommandName + " v1.0"); // todo... uh?
topCommandSpec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options
topCommandSpec.addOption(CommandLine.Model.OptionSpec.builder("-m", "--meta-data")
.type(boolean.class)
.description("Output the meta-data for this CLI")
.build());
/////////////////////////////////////
// add each table as a sub-command //
/////////////////////////////////////
qInstance.getTables().keySet().stream().sorted().forEach(tableName ->
{
QTableMetaData table = qInstance.getTable(tableName);
CommandLine.Model.CommandSpec tableCommand = CommandLine.Model.CommandSpec.create();
topCommandSpec.addSubcommand(table.getName(), tableCommand);
///////////////////////////////////////////////////
// add table-specific sub-commands for the table //
///////////////////////////////////////////////////
tableCommand.addSubcommand("meta-data", defineMetaDataCommand(table));
tableCommand.addSubcommand("query", defineQueryCommand(table));
tableCommand.addSubcommand("insert", defineInsertCommand(table));
tableCommand.addSubcommand("update", defineUpdateCommand(table));
tableCommand.addSubcommand("delete", defineDeleteCommand(table));
List<QProcessMetaData> processes = qInstance.getProcessesForTable(tableName);
if(CollectionUtils.nullSafeHasContents(processes))
{
tableCommand.addSubcommand("process", defineTableProcessesCommand(table, processes));
}
});
return topCommandSpec;
}
/*******************************************************************************
**
*******************************************************************************/
private CommandLine.Model.CommandSpec defineMetaDataCommand(QTableMetaData table)
{
return CommandLine.Model.CommandSpec.create();
}
/*******************************************************************************
**
*******************************************************************************/
private CommandLine.Model.CommandSpec defineQueryCommand(QTableMetaData table)
{
CommandLine.Model.CommandSpec queryCommand = CommandLine.Model.CommandSpec.create();
queryCommand.addOption(CommandLine.Model.OptionSpec.builder("-l", "--limit")
.type(int.class)
.build());
queryCommand.addOption(CommandLine.Model.OptionSpec.builder("-s", "--skip")
.type(int.class)
.build());
queryCommand.addOption(CommandLine.Model.OptionSpec.builder("-c", "--criteria")
.type(String[].class)
.build());
// todo - add the fields as explicit params?
return queryCommand;
}
/*******************************************************************************
**
*******************************************************************************/
private CommandLine.Model.CommandSpec defineUpdateCommand(QTableMetaData table)
{
CommandLine.Model.CommandSpec updateCommand = CommandLine.Model.CommandSpec.create();
/*
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--jsonBody")
.type(String.class)
.build());
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--jsonFile")
.type(String.class)
.build());
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--csvFile")
.type(String.class)
.build());
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--mapping")
.type(String.class)
.build());
*/
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--primaryKey")
// type(getClassForField(primaryKeyField))
.type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type?
.build());
for(QFieldMetaData field : table.getFields().values())
{
if(!field.equals(primaryKeyField))
{
updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--field-" + field.getName())
.type(getClassForField(field))
.build());
}
}
return updateCommand;
}
/*******************************************************************************
**
*******************************************************************************/
private CommandLine.Model.CommandSpec defineInsertCommand(QTableMetaData table)
{
CommandLine.Model.CommandSpec insertCommand = CommandLine.Model.CommandSpec.create();
insertCommand.addOption(CommandLine.Model.OptionSpec.builder("--jsonBody")
.type(String.class)
.build());
insertCommand.addOption(CommandLine.Model.OptionSpec.builder("--jsonFile")
.type(String.class)
.build());
insertCommand.addOption(CommandLine.Model.OptionSpec.builder("--csvFile")
.type(String.class)
.build());
insertCommand.addOption(CommandLine.Model.OptionSpec.builder("--mapping")
.type(String.class)
.build());
for(QFieldMetaData field : table.getFields().values())
{
insertCommand.addOption(CommandLine.Model.OptionSpec.builder("--field-" + field.getName())
.type(getClassForField(field))
.build());
}
return insertCommand;
}
/*******************************************************************************
**
*******************************************************************************/
private CommandLine.Model.CommandSpec defineDeleteCommand(QTableMetaData table)
{
CommandLine.Model.CommandSpec deleteCommand = CommandLine.Model.CommandSpec.create();
deleteCommand.addOption(CommandLine.Model.OptionSpec.builder("--primaryKey")
.type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type?
.build());
return deleteCommand;
}
/*******************************************************************************
**
*******************************************************************************/
private CommandLine.Model.CommandSpec defineTableProcessesCommand(QTableMetaData table, List<QProcessMetaData> processes)
{
CommandLine.Model.CommandSpec processesCommand = CommandLine.Model.CommandSpec.create();
for(QProcessMetaData process : processes)
{
CommandLine.Model.CommandSpec processCommand = CommandLine.Model.CommandSpec.create();
processesCommand.addSubcommand(process.getName(), processCommand);
}
return (processesCommand);
}
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("checkstyle:Indentation")
private Class<?> getClassForField(QFieldMetaData field)
{
// @formatter:off // IJ can't do new-style switch correctly yet...
return switch(field.getType())
{
case STRING, TEXT, HTML, PASSWORD -> String.class;
case INTEGER -> Integer.class;
case DECIMAL -> BigDecimal.class;
case DATE -> LocalDate.class;
// case TIME -> LocalTime.class;
case DATE_TIME -> LocalDateTime.class;
};
// @formatter:on
}
}

View File

@ -9,9 +9,7 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@ -23,9 +21,11 @@ import com.kingsrook.qqq.backend.core.actions.MetaDataAction;
import com.kingsrook.qqq.backend.core.actions.QueryAction;
import com.kingsrook.qqq.backend.core.actions.RunFunctionAction;
import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction;
import com.kingsrook.qqq.backend.core.actions.UpdateAction;
import com.kingsrook.qqq.backend.core.adapters.CsvToQRecordAdapter;
import com.kingsrook.qqq.backend.core.adapters.JsonToQFieldMappingAdapter;
import com.kingsrook.qqq.backend.core.adapters.JsonToQRecordAdapter;
import com.kingsrook.qqq.backend.core.adapters.QInstanceAdapter;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteRequest;
@ -42,15 +42,15 @@ import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.query.QueryRequest;
import com.kingsrook.qqq.backend.core.model.actions.query.QueryResult;
import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateRequest;
import com.kingsrook.qqq.backend.core.model.actions.update.UpdateResult;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.modules.QAuthenticationModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.interfaces.QAuthenticationModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import org.apache.commons.io.FileUtils;
import picocli.CommandLine;
@ -81,17 +81,29 @@ public class QPicoCliImplementation
/*******************************************************************************
**
*******************************************************************************/
public static void main(String[] args)
public static void main(String[] args) throws IOException
{
QInstance qInstance = new QInstance();
// todo - parse args to look up metaData and prime instance
// todo - authentication
// qInstance.addBackend(QMetaDataProvider.getQBackend());
QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
int exitCode = qPicoCliImplementation.runCli("qapi", args);
System.exit(exitCode);
// parse args to look up metaData and prime instance
if(args.length > 0 && args[0].startsWith("--qInstanceJsonFile="))
{
String filePath = args[0].replaceFirst("--.*=", "");
String qInstanceJson = FileUtils.readFileToString(new File(filePath));
qInstance = new QInstanceAdapter().jsonToQInstanceIncludingBackends(qInstanceJson);
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance);
int exitCode = qPicoCliImplementation.runCli("qapi", subArgs);
System.exit(exitCode);
}
else
{
System.err.println("To run this main class directly, you must specify: --qInstanceJsonFile=path/to/qInstance.json");
System.exit(1);
}
}
@ -107,7 +119,8 @@ public class QPicoCliImplementation
/*******************************************************************************
**
** Driver method that uses System out & err streams.
*
*******************************************************************************/
public int runCli(String name, String[] args)
{
@ -117,6 +130,8 @@ public class QPicoCliImplementation
/*******************************************************************************
** Actual driver methods that takes streams as params.
*
** examples - todo, make docs complete!
** my-app-cli [--all] [--format=]
** my-app-cli $table meta-data [--format=]
@ -130,42 +145,7 @@ public class QPicoCliImplementation
*******************************************************************************/
public int runCli(String name, String[] args, PrintStream out, PrintStream err)
{
//////////////////////////////////
// define the top-level command //
//////////////////////////////////
CommandSpec topCommandSpec = CommandSpec.create();
topCommandSpec.name(name);
topCommandSpec.version(name + " v1.0"); // todo... uh?
topCommandSpec.mixinStandardHelpOptions(true); // usageHelp and versionHelp options
topCommandSpec.addOption(OptionSpec.builder("-m", "--meta-data")
.type(boolean.class)
.description("Output the meta-data for this CLI")
.build());
/////////////////////////////////////
// add each table as a sub-command //
/////////////////////////////////////
qInstance.getTables().keySet().stream().sorted().forEach(tableName ->
{
QTableMetaData table = qInstance.getTable(tableName);
CommandSpec tableCommand = CommandSpec.create();
topCommandSpec.addSubcommand(table.getName(), tableCommand);
///////////////////////////////////////////////////
// add table-specific sub-commands for the table //
///////////////////////////////////////////////////
tableCommand.addSubcommand("meta-data", defineMetaDataCommand(table));
tableCommand.addSubcommand("query", defineQueryCommand(table));
tableCommand.addSubcommand("insert", defineInsertCommand(table));
tableCommand.addSubcommand("delete", defineDeleteCommand(table));
List<QProcessMetaData> processes = qInstance.getProcessesForTable(tableName);
if(CollectionUtils.nullSafeHasContents(processes))
{
tableCommand.addSubcommand("process", defineTableProcessesCommand(table, processes));
}
});
CommandSpec topCommandSpec = new QCommandBuilder(qInstance).buildCommandSpec(name);
CommandLine commandLine = new CommandLine(topCommandSpec);
commandLine.setOut(new PrintWriter(out, true));
@ -215,6 +195,7 @@ public class QPicoCliImplementation
///////////////////////////////////////////
// handle exceptions from business logic //
///////////////////////////////////////////
ex.printStackTrace();
commandLine.getErr().println("Error: " + ex.getMessage());
return (commandLine.getCommandSpec().exitCodeOnExecutionException());
}
@ -238,127 +219,6 @@ public class QPicoCliImplementation
/*******************************************************************************
**
*******************************************************************************/
private CommandSpec defineMetaDataCommand(QTableMetaData table)
{
return CommandSpec.create();
}
/*******************************************************************************
**
*******************************************************************************/
private CommandSpec defineQueryCommand(QTableMetaData table)
{
CommandSpec queryCommand = CommandSpec.create();
queryCommand.addOption(OptionSpec.builder("-l", "--limit")
.type(int.class)
.build());
queryCommand.addOption(OptionSpec.builder("-s", "--skip")
.type(int.class)
.build());
queryCommand.addOption(OptionSpec.builder("-c", "--criteria")
.type(String[].class)
.build());
// todo - add the fields as explicit params?
return queryCommand;
}
/*******************************************************************************
**
*******************************************************************************/
private CommandSpec defineInsertCommand(QTableMetaData table)
{
CommandSpec insertCommand = CommandSpec.create();
insertCommand.addOption(OptionSpec.builder("--jsonBody")
.type(String.class)
.build());
insertCommand.addOption(OptionSpec.builder("--jsonFile")
.type(String.class)
.build());
insertCommand.addOption(OptionSpec.builder("--csvFile")
.type(String.class)
.build());
insertCommand.addOption(OptionSpec.builder("--mapping")
.type(String.class)
.build());
for(QFieldMetaData field : table.getFields().values())
{
insertCommand.addOption(OptionSpec.builder("--field-" + field.getName())
.type(getClassForField(field))
.build());
}
return insertCommand;
}
/*******************************************************************************
**
*******************************************************************************/
private CommandSpec defineDeleteCommand(QTableMetaData table)
{
CommandSpec deleteCommand = CommandSpec.create();
deleteCommand.addOption(OptionSpec.builder("--primaryKey")
.type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type?
.build());
return deleteCommand;
}
/*******************************************************************************
**
*******************************************************************************/
private CommandSpec defineTableProcessesCommand(QTableMetaData table, List<QProcessMetaData> processes)
{
CommandSpec processesCommand = CommandSpec.create();
for(QProcessMetaData process : processes)
{
CommandSpec processCommand = CommandSpec.create();
processesCommand.addSubcommand(process.getName(), processCommand);
}
return (processesCommand);
}
/*******************************************************************************
**
*******************************************************************************/
private Class<?> getClassForField(QFieldMetaData field)
{
// @formatter:off // IJ can't do new-style switch correctly yet...
return switch(field.getType())
{
case STRING, TEXT, HTML, PASSWORD -> String.class;
case INTEGER -> Integer.class;
case DECIMAL -> BigDecimal.class;
case DATE -> LocalDate.class;
// case TIME -> LocalTime.class;
case DATE_TIME -> LocalDateTime.class;
};
// @formatter:on
}
/*******************************************************************************
**
*******************************************************************************/
@ -403,6 +263,10 @@ public class QPicoCliImplementation
{
return runTableInsert(commandLine, tableName, subParseResult);
}
case "update":
{
return runTableUpdate(commandLine, tableName, subParseResult);
}
case "delete":
{
return runTableDelete(commandLine, tableName, subParseResult);
@ -602,6 +466,57 @@ public class QPicoCliImplementation
/*******************************************************************************
**
*******************************************************************************/
private int runTableUpdate(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException
{
UpdateRequest updateRequest = new UpdateRequest(qInstance);
updateRequest.setSession(session);
updateRequest.setTableName(tableName);
QTableMetaData table = qInstance.getTable(tableName);
List<QRecord> recordList = new ArrayList<>();
boolean anyFields = false;
String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", "");
Serializable[] primaryKeyValues = primaryKeyOption.split(",");
for(Serializable primaryKeyValue : primaryKeyValues)
{
QRecord record = new QRecord();
recordList.add(record);
record.setValue(table.getPrimaryKeyField(), primaryKeyValue);
for(OptionSpec matchedOption : subParseResult.matchedOptions())
{
if(matchedOption.longestName().startsWith("--field-"))
{
anyFields = true;
String fieldName = matchedOption.longestName().substring(8);
record.setValue(fieldName, matchedOption.getValue());
}
}
}
if(!anyFields || recordList.isEmpty())
{
CommandLine subCommandLine = commandLine.getSubcommands().get("update");
subCommandLine.usage(commandLine.getOut());
return commandLine.getCommandSpec().exitCodeOnUsageHelp();
}
updateRequest.setRecords(recordList);
UpdateAction updateAction = new UpdateAction();
UpdateResult updateResult = updateAction.execute(updateRequest);
commandLine.getOut().println(JsonUtils.toPrettyJson(updateResult));
return commandLine.getCommandSpec().exitCodeOnSuccess();
}
/*******************************************************************************
**
*******************************************************************************/
@ -615,7 +530,7 @@ public class QPicoCliImplementation
// get the pKeys that the user specified //
/////////////////////////////////////////////
String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", "");
String[] primaryKeyValues = primaryKeyOption.split(",");
Serializable[] primaryKeyValues = primaryKeyOption.split(",");
deleteRequest.setPrimaryKeys(Arrays.asList(primaryKeyValues));
DeleteAction deleteAction = new DeleteAction();

View File

@ -198,10 +198,11 @@ class QPicoCliImplementationTest
TestOutput testOutput = testCli("person", "query", "--skip=1", "--limit=2", "--criteria", "id NOT_EQUALS 3");
JSONObject queryResult = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(queryResult);
assertEquals(2, queryResult.getJSONArray("records").length());
JSONArray records = queryResult.getJSONArray("records");
assertEquals(2, records.length());
// query for id != 3, and skipping 1, expect to get back rows 2 & 4
assertEquals(2, queryResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey"));
assertEquals(4, queryResult.getJSONArray("records").getJSONObject(1).getInt("primaryKey"));
assertEquals(2, records.getJSONObject(0).getJSONObject("values").getInt("id"));
assertEquals(4, records.getJSONObject(1).getJSONObject("values").getInt("id"));
}
@ -232,7 +233,7 @@ class QPicoCliImplementationTest
JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(insertResult);
assertEquals(1, insertResult.getJSONArray("records").length());
assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey"));
assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id"));
}
@ -256,7 +257,7 @@ class QPicoCliImplementationTest
JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(insertResult);
assertEquals(1, insertResult.getJSONArray("records").length());
assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey"));
assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id"));
assertEquals("Chester", insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getString("firstName"));
assertEquals("Cheese", insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getString("lastName"));
}
@ -287,8 +288,8 @@ class QPicoCliImplementationTest
assertNotNull(insertResult);
JSONArray records = insertResult.getJSONArray("records");
assertEquals(2, records.length());
assertEquals(6, records.getJSONObject(0).getInt("primaryKey"));
assertEquals(7, records.getJSONObject(1).getInt("primaryKey"));
assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id"));
assertEquals(7, insertResult.getJSONArray("records").getJSONObject(1).getJSONObject("values").getInt("id"));
assertEquals("Charlie", records.getJSONObject(0).getJSONObject("values").getString("firstName"));
assertEquals("Bear", records.getJSONObject(0).getJSONObject("values").getString("lastName"));
assertEquals("Coco", records.getJSONObject(1).getJSONObject("values").getString("firstName"));
@ -323,8 +324,8 @@ class QPicoCliImplementationTest
assertNotNull(insertResult);
JSONArray records = insertResult.getJSONArray("records");
assertEquals(2, records.length());
assertEquals(6, records.getJSONObject(0).getInt("primaryKey"));
assertEquals(7, records.getJSONObject(1).getInt("primaryKey"));
assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id"));
assertEquals(7, insertResult.getJSONArray("records").getJSONObject(1).getJSONObject("values").getInt("id"));
assertEquals("Louis", records.getJSONObject(0).getJSONObject("values").getString("firstName"));
assertEquals("Willikers", records.getJSONObject(0).getJSONObject("values").getString("lastName"));
assertEquals("Nestle", records.getJSONObject(1).getJSONObject("values").getString("firstName"));
@ -333,6 +334,56 @@ class QPicoCliImplementationTest
/*******************************************************************************
** test running an update w/o specifying any fields, prints usage
**
*******************************************************************************/
@Test
public void test_tableUpdateNoFieldsPrintsUsage()
{
TestOutput testOutput = testCli("person", "update");
assertTestOutputContains(testOutput, "Usage: " + CLI_NAME + " person update");
}
/*******************************************************************************
** test running an update w/ fields as arguments
**
*******************************************************************************/
@Test
public void test_tableUpdateFieldArguments() throws Exception
{
assertRowValueById("person", "first_name", "Garret", 5);
TestOutput testOutput = testCli("person", "update",
"--primaryKey=5",
"--field-firstName=Lucy",
"--field-lastName=Lu");
JSONObject updateResult = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(updateResult);
assertEquals(1, updateResult.getJSONArray("records").length());
assertEquals(5, updateResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id"));
assertRowValueById("person", "first_name", "Lucy", 5);
}
private void assertRowValueById(String tableName, String columnName, String value, Integer id) throws Exception
{
TestUtils.runTestSql("SELECT " + columnName + " FROM " + tableName + " WHERE id=" + id, (rs -> {
if(rs.next())
{
assertEquals(value, rs.getString(1));
}
else
{
fail("Row not found");
}
}));
}
/*******************************************************************************
** test running a delete against a table
**
@ -343,9 +394,10 @@ class QPicoCliImplementationTest
TestOutput testOutput = testCli("person", "delete", "--primaryKey", "2,4");
JSONObject deleteResult = JsonUtils.toJSONObject(testOutput.getOutput());
assertNotNull(deleteResult);
assertEquals(2, deleteResult.getJSONArray("records").length());
assertEquals(2, deleteResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey"));
assertEquals(4, deleteResult.getJSONArray("records").getJSONObject(1).getInt("primaryKey"));
JSONArray records = deleteResult.getJSONArray("records");
assertEquals(2, records.length());
assertEquals(2, records.getJSONObject(0).getJSONObject("values").getInt("id"));
assertEquals(4, records.getJSONObject(1).getJSONObject("values").getInt("id"));
TestUtils.runTestSql("SELECT id FROM person", (rs -> {
int rowsFound = 0;
while(rs.next())

View File

@ -9,13 +9,13 @@ import java.io.InputStream;
import java.sql.Connection;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData;

View File

@ -0,0 +1,156 @@
{
"authentication": {
"name": "mock",
"type": "mock",
"values": null
},
"tables": {
"person": {
"name": "person",
"label": "Person",
"backendName": "default",
"primaryKeyField": "id",
"fields": {
"id": {
"name": "id",
"label": null,
"backendName": null,
"type": "INTEGER",
"possibleValueSourceName": null
},
"createDate": {
"name": "createDate",
"label": null,
"backendName": null,
"type": "DATE_TIME",
"possibleValueSourceName": null
},
"modifyDate": {
"name": "modifyDate",
"label": null,
"backendName": null,
"type": "DATE_TIME",
"possibleValueSourceName": null
},
"firstName": {
"name": "firstName",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
},
"lastName": {
"name": "lastName",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
},
"birthDate": {
"name": "birthDate",
"label": null,
"backendName": null,
"type": "DATE",
"possibleValueSourceName": null
},
"email": {
"name": "email",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
},
"homeState": {
"name": "homeState",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": "state"
}
}
}
},
"possibleValueSources": {
"state": {
"name": "state",
"type": "ENUM",
"enumValues": [
"IL",
"MO"
]
}
},
"processes": {
"greet": {
"name": "greet",
"tableName": "person",
"functionList": [
{
"name": "prepare",
"label": null,
"inputMetaData": {
"recordListMetaData": {
"tableName": "person",
"fields": null
},
"fieldList": [
{
"name": "greetingPrefix",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
},
{
"name": "greetingSuffix",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
}
]
},
"outputMetaData": {
"recordListMetaData": {
"tableName": "person",
"fields": {
"fullGreeting": {
"name": "fullGreeting",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
}
}
},
"fieldList": [
{
"name": "outputMessage",
"label": null,
"backendName": null,
"type": "STRING",
"possibleValueSourceName": null
}
]
},
"code": {
"name": "com.kingsrook.qqq.backend.core.interfaces.mock.MockFunctionBody",
"codeType": "JAVA",
"codeUsage": "FUNCTION"
},
"outputView": {
"messageField": "outputMessage",
"recordListView": {
"fieldNames": [
"id",
"firstName",
"lastName",
"fullGreeting"
]
}
}
}
]
}
}
}

View File

@ -0,0 +1,163 @@
{
"tables": {
"person": {
"primaryKeyField": "id",
"name": "person",
"backendName": "default",
"label": "Person",
"fields": {
"firstName": {
"name": "firstName",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
"lastName": {
"name": "lastName",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
"modifyDate": {
"name": "modifyDate",
"backendName": null,
"label": null,
"type": "DATE_TIME",
"possibleValueSourceName": null
},
"homeState": {
"name": "homeState",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": "state"
},
"id": {
"name": "id",
"backendName": null,
"label": null,
"type": "INTEGER",
"possibleValueSourceName": null
},
"birthDate": {
"name": "birthDate",
"backendName": null,
"label": null,
"type": "DATE",
"possibleValueSourceName": null
},
"email": {
"name": "email",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
"createDate": {
"name": "createDate",
"backendName": null,
"label": null,
"type": "DATE_TIME",
"possibleValueSourceName": null
}
}
}
},
"processes": {
"greet": {
"functionList": [
{
"code": {
"codeUsage": "FUNCTION",
"codeType": "JAVA",
"name": "com.kingsrook.qqq.backend.core.interfaces.mock.MockFunctionBody"
},
"inputMetaData": {
"recordListMetaData": {
"fields": null,
"tableName": "person"
},
"fieldList": [
{
"name": "greetingPrefix",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
{
"name": "greetingSuffix",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
}
]
},
"outputMetaData": {
"recordListMetaData": {
"fields": {
"fullGreeting": {
"name": "fullGreeting",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
}
},
"tableName": "person"
},
"fieldList": [
{
"name": "outputMessage",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
}
]
},
"outputView": {
"messageField": "outputMessage",
"recordListView": {
"fieldNames": [
"id",
"firstName",
"lastName",
"fullGreeting"
]
}
},
"name": "prepare",
"label": null
}
],
"name": "greet",
"tableName": "person"
}
},
"possibleValueSources": {
"state": {
"name": "state",
"type": "ENUM",
"enumValues": [
"IL",
"MO"
]
}
},
"backends": {
"default": {
"values": null,
"name": "default",
"type": "mock"
}
},
"authentication": {
"values": null,
"name": "mock",
"type": "mock"
}
}

View File

@ -0,0 +1,70 @@
{
"tables": {
"series": {
"primaryKeyField": "id",
"name": "series",
"backendName": "wherenooneMysql",
"label": "Person",
"fields": {
"name": {
"name": "name",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
"abbreviation": {
"name": "abbreviation",
"backendName": null,
"label": null,
"type": "STRING",
"possibleValueSourceName": null
},
"id": {
"name": "id",
"backendName": null,
"label": null,
"type": "INTEGER",
"possibleValueSourceName": null
},
"createDate": {
"name": "createDate",
"backendName": "create_date",
"label": "Create Date",
"type": "DATE_TIME",
"possibleValueSourceName": null
},
"modifyDate": {
"name": "modifyDate",
"backendName": "modify_date",
"label": "Modify Date",
"type": "DATE_TIME",
"possibleValueSourceName": null
}
}
}
},
"processes": {
},
"possibleValueSources": {
},
"backends": {
"wherenooneMysql": {
"values": {
"vendor": "mysql",
"hostName": "localhost",
"port": "3306",
"databaseName": "wherenoone",
"username": "root",
"password": "password"
},
"name": "wherenooneMysql",
"type": "rdbms"
}
},
"authentication": {
"values": null,
"name": "mock",
"type": "mock"
}
}