From 9af1fed422c4ef17f00392aa764cd2af2e22fe4c Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 6 Jul 2023 18:53:10 -0500 Subject: [PATCH] Initial backend work to support datetime query expressions from frontend --- .../actions/tables/query/QFilterCriteria.java | 3 + .../expressions/AbstractFilterExpression.java | 21 +++ .../query/expressions/NowWithOffset.java | 62 +++++- .../query/expressions/ThisOrLastPeriod.java | 178 ++++++++++++++++++ .../QFilterCriteriaDeserializer.java | 124 ++++++++++++ .../query/expressions/NowWithOffsetTest.java | 68 +++++++ .../QFilterCriteriaDeserializerTest.java | 93 +++++++++ 7 files changed, 542 insertions(+), 7 deletions(-) create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/ThisOrLastPeriod.java create mode 100644 qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/serialization/QFilterCriteriaDeserializer.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/NowWithOffsetTest.java create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/serialization/QFilterCriteriaDeserializerTest.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QFilterCriteria.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QFilterCriteria.java index e096343e..f43f7eb9 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QFilterCriteria.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/QFilterCriteria.java @@ -26,8 +26,10 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; @@ -36,6 +38,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils; * A single criteria Component of a Query * *******************************************************************************/ +@JsonDeserialize(using = QFilterCriteriaDeserializer.class) public class QFilterCriteria implements Serializable, Cloneable { private static final QLogger LOG = QLogger.getLogger(QFilterCriteria.class); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/AbstractFilterExpression.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/AbstractFilterExpression.java index cc75d062..723fa818 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/AbstractFilterExpression.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/AbstractFilterExpression.java @@ -34,4 +34,25 @@ public abstract class AbstractFilterExpression ** *******************************************************************************/ public abstract T evaluate(); + + + + /******************************************************************************* + ** To help with serialization, define a "type" in all subclasses + *******************************************************************************/ + public String getType() + { + return (getClass().getSimpleName()); + } + + + + /******************************************************************************* + ** noop - but here so serialization won't be upset about there being a type + ** in a json object. + *******************************************************************************/ + public void setType(String type) + { + + } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/NowWithOffset.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/NowWithOffset.java index 9400313b..bf36d971 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/NowWithOffset.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/NowWithOffset.java @@ -23,6 +23,10 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; @@ -31,9 +35,9 @@ import java.util.concurrent.TimeUnit; *******************************************************************************/ public class NowWithOffset extends AbstractFilterExpression { - private final Operator operator; - private final int amount; - private final TimeUnit timeUnit; + private Operator operator; + private int amount; + private ChronoUnit timeUnit; @@ -46,7 +50,17 @@ public class NowWithOffset extends AbstractFilterExpression ** Constructor ** *******************************************************************************/ - private NowWithOffset(Operator operator, int amount, TimeUnit timeUnit) + public NowWithOffset() + { + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + private NowWithOffset(Operator operator, int amount, ChronoUnit timeUnit) { this.operator = operator; this.amount = amount; @@ -59,7 +73,19 @@ public class NowWithOffset extends AbstractFilterExpression ** Factory ** *******************************************************************************/ + @Deprecated public static NowWithOffset minus(int amount, TimeUnit timeUnit) + { + return (minus(amount, timeUnit.toChronoUnit())); + } + + + + /******************************************************************************* + ** Factory + ** + *******************************************************************************/ + public static NowWithOffset minus(int amount, ChronoUnit timeUnit) { return (new NowWithOffset(Operator.MINUS, amount, timeUnit)); } @@ -70,7 +96,19 @@ public class NowWithOffset extends AbstractFilterExpression ** Factory ** *******************************************************************************/ + @Deprecated public static NowWithOffset plus(int amount, TimeUnit timeUnit) + { + return (plus(amount, timeUnit.toChronoUnit())); + } + + + + /******************************************************************************* + ** Factory + ** + *******************************************************************************/ + public static NowWithOffset plus(int amount, ChronoUnit timeUnit) { return (new NowWithOffset(Operator.PLUS, amount, timeUnit)); } @@ -83,14 +121,24 @@ public class NowWithOffset extends AbstractFilterExpression @Override public Instant evaluate() { + ///////////////////////////////////////////////////////////////////////////// + // Instant doesn't let us plus/minus WEEK, MONTH, or YEAR... // + // but LocalDateTime does. So, make a LDT in UTC, do the plus/minus, then // + // convert back to Instant @ UTC // + ///////////////////////////////////////////////////////////////////////////// + LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC")); + + LocalDateTime then; if(operator.equals(Operator.PLUS)) { - return (Instant.now().plus(amount, timeUnit.toChronoUnit())); + then = now.plus(amount, timeUnit); } else { - return (Instant.now().minus(amount, timeUnit.toChronoUnit())); + then = now.minus(amount, timeUnit); } + + return (then.toInstant(ZoneOffset.UTC)); } @@ -121,7 +169,7 @@ public class NowWithOffset extends AbstractFilterExpression ** Getter for timeUnit ** *******************************************************************************/ - public TimeUnit getTimeUnit() + public ChronoUnit getTimeUnit() { return timeUnit; } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/ThisOrLastPeriod.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/ThisOrLastPeriod.java new file mode 100644 index 00000000..28e53c37 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/ThisOrLastPeriod.java @@ -0,0 +1,178 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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.backend.core.model.actions.tables.query.expressions; + + +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; + + +/******************************************************************************* + ** + *******************************************************************************/ +public class ThisOrLastPeriod extends AbstractFilterExpression +{ + private Operator operator; + private ChronoUnit timeUnit; + + + + public enum Operator + {THIS, LAST} + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + public ThisOrLastPeriod() + { + } + + + + /******************************************************************************* + ** Constructor + ** + *******************************************************************************/ + private ThisOrLastPeriod(Operator operator, ChronoUnit timeUnit) + { + this.operator = operator; + this.timeUnit = timeUnit; + } + + + + /******************************************************************************* + ** Factory + ** + *******************************************************************************/ + public static ThisOrLastPeriod this_(ChronoUnit timeUnit) + { + return (new ThisOrLastPeriod(Operator.THIS, timeUnit)); + } + + + + /******************************************************************************* + ** Factory + ** + *******************************************************************************/ + public static ThisOrLastPeriod last(int amount, ChronoUnit timeUnit) + { + return (new ThisOrLastPeriod(Operator.LAST, timeUnit)); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public Instant evaluate() + { + ZoneId zoneId = ValueUtils.getSessionOrInstanceZoneId(); + + switch(timeUnit) + { + case HOURS -> + { + if(operator.equals(Operator.THIS)) + { + return Instant.now().truncatedTo(ChronoUnit.HOURS); + } + else + { + return Instant.now().minus(1, ChronoUnit.HOURS).truncatedTo(ChronoUnit.HOURS); + } + } + case DAYS -> + { + Instant startOfToday = ValueUtils.getStartOfTodayInZoneId(zoneId.getId()); + return operator.equals(Operator.THIS) ? startOfToday : startOfToday.minus(1, ChronoUnit.DAYS); + } + case WEEKS -> + { + Instant startOfToday = ValueUtils.getStartOfTodayInZoneId(zoneId.getId()); + LocalDateTime startOfThisWeekLDT = LocalDateTime.ofInstant(startOfToday, zoneId); + while(startOfThisWeekLDT.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue()) + { + //////////////////////////////////////// + // go backwards until sunday is found // + //////////////////////////////////////// + startOfThisWeekLDT = startOfThisWeekLDT.minus(1, ChronoUnit.DAYS); + } + Instant startOfThisWeek = startOfThisWeekLDT.toInstant(zoneId.getRules().getOffset(startOfThisWeekLDT)); + + return operator.equals(Operator.THIS) ? startOfThisWeek : startOfThisWeek.minus(7, ChronoUnit.DAYS); + } + case MONTHS -> + { + Instant startOfThisMonth = ValueUtils.getStartOfMonthInZoneId(zoneId.getId()); + LocalDateTime startOfThisMonthLDT = LocalDateTime.ofInstant(startOfThisMonth, ZoneId.of(zoneId.getId())); + LocalDateTime startOfLastMonthLDT = startOfThisMonthLDT.minus(1, ChronoUnit.MONTHS); + Instant startOfLastMonth = startOfLastMonthLDT.toInstant(ZoneId.of(zoneId.getId()).getRules().getOffset(Instant.now())); + + return operator.equals(Operator.THIS) ? startOfThisMonth : startOfLastMonth; + } + case YEARS -> + { + Instant startOfThisYear = ValueUtils.getStartOfYearInZoneId(zoneId.getId()); + LocalDateTime startOfThisYearLDT = LocalDateTime.ofInstant(startOfThisYear, zoneId); + LocalDateTime startOfLastYearLDT = startOfThisYearLDT.minus(1, ChronoUnit.YEARS); + Instant startOfLastYear = startOfLastYearLDT.toInstant(zoneId.getRules().getOffset(Instant.now())); + + return operator.equals(Operator.THIS) ? startOfThisYear : startOfLastYear; + } + default -> throw (new QRuntimeException("Unsupported timeUnit: " + timeUnit)); + } + } + + + + /******************************************************************************* + ** Getter for operator + ** + *******************************************************************************/ + public Operator getOperator() + { + return operator; + } + + + + /******************************************************************************* + ** Getter for timeUnit + ** + *******************************************************************************/ + public ChronoUnit getTimeUnit() + { + return timeUnit; + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/serialization/QFilterCriteriaDeserializer.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/serialization/QFilterCriteriaDeserializer.java new file mode 100644 index 00000000..ae4dfd64 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/serialization/QFilterCriteriaDeserializer.java @@ -0,0 +1,124 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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.backend.core.model.actions.tables.query.serialization; + + +import java.io.IOException; +import java.util.List; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +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.expressions.AbstractFilterExpression; + + +/******************************************************************************* + ** Custom jackson deserializer, to deal w/ abstract expression field + *******************************************************************************/ +public class QFilterCriteriaDeserializer extends StdDeserializer +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public QFilterCriteriaDeserializer() + { + this(null); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + public QFilterCriteriaDeserializer(Class vc) + { + super(vc); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public QFilterCriteria deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException + { + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + ObjectMapper objectMapper = new ObjectMapper(); + + ///////////////////////////////// + // get values out of json node // + ///////////////////////////////// + List values = objectMapper.treeToValue(node.get("values"), List.class); + String fieldName = objectMapper.treeToValue(node.get("fieldName"), String.class); + QCriteriaOperator operator = objectMapper.treeToValue(node.get("operator"), QCriteriaOperator.class); + String otherFieldName = objectMapper.treeToValue(node.get("otherFieldName"), String.class); + + AbstractFilterExpression expression = null; + if(node.has("expression")) + { + JsonNode expressionNode = node.get("expression"); + String expressionType = objectMapper.treeToValue(expressionNode.get("type"), String.class); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // right now, we'll assume that all expression subclasses are in the same package as AbstractFilterExpression // + // so, we can just do a Class.forName on that name, and treeToValue like that. // + // if we ever had to, we could instead switch(expressionType), and do like so... // + // case "NowWithOffset" -> objectMapper.treeToValue(expressionNode, NowWithOffset.class); // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + try + { + String className = AbstractFilterExpression.class.getName().replace(AbstractFilterExpression.class.getSimpleName(), expressionType); + expression = (AbstractFilterExpression) objectMapper.treeToValue(expressionNode, Class.forName(className)); + } + catch(Exception e) + { + throw (new IOException("Error deserializing expression of type [" + expressionType + "] inside QFilterCriteria", e)); + } + } + + /////////////////////////////////// + // put fields into return object // + /////////////////////////////////// + QFilterCriteria criteria = new QFilterCriteria(); + criteria.setFieldName(fieldName); + criteria.setOperator(operator); + criteria.setValues(values); + criteria.setOtherFieldName(otherFieldName); + criteria.setExpression(expression); + + return (criteria); + + /* + int id = (Integer) (node.get("id")).numberValue(); + String itemName = node.get("itemName").asText(); + int userId = (Integer) (node.get("createdBy")).numberValue(); + + return null; + */ + } +} diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/NowWithOffsetTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/NowWithOffsetTest.java new file mode 100644 index 00000000..8ef5737d --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/expressions/NowWithOffsetTest.java @@ -0,0 +1,68 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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.backend.core.model.actions.tables.query.expressions; + + +import java.time.temporal.ChronoUnit; +import com.kingsrook.qqq.backend.core.BaseTest; +import org.assertj.core.data.Offset; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + + +/******************************************************************************* + ** Unit test for NowWithOffset + *******************************************************************************/ +class NowWithOffsetTest extends BaseTest +{ + private static final long DAY_IN_MILLIS = 24 * 60 * 60 * 1000; + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void test() + { + long now = System.currentTimeMillis(); + + long oneWeekAgoMillis = NowWithOffset.minus(1, ChronoUnit.WEEKS).evaluate().toEpochMilli(); + assertThat(oneWeekAgoMillis).isCloseTo(now - (7 * DAY_IN_MILLIS), Offset.offset(10_000L)); + + long oneWeekFromNowMillis = NowWithOffset.plus(2, ChronoUnit.WEEKS).evaluate().toEpochMilli(); + assertThat(oneWeekFromNowMillis).isCloseTo(now + (14 * DAY_IN_MILLIS), Offset.offset(10_000L)); + + long oneMonthAgoMillis = NowWithOffset.minus(1, ChronoUnit.MONTHS).evaluate().toEpochMilli(); + assertThat(oneMonthAgoMillis).isCloseTo(now - (30 * DAY_IN_MILLIS), Offset.offset(10_000L + 2 * DAY_IN_MILLIS)); + + long oneMonthFromNowMillis = NowWithOffset.plus(2, ChronoUnit.MONTHS).evaluate().toEpochMilli(); + assertThat(oneMonthFromNowMillis).isCloseTo(now + (60 * DAY_IN_MILLIS), Offset.offset(10_000L + 3 * DAY_IN_MILLIS)); + + long oneYearAgoMillis = NowWithOffset.minus(1, ChronoUnit.YEARS).evaluate().toEpochMilli(); + assertThat(oneYearAgoMillis).isCloseTo(now - (365 * DAY_IN_MILLIS), Offset.offset(10_000L + 2 * DAY_IN_MILLIS)); + + long oneYearFromNowMillis = NowWithOffset.plus(2, ChronoUnit.YEARS).evaluate().toEpochMilli(); + assertThat(oneYearFromNowMillis).isCloseTo(now + (730 * DAY_IN_MILLIS), Offset.offset(10_000L + 3 * DAY_IN_MILLIS)); + } + +} \ No newline at end of file diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/serialization/QFilterCriteriaDeserializerTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/serialization/QFilterCriteriaDeserializerTest.java new file mode 100644 index 00000000..42f7c3da --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/serialization/QFilterCriteriaDeserializerTest.java @@ -0,0 +1,93 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. 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.backend.core.model.actions.tables.query.serialization; + + +import java.io.IOException; +import java.time.temporal.ChronoUnit; +import java.util.List; +import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.Now; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.NowWithOffset; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import org.junit.jupiter.api.Test; +import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.EQUALS; +import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.GREATER_THAN; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + + +/******************************************************************************* + ** Unit test for QFilterCriteriaDeserializer + *******************************************************************************/ +class QFilterCriteriaDeserializerTest extends BaseTest +{ + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testDeserialize() throws IOException + { + { + QFilterCriteria criteria = JsonUtils.toObject(""" + {"fieldName": "id", "operator": "EQUALS", "values": [1]} + """, QFilterCriteria.class); + assertEquals("id", criteria.getFieldName()); + assertEquals(EQUALS, criteria.getOperator()); + assertEquals(List.of(1), criteria.getValues()); + } + + { + QFilterCriteria criteria = JsonUtils.toObject(""" + {"fieldName": "createDate", "operator": "GREATER_THAN", "expression": + {"type": "NowWithOffset", "operator": "PLUS", "amount": 5, "timeUnit": "MINUTES"} + } + """, QFilterCriteria.class); + assertEquals("createDate", criteria.getFieldName()); + assertEquals(GREATER_THAN, criteria.getOperator()); + assertNull(criteria.getValues()); + AbstractFilterExpression expression = criteria.getExpression(); + assertThat(expression).isInstanceOf(NowWithOffset.class); + NowWithOffset nowWithOffset = (NowWithOffset) expression; + assertEquals(5, nowWithOffset.getAmount()); + assertEquals(NowWithOffset.Operator.PLUS, nowWithOffset.getOperator()); + assertEquals(ChronoUnit.MINUTES, nowWithOffset.getTimeUnit()); + } + + { + QFilterCriteria criteria = JsonUtils.toObject(""" + {"fieldName": "orderDate", "operator": "EQUALS", "expression": {"type": "Now"} } + """, QFilterCriteria.class); + assertEquals("orderDate", criteria.getFieldName()); + assertEquals(EQUALS, criteria.getOperator()); + assertNull(criteria.getValues()); + AbstractFilterExpression expression = criteria.getExpression(); + assertThat(expression).isInstanceOf(Now.class); + } + + } +} \ No newline at end of file