diff --git a/.circleci/config.yml b/.circleci/config.yml
index 0ef02745..fe4c371c 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -42,7 +42,7 @@ jobs:
executor: java17
steps:
- run_maven:
- maven_subcommand: test
+ maven_subcommand: verify
- slack/notify:
event: fail
diff --git a/checkstyle.xml b/checkstyle.xml
index 76f872ed..f5e7412d 100644
--- a/checkstyle.xml
+++ b/checkstyle.xml
@@ -181,8 +181,8 @@
-->
-
+
+ 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
+ ]]>
+
+
+
+
+
+
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 9d2525df..d2fbed7b 100644
--- a/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java
+++ b/src/main/java/com/kingsrook/qqq/frontend/picocli/QCommandBuilder.java
@@ -90,6 +90,7 @@ public class QCommandBuilder
// add table-specific sub-commands for the table //
///////////////////////////////////////////////////
tableCommand.addSubcommand("meta-data", defineMetaDataCommand(table));
+ tableCommand.addSubcommand("count", defineQueryCommand(table));
tableCommand.addSubcommand("query", defineQueryCommand(table));
tableCommand.addSubcommand("insert", defineInsertCommand(table));
tableCommand.addSubcommand("update", defineUpdateCommand(table));
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 1302cb94..56cd335a 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 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;
@@ -45,6 +46,8 @@ 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;
@@ -94,7 +97,7 @@ public class QPicoCliImplementation
public static final int DEFAULT_LIMIT = 20;
private static QInstance qInstance;
- private static QSession session;
+ private static QSession session;
@@ -109,14 +112,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
@@ -229,7 +232,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<>();
@@ -251,7 +254,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)
{
@@ -282,13 +285,17 @@ 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":
{
return runTableMetaData(commandLine, tableName, subParseResult);
}
+ case "count":
+ {
+ return runTableCount(commandLine, tableName, subParseResult);
+ }
case "query":
{
return runTableQuery(commandLine, tableName, subParseResult);
@@ -345,7 +352,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());
}
@@ -358,9 +365,9 @@ public class QPicoCliImplementation
*******************************************************************************/
private int runActualProcess(CommandLine subCommandLine, ParseResult processParseResult)
{
- String processName = processParseResult.commandSpec().name();
- QProcessMetaData process = qInstance.getProcess(processName);
- RunProcessRequest request = new RunProcessRequest(qInstance);
+ String processName = processParseResult.commandSpec().name();
+ QProcessMetaData process = qInstance.getProcess(processName);
+ RunProcessRequest request = new RunProcessRequest(qInstance);
request.setSession(session);
request.setProcessName(processName);
@@ -384,9 +391,10 @@ public class QPicoCliImplementation
subCommandLine.getOut().format(" %s: %s\n", outputField.getLabel(), result.getValues().get(outputField.getName()));
}
- if(result.getError() != null)
+ if(result.getException().isPresent())
{
- subCommandLine.getOut().println("Process Error message: " + result.getError());
+ // todo - user-facing, similar to javalin
+ subCommandLine.getOut().println("Process Error message: " + result.getException().get().getMessage());
}
}
catch(Exception e)
@@ -417,6 +425,24 @@ 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));
+
+ CountAction countAction = new CountAction();
+ CountResult countResult = countAction.execute(countRequest);
+ commandLine.getOut().println(JsonUtils.toPrettyJson(countResult));
+ return commandLine.getCommandSpec().exitCodeOnSuccess();
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -427,15 +453,28 @@ public class QPicoCliImplementation
queryRequest.setTableName(tableName);
queryRequest.setSkip(subParseResult.matchedOptionValue("skip", null));
queryRequest.setLimit(subParseResult.matchedOptionValue("limit", DEFAULT_LIMIT));
+ queryRequest.setFilter(generateQueryFilter(subParseResult));
+ QueryAction queryAction = new QueryAction();
+ QueryResult queryResult = queryAction.execute(queryRequest);
+ commandLine.getOut().println(JsonUtils.toPrettyJson(queryResult));
+ return commandLine.getCommandSpec().exitCodeOnSuccess();
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private QQueryFilter generateQueryFilter(ParseResult subParseResult)
+ {
QQueryFilter filter = new QQueryFilter();
- queryRequest.setFilter(filter);
String[] criteria = subParseResult.matchedOptionValue("criteria", new String[] {});
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]));
@@ -443,10 +482,7 @@ public class QPicoCliImplementation
filter.addCriteria(qQueryCriteria);
}
- QueryAction queryAction = new QueryAction();
- QueryResult queryResult = queryAction.execute(queryRequest);
- commandLine.getOut().println(JsonUtils.toPrettyJson(queryResult));
- return commandLine.getCommandSpec().exitCodeOnSuccess();
+ return filter;
}
@@ -496,7 +532,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)
@@ -553,7 +589,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)
{
@@ -602,7 +638,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(",");
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 4f298522..8e14abd8 100644
--- a/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java
+++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/QPicoCliImplementationTest.java
@@ -202,7 +202,6 @@ class QPicoCliImplementationTest
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"));
@@ -214,6 +213,28 @@ class QPicoCliImplementationTest
+ /*******************************************************************************
+ ** test running a count on a table
+ **
+ *******************************************************************************/
+ @Test
+ public void test_tableCount()
+ {
+ TestOutput testOutput = testCli("person", "count", "--criteria", "id NOT_EQUALS 3");
+ JSONObject countResult = JsonUtils.toJSONObject(testOutput.getOutput());
+ assertNotNull(countResult);
+ int count = countResult.getInt("count");
+ assertEquals(4, count);
+
+ testOutput = testCli("person", "count", "--criteria", "id EQUALS 3");
+ countResult = JsonUtils.toJSONObject(testOutput.getOutput());
+ assertNotNull(countResult);
+ count = countResult.getInt("count");
+ assertEquals(1, count);
+ }
+
+
+
/*******************************************************************************
** test running a query on a table
**
@@ -497,9 +518,9 @@ class QPicoCliImplementationTest
@Test
public void test_tableProcessGreetUsingOptionsForFields() throws Exception
{
- TestOutput testOutput = testCli("person", "process", "greet", "--field-greetingPrefix=Hello", "--field-greetingSuffix=There");
+ TestOutput testOutput = testCli("person", "process", "greet", "--field-greetingPrefix=Hello", "--field-greetingSuffix=World");
assertTestOutputDoesNotContain(testOutput, "Please supply a value for the field");
- assertTestOutputContains(testOutput, "Hello X There");
+ assertTestOutputContains(testOutput, "Hello X World");
}
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 7940bbcc..c72c140b 100644
--- a/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java
+++ b/src/test/java/com/kingsrook/qqq/frontend/picocli/TestUtils.java
@@ -25,7 +25,7 @@ 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.MockFunctionBody;
+import com.kingsrook.qqq.backend.core.interfaces.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;
@@ -34,16 +34,14 @@ 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.QBackendStepMetaData;
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.model.metadata.RDBMSBackendMetaData;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
+import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import org.apache.commons.io.IOUtils;
import static junit.framework.Assert.assertNotNull;
@@ -166,12 +164,12 @@ public class TestUtils
return new QProcessMetaData()
.withName("greet")
.withTableName("person")
- .addFunction(new QFunctionMetaData()
+ .addStep(new QBackendStepMetaData()
.withName("prepare")
.withCode(new QCodeReference()
- .withName(MockFunctionBody.class.getName())
+ .withName(MockBackendStep.class.getName())
.withCodeType(QCodeType.JAVA)
- .withCodeUsage(QCodeUsage.FUNCTION)) // todo - needed, or implied in this context?
+ .withCodeUsage(QCodeUsage.BACKEND_STEP)) // todo - needed, or implied in this context?
.withInputData(new QFunctionInputMetaData()
.withRecordListMetaData(new QRecordListMetaData().withTableName("person"))
.withFieldList(List.of(
@@ -184,9 +182,6 @@ public class TestUtils
.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"))))
);
}