Skip to content

Commit 47e219e

Browse files
committed
fixup! api: reorganize directory layout
1 parent 8e86117 commit 47e219e

File tree

3 files changed

+87
-40
lines changed

3 files changed

+87
-40
lines changed

README.md

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
**Quamina** implements a data type that has APIs to
1414
create an instance and add multiple **Patterns** to it,
1515
and then query data objects called **Events** to
16-
discover which of the patterns match
17-
the fields in the event.
16+
discover which of the Patterns match
17+
the fields in the Event.
1818

1919
Quamina [welcomes contributions](CONTRIBUTING.md).
2020

@@ -127,7 +127,7 @@ support for a larger subset of regular expressions,
127127
eventually.
128128

129129
Number matching is weak - the number has to appear
130-
exactly the same in the pattern and the event. I.e.,
130+
exactly the same in the Pattern and the Event. I.e.,
131131
Quamina doesn't know that 35, 35.000, and 3.5e1 are the
132132
same number. There's a fix for this in the code which
133133
is not yet activated because it causes a
@@ -142,10 +142,7 @@ into a list of pathname/value pairs called Fields. Quamina
142142
defines a `Flattener` interface type and has a built-in
143143
`Flattener` for JSON.
144144

145-
`Flattener` implementations in general will have
146-
internal state and thus not be thread-safe.
147-
148-
Note that should you wish to process events
145+
Note that should you wish to process Events
149146
in a format other than JSON, you can implement
150147
the `Flattener` interface yourself.
151148

@@ -157,7 +154,7 @@ greater than 0XF4 (can't occur in correctly composed UTF-8)
157154
are rejected by the APIs.
158155
### Control APIs
159156
```go
160-
func New(...Option) (*Quamina, error)
157+
func New(opts ...Option) (*Quamina, error)
161158

162159
func WithMediaType(mediaType string) Option
163160
func WithFlattener(f Flattener) Option
@@ -177,7 +174,7 @@ Avro, Protobufs, and so on. This option will make sure
177174
to invoke the correct Flattener. At the moment, the only
178175
supported value is `application/json`, the default.
179176

180-
`WithFlattener`: Requests that Quamina flatten events with
177+
`WithFlattener`: Requests that Quamina flatten Events with
181178
the provided (presumably user-written) Flattener.
182179

183180
`WithPatternDeletion`: If true, arranges that Quamina
@@ -189,7 +186,7 @@ to improve this.)
189186
`WithPatternStorage`: If you provide an argument that
190187
supports the `LivePatternStorage` API, Quamina will
191188
use it to
192-
maintain a list of which patterns have currently been
189+
maintain a list of which Patterns have currently been
193190
added but not deleted. This could be useful if you
194191
wanted to rebuild Quamina instances for sharded
195192
processing or after a system failure. ***Note: Not
@@ -202,17 +199,17 @@ func (q *Quamina) AddPattern(x X, patternJSON string) error
202199
```
203200
The first argument identifies the Pattern and will be
204201
returned by Quamina when asked to match against Events.
205-
X is defined as `any`.
202+
X is defined as `any`.
206203

207-
The Pattern must be provided as a string which is a
208-
JSON object as exemplified above in this document.
204+
The Pattern is provided in the second argument string which
205+
must be a JSON object as exemplified above in this document.
209206

210207
The `error` return is used to signal invalid Pattern
211208
structure, which could be bad UTF-8 or malformed JSON
212209
or leaf values which are not provided as arrays.
213210

214211
As many Patterns as desired can be added to a Quamina
215-
instance. More than one pattern can be added with the
212+
instance. More than one Pattern can be added with the
216213
same `X` identifier.
217214

218215
The `AddPattern` call is single-threaded; if multiple
@@ -251,9 +248,8 @@ copies may safely run in parallel in different
251248
goroutines executing any combination of
252249
`MatchesForEvent()`, `AddPattern()`, and
253250
`DeletePattern()` calls. There is a significant
254-
performance penalty if there is a high rate of
255-
`AddPattern()` invocations in parallel with
256-
`MatchesForEvent()`.
251+
performance penalty if a high proportion of these
252+
calls are `AddPattern()`.
257253

258254
Note that the `Copy()` API is somewhat expensive, and
259255
that a Quamina instance exhibits “warm-up” behavior,
@@ -262,7 +258,7 @@ slightly upon repeated calls, especially over the
262258
first few calls. The conclusion is that, for maximum efficiency, once
263259
you’ve created a Quamina instance, whether through
264260
`New()` or `Copy()`, keep it around and run as many
265-
events through it as is practical.
261+
Events through it as is practical.
266262

267263

268264
### Performance
@@ -272,23 +268,23 @@ I used to say that the performance of
272268
Patterns. While that’s probably the right way to think
273269
about it, it’s not *quite* true,
274270
as it varies somewhat as a function of the number of
275-
unique fields that appear in all the patterns that have
271+
unique fields that appear in all the Patterns that have
276272
been added to Quamina, but still remains sublinear
277273
in that number.
278274

279275
A word of explanation: Quamina compiles the
280-
patterns into a somewhat-decorated automaton and uses
281-
that to find matches in events; the matching process is
282-
O(1) in the number of patterns.
276+
Patterns into a somewhat-decorated automaton and uses
277+
that to find matches in Events; the matching process is
278+
`O(1)` in the number of Patterns.
283279

284-
However, for this to work, the incoming event must be
280+
However, for this to work, the incoming Event must be
285281
flattened into a list of pathname/value pairs and
286282
sorted. This process exceeds 50% of execution time,
287283
and is optimized by discarding any fields that
288-
do not appear in one or more of the patterns added
289-
to Quamina. Thus, adding a new pattern that only
290-
mentions fields mentioned in previous patterns is
291-
effectively free i.e. `O(1)` in terms of run-time
284+
do not appear in one or more of the Patterns added
285+
to Quamina. Thus, adding a new Pattern that only
286+
mentions fields which are already mentioned in previous
287+
Patterns is effectively free i.e. `O(1)` in terms of run-time
292288
performance.
293289

294290
### Name

quamina.go

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,77 @@ import (
1111
// matcher is the root of the two-level automaton structure containing fieldMatcher and valueMatcher nodes. Multiple
1212
// Quamina instances may have the same matcher value, since it is designed for concurrent operation.
1313
type Quamina struct {
14-
flattener Flattener
15-
matcher matcher
14+
flattener Flattener
15+
matcher matcher
16+
flattenerSpecified bool
17+
mediaTypeSpecified bool
18+
deletionSpecified bool
1619
}
1720

1821
type Option func(q *Quamina) error
1922

23+
// WithMediaType provides a media-type to support the selection of an appropriate Flattener.
24+
// This option call may not be provided more than once, nor can it be combined on the same
25+
// invocation of quamina.New() with the WithFlattener() option.
2026
func WithMediaType(mediaType string) Option {
2127
return func(q *Quamina) error {
28+
if q.flattenerSpecified {
29+
return errors.New("flattener already specified")
30+
}
31+
if q.mediaTypeSpecified {
32+
return errors.New("media-type specified more than once")
33+
}
2234
switch mediaType {
2335
case "application/json":
2436
q.flattener = newJSONFlattener()
2537
default:
2638
return fmt.Errorf(`media type "%s" is not supported by Quamina`, mediaType)
2739
}
40+
q.mediaTypeSpecified = true
2841
return nil
2942
}
3043
}
44+
45+
// WithFlattener allows the specification of a caller-provided Flattener instance to use on incoming Events.
46+
// This option call may not be provided more than once, nor can it be combined on the same
47+
// invocation of quamina.New() with the WithMediaType() option.
3148
func WithFlattener(f Flattener) Option {
3249
return func(q *Quamina) error {
50+
if q.mediaTypeSpecified {
51+
return errors.New("media-type already specified")
52+
}
53+
if q.flattenerSpecified {
54+
return errors.New("flattener specified more than once")
55+
}
3356
if f == nil {
3457
return errors.New("nil Flattener")
3558
}
3659
q.flattener = f
60+
q.flattenerSpecified = true
3761
return nil
3862
}
3963
}
64+
65+
// WithPatternDeletion arranges, if the argument is true, that this Quamina instance will support
66+
// the DeletePatterns() method. This option call may not be provided more than once.
4067
func WithPatternDeletion(b bool) Option {
4168
return func(q *Quamina) error {
69+
if q.deletionSpecified {
70+
return errors.New("pattern deletion already specified")
71+
}
4272
if b {
4373
q.matcher = newPrunerMatcher(nil)
4474
} else {
4575
q.matcher = newCoreMatcher()
4676
}
77+
q.deletionSpecified = true
4778
return nil
4879
}
4980
}
81+
82+
// WithPatternDeletion supplies the Quamina instance with a LivePatternState instance to be used to store
83+
// the active patterns, i.e. those that have been added with AddPattern but not deleted with
84+
// DeletePattern. This option call may not be provided more than once.
5085
func WithPatternStorage(ps LivePatternsState) Option {
5186
return func(q *Quamina) error {
5287
if ps == nil {
@@ -58,17 +93,17 @@ func WithPatternStorage(ps LivePatternsState) Option {
5893

5994
func New(opts ...Option) (*Quamina, error) {
6095
var q Quamina
61-
defaultOps := []Option{WithPatternDeletion(false), WithFlattener(newJSONFlattener())}
62-
for _, option := range defaultOps {
63-
if err := option(&q); err != nil {
64-
return nil, err
65-
}
66-
}
6796
for _, option := range opts {
6897
if err := option(&q); err != nil {
6998
return nil, err
7099
}
71100
}
101+
if !(q.mediaTypeSpecified || q.flattenerSpecified) {
102+
q.flattener = newJSONFlattener()
103+
}
104+
if !q.deletionSpecified {
105+
q.matcher = newCoreMatcher()
106+
}
72107
return &q, nil
73108
}
74109

quamina_test.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,34 @@ func TestNewQOptions(t *testing.T) {
6565
if !ok {
6666
t.Error("should be core")
6767
}
68-
q, err = New(WithPatternDeletion(true), WithPatternDeletion(false), WithFlattener(newJSONFlattener()),
69-
WithMediaType("application/json"), WithPatternDeletion(true))
68+
69+
_, err = New(WithPatternDeletion(true), WithPatternDeletion(true))
70+
if err == nil {
71+
t.Error("allowed 2 patternDel" + err.Error())
72+
}
73+
_, err = New(WithFlattener(newJSONFlattener()), WithFlattener(newJSONFlattener()))
74+
if err == nil {
75+
t.Error("allowed 2 flatteners" + err.Error())
76+
}
77+
_, err = New(WithMediaType("application/json"), WithMediaType("application/json"))
78+
if err == nil {
79+
t.Error("allowed 2 mediatypes" + err.Error())
80+
}
81+
_, err = New(WithMediaType("application/json"), WithFlattener(newJSONFlattener()))
82+
if err == nil {
83+
t.Error("allowed flattener and media type" + err.Error())
84+
}
85+
q, err = New(WithPatternDeletion(true))
7086
if err != nil {
71-
t.Error("bad multi: " + err.Error())
87+
t.Error("WithPatternDeletion failed: " + err.Error())
7288
}
7389
_, ok = q.matcher.(*prunerMatcher)
7490
if !ok {
75-
t.Error("multi should be pruner")
91+
t.Error("not a pruner matcher")
7692
}
7793
_, ok = q.flattener.(*flattenJSON)
7894
if !ok {
79-
t.Error("multi should be flattenJSON")
95+
t.Error("flattener not for JSON")
8096
}
8197
}
8298

0 commit comments

Comments
 (0)