Browse Source

[pubsub/query] quote values using single quotes

This fixes the problem with base-16 encoded values which may start with
digits: 015AB.... In such cases, the parser recognizes them as numbers
but fails to parse because of the follow-up characters (AB).

```
failed to parse tm.events.type=Tx AND hash=136E18F7E4C348B780CF873A0BF43922E5BAFA63:
parse error near digit (line 1 symbol 31 - line 1 symbol 32):
  "6"
```

So, from now on we should quote any values. This seems to be the way
Postgresql has chosen.
pull/1842/head
Anton Kaliaev 8 years ago
parent
commit
a6a06f820f
No known key found for this signature in database GPG Key ID: 7B6881D965918214
7 changed files with 365 additions and 471 deletions
  1. +1
    -1
      pubsub/example_test.go
  2. +6
    -6
      pubsub/pubsub_test.go
  3. +20
    -13
      pubsub/query/parser_test.go
  4. +4
    -1
      pubsub/query/query.go
  5. +2
    -2
      pubsub/query/query.peg
  6. +328
    -444
      pubsub/query/query.peg.go
  7. +4
    -4
      pubsub/query/query_test.go

+ 1
- 1
pubsub/example_test.go View File

@ -19,7 +19,7 @@ func TestExample(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ch := make(chan interface{}, 1) ch := make(chan interface{}, 1)
err := s.Subscribe(ctx, "example-client", query.MustParse("abci.account.name=John"), ch)
err := s.Subscribe(ctx, "example-client", query.MustParse("abci.account.name='John'"), ch)
require.NoError(t, err) require.NoError(t, err)
err = s.PublishWithTags(ctx, "Tombstone", map[string]interface{}{"abci.account.name": "John"}) err = s.PublishWithTags(ctx, "Tombstone", map[string]interface{}{"abci.account.name": "John"})
require.NoError(t, err) require.NoError(t, err)


+ 6
- 6
pubsub/pubsub_test.go View File

@ -46,14 +46,14 @@ func TestDifferentClients(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ch1 := make(chan interface{}, 1) ch1 := make(chan interface{}, 1)
err := s.Subscribe(ctx, "client-1", query.MustParse("tm.events.type=NewBlock"), ch1)
err := s.Subscribe(ctx, "client-1", query.MustParse("tm.events.type='NewBlock'"), ch1)
require.NoError(t, err) require.NoError(t, err)
err = s.PublishWithTags(ctx, "Iceman", map[string]interface{}{"tm.events.type": "NewBlock"}) err = s.PublishWithTags(ctx, "Iceman", map[string]interface{}{"tm.events.type": "NewBlock"})
require.NoError(t, err) require.NoError(t, err)
assertReceive(t, "Iceman", ch1) assertReceive(t, "Iceman", ch1)
ch2 := make(chan interface{}, 1) ch2 := make(chan interface{}, 1)
err = s.Subscribe(ctx, "client-2", query.MustParse("tm.events.type=NewBlock AND abci.account.name=Igor"), ch2)
err = s.Subscribe(ctx, "client-2", query.MustParse("tm.events.type='NewBlock' AND abci.account.name='Igor'"), ch2)
require.NoError(t, err) require.NoError(t, err)
err = s.PublishWithTags(ctx, "Ultimo", map[string]interface{}{"tm.events.type": "NewBlock", "abci.account.name": "Igor"}) err = s.PublishWithTags(ctx, "Ultimo", map[string]interface{}{"tm.events.type": "NewBlock", "abci.account.name": "Igor"})
require.NoError(t, err) require.NoError(t, err)
@ -61,7 +61,7 @@ func TestDifferentClients(t *testing.T) {
assertReceive(t, "Ultimo", ch2) assertReceive(t, "Ultimo", ch2)
ch3 := make(chan interface{}, 1) ch3 := make(chan interface{}, 1)
err = s.Subscribe(ctx, "client-3", query.MustParse("tm.events.type=NewRoundStep AND abci.account.name=Igor AND abci.invoice.number = 10"), ch3)
err = s.Subscribe(ctx, "client-3", query.MustParse("tm.events.type='NewRoundStep' AND abci.account.name='Igor' AND abci.invoice.number = 10"), ch3)
require.NoError(t, err) require.NoError(t, err)
err = s.PublishWithTags(ctx, "Valeria Richards", map[string]interface{}{"tm.events.type": "NewRoundStep"}) err = s.PublishWithTags(ctx, "Valeria Richards", map[string]interface{}{"tm.events.type": "NewRoundStep"})
require.NoError(t, err) require.NoError(t, err)
@ -75,7 +75,7 @@ func TestClientSubscribesTwice(t *testing.T) {
defer s.Stop() defer s.Stop()
ctx := context.Background() ctx := context.Background()
q := query.MustParse("tm.events.type=NewBlock")
q := query.MustParse("tm.events.type='NewBlock'")
ch1 := make(chan interface{}, 1) ch1 := make(chan interface{}, 1)
err := s.Subscribe(ctx, clientID, q, ch1) err := s.Subscribe(ctx, clientID, q, ch1)
@ -184,7 +184,7 @@ func benchmarkNClients(n int, b *testing.B) {
for range ch { for range ch {
} }
}() }()
s.Subscribe(ctx, clientID, query.MustParse(fmt.Sprintf("abci.Account.Owner = Ivan AND abci.Invoices.Number = %d", i)), ch)
s.Subscribe(ctx, clientID, query.MustParse(fmt.Sprintf("abci.Account.Owner = 'Ivan' AND abci.Invoices.Number = %d", i)), ch)
} }
b.ReportAllocs() b.ReportAllocs()
@ -200,7 +200,7 @@ func benchmarkNClientsOneQuery(n int, b *testing.B) {
defer s.Stop() defer s.Stop()
ctx := context.Background() ctx := context.Background()
q := query.MustParse("abci.Account.Owner = Ivan AND abci.Invoices.Number = 1")
q := query.MustParse("abci.Account.Owner = 'Ivan' AND abci.Invoices.Number = 1")
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
ch := make(chan interface{}) ch := make(chan interface{})
go func() { go func() {


+ 20
- 13
pubsub/query/parser_test.go View File

@ -13,30 +13,37 @@ func TestParser(t *testing.T) {
query string query string
valid bool valid bool
}{ }{
{"tm.events.type=NewBlock", true},
{"tm.events.type = NewBlock", true},
{"tm.events.type=TIME", true},
{"tm.events.type=DATE", true},
{"tm.events.type='NewBlock'", true},
{"tm.events.type = 'NewBlock'", true},
{"tm.events.name = ''", true},
{"tm.events.type='TIME'", true},
{"tm.events.type='DATE'", true},
{"tm.events.type='='", true},
{"tm.events.type='TIME", false},
{"tm.events.type=TIME'", false},
{"tm.events.type==", false}, {"tm.events.type==", false},
{"tm.events.type=NewBlock", false},
{">==", false}, {">==", false},
{"tm.events.type NewBlock =", false},
{"tm.events.type>NewBlock", false},
{"tm.events.type 'NewBlock' =", false},
{"tm.events.type>'NewBlock'", false},
{"", false}, {"", false},
{"=", false}, {"=", false},
{"=NewBlock", false},
{"='NewBlock'", false},
{"tm.events.type=", false}, {"tm.events.type=", false},
{"tm.events.typeNewBlock", false}, {"tm.events.typeNewBlock", false},
{"tm.events.type'NewBlock'", false},
{"'NewBlock'", false},
{"NewBlock", false}, {"NewBlock", false},
{"", false}, {"", false},
{"tm.events.type=NewBlock AND abci.account.name=Igor", true},
{"tm.events.type=NewBlock AND", false},
{"tm.events.type=NewBlock AN", false},
{"tm.events.type=NewBlock AN tm.events.type=NewBlockHeader", false},
{"AND tm.events.type=NewBlock ", false},
{"tm.events.type='NewBlock' AND abci.account.name='Igor'", true},
{"tm.events.type='NewBlock' AND", false},
{"tm.events.type='NewBlock' AN", false},
{"tm.events.type='NewBlock' AN tm.events.type='NewBlockHeader'", false},
{"AND tm.events.type='NewBlock' ", false},
{"abci.account.name CONTAINS Igor", true},
{"abci.account.name CONTAINS 'Igor'", true},
{"tx.date > DATE 2013-05-03", true}, {"tx.date > DATE 2013-05-03", true},
{"tx.date < DATE 2013-05-03", true}, {"tx.date < DATE 2013-05-03", true},


+ 4
- 1
pubsub/query/query.go View File

@ -94,9 +94,12 @@ func (q *Query) Matches(tags map[string]interface{}) bool {
case rulecontains: case rulecontains:
op = opContains op = opContains
case rulevalue: case rulevalue:
// strip single quotes from value (i.e. "'NewBlock'" -> "NewBlock")
valueWithoutSingleQuotes := buffer[begin+1 : end-1]
// see if the triplet (tag, operator, operand) matches any tag // see if the triplet (tag, operator, operand) matches any tag
// "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" } // "tx.gas", "=", "7", { "tx.gas": 7, "tx.ID": "4AE393495334" }
if !match(tag, op, reflect.ValueOf(buffer[begin:end]), tags) {
if !match(tag, op, reflect.ValueOf(valueWithoutSingleQuotes), tags) {
return false return false
} }
case rulenumber: case rulenumber:


+ 2
- 2
pubsub/query/query.peg View File

@ -13,8 +13,8 @@ condition <- tag ' '* (le ' '* (number / time / date)
/ contains ' '* value / contains ' '* value
) )
tag <- < (![ \t\n\r\\()"=><] .)+ >
value <- < (![ \t\n\r\\()"=><] .)+ >
tag <- < (![ \t\n\r\\()"'=><] .)+ >
value <- < '\'' (!["'] .)* '\''>
number <- < ('0' number <- < ('0'
/ [1-9] digit* ('.' digit*)?) > / [1-9] digit* ('.' digit*)?) >
digit <- [0-9] digit <- [0-9]


+ 328
- 444
pubsub/query/query.peg.go
File diff suppressed because it is too large
View File


+ 4
- 4
pubsub/query/query_test.go View File

@ -22,7 +22,7 @@ func TestMatches(t *testing.T) {
err bool err bool
matches bool matches bool
}{ }{
{"tm.events.type=NewBlock", map[string]interface{}{"tm.events.type": "NewBlock"}, false, true},
{"tm.events.type='NewBlock'", map[string]interface{}{"tm.events.type": "NewBlock"}, false, true},
{"tx.gas > 7", map[string]interface{}{"tx.gas": 8}, false, true}, {"tx.gas > 7", map[string]interface{}{"tx.gas": 8}, false, true},
{"tx.gas > 7 AND tx.gas < 9", map[string]interface{}{"tx.gas": 8}, false, true}, {"tx.gas > 7 AND tx.gas < 9", map[string]interface{}{"tx.gas": 8}, false, true},
@ -40,8 +40,8 @@ func TestMatches(t *testing.T) {
{"tx.time >= TIME 2013-05-03T14:45:00Z", map[string]interface{}{"tx.time": time.Now()}, false, true}, {"tx.time >= TIME 2013-05-03T14:45:00Z", map[string]interface{}{"tx.time": time.Now()}, false, true},
{"tx.time = TIME 2013-05-03T14:45:00Z", map[string]interface{}{"tx.time": txTime}, false, false}, {"tx.time = TIME 2013-05-03T14:45:00Z", map[string]interface{}{"tx.time": txTime}, false, false},
{"abci.owner.name CONTAINS Igor", map[string]interface{}{"abci.owner.name": "Igor,Ivan"}, false, true},
{"abci.owner.name CONTAINS Igor", map[string]interface{}{"abci.owner.name": "Pavel,Ivan"}, false, false},
{"abci.owner.name CONTAINS 'Igor'", map[string]interface{}{"abci.owner.name": "Igor,Ivan"}, false, true},
{"abci.owner.name CONTAINS 'Igor'", map[string]interface{}{"abci.owner.name": "Pavel,Ivan"}, false, false},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -60,5 +60,5 @@ func TestMatches(t *testing.T) {
func TestMustParse(t *testing.T) { func TestMustParse(t *testing.T) {
assert.Panics(t, func() { query.MustParse("=") }) assert.Panics(t, func() { query.MustParse("=") })
assert.NotPanics(t, func() { query.MustParse("tm.events.type=NewBlock") })
assert.NotPanics(t, func() { query.MustParse("tm.events.type='NewBlock'") })
} }

Loading…
Cancel
Save