From 8d480e67c067183963bcf73d73ce8dc1344f675c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Jerman?= Date: Thu, 29 Sep 2022 16:34:00 +0200 Subject: [PATCH] Allow rdbms model ops to use already parsed filter expressions --- pkg/dal/def_datasource.go | 26 ++++++++++- store/adapters/rdbms/dal/model.go | 78 +++++++++++++++++++++---------- 2 files changed, 77 insertions(+), 27 deletions(-) diff --git a/pkg/dal/def_datasource.go b/pkg/dal/def_datasource.go index 9b0b33adf..f30379920 100644 --- a/pkg/dal/def_datasource.go +++ b/pkg/dal/def_datasource.go @@ -67,7 +67,11 @@ func (def *Datasource) init(ctx context.Context, s iterProvider) (err error) { } } - iter, model, err := s(ctx, def.ModelRef, def.filter) + // Do the first init to get the model. + // For now, this is a ~free operation, but it should change when we allow things + // like nesting reports, etc. + // @todo refactor datasource descriptors to avoid this + _, model, err := s(ctx, def.ModelRef, def.filter) if err != nil { return } @@ -76,7 +80,25 @@ func (def *Datasource) init(ctx context.Context, s iterProvider) (err error) { def.OutAttributes = def.outAttrsFromModel(model) } - def.auxIter = iter + pp := make([]string, 0, len(def.OutAttributes)/2+1) + for _, a := range def.OutAttributes { + if a.Properties().IsPrimary { + pp = append(pp, a.Identifier()) + } + } + + // Assure and attempt to correct the provided sort to conform with the data set and the + // paging cursor (if any) + def.filter, err = assureSort(def.filter, pp) + if err != nil { + return + } + + // Get the iterator for actual use + def.auxIter, _, err = s(ctx, def.ModelRef, def.filter) + if err != nil { + return + } err = def.validate() if err != nil { diff --git a/store/adapters/rdbms/dal/model.go b/store/adapters/rdbms/dal/model.go index ef26ddb66..77142a9f7 100644 --- a/store/adapters/rdbms/dal/model.go +++ b/store/adapters/rdbms/dal/model.go @@ -32,6 +32,10 @@ type ( table drivers.TableCodec } + + parsedFilter interface { + ExpressionParsed() *ql.ASTNode + } ) func validate(m *dal.Model) error { @@ -93,37 +97,50 @@ func Model(m *dal.Model, c queryRunner, d drivers.Dialect) *model { // since we're just initializing fairly light structs. func (d *model) parseQuery(q string) (out exp.Expression, err error) { pp := ql.Converter( - ql.SymHandler(func(node *ql.ASTNode) (exp.Expression, error) { - // @note normalize system idents on the RDBMS level for filters - // offloaded to the database. - sym := dal.NormalizeAttrNames(node.Symbol) - if d.model.ResourceType == "corteza::compose:module" { - // temporary solution - // - // before DAL some fields were aliased (recordID => ID) - switch sym { - case "recordID": - sym = "ID" - } - } - - attr := d.model.Attributes.FindByIdent(sym) - if attr == nil { - return nil, fmt.Errorf("unknown attribute %q used in query expression", node.Symbol) - } - - if !attr.Filterable { - return nil, fmt.Errorf("attribute %q can not be used in query expression", attr.Ident) - } - - return d.table.AttributeExpression(sym) - }), + ql.SymHandler(d.qlConverterGenericHandlers()), ql.RefHandler(d.dialect.ExprHandler), ) return pp.Parse(q) } +func (d *model) convertQuery(n *ql.ASTNode) (out exp.Expression, err error) { + pp := ql.Converter( + ql.SymHandler(d.qlConverterGenericHandlers()), + ql.RefHandler(d.dialect.ExprHandler), + ) + + return pp.Convert(n) +} + +func (d *model) qlConverterGenericHandlers() func(node *ql.ASTNode) (exp.Expression, error) { + return func(node *ql.ASTNode) (exp.Expression, error) { + // @note normalize system idents on the RDBMS level for filters + // offloaded to the database. + sym := dal.NormalizeAttrNames(node.Symbol) + if d.model.ResourceType == "corteza::compose:module" { + // temporary solution + // + // before DAL some fields were aliased (recordID => ID) + switch sym { + case "recordID": + sym = "ID" + } + } + + attr := d.model.Attributes.FindByIdent(sym) + if attr == nil { + return nil, fmt.Errorf("unknown attribute %q used in query expression", node.Symbol) + } + + if !attr.Filterable { + return nil, fmt.Errorf("attribute %q can not be used in query expression", attr.Ident) + } + + return d.table.AttributeExpression(sym) + } +} + func (d *model) Truncate(ctx context.Context) error { sql, args, err := d.truncateSql().ToSQL() if err != nil { @@ -391,6 +408,17 @@ func (d *model) searchSql(f filter.Filter) *goqu.SelectDataset { } } + if pf, ok := f.(parsedFilter); ok { + tmp := pf.ExpressionParsed() + if tmp != nil { + n, err := d.convertQuery(tmp) + if err != nil { + return base.SetError(err) + } + cnd = append(cnd, n) + } + } + if q := strings.TrimSpace(f.Expression()); len(q) > 0 { if tmp, err = d.parseQuery(q); err != nil { return base.SetError(err)