From 58f4c7420d5f02a050ff0121dcfc65f515c893a0 Mon Sep 17 00:00:00 2001 From: Ivan Panakhno Date: Mon, 6 Sep 2021 16:56:48 +0300 Subject: [PATCH 1/8] add command IN and NOT_IN for a filter to the dashboard --- .../data/query/StringFilterPredicate.java | 4 +++- .../dao/sql/query/EntityKeyMapping.java | 24 ++++++++++++++++++- .../app/shared/models/query/query.models.ts | 8 +++++-- .../assets/locale/locale.constant-en_US.json | 4 +++- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java index d3a09813e3..f4da15df4e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/StringFilterPredicate.java @@ -38,6 +38,8 @@ public class StringFilterPredicate implements SimpleKeyFilterPredicate { STARTS_WITH, ENDS_WITH, CONTAINS, - NOT_CONTAINS + NOT_CONTAINS, + IN, + NOT_IN } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index caed48758a..94645388b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -537,6 +537,7 @@ public class EntityKeyMapping { value = value.toLowerCase(); operationField = String.format("lower(%s)", operationField); } + List values = new ArrayList<>(); switch (stringFilterPredicate.getOperation()) { case EQUAL: stringOperationQuery = String.format("%s = :%s)", operationField, paramName); @@ -560,8 +561,29 @@ public class EntityKeyMapping { value = "%" + value + "%"; stringOperationQuery = String.format("%s not like :%s or %s is null)", operationField, paramName, operationField); break; + case IN: + value = value.replaceAll("'","").replaceAll("\"", ""); + values = List.of(value.trim().split("\\s*,\\s*")); + System.out.println(value); + stringOperationQuery = String.format("%s in (:%s))", operationField, paramName); + break; + case NOT_IN: + value = value.replaceAll("'","").replaceAll("\"", ""); + values = List.of(value.trim().split("\\s*,\\s*")); + System.out.println(value); + stringOperationQuery = String.format("%s not in (:%s))", operationField, paramName); + } + switch (stringFilterPredicate.getOperation()) { + case IN: + case NOT_IN: + for (String str : values) { + System.out.println(str); + } + ctx.addStringListParameter(paramName, values); + break; + default: + ctx.addStringParameter(paramName, value); } - ctx.addStringParameter(paramName, value); return String.format("((%s is not null and %s)", field, stringOperationQuery); } diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index e504a3480d..535319e7c4 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -226,7 +226,9 @@ export enum StringOperation { STARTS_WITH = 'STARTS_WITH', ENDS_WITH = 'ENDS_WITH', CONTAINS = 'CONTAINS', - NOT_CONTAINS = 'NOT_CONTAINS' + NOT_CONTAINS = 'NOT_CONTAINS', + IN = 'CONTAINS', + NOT_IN = 'NOT_CONTAINS' } export const stringOperationTranslationMap = new Map( @@ -236,7 +238,9 @@ export const stringOperationTranslationMap = new Map( [StringOperation.STARTS_WITH, 'filter.operation.starts-with'], [StringOperation.ENDS_WITH, 'filter.operation.ends-with'], [StringOperation.CONTAINS, 'filter.operation.contains'], - [StringOperation.NOT_CONTAINS, 'filter.operation.not-contains'] + [StringOperation.NOT_CONTAINS, 'filter.operation.not-contains'], + [StringOperation.IN, 'filter.operation.in'], + [StringOperation.NOT_IN, 'filter.operation.not-in'] ] ); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 5cd4a38065..4e8efca115 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2003,7 +2003,9 @@ "greater-or-equal": "greater or equal", "less-or-equal": "less or equal", "and": "and", - "or": "or" + "or": "or", + "in": "in", + "not-in": "not in" }, "ignore-case": "ignore case", "value": "Value", From e3dee573e53a0a5ba9e03d1c70360805688c91cf Mon Sep 17 00:00:00 2001 From: Ivan Panakhno Date: Mon, 6 Sep 2021 17:28:32 +0300 Subject: [PATCH 2/8] add command IN and NOT_IN for a filter to the dashboard(fix error) --- ui-ngx/src/app/shared/models/query/query.models.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index 535319e7c4..d72aed9798 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -227,8 +227,8 @@ export enum StringOperation { ENDS_WITH = 'ENDS_WITH', CONTAINS = 'CONTAINS', NOT_CONTAINS = 'NOT_CONTAINS', - IN = 'CONTAINS', - NOT_IN = 'NOT_CONTAINS' + IN = 'IN', + NOT_IN = 'NOT_IN' } export const stringOperationTranslationMap = new Map( From 7add33f3c8820364215498bc64db9c7483ca2498 Mon Sep 17 00:00:00 2001 From: Ivan Panakhno Date: Mon, 6 Sep 2021 17:29:12 +0300 Subject: [PATCH 3/8] add command IN and NOT_IN for a filter to the dashboard(fix error) --- .../org/thingsboard/server/dao/sql/query/EntityKeyMapping.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 94645388b6..575bb235e7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -576,9 +576,6 @@ public class EntityKeyMapping { switch (stringFilterPredicate.getOperation()) { case IN: case NOT_IN: - for (String str : values) { - System.out.println(str); - } ctx.addStringListParameter(paramName, values); break; default: From b95b8216948a37dd3b2a2be353c3639b6cb03644 Mon Sep 17 00:00:00 2001 From: Ivan Panakhno Date: Mon, 6 Sep 2021 17:31:41 +0300 Subject: [PATCH 4/8] add command IN and NOT_IN for a filter to the dashboard(fix error) --- .../org/thingsboard/server/dao/sql/query/EntityKeyMapping.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 575bb235e7..56bee96721 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -564,13 +564,11 @@ public class EntityKeyMapping { case IN: value = value.replaceAll("'","").replaceAll("\"", ""); values = List.of(value.trim().split("\\s*,\\s*")); - System.out.println(value); stringOperationQuery = String.format("%s in (:%s))", operationField, paramName); break; case NOT_IN: value = value.replaceAll("'","").replaceAll("\"", ""); values = List.of(value.trim().split("\\s*,\\s*")); - System.out.println(value); stringOperationQuery = String.format("%s not in (:%s))", operationField, paramName); } switch (stringFilterPredicate.getOperation()) { From f2aab72e06acbb481436c5eb59fd0beed453479d Mon Sep 17 00:00:00 2001 From: Ivan Panakhno Date: Mon, 6 Sep 2021 18:19:14 +0300 Subject: [PATCH 5/8] add command IN and NOT_IN for a filter to the dashboard(fix error after code review) --- .../thingsboard/server/dao/sql/query/EntityKeyMapping.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 56bee96721..3328ceb4fa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -537,7 +537,6 @@ public class EntityKeyMapping { value = value.toLowerCase(); operationField = String.format("lower(%s)", operationField); } - List values = new ArrayList<>(); switch (stringFilterPredicate.getOperation()) { case EQUAL: stringOperationQuery = String.format("%s = :%s)", operationField, paramName); @@ -563,18 +562,17 @@ public class EntityKeyMapping { break; case IN: value = value.replaceAll("'","").replaceAll("\"", ""); - values = List.of(value.trim().split("\\s*,\\s*")); stringOperationQuery = String.format("%s in (:%s))", operationField, paramName); break; case NOT_IN: value = value.replaceAll("'","").replaceAll("\"", ""); - values = List.of(value.trim().split("\\s*,\\s*")); stringOperationQuery = String.format("%s not in (:%s))", operationField, paramName); + break; } switch (stringFilterPredicate.getOperation()) { case IN: case NOT_IN: - ctx.addStringListParameter(paramName, values); + ctx.addStringListParameter(paramName, List.of(value.trim().split("\\s*,\\s*"))); break; default: ctx.addStringParameter(paramName, value); From c197f27d8e16b6b27009daa974f1b823ef3454b4 Mon Sep 17 00:00:00 2001 From: van-vanich Date: Fri, 5 Nov 2021 12:16:52 +0200 Subject: [PATCH 6/8] remove replace quote to method with a meaningful name. Also, remove split values to list to this method. --- .../server/dao/sql/query/EntityKeyMapping.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 3328ceb4fa..7a86431da0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -561,18 +561,16 @@ public class EntityKeyMapping { stringOperationQuery = String.format("%s not like :%s or %s is null)", operationField, paramName, operationField); break; case IN: - value = value.replaceAll("'","").replaceAll("\"", ""); stringOperationQuery = String.format("%s in (:%s))", operationField, paramName); break; case NOT_IN: - value = value.replaceAll("'","").replaceAll("\"", ""); stringOperationQuery = String.format("%s not in (:%s))", operationField, paramName); break; } switch (stringFilterPredicate.getOperation()) { case IN: case NOT_IN: - ctx.addStringListParameter(paramName, List.of(value.trim().split("\\s*,\\s*"))); + ctx.addStringListParameter(paramName, getListValuesWithoutQuote(value)); break; default: ctx.addStringParameter(paramName, value); @@ -580,6 +578,10 @@ public class EntityKeyMapping { return String.format("((%s is not null and %s)", field, stringOperationQuery); } + private List getListValuesWithoutQuote(String value) { + return List.of(value.replaceAll("'", "").replaceAll("\"", "").trim().split("\\s*,\\s*")); + } + private String buildNumericPredicateQuery(QueryContext ctx, String field, NumericFilterPredicate numericFilterPredicate) { String paramName = getNextParameterName(field); ctx.addDoubleParameter(paramName, numericFilterPredicate.getValue().getValue()); From 4bdc93f33ecad95869970538d159834e13228a21 Mon Sep 17 00:00:00 2001 From: van-vanich Date: Fri, 5 Nov 2021 17:51:05 +0200 Subject: [PATCH 7/8] removed quote and double quote when all values have the same way. Also, fix the delete quote, when the value has a quote. Also, added a test to check this method. --- .../dao/sql/query/EntityKeyMapping.java | 23 ++++- .../dao/sql/query/EntityKeyMappingTest.java | 88 +++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 7a86431da0..c653d77004 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -578,8 +578,27 @@ public class EntityKeyMapping { return String.format("((%s is not null and %s)", field, stringOperationQuery); } - private List getListValuesWithoutQuote(String value) { - return List.of(value.replaceAll("'", "").replaceAll("\"", "").trim().split("\\s*,\\s*")); + protected List getListValuesWithoutQuote(String value) { + List splitValues = List.of(value.trim().split("\\s*,\\s*")); + List result = new ArrayList<>(); + char lastWayInputValue = '#'; + for (String str : splitValues) { + char startWith = str.charAt(0); + char endWith = str.charAt(str.length() - 1); + + // if first value is not quote, so we return values after split + if (startWith != '\'' && startWith != '"') return splitValues; + + // if value is not in quote, so we return values after split + if (startWith != endWith) return splitValues; + + // if different way values, so don't replace quote and return values after split + if (lastWayInputValue != '#' && startWith != lastWayInputValue) return splitValues; + + result.add(str.substring(1, str.length() - 1)); + lastWayInputValue = startWith; + } + return result; } private String buildNumericPredicateQuery(QueryContext ctx, String field, NumericFilterPredicate numericFilterPredicate) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java new file mode 100644 index 0000000000..c914bce1c1 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java @@ -0,0 +1,88 @@ +package org.thingsboard.server.dao.sql.query; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +@RunWith(SpringRunner.class ) +@SpringBootTest(classes = EntityKeyMapping.class) +public class EntityKeyMappingTest { + + @Autowired + private EntityKeyMapping entityKeyMapping; + + private static final List result = List.of("device1", "device2", "device3"); + + @Test + public void testSplitToList() { + String value = "device1, device2, device3"; + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + } + + @Test + public void testReplaceSingleQuote() { + String value = "'device1', 'device2', 'device3'"; + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + } + + @Test + public void testReplaceDoubleQuote() { + String value = "\"device1\", \"device2\", \"device3\""; + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + } + + @Test + public void testSplitWithoutSpace() { + String value = "\"device1\" , \"device2\" , \"device3\""; + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + } + + @Test + public void testSaveSpacesBetweenString() { + String value = "device 1 , device 2 , device 3"; + List result = List.of("device 1", "device 2", "device 3"); + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + } + + @Test + public void testSaveQuoteInString() { + String value = "device ''1 , device \"\"2 , device \"'3"; + List result = List.of("device ''1", "device \"\"2", "device \"'3"); + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + } + + @Test + public void testNotDeleteQuoteWhenDifferentStyle() { + + String value = "\"device1\", 'device2', \"device3\""; + List result = List.of("\"device1\"", "'device2'", "\"device3\""); + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + + value = "'device1', \"device2\", \"device3\""; + result = List.of("'device1'", "\"device2\"", "\"device3\""); + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + + value = "device1, 'device2', \"device3\""; + result = List.of("device1", "'device2'", "\"device3\""); + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + + + value = "'device1', device2, \"device3\""; + result = List.of("'device1'", "device2", "\"device3\""); + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + + value = "device1, \"device2\", \"device3\""; + result = List.of("device1", "\"device2\"", "\"device3\""); + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + + + value = "\"device1\", device2, \"device3\""; + result = List.of("\"device1\"", "device2", "\"device3\""); + Assert.assertEquals(entityKeyMapping.getListValuesWithoutQuote(value), result); + } +} \ No newline at end of file From 132ace407c16fe1b51223b79148c35be72c38fa9 Mon Sep 17 00:00:00 2001 From: van-vanich Date: Thu, 25 Nov 2021 15:23:54 +0200 Subject: [PATCH 8/8] fix issue with forgotten license and fix the old test, which doesn't work because of a new feature --- .../server/dao/service/BaseEntityServiceTest.java | 8 ++++++++ .../dao/sql/query/EntityKeyMappingTest.java | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java index e58191b60d..9140c457e6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java @@ -1307,6 +1307,14 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { notEqualStrings.add(operationName); containsStrings.add(operationName); break; + case IN: + notEqualStrings.add(operationName); + notContainsStrings.add(operationName); + break; + case NOT_IN: + notEqualStrings.add(operationName); + notContainsStrings.add(operationName); + break; } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java index c914bce1c1..01b41121e4 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityKeyMappingTest.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.dao.sql.query; import org.junit.Assert;