mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Checkpoint: Added Update (edit) action; Load definition from JSON
This commit is contained in:
14
pom.xml
14
pom.xml
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
|
156
src/test/resources/personQInstance.json
Normal file
156
src/test/resources/personQInstance.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
163
src/test/resources/personQInstanceIncludingBackend.json
Normal file
163
src/test/resources/personQInstanceIncludingBackend.json
Normal 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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user