221 lines
4.3 KiB
Go
221 lines
4.3 KiB
Go
package dal
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/cortezaproject/corteza-server/pkg/filter"
|
|
)
|
|
|
|
type (
|
|
// OrderedBuffer provides the buffered data in the specified order
|
|
OrderedBuffer interface {
|
|
Buffer
|
|
|
|
// InOrder makes the buffer provide the stored data in the specified order
|
|
InOrder(ss ...*filter.SortExpr) (err error)
|
|
}
|
|
|
|
// Buffer provides a place where you can buffer the data provided by DAL
|
|
Buffer interface {
|
|
// Seek moves the index pointer to the specified location
|
|
// After the Seek call, a Next() call is required
|
|
Seek(context.Context, int) error
|
|
|
|
// Len returns the number of elements in the buffer
|
|
Len() int
|
|
|
|
Iterator
|
|
Adder
|
|
}
|
|
|
|
Adder interface {
|
|
// Add adds a new ValueGetter to the buffer
|
|
Add(context.Context, ValueGetter) (err error)
|
|
}
|
|
|
|
row struct {
|
|
counters map[string]uint
|
|
values valueSet
|
|
|
|
// ...
|
|
|
|
// Metadata to make it easier to work with
|
|
// @todo add when needed
|
|
}
|
|
|
|
valueSet map[string][]any
|
|
)
|
|
|
|
func (r row) SelectGVal(ctx context.Context, k string) (interface{}, error) {
|
|
if r.values[k] == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if len(r.values[k]) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
o := r.values[k][0]
|
|
return o, nil
|
|
}
|
|
|
|
func (r *row) Reset() {
|
|
for k := range r.counters {
|
|
r.counters[k] = 0
|
|
}
|
|
}
|
|
|
|
func (r *row) SetValue(name string, pos uint, v any) error {
|
|
if r.values == nil {
|
|
r.values = make(valueSet)
|
|
}
|
|
if r.counters == nil {
|
|
r.counters = make(map[string]uint)
|
|
}
|
|
|
|
// Make sure there is space for it
|
|
// @note benchmarking proves that the rest of the function introduces
|
|
// a lot of memory pressure.
|
|
// Investigate options on reworking this/reducing allocations.
|
|
if int(pos)+1 > len(r.values[name]) {
|
|
r.values[name] = append(r.values[name], make([]any, (int(pos)+1)-len(r.values[name]))...)
|
|
}
|
|
|
|
r.values[name][pos] = v
|
|
r.counters[name]++
|
|
|
|
return nil
|
|
}
|
|
|
|
// WithValue is a simple helper to construct rows with populated values
|
|
// The main use is for tests so restrain from using it in code.
|
|
func (r *row) WithValue(name string, pos uint, v any) *row {
|
|
err := r.SetValue(name, pos, v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func (r *row) CountValues() map[string]uint {
|
|
return r.counters
|
|
}
|
|
|
|
func (r *row) GetValue(name string, pos uint) (any, error) {
|
|
return r.values[name][pos], nil
|
|
}
|
|
|
|
func (r *row) String() string {
|
|
out := make([]string, 0, 20)
|
|
for k, vv := range r.values {
|
|
for i, v := range vv {
|
|
out = append(out, fmt.Sprintf("%s [%d] %v", k, i, v))
|
|
}
|
|
}
|
|
|
|
return strings.Join(out, " | ")
|
|
}
|
|
|
|
func (r row) Copy() *row {
|
|
out := &r
|
|
|
|
out.values = out.values.Copy()
|
|
|
|
return out
|
|
}
|
|
|
|
func (vv valueSet) Copy() valueSet {
|
|
out := make(valueSet)
|
|
|
|
for n, vv := range vv {
|
|
out[n] = vv
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func mergeRows(mapping []AttributeMapping, dst *row, ss ...*row) (err error) {
|
|
if len(mapping) == 0 {
|
|
return mergeRowsFull(dst, ss...)
|
|
}
|
|
|
|
return mergeRowsMapped(mapping, dst, ss...)
|
|
}
|
|
|
|
func mergeRowsFull(dst *row, rows ...*row) (err error) {
|
|
for _, r := range rows {
|
|
for name, vv := range r.values {
|
|
for i, values := range vv {
|
|
if dst.values == nil {
|
|
dst.values = make(valueSet)
|
|
dst.counters = make(map[string]uint)
|
|
}
|
|
|
|
if i == 0 {
|
|
dst.values[name] = make([]any, len(vv))
|
|
dst.counters[name] = 0
|
|
}
|
|
|
|
err = dst.SetValue(name, uint(i), values)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func mergeRowsMapped(mapping []AttributeMapping, out *row, rows ...*row) (err error) {
|
|
|
|
for _, mp := range mapping {
|
|
name := mp.Source()
|
|
for _, r := range rows {
|
|
if r.values[name] != nil {
|
|
if out.values == nil {
|
|
out.values = make(valueSet)
|
|
out.counters = make(map[string]uint)
|
|
}
|
|
|
|
out.values[mp.Identifier()] = r.values[name]
|
|
out.counters[mp.Identifier()] = r.counters[name]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// makeRowComparator is a utility for easily making a row comparator for
|
|
// the given sort expression
|
|
func makeRowComparator(ss ...*filter.SortExpr) func(a, b *row) bool {
|
|
return func(a, b *row) bool {
|
|
for _, s := range ss {
|
|
cmp := compareGetters(a, b, a.counters, b.counters, s.Column)
|
|
|
|
less, skip := evalCmpResult(cmp, s)
|
|
if !skip {
|
|
return less
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
func evalCmpResult(cmp int, s *filter.SortExpr) (less, skip bool) {
|
|
if cmp != 0 {
|
|
if s.Descending {
|
|
return cmp > 0, false
|
|
}
|
|
return cmp < 0, false
|
|
}
|
|
|
|
return false, true
|
|
}
|