From 8f5b16c20378e052357407114e56f54ee7665997 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff <93011140+darin-kelkhoff@users.noreply.github.com> Date: Mon, 8 Nov 2021 21:19:49 -0600 Subject: [PATCH 01/49] Initial commit --- .gitignore | 23 +++++++++++++++++++++++ README.md | 1 + 2 files changed, 24 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a1c2a238 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* diff --git a/README.md b/README.md new file mode 100644 index 00000000..0703c3eb --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# qqq-middleware-picocli \ No newline at end of file From 6432b7bf648aa60cba0db3edef1eda4c7b05a18d Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 8 Nov 2021 21:20:34 -0600 Subject: [PATCH 02/49] Initial code checkin --- .gitignore | 7 + checkstyle.xml | 242 +++++++++ pom.xml | 129 +++++ .../picocli/QPicoCliImplementation.java | 489 ++++++++++++++++++ .../picocli/QPicoCliImplementationTest.java | 308 +++++++++++ .../qqq/frontend/picocli/TestUtils.java | 94 ++++ src/test/resources/prime-test-database.sql | 18 + 7 files changed, 1287 insertions(+) create mode 100644 checkstyle.xml create mode 100644 pom.xml create mode 100644 src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java create mode 100644 src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java create mode 100644 src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java create mode 100644 src/test/resources/prime-test-database.sql diff --git a/.gitignore b/.gitignore index a1c2a238..39736a21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +target/ +*.iml + + +############################################# +## Original contents from github template: ## +############################################# # Compiled class file *.class diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000..fadf2cda --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..a3e1e473 --- /dev/null +++ b/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + + com.kingsrook.qqq + qqq-middleware-picocli + 0.0-SNAPSHOT + + + + + + + UTF-8 + UTF-8 + 17 + 17 + true + true + + + + + + com.kingsrook.qqq + qqq-backend-core + 0.0-SNAPSHOT + + + com.kingsrook.qqq + qqq-backend-module-rdbms + 0.0-SNAPSHOT + test + + + + + info.picocli + picocli + 4.6.1 + + + com.h2database + h2 + 1.4.197 + test + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + + org.apache.logging.log4j + log4j-api + 2.14.1 + + + org.apache.logging.log4j + log4j-core + 2.14.1 + + + org.junit.jupiter + junit-jupiter-engine + 5.8.1 + test + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + -Xlint:unchecked + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + + com.puppycrawl.tools + checkstyle + 9.0 + + + + + validate + validate + + checkstyle.xml + + UTF-8 + true + false + true + warning + **/target/generated-sources/*.* + + + + check + + + + + + + + diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java new file mode 100644 index 00000000..6017c827 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -0,0 +1,489 @@ +package com.kingsrook.qqq.frontend.picocli; + + +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.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import com.kingsrook.qqq.backend.core.actions.InsertAction; +import com.kingsrook.qqq.backend.core.actions.MetaDataAction; +import com.kingsrook.qqq.backend.core.actions.QueryAction; +import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction; +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.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.AbstractQFieldMapping; +import com.kingsrook.qqq.backend.core.model.actions.InsertRequest; +import com.kingsrook.qqq.backend.core.model.actions.InsertResult; +import com.kingsrook.qqq.backend.core.model.actions.MetaDataRequest; +import com.kingsrook.qqq.backend.core.model.actions.MetaDataResult; +import com.kingsrook.qqq.backend.core.model.actions.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.actions.QFilterCriteria; +import com.kingsrook.qqq.backend.core.model.actions.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.actions.QueryRequest; +import com.kingsrook.qqq.backend.core.model.actions.QueryResult; +import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataRequest; +import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataResult; +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.utils.JsonUtils; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Model.OptionSpec; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.ParseResult; +import picocli.CommandLine.UnmatchedArgumentException; + + +/******************************************************************************* + ** Note: Please do not use System.out or .err here -- rather, use the CommandLine + ** object's out & err members - so the unit test can see the output! + * + *******************************************************************************/ +public class QPicoCliImplementation +{ + private static final Logger LOG = LogManager.getLogger(QPicoCliImplementation.class); + + public static final int DEFAULT_LIMIT = 20; + + private static QInstance qInstance; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static void main(String[] args) + { + 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); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QPicoCliImplementation(QInstance qInstance) + { + QPicoCliImplementation.qInstance = qInstance; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public int runCli(String name, String[] args) + { + return (runCli(name, args, System.out, System.err)); + } + + + + /******************************************************************************* + ** examples - todo, make docs complete! + ** my-app-cli [--all] [--format=] + ** my-app-cli $table meta-data [--format=] + ** my-app-cli $table query [--filterId=]|[--filter=]|[--criteria=...] + ** my-app-cli $table get (--primaryKey=|--$uc=...) + ** my-app-cli $table delete (--primaryKey=|--$uc=...) + ** my-app-cli $table insert (--body=|--$field=...) + ** my-app-cli $table update (--primaryKey=|--$uc=...) (--body=|--$field=...) + ** + *******************************************************************************/ + 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)); + }); + + CommandLine commandLine = new CommandLine(topCommandSpec); + commandLine.setOut(new PrintWriter(out, true)); + commandLine.setErr(new PrintWriter(err, true)); + + try + { + ParseResult parseResult = commandLine.parseArgs(args); + + /////////////////////////////////////////// + // Did user request usage help (--help)? // + /////////////////////////////////////////// + if(commandLine.isUsageHelpRequested()) + { + commandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + //////////////////////////////////////////////// + // Did user request version help (--version)? // + //////////////////////////////////////////////// + else if(commandLine.isVersionHelpRequested()) + { + commandLine.printVersionHelp(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnVersionHelp(); + } + + /////////////////////////// + // else, run the command // + /////////////////////////// + return run(commandLine, parseResult); + } + catch(ParameterException ex) + { + ////////////////////////////////////////////////// + // handle command-line/param parsing exceptions // + ////////////////////////////////////////////////// + commandLine.getErr().println(ex.getMessage()); + UnmatchedArgumentException.printSuggestions(ex, commandLine.getErr()); + ex.getCommandLine().usage(commandLine.getErr()); + return commandLine.getCommandSpec().exitCodeOnInvalidInput(); + } + catch(Exception ex) + { + /////////////////////////////////////////// + // handle exceptions from business logic // + /////////////////////////////////////////// + commandLine.getErr().println("Error: " + ex.getMessage()); + return (commandLine.getCommandSpec().exitCodeOnExecutionException()); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + 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; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @SuppressWarnings("checkstyle:Indentation") + 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( + 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; + default -> throw new IllegalStateException("Unsupported field type: " + field.getType()); + } + ) + .build()); + } + return insertCommand; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int run(CommandLine commandLine, ParseResult parseResult) throws QException + { + if(!parseResult.hasSubcommand()) + { + return runTopLevelCommand(commandLine, parseResult); + } + else + { + String subCommandName = parseResult.subcommand().commandSpec().name(); + CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName); + return runTableLevelCommand(subCommandLine, parseResult.subcommand()); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableLevelCommand(CommandLine commandLine, ParseResult tableParseResult) throws QException + { + String tableName = tableParseResult.commandSpec().name(); + + if(tableParseResult.hasSubcommand()) + { + ParseResult subParseResult = tableParseResult.subcommand(); + switch(subParseResult.commandSpec().name()) + { + case "meta-data": + { + return runTableMetaData(commandLine, tableName, subParseResult); + } + case "query": + { + return runTableQuery(commandLine, tableName, subParseResult); + } + case "insert": + { + return runTableInsert(commandLine, tableName, subParseResult); + } + default: + { + commandLine.getErr().println("Unknown command: " + subParseResult.commandSpec().name()); + commandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + } + } + else + { + commandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableMetaData(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException + { + TableMetaDataRequest tableMetaDataRequest = new TableMetaDataRequest(qInstance); + tableMetaDataRequest.setTableName(tableName); + TableMetaDataAction tableMetaDataAction = new TableMetaDataAction(); + TableMetaDataResult tableMetaDataResult = tableMetaDataAction.execute(tableMetaDataRequest); + commandLine.getOut().println(JsonUtils.toPrettyJson(tableMetaDataResult)); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableQuery(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException + { + QueryRequest queryRequest = new QueryRequest(qInstance); + queryRequest.setTableName(tableName); + queryRequest.setSkip(subParseResult.matchedOptionValue("skip", null)); + queryRequest.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_LIMIT)); + + QQueryFilter filter = new QQueryFilter(); + queryRequest.setFilter(filter); + + String[] criteria = subParseResult.matchedOptionValue("criteria", new String[] {}); + for(String criterion : criteria) + { + // todo - parse! + String[] parts = criterion.split(" "); + QFilterCriteria qQueryCriteria = new QFilterCriteria(); + qQueryCriteria.setFieldName(parts[0]); + qQueryCriteria.setOperator(QCriteriaOperator.valueOf(parts[1])); + qQueryCriteria.setValues(List.of(parts[2])); + filter.addCriteria(qQueryCriteria); + } + + QueryAction queryAction = new QueryAction(); + QueryResult queryResult = queryAction.execute(queryRequest); + commandLine.getOut().println(JsonUtils.toPrettyJson(queryResult)); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableInsert(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException + { + InsertRequest insertRequest = new InsertRequest(qInstance); + insertRequest.setTableName(tableName); + QTableMetaData table = qInstance.getTable(tableName); + + AbstractQFieldMapping mapping = null; + + if(subParseResult.hasMatchedOption("--mapping")) + { + String json = subParseResult.matchedOptionValue("--mapping", ""); + mapping = new JsonToQFieldMappingAdapter().buildMappingFromJson(json); + } + + ///////////////////////////////////////////// + // get the records that the user specified // + ///////////////////////////////////////////// + List recordList = null; + if(subParseResult.hasMatchedOption("--jsonBody")) + { + String json = subParseResult.matchedOptionValue("--jsonBody", ""); + recordList = new JsonToQRecordAdapter().buildRecordsFromJson(json, table, mapping); + } + else if(subParseResult.hasMatchedOption("--jsonFile")) + { + try + { + String path = subParseResult.matchedOptionValue("--jsonFile", ""); + String json = FileUtils.readFileToString(new File(path)); + recordList = new JsonToQRecordAdapter().buildRecordsFromJson(json, table, mapping); + } + catch(IOException e) + { + throw (new QException("Error building records from file:" + e.getMessage(), e)); + } + } + else if(subParseResult.hasMatchedOption("--csvFile")) + { + try + { + String path = subParseResult.matchedOptionValue("--csvFile", ""); + String csv = FileUtils.readFileToString(new File(path)); + recordList = new CsvToQRecordAdapter().buildRecordsFromCsv(csv, table, mapping); + } + catch(IOException e) + { + throw (new QException("Error building records from file:" + e.getMessage(), e)); + } + } + else + { + QRecord record = new QRecord(); + recordList = new ArrayList<>(); + recordList.add(record); + + boolean anyFields = false; + 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) + { + CommandLine subCommandLine = commandLine.getSubcommands().get("insert"); + subCommandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + } + + insertRequest.setRecords(recordList); + + InsertAction insertAction = new InsertAction(); + InsertResult insertResult = insertAction.execute(insertRequest); + commandLine.getOut().println(JsonUtils.toPrettyJson(insertResult)); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTopLevelCommand(CommandLine commandLine, ParseResult parseResult) throws QException + { + if(parseResult.hasMatchedOption("--meta-data")) + { + MetaDataRequest metaDataRequest = new MetaDataRequest(qInstance); + MetaDataAction metaDataAction = new MetaDataAction(); + MetaDataResult metaDataResult = metaDataAction.execute(metaDataRequest); + commandLine.getOut().println(JsonUtils.toPrettyJson(metaDataResult)); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + + commandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } +} diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java new file mode 100644 index 00000000..5d132429 --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -0,0 +1,308 @@ +package com.kingsrook.qqq.frontend.picocli; + + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/******************************************************************************* + ** + *******************************************************************************/ +class QPicoCliImplementationTest +{ + private static final boolean VERBOSE = true; + private static final String CLI_NAME = "cli-unit-test"; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @BeforeEach + public void beforeEach() throws Exception + { + TestUtils.primeTestDatabase(); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_noArgs() + { + TestOutput testOutput = testCli(); + assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_help() + { + TestOutput testOutput = testCli("--help"); + assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME)); + assertTrue(testOutput.getOutput().matches("(?s).*Commands:.*person.*")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_version() + { + TestOutput testOutput = testCli("--version"); + assertTrue(testOutput.getOutput().contains(CLI_NAME + " v1.0")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_badOption() + { + String badOption = "--asdf"; + TestOutput testOutput = testCli(badOption); + assertTrue(testOutput.getError().contains("Unknown option: '" + badOption + "'")); + assertTrue(testOutput.getError().contains("Usage: " + CLI_NAME)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_metaData() + { + TestOutput testOutput = testCli("--meta-data"); + JSONObject metaData = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(metaData); + assertEquals(1, metaData.keySet().size(), "Number of top-level keys"); + assertTrue(metaData.has("tables")); + JSONObject tables = metaData.getJSONObject("tables"); + JSONObject personTable = tables.getJSONObject("person"); + assertEquals("person", personTable.getString("name")); + assertEquals("Person", personTable.getString("label")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_table() + { + TestOutput testOutput = testCli("person"); + assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME + " person [COMMAND]")); + assertTrue(testOutput.getOutput().matches("(?s).*Commands:.*query.*")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_tableMetaData() + { + TestOutput testOutput = testCli("person", "meta-data"); + JSONObject metaData = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(metaData); + assertEquals(1, metaData.keySet().size(), "Number of top-level keys"); + JSONObject table = metaData.getJSONObject("table"); + assertEquals(4, table.keySet().size(), "Number of mid-level keys"); + assertEquals("person", table.getString("name")); + assertEquals("Person", table.getString("label")); + assertEquals("id", table.getString("primaryKeyField")); + JSONObject fields = table.getJSONObject("fields"); + JSONObject field0 = fields.getJSONObject("id"); + assertEquals("id", field0.getString("name")); + assertEquals("INTEGER", field0.getString("type")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_tableQuery() + { + 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()); + // 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")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private TestOutput testCli(String... args) + { + QInstance qInstance = TestUtils.defineInstance(); + QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); + + if(VERBOSE) + { + System.out.println("> " + CLI_NAME + (args == null ? "" : " " + StringUtils.join(" ", Arrays.stream(args).toList()))); + } + + qPicoCliImplementation.runCli(CLI_NAME, args, new PrintStream(outputStream, true), new PrintStream(errorStream, true)); + + String output = outputStream.toString(StandardCharsets.UTF_8); + String error = errorStream.toString(StandardCharsets.UTF_8); + + if(VERBOSE) + { + System.out.println(output); + System.err.println(error); + } + + TestOutput testOutput = new TestOutput(output, error); + return (testOutput); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static class TestOutput + { + private String output; + private String[] outputLines; + private String error; + private String[] errorLines; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public TestOutput(String output, String error) + { + this.output = output; + this.error = error; + + this.outputLines = output.split("\n"); + this.errorLines = error.split("\n"); + } + + + + /******************************************************************************* + ** Getter for output + ** + *******************************************************************************/ + public String getOutput() + { + return output; + } + + + + /******************************************************************************* + ** Setter for output + ** + *******************************************************************************/ + public void setOutput(String output) + { + this.output = output; + } + + + + /******************************************************************************* + ** Getter for outputLines + ** + *******************************************************************************/ + public String[] getOutputLines() + { + return outputLines; + } + + + + /******************************************************************************* + ** Setter for outputLines + ** + *******************************************************************************/ + public void setOutputLines(String[] outputLines) + { + this.outputLines = outputLines; + } + + + + /******************************************************************************* + ** Getter for error + ** + *******************************************************************************/ + public String getError() + { + return error; + } + + + + /******************************************************************************* + ** Setter for error + ** + *******************************************************************************/ + public void setError(String error) + { + this.error = error; + } + + + + /******************************************************************************* + ** Getter for errorLines + ** + *******************************************************************************/ + public String[] getErrorLines() + { + return errorLines; + } + + + + /******************************************************************************* + ** Setter for errorLines + ** + *******************************************************************************/ + public void setErrorLines(String[] errorLines) + { + this.errorLines = errorLines; + } + } +} \ No newline at end of file diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java new file mode 100644 index 00000000..622d435a --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -0,0 +1,94 @@ +package com.kingsrook.qqq.frontend.picocli; + + +import java.io.InputStream; +import java.sql.Connection; +import java.util.List; +import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; +import com.kingsrook.qqq.backend.module.rdbms.RDBSMBackendMetaData; +import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; +import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; +import org.apache.commons.io.IOUtils; +import static junit.framework.Assert.assertNotNull; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class TestUtils +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @SuppressWarnings("unchecked") + public static void primeTestDatabase() throws Exception + { + ConnectionManager connectionManager = new ConnectionManager(); + Connection connection = connectionManager.getConnection(new RDBSMBackendMetaData(TestUtils.defineBackend())); + InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql"); + assertNotNull(primeTestDatabaseSqlStream); + List lines = (List) IOUtils.readLines(primeTestDatabaseSqlStream); + String joinedSQL = String.join("\n", lines); + for(String sql : joinedSQL.split(";")) + { + QueryManager.executeUpdate(connection, sql); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QInstance defineInstance() + { + QInstance qInstance = new QInstance(); + qInstance.addBackend(defineBackend()); + qInstance.addTable(defineTablePerson()); + return (qInstance); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QBackendMetaData defineBackend() + { + return new QBackendMetaData() + .withName("default") + .withType("rdbms") + .withValue("vendor", "h2") + .withValue("hostName", "mem") + .withValue("databaseName", "test_database") + .withValue("username", "sa") + .withValue("password", ""); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public static QTableMetaData defineTablePerson() + { + return new QTableMetaData() + .withName("person") + .withLabel("Person") + .withBackendName(defineBackend().getName()) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("create_date")) + .withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("modify_date")) + .withField(new QFieldMetaData("firstName", QFieldType.STRING).withBackendName("first_name")) + .withField(new QFieldMetaData("lastName", QFieldType.STRING).withBackendName("last_name")) + .withField(new QFieldMetaData("birthDate", QFieldType.DATE).withBackendName("birth_date")) + .withField(new QFieldMetaData("email", QFieldType.STRING)); + } + +} diff --git a/src/test/resources/prime-test-database.sql b/src/test/resources/prime-test-database.sql new file mode 100644 index 00000000..e5adab34 --- /dev/null +++ b/src/test/resources/prime-test-database.sql @@ -0,0 +1,18 @@ +DROP TABLE IF EXISTS person; +CREATE TABLE person +( + id SERIAL, + create_date TIMESTAMP DEFAULT now(), + modify_date TIMESTAMP DEFAULT now(), + + first_name VARCHAR(80) NOT NULL, + last_name VARCHAR(80) NOT NULL, + birth_date DATE, + email VARCHAR(250) NOT NULL +); + +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com'); +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (2, 'James', 'Maes', '1980-05-15', 'jmaes@mmltholdings.com'); +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (3, 'Tim', 'Chamberlain', '1976-05-28', 'tchamberlain@mmltholdings.com'); +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (4, 'Tyler', 'Samples', '1990-01-01', 'tsamples@mmltholdings.com'); +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com'); From c51b55cfe411e4d19cba50ebb3416203af77b4e4 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff <93011140+darin-kelkhoff@users.noreply.github.com> Date: Mon, 8 Nov 2021 22:09:33 -0600 Subject: [PATCH 03/49] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0703c3eb..b53deeac 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ -# qqq-middleware-picocli \ No newline at end of file +# qqq-middleware-picocli + +This is a qqq middleware module, providing [picocli](https://picocli.info) access to the qqq-backend. + From 01142017a3602b42e753e9767782c9196133d352 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Sun, 28 Nov 2021 21:41:09 -0600 Subject: [PATCH 04/49] Remove TIME --- .../kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 6017c827..08622430 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -8,7 +8,6 @@ import java.io.PrintWriter; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.backend.core.actions.InsertAction; @@ -262,7 +261,7 @@ public class QPicoCliImplementation case INTEGER -> Integer.class; case DECIMAL -> BigDecimal.class; case DATE -> LocalDate.class; - case TIME -> LocalTime.class; + // case TIME -> LocalTime.class; case DATE_TIME -> LocalDateTime.class; default -> throw new IllegalStateException("Unsupported field type: " + field.getType()); } From c0ad05d1195cf0c77b6fd17552482fbe7da400b4 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 30 Nov 2021 18:10:22 -0600 Subject: [PATCH 05/49] Implemented delete --- .../picocli/QPicoCliImplementation.java | 86 ++++++++++++++++--- .../picocli/QPicoCliImplementationTest.java | 25 ++++++ .../qqq/frontend/picocli/TestUtils.java | 12 +++ 3 files changed, 110 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 08622430..5dc585b9 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -5,11 +5,14 @@ import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; +import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import com.kingsrook.qqq.backend.core.actions.DeleteAction; import com.kingsrook.qqq.backend.core.actions.InsertAction; import com.kingsrook.qqq.backend.core.actions.MetaDataAction; import com.kingsrook.qqq.backend.core.actions.QueryAction; @@ -19,6 +22,8 @@ import com.kingsrook.qqq.backend.core.adapters.JsonToQFieldMappingAdapter; import com.kingsrook.qqq.backend.core.adapters.JsonToQRecordAdapter; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.AbstractQFieldMapping; +import com.kingsrook.qqq.backend.core.model.actions.DeleteRequest; +import com.kingsrook.qqq.backend.core.model.actions.DeleteResult; import com.kingsrook.qqq.backend.core.model.actions.InsertRequest; import com.kingsrook.qqq.backend.core.model.actions.InsertResult; import com.kingsrook.qqq.backend.core.model.actions.MetaDataRequest; @@ -140,6 +145,7 @@ public class QPicoCliImplementation tableCommand.addSubcommand("meta-data", defineMetaDataCommand(table)); tableCommand.addSubcommand("query", defineQueryCommand(table)); tableCommand.addSubcommand("insert", defineInsertCommand(table)); + tableCommand.addSubcommand("delete", defineDeleteCommand(table)); }); CommandLine commandLine = new CommandLine(topCommandSpec); @@ -230,7 +236,6 @@ public class QPicoCliImplementation /******************************************************************************* ** *******************************************************************************/ - @SuppressWarnings("checkstyle:Indentation") private CommandSpec defineInsertCommand(QTableMetaData table) { CommandSpec insertCommand = CommandSpec.create(); @@ -254,18 +259,7 @@ public class QPicoCliImplementation for(QFieldMetaData field : table.getFields().values()) { insertCommand.addOption(OptionSpec.builder("--field-" + field.getName()) - .type( - 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; - default -> throw new IllegalStateException("Unsupported field type: " + field.getType()); - } - ) + .type(getClassForField(field)) .build()); } return insertCommand; @@ -273,6 +267,43 @@ public class QPicoCliImplementation + /******************************************************************************* + ** + *******************************************************************************/ + private CommandSpec defineDeleteCommand(QTableMetaData table) + { + CommandSpec deleteCommand = CommandSpec.create(); + + QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField()); + 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; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @SuppressWarnings("checkstyle:Indentation") + private Class getClassForField(QFieldMetaData field) + { + 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; + default -> throw new IllegalStateException("Unsupported field type: " + field.getType()); + }; + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -316,6 +347,10 @@ public class QPicoCliImplementation { return runTableInsert(commandLine, tableName, subParseResult); } + case "delete": + { + return runTableDelete(commandLine, tableName, subParseResult); + } default: { commandLine.getErr().println("Unknown command: " + subParseResult.commandSpec().name()); @@ -467,6 +502,31 @@ public class QPicoCliImplementation } + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableDelete(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException + { + DeleteRequest deleteRequest = new DeleteRequest(qInstance); + deleteRequest.setTableName(tableName); + QTableMetaData table = qInstance.getTable(tableName); + + ///////////////////////////////////////////// + // get the pKeys that the user specified // + ///////////////////////////////////////////// + List primaryKeyList = null; + + String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); + String[] primaryKeyValues = primaryKeyOption.split(","); + deleteRequest.setPrimaryKeys(Arrays.asList(primaryKeyValues)); + + DeleteAction deleteAction = new DeleteAction(); + DeleteResult deleteResult = deleteAction.execute(deleteRequest); + commandLine.getOut().println(JsonUtils.toPrettyJson(deleteResult)); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + + /******************************************************************************* ** diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index 5d132429..3a8a2689 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -160,6 +160,31 @@ class QPicoCliImplementationTest + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void test_tableDelete() throws Exception + { + 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")); + TestUtils.runTestSql("SELECT id FROM person", (rs -> { + int rowsFound = 0; + while(rs.next()) + { + rowsFound++; + assertTrue(rs.getInt(1) == 1 || rs.getInt(1) == 3 || rs.getInt(1) == 5); + } + assertEquals(3, rowsFound); + })); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index 622d435a..b66b9073 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -42,6 +42,18 @@ public class TestUtils + /******************************************************************************* + ** + *******************************************************************************/ + public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception + { + ConnectionManager connectionManager = new ConnectionManager(); + Connection connection = connectionManager.getConnection(new RDBSMBackendMetaData(defineBackend())); + QueryManager.executeStatement(connection, sql, resultSetProcessor); + } + + + /******************************************************************************* ** *******************************************************************************/ From 90cd12e09f9dfcb6717dc080e3545374e8c8325c Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 1 Dec 2021 22:57:45 -0600 Subject: [PATCH 06/49] Adding comments and more tests --- .../picocli/QPicoCliImplementation.java | 37 ++-- .../picocli/QPicoCliImplementationTest.java | 160 +++++++++++++++++- .../qqq/frontend/picocli/TestUtils.java | 6 + 3 files changed, 181 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 5dc585b9..e1f2bee5 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -5,7 +5,6 @@ import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; -import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; @@ -41,8 +40,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; import com.kingsrook.qqq.backend.core.utils.JsonUtils; import org.apache.commons.io.FileUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.OptionSpec; @@ -52,14 +49,15 @@ import picocli.CommandLine.UnmatchedArgumentException; /******************************************************************************* + ** QQQ PicoCLI implementation. Given a QInstance, produces an entire CLI + ** for working with all tables in that instance. + ** ** Note: Please do not use System.out or .err here -- rather, use the CommandLine ** object's out & err members - so the unit test can see the output! - * + ** *******************************************************************************/ public class QPicoCliImplementation { - private static final Logger LOG = LogManager.getLogger(QPicoCliImplementation.class); - public static final int DEFAULT_LIMIT = 20; private static QInstance qInstance; @@ -274,7 +272,6 @@ public class QPicoCliImplementation { CommandSpec deleteCommand = CommandSpec.create(); - QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField()); deleteCommand.addOption(OptionSpec.builder("--primaryKey") .type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type? .build()); @@ -287,19 +284,19 @@ public class QPicoCliImplementation /******************************************************************************* ** *******************************************************************************/ - @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; - default -> throw new IllegalStateException("Unsupported field type: " + 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 } @@ -436,7 +433,7 @@ public class QPicoCliImplementation ///////////////////////////////////////////// // get the records that the user specified // ///////////////////////////////////////////// - List recordList = null; + List recordList; if(subParseResult.hasMatchedOption("--jsonBody")) { String json = subParseResult.matchedOptionValue("--jsonBody", ""); @@ -502,6 +499,7 @@ public class QPicoCliImplementation } + /******************************************************************************* ** *******************************************************************************/ @@ -509,13 +507,10 @@ public class QPicoCliImplementation { DeleteRequest deleteRequest = new DeleteRequest(qInstance); deleteRequest.setTableName(tableName); - QTableMetaData table = qInstance.getTable(tableName); ///////////////////////////////////////////// // get the pKeys that the user specified // ///////////////////////////////////////////// - List primaryKeyList = null; - String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); String[] primaryKeyValues = primaryKeyOption.split(","); deleteRequest.setPrimaryKeys(Arrays.asList(primaryKeyValues)); diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index 3a8a2689..f86977fb 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -2,12 +2,17 @@ package com.kingsrook.qqq.frontend.picocli; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.UUID; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; +import org.apache.commons.io.FileUtils; +import org.json.JSONArray; import org.json.JSONObject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; /******************************************************************************* + ** Unit test for the QPicoCliImplementation. ** *******************************************************************************/ class QPicoCliImplementationTest @@ -27,6 +33,7 @@ class QPicoCliImplementationTest /******************************************************************************* + ** Fully rebuild the test-database before each test runs, for completely known state. ** *******************************************************************************/ @BeforeEach @@ -38,6 +45,7 @@ class QPicoCliImplementationTest /******************************************************************************* + ** test that w/ no arguments you just get usage. ** *******************************************************************************/ @Test @@ -50,6 +58,7 @@ class QPicoCliImplementationTest /******************************************************************************* + ** test that --help gives you usage. ** *******************************************************************************/ @Test @@ -63,6 +72,7 @@ class QPicoCliImplementationTest /******************************************************************************* + ** test the --verion argument ** *******************************************************************************/ @Test @@ -75,6 +85,7 @@ class QPicoCliImplementationTest /******************************************************************************* + ** Test that an unrecognized opttion gives an error ** *******************************************************************************/ @Test @@ -89,6 +100,7 @@ class QPicoCliImplementationTest /******************************************************************************* + ** test the top-level --meta-data option ** *******************************************************************************/ @Test @@ -108,6 +120,7 @@ class QPicoCliImplementationTest /******************************************************************************* + ** test giving a table-name, gives usage for that table ** *******************************************************************************/ @Test @@ -121,6 +134,22 @@ class QPicoCliImplementationTest /******************************************************************************* + ** test unknown command under table, prints error and usage. + ** + *******************************************************************************/ + @Test + public void test_tableUnknownCommand() + { + String badCommand = "qwuijibo"; + TestOutput testOutput = testCli("person", badCommand); + assertTrue(testOutput.getError().contains("Unmatched argument at index 1: '" + badCommand + "'")); + assertTrue(testOutput.getError().contains("Usage: " + CLI_NAME + " person [COMMAND]")); + } + + + + /******************************************************************************* + ** test requesting table meta-data ** *******************************************************************************/ @Test @@ -144,6 +173,7 @@ class QPicoCliImplementationTest /******************************************************************************* + ** test running a query on a table ** *******************************************************************************/ @Test @@ -161,6 +191,134 @@ class QPicoCliImplementationTest /******************************************************************************* + ** test running an insert w/o specifying any fields, prints usage + ** + *******************************************************************************/ + @Test + public void test_tableInsertNoFieldsPrintsUsage() + { + TestOutput testOutput = testCli("person", "insert"); + assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME + " person insert")); + } + + + + /******************************************************************************* + ** test running an insert w/ fields as arguments + ** + *******************************************************************************/ + @Test + public void test_tableInsertFieldArguments() + { + TestOutput testOutput = testCli("person", "insert", + "--field-firstName=Lucy", + "--field-lastName=Lu"); + JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(insertResult); + assertEquals(1, insertResult.getJSONArray("records").length()); + assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey")); + } + + + + /******************************************************************************* + ** test running an insert w/ a mapping and json as an argument + ** + *******************************************************************************/ + @Test + public void test_tableInsertJsonObjectArgumentWithMapping() + { + String mapping = """ + --mapping={"firstName":"first","lastName":"ln"} + """; + + String jsonBody = """ + --jsonBody={"first":"Chester","ln":"Cheese"} + """; + + TestOutput testOutput = testCli("person", "insert", mapping, jsonBody); + JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(insertResult); + assertEquals(1, insertResult.getJSONArray("records").length()); + assertEquals(6, insertResult.getJSONArray("records").getJSONObject(0).getInt("primaryKey")); + assertEquals("Chester", insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getString("firstName")); + assertEquals("Cheese", insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getString("lastName")); + } + + + + /******************************************************************************* + ** test running an insert w/ a mapping and json as a multi-record file + ** + *******************************************************************************/ + @Test + public void test_tableInsertJsonArrayFileWithMapping() throws IOException + { + String mapping = """ + --mapping={"firstName":"first","lastName":"ln"} + """; + + String jsonContents = """ + [{"first":"Charlie","ln":"Bear"},{"first":"Coco","ln":"Bean"}] + """; + + File file = new File("/tmp/" + UUID.randomUUID() + ".json"); + file.deleteOnExit(); + FileUtils.writeStringToFile(file, jsonContents); + + TestOutput testOutput = testCli("person", "insert", mapping, "--jsonFile=" + file.getAbsolutePath()); + JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput()); + 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("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")); + assertEquals("Bean", records.getJSONObject(1).getJSONObject("values").getString("lastName")); + } + + + + /******************************************************************************* + ** test running an insert w/ an index-based mapping and csv file + ** + *******************************************************************************/ + @Test + public void test_tableInsertCsvFileWithIndexMapping() throws IOException + { + String mapping = """ + --mapping={"firstName":1,"lastName":3} + """; + + String csvContents = """ + "Louis","P","Willikers",1024, + "Nestle","G","Crunch",1701, + + """; + + File file = new File("/tmp/" + UUID.randomUUID() + ".csv"); + file.deleteOnExit(); + FileUtils.writeStringToFile(file, csvContents); + + TestOutput testOutput = testCli("person", "insert", mapping, "--csvFile=" + file.getAbsolutePath()); + JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput()); + 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("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")); + assertEquals("Crunch", records.getJSONObject(1).getJSONObject("values").getString("lastName")); + } + + + + /******************************************************************************* + ** test running a delete against a table ** *******************************************************************************/ @Test @@ -330,4 +488,4 @@ class QPicoCliImplementationTest this.errorLines = errorLines; } } -} \ No newline at end of file +} diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index b66b9073..f7e0c5d3 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -17,12 +17,14 @@ import static junit.framework.Assert.assertNotNull; /******************************************************************************* + ** Utility methods for unit tests. ** *******************************************************************************/ public class TestUtils { /******************************************************************************* + ** Prime a test database (e.g., h2, in-memory) ** *******************************************************************************/ @SuppressWarnings("unchecked") @@ -43,6 +45,7 @@ public class TestUtils /******************************************************************************* + ** Run an SQL Query in the test database ** *******************************************************************************/ public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception @@ -55,6 +58,7 @@ public class TestUtils /******************************************************************************* + ** Define the q-instance for testing (h2 rdbms and 'person' table) ** *******************************************************************************/ public static QInstance defineInstance() @@ -68,6 +72,7 @@ public class TestUtils /******************************************************************************* + ** Define the h2 rdbms backend ** *******************************************************************************/ public static QBackendMetaData defineBackend() @@ -85,6 +90,7 @@ public class TestUtils /******************************************************************************* + ** Define the person table ** *******************************************************************************/ public static QTableMetaData defineTablePerson() From d5c261d7e5bb08cf075ff88e811cef4a6f7e99d3 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 13 Dec 2021 22:09:09 -0600 Subject: [PATCH 07/49] Updating to log4j 2.15.0 for security patch --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a3e1e473..ead9e1d6 100644 --- a/pom.xml +++ b/pom.xml @@ -57,12 +57,12 @@ org.apache.logging.log4j log4j-api - 2.14.1 + 2.15.0 org.apache.logging.log4j log4j-core - 2.14.1 + 2.15.0 org.junit.jupiter From b33d99ce11c1c5881e222d7ab1660bdbf4a6afa5 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 14 Dec 2021 18:42:09 -0600 Subject: [PATCH 08/49] Added copyright --- checkstyle.xml | 4 ++++ pom.xml | 4 ++++ .../qqq/frontend/picocli/QPicoCliImplementation.java | 4 ++++ .../qqq/frontend/picocli/QPicoCliImplementationTest.java | 4 ++++ .../java/com/kingsrook/qqq/frontend/picocli/TestUtils.java | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/checkstyle.xml b/checkstyle.xml index fadf2cda..56a37ba1 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1,4 +1,8 @@ + + diff --git a/pom.xml b/pom.xml index ead9e1d6..f887c5c3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,8 @@ + + diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index e1f2bee5..3852c18e 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -1,3 +1,7 @@ +/* + * Copyright © 2021-2021. Kingsrook LLC . All Rights Reserved. + */ + package com.kingsrook.qqq.frontend.picocli; diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index f86977fb..7341102f 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -1,3 +1,7 @@ +/* + * Copyright © 2021-2021. Kingsrook LLC . All Rights Reserved. + */ + package com.kingsrook.qqq.frontend.picocli; diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index f7e0c5d3..70b8535a 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -1,3 +1,7 @@ +/* + * Copyright © 2021-2021. Kingsrook LLC . All Rights Reserved. + */ + package com.kingsrook.qqq.frontend.picocli; From 200a0b7ccff013de74b00b32f60487bd1e0ca2f0 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 15 Feb 2022 18:20:00 -0600 Subject: [PATCH 09/49] Checkpoint: support for authentication; initial process tests --- .../picocli/QPicoCliImplementation.java | 132 ++++++++++++++--- .../picocli/QPicoCliImplementationTest.java | 135 ++++++++++++++++-- .../qqq/frontend/picocli/TestUtils.java | 66 ++++++++- 3 files changed, 302 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 3852c18e..172d9c60 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -14,34 +14,43 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import com.kingsrook.qqq.backend.core.actions.DeleteAction; import com.kingsrook.qqq.backend.core.actions.InsertAction; 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.adapters.CsvToQRecordAdapter; import com.kingsrook.qqq.backend.core.adapters.JsonToQFieldMappingAdapter; import com.kingsrook.qqq.backend.core.adapters.JsonToQRecordAdapter; import com.kingsrook.qqq.backend.core.exceptions.QException; -import com.kingsrook.qqq.backend.core.model.actions.AbstractQFieldMapping; -import com.kingsrook.qqq.backend.core.model.actions.DeleteRequest; -import com.kingsrook.qqq.backend.core.model.actions.DeleteResult; -import com.kingsrook.qqq.backend.core.model.actions.InsertRequest; -import com.kingsrook.qqq.backend.core.model.actions.InsertResult; -import com.kingsrook.qqq.backend.core.model.actions.MetaDataRequest; -import com.kingsrook.qqq.backend.core.model.actions.MetaDataResult; -import com.kingsrook.qqq.backend.core.model.actions.QCriteriaOperator; -import com.kingsrook.qqq.backend.core.model.actions.QFilterCriteria; -import com.kingsrook.qqq.backend.core.model.actions.QQueryFilter; -import com.kingsrook.qqq.backend.core.model.actions.QueryRequest; -import com.kingsrook.qqq.backend.core.model.actions.QueryResult; -import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataRequest; -import com.kingsrook.qqq.backend.core.model.actions.TableMetaDataResult; +import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException; +import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteRequest; +import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteResult; +import com.kingsrook.qqq.backend.core.model.actions.insert.InsertRequest; +import com.kingsrook.qqq.backend.core.model.actions.insert.InsertResult; +import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataRequest; +import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataResult; +import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataRequest; +import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataResult; +import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria; +import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.actions.query.QueryRequest; +import com.kingsrook.qqq.backend.core.model.actions.query.QueryResult; +import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping; 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; @@ -65,6 +74,7 @@ public class QPicoCliImplementation public static final int DEFAULT_LIMIT = 20; private static QInstance qInstance; + private static QSession session; @@ -115,6 +125,7 @@ public class QPicoCliImplementation ** my-app-cli $table delete (--primaryKey=|--$uc=...) ** my-app-cli $table insert (--body=|--$field=...) ** my-app-cli $table update (--primaryKey=|--$uc=...) (--body=|--$field=...) + ** my-app-cli $table process $process ... ** *******************************************************************************/ public int runCli(String name, String[] args, PrintStream out, PrintStream err) @@ -148,6 +159,12 @@ public class QPicoCliImplementation tableCommand.addSubcommand("query", defineQueryCommand(table)); tableCommand.addSubcommand("insert", defineInsertCommand(table)); tableCommand.addSubcommand("delete", defineDeleteCommand(table)); + + List processes = qInstance.getProcessesForTable(tableName); + if(CollectionUtils.nullSafeHasContents(processes)) + { + tableCommand.addSubcommand("process", defineTableProcessesCommand(table, processes)); + } }); CommandLine commandLine = new CommandLine(topCommandSpec); @@ -156,6 +173,9 @@ public class QPicoCliImplementation try { + setupSession(args); + // todo - think about, do some tables get turned off based on authentication? + ParseResult parseResult = commandLine.parseArgs(args); /////////////////////////////////////////// @@ -202,6 +222,22 @@ public class QPicoCliImplementation + /******************************************************************************* + ** + *******************************************************************************/ + private static void setupSession(String[] args) throws QModuleDispatchException + { + QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher(); + QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication()); + + // todo - does this need some per-provider logic actually? mmm... + Map authenticationContext = new HashMap<>(); + authenticationContext.put("sessionId", System.getenv("sessionId")); + session = authenticationModule.createSession(authenticationContext); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -285,6 +321,24 @@ public class QPicoCliImplementation + /******************************************************************************* + ** + *******************************************************************************/ + private CommandSpec defineTableProcessesCommand(QTableMetaData table, List processes) + { + CommandSpec processesCommand = CommandSpec.create(); + + for(QProcessMetaData process : processes) + { + CommandSpec processCommand = CommandSpec.create(); + processesCommand.addSubcommand(process.getName(), processCommand); + } + + return (processesCommand); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -334,7 +388,8 @@ public class QPicoCliImplementation if(tableParseResult.hasSubcommand()) { ParseResult subParseResult = tableParseResult.subcommand(); - switch(subParseResult.commandSpec().name()) + String subCommandName = subParseResult.commandSpec().name(); + switch(subCommandName) { case "meta-data": { @@ -352,9 +407,14 @@ public class QPicoCliImplementation { return runTableDelete(commandLine, tableName, subParseResult); } + case "process": + { + CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName); + return runTableProcess(subCommandLine, tableName, subParseResult); + } default: { - commandLine.getErr().println("Unknown command: " + subParseResult.commandSpec().name()); + commandLine.getErr().println("Unknown command: " + subCommandName); commandLine.usage(commandLine.getOut()); return commandLine.getCommandSpec().exitCodeOnUsageHelp(); } @@ -369,12 +429,48 @@ public class QPicoCliImplementation + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableProcess(CommandLine commandLine, String tableName, ParseResult subParseResult) + { + if(!subParseResult.hasSubcommand()) + { + commandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + else + { + String subCommandName = subParseResult.subcommand().commandSpec().name(); + CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName); + return runTableProcessLevelCommand(subCommandLine, tableName, subParseResult.subcommand()); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableProcessLevelCommand(CommandLine subCommandLine, String tableName, ParseResult processParseResult) + { + String processName = processParseResult.commandSpec().name(); + QTableMetaData table = qInstance.getTable(tableName); + QProcessMetaData process = qInstance.getProcess(processName); + RunFunctionAction runFunctionAction = new RunFunctionAction(); + // todo! + return 0; + } + + + /******************************************************************************* ** *******************************************************************************/ private int runTableMetaData(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException { TableMetaDataRequest tableMetaDataRequest = new TableMetaDataRequest(qInstance); + tableMetaDataRequest.setSession(session); tableMetaDataRequest.setTableName(tableName); TableMetaDataAction tableMetaDataAction = new TableMetaDataAction(); TableMetaDataResult tableMetaDataResult = tableMetaDataAction.execute(tableMetaDataRequest); @@ -390,6 +486,7 @@ public class QPicoCliImplementation private int runTableQuery(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException { QueryRequest queryRequest = new QueryRequest(qInstance); + queryRequest.setSession(session); queryRequest.setTableName(tableName); queryRequest.setSkip(subParseResult.matchedOptionValue("skip", null)); queryRequest.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_LIMIT)); @@ -423,6 +520,7 @@ public class QPicoCliImplementation private int runTableInsert(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException { InsertRequest insertRequest = new InsertRequest(qInstance); + insertRequest.setSession(session); insertRequest.setTableName(tableName); QTableMetaData table = qInstance.getTable(tableName); @@ -510,6 +608,7 @@ public class QPicoCliImplementation private int runTableDelete(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException { DeleteRequest deleteRequest = new DeleteRequest(qInstance); + deleteRequest.setSession(session); deleteRequest.setTableName(tableName); ///////////////////////////////////////////// @@ -535,6 +634,7 @@ public class QPicoCliImplementation if(parseResult.hasMatchedOption("--meta-data")) { MetaDataRequest metaDataRequest = new MetaDataRequest(qInstance); + metaDataRequest.setSession(session); MetaDataAction metaDataAction = new MetaDataAction(); MetaDataResult metaDataResult = metaDataAction.execute(metaDataRequest); commandLine.getOut().println(JsonUtils.toPrettyJson(metaDataResult)); diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index 7341102f..9569757a 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.HashMap; import java.util.UUID; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.utils.JsonUtils; @@ -19,10 +20,12 @@ import org.apache.commons.io.FileUtils; import org.json.JSONArray; import org.json.JSONObject; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; 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; +import static org.junit.jupiter.api.Assertions.fail; /******************************************************************************* @@ -56,7 +59,7 @@ class QPicoCliImplementationTest public void test_noArgs() { TestOutput testOutput = testCli(); - assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME)); + assertTestOutputContains(testOutput, "Usage: " + CLI_NAME); } @@ -69,8 +72,8 @@ class QPicoCliImplementationTest public void test_help() { TestOutput testOutput = testCli("--help"); - assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME)); - assertTrue(testOutput.getOutput().matches("(?s).*Commands:.*person.*")); + assertTestOutputContains(testOutput, "Usage: " + CLI_NAME); + assertTestOutputContains(testOutput, "Commands:.*person"); } @@ -83,7 +86,7 @@ class QPicoCliImplementationTest public void test_version() { TestOutput testOutput = testCli("--version"); - assertTrue(testOutput.getOutput().contains(CLI_NAME + " v1.0")); + assertTestOutputContains(testOutput, CLI_NAME + " v1.0"); } @@ -97,8 +100,8 @@ class QPicoCliImplementationTest { String badOption = "--asdf"; TestOutput testOutput = testCli(badOption); - assertTrue(testOutput.getError().contains("Unknown option: '" + badOption + "'")); - assertTrue(testOutput.getError().contains("Usage: " + CLI_NAME)); + assertTestErrorContains(testOutput, "Unknown option: '" + badOption + "'"); + assertTestErrorContains(testOutput, "Usage: " + CLI_NAME); } @@ -131,8 +134,17 @@ class QPicoCliImplementationTest public void test_table() { TestOutput testOutput = testCli("person"); - assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME + " person [COMMAND]")); - assertTrue(testOutput.getOutput().matches("(?s).*Commands:.*query.*")); + assertTestOutputContains(testOutput, "Usage: " + CLI_NAME + " person \\[COMMAND\\]"); + assertTestOutputContains(testOutput, "Commands:.*query.*process"); + + /////////////////////////////////////////////////////// + // make sure that if there are no processes for the // + // table, that the processes sub-command isn't given // + /////////////////////////////////////////////////////// + QInstance qInstanceWithoutProcesses = TestUtils.defineInstance(); + qInstanceWithoutProcesses.setProcesses(new HashMap<>()); + testOutput = testCli(qInstanceWithoutProcesses, "person"); + assertTestOutputDoesNotContain(testOutput, "process"); } @@ -146,8 +158,8 @@ class QPicoCliImplementationTest { String badCommand = "qwuijibo"; TestOutput testOutput = testCli("person", badCommand); - assertTrue(testOutput.getError().contains("Unmatched argument at index 1: '" + badCommand + "'")); - assertTrue(testOutput.getError().contains("Usage: " + CLI_NAME + " person [COMMAND]")); + assertTestErrorContains(testOutput, "Unmatched argument at index 1: '" + badCommand + "'"); + assertTestErrorContains(testOutput, "Usage: " + CLI_NAME + " person \\[COMMAND\\]"); } @@ -202,7 +214,7 @@ class QPicoCliImplementationTest public void test_tableInsertNoFieldsPrintsUsage() { TestOutput testOutput = testCli("person", "insert"); - assertTrue(testOutput.getOutput().contains("Usage: " + CLI_NAME + " person insert")); + assertTestOutputContains(testOutput, "Usage: " + CLI_NAME + " person insert"); } @@ -299,7 +311,7 @@ class QPicoCliImplementationTest String csvContents = """ "Louis","P","Willikers",1024, "Nestle","G","Crunch",1701, - + """; File file = new File("/tmp/" + UUID.randomUUID() + ".csv"); @@ -347,12 +359,111 @@ class QPicoCliImplementationTest + /******************************************************************************* + ** test requesting the list of processes for a table + ** + *******************************************************************************/ + @Test + public void test_tableProcess() throws Exception + { + TestOutput testOutput = testCli("person", "process"); + + //////////////////////////////////////////////// + // should list the processes under this table // + //////////////////////////////////////////////// + assertTestOutputContains(testOutput, "Commands.*greet"); + } + + + + /******************************************************************************* + ** test trying to run a process, but giving an invalid name. + ** + *******************************************************************************/ + @Test + public void test_tableProcessUnknownName() throws Exception + { + String badProcessName = "not-a-process"; + TestOutput testOutput = testCli("person", "process", badProcessName); + assertTestErrorContains(testOutput, "Unmatched argument at index 2: '" + badProcessName + "'"); + assertTestErrorContains(testOutput, "Usage: " + CLI_NAME + " person process \\[COMMAND\\]"); + } + + + + /******************************************************************************* + ** test running a process on a table + ** + *******************************************************************************/ + @Test + @Disabled // not yet done. + public void test_tableProcessGreet() throws Exception + { + TestOutput testOutput = testCli("person", "process", "greet"); + + fail("Assertion not written..."); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void assertTestOutputContains(TestOutput testOutput, String expectedRegexSubstring) + { + if(!testOutput.getOutput().matches("(?s).*" + expectedRegexSubstring + ".*")) + { + fail("Expected output to contain this regex pattern:\n" + expectedRegexSubstring + + "\nBut it did not. The full output was:\n" + testOutput.getOutput()); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void assertTestOutputDoesNotContain(TestOutput testOutput, String expectedRegexSubstring) + { + if(testOutput.getOutput().matches("(?s).*" + expectedRegexSubstring + ".*")) + { + fail("Expected output to not contain this regex pattern:\n" + expectedRegexSubstring + + "\nBut it did. The full output was:\n" + testOutput.getOutput()); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void assertTestErrorContains(TestOutput testOutput, String expectedRegexSubstring) + { + if(!testOutput.getError().matches("(?s).*" + expectedRegexSubstring + ".*")) + { + fail("Expected error-output to contain this regex pattern:\n" + expectedRegexSubstring + + "\nBut it did not. The full error-output was:\n" + testOutput.getOutput()); + } + } + + + /******************************************************************************* ** *******************************************************************************/ private TestOutput testCli(String... args) { QInstance qInstance = TestUtils.defineInstance(); + return testCli(qInstance, args); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private TestOutput testCli(QInstance qInstance, String... args) + { QPicoCliImplementation qPicoCliImplementation = new QPicoCliImplementation(qInstance); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index 70b8535a..12c55e79 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -8,12 +8,23 @@ package com.kingsrook.qqq.frontend.picocli; 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.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.QTableMetaData; -import com.kingsrook.qqq.backend.module.rdbms.RDBSMBackendMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QOutputView; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListView; +import com.kingsrook.qqq.backend.module.rdbms.RDBMSBackendMetaData; import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager; import org.apache.commons.io.IOUtils; @@ -35,7 +46,7 @@ public class TestUtils public static void primeTestDatabase() throws Exception { ConnectionManager connectionManager = new ConnectionManager(); - Connection connection = connectionManager.getConnection(new RDBSMBackendMetaData(TestUtils.defineBackend())); + Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(TestUtils.defineBackend())); InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql"); assertNotNull(primeTestDatabaseSqlStream); List lines = (List) IOUtils.readLines(primeTestDatabaseSqlStream); @@ -55,7 +66,7 @@ public class TestUtils public static void runTestSql(String sql, QueryManager.ResultSetProcessor resultSetProcessor) throws Exception { ConnectionManager connectionManager = new ConnectionManager(); - Connection connection = connectionManager.getConnection(new RDBSMBackendMetaData(defineBackend())); + Connection connection = connectionManager.getConnection(new RDBMSBackendMetaData(defineBackend())); QueryManager.executeStatement(connection, sql, resultSetProcessor); } @@ -68,13 +79,28 @@ public class TestUtils public static QInstance defineInstance() { QInstance qInstance = new QInstance(); + qInstance.setAuthentication(defineAuthentication()); qInstance.addBackend(defineBackend()); qInstance.addTable(defineTablePerson()); + qInstance.addProcess(defineProcessGreetPeople()); return (qInstance); } + /******************************************************************************* + ** Define the authentication used in standard tests - using 'mock' type. + ** + *******************************************************************************/ + private static QAuthenticationMetaData defineAuthentication() + { + return new QAuthenticationMetaData() + .withName("mock") + .withType("mock"); + } + + + /******************************************************************************* ** Define the h2 rdbms backend ** @@ -113,4 +139,38 @@ public class TestUtils .withField(new QFieldMetaData("email", QFieldType.STRING)); } + + + /******************************************************************************* + ** Define the 'greet people' process + *******************************************************************************/ + private static QProcessMetaData defineProcessGreetPeople() + { + return new QProcessMetaData() + .withName("greet") + .withTableName("person") + .addFunction(new QFunctionMetaData() + .withName("prepare") + .withCode(new QCodeReference() + .withName("com.kingsrook.qqq.backend.core.interfaces.mock.MockFunctionBody") + .withCodeType(QCodeType.JAVA) + .withCodeUsage(QCodeUsage.FUNCTION)) // todo - needed, or implied in this context? + .withInputData(new QFunctionInputMetaData() + .withRecordListMetaData(new QRecordListMetaData().withTableName("person")) + .withFieldList(List.of( + new QFieldMetaData("greetingPrefix", QFieldType.STRING), + new QFieldMetaData("greetingSuffix", QFieldType.STRING) + ))) + .withOutputMetaData(new QFunctionOutputMetaData() + .withRecordListMetaData(new QRecordListMetaData() + .withTableName("person") + .addField(new QFieldMetaData("fullGreeting", QFieldType.STRING)) + ) + .withFieldList(List.of(new QFieldMetaData("outputMessage", QFieldType.STRING)))) + .withOutputView(new QOutputView() + .withMessageField("outputMessage") + .withRecordListView(new QRecordListView().withFieldNames(List.of("id", "firstName", "lastName", "fullGreeting")))) + ); + } + } From 154468bdf340a42edd0a2f3b2bff5363151f32e2 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 1 Mar 2022 18:29:59 -0600 Subject: [PATCH 10/49] Checkpoint: Added Update (edit) action; Load definition from JSON --- pom.xml | 14 +- .../qqq/frontend/picocli/QCommandBuilder.java | 253 +++++++++++++++++ .../picocli/QPicoCliImplementation.java | 257 ++++++------------ .../picocli/QPicoCliImplementationTest.java | 76 +++++- .../qqq/frontend/picocli/TestUtils.java | 4 +- src/test/resources/personQInstance.json | 156 +++++++++++ .../personQInstanceIncludingBackend.json | 163 +++++++++++ ...TrekDatabaseQInstanceIncludingBackend.json | 70 +++++ 8 files changed, 807 insertions(+), 186 deletions(-) create mode 100644 src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java create mode 100644 src/test/resources/personQInstance.json create mode 100644 src/test/resources/personQInstanceIncludingBackend.json create mode 100644 src/test/resources/starTrekDatabaseQInstanceIncludingBackend.json diff --git a/pom.xml b/pom.xml index f887c5c3..7099f797 100644 --- a/pom.xml +++ b/pom.xml @@ -79,7 +79,19 @@ - + + maven-assembly-plugin + + + jar-with-dependencies + + + + com.kingsrook.qqq.frontend.picocli.QPicoCliImplementation + + + + diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java new file mode 100644 index 00000000..4a3f84b5 --- /dev/null +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java @@ -0,0 +1,253 @@ +/* + * Copyright © 2021-2022. Kingsrook LLC . 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 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 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 + } + +} diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 172d9c60..62e92c54 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -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 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 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 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(); diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index 9569757a..a2fc1b17 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -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()) diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index 12c55e79..7250b132 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -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; diff --git a/src/test/resources/personQInstance.json b/src/test/resources/personQInstance.json new file mode 100644 index 00000000..250e5f57 --- /dev/null +++ b/src/test/resources/personQInstance.json @@ -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" + ] + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/src/test/resources/personQInstanceIncludingBackend.json b/src/test/resources/personQInstanceIncludingBackend.json new file mode 100644 index 00000000..74095833 --- /dev/null +++ b/src/test/resources/personQInstanceIncludingBackend.json @@ -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" + } +} \ No newline at end of file diff --git a/src/test/resources/starTrekDatabaseQInstanceIncludingBackend.json b/src/test/resources/starTrekDatabaseQInstanceIncludingBackend.json new file mode 100644 index 00000000..69d0389a --- /dev/null +++ b/src/test/resources/starTrekDatabaseQInstanceIncludingBackend.json @@ -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" + } +} From 12a85b1d68214da49b7e8bf89b2904c4d4037813 Mon Sep 17 00:00:00 2001 From: tsamplesKR <93012035+tsamplesKR@users.noreply.github.com> Date: Thu, 28 Apr 2022 12:34:43 -0500 Subject: [PATCH 11/49] Update workflow to publish jar in qqq-maven --- .github/workflows/maven.yml | 35 +++++++++++++++++++++++++++++++++++ pom.xml | 16 ++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 00000000..80d00dfd --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,35 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: '17' + distribution: 'adopt' + cache: maven + - name: maven-settings-xml-action + uses: whelk-io/maven-settings-xml-action@v20 + with: + servers: '[{ "id": "github-qqq-maven-registry", "username": "${{ secrets.QQQ_MAVEN_REGISTRY_USERNAME }}", "password": "${{ secrets.QQQ_MAVEN_REGISTRY_PASSWORD }}" }]' + repositories: '[{ "id": "github-qqq-maven-registry", "url": "https://maven.pkg.github.com/Kingsrook/qqq-maven-registry", "snapshots": { "enabled": "true" }}]' + - name: Build with Maven + run: mvn -B package --file pom.xml + - name: Publish to GitHub Packages Apache Maven + run: mvn deploy + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/pom.xml b/pom.xml index 7099f797..749fa7cc 100644 --- a/pom.xml +++ b/pom.xml @@ -142,4 +142,20 @@ + + + github + GitHub QQQ Maven Registry + https://maven.pkg.github.com/Kingsrook/qqq-maven-registry + + + + + + github-qqq-maven-registry + GitHub QQQ Maven Registry + https://maven.pkg.github.com/Kingsrook/qqq-maven-registry + + + From 44761eacba67159ea40f9fbb8b072730e30b4680 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 7 Jun 2022 16:52:10 -0500 Subject: [PATCH 12/49] QQQ-4 Applying AGPL Licence --- LICENSE | 619 ++++++++++++++++++ README.md | 19 + checkstyle.xml | 19 +- pom.xml | 19 +- .../qqq/frontend/picocli/QCommandBuilder.java | 19 +- .../picocli/QPicoCliImplementation.java | 19 +- .../picocli/QPicoCliImplementationTest.java | 19 +- .../qqq/frontend/picocli/TestUtils.java | 19 +- src/test/resources/prime-test-database.sql | 21 + 9 files changed, 767 insertions(+), 6 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ca9b0551 --- /dev/null +++ b/LICENSE @@ -0,0 +1,619 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md index b53deeac..fac125e2 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,22 @@ This is a qqq middleware module, providing [picocli](https://picocli.info) access to the qqq-backend. +## License +QQQ - Low-code Application Framework for Engineers. \ +Copyright (C) 2022. Kingsrook, LLC \ +651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States \ +contact@kingsrook.com +https://github.com/Kingsrook/intellij-commentator-plugin + +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 . diff --git a/checkstyle.xml b/checkstyle.xml index 56a37ba1..a6a0d052 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1,6 +1,23 @@ . All Rights Reserved. + * 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/intellij-commentator-plugin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ package com.kingsrook.qqq.frontend.picocli; diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 62e92c54..362537e0 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -1,5 +1,22 @@ /* - * Copyright © 2021-2021. Kingsrook LLC . All Rights Reserved. + * 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/intellij-commentator-plugin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ package com.kingsrook.qqq.frontend.picocli; diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index a2fc1b17..831a65d9 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -1,5 +1,22 @@ /* - * Copyright © 2021-2021. Kingsrook LLC . All Rights Reserved. + * 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/intellij-commentator-plugin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ package com.kingsrook.qqq.frontend.picocli; diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index 7250b132..c89cfb17 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -1,5 +1,22 @@ /* - * Copyright © 2021-2021. Kingsrook LLC . All Rights Reserved. + * 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/intellij-commentator-plugin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ package com.kingsrook.qqq.frontend.picocli; diff --git a/src/test/resources/prime-test-database.sql b/src/test/resources/prime-test-database.sql index e5adab34..96a050d7 100644 --- a/src/test/resources/prime-test-database.sql +++ b/src/test/resources/prime-test-database.sql @@ -1,3 +1,24 @@ +/* + * 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/intellij-commentator-plugin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + DROP TABLE IF EXISTS person; CREATE TABLE person ( From 75a9582a9c4bf5984951dcb9c743d5bdd221593b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 8 Jun 2022 09:07:52 -0500 Subject: [PATCH 13/49] Fixing github link in copyright --- checkstyle.xml | 2 +- pom.xml | 2 +- .../com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java | 2 +- .../kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java | 2 +- .../qqq/frontend/picocli/QPicoCliImplementationTest.java | 2 +- src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java | 2 +- src/test/resources/prime-test-database.sql | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/checkstyle.xml b/checkstyle.xml index a6a0d052..dbaa3479 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -4,7 +4,7 @@ ~ 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/intellij-commentator-plugin + ~ https://github.com/Kingsrook/ ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU Affero General Public License as diff --git a/pom.xml b/pom.xml index 73d73be3..e9d2c95f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ~ 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/intellij-commentator-plugin + ~ https://github.com/Kingsrook/ ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU Affero General Public License as diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java index 536d4826..3f5f0538 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java @@ -3,7 +3,7 @@ * 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/intellij-commentator-plugin + * https://github.com/Kingsrook/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 362537e0..fe97a991 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -3,7 +3,7 @@ * 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/intellij-commentator-plugin + * https://github.com/Kingsrook/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index 831a65d9..306b537f 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -3,7 +3,7 @@ * 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/intellij-commentator-plugin + * https://github.com/Kingsrook/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index c89cfb17..13835afb 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -3,7 +3,7 @@ * 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/intellij-commentator-plugin + * https://github.com/Kingsrook/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as diff --git a/src/test/resources/prime-test-database.sql b/src/test/resources/prime-test-database.sql index 96a050d7..6227f249 100644 --- a/src/test/resources/prime-test-database.sql +++ b/src/test/resources/prime-test-database.sql @@ -3,7 +3,7 @@ * 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/intellij-commentator-plugin + * 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 From 882505bb09fbaa3f6f334cdd323971bd04f6a83c Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 13 Jun 2022 15:00:58 -0500 Subject: [PATCH 14/49] Real-development-mode ready versions of pom and CI scripts --- .circleci/config.yml | 77 +++++++++++++++++++++++++++++++++++++ .circleci/mvn-settings.xml | 9 +++++ .github/workflows/maven.yml | 35 ----------------- pom.xml | 34 ++++++++++++---- 4 files changed, 113 insertions(+), 42 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 .circleci/mvn-settings.xml delete mode 100644 .github/workflows/maven.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..7c158891 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,77 @@ +version: 2.1 + +executors: + java17: + docker: + - image: 'cimg/openjdk:17.0' + resource_class: small + +orbs: + slack: circleci/slack@4.10.1 + +commands: + run_maven: + parameters: + maven_subcommand: + default: test + type: string + steps: + - checkout + - restore_cache: + keys: + - v1-dependencies-{{ checksum "pom.xml" }} + - run: + name: Run Maven + command: | + mvn -s .circleci/mvn-settings.xml << parameters.maven_subcommand >> + - run: + name: Save test results + command: | + mkdir -p ~/test-results/junit/ + find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} ~/test-results/junit/ \; + when: always + - store_test_results: + path: ~/test-results + - save_cache: + paths: + - ~/.m2 + key: v1-dependencies-{{ checksum "pom.xml" }} + +jobs: + mvn_test: + executor: java17 + steps: + - run_maven: + maven_subcommand: test + - slack/notify: + event: fail + + mvn_deploy: + executor: java17 + steps: + - run_maven: + maven_subcommand: deploy + - slack/notify: + event: always + +workflows: + test_only: + jobs: + - mvn_test: + context: [ qqq-maven-registry-credentials, kingsrook-slack ] + filters: + branches: + ignore: /dev/ + tags: + ignore: /version-.*/ + + deploy: + jobs: + - mvn_deploy: + context: [ qqq-maven-registry-credentials, kingsrook-slack ] + filters: + branches: + only: /dev/ + tags: + only: /version-.*/ + diff --git a/.circleci/mvn-settings.xml b/.circleci/mvn-settings.xml new file mode 100644 index 00000000..b2a345f0 --- /dev/null +++ b/.circleci/mvn-settings.xml @@ -0,0 +1,9 @@ + + + + github-qqq-maven-registry + ${env.QQQ_MAVEN_REGISTRY_USERNAME} + ${env.QQQ_MAVEN_REGISTRY_PASSWORD} + + + diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index 80d00dfd..00000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,35 +0,0 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -name: Java CI with Maven - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - java-version: '17' - distribution: 'adopt' - cache: maven - - name: maven-settings-xml-action - uses: whelk-io/maven-settings-xml-action@v20 - with: - servers: '[{ "id": "github-qqq-maven-registry", "username": "${{ secrets.QQQ_MAVEN_REGISTRY_USERNAME }}", "password": "${{ secrets.QQQ_MAVEN_REGISTRY_PASSWORD }}" }]' - repositories: '[{ "id": "github-qqq-maven-registry", "url": "https://maven.pkg.github.com/Kingsrook/qqq-maven-registry", "snapshots": { "enabled": "true" }}]' - - name: Build with Maven - run: mvn -B package --file pom.xml - - name: Publish to GitHub Packages Apache Maven - run: mvn deploy - env: - GITHUB_TOKEN: ${{ github.token }} diff --git a/pom.xml b/pom.xml index e9d2c95f..61dfd039 100644 --- a/pom.xml +++ b/pom.xml @@ -20,14 +20,18 @@ ~ along with this program. If not, see . --> - + 4.0.0 com.kingsrook.qqq qqq-middleware-picocli - 0.0-SNAPSHOT + 0.0.0-SNAPSHOT + + + scm:git:git@github.com:Kingsrook/qqq-middleware-picocli.git + scm:git:git@github.com:Kingsrook/qqq-middleware-picocli.git + HEAD + @@ -47,12 +51,12 @@ com.kingsrook.qqq qqq-backend-core - 0.0-SNAPSHOT + 0.0.0-SNAPSHOT com.kingsrook.qqq qqq-backend-module-rdbms - 0.0-SNAPSHOT + 0.0.0-SNAPSHOT test @@ -156,12 +160,28 @@ + + com.amashchenko.maven.plugin + gitflow-maven-plugin + 1.18.0 + + + main + dev + version- + + true + install + true + 1 + + - github + github-qqq-maven-registry GitHub QQQ Maven Registry https://maven.pkg.github.com/Kingsrook/qqq-maven-registry From ca2874b06fe503a6558e6f31dcc24a223d859a1b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 13 Jun 2022 15:01:05 -0500 Subject: [PATCH 15/49] Update to strip away comment lines (e.g., copyright) and use -- comments --- .../qqq/frontend/picocli/TestUtils.java | 1 + src/test/resources/prime-test-database.sql | 40 +++++++++---------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index 13835afb..d880837d 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -67,6 +67,7 @@ public class TestUtils InputStream primeTestDatabaseSqlStream = TestUtils.class.getResourceAsStream("/prime-test-database.sql"); assertNotNull(primeTestDatabaseSqlStream); List lines = (List) IOUtils.readLines(primeTestDatabaseSqlStream); + lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList(); String joinedSQL = String.join("\n", lines); for(String sql : joinedSQL.split(";")) { diff --git a/src/test/resources/prime-test-database.sql b/src/test/resources/prime-test-database.sql index 6227f249..0780239c 100644 --- a/src/test/resources/prime-test-database.sql +++ b/src/test/resources/prime-test-database.sql @@ -1,23 +1,23 @@ -/* - * QQQ - Low-code Application Framework for Engineers. - * Copyright (C) 2021-2022. Kingsrook, LLC - * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States - * contact@kingsrook.com - * https://github.com/Kingsrook/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ +-- +-- QQQ - Low-code Application Framework for Engineers. +-- Copyright (C) 2021-2022. Kingsrook, LLC +-- 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States +-- contact@kingsrook.com +-- https://github.com/Kingsrook/ +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . +-- DROP TABLE IF EXISTS person; CREATE TABLE person From 8dd16cfa92f40c4e0b71a0e0f8f62350d32a36df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 20:09:53 +0000 Subject: [PATCH 16/49] Bump log4j-core from 2.15.0 to 2.17.1 Bumps log4j-core from 2.15.0 to 2.17.1. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-core dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 61dfd039..53077c3b 100644 --- a/pom.xml +++ b/pom.xml @@ -87,7 +87,7 @@ org.apache.logging.log4j log4j-core - 2.15.0 + 2.17.1 org.junit.jupiter From 1f5a1ac0b072b5019ef531b9e9f77eb0b65107ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 20:35:49 +0000 Subject: [PATCH 17/49] Bump log4j-api from 2.15.0 to 2.17.1 Bumps log4j-api from 2.15.0 to 2.17.1. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 53077c3b..c794a137 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ org.apache.logging.log4j log4j-api - 2.15.0 + 2.17.1 org.apache.logging.log4j From 12e53407f2d59d00faf0a1c1f2c0d579d722c705 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 20:36:00 +0000 Subject: [PATCH 18/49] Bump h2 from 1.4.197 to 2.1.210 Bumps [h2](https://github.com/h2database/h2database) from 1.4.197 to 2.1.210. - [Release notes](https://github.com/h2database/h2database/releases) - [Commits](https://github.com/h2database/h2database/compare/version-1.4.197...version-2.1.210) --- updated-dependencies: - dependency-name: com.h2database:h2 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 53077c3b..b8a678f4 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ com.h2database h2 - 1.4.197 + 2.1.210 test From 59bab29ed9678b598734bbc3c80d4fc026a78a0d Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 13 Jun 2022 15:44:21 -0500 Subject: [PATCH 19/49] QQQ-8 Updating compatibility with h2 updagrade --- .../picocli/QPicoCliImplementationTest.java | 23 ++++++++++++------- src/test/resources/prime-test-database.sql | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index 306b537f..0a1a0816 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -246,7 +246,8 @@ class QPicoCliImplementationTest { TestOutput testOutput = testCli("person", "insert", "--field-firstName=Lucy", - "--field-lastName=Lu"); + "--field-lastName=Lu", + "--field-email=lucy@kingsrook.com"); JSONObject insertResult = JsonUtils.toJSONObject(testOutput.getOutput()); assertNotNull(insertResult); assertEquals(1, insertResult.getJSONArray("records").length()); @@ -263,11 +264,11 @@ class QPicoCliImplementationTest public void test_tableInsertJsonObjectArgumentWithMapping() { String mapping = """ - --mapping={"firstName":"first","lastName":"ln"} + --mapping={"firstName":"first","lastName":"ln","email":"email"} """; String jsonBody = """ - --jsonBody={"first":"Chester","ln":"Cheese"} + --jsonBody={"first":"Chester","ln":"Cheese","email":"chester@kingsrook.com"} """; TestOutput testOutput = testCli("person", "insert", mapping, jsonBody); @@ -277,6 +278,7 @@ class QPicoCliImplementationTest 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")); + assertEquals("chester@kingsrook.com", insertResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getString("email")); } @@ -289,11 +291,14 @@ class QPicoCliImplementationTest public void test_tableInsertJsonArrayFileWithMapping() throws IOException { String mapping = """ - --mapping={"firstName":"first","lastName":"ln"} + --mapping={"firstName":"first","lastName":"ln","email":"email"} """; String jsonContents = """ - [{"first":"Charlie","ln":"Bear"},{"first":"Coco","ln":"Bean"}] + [ + {"first":"Charlie","ln":"Bear","email":"charlie-bear@kingsrook.com"}, + {"first":"Coco","ln":"Bean","email":"coco-bean@kingsrook.com"} + ] """; File file = new File("/tmp/" + UUID.randomUUID() + ".json"); @@ -309,8 +314,10 @@ class QPicoCliImplementationTest 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("charlie-bear@kingsrook.com", records.getJSONObject(0).getJSONObject("values").getString("email")); assertEquals("Coco", records.getJSONObject(1).getJSONObject("values").getString("firstName")); assertEquals("Bean", records.getJSONObject(1).getJSONObject("values").getString("lastName")); + assertEquals("coco-bean@kingsrook.com", records.getJSONObject(1).getJSONObject("values").getString("email")); } @@ -323,12 +330,12 @@ class QPicoCliImplementationTest public void test_tableInsertCsvFileWithIndexMapping() throws IOException { String mapping = """ - --mapping={"firstName":1,"lastName":3} + --mapping={"firstName":1,"lastName":3,"email":5} """; String csvContents = """ - "Louis","P","Willikers",1024, - "Nestle","G","Crunch",1701, + "Louis","P","Willikers",1024,"louis@kingsrook.com", + "Nestle","G","Crunch",1701,"nestle@kingsrook.com", """; diff --git a/src/test/resources/prime-test-database.sql b/src/test/resources/prime-test-database.sql index 0780239c..be858987 100644 --- a/src/test/resources/prime-test-database.sql +++ b/src/test/resources/prime-test-database.sql @@ -22,7 +22,7 @@ DROP TABLE IF EXISTS person; CREATE TABLE person ( - id SERIAL, + id INT AUTO_INCREMENT, create_date TIMESTAMP DEFAULT now(), modify_date TIMESTAMP DEFAULT now(), From 81b494e494c9c82b1bfa98957c2ffd9fcb645e4e Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 13 Jun 2022 15:51:12 -0500 Subject: [PATCH 20/49] Fixed github link in License --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index fac125e2..672d87d7 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,7 @@ This is a qqq middleware module, providing [picocli](https://picocli.info) acces QQQ - Low-code Application Framework for Engineers. \ Copyright (C) 2022. Kingsrook, LLC \ 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States \ -contact@kingsrook.com -https://github.com/Kingsrook/intellij-commentator-plugin +contact@kingsrook.com | https://github.com/Kingsrook/ This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as From fcc831008facb095090b773de6e0506fa3116d4d Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 23 Jun 2022 16:49:44 -0500 Subject: [PATCH 21/49] Update to run deploy on snapshot tags --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7c158891..0ef02745 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -63,7 +63,7 @@ workflows: branches: ignore: /dev/ tags: - ignore: /version-.*/ + ignore: /(version|snapshot)-.*/ deploy: jobs: @@ -73,5 +73,5 @@ workflows: branches: only: /dev/ tags: - only: /version-.*/ + only: /(version|snapshot)-.*/ From ff834274837ac1745f5d711e3b0bcaec46d34087 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 24 Jun 2022 08:12:21 -0500 Subject: [PATCH 22/49] QQQ-7 first version of actual execution of processes --- checkstyle.xml | 4 +- .../picocli/PicoCliProcessCallback.java | 87 ++++++++++++++++++ .../qqq/frontend/picocli/QCommandBuilder.java | 46 +++++++++- .../picocli/QPicoCliImplementation.java | 86 ++++++++++++++---- .../picocli/QPicoCliImplementationTest.java | 88 ++++++++++++++----- .../qqq/frontend/picocli/TestUtils.java | 28 +++--- 6 files changed, 280 insertions(+), 59 deletions(-) create mode 100644 src/main/java/com/kingsrook/qqq/frontend/picocli/PicoCliProcessCallback.java diff --git a/checkstyle.xml b/checkstyle.xml index dbaa3479..76f872ed 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -46,6 +46,7 @@ --> + @@ -171,7 +172,7 @@ - + + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + pre-unit-test + + prepare-agent + + + jaCoCoArgLine + + + + unit-test-check + + check + + + + ${coverage.haltOnFailure} + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${coverage.instructionCoveredRatioMinimum} + + + + + + + + post-unit-test + verify + + report + + + + + + exec-maven-plugin + org.codehaus.mojo + 3.0.0 + + + test-coverage-summary + verify + + exec + + + sh + + -c + + /tmp/$$.headers +xpath -q -e '/html/body/table/tfoot/tr[1]/td/text()' target/site/jacoco/index.html > /tmp/$$.values +echo +echo "Jacoco coverage summary report:" +echo " See also target/site/jacoco/index.html" +echo " and https://www.jacoco.org/jacoco/trunk/doc/counters.html" +echo "------------------------------------------------------------" +paste /tmp/$$.headers /tmp/$$.values | tail +2 | awk -v FS='\t' '{printf("%-20s %s\n",$1,$2)}' +rm /tmp/$$.headers /tmp/$$.values + ]]> + + + + + + From 8a2cb95a07f938a53a5e7bba6ca4d2eb20f3364a Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Fri, 8 Jul 2022 12:06:52 -0500 Subject: [PATCH 30/49] QQQ-21 update qqq-backend-core for process function->step refactor --- checkstyle.xml | 2 +- .../qqq/frontend/picocli/QPicoCliImplementation.java | 5 +++-- .../java/com/kingsrook/qqq/frontend/picocli/TestUtils.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/checkstyle.xml b/checkstyle.xml index 76f872ed..f5e7412d 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -181,8 +181,8 @@ --> - - info.picocli - picocli - 4.6.1 + info.picocli + picocli + 4.6.1 com.h2database diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/PicoCliProcessCallback.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/PicoCliProcessCallback.java index b4cf0a70..9d6f4555 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/PicoCliProcessCallback.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/PicoCliProcessCallback.java @@ -27,9 +27,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; -import com.kingsrook.qqq.backend.core.callbacks.QProcessCallback; -import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter; -import com.kingsrook.qqq.backend.core.model.metadata.QFieldMetaData; +import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import picocli.CommandLine; diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java index d2fbed7b..c949c9fd 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java @@ -29,10 +29,10 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -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.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; import picocli.CommandLine; diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 56cd335a..8177b6df 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -32,48 +32,48 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import com.kingsrook.qqq.backend.core.actions.CountAction; -import com.kingsrook.qqq.backend.core.actions.DeleteAction; -import com.kingsrook.qqq.backend.core.actions.InsertAction; -import com.kingsrook.qqq.backend.core.actions.MetaDataAction; -import com.kingsrook.qqq.backend.core.actions.QueryAction; -import com.kingsrook.qqq.backend.core.actions.RunProcessAction; -import com.kingsrook.qqq.backend.core.actions.TableMetaDataAction; -import com.kingsrook.qqq.backend.core.actions.UpdateAction; +import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction; +import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction; +import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction; +import com.kingsrook.qqq.backend.core.actions.tables.CountAction; +import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction; +import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.actions.tables.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.count.CountRequest; -import com.kingsrook.qqq.backend.core.model.actions.count.CountResult; -import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteRequest; -import com.kingsrook.qqq.backend.core.model.actions.delete.DeleteResult; -import com.kingsrook.qqq.backend.core.model.actions.insert.InsertRequest; -import com.kingsrook.qqq.backend.core.model.actions.insert.InsertResult; -import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataRequest; -import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataResult; -import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataRequest; -import com.kingsrook.qqq.backend.core.model.actions.metadata.table.TableMetaDataResult; -import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessRequest; -import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessResult; -import com.kingsrook.qqq.backend.core.model.actions.query.QCriteriaOperator; -import com.kingsrook.qqq.backend.core.model.actions.query.QFilterCriteria; -import com.kingsrook.qqq.backend.core.model.actions.query.QQueryFilter; -import com.kingsrook.qqq.backend.core.model.actions.query.QueryRequest; -import com.kingsrook.qqq.backend.core.model.actions.query.QueryResult; +import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; +import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput; +import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput; +import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput; 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.actions.tables.count.CountInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; +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.insert.InsertInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; +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.QueryInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; +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.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.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; 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.modules.authentication.QAuthenticationModuleDispatcher; +import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface; import com.kingsrook.qqq.backend.core.utils.JsonUtils; import org.apache.commons.io.FileUtils; import picocli.CommandLine; @@ -367,7 +367,7 @@ public class QPicoCliImplementation { String processName = processParseResult.commandSpec().name(); QProcessMetaData process = qInstance.getProcess(processName); - RunProcessRequest request = new RunProcessRequest(qInstance); + RunProcessInput request = new RunProcessInput(qInstance); request.setSession(session); request.setProcessName(processName); @@ -384,7 +384,7 @@ public class QPicoCliImplementation try { - RunProcessResult result = new RunProcessAction().execute(request); + RunProcessOutput result = new RunProcessAction().execute(request); subCommandLine.getOut().println("Process Results: "); // todo better!! for(QFieldMetaData outputField : process.getOutputFields()) { @@ -414,12 +414,12 @@ public class QPicoCliImplementation *******************************************************************************/ private int runTableMetaData(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException { - TableMetaDataRequest tableMetaDataRequest = new TableMetaDataRequest(qInstance); - tableMetaDataRequest.setSession(session); - tableMetaDataRequest.setTableName(tableName); + TableMetaDataInput tableMetaDataInput = new TableMetaDataInput(qInstance); + tableMetaDataInput.setSession(session); + tableMetaDataInput.setTableName(tableName); TableMetaDataAction tableMetaDataAction = new TableMetaDataAction(); - TableMetaDataResult tableMetaDataResult = tableMetaDataAction.execute(tableMetaDataRequest); - commandLine.getOut().println(JsonUtils.toPrettyJson(tableMetaDataResult)); + TableMetaDataOutput tableMetaDataOutput = tableMetaDataAction.execute(tableMetaDataInput); + commandLine.getOut().println(JsonUtils.toPrettyJson(tableMetaDataOutput)); return commandLine.getCommandSpec().exitCodeOnSuccess(); } @@ -430,14 +430,14 @@ public class QPicoCliImplementation *******************************************************************************/ private int runTableCount(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException { - CountRequest countRequest = new CountRequest(qInstance); - countRequest.setSession(session); - countRequest.setTableName(tableName); - countRequest.setFilter(generateQueryFilter(subParseResult)); + CountInput countInput = new CountInput(qInstance); + countInput.setSession(session); + countInput.setTableName(tableName); + countInput.setFilter(generateQueryFilter(subParseResult)); CountAction countAction = new CountAction(); - CountResult countResult = countAction.execute(countRequest); - commandLine.getOut().println(JsonUtils.toPrettyJson(countResult)); + CountOutput countOutput = countAction.execute(countInput); + commandLine.getOut().println(JsonUtils.toPrettyJson(countOutput)); return commandLine.getCommandSpec().exitCodeOnSuccess(); } @@ -448,16 +448,16 @@ public class QPicoCliImplementation *******************************************************************************/ private int runTableQuery(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException { - QueryRequest queryRequest = new QueryRequest(qInstance); - queryRequest.setSession(session); - queryRequest.setTableName(tableName); - queryRequest.setSkip(subParseResult.matchedOptionValue("skip", null)); - queryRequest.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_LIMIT)); - queryRequest.setFilter(generateQueryFilter(subParseResult)); + QueryInput queryInput = new QueryInput(qInstance); + queryInput.setSession(session); + queryInput.setTableName(tableName); + queryInput.setSkip(subParseResult.matchedOptionValue("skip", null)); + queryInput.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_LIMIT)); + queryInput.setFilter(generateQueryFilter(subParseResult)); QueryAction queryAction = new QueryAction(); - QueryResult queryResult = queryAction.execute(queryRequest); - commandLine.getOut().println(JsonUtils.toPrettyJson(queryResult)); + QueryOutput queryOutput = queryAction.execute(queryInput); + commandLine.getOut().println(JsonUtils.toPrettyJson(queryOutput)); return commandLine.getCommandSpec().exitCodeOnSuccess(); } @@ -492,9 +492,9 @@ public class QPicoCliImplementation *******************************************************************************/ private int runTableInsert(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException { - InsertRequest insertRequest = new InsertRequest(qInstance); - insertRequest.setSession(session); - insertRequest.setTableName(tableName); + InsertInput insertInput = new InsertInput(qInstance); + insertInput.setSession(session); + insertInput.setTableName(tableName); QTableMetaData table = qInstance.getTable(tableName); AbstractQFieldMapping mapping = null; @@ -565,11 +565,11 @@ public class QPicoCliImplementation } } - insertRequest.setRecords(recordList); + insertInput.setRecords(recordList); InsertAction insertAction = new InsertAction(); - InsertResult insertResult = insertAction.execute(insertRequest); - commandLine.getOut().println(JsonUtils.toPrettyJson(insertResult)); + InsertOutput insertOutput = insertAction.execute(insertInput); + commandLine.getOut().println(JsonUtils.toPrettyJson(insertOutput)); return commandLine.getCommandSpec().exitCodeOnSuccess(); } @@ -580,9 +580,9 @@ 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); + UpdateInput updateInput = new UpdateInput(qInstance); + updateInput.setSession(session); + updateInput.setTableName(tableName); QTableMetaData table = qInstance.getTable(tableName); List recordList = new ArrayList<>(); @@ -616,10 +616,10 @@ public class QPicoCliImplementation return commandLine.getCommandSpec().exitCodeOnUsageHelp(); } - updateRequest.setRecords(recordList); + updateInput.setRecords(recordList); UpdateAction updateAction = new UpdateAction(); - UpdateResult updateResult = updateAction.execute(updateRequest); + UpdateOutput updateResult = updateAction.execute(updateInput); commandLine.getOut().println(JsonUtils.toPrettyJson(updateResult)); return commandLine.getCommandSpec().exitCodeOnSuccess(); } @@ -631,19 +631,19 @@ public class QPicoCliImplementation *******************************************************************************/ private int runTableDelete(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException { - DeleteRequest deleteRequest = new DeleteRequest(qInstance); - deleteRequest.setSession(session); - deleteRequest.setTableName(tableName); + DeleteInput deleteInput = new DeleteInput(qInstance); + deleteInput.setSession(session); + deleteInput.setTableName(tableName); ///////////////////////////////////////////// // get the pKeys that the user specified // ///////////////////////////////////////////// String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); Serializable[] primaryKeyValues = primaryKeyOption.split(","); - deleteRequest.setPrimaryKeys(Arrays.asList(primaryKeyValues)); + deleteInput.setPrimaryKeys(Arrays.asList(primaryKeyValues)); DeleteAction deleteAction = new DeleteAction(); - DeleteResult deleteResult = deleteAction.execute(deleteRequest); + DeleteOutput deleteResult = deleteAction.execute(deleteInput); commandLine.getOut().println(JsonUtils.toPrettyJson(deleteResult)); return commandLine.getCommandSpec().exitCodeOnSuccess(); } @@ -657,11 +657,11 @@ public class QPicoCliImplementation { if(parseResult.hasMatchedOption("--meta-data")) { - MetaDataRequest metaDataRequest = new MetaDataRequest(qInstance); - metaDataRequest.setSession(session); + MetaDataInput metaDataInput = new MetaDataInput(qInstance); + metaDataInput.setSession(session); MetaDataAction metaDataAction = new MetaDataAction(); - MetaDataResult metaDataResult = metaDataAction.execute(metaDataRequest); - commandLine.getOut().println(JsonUtils.toPrettyJson(metaDataResult)); + MetaDataOutput metaDataOutput = metaDataAction.execute(metaDataInput); + commandLine.getOut().println(JsonUtils.toPrettyJson(metaDataOutput)); return commandLine.getCommandSpec().exitCodeOnSuccess(); } diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index c72c140b..926cc972 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -25,15 +25,15 @@ package com.kingsrook.qqq.frontend.picocli; import java.io.InputStream; import java.sql.Connection; import java.util.List; -import com.kingsrook.qqq.backend.core.interfaces.mock.MockBackendStep; +import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep; import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData; -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.QFieldMetaData; -import com.kingsrook.qqq.backend.core.model.metadata.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage; +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.QInstance; -import com.kingsrook.qqq.backend.core.model.metadata.QTableMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData; From 84858b1eb463a1265130c2f9e32263558df6080e Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 19 Jul 2022 12:18:47 -0500 Subject: [PATCH 40/49] QQQ-26 update backend-core, queryOutput interface; also add 'get' command, and move log4j output to log file --- .circleci/config.yml | 2 + .gitignore | 1 + pom.xml | 10 +- .../qqq/frontend/picocli/QCommandBuilder.java | 69 +++- .../picocli/QPicoCliImplementation.java | 172 +++++++- src/main/resources/qqq-picocli-log4j2.xml | 18 + .../picocli/QPicoCliImplementationTest.java | 386 ++++++++++++------ .../qqq/frontend/picocli/TestOutput.java | 136 ++++++ 8 files changed, 656 insertions(+), 138 deletions(-) create mode 100644 src/main/resources/qqq-picocli-log4j2.xml create mode 100644 src/test/java/com/kingsrook/qqq/frontend/picocli/TestOutput.java diff --git a/.circleci/config.yml b/.circleci/config.yml index fe4c371c..658f75d1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,6 +24,8 @@ commands: name: Run Maven command: | mvn -s .circleci/mvn-settings.xml << parameters.maven_subcommand >> + - store_artifacts: + path: target/site/jacoco - run: name: Save test results command: | diff --git a/.gitignore b/.gitignore index 39736a21..b65cb041 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ target/ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +.DS_Store diff --git a/pom.xml b/pom.xml index 59ce218d..292d8a30 100644 --- a/pom.xml +++ b/pom.xml @@ -53,12 +53,12 @@ com.kingsrook.qqq qqq-backend-core - 0.2.0-SNAPSHOT + 0.2.0-20220719.154219-3 com.kingsrook.qqq qqq-backend-module-rdbms - 0.2.0-SNAPSHOT + 0.2.0-20220719.155012-5 test @@ -97,6 +97,12 @@ 5.8.1 test + + org.assertj + assertj-core + 3.23.1 + test + diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java index c949c9fd..970034b4 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java @@ -90,11 +90,13 @@ public class QCommandBuilder // add table-specific sub-commands for the table // /////////////////////////////////////////////////// tableCommand.addSubcommand("meta-data", defineMetaDataCommand(table)); - tableCommand.addSubcommand("count", defineQueryCommand(table)); + tableCommand.addSubcommand("count", defineCountCommand(table)); + tableCommand.addSubcommand("get", defineGetCommand(table)); tableCommand.addSubcommand("query", defineQueryCommand(table)); tableCommand.addSubcommand("insert", defineInsertCommand(table)); tableCommand.addSubcommand("update", defineUpdateCommand(table)); tableCommand.addSubcommand("delete", defineDeleteCommand(table)); + tableCommand.addSubcommand("export", defineExportCommand(table)); List processes = qInstance.getProcessesForTable(tableName); if(CollectionUtils.nullSafeHasContents(processes)) @@ -158,6 +160,71 @@ public class QCommandBuilder + /******************************************************************************* + ** + *******************************************************************************/ + private CommandLine.Model.CommandSpec defineExportCommand(QTableMetaData table) + { + CommandLine.Model.CommandSpec exportCommand = CommandLine.Model.CommandSpec.create(); + exportCommand.addOption(CommandLine.Model.OptionSpec.builder("-f", "--filename") + .type(String.class) + .description("File name (including path) to write to. File extension will be used to determine the report format. Supported formats are: csv, xlsx.") + .required(true) + .build()); + exportCommand.addOption(CommandLine.Model.OptionSpec.builder("-e", "--fieldNames") + .type(String.class) + .description("Comma-separated list of field names (e.g., from table meta-data) to include in the export. If not given, then all fields in the table are included.") + .build()); + exportCommand.addOption(CommandLine.Model.OptionSpec.builder("-l", "--limit") + .type(int.class) + .description("Optional limit on the max number of records to include in the export.") + .build()); + exportCommand.addOption(CommandLine.Model.OptionSpec.builder("-c", "--criteria") + .type(String[].class) + .description("Query filter criteria for the export. May be given multiple times. Use format: $fieldName $operator $value. e.g., id EQUALS 42") + .build()); + + // todo - add the fields as explicit params? + + return exportCommand; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private CommandLine.Model.CommandSpec defineGetCommand(QTableMetaData table) + { + CommandLine.Model.CommandSpec getCommand = CommandLine.Model.CommandSpec.create(); + getCommand.addPositional(CommandLine.Model.PositionalParamSpec.builder() + .index("0") + // .type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type? + .description("Primary key value from the table") + .build()); + + return getCommand; + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private CommandLine.Model.CommandSpec defineCountCommand(QTableMetaData table) + { + CommandLine.Model.CommandSpec countCommand = CommandLine.Model.CommandSpec.create(); + countCommand.addOption(CommandLine.Model.OptionSpec.builder("-c", "--criteria") + .type(String[].class) + .build()); + + // todo - add the fields as explicit params? + + return countCommand; + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 8177b6df..e34de75d 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -23,7 +23,9 @@ package com.kingsrook.qqq.frontend.picocli; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Serializable; @@ -35,6 +37,7 @@ import java.util.Map; import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction; import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction; import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction; +import com.kingsrook.qqq.backend.core.actions.reporting.ReportAction; import com.kingsrook.qqq.backend.core.actions.tables.CountAction; import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; @@ -46,12 +49,16 @@ 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.exceptions.QUserFacingException; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput; import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataInput; import com.kingsrook.qqq.backend.core.model.actions.metadata.TableMetaDataOutput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput; import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput; +import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat; +import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput; +import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportOutput; import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput; import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput; @@ -75,7 +82,9 @@ import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface; import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import com.kingsrook.qqq.backend.core.utils.StringUtils; import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.core.config.Configurator; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.OptionSpec; @@ -94,10 +103,10 @@ import picocli.CommandLine.UnmatchedArgumentException; *******************************************************************************/ public class QPicoCliImplementation { - public static final int DEFAULT_LIMIT = 20; + public static final int DEFAULT_QUERY_LIMIT = 20; private static QInstance qInstance; - private static QSession session; + private static QSession session; @@ -112,14 +121,14 @@ public class QPicoCliImplementation // parse args to look up metaData and prime instance if(args.length > 0 && args[0].startsWith("--qInstanceJsonFile=")) { - String filePath = args[0].replaceFirst("--.*=", ""); + 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); + int exitCode = qPicoCliImplementation.runCli("qapi", subArgs); System.exit(exitCode); } else @@ -136,6 +145,14 @@ public class QPicoCliImplementation *******************************************************************************/ public QPicoCliImplementation(QInstance qInstance) { + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // use the qqq-picocli log4j config, less the system property log4j.configurationFile was set by the runner // + ////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if(System.getProperty("log4j.configurationFile") == null) + { + Configurator.initialize(null, "qqq-picocli-log4j2.xml"); + } + QPicoCliImplementation.qInstance = qInstance; } @@ -232,7 +249,7 @@ public class QPicoCliImplementation private static void setupSession(String[] args) throws QModuleDispatchException { QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher(); - QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication()); + QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication()); // todo - does this need some per-provider logic actually? mmm... Map authenticationContext = new HashMap<>(); @@ -254,7 +271,7 @@ public class QPicoCliImplementation else { ParseResult subParseResult = parseResult.subcommand(); - String subCommandName = subParseResult.commandSpec().name(); + String subCommandName = subParseResult.commandSpec().name(); CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName); switch(subCommandName) { @@ -285,7 +302,7 @@ public class QPicoCliImplementation if(tableParseResult.hasSubcommand()) { ParseResult subParseResult = tableParseResult.subcommand(); - String subCommandName = subParseResult.commandSpec().name(); + String subCommandName = subParseResult.commandSpec().name(); switch(subCommandName) { case "meta-data": @@ -296,10 +313,19 @@ public class QPicoCliImplementation { return runTableCount(commandLine, tableName, subParseResult); } + case "get": + { + CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName); + return runTableGet(commandLine, tableName, subParseResult, subCommandLine); + } case "query": { return runTableQuery(commandLine, tableName, subParseResult); } + case "export": + { + return runTableExport(commandLine, tableName, subParseResult); + } case "insert": { return runTableInsert(commandLine, tableName, subParseResult); @@ -352,7 +378,7 @@ public class QPicoCliImplementation /////////////////////////////////////////// // move on to running the actual process // /////////////////////////////////////////// - String subCommandName = subParseResult.subcommand().commandSpec().name(); + String subCommandName = subParseResult.subcommand().commandSpec().name(); CommandLine subCommandLine = commandLine.getSubcommands().get(subCommandName); return runActualProcess(subCommandLine, subParseResult.subcommand()); } @@ -365,9 +391,9 @@ public class QPicoCliImplementation *******************************************************************************/ private int runActualProcess(CommandLine subCommandLine, ParseResult processParseResult) { - String processName = processParseResult.commandSpec().name(); - QProcessMetaData process = qInstance.getProcess(processName); - RunProcessInput request = new RunProcessInput(qInstance); + String processName = processParseResult.commandSpec().name(); + QProcessMetaData process = qInstance.getProcess(processName); + RunProcessInput request = new RunProcessInput(qInstance); request.setSession(session); request.setProcessName(processName); @@ -443,6 +469,49 @@ public class QPicoCliImplementation + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableGet(CommandLine commandLine, String tableName, ParseResult subParseResult, CommandLine subCommandLine) throws QException + { + QueryInput queryInput = new QueryInput(qInstance); + queryInput.setSession(session); + queryInput.setTableName(tableName); + queryInput.setSkip(subParseResult.matchedOptionValue("skip", null)); + queryInput.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_QUERY_LIMIT)); + String primaryKeyValue = subParseResult.matchedPositionalValue(0, null); + + if(primaryKeyValue == null) + { + subCommandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + + QTableMetaData table = queryInput.getTable(); + QQueryFilter filter = new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName(table.getPrimaryKeyField()) + .withOperator(QCriteriaOperator.EQUALS) + .withValues(List.of(primaryKeyValue))); + queryInput.setFilter(filter); + + QueryAction queryAction = new QueryAction(); + QueryOutput queryOutput = queryAction.execute(queryInput); + List records = queryOutput.getRecords(); + if(records.isEmpty()) + { + commandLine.getOut().println("No " + table.getLabel() + " found for " + table.getField(table.getPrimaryKeyField()).getLabel() + ": " + primaryKeyValue); + return commandLine.getCommandSpec().exitCodeOnInvalidInput(); + } + else + { + commandLine.getOut().println(JsonUtils.toPrettyJson(records.get(0))); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -452,7 +521,7 @@ public class QPicoCliImplementation queryInput.setSession(session); queryInput.setTableName(tableName); queryInput.setSkip(subParseResult.matchedOptionValue("skip", null)); - queryInput.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_LIMIT)); + queryInput.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_QUERY_LIMIT)); queryInput.setFilter(generateQueryFilter(subParseResult)); QueryAction queryAction = new QueryAction(); @@ -463,6 +532,77 @@ public class QPicoCliImplementation + /******************************************************************************* + ** + *******************************************************************************/ + private int runTableExport(CommandLine commandLine, String tableName, ParseResult subParseResult) throws QException + { + String filename = subParseResult.matchedOptionValue("--filename", ""); + + ///////////////////////////////////////////////////////////////////////////////////////// + // if a format query param wasn't given, then try to get file extension from file name // + ///////////////////////////////////////////////////////////////////////////////////////// + ReportFormat reportFormat; + if(filename.contains(".")) + { + reportFormat = ReportFormat.fromString(filename.substring(filename.lastIndexOf(".") + 1)); + } + else + { + throw (new QUserFacingException("File name did not contain an extension, so report format could not be inferred.")); + } + + OutputStream outputStream; + try + { + outputStream = new FileOutputStream(filename); + } + catch(Exception e) + { + throw (new QException("Error opening report file: " + e.getMessage(), e)); + } + + try + { + ///////////////////////////////////////////// + // set up the report action's input object // + ///////////////////////////////////////////// + ReportInput reportInput = new ReportInput(qInstance); + reportInput.setSession(session); + reportInput.setTableName(tableName); + reportInput.setReportFormat(reportFormat); + reportInput.setFilename(filename); + reportInput.setReportOutputStream(outputStream); + reportInput.setLimit(subParseResult.matchedOptionValue("limit", null)); + + reportInput.setQueryFilter(generateQueryFilter(subParseResult)); + + String fieldNames = subParseResult.matchedOptionValue("--fieldNames", ""); + if(StringUtils.hasContent(fieldNames)) + { + reportInput.setFieldNames(Arrays.asList(fieldNames.split(","))); + } + + ReportOutput reportOutput = new ReportAction().execute(reportInput); + + commandLine.getOut().println("Wrote " + reportOutput.getRecordCount() + " records to file " + filename); + return commandLine.getCommandSpec().exitCodeOnSuccess(); + } + finally + { + try + { + outputStream.close(); + } + catch(IOException e) + { + throw (new QException("Error closing report file", e)); + } + } + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -474,7 +614,7 @@ public class QPicoCliImplementation for(String criterion : criteria) { // todo - parse! - String[] parts = criterion.split(" "); + String[] parts = criterion.split(" "); QFilterCriteria qQueryCriteria = new QFilterCriteria(); qQueryCriteria.setFieldName(parts[0]); qQueryCriteria.setOperator(QCriteriaOperator.valueOf(parts[1])); @@ -532,7 +672,7 @@ public class QPicoCliImplementation try { String path = subParseResult.matchedOptionValue("--csvFile", ""); - String csv = FileUtils.readFileToString(new File(path)); + String csv = FileUtils.readFileToString(new File(path)); recordList = new CsvToQRecordAdapter().buildRecordsFromCsv(csv, table, mapping); } catch(IOException e) @@ -589,7 +729,7 @@ public class QPicoCliImplementation boolean anyFields = false; - String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); + String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); Serializable[] primaryKeyValues = primaryKeyOption.split(","); for(Serializable primaryKeyValue : primaryKeyValues) { @@ -638,7 +778,7 @@ public class QPicoCliImplementation ///////////////////////////////////////////// // get the pKeys that the user specified // ///////////////////////////////////////////// - String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); + String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); Serializable[] primaryKeyValues = primaryKeyOption.split(","); deleteInput.setPrimaryKeys(Arrays.asList(primaryKeyValues)); diff --git a/src/main/resources/qqq-picocli-log4j2.xml b/src/main/resources/qqq-picocli-log4j2.xml new file mode 100644 index 00000000..5b03a388 --- /dev/null +++ b/src/main/resources/qqq-picocli-log4j2.xml @@ -0,0 +1,18 @@ + + + + %date{ISO8601} | %relative | %level | %threadName{1} | %logger{1}.%method | %message%n + + + + + + + + + + + + + + diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index 8e14abd8..9f0f53fb 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -31,6 +31,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.UUID; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.utils.JsonUtils; @@ -40,6 +41,7 @@ import org.json.JSONArray; import org.json.JSONObject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -226,7 +228,7 @@ class QPicoCliImplementationTest int count = countResult.getInt("count"); assertEquals(4, count); - testOutput = testCli("person", "count", "--criteria", "id EQUALS 3"); + testOutput = testCli("person", "count", "--criteria", "id EQUALS 3"); countResult = JsonUtils.toJSONObject(testOutput.getOutput()); assertNotNull(countResult); count = countResult.getInt("count"); @@ -254,6 +256,49 @@ class QPicoCliImplementationTest + /******************************************************************************* + ** test running a "get single record" action (singleton query) on a table + ** + *******************************************************************************/ + @Test + public void test_tableGetNoIdGiven() + { + TestOutput testOutput = testCli("person", "get"); + assertTestOutputContains(testOutput, "Usage: " + CLI_NAME + " person get PARAM"); + assertTestOutputContains(testOutput, "Primary key value from the table"); + } + + + + /******************************************************************************* + ** test running a "get single record" action (singleton query) on a table + ** + *******************************************************************************/ + @Test + public void test_tableGet() + { + TestOutput testOutput = testCli("person", "get", "1"); + JSONObject getResult = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(getResult); + assertEquals(1, getResult.getJSONObject("values").getInt("id")); + assertEquals("Darin", getResult.getJSONObject("values").getString("firstName")); + } + + + + /******************************************************************************* + ** test running a "get single record" action (singleton query) on a table + ** + *******************************************************************************/ + @Test + public void test_tableGetMissingId() + { + TestOutput testOutput = testCli("person", "get", "1976"); + assertTestOutputContains(testOutput, "No Person found for Id: 1976"); + } + + + /******************************************************************************* ** test running an insert w/o specifying any fields, prints usage ** @@ -422,6 +467,9 @@ class QPicoCliImplementationTest + /******************************************************************************* + ** + *******************************************************************************/ private void assertRowValueById(String tableName, String columnName, String value, Integer id) throws Exception { TestUtils.runTestSql("SELECT " + columnName + " FROM " + tableName + " WHERE id=" + id, (rs -> { @@ -470,7 +518,7 @@ class QPicoCliImplementationTest ** *******************************************************************************/ @Test - public void test_tableProcess() throws Exception + public void test_tableProcess() { TestOutput testOutput = testCli("person", "process"); @@ -487,7 +535,7 @@ class QPicoCliImplementationTest ** *******************************************************************************/ @Test - public void test_tableProcessUnknownName() throws Exception + public void test_tableProcessUnknownName() { String badProcessName = "not-a-process"; TestOutput testOutput = testCli("person", "process", badProcessName); @@ -502,7 +550,7 @@ class QPicoCliImplementationTest ** *******************************************************************************/ @Test - public void test_tableProcessGreetUsingCallbackForFields() throws Exception + public void test_tableProcessGreetUsingCallbackForFields() { setStandardInputLines("Hi", "How are you?"); TestOutput testOutput = testCli("person", "process", "greet"); @@ -511,12 +559,216 @@ class QPicoCliImplementationTest } + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportNoArgsExcel() + { + String filename = "/tmp/" + UUID.randomUUID() + ".xlsx"; + TestOutput testOutput = testCli("person", "export", "--filename=" + filename); + assertTestOutputContains(testOutput, "Wrote 5 records to file " + filename); + + File file = new File(filename); + assertTrue(file.exists()); + + // todo - some day when we learn to read Excel, assert that we wrote as expected. + + deleteFile(file); + } + + + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportWithLimit() throws Exception + { + String filename = "/tmp/" + UUID.randomUUID() + ".csv"; + TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--limit=3"); + assertTestOutputContains(testOutput, "Wrote 3 records to file " + filename); + + File file = new File(filename); + @SuppressWarnings("unchecked") + List list = FileUtils.readLines(file); + assertEquals(4, list.size()); + assertThat(list.get(0)).contains(""" + "Id","Create Date","Modify Date\""""); + assertThat(list.get(1)).matches(""" + ^"1",.*"Darin.*"""); + assertThat(list.get(3)).matches(""" + ^"3",.*"Tim.*"""); + + deleteFile(file); + } + + + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportWithCriteria() throws Exception + { + String filename = "/tmp/" + UUID.randomUUID() + ".csv"; + TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--criteria", "id NOT_EQUALS 3"); + assertTestOutputContains(testOutput, "Wrote 4 records to file " + filename); + + File file = new File(filename); + @SuppressWarnings("unchecked") + List list = FileUtils.readLines(file); + assertEquals(5, list.size()); + assertThat(list.get(0)).contains(""" + "Id","Create Date","Modify Date\""""); + assertThat(list.get(1)).matches("^\"1\",.*"); + assertThat(list.get(2)).matches("^\"2\",.*"); + assertThat(list.get(3)).matches("^\"4\",.*"); + assertThat(list.get(4)).matches("^\"5\",.*"); + + deleteFile(file); + } + + + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportWithoutFilename() + { + TestOutput testOutput = testCli("person", "export"); + assertTestErrorContains(testOutput, "Missing required option: '--filename=PARAM'"); + assertTestErrorContains(testOutput, "Usage: " + CLI_NAME + " person export"); + assertTestErrorContains(testOutput, "-f=PARAM"); + } + + + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportNoFileExtension() + { + String filename = "/tmp/" + UUID.randomUUID(); + TestOutput testOutput = testCli("person", "export", "--filename=" + filename); + assertTestErrorContains(testOutput, "File name did not contain an extension"); + } + + + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportBadFileType() + { + String filename = "/tmp/" + UUID.randomUUID() + ".docx"; + TestOutput testOutput = testCli("person", "export", "--filename=" + filename); + assertTestErrorContains(testOutput, "Unsupported report format: docx."); + } + + + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportBadFilePath() + { + String filename = "/no-such/directory/" + UUID.randomUUID() + "report.csv"; + TestOutput testOutput = testCli("person", "export", "--filename=" + filename); + assertTestErrorContains(testOutput, "No such file or directory"); + } + + + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportBadFieldNams() + { + String filename = "/tmp/" + UUID.randomUUID() + ".csv"; + TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--fieldNames=foo"); + assertTestErrorContains(testOutput, "Field name foo was not found on the Person table"); + } + + + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportBadFieldNames() + { + String filename = "/tmp/" + UUID.randomUUID() + ".csv"; + TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--fieldNames=foo,bar,baz"); + assertTestErrorContains(testOutput, "Fields names foo, bar, and baz were not found on the Person table"); + } + + + + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportGoodFieldNamesXslx() throws IOException + { + String filename = "/tmp/" + UUID.randomUUID() + ".xlsx"; + TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--fieldNames=id,lastName,birthDate"); + + File file = new File(filename); + assertTrue(file.exists()); + + // todo - some day when we learn to read Excel, assert that we wrote as expected (with 3 columns) + + deleteFile(file); + } + + /******************************************************************************* + ** test exporting a table + ** + *******************************************************************************/ + @Test + public void test_tableExportGoodFieldNamesCSV() throws IOException + { + String filename = "/tmp/" + UUID.randomUUID() + ".csv"; + TestOutput testOutput = testCli("person", "export", "--filename=" + filename, "--fieldNames=id,lastName,birthDate"); + + File file = new File(filename); + @SuppressWarnings("unchecked") + List list = FileUtils.readLines(file); + assertEquals(6, list.size()); + assertThat(list.get(0)).isEqualTo(""" + "Id","Last Name","Birth Date\""""); + assertThat(list.get(1)).isEqualTo(""" + "1","Kelkhoff","1980-05-31\""""); + + deleteFile(file); + } + + + /******************************************************************************* ** test running a process on a table ** *******************************************************************************/ @Test - public void test_tableProcessGreetUsingOptionsForFields() throws Exception + public void test_tableProcessGreetUsingOptionsForFields() { TestOutput testOutput = testCli("person", "process", "greet", "--field-greetingPrefix=Hello", "--field-greetingSuffix=World"); assertTestOutputDoesNotContain(testOutput, "Please supply a value for the field"); @@ -567,6 +819,16 @@ class QPicoCliImplementationTest + /******************************************************************************* + ** delete a file, asserting that we did so. + *******************************************************************************/ + private void deleteFile(File file) + { + assertTrue(file.delete()); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -628,118 +890,4 @@ class QPicoCliImplementationTest System.setIn(stdin); } - - - /******************************************************************************* - ** - *******************************************************************************/ - private static class TestOutput - { - private String output; - private String[] outputLines; - private String error; - private String[] errorLines; - - - - /******************************************************************************* - ** - *******************************************************************************/ - public TestOutput(String output, String error) - { - this.output = output; - this.error = error; - - this.outputLines = output.split("\n"); - this.errorLines = error.split("\n"); - } - - - - /******************************************************************************* - ** Getter for output - ** - *******************************************************************************/ - public String getOutput() - { - return output; - } - - - - /******************************************************************************* - ** Setter for output - ** - *******************************************************************************/ - public void setOutput(String output) - { - this.output = output; - } - - - - /******************************************************************************* - ** Getter for outputLines - ** - *******************************************************************************/ - public String[] getOutputLines() - { - return outputLines; - } - - - - /******************************************************************************* - ** Setter for outputLines - ** - *******************************************************************************/ - public void setOutputLines(String[] outputLines) - { - this.outputLines = outputLines; - } - - - - /******************************************************************************* - ** Getter for error - ** - *******************************************************************************/ - public String getError() - { - return error; - } - - - - /******************************************************************************* - ** Setter for error - ** - *******************************************************************************/ - public void setError(String error) - { - this.error = error; - } - - - - /******************************************************************************* - ** Getter for errorLines - ** - *******************************************************************************/ - public String[] getErrorLines() - { - return errorLines; - } - - - - /******************************************************************************* - ** Setter for errorLines - ** - *******************************************************************************/ - public void setErrorLines(String[] errorLines) - { - this.errorLines = errorLines; - } - } } diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestOutput.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestOutput.java new file mode 100644 index 00000000..fcbb76d8 --- /dev/null +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestOutput.java @@ -0,0 +1,136 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.frontend.picocli; + + +/******************************************************************************* + ** + *******************************************************************************/ +class TestOutput +{ + private String output; + private String[] outputLines; + private String error; + private String[] errorLines; + + + + /******************************************************************************* + ** + *******************************************************************************/ + public TestOutput(String output, String error) + { + this.output = output; + this.error = error; + + this.outputLines = output.split("\n"); + this.errorLines = error.split("\n"); + } + + + + /******************************************************************************* + ** Getter for output + ** + *******************************************************************************/ + public String getOutput() + { + return output; + } + + + + /******************************************************************************* + ** Setter for output + ** + *******************************************************************************/ + public void setOutput(String output) + { + this.output = output; + } + + + + /******************************************************************************* + ** Getter for outputLines + ** + *******************************************************************************/ + public String[] getOutputLines() + { + return outputLines; + } + + + + /******************************************************************************* + ** Setter for outputLines + ** + *******************************************************************************/ + public void setOutputLines(String[] outputLines) + { + this.outputLines = outputLines; + } + + + + /******************************************************************************* + ** Getter for error + ** + *******************************************************************************/ + public String getError() + { + return error; + } + + + + /******************************************************************************* + ** Setter for error + ** + *******************************************************************************/ + public void setError(String error) + { + this.error = error; + } + + + + /******************************************************************************* + ** Getter for errorLines + ** + *******************************************************************************/ + public String[] getErrorLines() + { + return errorLines; + } + + + + /******************************************************************************* + ** Setter for errorLines + ** + *******************************************************************************/ + public void setErrorLines(String[] errorLines) + { + this.errorLines = errorLines; + } +} From b14f96ef6c5a77eaa85c4f38c445d80bd299142e Mon Sep 17 00:00:00 2001 From: Tim Chamberlain Date: Tue, 19 Jul 2022 18:28:09 -0500 Subject: [PATCH 41/49] QQQ-27: updates to allow Auth0 to be an authentication model in picocli --- pom.xml | 11 +++ .../picocli/QPicoCliImplementation.java | 69 +++++++++++++++++-- .../qqq/frontend/picocli/TestUtils.java | 5 +- 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 59ce218d..9cf6787a 100644 --- a/pom.xml +++ b/pom.xml @@ -62,12 +62,23 @@ test + info.picocli picocli 4.6.1 + + info.picocli + picocli-shell-jline3 + 4.6.3 + + + io.github.cdimascio + java-dotenv + 5.2.2 + com.h2database h2 diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 8177b6df..feecd243 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction; import com.kingsrook.qqq.backend.core.actions.metadata.TableMetaDataAction; import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction; @@ -44,6 +45,7 @@ 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.QAuthenticationException; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException; import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput; @@ -72,10 +74,15 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.session.QSession; +import com.kingsrook.qqq.backend.core.modules.authentication.Auth0AuthenticationModule; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher; import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface; import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import io.github.cdimascio.dotenv.Dotenv; import org.apache.commons.io.FileUtils; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.utils.Log; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.OptionSpec; @@ -229,15 +236,65 @@ public class QPicoCliImplementation /******************************************************************************* ** *******************************************************************************/ - private static void setupSession(String[] args) throws QModuleDispatchException + private static Optional loadDotEnv() + { + Optional dotenvOptional = Optional.empty(); + try + { + dotenvOptional = Optional.of(Dotenv.configure().load()); + } + catch(Exception e) + { + Log.info("No session information found in environment"); + } + + return(dotenvOptional); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static void setupSession(String[] args) throws QModuleDispatchException, QAuthenticationException { QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher(); QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication()); - // todo - does this need some per-provider logic actually? mmm... - Map authenticationContext = new HashMap<>(); - authenticationContext.put("sessionId", System.getenv("sessionId")); - session = authenticationModule.createSession(authenticationContext); + try + { + //////////////////////////////////// + // look for .env environment file // + //////////////////////////////////// + String sessionId = null; + Optional dotenv = loadDotEnv(); + if(dotenv.isPresent()) + { + sessionId = dotenv.get().get("SESSION_ID"); + } + + Map authenticationContext = new HashMap<>(); + if(sessionId == null && authenticationModule instanceof Auth0AuthenticationModule) + { + LineReader lr = LineReaderBuilder.builder().build(); + String tokenId = lr.readLine("Create a .env file with the contents of the Auth0 JWT Id Token in the variable 'SESSION_ID': \nPress enter once complete..."); + dotenv = loadDotEnv(); + if(dotenv.isPresent()) + { + sessionId = dotenv.get().get("SESSION_ID"); + } + } + + authenticationContext.put("sessionId", sessionId); + + // todo - does this need some per-provider logic actually? mmm... + session = authenticationModule.createSession(qInstance, authenticationContext); + } + catch(QAuthenticationException qae) + { + throw (qae); + } + } @@ -367,7 +424,7 @@ public class QPicoCliImplementation { String processName = processParseResult.commandSpec().name(); QProcessMetaData process = qInstance.getProcess(processName); - RunProcessInput request = new RunProcessInput(qInstance); + RunProcessInput request = new RunProcessInput(qInstance); request.setSession(session); request.setProcessName(processName); diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java index 926cc972..434b9d39 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java @@ -25,8 +25,9 @@ package com.kingsrook.qqq.frontend.picocli; import java.io.InputStream; import java.sql.Connection; import java.util.List; +import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType; import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep; -import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationMetaData; +import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage; @@ -112,7 +113,7 @@ public class TestUtils { return new QAuthenticationMetaData() .withName("mock") - .withType("mock"); + .withType(QAuthenticationType.MOCK); } From 59211717a02d328ddc16cf13014762812da3b8c2 Mon Sep 17 00:00:00 2001 From: Tim Chamberlain Date: Thu, 21 Jul 2022 11:54:13 -0500 Subject: [PATCH 42/49] QQQ-27: upped qqq-backend-core / rdbms versions --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9cf6787a..60c9fb95 100644 --- a/pom.xml +++ b/pom.xml @@ -53,12 +53,12 @@ com.kingsrook.qqq qqq-backend-core - 0.2.0-SNAPSHOT + 0.2.0-20220721.162748-8 com.kingsrook.qqq qqq-backend-module-rdbms - 0.2.0-SNAPSHOT + 0.2.0-20220721.162748-8 test From 984a650d8c16e453a75352e6776237232857dbc8 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 26 Jul 2022 16:49:42 -0500 Subject: [PATCH 43/49] Update deps --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 82a38d84..0001bcf5 100644 --- a/pom.xml +++ b/pom.xml @@ -53,12 +53,12 @@ com.kingsrook.qqq qqq-backend-core - 0.2.0-20220725.183211-13 + 0.2.0-20220726.214150-15 com.kingsrook.qqq qqq-backend-module-rdbms - 0.2.0-20220725.183409-11 + 0.2.0-20220726.214633-12 test From f17514c608ca80ff8dd5b832bfe44bed45cbfb3f Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 27 Jul 2022 09:15:47 -0500 Subject: [PATCH 44/49] QQQ-28 implement bulk edit, delete, insert --- .../qqq/frontend/picocli/QCommandBuilder.java | 54 +++++-- .../picocli/QPicoCliImplementation.java | 134 ++++++++++++++---- .../picocli/QPicoCliImplementationTest.java | 81 ++++++++++- 3 files changed, 230 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java index 6d2fdff1..415922c3 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java @@ -179,10 +179,7 @@ public class QCommandBuilder .type(int.class) .description("Optional limit on the max number of records to include in the export.") .build()); - exportCommand.addOption(CommandLine.Model.OptionSpec.builder("-c", "--criteria") - .type(String[].class) - .description("Query filter criteria for the export. May be given multiple times. Use format: $fieldName $operator $value. e.g., id EQUALS 42") - .build()); + addCriteriaOption(exportCommand); // todo - add the fields as explicit params? @@ -191,6 +188,38 @@ public class QCommandBuilder + /******************************************************************************* + ** add the standard '--criteria' option + *******************************************************************************/ + private void addCriteriaOption(CommandLine.Model.CommandSpec commandSpec) + { + commandSpec.addOption(CommandLine.Model.OptionSpec.builder("-c", "--criteria") + .type(String[].class) + .description(""" + Query filter criteria. May be given multiple times. + Use format: "$fieldName $operator $value". + e.g., "id EQUALS 42\"""") + .build()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void addPrimaryKeyOrKeysOption(CommandLine.Model.CommandSpec updateCommand, String verbForDescription) + { + 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? + .description(""" + Primary Key(s) for the records to %s. + May provide multiple values, separated by commas""".formatted(verbForDescription)) + .build()); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -214,9 +243,7 @@ public class QCommandBuilder private CommandLine.Model.CommandSpec defineCountCommand(QTableMetaData table) { CommandLine.Model.CommandSpec countCommand = CommandLine.Model.CommandSpec.create(); - countCommand.addOption(CommandLine.Model.OptionSpec.builder("-c", "--criteria") - .type(String[].class) - .build()); + addCriteriaOption(countCommand); // todo - add the fields as explicit params? @@ -233,6 +260,7 @@ public class QCommandBuilder CommandLine.Model.CommandSpec updateCommand = CommandLine.Model.CommandSpec.create(); /* + todo - future may accept files, similar to (bulk) insert updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--jsonBody") .type(String.class) .build()); @@ -251,10 +279,7 @@ public class QCommandBuilder */ 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()); + addPrimaryKeyOrKeysOption(updateCommand, "update"); for(QFieldMetaData field : table.getFields().values()) { @@ -262,9 +287,14 @@ public class QCommandBuilder { updateCommand.addOption(CommandLine.Model.OptionSpec.builder("--field-" + field.getName()) .type(getClassForField(field)) + .description(""" + Value to set for the field %s""".formatted(field.getName())) .build()); } } + + addCriteriaOption(updateCommand); + return updateCommand; } @@ -315,6 +345,8 @@ public class QCommandBuilder .type(String.class) // todo - mmm, better as picocli's "compound" thing, w/ the actual pkey's type? .build()); + addCriteriaOption(deleteCommand); + return deleteCommand; } diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index 5dd1835f..e518355a 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -62,6 +62,7 @@ import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput; import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportOutput; import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.AbstractQFieldMapping; +import com.kingsrook.qqq.backend.core.model.actions.shared.mapping.QKeyBasedFieldMapping; 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.delete.DeleteInput; @@ -265,7 +266,7 @@ public class QPicoCliImplementation Log.info("No session information found in environment"); } - return(dotenvOptional); + return (dotenvOptional); } @@ -283,8 +284,8 @@ public class QPicoCliImplementation //////////////////////////////////// // look for .env environment file // //////////////////////////////////// - String sessionId = null; - Optional dotenv = loadDotEnv(); + String sessionId = null; + Optional dotenv = loadDotEnv(); if(dotenv.isPresent()) { sessionId = dotenv.get().get("SESSION_ID"); @@ -293,8 +294,8 @@ public class QPicoCliImplementation Map authenticationContext = new HashMap<>(); if(sessionId == null && authenticationModule instanceof Auth0AuthenticationModule) { - LineReader lr = LineReaderBuilder.builder().build(); - String tokenId = lr.readLine("Create a .env file with the contents of the Auth0 JWT Id Token in the variable 'SESSION_ID': \nPress enter once complete..."); + LineReader lr = LineReaderBuilder.builder().build(); + String tokenId = lr.readLine("Create a .env file with the contents of the Auth0 JWT Id Token in the variable 'SESSION_ID': \nPress enter once complete..."); dotenv = loadDotEnv(); if(dotenv.isPresent()) { @@ -701,6 +702,14 @@ public class QPicoCliImplementation String json = subParseResult.matchedOptionValue("--mapping", ""); mapping = new JsonToQFieldMappingAdapter().buildMappingFromJson(json); } + else + { + mapping = new QKeyBasedFieldMapping(); + for(Map.Entry entry : table.getFields().entrySet()) + { + ((QKeyBasedFieldMapping) mapping).addMapping(entry.getKey(), entry.getValue().getLabel()); + } + } ///////////////////////////////////////////// // get the records that the user specified // @@ -782,38 +791,81 @@ public class QPicoCliImplementation updateInput.setTableName(tableName); QTableMetaData table = qInstance.getTable(tableName); - List recordList = new ArrayList<>(); + List recordsToUpdate = new ArrayList<>(); + boolean anyFields = false; - boolean anyFields = false; + String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); + String[] criteria = subParseResult.matchedOptionValue("criteria", new String[] {}); - String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); - Serializable[] primaryKeyValues = primaryKeyOption.split(","); - for(Serializable primaryKeyValue : primaryKeyValues) + if(StringUtils.hasContent(primaryKeyOption)) { - QRecord record = new QRecord(); + ////////////////////////////////////////////////////////////////////////////////////// + // if the primaryKey option was given, split it up and seed the recordToUpdate list // + ////////////////////////////////////////////////////////////////////////////////////// + Serializable[] primaryKeyValues = primaryKeyOption.split(","); + for(Serializable primaryKeyValue : primaryKeyValues) + { + recordsToUpdate.add(new QRecord().withValue(table.getPrimaryKeyField(), primaryKeyValue)); + } + } + else if(criteria.length > 0) + { + ////////////////////////////////////////////////////////////////////////////////////// + // else if criteria were given, execute the query for the lsit of records to update // + ////////////////////////////////////////////////////////////////////////////////////// + for(QRecord qRecord : executeQuery(tableName, subParseResult)) + { + recordsToUpdate.add(new QRecord().withValue(table.getPrimaryKeyField(), qRecord.getValue(table.getPrimaryKeyField()))); + } + } + else + { + commandLine.getErr().println("Error: Either primaryKey or criteria must be specified."); + CommandLine subCommandLine = commandLine.getSubcommands().get("update"); + subCommandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } - recordList.add(record); - record.setValue(table.getPrimaryKeyField(), primaryKeyValue); + /////////////////////////////////////////////////// + // make sure at least one --field- arg was given // + /////////////////////////////////////////////////// + for(OptionSpec matchedOption : subParseResult.matchedOptions()) + { + if(matchedOption.longestName().startsWith("--field-")) + { + anyFields = true; + } + } + if(!anyFields) + { + commandLine.getErr().println("Error: At least one field to update must be specified."); + CommandLine subCommandLine = commandLine.getSubcommands().get("update"); + subCommandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + + if(recordsToUpdate.isEmpty()) + { + commandLine.getErr().println("No rows to update were found."); + CommandLine subCommandLine = commandLine.getSubcommands().get("update"); + subCommandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } + + for(QRecord record : recordsToUpdate) + { 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(); - } - - updateInput.setRecords(recordList); + updateInput.setRecords(recordsToUpdate); UpdateAction updateAction = new UpdateAction(); UpdateOutput updateResult = updateAction.execute(updateInput); @@ -835,9 +887,24 @@ public class QPicoCliImplementation ///////////////////////////////////////////// // get the pKeys that the user specified // ///////////////////////////////////////////// - String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); - Serializable[] primaryKeyValues = primaryKeyOption.split(","); - deleteInput.setPrimaryKeys(Arrays.asList(primaryKeyValues)); + String primaryKeyOption = subParseResult.matchedOptionValue("--primaryKey", ""); + String[] criteria = subParseResult.matchedOptionValue("criteria", new String[] {}); + + if(StringUtils.hasContent(primaryKeyOption)) + { + deleteInput.setPrimaryKeys(Arrays.asList(primaryKeyOption.split(","))); + } + else if(criteria.length > 0) + { + deleteInput.setQueryFilter(generateQueryFilter(subParseResult)); + } + else + { + commandLine.getErr().println("Error: Either primaryKey or criteria must be specified."); + CommandLine subCommandLine = commandLine.getSubcommands().get("delete"); + subCommandLine.usage(commandLine.getOut()); + return commandLine.getCommandSpec().exitCodeOnUsageHelp(); + } DeleteAction deleteAction = new DeleteAction(); DeleteOutput deleteResult = deleteAction.execute(deleteInput); @@ -865,4 +932,21 @@ public class QPicoCliImplementation commandLine.usage(commandLine.getOut()); return commandLine.getCommandSpec().exitCodeOnUsageHelp(); } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private List executeQuery(String tableName, ParseResult subParseResult) throws QException + { + QueryInput queryInput = new QueryInput(qInstance); + queryInput.setSession(session); + queryInput.setTableName(tableName); + queryInput.setFilter(generateQueryFilter(subParseResult)); + + QueryAction queryAction = new QueryAction(); + QueryOutput queryOutput = queryAction.execute(queryInput); + return (queryOutput.getRecords()); + } } diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index cc70e5e5..73671010 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -447,11 +447,24 @@ class QPicoCliImplementationTest /******************************************************************************* - ** test running an update w/ fields as arguments + ** test running an update w/o specifying any pkeys or criteria, prints usage ** *******************************************************************************/ @Test - public void test_tableUpdateFieldArguments() throws Exception + public void test_tableUpdateNoRecordsPrintsUsage() + { + TestOutput testOutput = testCli("person", "update", "--field-firstName=Lucy"); + assertTestOutputContains(testOutput, "Usage: " + CLI_NAME + " person update"); + } + + + + /******************************************************************************* + ** test running an update w/ fields as arguments and one primary key + ** + *******************************************************************************/ + @Test + public void test_tableUpdateFieldArgumentsOnePrimaryKey() throws Exception { assertRowValueById("person", "first_name", "Garret", 5); TestOutput testOutput = testCli("person", "update", @@ -467,6 +480,55 @@ class QPicoCliImplementationTest + /******************************************************************************* + ** test running an update w/ fields as arguments and multiple primary keys + ** + *******************************************************************************/ + @Test + public void test_tableUpdateFieldArgumentsManyPrimaryKeys() throws Exception + { + assertRowValueById("person", "first_name", "Tyler", 4); + assertRowValueById("person", "first_name", "Garret", 5); + TestOutput testOutput = testCli("person", "update", + "--primaryKey=4,5", + "--field-firstName=Lucy", + "--field-lastName=Lu"); + JSONObject updateResult = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(updateResult); + assertEquals(2, updateResult.getJSONArray("records").length()); + assertEquals(4, updateResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id")); + assertEquals(5, updateResult.getJSONArray("records").getJSONObject(1).getJSONObject("values").getInt("id")); + assertRowValueById("person", "first_name", "Lucy", 4); + assertRowValueById("person", "first_name", "Lucy", 5); + } + + + + /******************************************************************************* + ** test running an update w/ fields as arguments and a criteria + ** + *******************************************************************************/ + @Test + public void test_tableUpdateFieldArgumentsCriteria() throws Exception + { + assertRowValueById("person", "first_name", "Tyler", 4); + assertRowValueById("person", "first_name", "Garret", 5); + TestOutput testOutput = testCli("person", "update", + "--criteria", + "id GREATER_THAN_OR_EQUALS 4", + "--field-firstName=Lucy", + "--field-lastName=Lu"); + JSONObject updateResult = JsonUtils.toJSONObject(testOutput.getOutput()); + assertNotNull(updateResult); + assertEquals(2, updateResult.getJSONArray("records").length()); + assertEquals(4, updateResult.getJSONArray("records").getJSONObject(0).getJSONObject("values").getInt("id")); + assertEquals(5, updateResult.getJSONArray("records").getJSONObject(1).getJSONObject("values").getInt("id")); + assertRowValueById("person", "first_name", "Lucy", 4); + assertRowValueById("person", "first_name", "Lucy", 5); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -486,6 +548,18 @@ class QPicoCliImplementationTest + /******************************************************************************* + ** test running a delete without enough args + ** + *******************************************************************************/ + @Test + public void test_tableDeleteWithoutArgs() throws Exception + { + TestOutput testOutput = testCli("person", "delete"); + assertTestOutputContains(testOutput, "Usage: " + CLI_NAME + " person delete"); + } + + /******************************************************************************* ** test running a delete against a table ** @@ -717,7 +791,6 @@ class QPicoCliImplementationTest - /******************************************************************************* ** test exporting a table ** @@ -736,6 +809,8 @@ class QPicoCliImplementationTest deleteFile(file); } + + /******************************************************************************* ** test exporting a table ** From e5ea13c2e07559f6fb8c8d948dbf0ec9248d1c4f Mon Sep 17 00:00:00 2001 From: Tim Chamberlain Date: Wed, 27 Jul 2022 16:57:27 -0500 Subject: [PATCH 45/49] QQQ-27: fixed bug when a primary key not specified --- .../kingsrook/qqq/frontend/picocli/QCommandBuilder.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java index 415922c3..505f1340 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java @@ -278,8 +278,12 @@ public class QCommandBuilder .build()); */ - QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField()); - addPrimaryKeyOrKeysOption(updateCommand, "update"); + QFieldMetaData primaryKeyField = null; + if(table.getPrimaryKeyField() != null) + { + primaryKeyField = table.getField(table.getPrimaryKeyField()); + addPrimaryKeyOrKeysOption(updateCommand, "update"); + } for(QFieldMetaData field : table.getFields().values()) { From 2079c82101ef8c2a07e03acfa8ef286ed2290353 Mon Sep 17 00:00:00 2001 From: Tim Chamberlain Date: Wed, 27 Jul 2022 23:45:14 -0500 Subject: [PATCH 46/49] QQQ-27: gitignore .env --- .gitignore | 1 + pom.xml | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index b65cb041..2c7054c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ target/ *.iml +.env ############################################# diff --git a/pom.xml b/pom.xml index 0001bcf5..f5d10a42 100644 --- a/pom.xml +++ b/pom.xml @@ -74,11 +74,6 @@ picocli-shell-jline3 4.6.3 - - io.github.cdimascio - java-dotenv - 5.2.2 - com.h2database h2 From a7e7122fdef1fde920aaf2c565e88997e81d505e Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 28 Jul 2022 09:13:14 -0500 Subject: [PATCH 47/49] Removing limit on query; updating to qqq-backend 0.2.0 --- pom.xml | 4 ++-- .../qqq/frontend/picocli/QPicoCliImplementation.java | 3 +-- .../qqq/frontend/picocli/QPicoCliImplementationTest.java | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index f5d10a42..2c577f31 100644 --- a/pom.xml +++ b/pom.xml @@ -53,12 +53,12 @@ com.kingsrook.qqq qqq-backend-core - 0.2.0-20220726.214150-15 + 0.2.0 com.kingsrook.qqq qqq-backend-module-rdbms - 0.2.0-20220726.214633-12 + 0.2.0 test diff --git a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java index e518355a..e83fe394 100644 --- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java +++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementation.java @@ -536,7 +536,6 @@ public class QPicoCliImplementation queryInput.setSession(session); queryInput.setTableName(tableName); queryInput.setSkip(subParseResult.matchedOptionValue("skip", null)); - queryInput.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_QUERY_LIMIT)); String primaryKeyValue = subParseResult.matchedPositionalValue(0, null); if(primaryKeyValue == null) @@ -579,7 +578,7 @@ public class QPicoCliImplementation queryInput.setSession(session); queryInput.setTableName(tableName); queryInput.setSkip(subParseResult.matchedOptionValue("skip", null)); - queryInput.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_QUERY_LIMIT)); + queryInput.setLimit(subParseResult.matchedOptionValue("limit", null)); queryInput.setFilter(generateQueryFilter(subParseResult)); QueryAction queryAction = new QueryAction(); diff --git a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java index 73671010..12d30a86 100644 --- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java +++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java @@ -491,6 +491,7 @@ class QPicoCliImplementationTest assertRowValueById("person", "first_name", "Garret", 5); TestOutput testOutput = testCli("person", "update", "--primaryKey=4,5", + "--field-birthDate=1980-05-31", "--field-firstName=Lucy", "--field-lastName=Lu"); JSONObject updateResult = JsonUtils.toJSONObject(testOutput.getOutput()); From c2aa82cd8df7c64f2884b94f7ff263274524d9ab Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 28 Jul 2022 09:18:57 -0500 Subject: [PATCH 48/49] Update versions for release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2c577f31..df0fcb18 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ com.kingsrook.qqq qqq-middleware-picocli - 0.2.0-SNAPSHOT + 0.2.0 scm:git:git@github.com:Kingsrook/qqq-middleware-picocli.git From df6c0403bccb58b84fdb816fb6ed7938996662bb Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 28 Jul 2022 09:18:58 -0500 Subject: [PATCH 49/49] Update for next development version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index df0fcb18..07414d4f 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ com.kingsrook.qqq qqq-middleware-picocli - 0.2.0 + 0.3.0-SNAPSHOT scm:git:git@github.com:Kingsrook/qqq-middleware-picocli.git