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 @@ -19,9 +19,20 @@

package org.elasticsearch.index.query;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.support.QueryParsers;

import java.io.IOException;
import java.util.Objects;

/**
* Implements the wildcard search query. Supported wildcards are <tt>*</tt>, which
Expand All @@ -35,9 +46,9 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil

public static final String NAME = "wildcard";

private final String name;
private final String fieldName;

private final String wildcard;
private final String value;

private String rewrite;

Expand All @@ -51,24 +62,41 @@ public class WildcardQueryBuilder extends AbstractQueryBuilder<WildcardQueryBuil
* a Wildcard term should not start with one of the wildcards <tt>*</tt> or
* <tt>?</tt>.
*
* @param name The field name
* @param wildcard The wildcard query string
* @param fieldName The field name
* @param value The wildcard query string
*/
public WildcardQueryBuilder(String name, String wildcard) {
this.name = name;
this.wildcard = wildcard;
public WildcardQueryBuilder(String fieldName, String value) {
this.fieldName = fieldName;
this.value = value;
}

public String fieldName() {
return fieldName;
}

public String value() {
return value;
}

public WildcardQueryBuilder rewrite(String rewrite) {
this.rewrite = rewrite;
return this;
}

public String rewrite() {
return this.rewrite;
}

@Override
public String getName() {
return NAME;
}

@Override
public void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.startObject(name);
builder.field("wildcard", wildcard);
builder.startObject(fieldName);
builder.field("wildcard", value);
if (rewrite != null) {
builder.field("rewrite", rewrite);
}
Expand All @@ -78,7 +106,60 @@ public void doXContent(XContentBuilder builder, Params params) throws IOExceptio
}

@Override
public String getName() {
return NAME;
protected Query doToQuery(QueryParseContext parseContext) throws IOException {
String indexFieldName;
BytesRef valueBytes;

MappedFieldType fieldType = parseContext.fieldMapper(fieldName);
if (fieldType != null) {
indexFieldName = fieldType.names().indexName();
valueBytes = fieldType.indexedValueForSearch(value);
} else {
indexFieldName = fieldName;
valueBytes = new BytesRef(value);
}

WildcardQuery query = new WildcardQuery(new Term(indexFieldName, valueBytes));
MultiTermQuery.RewriteMethod rewriteMethod = QueryParsers.parseRewriteMethod(parseContext.parseFieldMatcher(), rewrite, null);
QueryParsers.setRewriteMethod(query, rewriteMethod);
return query;
}

@Override
public QueryValidationException validate() {
QueryValidationException validationException = null;
if (Strings.isEmpty(this.fieldName)) {
validationException = addValidationError("field name cannot be null or empty.", validationException);
}
if (this.value == null) {
Copy link

Choose a reason for hiding this comment

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

Would it make sense to check for empty string here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is not something we systematically do. The parser did not do this check, hence the reason why it is not in validate.

Copy link
Member

Choose a reason for hiding this comment

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

I think it would be interesting then to test what happens when you provide an empty string? is it something that we should accept or should we add validation for it?

Copy link
Member

Choose a reason for hiding this comment

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

did we test this? what happens with empty string? is it something that we should allow or maybe change and disallow?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The parser did allow for it. It does return a valid query.

Copy link
Member

Choose a reason for hiding this comment

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

the question is if that query makes sense, does it return documents? which ones?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it returns an empty result set.

Copy link
Member

Choose a reason for hiding this comment

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

ok lets leave it, it doesn't seem like a problem for now

validationException = addValidationError("wildcard cannot be null", validationException);
}
return validationException;
}

@Override
protected WildcardQueryBuilder doReadFrom(StreamInput in) throws IOException {
WildcardQueryBuilder wildcardQueryBuilder = new WildcardQueryBuilder(in.readString(), in.readString());
wildcardQueryBuilder.rewrite = in.readOptionalString();
return wildcardQueryBuilder;
}

@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldName);
out.writeString(value);
out.writeOptionalString(rewrite);
}

@Override
protected int doHashCode() {
return Objects.hash(fieldName, value, rewrite);
}

@Override
protected boolean doEquals(WildcardQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(value, other.value) &&
Objects.equals(rewrite, other.rewrite);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,15 @@

package org.elasticsearch.index.query;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.support.QueryParsers;

import java.io.IOException;

/**
*
*/
public class WildcardQueryParser extends BaseQueryParserTemp {
public class WildcardQueryParser extends BaseQueryParser {

@Inject
public WildcardQueryParser() {
Expand All @@ -45,15 +39,15 @@ public String[] names() {
}

@Override
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();

XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.FIELD_NAME) {
throw new QueryParsingException(parseContext, "[wildcard] query malformed, no field");
}
String fieldName = parser.currentName();
String rewriteMethod = null;
String rewrite = null;

String value = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
Expand All @@ -72,7 +66,7 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
} else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue();
} else if ("rewrite".equals(currentFieldName)) {
rewriteMethod = parser.textOrNull();
rewrite = parser.textOrNull();
} else if ("_name".equals(currentFieldName)) {
queryName = parser.text();
} else {
Expand All @@ -89,23 +83,10 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars
if (value == null) {
throw new QueryParsingException(parseContext, "No value specified for prefix query");
}

BytesRef valueBytes;
MappedFieldType fieldType = parseContext.fieldMapper(fieldName);
if (fieldType != null) {
fieldName = fieldType.names().indexName();
valueBytes = fieldType.indexedValueForSearch(value);
} else {
valueBytes = new BytesRef(value);
}

WildcardQuery wildcardQuery = new WildcardQuery(new Term(fieldName, valueBytes));
QueryParsers.setRewriteMethod(wildcardQuery, parseContext.parseFieldMatcher(), rewriteMethod);
wildcardQuery.setBoost(boost);
if (queryName != null) {
parseContext.addNamedQuery(queryName, wildcardQuery);
}
return wildcardQuery;
return new WildcardQueryBuilder(fieldName, value)
.rewrite(rewrite)
.boost(boost)
.queryName(queryName);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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.elasticsearch.index.query;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.support.QueryParsers;
import org.junit.Test;

import java.io.IOException;

import static org.hamcrest.Matchers.is;

public class WildcardQueryBuilderTest extends BaseQueryTestCase<WildcardQueryBuilder> {

@Override
protected WildcardQueryBuilder doCreateTestQueryBuilder() {
WildcardQueryBuilder query;

// mapped or unmapped field
String text = randomAsciiOfLengthBetween(1, 10);
if (randomBoolean()) {
query = new WildcardQueryBuilder(STRING_FIELD_NAME, text);
} else {
query = new WildcardQueryBuilder(randomAsciiOfLengthBetween(1, 10), text);
}
if (randomBoolean()) {
query.rewrite(randomFrom(getRandomRewriteMethod()));
}
return query;
}

@Override
protected Query doCreateExpectedQuery(WildcardQueryBuilder queryBuilder, QueryParseContext context) throws IOException {
String indexFieldName;
BytesRef valueBytes;

MappedFieldType fieldType = context.fieldMapper(queryBuilder.fieldName());
if (fieldType != null) {
indexFieldName = fieldType.names().indexName();
valueBytes = fieldType.indexedValueForSearch(queryBuilder.value());
} else {
indexFieldName = queryBuilder.fieldName();
valueBytes = new BytesRef(queryBuilder.value());
}

WildcardQuery expectedQuery = new WildcardQuery(new Term(indexFieldName, valueBytes));

//norelease fix to be removed to avoid NPE on unmapped fields
context.parseFieldMatcher(randomBoolean() ? ParseFieldMatcher.EMPTY : ParseFieldMatcher.STRICT);
MultiTermQuery.RewriteMethod rewriteMethod = QueryParsers.parseRewriteMethod(context.parseFieldMatcher(), queryBuilder.rewrite(), null);
QueryParsers.setRewriteMethod(expectedQuery, rewriteMethod);
return expectedQuery;
}

@Test
public void testValidate() {
WildcardQueryBuilder wildcardQueryBuilder = new WildcardQueryBuilder("", "text");
assertThat(wildcardQueryBuilder.validate().validationErrors().size(), is(1));

wildcardQueryBuilder = new WildcardQueryBuilder("field", null);
assertThat(wildcardQueryBuilder.validate().validationErrors().size(), is(1));

wildcardQueryBuilder = new WildcardQueryBuilder(null, null);
assertThat(wildcardQueryBuilder.validate().validationErrors().size(), is(2));

wildcardQueryBuilder = new WildcardQueryBuilder("field", "text");
assertNull(wildcardQueryBuilder.validate());
}

@Test
public void testEmptyValue() throws IOException {
QueryParseContext context = createContext();
context.setAllowUnmappedFields(true);

WildcardQueryBuilder wildcardQueryBuilder = new WildcardQueryBuilder(getRandomType(), "");
assertEquals(wildcardQueryBuilder.toQuery(context).getClass(), WildcardQuery.class);
}
}