Commit 3420eeb9d71e6dc542004d4dc5c7f75fc2d0fe0b
Committed by
GitHub
1 parent
bc6efa5e
[3.3] [PROD-800] Fix empty search result when query is empty (#4209)
* Fix empty search result when query is empty * Refactor
Showing
2 changed files
with
165 additions
and
12 deletions
@@ -519,6 +519,9 @@ public class EntityKeyMapping { | @@ -519,6 +519,9 @@ public class EntityKeyMapping { | ||
519 | String operationField = field; | 519 | String operationField = field; |
520 | String paramName = getNextParameterName(field); | 520 | String paramName = getNextParameterName(field); |
521 | String value = stringFilterPredicate.getValue().getValue(); | 521 | String value = stringFilterPredicate.getValue().getValue(); |
522 | + if (value.isEmpty()) { | ||
523 | + return null; | ||
524 | + } | ||
522 | String stringOperationQuery = ""; | 525 | String stringOperationQuery = ""; |
523 | if (stringFilterPredicate.isIgnoreCase()) { | 526 | if (stringFilterPredicate.isIgnoreCase()) { |
524 | value = value.toLowerCase(); | 527 | value = value.toLowerCase(); |
@@ -526,30 +529,26 @@ public class EntityKeyMapping { | @@ -526,30 +529,26 @@ public class EntityKeyMapping { | ||
526 | } | 529 | } |
527 | switch (stringFilterPredicate.getOperation()) { | 530 | switch (stringFilterPredicate.getOperation()) { |
528 | case EQUAL: | 531 | case EQUAL: |
529 | - stringOperationQuery = String.format("%s = :%s) or (%s is null and :%s = '')", operationField, paramName, operationField, paramName); | 532 | + stringOperationQuery = String.format("%s = :%s)", operationField, paramName); |
530 | break; | 533 | break; |
531 | case NOT_EQUAL: | 534 | case NOT_EQUAL: |
532 | - stringOperationQuery = String.format("%s != :%s) or (%s is null and :%s != '')", operationField, paramName, operationField, paramName); | 535 | + stringOperationQuery = String.format("%s != :%s or %s is null)", operationField, paramName, operationField); |
533 | break; | 536 | break; |
534 | case STARTS_WITH: | 537 | case STARTS_WITH: |
535 | value += "%"; | 538 | value += "%"; |
536 | - stringOperationQuery = String.format("%s like :%s) or (%s is null and :%s = '%%')", operationField, paramName, operationField, paramName); | 539 | + stringOperationQuery = String.format("%s like :%s)", operationField, paramName); |
537 | break; | 540 | break; |
538 | case ENDS_WITH: | 541 | case ENDS_WITH: |
539 | value = "%" + value; | 542 | value = "%" + value; |
540 | - stringOperationQuery = String.format("%s like :%s) or (%s is null and :%s = '%%')", operationField, paramName, operationField, paramName); | 543 | + stringOperationQuery = String.format("%s like :%s)", operationField, paramName); |
541 | break; | 544 | break; |
542 | case CONTAINS: | 545 | case CONTAINS: |
543 | - if (value.length() > 0) { | ||
544 | - value = "%" + value + "%"; | ||
545 | - } | ||
546 | - stringOperationQuery = String.format("%s like :%s) or (%s is null and :%s = '')", operationField, paramName, operationField, paramName); | 546 | + value = "%" + value + "%"; |
547 | + stringOperationQuery = String.format("%s like :%s)", operationField, paramName); | ||
547 | break; | 548 | break; |
548 | case NOT_CONTAINS: | 549 | case NOT_CONTAINS: |
549 | - if (value.length() > 0) { | ||
550 | - value = "%" + value + "%"; | ||
551 | - } | ||
552 | - stringOperationQuery = String.format("%s not like :%s) or (%s is null and :%s != '')", operationField, paramName, operationField, paramName); | 550 | + value = "%" + value + "%"; |
551 | + stringOperationQuery = String.format("%s not like :%s or %s is null)", operationField, paramName, operationField); | ||
553 | break; | 552 | break; |
554 | } | 553 | } |
555 | ctx.addStringParameter(paramName, value); | 554 | ctx.addStringParameter(paramName, value); |
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service; | @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service; | ||
17 | 17 | ||
18 | import com.google.common.util.concurrent.Futures; | 18 | import com.google.common.util.concurrent.Futures; |
19 | import com.google.common.util.concurrent.ListenableFuture; | 19 | import com.google.common.util.concurrent.ListenableFuture; |
20 | +import org.apache.commons.lang3.RandomUtils; | ||
20 | import org.junit.After; | 21 | import org.junit.After; |
21 | import org.junit.Assert; | 22 | import org.junit.Assert; |
22 | import org.junit.Before; | 23 | import org.junit.Before; |
@@ -56,6 +57,7 @@ import org.thingsboard.server.common.data.query.KeyFilter; | @@ -56,6 +57,7 @@ import org.thingsboard.server.common.data.query.KeyFilter; | ||
56 | import org.thingsboard.server.common.data.query.NumericFilterPredicate; | 57 | import org.thingsboard.server.common.data.query.NumericFilterPredicate; |
57 | import org.thingsboard.server.common.data.query.RelationsQueryFilter; | 58 | import org.thingsboard.server.common.data.query.RelationsQueryFilter; |
58 | import org.thingsboard.server.common.data.query.StringFilterPredicate; | 59 | import org.thingsboard.server.common.data.query.StringFilterPredicate; |
60 | +import org.thingsboard.server.common.data.query.StringFilterPredicate.StringOperation; | ||
59 | import org.thingsboard.server.common.data.relation.EntityRelation; | 61 | import org.thingsboard.server.common.data.relation.EntityRelation; |
60 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; | 62 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
61 | import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; | 63 | import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; |
@@ -72,6 +74,9 @@ import java.util.List; | @@ -72,6 +74,9 @@ import java.util.List; | ||
72 | import java.util.Random; | 74 | import java.util.Random; |
73 | import java.util.concurrent.ExecutionException; | 75 | import java.util.concurrent.ExecutionException; |
74 | import java.util.stream.Collectors; | 76 | import java.util.stream.Collectors; |
77 | +import java.util.stream.Stream; | ||
78 | + | ||
79 | +import static org.junit.Assert.assertEquals; | ||
75 | 80 | ||
76 | public abstract class BaseEntityServiceTest extends AbstractServiceTest { | 81 | public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
77 | 82 | ||
@@ -542,6 +547,155 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { | @@ -542,6 +547,155 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { | ||
542 | } | 547 | } |
543 | 548 | ||
544 | @Test | 549 | @Test |
550 | + public void testFindEntityDataByQuery_operationEqual_emptySearchQuery() { | ||
551 | + List<Device> devices = createMockDevices(10); | ||
552 | + devices.get(0).setLabel(""); | ||
553 | + devices.get(1).setLabel(null); | ||
554 | + devices.forEach(deviceService::saveDevice); | ||
555 | + | ||
556 | + String searchQuery = ""; | ||
557 | + EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.EQUAL, searchQuery); | ||
558 | + | ||
559 | + PageData<EntityData> result = searchEntities(query); | ||
560 | + assertEquals(devices.size(), result.getTotalElements()); | ||
561 | + } | ||
562 | + | ||
563 | + @Test | ||
564 | + public void testFindEntityDataByQuery_operationNotEqual() { | ||
565 | + List<Device> devices = createMockDevices(10); | ||
566 | + devices.get(0).setLabel(""); | ||
567 | + devices.get(1).setLabel(null); | ||
568 | + devices.forEach(deviceService::saveDevice); | ||
569 | + | ||
570 | + String searchQuery = devices.get(2).getLabel(); | ||
571 | + EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.NOT_EQUAL, searchQuery); | ||
572 | + | ||
573 | + PageData<EntityData> result = searchEntities(query); | ||
574 | + assertEquals(devices.size() - 1, result.getTotalElements()); | ||
575 | + } | ||
576 | + | ||
577 | + @Test | ||
578 | + public void testFindEntityDataByQuery_operationNotEqual_emptySearchQuery() { | ||
579 | + List<Device> devices = createMockDevices(10); | ||
580 | + devices.get(0).setLabel(""); | ||
581 | + devices.get(1).setLabel(null); | ||
582 | + devices.forEach(deviceService::saveDevice); | ||
583 | + | ||
584 | + String searchQuery = ""; | ||
585 | + EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.NOT_EQUAL, searchQuery); | ||
586 | + | ||
587 | + PageData<EntityData> result = searchEntities(query); | ||
588 | + assertEquals(devices.size(), result.getTotalElements()); | ||
589 | + } | ||
590 | + | ||
591 | + @Test | ||
592 | + public void testFindEntityDataByQuery_operationStartsWith_emptySearchQuery() { | ||
593 | + List<Device> devices = createMockDevices(10); | ||
594 | + devices.get(0).setLabel(""); | ||
595 | + devices.get(1).setLabel(null); | ||
596 | + devices.forEach(deviceService::saveDevice); | ||
597 | + | ||
598 | + String searchQuery = ""; | ||
599 | + EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.STARTS_WITH, searchQuery); | ||
600 | + | ||
601 | + PageData<EntityData> result = searchEntities(query); | ||
602 | + assertEquals(devices.size(), result.getTotalElements()); | ||
603 | + } | ||
604 | + | ||
605 | + @Test | ||
606 | + public void testFindEntityDataByQuery_operationEndsWith_emptySearchQuery() { | ||
607 | + List<Device> devices = createMockDevices(10); | ||
608 | + devices.get(0).setLabel(""); | ||
609 | + devices.get(1).setLabel(null); | ||
610 | + devices.forEach(deviceService::saveDevice); | ||
611 | + | ||
612 | + String searchQuery = ""; | ||
613 | + EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.ENDS_WITH, searchQuery); | ||
614 | + | ||
615 | + PageData<EntityData> result = searchEntities(query); | ||
616 | + assertEquals(devices.size(), result.getTotalElements()); | ||
617 | + } | ||
618 | + | ||
619 | + @Test | ||
620 | + public void testFindEntityDataByQuery_operationContains_emptySearchQuery() { | ||
621 | + List<Device> devices = createMockDevices(10); | ||
622 | + devices.get(0).setLabel(""); | ||
623 | + devices.get(1).setLabel(null); | ||
624 | + devices.forEach(deviceService::saveDevice); | ||
625 | + | ||
626 | + String searchQuery = ""; | ||
627 | + EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.CONTAINS, searchQuery); | ||
628 | + | ||
629 | + PageData<EntityData> result = searchEntities(query); | ||
630 | + assertEquals(devices.size(), result.getTotalElements()); | ||
631 | + } | ||
632 | + | ||
633 | + @Test | ||
634 | + public void testFindEntityDataByQuery_operationNotContains() { | ||
635 | + List<Device> devices = createMockDevices(10); | ||
636 | + devices.get(0).setLabel(""); | ||
637 | + devices.get(1).setLabel(null); | ||
638 | + devices.forEach(deviceService::saveDevice); | ||
639 | + | ||
640 | + String searchQuery = "label-"; | ||
641 | + EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.NOT_CONTAINS, searchQuery); | ||
642 | + | ||
643 | + PageData<EntityData> result = searchEntities(query); | ||
644 | + assertEquals(2, result.getTotalElements()); | ||
645 | + } | ||
646 | + | ||
647 | + @Test | ||
648 | + public void testFindEntityDataByQuery_operationNotContains_emptySearchQuery() { | ||
649 | + List<Device> devices = createMockDevices(10); | ||
650 | + devices.get(0).setLabel(""); | ||
651 | + devices.get(1).setLabel(null); | ||
652 | + devices.forEach(deviceService::saveDevice); | ||
653 | + | ||
654 | + String searchQuery = ""; | ||
655 | + EntityDataQuery query = createDeviceSearchQuery("label", StringOperation.NOT_CONTAINS, searchQuery); | ||
656 | + | ||
657 | + PageData<EntityData> result = searchEntities(query); | ||
658 | + assertEquals(devices.size(), result.getTotalElements()); | ||
659 | + } | ||
660 | + | ||
661 | + private PageData<EntityData> searchEntities(EntityDataQuery query) { | ||
662 | + return entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query); | ||
663 | + } | ||
664 | + | ||
665 | + private EntityDataQuery createDeviceSearchQuery(String deviceField, StringOperation operation, String searchQuery) { | ||
666 | + DeviceTypeFilter deviceTypeFilter = new DeviceTypeFilter(); | ||
667 | + deviceTypeFilter.setDeviceType("default"); | ||
668 | + deviceTypeFilter.setDeviceNameFilter(""); | ||
669 | + | ||
670 | + EntityDataSortOrder sortOrder = new EntityDataSortOrder( | ||
671 | + new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC | ||
672 | + ); | ||
673 | + EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, sortOrder); | ||
674 | + List<EntityKey> entityFields = Arrays.asList( | ||
675 | + new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), | ||
676 | + new EntityKey(EntityKeyType.ENTITY_FIELD, "label") | ||
677 | + ); | ||
678 | + | ||
679 | + List<KeyFilter> keyFilters = createStringKeyFilters(deviceField, EntityKeyType.ENTITY_FIELD, operation, searchQuery); | ||
680 | + | ||
681 | + return new EntityDataQuery(deviceTypeFilter, pageLink, entityFields, null, keyFilters); | ||
682 | + } | ||
683 | + | ||
684 | + private List<Device> createMockDevices(int count) { | ||
685 | + return Stream.iterate(1, i -> i + 1) | ||
686 | + .map(i -> { | ||
687 | + Device device = new Device(); | ||
688 | + device.setTenantId(tenantId); | ||
689 | + device.setName("Device " + i); | ||
690 | + device.setType("default"); | ||
691 | + device.setLabel("label-" + RandomUtils.nextInt(100, 10000)); | ||
692 | + return device; | ||
693 | + }) | ||
694 | + .limit(count) | ||
695 | + .collect(Collectors.toList()); | ||
696 | + } | ||
697 | + | ||
698 | + @Test | ||
545 | public void testFindEntityDataByQueryWithAttributes() throws ExecutionException, InterruptedException { | 699 | public void testFindEntityDataByQueryWithAttributes() throws ExecutionException, InterruptedException { |
546 | 700 | ||
547 | List<EntityKeyType> attributesEntityTypes = new ArrayList<>(Arrays.asList(EntityKeyType.CLIENT_ATTRIBUTE, EntityKeyType.SHARED_ATTRIBUTE, EntityKeyType.SERVER_ATTRIBUTE)); | 701 | List<EntityKeyType> attributesEntityTypes = new ArrayList<>(Arrays.asList(EntityKeyType.CLIENT_ATTRIBUTE, EntityKeyType.SHARED_ATTRIBUTE, EntityKeyType.SERVER_ATTRIBUTE)); |