blob: fea7cfb222913a05ae678694b0de9fd2ed57b4cf [file] [log] [blame]
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package db
import (
"fmt"
"io"
"strings"
"unicode"
)
// operation is the enum for possible query operations.
type operation rune
// Query operations.
// Only equals, lt, and gt can be specified in a query.
// The order of these operations is used by merge.
const (
equals operation = iota
ltgt
lt
gt
)
// A part is a single query part with a key, operator, and value.
type part struct {
key string
operator operation
// value and value2 hold the values to compare against.
value, value2 string
}
// sepToOperation maps runes to operation values.
var sepToOperation = map[byte]operation{
':': equals,
'<': lt,
'>': gt,
}
// parseWord parse a single query part (as returned by SplitWords with quoting and escaping already removed) into a part struct.
func parseWord(word string) (part, error) {
sepIndex := strings.IndexFunc(word, func(r rune) bool {
return r == ':' || r == '>' || r == '<' || unicode.IsSpace(r) || unicode.IsUpper(r)
})
if sepIndex < 0 {
return part{}, fmt.Errorf("query part %q is missing operator", word)
}
key, sep, value := word[:sepIndex], word[sepIndex], word[sepIndex+1:]
if oper, ok := sepToOperation[sep]; ok {
return part{key, oper, value, ""}, nil
}
return part{}, fmt.Errorf("query part %q has invalid key", word)
}
// merge merges two query parts together into a single query part.
// The keys of the two parts must be equal.
// If the result is a query part that can never match, io.EOF is returned as the error.
func (p part) merge(p2 part) (part, error) {
if p2.operator < p.operator {
// Sort the parts so we only need half the table below.
p, p2 = p2, p
}
switch p.operator {
case equals:
switch p2.operator {
case equals:
if p.value == p2.value {
return p, nil
}
return part{}, io.EOF
case lt:
if p.value < p2.value {
return p, nil
}
return part{}, io.EOF
case gt:
if p.value > p2.value {
return p, nil
}
return part{}, io.EOF
case ltgt:
if p.value < p2.value && p.value > p2.value2 {
return p, nil
}
return part{}, io.EOF
}
case ltgt:
switch p2.operator {
case ltgt:
if p2.value < p.value {
p.value = p2.value
}
if p2.value2 > p.value2 {
p.value2 = p2.value2
}
case lt:
if p2.value < p.value {
p.value = p2.value
}
case gt:
if p2.value > p.value2 {
p.value2 = p2.value
}
}
case lt:
switch p2.operator {
case lt:
if p2.value < p.value {
return p2, nil
}
return p, nil
case gt:
p = part{p.key, ltgt, p.value, p2.value}
}
case gt:
// p2.operator == gt
if p2.value > p.value {
return p2, nil
}
return p, nil
}
// p.operator == ltgt
if p.value <= p.value2 || p.value == "" {
return part{}, io.EOF
}
if p.value2 == "" {
return part{p.key, lt, p.value, ""}, nil
}
return p, nil
}
// sql returns a SQL expression and a list of arguments for finding records matching p.
func (p part) sql() (sql string, args []interface{}, err error) {
if p.key == "upload" {
switch p.operator {
case equals:
return "SELECT UploadID, RecordID FROM Records WHERE UploadID = ?", []interface{}{p.value}, nil
case lt:
return "SELECT UploadID, RecordID FROM Records WHERE UploadID < ?", []interface{}{p.value}, nil
case gt:
return "SELECT UploadID, RecordID FROM Records WHERE UploadID > ?", []interface{}{p.value}, nil
case ltgt:
return "SELECT UploadID, RecordID FROM Records WHERE UploadID < ? AND UploadID > ?", []interface{}{p.value, p.value2}, nil
}
}
switch p.operator {
case equals:
if p.value == "" {
// TODO(quentin): Implement support for searching for missing labels.
return "", nil, fmt.Errorf("missing value for key %q", p.key)
}
return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ? AND Value = ?", []interface{}{p.key, p.value}, nil
case lt:
return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ? AND Value < ?", []interface{}{p.key, p.value}, nil
case gt:
if p.value == "" {
// Simplify queries for any value.
return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ?", []interface{}{p.key}, nil
}
return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ? AND Value > ?", []interface{}{p.key, p.value}, nil
case ltgt:
return "SELECT UploadID, RecordID FROM RecordLabels WHERE Name = ? AND Value < ? AND Value > ?", []interface{}{p.key, p.value, p.value2}, nil
default:
panic("unknown operator " + string(p.operator))
}
}