Repository: finos/tracdap (language: python)

Issue:
It is not possible to search without a search expression
## Bug Report

### Steps to Reproduce:
Write a search request without a search expression:

```
const searchRequest = trac.api.MetadataSearchRequest.create({
        tenant: tenant,
        searchParams: {
            searchAsOf: searchAsOf,
            objectType: trac.ObjectType.DATA
        }
    })
```

or

```
const searchRequest = trac.api.MetadataSearchRequest.create({
        tenant: tenant,
        searchParams: {
            searchAsOf: searchAsOf,
            objectType: trac.ObjectType.DATA,
            search: undefined
        }
    })
```

### Expected Result:
Without a search term the request should bring back a full list of objects without filtering applied.

### Actual Result:
The request errors.

### Environment:

`trac-web-api": "file:../../../../dev/trac/trac-web-api-0.3.1.tgz`


Discussion / hints:
Duplicate of #45 

Fix patch:
diff --git a/tracdap-api/tracdap-metadata/src/main/proto/tracdap/metadata/search.proto b/tracdap-api/tracdap-metadata/src/main/proto/tracdap/metadata/search.proto
index 104bdc04b..5c10e270f 100644
--- a/tracdap-api/tracdap-metadata/src/main/proto/tracdap/metadata/search.proto
+++ b/tracdap-api/tracdap-metadata/src/main/proto/tracdap/metadata/search.proto
@@ -233,7 +233,11 @@ message SearchParameters {
   /// The type of object to search for
   ObjectType objectType = 1;
 
-  /// A search expression based on tag attributes
+  /**
+   * A search expression based on tag attributes.
+   *
+   * This field is optional. If no search parameters are given, then all objects are returned.
+   */
   SearchExpression search = 2;
 
   /**
diff --git a/tracdap-libs/tracdap-lib-validation/src/main/java/org/finos/tracdap/common/validation/static_/SearchValidator.java b/tracdap-libs/tracdap-lib-validation/src/main/java/org/finos/tracdap/common/validation/static_/SearchValidator.java
index 6502475e1..5cd289592 100644
--- a/tracdap-libs/tracdap-lib-validation/src/main/java/org/finos/tracdap/common/validation/static_/SearchValidator.java
+++ b/tracdap-libs/tracdap-lib-validation/src/main/java/org/finos/tracdap/common/validation/static_/SearchValidator.java
@@ -115,7 +115,7 @@ public static ValidationContext searchParameters(SearchParameters msg, Validatio
         // Note - there is an open issue to relax this, i.e. allow searches for all objects of a given type
 
         ctx = ctx.push(SP_SEARCH)
-                .apply(CommonValidators::required)
+                .apply(CommonValidators::optional)
                 .apply(SearchValidator::searchExpression, SearchExpression.class)
                 .pop();
 
diff --git a/tracdap-services/tracdap-svc-meta/src/main/java/org/finos/tracdap/svc/meta/dal/jdbc/JdbcSearchQueryBuilder.java b/tracdap-services/tracdap-svc-meta/src/main/java/org/finos/tracdap/svc/meta/dal/jdbc/JdbcSearchQueryBuilder.java
index f94f00669..4f276779e 100644
--- a/tracdap-services/tracdap-svc-meta/src/main/java/org/finos/tracdap/svc/meta/dal/jdbc/JdbcSearchQueryBuilder.java
+++ b/tracdap-services/tracdap-svc-meta/src/main/java/org/finos/tracdap/svc/meta/dal/jdbc/JdbcSearchQueryBuilder.java
@@ -101,7 +101,7 @@ JdbcSearchQuery buildCommonSearchQuery(
         // Build query parts for the main search expression and version / temporal handling
 
         var queryParts = new JdbcSearchQuery(0, 0, List.of());
-        queryParts = buildSearchExpr(queryParts, searchParameters.getSearch());
+        queryParts = buildSearchExpr(queryParts, searchParameters.hasSearch() ? searchParameters.getSearch() : null);
         queryParts = buildAsOfCondition(queryParts, searchParameters);
         queryParts = buildPriorVersions(queryParts, searchParameters);
         queryParts = buildPriorTags(queryParts, searchParameters);
@@ -151,6 +151,10 @@ JdbcSearchQuery buildSearchQueryFromTemplate(
 
     JdbcSearchQuery buildSearchExpr(JdbcSearchQuery baseQuery, SearchExpression searchExpr) {
 
+        // If there is no search condition, do not add any conditions to the base query
+        if (searchExpr == null)
+            return baseQuery;
+
         switch (searchExpr.getExprCase()) {
 
             case LOGICAL:


Test patch:
diff --git a/tracdap-services/tracdap-svc-meta/src/test/java/org/finos/tracdap/svc/meta/api/MetadataSearchApiTest.java b/tracdap-services/tracdap-svc-meta/src/test/java/org/finos/tracdap/svc/meta/api/MetadataSearchApiTest.java
index 838b55c51..4b73dd8e0 100644
--- a/tracdap-services/tracdap-svc-meta/src/test/java/org/finos/tracdap/svc/meta/api/MetadataSearchApiTest.java
+++ b/tracdap-services/tracdap-svc-meta/src/test/java/org/finos/tracdap/svc/meta/api/MetadataSearchApiTest.java
@@ -33,11 +33,13 @@
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.EnumSource;
 
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
 import static org.finos.tracdap.common.metadata.MetadataCodec.encodeNativeObject;
 import static org.finos.tracdap.common.metadata.MetadataCodec.encodeValue;
 import static org.finos.tracdap.test.meta.TestData.*;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
 
 
 abstract class MetadataSearchApiTest {
@@ -93,6 +95,60 @@ void setup() {
         }
     }
 
+    @Test
+    void trivialSearch() {
+
+        var someAttr = "trivialSearch_WHICH_DROIDS";
+
+        var obj1 = TestData.dummyDataDef();
+        var obj2 = TestData.dummyDataDef();
+
+        var tag1 = Tag.newBuilder()
+                .setDefinition(obj1)
+                .putAttrs(someAttr, encodeValue("alpha"))
+                .build();
+
+        var tag2 = Tag.newBuilder()
+                .setDefinition(obj2)
+                .putAttrs(someAttr, encodeValue("beta"))
+                .build();
+
+        var save1 = MetadataWriteRequest.newBuilder()
+                .setTenant(TEST_TENANT)
+                .setObjectType(ObjectType.DATA)
+                .setDefinition(obj1)
+                .addAllTagUpdates(tagUpdatesForAttrs(tag1.getAttrsMap()))
+                .build();
+
+        var save2 = MetadataWriteRequest.newBuilder()
+                .setTenant(TEST_TENANT)
+                .setObjectType(ObjectType.DATA)
+                .setDefinition(obj2)
+                .addAllTagUpdates(tagUpdatesForAttrs(tag2.getAttrsMap()))
+                .build();
+
+        var objectIds = Stream.of(save1, save2)
+                .map(s -> writeApi.createObject(s).getObjectId())
+                .collect(Collectors.toSet());
+
+        var searchRequest = MetadataSearchRequest.newBuilder()
+                .setTenant(TEST_TENANT)
+                .setSearchParams(
+                        SearchParameters.newBuilder()
+                                .setObjectType(ObjectType.DATA)
+                )
+                .build();
+
+        var searchResult = searchApi.search(searchRequest);
+        var foundIds = searchResult.getSearchResultList().stream()
+                        .map(s -> s.getHeader().getObjectId())
+                        .collect(Collectors.toSet());
+
+        // The metadata system can contain objects from other tests.
+        // We check, that we got IDs of the objects from this test.
+        assertTrue(foundIds.containsAll(objectIds));
+    }
+
     @Test
     void basicSearch() {
 
@@ -578,17 +634,6 @@ void invalidSearch_nullParams() {
         // noinspection ResultOfMethodCallIgnored
         var error = assertThrows(StatusRuntimeException.class, () -> searchApi.search(searchRequest));
         assertEquals(Status.Code.INVALID_ARGUMENT, error.getStatus().getCode());
-
-        var searchRequest2 = MetadataSearchRequest.newBuilder()
-                .setTenant(TEST_TENANT)
-                .setSearchParams(SearchParameters.newBuilder()
-                .setObjectType(ObjectType.DATA)
-                .clearSearch())
-                .build();
-
-        // noinspection ResultOfMethodCallIgnored
-        var error2 = assertThrows(StatusRuntimeException.class, () -> searchApi.search(searchRequest2));
-        assertEquals(Status.Code.INVALID_ARGUMENT, error2.getStatus().getCode());
     }
 
     @Test