diff --git a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java index 5dc7eda3..7bea3ccc 100644 --- a/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java +++ b/qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementation.java @@ -85,6 +85,7 @@ 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.QQueryFilter; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin; 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; @@ -112,6 +113,7 @@ import io.javalin.apibuilder.EndpointGroup; import io.javalin.http.Context; import org.apache.commons.io.FileUtils; import org.eclipse.jetty.http.HttpStatus; +import org.json.JSONArray; import org.json.JSONObject; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; import static com.kingsrook.qqq.backend.javalin.QJavalinAccessLogger.logPairIfSlow; @@ -749,6 +751,8 @@ public class QJavalinImplementation countInput.setFilter(JsonUtils.toObject(filter, QQueryFilter.class)); } + countInput.setQueryJoins(processQueryJoinsParam(context)); + CountAction countAction = new CountAction(); CountOutput countOutput = countAction.execute(countInput); @@ -777,6 +781,16 @@ public class QJavalinImplementation * {"fieldName":"age","isAscending":true} * ]} * + * + * queryJoins parameter is a JSONArray of objects which represent a QueryJoin object. e.g., + *
+    *   queryJoins=
+    *    [
+    *       {"joinTable":"orderLine","select":true,"type":"INNER"},
+    *       {"joinTable":"customer","select":true,"type":"LEFT"}
+    *    }
+    * 
+ * Additional field names in the JSONObjects there are: baseTableOrAlias, alias, joinName. *******************************************************************************/ static void dataQuery(Context context) { @@ -807,6 +821,8 @@ public class QJavalinImplementation queryInput.setFilter(JsonUtils.toObject(filter, QQueryFilter.class)); } + queryInput.setQueryJoins(processQueryJoinsParam(context)); + QueryAction queryAction = new QueryAction(); QueryOutput queryOutput = queryAction.execute(queryInput); @@ -822,6 +838,56 @@ public class QJavalinImplementation + /******************************************************************************* + ** + *******************************************************************************/ + private static List processQueryJoinsParam(Context context) + { + List queryJoins = null; + + String queryJoinsParam = stringQueryParam(context, "queryJoins"); + if(StringUtils.hasContent(queryJoinsParam)) + { + queryJoins = new ArrayList<>(); + + JSONArray queryJoinsJSON = new JSONArray(queryJoinsParam); + for(int i = 0; i < queryJoinsJSON.length(); i++) + { + QueryJoin queryJoin = new QueryJoin(); + queryJoins.add(queryJoin); + + JSONObject jsonObject = queryJoinsJSON.getJSONObject(i); + queryJoin.setJoinTable(jsonObject.optString("joinTable")); + + if(jsonObject.has("baseTableOrAlias") && !jsonObject.isNull("baseTableOrAlias")) + { + queryJoin.setBaseTableOrAlias(jsonObject.optString("baseTableOrAlias")); + } + + if(jsonObject.has("alias") && !jsonObject.isNull("alias")) + { + queryJoin.setAlias(jsonObject.optString("alias")); + } + + queryJoin.setSelect(jsonObject.optBoolean("select")); + + if(jsonObject.has("type") && !jsonObject.isNull("type")) + { + queryJoin.setType(QueryJoin.Type.valueOf(jsonObject.getString("type"))); + } + + if(jsonObject.has("joinName") && !jsonObject.isNull("joinName")) + { + queryJoin.setJoinMetaData(QContext.getQInstance().getJoin(jsonObject.getString("joinName"))); + } + } + } + + return (queryJoins); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java index f3f935c9..c8ba55a8 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/QJavalinImplementationTest.java @@ -234,7 +234,7 @@ class QJavalinImplementationTest extends QJavalinTestBase JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); assertTrue(jsonObject.has("count")); int count = jsonObject.getInt("count"); - assertEquals(5, count); + assertEquals(6, count); } @@ -246,7 +246,7 @@ class QJavalinImplementationTest extends QJavalinTestBase @Test public void test_dataCountWithFilter() { - String filterJson = getFirstNameEqualsTimFilterJSON(); + String filterJson = getFirstNameEqualsFilterJSON("Tim"); HttpResponse response = Unirest.get(BASE_URL + "/data/person/count?filter=" + URLEncoder.encode(filterJson, StandardCharsets.UTF_8)).asString(); assertEquals(200, response.getStatus()); @@ -266,7 +266,7 @@ class QJavalinImplementationTest extends QJavalinTestBase @Test public void test_dataCountPOST() { - String filterJson = getFirstNameEqualsTimFilterJSON(); + String filterJson = getFirstNameEqualsFilterJSON("Tim"); HttpResponse response = Unirest.post(BASE_URL + "/data/person/count") .field("filter", filterJson) .asString(); @@ -294,7 +294,7 @@ class QJavalinImplementationTest extends QJavalinTestBase JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); assertTrue(jsonObject.has("records")); JSONArray records = jsonObject.getJSONArray("records"); - assertEquals(5, records.length()); + assertEquals(6, records.length()); JSONObject record0 = records.getJSONObject(0); assertTrue(record0.has("values")); assertEquals("person", record0.getString("tableName")); @@ -312,7 +312,7 @@ class QJavalinImplementationTest extends QJavalinTestBase @Test public void test_dataQueryWithFilter() { - String filterJson = getFirstNameEqualsTimFilterJSON(); + String filterJson = getFirstNameEqualsFilterJSON("Tim"); HttpResponse response = Unirest.get(BASE_URL + "/data/person?filter=" + URLEncoder.encode(filterJson, StandardCharsets.UTF_8)).asString(); assertEquals(200, response.getStatus()); @@ -327,6 +327,34 @@ class QJavalinImplementationTest extends QJavalinTestBase + /******************************************************************************* + ** test a table query using a filter and a queryJoin. + ** + *******************************************************************************/ + @Test + public void test_dataQueryWithFilterAndQueryJoin() + { + String filterJson = getFirstNameEqualsFilterJSON("Darin"); + String queryJoinsJson = """ + [{"joinTable":"person","select":true,"type":"INNER","joinName":"PersonJoinPartnerPerson","alias":"partnerPerson"}] + """; + + HttpResponse response = Unirest.get(BASE_URL + "/data/person?filter=" + URLEncoder.encode(filterJson, StandardCharsets.UTF_8) + + "&queryJoins=" + URLEncoder.encode(queryJoinsJson, StandardCharsets.UTF_8)).asString(); + + assertEquals(200, response.getStatus()); + JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); + assertTrue(jsonObject.has("records")); + JSONArray records = jsonObject.getJSONArray("records"); + assertEquals(1, records.length()); + JSONObject record0 = records.getJSONObject(0); + JSONObject values0 = record0.getJSONObject("values"); + assertEquals("Darin", values0.getString("firstName")); + assertEquals("Linda", values0.getString("partnerPerson.firstName")); + } + + + /******************************************************************************* ** test a table query using an actual filter via POST. ** @@ -334,7 +362,7 @@ class QJavalinImplementationTest extends QJavalinTestBase @Test public void test_dataQueryWithFilterPOST() { - String filterJson = getFirstNameEqualsTimFilterJSON(); + String filterJson = getFirstNameEqualsFilterJSON("Tim"); HttpResponse response = Unirest.post(BASE_URL + "/data/person/query") .field("filter", filterJson) .asString(); @@ -354,10 +382,10 @@ class QJavalinImplementationTest extends QJavalinTestBase /******************************************************************************* ** *******************************************************************************/ - private String getFirstNameEqualsTimFilterJSON() + private String getFirstNameEqualsFilterJSON(String name) { return """ - {"criteria":[{"fieldName":"firstName","operator":"EQUALS","values":["Tim"]}]}"""; + {"criteria":[{"fieldName":"firstName","operator":"EQUALS","values":["${name}"]}]}""".replaceAll("\\$\\{name}", name); } @@ -391,7 +419,7 @@ class QJavalinImplementationTest extends QJavalinTestBase assertTrue(values0.has("firstName")); assertEquals("Bobby", values0.getString("firstName")); assertTrue(values0.has("id")); - assertEquals(6, values0.getInt("id")); + assertEquals(7, values0.getInt("id")); } @@ -457,7 +485,7 @@ class QJavalinImplementationTest extends QJavalinTestBase rowsFound++; assertNotEquals(3, rs.getInt(1)); } - assertEquals(4, rowsFound); + assertEquals(5, rowsFound); })); } @@ -474,7 +502,7 @@ class QJavalinImplementationTest extends QJavalinTestBase assertEquals("text/csv;charset=utf-8", response.getHeaders().get("Content-Type").get(0)); assertEquals("filename=MyPersonExport.csv", response.getHeaders().get("Content-Disposition").get(0)); String[] csvLines = response.getBody().split("\n"); - assertEquals(6, csvLines.length); + assertEquals(7, csvLines.length); } @@ -512,7 +540,7 @@ class QJavalinImplementationTest extends QJavalinTestBase @Test void testExportFilterQueryParam() { - String filterJson = getFirstNameEqualsTimFilterJSON(); + String filterJson = getFirstNameEqualsFilterJSON("Tim"); HttpResponse response = Unirest.get(BASE_URL + "/data/person/export/Favorite People.csv?filter=" + URLEncoder.encode(filterJson, StandardCharsets.UTF_8)).asString(); assertEquals("filename=Favorite People.csv", response.getHeaders().get("Content-Disposition").get(0)); String[] csvLines = response.getBody().split("\n"); @@ -576,7 +604,7 @@ class QJavalinImplementationTest extends QJavalinTestBase JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody()); assertNotNull(jsonObject); assertNotNull(jsonObject.getJSONArray("options")); - assertEquals(5, jsonObject.getJSONArray("options").length()); + assertEquals(6, jsonObject.getJSONArray("options").length()); assertEquals(1, jsonObject.getJSONArray("options").getJSONObject(0).getInt("id")); assertEquals("Darin Kelkhoff (1)", jsonObject.getJSONArray("options").getJSONObject(0).getString("label")); } diff --git a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java index e3daf4e2..b2953749 100644 --- a/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java +++ b/qqq-middleware-javalin/src/test/java/com/kingsrook/qqq/backend/javalin/TestUtils.java @@ -46,6 +46,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage; import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType; @@ -139,6 +142,7 @@ public class TestUtils qInstance.setAuthentication(defineAuthentication()); qInstance.addBackend(defineDefaultH2Backend()); qInstance.addTable(defineTablePerson()); + qInstance.addJoin(definePersonJoinPartnerPerson()); qInstance.addProcess(defineProcessGreetPeople()); qInstance.addProcess(defineProcessGreetPeopleInteractive()); qInstance.addProcess(defineProcessSimpleSleep()); @@ -249,6 +253,21 @@ public class TestUtils + /******************************************************************************* + ** + *******************************************************************************/ + public static QJoinMetaData definePersonJoinPartnerPerson() + { + return (new QJoinMetaData() + .withLeftTable(TABLE_NAME_PERSON) + .withRightTable(TABLE_NAME_PERSON) + .withType(JoinType.MANY_TO_ONE) + .withName("PersonJoinPartnerPerson") + .withJoinOn(new JoinOn("partnerPersonId", "id"))); + } + + + /******************************************************************************* ** Define the 'greet people' process *******************************************************************************/ diff --git a/qqq-middleware-javalin/src/test/resources/prime-test-database.sql b/qqq-middleware-javalin/src/test/resources/prime-test-database.sql index c0a67766..a94c185b 100644 --- a/qqq-middleware-javalin/src/test/resources/prime-test-database.sql +++ b/qqq-middleware-javalin/src/test/resources/prime-test-database.sql @@ -34,8 +34,9 @@ CREATE TABLE person test_script_id INT ); -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, partner_person_id) VALUES (1, 'Darin', 'Kelkhoff', '1980-05-31', 'darin.kelkhoff@gmail.com', 6); 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'); +INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (6, 'Linda', 'Kelkhoff', '1976-01-01', 'not-linda.kelkhoff@gmail.com');