diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java index a60ac7ff..6b5baa83 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java @@ -5,9 +5,11 @@ import java.io.Serializable; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; +import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.function.Function; import java.util.stream.Collectors; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.model.actions.QFilterCriteria; @@ -17,6 +19,7 @@ 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.data.QRecord; 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.core.modules.interfaces.QueryInterface; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; @@ -95,7 +98,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf for(int i = 1; i <= metaData.getColumnCount(); i++) { QFieldMetaData qFieldMetaData = fieldList.get(i - 1); - String value = QueryManager.getString(resultSet, i); // todo - types! + Serializable value = getValue(qFieldMetaData, resultSet, i); values.put(qFieldMetaData.getName(), value); if(qFieldMetaData.getName().equals(table.getPrimaryKeyField())) { @@ -117,6 +120,46 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf + /******************************************************************************* + ** + *******************************************************************************/ + private Serializable getValue(QFieldMetaData qFieldMetaData, ResultSet resultSet, int i) throws SQLException + { + switch(qFieldMetaData.getType()) + { + case STRING: + case TEXT: + case HTML: + case PASSWORD: + { + return (QueryManager.getString(resultSet, i)); + } + case INTEGER: + { + return (QueryManager.getInteger(resultSet, i)); + } + case DECIMAL: + { + return (QueryManager.getBigDecimal(resultSet, i)); + } + case DATE: + { + return (QueryManager.getDate(resultSet, i)); + } + case DATE_TIME: + { + return (QueryManager.getLocalDateTime(resultSet, i)); + } + default: + { + throw new IllegalStateException("Unexpected field type: " + qFieldMetaData.getType()); + } + } + + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -126,6 +169,7 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf for(QFilterCriteria criterion : criteria) { QFieldMetaData field = table.getField(criterion.getFieldName()); + List values = criterion.getValues() == null ? new ArrayList<>() : new ArrayList<>(criterion.getValues()); String column = getColumnName(field); String clause = column; Integer expectedNoOfParams = null; @@ -145,7 +189,98 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf } case IN: { - clause += " IN (" + criterion.getValues().stream().map(x -> "?").collect(Collectors.joining(",")) + ") "; + clause += " IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") "; + break; + } + case NOT_IN: + { + clause += " NOT IN (" + values.stream().map(x -> "?").collect(Collectors.joining(",")) + ") "; + break; + } + case STARTS_WITH: + { + clause += " LIKE ? "; + editFirstValue(values, (s -> s + "%")); + expectedNoOfParams = 1; + break; + } + case ENDS_WITH: + { + clause += " LIKE ? "; + editFirstValue(values, (s -> "%" + s)); + expectedNoOfParams = 1; + break; + } + case CONTAINS: + { + clause += " LIKE ? "; + editFirstValue(values, (s -> "%" + s + "%")); + expectedNoOfParams = 1; + break; + } + case NOT_STARTS_WITH: + { + clause += " NOT LIKE ? "; + editFirstValue(values, (s -> s + "%")); + expectedNoOfParams = 1; + break; + } + case NOT_ENDS_WITH: + { + clause += " NOT LIKE ? "; + editFirstValue(values, (s -> "%" + s)); + expectedNoOfParams = 1; + break; + } + case NOT_CONTAINS: + { + clause += " NOT LIKE ? "; + editFirstValue(values, (s -> "%" + s + "%")); + expectedNoOfParams = 1; + break; + } + case LESS_THAN: + { + clause += " < ? "; + expectedNoOfParams = 1; + break; + } + case LESS_THAN_OR_EQUALS: + { + clause += " <= ? "; + expectedNoOfParams = 1; + break; + } + case GREATER_THAN: + { + clause += " > ? "; + expectedNoOfParams = 1; + break; + } + case GREATER_THAN_OR_EQUALS: + { + clause += " >= ? "; + expectedNoOfParams = 1; + break; + } + case IS_BLANK: + { + clause += " IS NULL "; + if(isString(field.getType())) + { + clause += " OR " + column + " = '' "; + } + expectedNoOfParams = 0; + break; + } + case IS_NOT_BLANK: + { + clause += " IS NOT NULL "; + if(isString(field.getType())) + { + clause += " AND " + column + " !+ '' "; + } + expectedNoOfParams = 0; break; } default: @@ -153,12 +288,16 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf throw new IllegalArgumentException("Unexpected operator: " + criterion.getOperator()); } } - clauses.add(clause); - if(expectedNoOfParams != null && criterion.getValues().size() != expectedNoOfParams) + clauses.add("(" + clause + ")"); + if(expectedNoOfParams != null) { - throw new IllegalArgumentException("Incorrect number of values given for criteria [" + field.getName() + "]"); + if(!expectedNoOfParams.equals(values.size())) + { + throw new IllegalArgumentException("Incorrect number of values given for criteria [" + field.getName() + "]"); + } } - params.addAll(criterion.getValues()); + + params.addAll(values); } return (String.join(" AND ", clauses)); @@ -166,6 +305,29 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf + /******************************************************************************* + ** + *******************************************************************************/ + private void editFirstValue(List values, Function editFunction) + { + if(values.size() > 0) + { + values.set(0, editFunction.apply(String.valueOf(values.get(0)))); + } + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private boolean isString(QFieldType fieldType) + { + return fieldType == QFieldType.STRING || fieldType == QFieldType.TEXT || fieldType == QFieldType.HTML || fieldType == QFieldType.PASSWORD; + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java index f6fa4be1..af8b4b24 100644 --- a/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java +++ b/src/main/java/com/kingsrook/qqq/backend/module/rdbms/jdbc/QueryManager.java @@ -1142,6 +1142,24 @@ public class QueryManager + /******************************************************************************* + ** + *******************************************************************************/ + @SuppressWarnings("deprecation") + public static LocalDateTime getLocalDateTime(ResultSet resultSet, int column) throws SQLException + { + Timestamp value = resultSet.getTimestamp(column); + if(resultSet.wasNull()) + { + return (null); + } + + LocalDateTime dateTime = LocalDateTime.of(value.getYear() + NINETEEN_HUNDRED, value.getMonth() + 1, value.getDate(), value.getHours(), value.getMinutes(), value.getSeconds(), 0); + return (dateTime); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java index fc555482..9c28b934 100644 --- a/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java +++ b/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java @@ -29,6 +29,7 @@ public class RDBMSQueryActionTest extends RDBMSActionTest } + /******************************************************************************* ** *******************************************************************************/ @@ -58,12 +59,293 @@ public class RDBMSQueryActionTest extends RDBMSActionTest .withValues(List.of(email))) ); QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); - Assertions.assertEquals(1, queryResult.getRecords().size(), "Equals query should find 1 row"); + Assertions.assertEquals(1, queryResult.getRecords().size(), "Expected # of rows"); Assertions.assertEquals(email, queryResult.getRecords().get(0).getValueString("email"), "Should find expected email address"); } + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testNotEqualsQuery() throws QException + { + String email = "darin.kelkhoff@gmail.com"; + + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("email") + .withOperator(QCriteriaOperator.NOT_EQUALS) + .withValues(List.of(email))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(4, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().noneMatch(r -> r.getValueString("email").equals(email)), "Should NOT find expected email address"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testInQuery() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("id") + .withOperator(QCriteriaOperator.IN) + .withValues(List.of(2, 4))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(2, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(2) || r.getValueInteger("id").equals(4)), "Should find expected ids"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testNotInQuery() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("id") + .withOperator(QCriteriaOperator.NOT_IN) + .withValues(List.of(2, 3, 4))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(2, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(5)), "Should find expected ids"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testStartsWith() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("email") + .withOperator(QCriteriaOperator.STARTS_WITH) + .withValues(List.of("darin"))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(1, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testContains() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("email") + .withOperator(QCriteriaOperator.CONTAINS) + .withValues(List.of("kelkhoff"))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(1, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testEndsWith() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("email") + .withOperator(QCriteriaOperator.ENDS_WITH) + .withValues(List.of("gmail.com"))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(1, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testNotStartsWith() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("email") + .withOperator(QCriteriaOperator.NOT_STARTS_WITH) + .withValues(List.of("darin"))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(4, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().noneMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testNotContains() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("email") + .withOperator(QCriteriaOperator.NOT_CONTAINS) + .withValues(List.of("kelkhoff"))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(4, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testNotEndsWith() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("email") + .withOperator(QCriteriaOperator.NOT_ENDS_WITH) + .withValues(List.of("gmail.com"))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(4, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testLessThanQuery() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("id") + .withOperator(QCriteriaOperator.LESS_THAN) + .withValues(List.of(3))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(2, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(2)), "Should find expected ids"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testLessThanOrEqualsQuery() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("id") + .withOperator(QCriteriaOperator.LESS_THAN_OR_EQUALS) + .withValues(List.of(2))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(2, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(1) || r.getValueInteger("id").equals(2)), "Should find expected ids"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testGreaterThanQuery() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("id") + .withOperator(QCriteriaOperator.GREATER_THAN) + .withValues(List.of(3))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(2, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(4) || r.getValueInteger("id").equals(5)), "Should find expected ids"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testGreaterThanOrEqualsQuery() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("id") + .withOperator(QCriteriaOperator.GREATER_THAN_OR_EQUALS) + .withValues(List.of(4))) + ); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(2, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValueInteger("id").equals(4) || r.getValueInteger("id").equals(5)), "Should find expected ids"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + public void testIsBlankQuery() throws QException + { + QueryRequest queryRequest = initQueryRequest(); + queryRequest.setFilter(new QQueryFilter() + .withCriteria(new QFilterCriteria() + .withFieldName("birthDate") + .withOperator(QCriteriaOperator.IS_BLANK) + )); + QueryResult queryResult = new RDBMSQueryAction().execute(queryRequest); + Assertions.assertEquals(1, queryResult.getRecords().size(), "Expected # of rows"); + Assertions.assertTrue(queryResult.getRecords().stream().allMatch(r -> r.getValue("birthDate") == null), "Should find expected row"); + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/src/test/resources/prime-test-database.sql b/src/test/resources/prime-test-database.sql index e5adab34..df1b2040 100644 --- a/src/test/resources/prime-test-database.sql +++ b/src/test/resources/prime-test-database.sql @@ -14,5 +14,5 @@ CREATE TABLE person 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 (4, 'Tyler', 'Samples', NULL, 'tsamples@mmltholdings.com'); INSERT INTO person (id, first_name, last_name, birth_date, email) VALUES (5, 'Garret', 'Richardson', '1981-01-01', 'grichardson@mmltholdings.com');