Skip to content

Commit 4cd3457

Browse files
committed
implement skip filter
this allows using keys like a directory listing. apogy q 'ixeri.Object(val.path^"/foo/bar")' would normally list all keys with that prefix, i.e. "recursive" ls apogy q 'ixeri.Object(val.path^"/foo/bar"$"/")' will stop scanning after /foo/bar/aaa/something/deep and restart the scan with /foo/bar/aab , ignoring any key that was "deeper" inbetween
1 parent 314b9f4 commit 4cd3457

File tree

10 files changed

+489
-134
lines changed

10 files changed

+489
-134
lines changed

api/go/gen.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/openapi.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ components:
199199
greater: {}
200200
less: {}
201201
prefix: {}
202+
skip: {}
202203

203204
Query:
204205
type: object

api/ts/client/models/Filter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ export type Filter = {
88
greater?: any;
99
less?: any;
1010
prefix?: any;
11+
skip?: any;
1112
};
1213

aql/parser.go

Lines changed: 131 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package aql
33
import (
44
"errors"
55
"fmt"
6+
openapi "github.com/aep/apogy/api/go"
67
"strconv"
78
"strings"
89
"unicode"
@@ -26,6 +27,7 @@ const (
2627
TOKEN_COMMA
2728
TOKEN_PARAM // New token type for parameter placeholders
2829
TOKEN_AND // Logical AND operator (& or &&)
30+
TOKEN_SKIP // Skip operator ($)
2931
)
3032

3133
func tokenName(i TokenType) string {
@@ -58,6 +60,8 @@ func tokenName(i TokenType) string {
5860
return "PARAM"
5961
case TOKEN_AND:
6062
return "AND"
63+
case TOKEN_SKIP:
64+
return "SKIP"
6165
}
6266
return "ILLEGAL"
6367
}
@@ -155,6 +159,8 @@ func (l *Lexer) NextToken() Token {
155159
tok = Token{TOKEN_GREATER, string(l.ch)}
156160
case '^':
157161
tok = Token{TOKEN_PREFIX, string(l.ch)}
162+
case '$':
163+
tok = Token{TOKEN_SKIP, string(l.ch)}
158164
case '(':
159165
tok = Token{TOKEN_LPAREN, string(l.ch)}
160166
case ')':
@@ -203,7 +209,7 @@ func (l *Lexer) NextToken() Token {
203209

204210
type Query struct {
205211
Type string
206-
Filter map[string]interface{}
212+
Filter []openapi.Filter
207213
Links []*Query
208214
}
209215

@@ -213,30 +219,62 @@ func (q *Query) String() string {
213219

214220
if len(q.Filter) > 0 {
215221
filters := make([]string, 0)
216-
for k, v := range q.Filter {
217-
// Determine operator
222+
for _, filter := range q.Filter {
218223
var operator string = "="
219-
var actualKey string = k
224+
var value interface{} = nil
225+
var skipValue interface{} = nil
220226

221-
if strings.HasSuffix(k, "<") {
227+
if filter.Equal != nil {
228+
operator = "="
229+
value = *filter.Equal
230+
} else if filter.Less != nil {
222231
operator = "<"
223-
actualKey = strings.TrimSuffix(k, "<")
224-
} else if strings.HasSuffix(k, ">") {
232+
value = *filter.Less
233+
} else if filter.Greater != nil {
225234
operator = ">"
226-
actualKey = strings.TrimSuffix(k, ">")
227-
} else if strings.HasSuffix(k, "^") {
235+
value = *filter.Greater
236+
} else if filter.Prefix != nil {
228237
operator = "^"
229-
actualKey = strings.TrimSuffix(k, "^")
238+
value = *filter.Prefix
239+
240+
// Check for Skip value
241+
if filter.Skip != nil {
242+
skipValue = *filter.Skip
243+
}
244+
}
245+
246+
if value == nil {
247+
filters = append(filters, filter.Key)
248+
continue
230249
}
231250

232-
switch val := v.(type) {
251+
var filterStr string
252+
switch val := value.(type) {
233253
case string:
234-
filters = append(filters, fmt.Sprintf(`%s%s"%s"`, actualKey, operator, val))
254+
filterStr = fmt.Sprintf(`%s%s"%s"`, filter.Key, operator, val)
235255
case float64:
236-
filters = append(filters, fmt.Sprintf(`%s%s%g`, actualKey, operator, val))
256+
filterStr = fmt.Sprintf(`%s%s%g`, filter.Key, operator, val)
237257
case bool:
238-
filters = append(filters, fmt.Sprintf(`%s%s%v`, actualKey, operator, val))
258+
filterStr = fmt.Sprintf(`%s%s%v`, filter.Key, operator, val)
259+
default:
260+
filterStr = fmt.Sprintf(`%s%s%v`, filter.Key, operator, val)
261+
}
262+
263+
// Append skip part if it exists
264+
if skipValue != nil {
265+
switch skipVal := skipValue.(type) {
266+
case string:
267+
filterStr += fmt.Sprintf(`$"%s"`, skipVal)
268+
case float64:
269+
filterStr += fmt.Sprintf(`$%g`, skipVal)
270+
case bool:
271+
filterStr += fmt.Sprintf(`$%v`, skipVal)
272+
default:
273+
filterStr += fmt.Sprintf(`$%v`, skipVal)
274+
}
239275
}
276+
277+
filters = append(filters, filterStr)
240278
}
241279
parts = append(parts, fmt.Sprintf("(%s)", strings.Join(filters, " ")))
242280
}
@@ -306,8 +344,8 @@ func (p *Parser) ParseQuery() (*Query, error) {
306344
return query, nil
307345
}
308346

309-
func (p *Parser) parseFilter() (map[string]interface{}, error) {
310-
filter := make(map[string]interface{})
347+
func (p *Parser) parseFilter() ([]openapi.Filter, error) {
348+
filters := make([]openapi.Filter, 0)
311349

312350
p.nextToken() // consume (
313351

@@ -326,7 +364,11 @@ func (p *Parser) parseFilter() (map[string]interface{}, error) {
326364
p.nextToken()
327365

328366
if p.curToken.Type == TOKEN_RPAREN || p.curToken.Type == TOKEN_IDENT {
329-
filter[key] = nil
367+
// Create a filter with just a key (no operator/value)
368+
filter := openapi.Filter{
369+
Key: key,
370+
}
371+
filters = append(filters, filter)
330372
continue
331373
}
332374

@@ -378,18 +420,84 @@ func (p *Parser) parseFilter() (map[string]interface{}, error) {
378420
}
379421
}
380422

381-
// Store value with appropriate operator metadata
423+
// Create filter with appropriate operator
424+
filter := openapi.Filter{
425+
Key: key,
426+
}
427+
428+
// Set the operator-specific field
382429
switch operator {
383430
case TOKEN_EQUALS:
384-
filter[key] = processedValue
431+
filter.Equal = &processedValue
385432
case TOKEN_LESS:
386-
filter[key+"<"] = processedValue
433+
filter.Less = &processedValue
387434
case TOKEN_GREATER:
388-
filter[key+">"] = processedValue
435+
filter.Greater = &processedValue
389436
case TOKEN_PREFIX:
390-
filter[key+"^"] = processedValue
437+
filter.Prefix = &processedValue
438+
439+
// Check for SKIP operation after a PREFIX
440+
p.nextToken()
441+
442+
if p.curToken.Type == TOKEN_SKIP {
443+
// We found a skip operation, advance and get the skip value
444+
p.nextToken()
445+
446+
if p.curToken.Type != TOKEN_IDENT && p.curToken.Type != TOKEN_STRING && p.curToken.Type != TOKEN_PARAM {
447+
return nil, fmt.Errorf("expected identifier, string, or parameter placeholder as skip value, got %s", tokenName(p.curToken.Type))
448+
}
449+
450+
var skipValue interface{}
451+
452+
if p.curToken.Type == TOKEN_PARAM {
453+
// Handle parameter placeholder for skip
454+
if p.collectingOnly {
455+
p.paramIndex++
456+
skipValue = nil
457+
} else {
458+
if p.paramIndex >= len(p.params) {
459+
return nil, fmt.Errorf("not enough parameters provided for skip, needed at least %d", p.paramIndex+1)
460+
}
461+
skipValue = p.params[p.paramIndex]
462+
p.paramIndex++
463+
}
464+
} else {
465+
// Handle literal skip values
466+
skip := p.curToken.Literal
467+
468+
if p.curToken.Type == TOKEN_IDENT {
469+
if skip == "true" {
470+
skipValue = true
471+
} else if skip == "false" {
472+
skipValue = false
473+
} else if num, err := strconv.ParseFloat(skip, 64); err == nil {
474+
skipValue = num
475+
} else {
476+
skipValue = skip
477+
}
478+
} else if p.curToken.Type == TOKEN_STRING {
479+
skipValue = skip
480+
}
481+
}
482+
483+
// Set the Skip field in the filter
484+
filter.Skip = &skipValue
485+
486+
// Add the filter with both Prefix and Skip to the filters slice
487+
filters = append(filters, filter)
488+
489+
p.nextToken()
490+
continue // Skip the next token advancement since we've already done it
491+
} else {
492+
// No skip operation, add the filter with just the prefix
493+
filters = append(filters, filter)
494+
continue // Skip the p.nextToken() at the end of the loop since we already advanced
495+
}
496+
default:
497+
// For other operators, proceed normally
391498
}
392499

500+
filters = append(filters, filter)
393501
p.nextToken()
394502
}
395503

@@ -398,7 +506,7 @@ func (p *Parser) parseFilter() (map[string]interface{}, error) {
398506
}
399507
p.nextToken()
400508

401-
return filter, nil
509+
return filters, nil
402510
}
403511

404512
func (p *Parser) parseNested() ([]*Query, error) {

0 commit comments

Comments
 (0)