Skip to content

Commit

Permalink
switch DateRangeQuery to use time.Time instead of string
Browse files Browse the repository at this point in the history
as we are a Go library is this the much more natural way to
express such queries.

support for strings is still supported through json marshal
and unmarshal, as well as inside query string queries

as before we use the package level QueryDateTimeParser to
deterimine which date time parser to use for parsing

only serializing out to json, we consult a new package
variable: QueryDateTimeFormat

this addresses the longstanding PR blevesearch#255
  • Loading branch information
mschoch committed Sep 29, 2016
1 parent a265218 commit ee17941
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 91 deletions.
18 changes: 9 additions & 9 deletions index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1280,9 +1280,9 @@ func TestDateTimeFieldMappingIssue287(t *testing.T) {
}

// search range across all docs
start := now.Add(-4 * time.Hour).Format(time.RFC3339)
end := now.Format(time.RFC3339)
sreq := NewSearchRequest(NewDateRangeQuery(&start, &end))
start := now.Add(-4 * time.Hour)
end := now
sreq := NewSearchRequest(NewDateRangeQuery(start, end))
sres, err := index.Search(sreq)
if err != nil {
t.Fatal(err)
Expand All @@ -1292,9 +1292,9 @@ func TestDateTimeFieldMappingIssue287(t *testing.T) {
}

// search range includes only oldest
start = now.Add(-4 * time.Hour).Format(time.RFC3339)
end = now.Add(-121 * time.Minute).Format(time.RFC3339)
sreq = NewSearchRequest(NewDateRangeQuery(&start, &end))
start = now.Add(-4 * time.Hour)
end = now.Add(-121 * time.Minute)
sreq = NewSearchRequest(NewDateRangeQuery(start, end))
sres, err = index.Search(sreq)
if err != nil {
t.Fatal(err)
Expand All @@ -1307,9 +1307,9 @@ func TestDateTimeFieldMappingIssue287(t *testing.T) {
}

// search range includes only newest
start = now.Add(-61 * time.Minute).Format(time.RFC3339)
end = now.Format(time.RFC3339)
sreq = NewSearchRequest(NewDateRangeQuery(&start, &end))
start = now.Add(-61 * time.Minute)
end = now
sreq = NewSearchRequest(NewDateRangeQuery(start, end))
sres, err = index.Search(sreq)
if err != nil {
t.Fatal(err)
Expand Down
10 changes: 7 additions & 3 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@

package bleve

import "github.com/blevesearch/bleve/search/query"
import (
"time"

"github.com/blevesearch/bleve/search/query"
)

// NewBoolFieldQuery creates a new Query for boolean fields
func NewBoolFieldQuery(val bool) *query.BoolFieldQuery {
Expand Down Expand Up @@ -41,7 +45,7 @@ func NewConjunctionQuery(conjuncts ...query.Query) *query.ConjunctionQuery {
// Date strings are parsed using the DateTimeParser configured in the
// top-level config.QueryDateTimeParser
// Either, but not both endpoints can be nil.
func NewDateRangeQuery(start, end *string) *query.DateRangeQuery {
func NewDateRangeQuery(start, end time.Time) *query.DateRangeQuery {
return query.NewDateRangeQuery(start, end)
}

Expand All @@ -51,7 +55,7 @@ func NewDateRangeQuery(start, end *string) *query.DateRangeQuery {
// top-level config.QueryDateTimeParser
// Either, but not both endpoints can be nil.
// startInclusive and endInclusive control inclusion of the endpoints.
func NewDateRangeInclusiveQuery(start, end *string, startInclusive, endInclusive *bool) *query.DateRangeQuery {
func NewDateRangeInclusiveQuery(start, end time.Time, startInclusive, endInclusive *bool) *query.DateRangeQuery {
return query.NewDateRangeInclusiveQuery(start, end, startInclusive, endInclusive)
}

Expand Down
88 changes: 58 additions & 30 deletions search/query/query_date_range.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
package query

import (
"encoding/json"
"fmt"
"math"
"time"

"github.com/blevesearch/bleve/analysis/datetime_parsers/datetime_optional"
"github.com/blevesearch/bleve/index"
Expand All @@ -25,23 +27,64 @@ import (
// QueryDateTimeParser controls the default query date time parser
var QueryDateTimeParser = datetime_optional.Name

// QueryDateTimeFormat controls the format when Marshaling to JSON
var QueryDateTimeFormat = time.RFC3339

var cache = registry.NewCache()

type BleveQueryTime struct {
time.Time
}

func QueryTimeFromString(t string) (time.Time, error) {
dateTimeParser, err := cache.DateTimeParserNamed(QueryDateTimeParser)
if err != nil {
return time.Time{}, err
}
rv, err := dateTimeParser.ParseDateTime(t)
if err != nil {
return time.Time{}, err
}
return rv, nil
}

func (t *BleveQueryTime) MarshalJSON() ([]byte, error) {
tt := time.Time(t.Time)
return []byte(tt.Format(QueryDateTimeFormat)), nil
}

func (t *BleveQueryTime) UnmarshalJSON(data []byte) error {
var timeString string
err := json.Unmarshal(data, &timeString)
if err != nil {
return err
}
dateTimeParser, err := cache.DateTimeParserNamed(QueryDateTimeParser)
if err != nil {
return err
}
t.Time, err = dateTimeParser.ParseDateTime(timeString)
if err != nil {
return err
}
return nil
}

type DateRangeQuery struct {
Start *string `json:"start,omitempty"`
End *string `json:"end,omitempty"`
InclusiveStart *bool `json:"inclusive_start,omitempty"`
InclusiveEnd *bool `json:"inclusive_end,omitempty"`
Field string `json:"field,omitempty"`
Boost *Boost `json:"boost,omitempty"`
Start BleveQueryTime `json:"start,omitempty"`
End BleveQueryTime `json:"end,omitempty"`
InclusiveStart *bool `json:"inclusive_start,omitempty"`
InclusiveEnd *bool `json:"inclusive_end,omitempty"`
Field string `json:"field,omitempty"`
Boost *Boost `json:"boost,omitempty"`
}

// NewDateRangeQuery creates a new Query for ranges
// of date values.
// Date strings are parsed using the DateTimeParser configured in the
// top-level config.QueryDateTimeParser
// Either, but not both endpoints can be nil.
func NewDateRangeQuery(start, end *string) *DateRangeQuery {
func NewDateRangeQuery(start, end time.Time) *DateRangeQuery {
return NewDateRangeInclusiveQuery(start, end, nil, nil)
}

Expand All @@ -51,10 +94,10 @@ func NewDateRangeQuery(start, end *string) *DateRangeQuery {
// top-level config.QueryDateTimeParser
// Either, but not both endpoints can be nil.
// startInclusive and endInclusive control inclusion of the endpoints.
func NewDateRangeInclusiveQuery(start, end *string, startInclusive, endInclusive *bool) *DateRangeQuery {
func NewDateRangeInclusiveQuery(start, end time.Time, startInclusive, endInclusive *bool) *DateRangeQuery {
return &DateRangeQuery{
Start: start,
End: end,
Start: BleveQueryTime{start},
End: BleveQueryTime{end},
InclusiveStart: startInclusive,
InclusiveEnd: endInclusive,
}
Expand All @@ -70,7 +113,6 @@ func (q *DateRangeQuery) SetField(f string) {
}

func (q *DateRangeQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, explain bool) (search.Searcher, error) {

min, max, err := q.parseEndpoints()
if err != nil {
return nil, err
Expand All @@ -85,34 +127,20 @@ func (q *DateRangeQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, e
}

func (q *DateRangeQuery) parseEndpoints() (*float64, *float64, error) {
dateTimeParser, err := cache.DateTimeParserNamed(QueryDateTimeParser)
if err != nil {
return nil, nil, err
}

// now parse the endpoints
min := math.Inf(-1)
max := math.Inf(1)
if q.Start != nil && *q.Start != "" {
startTime, err := dateTimeParser.ParseDateTime(*q.Start)
if err != nil {
return nil, nil, err
}
min = numeric_util.Int64ToFloat64(startTime.UnixNano())
if !q.Start.IsZero() {
min = numeric_util.Int64ToFloat64(q.Start.UnixNano())
}
if q.End != nil && *q.End != "" {
endTime, err := dateTimeParser.ParseDateTime(*q.End)
if err != nil {
return nil, nil, err
}
max = numeric_util.Int64ToFloat64(endTime.UnixNano())
if !q.End.IsZero() {
max = numeric_util.Int64ToFloat64(q.End.UnixNano())
}

return &min, &max, nil
}

func (q *DateRangeQuery) Validate() error {
if q.Start == nil && q.Start == q.End {
if q.Start.IsZero() && q.End.IsZero() {
return fmt.Errorf("must specify start or end")
}
_, _, err := q.parseEndpoints()
Expand Down
25 changes: 21 additions & 4 deletions search/query/query_string.y
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
"strings"
"time"
)

func logDebugGrammar(format string, v ...interface{}) {
Expand Down Expand Up @@ -217,7 +218,11 @@ tSTRING tCOLON tGREATER tPHRASE {
phrase := $4

logDebugGrammar("FIELD - GREATER THAN DATE %s", phrase)
q := NewDateRangeInclusiveQuery(&phrase, nil, &minInclusive, nil)
minTime, err := QueryTimeFromString(phrase)
if err != nil {
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
}
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
q.SetField(field)
$$ = q
}
Expand All @@ -228,7 +233,11 @@ tSTRING tCOLON tGREATER tEQUAL tPHRASE {
phrase := $5

logDebugGrammar("FIELD - GREATER THAN OR EQUAL DATE %s", phrase)
q := NewDateRangeInclusiveQuery(&phrase, nil, &minInclusive, nil)
minTime, err := QueryTimeFromString(phrase)
if err != nil {
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
}
q := NewDateRangeInclusiveQuery(minTime, time.Time{}, &minInclusive, nil)
q.SetField(field)
$$ = q
}
Expand All @@ -239,7 +248,11 @@ tSTRING tCOLON tLESS tPHRASE {
phrase := $4

logDebugGrammar("FIELD - LESS THAN DATE %s", phrase)
q := NewDateRangeInclusiveQuery(nil, &phrase, nil, &maxInclusive)
maxTime, err := QueryTimeFromString(phrase)
if err != nil {
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
}
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
q.SetField(field)
$$ = q
}
Expand All @@ -250,7 +263,11 @@ tSTRING tCOLON tLESS tEQUAL tPHRASE {
phrase := $5

logDebugGrammar("FIELD - LESS THAN OR EQUAL DATE %s", phrase)
q := NewDateRangeInclusiveQuery(nil, &phrase, nil, &maxInclusive)
maxTime, err := QueryTimeFromString(phrase)
if err != nil {
yylex.(*lexerWrapper).lex.Error(fmt.Sprintf("invalid time: %v", err))
}
q := NewDateRangeInclusiveQuery(time.Time{}, maxTime, nil, &maxInclusive)
q.SetField(field)
$$ = q
};
Expand Down
Loading

0 comments on commit ee17941

Please sign in to comment.