diff --git a/store/rdbms/compose_records.gen.go b/store/rdbms/compose_records.gen.go index 42acad827..9b9d36e1c 100644 --- a/store/rdbms/compose_records.gen.go +++ b/store/rdbms/compose_records.gen.go @@ -11,6 +11,7 @@ package rdbms import ( "context" "database/sql" + "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/errors" diff --git a/store/rdbms/compose_records.go b/store/rdbms/compose_records.go index 63e20fc07..c6da39fbd 100644 --- a/store/rdbms/compose_records.go +++ b/store/rdbms/compose_records.go @@ -482,30 +482,46 @@ func (s Store) convertComposeRecordFilter(m *types.Module, f types.RecordFilter) return false } - identResolver = func(i ql.Ident) (ql.Ident, error) { - var is bool - if i.Value, _, is = isRealRecordCol(i.Value); is { - i.Value += " " - return i, nil - } + identResolver = func(sortBy bool) func(i ql.Ident) (ql.Ident, error) { + return func(i ql.Ident) (ql.Ident, error) { + var is bool + if i.Value, _, is = isRealRecordCol(i.Value); is { + i.Value += " " + return i, nil + } - if !m.Fields.HasName(i.Value) { - return i, fmt.Errorf("unknown field %q", i.Value) - } + if !m.Fields.HasName(i.Value) { + return i, fmt.Errorf("unknown field %q", i.Value) + } - if !alreadyJoined(i.Value) { - join := composeRecordValueJoinTpl - join = strings.ReplaceAll(join, "{alias}", composeRecordValueAliasPfx+i.Value) - join = strings.ReplaceAll(join, "{field}", i.Value) - query = query.LeftJoin(join) - } + if !alreadyJoined(i.Value) { + join := composeRecordValueJoinTpl + join = strings.ReplaceAll(join, "{alias}", composeRecordValueAliasPfx+i.Value) + join = strings.ReplaceAll(join, "{field}", i.Value) + query = query.LeftJoin(join) - return s.FieldToColumnTypeCaster(m.Fields.FindByName(i.Value), i) + if sortBy { + var sortCol string + sortCol, _, _, err = s.config.CastModuleFieldToColumnType(m.Fields.FindByName(i.Value), i.Value) + if err != nil { + return i, err + } + + query = query.Column(squirrel.Alias(squirrel.Expr(sortCol), composeRecordValueAliasPfx+i.Value)) + } + + } + + return s.FieldToColumnTypeCaster(m.Fields.FindByName(i.Value), i) + } } ) // Create query for fetching and counting records. query = s.composeRecordsSelectBuilder(). + Prefix("SELECT "+strings.Join(s.composeRecordColumns("sub"), ", ")+" FROM ("). + Suffix(") AS sub"). + Distinct(). Where("crd.module_id = ?", m.ID). Where("crd.rel_namespace = ?", m.NamespaceID) @@ -528,7 +544,7 @@ func (s Store) convertComposeRecordFilter(m *types.Module, f types.RecordFilter) // Resolve all identifiers found in the query // into their table/column counterparts - fp.OnIdent = identResolver + fp.OnIdent = identResolver(false) if fn, err = fp.ParseExpression(f.Query); err != nil { return @@ -547,7 +563,7 @@ func (s Store) convertComposeRecordFilter(m *types.Module, f types.RecordFilter) // Resolve all identifiers found in sort // into their table/column counterparts - sp.OnIdent = identResolver + sp.OnIdent = identResolver(true) if _, err = sp.ParseColumns(f.Sort.String()); err != nil { return @@ -562,7 +578,7 @@ func (s Store) convertComposeRecordFilter(m *types.Module, f types.RecordFilter) // Resolve all identifiers found in sort // into their table/column counterparts - sp.OnIdent = identResolver + sp.OnIdent = identResolver(true) if _, err = sp.ParseColumns(strings.Join(f.PageCursor.Keys(), ", ")); err != nil { return diff --git a/store/tests/compose_records_test.go b/store/tests/compose_records_test.go index 2ceb3590f..b56cdc892 100644 --- a/store/tests/compose_records_test.go +++ b/store/tests/compose_records_test.go @@ -360,6 +360,35 @@ func testComposeRecords(t *testing.T, s store.ComposeRecords) { ) req.NoError(err) }) + + t.Run("with multi-value field", func(t *testing.T) { + var ( + err error + set types.RecordSet + + req, _ = truncAndCreate(t, + makeNew(&types.RecordValue{Name: "strMulti", Value: "v1"}, &types.RecordValue{Name: "strMulti", Value: "v2", Place: 1}, &types.RecordValue{Name: "strMulti", Value: "v3", Place: 2}), + makeNew(&types.RecordValue{Name: "strMulti", Value: "v1"}, &types.RecordValue{Name: "strMulti", Value: "v2", Place: 1}), + makeNew(&types.RecordValue{Name: "strMulti", Value: "v1"}), + makeNew(&types.RecordValue{Name: "strMulti", Value: "same"}, &types.RecordValue{Name: "strMulti", Value: "same", Place: 1}, &types.RecordValue{Name: "strMulti", Value: "same", Place: 2}), + ) + + f = types.RecordFilter{ + ModuleID: mod.ID, + NamespaceID: mod.NamespaceID, + } + ) + + f.Query = `strMulti = 'v1'` + set, _, err = s.SearchComposeRecords(ctx, mod, f) + req.NoError(err) + req.Len(set, 3) + + f.Query = `strMulti = 'same'` + set, _, err = s.SearchComposeRecords(ctx, mod, f) + req.NoError(err) + req.Len(set, 1) + }) }) t.Run("paging and sorting", func(t *testing.T) { @@ -1167,7 +1196,7 @@ func testComposeRecords(t *testing.T, s store.ComposeRecords) { if fields[f] == "updatedAt" { v := set[r].UpdatedAt if v != nil && !v.IsZero() { - out += v.Format(time.RFC3339) + out += v.UTC().Format(time.RFC3339) } else { out += "" }