3
0

Allow rdbms model ops to use already parsed filter expressions

This commit is contained in:
Tomaž Jerman
2022-09-29 16:34:00 +02:00
parent 75a3394d15
commit 8d480e67c0
2 changed files with 77 additions and 27 deletions

View File

@@ -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 {

View File

@@ -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)