Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public CollectorInstancesResource(CollectorInstanceService collectorInstanceServ
this.fleetService = fleetService;
this.sourceService = sourceService;
this.txnLogService = txnLogService;
this.dbQueryCreator = new DbQueryCreator(CollectorInstanceDTO.FIELD_INSTANCE_UID, ATTRIBUTES, computedFieldRegistry);
this.dbQueryCreator = new DbQueryCreator("hostname", ATTRIBUTES, computedFieldRegistry);
this.collectorsConfigService = collectorsConfigService;
this.auditEventSender = auditEventSender;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,25 @@ public SearchQueryParser(@Nonnull String defaultField,
this.dbFieldMapping = allowedFieldsWithMapping;
}

/**
* Constructs a new parser with a list of attribute declarations.
* This form supports more complex mappings for attribute declarations than some of the other constructors.
*
* @param defaultField the name of the default field (already mapped)
* @param attributes the list of full entity attributes to use.
*/
public SearchQueryParser(@Nonnull String defaultField,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add JavaDoc explaining the behavior? The other constructor require the default field to be already mapped, this is different here.

@Nonnull final List<EntityAttribute> attributes) {

this.defaultField = requireNonNull(defaultField);
this.defaultFieldKey = SearchQueryField.create(defaultField, STRING);
this.dbFieldMapping = DbFieldMappingCreator.createFromEntityAttributes(attributes);
final SearchQueryField resolvedDefault = this.dbFieldMapping.get(requireNonNull(defaultField));
if (resolvedDefault != null) {
this.defaultField = resolvedDefault.getDbField();
this.defaultFieldKey = resolvedDefault;
} else {
this.defaultField = defaultField;
this.defaultFieldKey = SearchQueryField.create(defaultField, STRING);
}
}

@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.mongodb.MongoClientSettings;
import org.apache.commons.lang3.tuple.Pair;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.graylog2.rest.resources.entities.EntityAttribute;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.jupiter.api.Test;

import java.io.UnsupportedEncodingException;
import java.util.List;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
Expand All @@ -42,6 +47,12 @@

public class SearchQueryParserTest {

private static final CodecRegistry CODEC_REGISTRY = MongoClientSettings.getDefaultCodecRegistry();

private String toJson(Bson bson) {
return bson.toBsonDocument(BsonDocument.class, CODEC_REGISTRY).toJson();
}

@Test
void explicitAllowedField() {
SearchQueryParser parser = new SearchQueryParser("defaultfield", ImmutableSet.of("name", "id"));
Expand Down Expand Up @@ -371,4 +382,48 @@ void objectIdValuesSupported() {
);

}

@Test
void unqualifiedSearchUsesDefaultFieldBsonFilterCreator() {
final List<EntityAttribute> attributes = List.of(
EntityAttribute.builder().id("hostname").title("Hostname")
.dbField("non_identifying_attributes")
.bsonFilterCreator(AttributeFieldFilters.attributeArray("host.name"))
.sortable(true).searchable(true).build(),
EntityAttribute.builder().id("instance_uid").title("Instance UID")
.sortable(true).searchable(true).build()
);

final SearchQueryParser parser = new SearchQueryParser("hostname", attributes);

// Unqualified search: bare term without field prefix
final SearchQuery searchQuery = parser.parse("server01");
final List<Bson> filters = searchQuery.toBsonFilterList();

assertThat(filters).hasSize(1);
final String json = toJson(filters.getFirst());

// Must use $elemMatch on the attribute array, not a plain regex on "hostname"
assertThat(json).contains("$elemMatch");
assertThat(json).contains("non_identifying_attributes");
assertThat(json).contains("\"key\": \"host.name\"");
assertThat(json).contains("$regularExpression");
}

@Test
void qualifiedAndUnqualifiedSearchProduceSameFilterForDefaultField() {
final List<EntityAttribute> attributes = List.of(
EntityAttribute.builder().id("hostname").title("Hostname")
.dbField("non_identifying_attributes")
.bsonFilterCreator(AttributeFieldFilters.attributeArray("host.name"))
.sortable(true).searchable(true).build()
);

final SearchQueryParser parser = new SearchQueryParser("hostname", attributes);

final String unqualifiedJson = toJson(parser.parse("server01").toBsonFilterList().getFirst());
final String qualifiedJson = toJson(parser.parse("hostname:server01").toBsonFilterList().getFirst());

assertThat(unqualifiedJson).isEqualTo(qualifiedJson);
}
}
Loading