You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
9.6 KiB

  1. // Package query provides a parser for a custom query format:
  2. //
  3. // abci.invoice.number=22 AND abci.invoice.owner=Ivan
  4. //
  5. // See query.peg for the grammar, which is a https://en.wikipedia.org/wiki/Parsing_expression_grammar.
  6. // More: https://github.com/PhilippeSigaud/Pegged/wiki/PEG-Basics
  7. //
  8. // It has a support for numbers (integer and floating point), dates and times.
  9. package query
  10. import (
  11. "fmt"
  12. "reflect"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/tendermint/tendermint/libs/pubsub"
  17. )
  18. // Query holds the query string and the query parser.
  19. type Query struct {
  20. str string
  21. parser *QueryParser
  22. }
  23. // Condition represents a single condition within a query and consists of tag
  24. // (e.g. "tx.gas"), operator (e.g. "=") and operand (e.g. "7").
  25. type Condition struct {
  26. Tag string
  27. Op Operator
  28. Operand interface{}
  29. }
  30. // New parses the given string and returns a query or error if the string is
  31. // invalid.
  32. func New(s string) (*Query, error) {
  33. p := &QueryParser{Buffer: fmt.Sprintf(`"%s"`, s)}
  34. p.Init()
  35. if err := p.Parse(); err != nil {
  36. return nil, err
  37. }
  38. return &Query{str: s, parser: p}, nil
  39. }
  40. // MustParse turns the given string into a query or panics; for tests or others
  41. // cases where you know the string is valid.
  42. func MustParse(s string) *Query {
  43. q, err := New(s)
  44. if err != nil {
  45. panic(fmt.Sprintf("failed to parse %s: %v", s, err))
  46. }
  47. return q
  48. }
  49. // String returns the original string.
  50. func (q *Query) String() string {
  51. return q.str
  52. }
  53. // Operator is an operator that defines some kind of relation between tag and
  54. // operand (equality, etc.).
  55. type Operator uint8
  56. const (
  57. // "<="
  58. OpLessEqual Operator = iota
  59. // ">="
  60. OpGreaterEqual
  61. // "<"
  62. OpLess
  63. // ">"
  64. OpGreater
  65. // "="
  66. OpEqual
  67. // "CONTAINS"; used to check if a string contains a certain sub string.
  68. OpContains
  69. )
  70. const (
  71. // DateLayout defines a layout for all dates (`DATE date`)
  72. DateLayout = "2006-01-02"
  73. // TimeLayout defines a layout for all times (`TIME time`)
  74. TimeLayout = time.RFC3339
  75. )
  76. // Conditions returns a list of conditions.
  77. func (q *Query) Conditions() []Condition {
  78. conditions := make([]Condition, 0)
  79. buffer, begin, end := q.parser.Buffer, 0, 0
  80. var tag string
  81. var op Operator
  82. // tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7")
  83. for _, token := range q.parser.Tokens() {
  84. switch token.pegRule {
  85. case rulePegText:
  86. begin, end = int(token.begin), int(token.end)
  87. case ruletag:
  88. tag = buffer[begin:end]
  89. case rulele:
  90. op = OpLessEqual
  91. case rulege:
  92. op = OpGreaterEqual
  93. case rulel:
  94. op = OpLess
  95. case ruleg:
  96. op = OpGreater
  97. case ruleequal:
  98. op = OpEqual
  99. case rulecontains:
  100. op = OpContains
  101. case rulevalue:
  102. // strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock")
  103. valueWithoutSingleQuotes := buffer[begin+1 : end-1]
  104. conditions = append(conditions, Condition{tag, op, valueWithoutSingleQuotes})
  105. case rulenumber:
  106. number := buffer[begin:end]
  107. if strings.ContainsAny(number, ".") { // if it looks like a floating-point number
  108. value, err := strconv.ParseFloat(number, 64)
  109. if err != nil {
  110. panic(fmt.Sprintf("got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", err, number))
  111. }
  112. conditions = append(conditions, Condition{tag, op, value})
  113. } else {
  114. value, err := strconv.ParseInt(number, 10, 64)
  115. if err != nil {
  116. panic(fmt.Sprintf("got %v while trying to parse %s as int64 (should never happen if the grammar is correct)", err, number))
  117. }
  118. conditions = append(conditions, Condition{tag, op, value})
  119. }
  120. case ruletime:
  121. value, err := time.Parse(TimeLayout, buffer[begin:end])
  122. if err != nil {
  123. panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)", err, buffer[begin:end]))
  124. }
  125. conditions = append(conditions, Condition{tag, op, value})
  126. case ruledate:
  127. value, err := time.Parse("2006-01-02", buffer[begin:end])
  128. if err != nil {
  129. panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / '2006-01-02' (should never happen if the grammar is correct)", err, buffer[begin:end]))
  130. }
  131. conditions = append(conditions, Condition{tag, op, value})
  132. }
  133. }
  134. return conditions
  135. }
  136. // Matches returns true if the query matches the given set of tags, false otherwise.
  137. //
  138. // For example, query "name=John" matches tags = {"name": "John"}. More
  139. // examples could be found in parser_test.go and query_test.go.
  140. func (q *Query) Matches(tags pubsub.TagMap) bool {
  141. if tags.Len() == 0 {
  142. return false
  143. }
  144. buffer, begin, end := q.parser.Buffer, 0, 0
  145. var tag string
  146. var op Operator
  147. // tokens must be in the following order: tag ("tx.gas") -> operator ("=") -> operand ("7")
  148. for _, token := range q.parser.Tokens() {
  149. switch token.pegRule {
  150. case rulePegText:
  151. begin, end = int(token.begin), int(token.end)
  152. case ruletag:
  153. tag = buffer[begin:end]
  154. case rulele:
  155. op = OpLessEqual
  156. case rulege:
  157. op = OpGreaterEqual
  158. case rulel:
  159. op = OpLess
  160. case ruleg:
  161. op = OpGreater
  162. case ruleequal:
  163. op = OpEqual
  164. case rulecontains:
  165. op = OpContains
  166. case rulevalue:
  167. // strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock")
  168. valueWithoutSingleQuotes := buffer[begin+1 : end-1]
  169. // see if the triplet (tag, operator, operand) matches any tag
  170. // "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" }
  171. if !match(tag, op, reflect.ValueOf(valueWithoutSingleQuotes), tags) {
  172. return false
  173. }
  174. case rulenumber:
  175. number := buffer[begin:end]
  176. if strings.ContainsAny(number, ".") { // if it looks like a floating-point number
  177. value, err := strconv.ParseFloat(number, 64)
  178. if err != nil {
  179. panic(fmt.Sprintf("got %v while trying to parse %s as float64 (should never happen if the grammar is correct)", err, number))
  180. }
  181. if !match(tag, op, reflect.ValueOf(value), tags) {
  182. return false
  183. }
  184. } else {
  185. value, err := strconv.ParseInt(number, 10, 64)
  186. if err != nil {
  187. panic(fmt.Sprintf("got %v while trying to parse %s as int64 (should never happen if the grammar is correct)", err, number))
  188. }
  189. if !match(tag, op, reflect.ValueOf(value), tags) {
  190. return false
  191. }
  192. }
  193. case ruletime:
  194. value, err := time.Parse(TimeLayout, buffer[begin:end])
  195. if err != nil {
  196. panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / RFC3339 (should never happen if the grammar is correct)", err, buffer[begin:end]))
  197. }
  198. if !match(tag, op, reflect.ValueOf(value), tags) {
  199. return false
  200. }
  201. case ruledate:
  202. value, err := time.Parse("2006-01-02", buffer[begin:end])
  203. if err != nil {
  204. panic(fmt.Sprintf("got %v while trying to parse %s as time.Time / '2006-01-02' (should never happen if the grammar is correct)", err, buffer[begin:end]))
  205. }
  206. if !match(tag, op, reflect.ValueOf(value), tags) {
  207. return false
  208. }
  209. }
  210. }
  211. return true
  212. }
  213. // match returns true if the given triplet (tag, operator, operand) matches any tag.
  214. //
  215. // First, it looks up the tag in tags and if it finds one, tries to compare the
  216. // value from it to the operand using the operator.
  217. //
  218. // "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" }
  219. func match(tag string, op Operator, operand reflect.Value, tags pubsub.TagMap) bool {
  220. // look up the tag from the query in tags
  221. value, ok := tags.Get(tag)
  222. if !ok {
  223. return false
  224. }
  225. switch operand.Kind() {
  226. case reflect.Struct: // time
  227. operandAsTime := operand.Interface().(time.Time)
  228. // try our best to convert value from tags to time.Time
  229. var (
  230. v time.Time
  231. err error
  232. )
  233. if strings.ContainsAny(value, "T") {
  234. v, err = time.Parse(TimeLayout, value)
  235. } else {
  236. v, err = time.Parse(DateLayout, value)
  237. }
  238. if err != nil {
  239. panic(fmt.Sprintf("Failed to convert value %v from tag to time.Time: %v", value, err))
  240. }
  241. switch op {
  242. case OpLessEqual:
  243. return v.Before(operandAsTime) || v.Equal(operandAsTime)
  244. case OpGreaterEqual:
  245. return v.Equal(operandAsTime) || v.After(operandAsTime)
  246. case OpLess:
  247. return v.Before(operandAsTime)
  248. case OpGreater:
  249. return v.After(operandAsTime)
  250. case OpEqual:
  251. return v.Equal(operandAsTime)
  252. }
  253. case reflect.Float64:
  254. operandFloat64 := operand.Interface().(float64)
  255. var v float64
  256. // try our best to convert value from tags to float64
  257. v, err := strconv.ParseFloat(value, 64)
  258. if err != nil {
  259. panic(fmt.Sprintf("Failed to convert value %v from tag to float64: %v", value, err))
  260. }
  261. switch op {
  262. case OpLessEqual:
  263. return v <= operandFloat64
  264. case OpGreaterEqual:
  265. return v >= operandFloat64
  266. case OpLess:
  267. return v < operandFloat64
  268. case OpGreater:
  269. return v > operandFloat64
  270. case OpEqual:
  271. return v == operandFloat64
  272. }
  273. case reflect.Int64:
  274. operandInt := operand.Interface().(int64)
  275. var v int64
  276. // if value looks like float, we try to parse it as float
  277. if strings.ContainsAny(value, ".") {
  278. v1, err := strconv.ParseFloat(value, 64)
  279. if err != nil {
  280. panic(fmt.Sprintf("Failed to convert value %v from tag to float64: %v", value, err))
  281. }
  282. v = int64(v1)
  283. } else {
  284. var err error
  285. // try our best to convert value from tags to int64
  286. v, err = strconv.ParseInt(value, 10, 64)
  287. if err != nil {
  288. panic(fmt.Sprintf("Failed to convert value %v from tag to int64: %v", value, err))
  289. }
  290. }
  291. switch op {
  292. case OpLessEqual:
  293. return v <= operandInt
  294. case OpGreaterEqual:
  295. return v >= operandInt
  296. case OpLess:
  297. return v < operandInt
  298. case OpGreater:
  299. return v > operandInt
  300. case OpEqual:
  301. return v == operandInt
  302. }
  303. case reflect.String:
  304. switch op {
  305. case OpEqual:
  306. return value == operand.String()
  307. case OpContains:
  308. return strings.Contains(value, operand.String())
  309. }
  310. default:
  311. panic(fmt.Sprintf("Unknown kind of operand %v", operand.Kind()))
  312. }
  313. return false
  314. }