diff --git a/compose/component.cue b/compose/component.cue index 9525bb0dc..139b8e297 100644 --- a/compose/component.cue +++ b/compose/component.cue @@ -15,7 +15,6 @@ component: schema.#component & { "namespace": namespace "page": page "record": record - "record-value": recordValue } rbac: operations: { diff --git a/compose/record_value.cue b/compose/record_value.cue deleted file mode 100644 index 0f4101065..000000000 --- a/compose/record_value.cue +++ /dev/null @@ -1,60 +0,0 @@ -package compose - -import ( - "github.com/cortezaproject/corteza-server/codegen/schema" -) - -recordValue: schema.#Resource & { - struct: { - record_id: { primaryKey: true, goType: "uint64", ident: "recordID" } - name: { primaryKey: true, } - place: { primaryKey: true, goType: "uint" } - ref: { goType: "uint64" } - value: { } - deleted_at: schema.SortableTimestampNilField - } - - features: { - labels: false - paging: false - sorting: false - checkFn: false - } - - filter: { - struct: { - record_id: { goType: "[]uint64", ident: "recordID" } - deleted: { goType: "filter.State", storeIdent: "deleted_at" } - } - - byValue: ["record_id"] - byNilState: ["deleted"] - } - - - store: { - ident: "composeRecordValue" - - settings: { - rdbms: { - table: "compose_record_value" - } - } - - api: { - - - functions: [ - { - expIdent: "ComposeRecordValueRefLookup" - args: [ - { ident: "mod", goType: "*types.Module" }, - { ident: "field", goType: "string" }, - { ident: "ref", goType: "uint64" } - ] - return: [ "uint64" ] - } - ] - } - } -} diff --git a/compose/service/record.go b/compose/service/record.go index a9f37f684..3cb2882ee 100644 --- a/compose/service/record.go +++ b/compose/service/record.go @@ -181,7 +181,8 @@ func defaultValidator(svc RecordService) recordValuesValidator { return 0, nil } - return store.ComposeRecordValueRefLookup(ctx, s, m, f.Name, v.Ref) + // @todo re-implement record-value ref lookup through DAL + panic("implement me") }) validator.RecordRefChecker(func(ctx context.Context, s store.Storer, v *types.RecordValue, f *types.ModuleField, m *types.Module) (bool, error) { diff --git a/pkg/envoy/store/compose.go b/pkg/envoy/store/compose.go index 339daa453..884ecc02a 100644 --- a/pkg/envoy/store/compose.go +++ b/pkg/envoy/store/compose.go @@ -31,7 +31,6 @@ type ( store.ComposeModules store.ComposeNamespaces store.ComposePages - store.ComposeRecordValues } dalService interface { diff --git a/store/adapters/rdbms/aux_types.gen.go b/store/adapters/rdbms/aux_types.gen.go index 663076ed4..49fd78d54 100644 --- a/store/adapters/rdbms/aux_types.gen.go +++ b/store/adapters/rdbms/aux_types.gen.go @@ -308,16 +308,6 @@ type ( DeletedAt *time.Time `db:"deleted_at"` } - // auxComposeRecordValue is an auxiliary structure used for transporting to/from RDBMS store - auxComposeRecordValue struct { - RecordID uint64 `db:"record_id"` - Name string `db:"name"` - Place uint `db:"place"` - Ref uint64 `db:"ref"` - Value string `db:"value"` - DeletedAt *time.Time `db:"deleted_at"` - } - // auxCredential is an auxiliary structure used for transporting to/from RDBMS store auxCredential struct { ID uint64 `db:"id"` @@ -1687,47 +1677,6 @@ func (aux *auxComposePage) scan(row scanner) error { ) } -// encodes ComposeRecordValue to auxComposeRecordValue -// -// This function is auto-generated -func (aux *auxComposeRecordValue) encode(res *composeType.RecordValue) (_ error) { - aux.RecordID = res.RecordID - aux.Name = res.Name - aux.Place = res.Place - aux.Ref = res.Ref - aux.Value = res.Value - aux.DeletedAt = res.DeletedAt - return -} - -// decodes ComposeRecordValue from auxComposeRecordValue -// -// This function is auto-generated -func (aux auxComposeRecordValue) decode() (res *composeType.RecordValue, _ error) { - res = new(composeType.RecordValue) - res.RecordID = aux.RecordID - res.Name = aux.Name - res.Place = aux.Place - res.Ref = aux.Ref - res.Value = aux.Value - res.DeletedAt = aux.DeletedAt - return -} - -// scans row and fills auxComposeRecordValue fields -// -// This function is auto-generated -func (aux *auxComposeRecordValue) scan(row scanner) error { - return row.Scan( - &aux.RecordID, - &aux.Name, - &aux.Place, - &aux.Ref, - &aux.Value, - &aux.DeletedAt, - ) -} - // encodes Credential to auxCredential // // This function is auto-generated diff --git a/store/adapters/rdbms/custom_compose_records.go b/store/adapters/rdbms/custom_compose_records.go deleted file mode 100644 index f51ee46fa..000000000 --- a/store/adapters/rdbms/custom_compose_records.go +++ /dev/null @@ -1,52 +0,0 @@ -package rdbms - -import ( - "context" - - composeType "github.com/cortezaproject/corteza-server/compose/types" - "github.com/cortezaproject/corteza-server/pkg/report" -) - -func (s Store) ComposeRecordReport(ctx context.Context, mod *composeType.Module, metrics string, dimensions string, filters string) ([]map[string]interface{}, error) { - //TODO implement me - panic("implement me") -} - -func (s Store) ComposeRecordDatasource(ctx context.Context, mod *composeType.Module, ld *report.LoadStepDefinition) (report.Datasource, error) { - //TODO implement me - panic("implement me") -} - -func (s Store) PartialComposeRecordValueUpdate(ctx context.Context, mod *composeType.Module, values ...*composeType.RecordValue) error { - //TODO implement me - panic("implement me") -} - -func (s Store) ComposeRecordValueRefLookup(ctx context.Context, m *composeType.Module, field string, ref uint64) (uint64, error) { - //q := s.composeRecordValuesSelectBuilder(). - // Join(s.composeRecordTable("crd"), "crv.record_id = crd.id"). - // Where(squirrel.Eq{ - // "crv.name": field, - // "crv.ref": ref, - // "crv.deleted_at": nil, - // "crd.module_id": m.ID, - // "crd.deleted_at": nil, - // }). - // Column("record_id"). - // Limit(1) - // - //row, err := s.QueryRow(ctx, q) - //if errors.Is(err, sql.ErrNoRows) { - // return 0, nil - //} else if err != nil { - // return 0, err - //} - // - //var recordID uint64 - //if err = row.Scan(&recordID); err != nil { - // return 0, err - //} - // - //return recordID, nil - return 0, nil -} diff --git a/store/adapters/rdbms/filters.gen.go b/store/adapters/rdbms/filters.gen.go index 028823df5..a662f79bf 100644 --- a/store/adapters/rdbms/filters.gen.go +++ b/store/adapters/rdbms/filters.gen.go @@ -83,9 +83,6 @@ type ( // optional composePage filter function called after the generated function ComposePage func(*Store, composeType.PageFilter) ([]goqu.Expression, composeType.PageFilter, error) - // optional composeRecordValue filter function called after the generated function - ComposeRecordValue func(*Store, composeType.RecordValueFilter) ([]goqu.Expression, composeType.RecordValueFilter, error) - // optional credential filter function called after the generated function Credential func(*Store, systemType.CredentialFilter) ([]goqu.Expression, systemType.CredentialFilter, error) @@ -658,26 +655,6 @@ func ComposePageFilter(f composeType.PageFilter) (ee []goqu.Expression, _ compos return ee, f, err } -// ComposeRecordValueFilter returns logical expressions -// -// This function is called from Store.QueryComposeRecordValues() and can be extended -// by setting Store.Filters.ComposeRecordValue. Extension is called after all expressions -// are generated and can choose to ignore or alter them. -// -// This function is auto-generated -func ComposeRecordValueFilter(f composeType.RecordValueFilter) (ee []goqu.Expression, _ composeType.RecordValueFilter, err error) { - - if expr := stateNilComparison("deleted_at", f.Deleted); expr != nil { - ee = append(ee, expr) - } - - if len(f.RecordID) > 0 { - ee = append(ee, goqu.C("record_id").In(f.RecordID)) - } - - return ee, f, err -} - // CredentialFilter returns logical expressions // // This function is called from Store.QueryCredentials() and can be extended diff --git a/store/adapters/rdbms/queries.gen.go b/store/adapters/rdbms/queries.gen.go index 1bbc8259e..d9212aab7 100644 --- a/store/adapters/rdbms/queries.gen.go +++ b/store/adapters/rdbms/queries.gen.go @@ -2095,96 +2095,6 @@ var ( } } - // composeRecordValueTable represents composeRecordValues store table - // - // This value is auto-generated - composeRecordValueTable = goqu.T("compose_record_value") - - // composeRecordValueSelectQuery assembles select query for fetching composeRecordValues - // - // This function is auto-generated - composeRecordValueSelectQuery = func(d goqu.DialectWrapper) *goqu.SelectDataset { - return d.Select( - "record_id", - "name", - "place", - "ref", - "value", - "deleted_at", - ).From(composeRecordValueTable) - } - - // composeRecordValueInsertQuery assembles query inserting composeRecordValues - // - // This function is auto-generated - composeRecordValueInsertQuery = func(d goqu.DialectWrapper, res *composeType.RecordValue) *goqu.InsertDataset { - return d.Insert(composeRecordValueTable). - Rows(goqu.Record{ - "record_id": res.RecordID, - "name": res.Name, - "place": res.Place, - "ref": res.Ref, - "value": res.Value, - "deleted_at": res.DeletedAt, - }) - } - - // composeRecordValueUpsertQuery assembles (insert+on-conflict) query for replacing composeRecordValues - // - // This function is auto-generated - composeRecordValueUpsertQuery = func(d goqu.DialectWrapper, res *composeType.RecordValue) *goqu.InsertDataset { - var target = `,record_id,name,place` - - return composeRecordValueInsertQuery(d, res). - OnConflict( - goqu.DoUpdate(target[1:], - goqu.Record{ - "ref": res.Ref, - "value": res.Value, - "deleted_at": res.DeletedAt, - }, - ), - ) - } - - // composeRecordValueUpdateQuery assembles query for updating composeRecordValues - // - // This function is auto-generated - composeRecordValueUpdateQuery = func(d goqu.DialectWrapper, res *composeType.RecordValue) *goqu.UpdateDataset { - return d.Update(composeRecordValueTable). - Set(goqu.Record{ - "ref": res.Ref, - "value": res.Value, - "deleted_at": res.DeletedAt, - }). - Where(composeRecordValuePrimaryKeys(res)) - } - - // composeRecordValueDeleteQuery assembles delete query for removing composeRecordValues - // - // This function is auto-generated - composeRecordValueDeleteQuery = func(d goqu.DialectWrapper, ee ...goqu.Expression) *goqu.DeleteDataset { - return d.Delete(composeRecordValueTable).Where(ee...) - } - - // composeRecordValueDeleteQuery assembles delete query for removing composeRecordValues - // - // This function is auto-generated - composeRecordValueTruncateQuery = func(d goqu.DialectWrapper) *goqu.TruncateDataset { - return d.Truncate(composeRecordValueTable) - } - - // composeRecordValuePrimaryKeys assembles set of conditions for all primary keys - // - // This function is auto-generated - composeRecordValuePrimaryKeys = func(res *composeType.RecordValue) goqu.Ex { - return goqu.Ex{ - "record_id": res.RecordID, - "name": res.Name, - "place": res.Place, - } - } - // credentialTable represents credentials store table // // This value is auto-generated diff --git a/store/adapters/rdbms/rdbms.gen.go b/store/adapters/rdbms/rdbms.gen.go index cafdcb597..377f86b30 100644 --- a/store/adapters/rdbms/rdbms.gen.go +++ b/store/adapters/rdbms/rdbms.gen.go @@ -47,7 +47,6 @@ var ( _ store.ComposeModuleFields = &Store{} _ store.ComposeNamespaces = &Store{} _ store.ComposePages = &Store{} - _ store.ComposeRecordValues = &Store{} _ store.Credentials = &Store{} _ store.DalConnections = &Store{} _ store.DalSensitivityLevels = &Store{} @@ -8529,265 +8528,6 @@ func (s *Store) checkComposePageConstraints(ctx context.Context, res *composeTyp return nil } -// CreateComposeRecordValue creates one or more rows in composeRecordValue collection -// -// This function is auto-generated -func (s *Store) CreateComposeRecordValue(ctx context.Context, rr ...*composeType.RecordValue) (err error) { - for i := range rr { - if err = s.checkComposeRecordValueConstraints(ctx, rr[i]); err != nil { - return - } - - if err = s.Exec(ctx, composeRecordValueInsertQuery(s.Dialect, rr[i])); err != nil { - return - } - } - - return -} - -// UpdateComposeRecordValue updates one or more existing entries in composeRecordValue collection -// -// This function is auto-generated -func (s *Store) UpdateComposeRecordValue(ctx context.Context, rr ...*composeType.RecordValue) (err error) { - for i := range rr { - if err = s.checkComposeRecordValueConstraints(ctx, rr[i]); err != nil { - return - } - - if err = s.Exec(ctx, composeRecordValueUpdateQuery(s.Dialect, rr[i])); err != nil { - return - } - } - - return -} - -// UpsertComposeRecordValue updates one or more existing entries in composeRecordValue collection -// -// This function is auto-generated -func (s *Store) UpsertComposeRecordValue(ctx context.Context, rr ...*composeType.RecordValue) (err error) { - for i := range rr { - if err = s.checkComposeRecordValueConstraints(ctx, rr[i]); err != nil { - return - } - - if err = s.Exec(ctx, composeRecordValueUpsertQuery(s.Dialect, rr[i])); err != nil { - return - } - } - - return -} - -// DeleteComposeRecordValue Deletes one or more entries from composeRecordValue collection -// -// This function is auto-generated -func (s *Store) DeleteComposeRecordValue(ctx context.Context, rr ...*composeType.RecordValue) (err error) { - for i := range rr { - if err = s.Exec(ctx, composeRecordValueDeleteQuery(s.Dialect, composeRecordValuePrimaryKeys(rr[i]))); err != nil { - return - } - } - - return nil -} - -// DeleteComposeRecordValueByID deletes single entry from composeRecordValue collection -// -// This function is auto-generated -func (s *Store) DeleteComposeRecordValueByRecordIDNamePlace(ctx context.Context, recordID uint64, name string, place uint) error { - return s.Exec(ctx, composeRecordValueDeleteQuery(s.Dialect, goqu.Ex{ - "record_id": recordID, - "name": name, - "place": place, - })) -} - -// TruncateComposeRecordValues Deletes all rows from the composeRecordValue collection -func (s Store) TruncateComposeRecordValues(ctx context.Context) error { - return s.Exec(ctx, composeRecordValueTruncateQuery(s.Dialect)) -} - -// SearchComposeRecordValues returns (filtered) set of ComposeRecordValues -// -// This function is auto-generated -func (s *Store) SearchComposeRecordValues(ctx context.Context, f composeType.RecordValueFilter) (set composeType.RecordValueSet, _ composeType.RecordValueFilter, err error) { - - set, _, err = s.QueryComposeRecordValues(ctx, f) - if err != nil { - return nil, f, err - } - - return set, f, nil -} - -// QueryComposeRecordValues queries the database, converts and checks each row and returns collected set -// -// With generics, we can remove this per-resource-generated function -// and replace it with a single utility fetcher -// -// This function is auto-generated -func (s *Store) QueryComposeRecordValues( - ctx context.Context, - f composeType.RecordValueFilter, -) (_ []*composeType.RecordValue, more bool, err error) { - var ( - set = make([]*composeType.RecordValue, 0, DefaultSliceCapacity) - res *composeType.RecordValue - aux *auxComposeRecordValue - rows *sql.Rows - count uint - expr, tExpr []goqu.Expression - ) - - if s.Filters.ComposeRecordValue != nil { - // extended filter set - tExpr, f, err = s.Filters.ComposeRecordValue(s, f) - } else { - // using generated filter - tExpr, f, err = ComposeRecordValueFilter(f) - } - - if err != nil { - err = fmt.Errorf("could generate filter expression for ComposeRecordValue: %w", err) - return - } - - expr = append(expr, tExpr...) - - query := composeRecordValueSelectQuery(s.Dialect).Where(expr...) - - if f.Limit > 0 { - query = query.Limit(f.Limit) - } - - rows, err = s.Query(ctx, query) - if err != nil { - err = fmt.Errorf("could not query ComposeRecordValue: %w", err) - return - } - - if err = rows.Err(); err != nil { - err = fmt.Errorf("could not query ComposeRecordValue: %w", err) - return - } - - defer func() { - closeError := rows.Close() - if err == nil { - // return error from close - err = closeError - } - }() - - for rows.Next() { - if err = rows.Err(); err != nil { - err = fmt.Errorf("could not query ComposeRecordValue: %w", err) - return - } - - aux = new(auxComposeRecordValue) - if err = aux.scan(rows); err != nil { - err = fmt.Errorf("could not scan rows for ComposeRecordValue: %w", err) - return - } - - count++ - if res, err = aux.decode(); err != nil { - err = fmt.Errorf("could not decode ComposeRecordValue: %w", err) - return - } - - set = append(set, res) - } - - return set, false, err - -} - -// sortableComposeRecordValueFields returns all columns flagged as sortable -// -// With optional string arg, all columns are returned aliased -// -// This function is auto-generated -func (Store) sortableComposeRecordValueFields() map[string]string { - return map[string]string{ - "deleted_at": "deleted_at", - "deletedat": "deleted_at", - "name": "name", - "place": "place", - "record_id": "record_id", - "recordid": "record_id", - } -} - -// collectComposeRecordValueCursorValues collects values from the given resource that and sets them to the cursor -// to be used for pagination -// -// Values that are collected must come from sortable, unique or primary columns/fields -// At least one of the collected columns must be flagged as unique, otherwise fn appends primary keys at the end -// -// Known issue: -// when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on -// undeleted items) -// -// This function is auto-generated -func (s *Store) collectComposeRecordValueCursorValues(res *composeType.RecordValue, cc ...*filter.SortExpr) *filter.PagingCursor { - var ( - cur = &filter.PagingCursor{LThen: filter.SortExprSet(cc).Reversed()} - - hasUnique bool - - pkRecordID bool - pkName bool - pkPlace bool - - collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { - case "recordID": - cur.Set(c.Column, res.RecordID, c.Descending) - pkRecordID = true - case "name": - cur.Set(c.Column, res.Name, c.Descending) - pkName = true - case "place": - cur.Set(c.Column, res.Place, c.Descending) - pkPlace = true - case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) - } - } - } - ) - - collect(cc...) - if !hasUnique || !pkRecordID { - collect(&filter.SortExpr{Column: "recordID", Descending: false}) - } - if !hasUnique || !pkName { - collect(&filter.SortExpr{Column: "name", Descending: false}) - } - if !hasUnique || !pkPlace { - collect(&filter.SortExpr{Column: "place", Descending: false}) - } - - return cur - -} - -// checkComposeRecordValueConstraints performs lookups (on valid) resource to check if any of the values on unique fields -// already exists in the store -// -// Using built-in constraint checking would be more performant, but unfortunately we cannot rely -// on the full support (MySQL does not support conditional indexes) -// -// This function is auto-generated -func (s *Store) checkComposeRecordValueConstraints(ctx context.Context, res *composeType.RecordValue) (err error) { - return nil -} - // CreateCredential creates one or more rows in credential collection // // This function is auto-generated diff --git a/store/interfaces.gen.go b/store/interfaces.gen.go index 33b7e297b..02cc8c76f 100644 --- a/store/interfaces.gen.go +++ b/store/interfaces.gen.go @@ -56,7 +56,6 @@ type ( ComposeModuleFields ComposeNamespaces ComposePages - ComposeRecordValues Credentials DalConnections DalSensitivityLevels @@ -304,17 +303,6 @@ type ( ReorderComposePages(ctx context.Context, namespace_id uint64, parent_id uint64, page_ids []uint64) error } - ComposeRecordValues interface { - SearchComposeRecordValues(ctx context.Context, f composeType.RecordValueFilter) (composeType.RecordValueSet, composeType.RecordValueFilter, error) - CreateComposeRecordValue(ctx context.Context, rr ...*composeType.RecordValue) error - UpdateComposeRecordValue(ctx context.Context, rr ...*composeType.RecordValue) error - UpsertComposeRecordValue(ctx context.Context, rr ...*composeType.RecordValue) error - DeleteComposeRecordValue(ctx context.Context, rr ...*composeType.RecordValue) error - DeleteComposeRecordValueByRecordIDNamePlace(ctx context.Context, recordID uint64, name string, place uint) error - TruncateComposeRecordValues(ctx context.Context) error - ComposeRecordValueRefLookup(ctx context.Context, mod *composeType.Module, field string, ref uint64) (uint64, error) - } - Credentials interface { SearchCredentials(ctx context.Context, f systemType.CredentialFilter) (systemType.CredentialSet, systemType.CredentialFilter, error) CreateCredential(ctx context.Context, rr ...*systemType.Credential) error @@ -1790,62 +1778,6 @@ func ReorderComposePages(ctx context.Context, s ComposePages, namespace_id uint6 return s.ReorderComposePages(ctx, namespace_id, parent_id, page_ids) } -// SearchComposeRecordValues returns all matching ComposeRecordValues from store -// -// This function is auto-generated -func SearchComposeRecordValues(ctx context.Context, s ComposeRecordValues, f composeType.RecordValueFilter) (composeType.RecordValueSet, composeType.RecordValueFilter, error) { - return s.SearchComposeRecordValues(ctx, f) -} - -// CreateComposeRecordValue creates one or more ComposeRecordValues in store -// -// This function is auto-generated -func CreateComposeRecordValue(ctx context.Context, s ComposeRecordValues, rr ...*composeType.RecordValue) error { - return s.CreateComposeRecordValue(ctx, rr...) -} - -// UpdateComposeRecordValue updates one or more (existing) ComposeRecordValues in store -// -// This function is auto-generated -func UpdateComposeRecordValue(ctx context.Context, s ComposeRecordValues, rr ...*composeType.RecordValue) error { - return s.UpdateComposeRecordValue(ctx, rr...) -} - -// UpsertComposeRecordValue creates new or updates existing one or more ComposeRecordValues in store -// -// This function is auto-generated -func UpsertComposeRecordValue(ctx context.Context, s ComposeRecordValues, rr ...*composeType.RecordValue) error { - return s.UpsertComposeRecordValue(ctx, rr...) -} - -// DeleteComposeRecordValue deletes one or more ComposeRecordValues from store -// -// This function is auto-generated -func DeleteComposeRecordValue(ctx context.Context, s ComposeRecordValues, rr ...*composeType.RecordValue) error { - return s.DeleteComposeRecordValue(ctx, rr...) -} - -// DeleteComposeRecordValueByID deletes one or more ComposeRecordValues from store -// -// This function is auto-generated -func DeleteComposeRecordValueByRecordIDNamePlace(ctx context.Context, s ComposeRecordValues, recordID uint64, name string, place uint) error { - return s.DeleteComposeRecordValueByRecordIDNamePlace(ctx, recordID, name, place) -} - -// TruncateComposeRecordValues Deletes all ComposeRecordValues from store -// -// This function is auto-generated -func TruncateComposeRecordValues(ctx context.Context, s ComposeRecordValues) error { - return s.TruncateComposeRecordValues(ctx) -} - -// ComposeRecordValueRefLookup -// -// This function is auto-generated -func ComposeRecordValueRefLookup(ctx context.Context, s ComposeRecordValues, mod *composeType.Module, field string, ref uint64) (uint64, error) { - return s.ComposeRecordValueRefLookup(ctx, mod, field, ref) -} - // SearchCredentials returns all matching Credentials from store // // This function is auto-generated diff --git a/store/tests/all_test.go b/store/tests/all_test.go index 1d7960214..570bc33e6 100644 --- a/store/tests/all_test.go +++ b/store/tests/all_test.go @@ -68,9 +68,6 @@ func testAllGenerated(t *testing.T, s store.Storer) { t.Run("composePage", func(t *testing.T) { testComposePages(t, s) }) - t.Run("composeRecordValue", func(t *testing.T) { - testComposeRecordValues(t, s) - }) t.Run("credential", func(t *testing.T) { testCredentials(t, s) }) diff --git a/store/tests/compose_record_values_test.go b/store/tests/compose_record_values_test.go deleted file mode 100644 index bf5f64bb5..000000000 --- a/store/tests/compose_record_values_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package tests - -import ( - "testing" - - "github.com/cortezaproject/corteza-server/store" -) - -func testComposeRecordValues(t *testing.T, s store.ComposeRecordValues) { - t.Run("RefLookup", func(t *testing.T) { - t.Skip("not implemented") - }) -} diff --git a/store/tests/compose_records_test.go b/store/tests/compose_records_test.go deleted file mode 100644 index 74ea9bf7d..000000000 --- a/store/tests/compose_records_test.go +++ /dev/null @@ -1,1694 +0,0 @@ -package tests - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "github.com/cortezaproject/corteza-server/compose/types" - "github.com/cortezaproject/corteza-server/pkg/filter" - "github.com/cortezaproject/corteza-server/pkg/id" - "github.com/cortezaproject/corteza-server/store" - "github.com/stretchr/testify/require" -) - -func testComposeRecords(t *testing.T, s store.ComposeRecords) { - t.Skip("temporary disabled") - - var ( - ctx = context.Background() - - offset = 0 - - mod = &types.Module{ - ID: id.Next(), - NamespaceID: id.Next(), - Handle: "", - Name: "testComposeRecords", - CreatedAt: time.Now(), - Fields: types.ModuleFieldSet{ - &types.ModuleField{Kind: "String", Name: "str1"}, - &types.ModuleField{Kind: "String", Name: "str2"}, - &types.ModuleField{Kind: "String", Name: "str3"}, - &types.ModuleField{Kind: "Number", Name: "num1"}, - &types.ModuleField{Kind: "Number", Name: "num2"}, - &types.ModuleField{Kind: "Number", Name: "num3"}, - &types.ModuleField{Kind: "DateTime", Name: "dt1"}, - &types.ModuleField{Kind: "String", Name: "strMulti", Multi: true}, - - // These are used bellow to test (almost) all permutations - &types.ModuleField{Kind: "Bool", Name: "bool1"}, - &types.ModuleField{Kind: "Bool", Name: "bool2"}, - &types.ModuleField{Kind: "Bool", Name: "bool3"}, - - &types.ModuleField{Kind: "DateTime", Name: "datetime1"}, - &types.ModuleField{Kind: "DateTime", Name: "datetime2"}, - &types.ModuleField{Kind: "DateTime", Name: "datetime3"}, - &types.ModuleField{Kind: "DateTime", Name: "date1", Options: map[string]interface{}{"onlyDate": true}}, - &types.ModuleField{Kind: "DateTime", Name: "time1", Options: map[string]interface{}{"onlyTime": true}}, - - &types.ModuleField{Kind: "Email", Name: "email1"}, - &types.ModuleField{Kind: "Email", Name: "email2"}, - &types.ModuleField{Kind: "Email", Name: "email3"}, - - // &types.ModuleField{Kind: "File", Name: "file1"}, - // &types.ModuleField{Kind: "File", Name: "file2"}, - // &types.ModuleField{Kind: "File", Name: "file3"}, - - &types.ModuleField{Kind: "Select", Name: "select1"}, - &types.ModuleField{Kind: "Select", Name: "select2"}, - &types.ModuleField{Kind: "Select", Name: "select3"}, - - &types.ModuleField{Kind: "Number", Name: "number1"}, - &types.ModuleField{Kind: "Number", Name: "number2"}, - &types.ModuleField{Kind: "Number", Name: "number3"}, - - // &types.ModuleField{Kind: "Record", Name: "record1"}, - // &types.ModuleField{Kind: "Record", Name: "record2"}, - // &types.ModuleField{Kind: "Record", Name: "record3"}, - - &types.ModuleField{Kind: "String", Name: "string1"}, - &types.ModuleField{Kind: "String", Name: "string2"}, - &types.ModuleField{Kind: "String", Name: "string3"}, - - &types.ModuleField{Kind: "Url", Name: "url1"}, - &types.ModuleField{Kind: "Url", Name: "url2"}, - &types.ModuleField{Kind: "Url", Name: "url3"}, - - // &types.ModuleField{Kind: "User", Name: "user1"}, - // &types.ModuleField{Kind: "User", Name: "user2"}, - // &types.ModuleField{Kind: "User", Name: "user3"}, - }, - } - - makeNew = func(vv ...*types.RecordValue) *types.Record { - // minimum data set for new composeRecord - var recordID = id.Next() - - for _, v := range vv { - v.RecordID = recordID - } - - offset++ - return &types.Record{ - ID: recordID, - NamespaceID: mod.NamespaceID, - ModuleID: mod.ID, - // if we don't round this up to a second we'll confuse the sorting - CreatedAt: time.Now().Round(time.Second).Add(time.Second * time.Duration(offset)), - Values: vv, - } - } - - makeNewUpd = func(t *testing.T, dt string, vv ...*types.RecordValue) *types.Record { - r := makeNew(vv...) - - if dt != "" { - n, err := time.Parse(time.RFC3339, dt) - if err != nil { - t.Error(err) - } - r.UpdatedAt = &n - } - return r - } - - truncAndCreate = func(t *testing.T, rr ...*types.Record) (*require.Assertions, types.RecordSet) { - req := require.New(t) - req.NoError(s.TruncateComposeRecords(ctx, mod)) - - if len(rr) == 0 { - rr = []*types.Record{makeNew()} - } - - for _, rec := range rr { - req.NoError(s.CreateComposeRecord(ctx, mod, rec)) - } - - return req, rr - } - - stringifyValues = func(set types.RecordSet, fields ...string) string { - var out string - for r := range set { - if r > 0 { - out += ";" - } - - for f := range fields { - if f > 0 { - out += "," - } - - v := set[r].Values.Get(fields[f], 0) - if v != nil { - out += v.Value - } else { - out += "" - } - - } - - } - - return out - } - ) - - t.Run("create", func(t *testing.T) { - req := require.New(t) - composeRecord := makeNew() - req.NoError(s.CreateComposeRecord(ctx, mod, composeRecord)) - }) - - t.Run("lookup by ID", func(t *testing.T) { - req, rr := truncAndCreate(t, makeNew( - &types.RecordValue{Name: "str1", Value: "v1", Ref: 1}, - &types.RecordValue{Name: "str2", Value: "v2", Ref: 2}, - &types.RecordValue{Name: "str3", Value: "v3", Ref: 3}, - )) - rec := rr[0] - - fetched, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) - req.NoError(err) - req.Equal(rec.ID, fetched.ID) - req.NotNil(fetched.CreatedAt) - req.Nil(fetched.UpdatedAt) - req.Nil(fetched.DeletedAt) - req.Len(fetched.Values, len(rec.Values)) - req.Equal("str2", fetched.Values[1].Name) - req.Equal("v2", fetched.Values[1].Value) - req.Equal(uint64(2), fetched.Values[1].Ref) - }) - - t.Run("update", func(t *testing.T) { - req, rr := truncAndCreate(t) - rec := rr[0] - - rec = &types.Record{ - ID: rec.ID, - CreatedAt: rec.CreatedAt, - ModuleID: mod.ID, - NamespaceID: mod.NamespaceID, - OwnedBy: id.Next(), - } - - req.NoError(s.UpdateComposeRecord(ctx, mod, rec)) - - updated, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) - req.NoError(err) - req.Equal(rec.OwnedBy, updated.OwnedBy) - }) - - t.Run("update values", func(t *testing.T) { - req, rr := truncAndCreate(t, makeNew( - &types.RecordValue{Name: "str1", Value: "v1", Ref: 1}, - &types.RecordValue{Name: "str2", Value: "v2", Ref: 2}, - )) - rec := rr[0] - - rec = &types.Record{ - ID: rec.ID, - CreatedAt: rec.CreatedAt, - OwnedBy: id.Next(), - Values: rec.Values, - ModuleID: mod.ID, - NamespaceID: mod.NamespaceID, - } - - rec.Values[0].Value = "vv10" - rec.Values[1].Value = "vv20" - rec.Values = append(rec.Values, &types.RecordValue{Name: "str3", Value: "vv30", Ref: 3}) - rec.Values.SetRecordID(rec.ID) - - req.NoError(s.UpdateComposeRecord(ctx, mod, rec)) - - updated, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) - req.NoError(err) - req.Equal(rec.OwnedBy, updated.OwnedBy) - req.Len(updated.Values, len(rec.Values)) - req.Equal("str2", updated.Values[1].Name) - req.Equal("vv20", updated.Values[1].Value) - }) - - t.Run("soft delete values", func(t *testing.T) { - req, rr := truncAndCreate(t, makeNew( - &types.RecordValue{Name: "str1", Value: "v1", Ref: 1}, - &types.RecordValue{Name: "str2", Value: "v2", Ref: 2}, - )) - rec := rr[0] - rec.DeletedAt = &rec.CreatedAt - - req.NoError(s.UpdateComposeRecord(ctx, mod, rec)) - - updated, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) - - req.NoError(err) - req.NotNil(rec) - req.NotNil(rec.DeletedAt) - req.Len(updated.Values, len(rec.Values)) - req.NotNil(updated.Values[0].DeletedAt) - req.NotNil(updated.Values[1].DeletedAt) - }) - - t.Run("delete", func(t *testing.T) { - t.Run("by Record", func(t *testing.T) { - req, rr := truncAndCreate(t) - rec := rr[0] - - req.NoError(s.DeleteComposeRecord(ctx, mod, rec)) - _, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) - req.EqualError(err, store.ErrNotFound.Error()) - }) - - t.Run("by ID", func(t *testing.T) { - req, rr := truncAndCreate(t) - rec := rr[0] - - req.NoError(s.DeleteComposeRecordByID(ctx, mod, rec.ID)) - _, err := s.LookupComposeRecordByID(ctx, mod, rec.ID) - req.EqualError(err, store.ErrNotFound.Error()) - }) - }) - - t.Run("search", func(t *testing.T) { - t.Run("by record attributes", func(t *testing.T) { - prefill := []*types.Record{ - makeNew(), - makeNew(), - makeNew(), - makeNew(), - makeNew(), - } - - count := len(prefill) - - prefill[4].DeletedAt = &prefill[4].CreatedAt - valid := count - 1 - - req, _ := truncAndCreate(t, prefill...) - - // search for all valid - set, _, err := s.SearchComposeRecords(ctx, mod, types.RecordFilter{}) - req.NoError(err) - req.Len(set, valid) // we've deleted one - - // search for ALL - set, _, err = s.SearchComposeRecords(ctx, mod, types.RecordFilter{Deleted: filter.StateInclusive}) - req.NoError(err) - req.Len(set, count) // we've deleted one - - // search for deleted only - set, _, err = s.SearchComposeRecords(ctx, mod, types.RecordFilter{Deleted: filter.StateExclusive}) - req.NoError(err) - req.Len(set, 1) // we've deleted one - }) - - t.Run("by values", func(t *testing.T) { - var ( - err error - set types.RecordSet - - req, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "str1", Value: "v1"}, &types.RecordValue{Name: "str2", Value: "same"}, &types.RecordValue{Name: "str3", Value: "three"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v2"}, &types.RecordValue{Name: "str2", Value: "same"}, &types.RecordValue{Name: "str3", Value: "three"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v3"}, &types.RecordValue{Name: "str2", Value: "same"}, &types.RecordValue{Name: "str3", Value: "three"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v4"}, &types.RecordValue{Name: "str2", Value: "same"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v5"}, &types.RecordValue{Name: "str2", Value: "same"}), - - // Add one additional record with deleted values - makeNew(&types.RecordValue{Name: "str1", Value: "v6", DeletedAt: now()}, &types.RecordValue{Name: "str2", Value: "deleted", DeletedAt: now()}), - ) - - f = types.RecordFilter{ - ModuleID: mod.ID, - NamespaceID: mod.NamespaceID, - } - ) - - f.Query = `str1 = 'v1'` - set, _, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Len(set, 1) - - f.Query = `str2 = 'same'` - set, _, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Len(set, 5) - - f.Query = `str2 = 'different'` - set, _, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Len(set, 0) - - f.Query = `str3 = 'three' AND str1 = 'v1'` - set, _, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Len(set, 1) - }) - - t.Run("with like op", func(t *testing.T) { - var ( - req = require.New(t) - f = types.RecordFilter{Query: `str3 LIKE '%“2020%'`} - - _, _, err = s.SearchComposeRecords(ctx, mod, f) - ) - req.NoError(err) - }) - - t.Run("with bool field", func(t *testing.T) { - var ( - req = require.New(t) - f = types.RecordFilter{Query: `bool1 = true`} - - _, _, err = s.SearchComposeRecords(ctx, mod, f) - ) - req.NoError(err) - }) - }) - - t.Run("paging and sorting", func(t *testing.T) { - type ( - tc struct { - // how data is sorted - sort string - - // expected data - rval []string - - // how cursors should be set when moving forward/backward - curr []int - } - ) - var ( - _, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "str1", Value: "v1"}, &types.RecordValue{Name: "str3", Value: "a"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v2"}, &types.RecordValue{Name: "str3", Value: "b"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v3"}, &types.RecordValue{Name: "str3", Value: "b"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v4"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v5"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v6"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v7"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v8"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v9"}, &types.RecordValue{Name: "str3", Value: "c"}), - ) - ) - - prevCur := 1 - nextCur := 2 - bothCur := prevCur | nextCur - - // tests if cursors are properly set/unset by inspecting req. bits - testCursors := func(req *require.Assertions, b int, f types.RecordFilter) { - if b&prevCur == 0 { - req.Nil(f.PrevPage) - } else { - req.NotNil(f.PrevPage) - } - - if b&nextCur == 0 { - req.Nil(f.NextPage) - } else { - req.NotNil(f.NextPage) - } - } - - tcc := []tc{ - { - "id", - []string{"v1,a;v2,b;v3,b", "v4,;v5,;v6,", "v7,;v8,;v9,c"}, - []int{nextCur, bothCur, prevCur}, - }, - { - "id DESC", - []string{"v9,c;v8,;v7,", "v6,;v5,;v4,", "v3,b;v2,b;v1,a"}, - []int{nextCur, bothCur, prevCur}, - }, - { - "str1", - []string{"v1,a;v2,b;v3,b", "v4,;v5,;v6,", "v7,;v8,;v9,c"}, - []int{nextCur, bothCur, prevCur}, - }, - { - "str1 DESC", - []string{"v9,c;v8,;v7,", "v6,;v5,;v4,", "v3,b;v2,b;v1,a"}, - []int{nextCur, bothCur, prevCur}, - }, - { - "str3", - []string{"v4,;v5,;v6,", "v7,;v8,;v1,a", "v2,b;v3,b;v9,c"}, - []int{nextCur, bothCur, prevCur}, - }, - { - "str3 DESC", - []string{"v9,c;v3,b;v2,b", "v1,a;v8,;v7,", "v6,;v5,;v4,"}, - []int{nextCur, bothCur, prevCur}, - }, - } - - for _, tc := range tcc { - t.Run("crawling: "+tc.sort, func(t *testing.T) { - var ( - req = require.New(t) - - f = types.RecordFilter{} - set types.RecordSet - err error - ) - - f.Sort.Set(tc.sort) - f.Limit = 3 - - for p := 0; p < 3; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := 1; p >= 0; p-- { - f.Sort = nil - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - testCursors(req, tc.curr[p], f) - - // reverse to previous page - f.PageCursor = f.PrevPage - } - - f.PageCursor = f.NextPage - - for p := 1; p < 3; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - }) - } - - t.Run("advanced sorting", func(t *testing.T) { - var ( - _, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0010"}), - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0009"}), - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0008"}), - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0007"}), - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0006"}), - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0005"}), - ) - ) - - tcc := []tc{ - { - "str1", - []string{"0001,0010;0001,0009;0001,0008", "0001,0007;0001,0006;0001,0005"}, - []int{nextCur, prevCur}, - }, - { - "str1, str3 DESC", - []string{"0001,0010;0001,0009;0001,0008", "0001,0007;0001,0006;0001,0005"}, - []int{nextCur, prevCur}, - }, - { - "str1 DESC, str3", - []string{"0001,0005;0001,0006;0001,0007", "0001,0008;0001,0009;0001,0010"}, - []int{nextCur, prevCur}, - }, - } - - for _, tc := range tcc { - t.Run("crawling: "+tc.sort, func(t *testing.T) { - var ( - req = require.New(t) - - f = types.RecordFilter{} - set types.RecordSet - err error - ) - - f.Sort.Set(tc.sort) - f.Limit = 3 - - for p := 0; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := 0; p >= 0; p-- { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - testCursors(req, tc.curr[p], f) - - // reverse to previous page - f.PageCursor = f.PrevPage - } - - f.PageCursor = f.NextPage - - for p := 1; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - }) - } - }) - - t.Run("advanced sorting; NULL; #1", func(t *testing.T) { - var ( - _, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0010"}), - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0009"}), - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0008"}), - makeNew( /* padding */ &types.RecordValue{Name: "str3", Value: "0007"}), - makeNew( /* padding */ &types.RecordValue{Name: "str3", Value: "0006"}), - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0005"}), - ) - ) - - tcc := []tc{ - { - "str1", - []string{",0007;,0006;0001,0010", "0001,0009;0001,0008;0001,0005"}, - []int{nextCur, prevCur}, - }, - { - "str1 DESC", - []string{"0001,0005;0001,0008;0001,0009", "0001,0010;,0006;,0007"}, - []int{nextCur, prevCur}, - }, - { - "str1, str3 DESC", - []string{",0007;,0006;0001,0010", "0001,0009;0001,0008;0001,0005"}, - []int{nextCur, prevCur}, - }, - { - "str1 DESC, str3 DESC", - []string{"0001,0010;0001,0009;0001,0008", "0001,0005;,0007;,0006"}, - []int{nextCur, prevCur}, - }, - } - - for _, tc := range tcc { - t.Run("crawling: "+tc.sort, func(t *testing.T) { - var ( - req = require.New(t) - - f = types.RecordFilter{} - set types.RecordSet - err error - ) - - f.Sort.Set(tc.sort) - f.Limit = 3 - - for p := 0; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := 0; p >= 0; p-- { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - testCursors(req, tc.curr[p], f) - - // reverse to previous page - f.PageCursor = f.PrevPage - } - - f.PageCursor = f.NextPage - - for p := 1; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - }) - } - }) - - t.Run("advanced sorting; NULL; #2", func(t *testing.T) { - var ( - _, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "str1", Value: "0001"}, &types.RecordValue{Name: "str3", Value: "0010"}), - makeNew( /* padding */ &types.RecordValue{Name: "str3", Value: "0009"}), - makeNew(&types.RecordValue{Name: "str1", Value: "0001"} /* padding */), - makeNew( /* padding */ /* padding */ ), - ) - ) - - tcc := []tc{ - { - "str1, str3", - []string{",;,0009", "0001,;0001,0010"}, - []int{nextCur, prevCur}, - }, - { - "str1, str3 DESC", - []string{",0009;,", "0001,0010;0001,"}, - []int{nextCur, prevCur}, - }, - { - "str1 DESC, str3", - []string{"0001,;0001,0010", ",;,0009"}, - []int{nextCur, prevCur}, - }, - { - "str1 DESC, str3 DESC", - []string{"0001,0010;0001,", ",0009;,"}, - []int{nextCur, prevCur}, - }, - } - - for _, tc := range tcc { - t.Run("crawling: "+tc.sort, func(t *testing.T) { - var ( - req = require.New(t) - - f = types.RecordFilter{} - set types.RecordSet - err error - ) - - f.Sort.Set(tc.sort) - f.Limit = 2 - - for p := 0; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := 0; p >= 0; p-- { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - testCursors(req, tc.curr[p], f) - - // reverse to previous page - f.PageCursor = f.PrevPage - } - - f.PageCursor = f.NextPage - - for p := 1; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "str1", "str3")) - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - }) - } - }) - - t.Run("advanced sorting; NULL; #4", func(t *testing.T) { - // Build all record value permutations for the given field based on a pattern in a 3x8 matrix - // - // The matrix defines all NULL / NOT NULL permutations where NOT NULL values define a mini permutation - // of increasing values -- a good enough permutation to prove correctness. - // - // Boolean and pure Timestamp values are tested elsewhere - permute := func(ptrn string, f1, f2, f3 string) { - spf := func(ptr, val string) string { - return fmt.Sprintf(ptrn, val) - } - - _, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: f1, Value: spf(ptrn, "1")}, &types.RecordValue{Name: f2, Value: spf(ptrn, "1")}, &types.RecordValue{Name: f3, Value: spf(ptrn, "1")}), - makeNew( /* padding */ &types.RecordValue{Name: f2, Value: spf(ptrn, "1")}, &types.RecordValue{Name: f3, Value: spf(ptrn, "2")}), - makeNew(&types.RecordValue{Name: f1, Value: spf(ptrn, "1")} /* padding */, &types.RecordValue{Name: f3, Value: spf(ptrn, "3")}), - makeNew( /* padding */ /* padding */ &types.RecordValue{Name: f3, Value: spf(ptrn, "4")}), - makeNew(&types.RecordValue{Name: f1, Value: spf(ptrn, "2")}, &types.RecordValue{Name: f2, Value: spf(ptrn, "3")} /* padding */), - makeNew( /* padding */ &types.RecordValue{Name: f2, Value: spf(ptrn, "3")} /* padding */), - makeNew(&types.RecordValue{Name: f1, Value: spf(ptrn, "2")} /* padding */ /* padding */), - makeNew( /* padding */ /* padding */ /* padding */ ), - ) - } - - kinds := []struct { - kind string - ptrn string - }{ - {kind: "DateTime", ptrn: "2021-02-0%sT01:00:00.000Z"}, - {kind: "Email", ptrn: "test+e%s@test.tld"}, - {kind: "Select", ptrn: "opt%s"}, - {kind: "Number", ptrn: "%s"}, - {kind: "String", ptrn: "string%s"}, - {kind: "Url", ptrn: "https://www.testko-%s.tld"}, - // { kind: "Bool", ptrn: ""}, - // { kind: "File", ptrn: ""}, - // { kind: "Record", ptrn: ""}, - // { kind: "User", ptrn: ""}, - } - - // These test cases check all 3 field sort permutations. - // Each field kind must return items in the same order. - // - // 0 == - tcc := []struct { - sort string - curr []int - exp [][]string - }{ - { - sort: "%s, %s, %s", - curr: []int{nextCur, bothCur, bothCur, prevCur}, - exp: [][]string{ - {"0", "0", "0"}, - {"0", "0", "4"}, - {"0", "1", "2"}, - {"0", "3", "0"}, - {"1", "0", "3"}, - {"1", "1", "1"}, - {"2", "0", "0"}, - {"2", "3", "0"}, - }, - }, - { - sort: "%s DESC, %s, %s", - curr: []int{nextCur, bothCur, bothCur, prevCur}, - exp: [][]string{ - {"2", "0", "0"}, - {"2", "3", "0"}, - {"1", "0", "3"}, - {"1", "1", "1"}, - {"0", "0", "0"}, - {"0", "0", "4"}, - {"0", "1", "2"}, - {"0", "3", "0"}, - }, - }, - { - sort: "%s, %s DESC, %s", - curr: []int{nextCur, bothCur, bothCur, prevCur}, - exp: [][]string{ - {"0", "3", "0"}, - {"0", "1", "2"}, - {"0", "0", "0"}, - {"0", "0", "4"}, - {"1", "1", "1"}, - {"1", "0", "3"}, - {"2", "3", "0"}, - {"2", "0", "0"}, - }, - }, - { - sort: "%s DESC, %s DESC, %s", - curr: []int{nextCur, bothCur, bothCur, prevCur}, - exp: [][]string{ - {"2", "3", "0"}, - {"2", "0", "0"}, - {"1", "1", "1"}, - {"1", "0", "3"}, - {"0", "3", "0"}, - {"0", "1", "2"}, - {"0", "0", "0"}, - {"0", "0", "4"}, - }, - }, - { - sort: "%s, %s, %s DESC", - curr: []int{nextCur, bothCur, bothCur, prevCur}, - exp: [][]string{ - {"0", "0", "4"}, - {"0", "0", "0"}, - {"0", "1", "2"}, - {"0", "3", "0"}, - {"1", "0", "3"}, - {"1", "1", "1"}, - {"2", "0", "0"}, - {"2", "3", "0"}, - }, - }, - { - sort: "%s DESC, %s, %s DESC", - curr: []int{nextCur, bothCur, bothCur, prevCur}, - exp: [][]string{ - {"2", "0", "0"}, - {"2", "3", "0"}, - {"1", "0", "3"}, - {"1", "1", "1"}, - {"0", "0", "4"}, - {"0", "0", "0"}, - {"0", "1", "2"}, - {"0", "3", "0"}, - }, - }, - { - sort: "%s, %s DESC, %s DESC", - curr: []int{nextCur, bothCur, bothCur, prevCur}, - exp: [][]string{ - {"0", "3", "0"}, - {"0", "1", "2"}, - {"0", "0", "4"}, - {"0", "0", "0"}, - {"1", "1", "1"}, - {"1", "0", "3"}, - {"2", "3", "0"}, - {"2", "0", "0"}, - }, - }, - { - sort: "%s DESC, %s DESC, %s DESC", - curr: []int{nextCur, bothCur, bothCur, prevCur}, - exp: [][]string{ - {"2", "3", "0"}, - {"2", "0", "0"}, - {"1", "1", "1"}, - {"1", "0", "3"}, - {"0", "3", "0"}, - {"0", "1", "2"}, - {"0", "0", "4"}, - {"0", "0", "0"}, - }, - }, - } - - // Helper to comvert expected items from above-defined cases into a string. - expToString := func(ptrn string, exp [][]string) string { - rr := make([]string, 0, len(exp)) - - for _, page := range exp { - rec := make([]string, 0) - for _, val := range page { - if val == "0" { - rec = append(rec, "") - } else { - rec = append(rec, fmt.Sprintf(ptrn, val)) - } - } - rr = append(rr, strings.Join(rec, ",")) - } - return strings.Join(rr, ";") - } - - for _, k := range kinds { - for _, tc := range tcc { - - f1 := strings.ToLower(k.kind) + "1" - f2 := strings.ToLower(k.kind) + "2" - f3 := strings.ToLower(k.kind) + "3" - - tc.sort = fmt.Sprintf(tc.sort, f1, f2, f3) - - t.Run("crawling: "+k.kind+"; "+tc.sort, func(t *testing.T) { - var ( - req = require.New(t) - - f = types.RecordFilter{} - set types.RecordSet - err error - ) - - f.Sort.Set(tc.sort) - f.Limit = 2 - - permute(k.ptrn, f1, f2, f3) - - for p := 0; p < 4; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - ff := p * 2 - req.Equal(expToString(k.ptrn, tc.exp[ff:ff+int(f.Limit)]), stringifyValues(set, strings.ToLower(k.kind)+"1", strings.ToLower(k.kind)+"2", strings.ToLower(k.kind)+"3")) - - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := 2; p >= 0; p-- { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - ff := p * 2 - req.Equal(expToString(k.ptrn, tc.exp[ff:ff+int(f.Limit)]), stringifyValues(set, strings.ToLower(k.kind)+"1", strings.ToLower(k.kind)+"2", strings.ToLower(k.kind)+"3")) - testCursors(req, tc.curr[p], f) - - // reverse to previous page - f.PageCursor = f.PrevPage - } - - f.PageCursor = f.NextPage - - for p := 1; p < 4; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - ff := p * 2 - req.Equal(expToString(k.ptrn, tc.exp[ff:ff+int(f.Limit)]), stringifyValues(set, strings.ToLower(k.kind)+"1", strings.ToLower(k.kind)+"2", strings.ToLower(k.kind)+"3")) - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - }) - } - } - }) - - t.Run("advanced sorting; NULL; Booleans", func(t *testing.T) { - var ( - _, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "bool1", Value: ""}, &types.RecordValue{Name: "bool3", Value: ""}), - makeNew( /* padding */ &types.RecordValue{Name: "bool3", Value: "1"}), - makeNew(&types.RecordValue{Name: "bool1", Value: "1"} /* padding */), - makeNew( /* padding */ /* padding */ ), - ) - ) - - tcc := []tc{ - { - "bool1, bool3", - []string{",;,1", ",;1,"}, - []int{nextCur, prevCur}, - }, - { - "bool1, bool3 DESC", - []string{",1;,", ",;1,"}, - []int{nextCur, prevCur}, - }, - { - "bool1 DESC, bool3", - []string{"1,;,", ",;,1"}, - []int{nextCur, prevCur}, - }, - { - "bool1 DESC, bool3 DESC", - []string{"1,;,", ",1;,"}, - []int{nextCur, prevCur}, - }, - } - - for _, tc := range tcc { - t.Run("crawling: "+tc.sort, func(t *testing.T) { - var ( - req = require.New(t) - - f = types.RecordFilter{} - set types.RecordSet - err error - ) - - f.Sort.Set(tc.sort) - f.Limit = 2 - - for p := 0; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - req.Equal(tc.rval[p], stringifyValues(set, "bool1", "bool3")) - - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := 0; p >= 0; p-- { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "bool1", "bool3")) - testCursors(req, tc.curr[p], f) - - // reverse to previous page - f.PageCursor = f.PrevPage - } - - f.PageCursor = f.NextPage - - for p := 1; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyValues(set, "bool1", "bool3")) - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - }) - } - }) - - t.Run("advanced sorting; disable multi-value sorting", func(t *testing.T) { - var ( - _, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "strMulti", Place: 0, Value: "a"}, &types.RecordValue{Name: "strMulti", Place: 1, Value: "b"}, &types.RecordValue{Name: "strMulti", Place: 2, Value: "c"}), - ) - ) - - var ( - req = require.New(t) - - f = types.RecordFilter{} - err error - ) - - f.Sort.Set("strMulti DESC") - f.Limit = 100 - - _, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.Error(err, "by.errors.notAllowedToSort multi-value fields: strMulti") - }) - - t.Run("advanced sorting; NULL; record value + sys fields", func(t *testing.T) { - var ( - _, _ = truncAndCreate(t, - makeNewUpd(t, "2021-01-01T01:00:00Z", &types.RecordValue{Name: "str1", Value: "a"}), - makeNewUpd(t, "" /* padding */, &types.RecordValue{Name: "str1", Value: "b"}), - makeNewUpd(t, "2021-01-01T02:00:00Z"), - makeNewUpd(t, "" /* padding */), - ) - ) - - tcc := []tc{ - { - "updatedAt, str1", - []string{",;,b", "2021-01-01T01:00:00Z,a;2021-01-01T02:00:00Z,"}, - []int{nextCur, prevCur}, - }, - { - "updatedAt, str1 DESC", - []string{",b;,", "2021-01-01T01:00:00Z,a;2021-01-01T02:00:00Z,"}, - []int{nextCur, prevCur}, - }, - { - "updatedAt DESC, str1", - []string{"2021-01-01T02:00:00Z,;2021-01-01T01:00:00Z,a", ",;,b"}, - []int{nextCur, prevCur}, - }, - { - "updatedAt DESC, str1 DESC", - []string{"2021-01-01T02:00:00Z,;2021-01-01T01:00:00Z,a", ",b;,"}, - []int{nextCur, prevCur}, - }, - } - - stringifyMix := func(set types.RecordSet, fields ...string) string { - var out string - for r := range set { - if r > 0 { - out += ";" - } - - for f := range fields { - if f > 0 { - out += "," - } - - if fields[f] == "updatedAt" { - v := set[r].UpdatedAt - if v != nil && !v.IsZero() { - out += v.UTC().Format(time.RFC3339) - } else { - out += "" - } - } else { - v := set[r].Values.Get(fields[f], 0) - if v != nil { - out += v.Value - } else { - out += "" - } - } - } - } - return out - } - - for _, tc := range tcc { - t.Run("crawling: "+tc.sort, func(t *testing.T) { - var ( - req = require.New(t) - - f = types.RecordFilter{} - set types.RecordSet - err error - ) - - f.Sort.Set(tc.sort) - f.Limit = 2 - - for p := 0; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - req.Equal(tc.rval[p], stringifyMix(set, "updatedAt", "str1")) - - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := 0; p >= 0; p-- { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - - req.Equal(tc.rval[p], stringifyMix(set, "updatedAt", "str1")) - testCursors(req, tc.curr[p], f) - - // reverse to previous page - f.PageCursor = f.PrevPage - } - - f.PageCursor = f.NextPage - - for p := 1; p < 2; p++ { - set, f, err = store.SearchComposeRecords(ctx, s, mod, f) - req.NoError(err) - req.True(tc.sort == f.Sort.String() || strings.HasPrefix(f.Sort.String(), tc.sort+",")) - req.Equal(tc.rval[p], stringifyMix(set, "updatedAt", "str1")) - - testCursors(req, tc.curr[p], f) - - // advance to next page - f.PageCursor = f.NextPage - } - }) - } - }) - - }) - - t.Run("sort by system field, paged", func(t *testing.T) { - var ( - req, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "str1", Value: "v1"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v2"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v3"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v4"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v5"}), - ) - - err error - set types.RecordSet - f = types.RecordFilter{ - ModuleID: mod.ID, - NamespaceID: mod.NamespaceID, - } - ) - - // sorting by system field - - f.Limit = 2 - - req.NoError(f.Sort.Set("str1, createdAt")) - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v1;v2", stringifyValues(set, "str1")) - req.NotNil(f.NextPage) - req.Nil(f.PrevPage) - - f.PageCursor = f.NextPage - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v3;v4", stringifyValues(set, "str1")) - req.NotNil(f.PrevPage) - req.NotNil(f.NextPage) - - f.PageCursor = f.NextPage - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v5", stringifyValues(set, "str1")) - req.NotNil(f.PrevPage) - req.Nil(f.NextPage) - - f.PageCursor = f.PrevPage - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v3;v4", stringifyValues(set, "str1")) - req.NotNil(f.PrevPage) - req.NotNil(f.NextPage) - - f.Limit = 1 - f.PageCursor = f.PrevPage - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v2", stringifyValues(set, "str1")) - req.NotNil(f.PrevPage) - req.NotNil(f.NextPage) - - f.PageCursor = f.NextPage - f.Limit = 3 - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v3;v4;v5", stringifyValues(set, "str1")) - req.Nil(f.NextPage) - req.NotNil(f.PrevPage) // we can't actually know if we're on the last page or not.. - - // sorting by module field - - f.PageCursor = nil - f.Limit = 2 - req.NoError(f.Sort.Set("str1 DESC")) - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v5;v4", stringifyValues(set, "str1")) - req.NotNil(f.NextPage) - req.Nil(f.PrevPage) - - f.PageCursor = f.NextPage - f.Sort = nil - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v3;v2", stringifyValues(set, "str1")) - req.NotNil(f.NextPage) - req.NotNil(f.PrevPage) - - f.PageCursor = f.PrevPage - f.Sort = nil - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v5;v4", stringifyValues(set, "str1")) - req.NotNil(f.NextPage) - req.Nil(f.PrevPage) - }) - - t.Run("paged", func(t *testing.T) { - var ( - err error - set types.RecordSet - - req, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "str1", Value: "v1"}, &types.RecordValue{Name: "str3", Value: "w3.1"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v2"}, &types.RecordValue{Name: "str3", Value: "w3.2"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v3"}, &types.RecordValue{Name: "str3", Value: "w3.3"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v4"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v5"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v6"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v7"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v8"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v9"}), - ) - - f = types.RecordFilter{ - ModuleID: mod.ID, - NamespaceID: mod.NamespaceID, - } - ) - - req.NoError(f.Sort.Set("str1")) - f.Limit = 3 - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.NotNil(f.NextPage) - req.Nil(f.PrevPage) - req.Equal("v1,w3.1;v2,w3.2;v3,w3.3", stringifyValues(set, "str1", "str3")) - - f.PageCursor = f.NextPage - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("v4,;v5,;v6,", stringifyValues(set, "str1", "str3")) - - req.NoError(f.Sort.Set("str3 DESC, str1 ASC")) - f.PageCursor = nil - f.Limit = 1 - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal("w3.3,v3", stringifyValues(set, "str3", "str1")) - - f.Limit = 1 - req.NoError(f.Sort.Set("str1")) - - validOrderOfStr1 := []int{0, 1, 2, 3, 4, 5, 6, 7} - - f.PageCursor = nil - for p := 1; p <= 5; p++ { - if p > 1 { - f.Sort = nil - } - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.Equal(fmt.Sprintf("v%d", validOrderOfStr1[p]), stringifyValues(set, "str1")) - //fmt.Printf("%-30s\t%v\t%30s\t%30s\n", stringifyValues(set, "str1", "str3"), f.Sort, f.NextPage, f.PrevPage) - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := 4; p >= 1; p-- { - f.Sort = nil - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - //fmt.Printf("%-30s\t%v\t%30s\t%30s\n", stringifyValues(set, "str1", "str3"), f.Sort, f.NextPage, f.PrevPage) - req.Equal(fmt.Sprintf("v%d", validOrderOfStr1[p]), stringifyValues(set, "str1")) - f.PageCursor = f.PrevPage - } - - f.Limit = 1 - - req.NoError(f.Sort.Set("str3 DESC, id DESC")) - f.PageCursor = nil - - validOrderOfStr1 = []int{0, 3, 2, 1, 9, 8, 7} - - for p := 1; p <= 6; p++ { - if p > 1 { - f.Sort = nil - } - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - //fmt.Printf("%-30s\t%v\t%30s\t%30s\n", stringifyValues(set, "str3", "str1"), f.Sort, f.NextPage, f.PrevPage) - req.Equal(fmt.Sprintf("v%d", validOrderOfStr1[p]), stringifyValues(set, "str1")) - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := 5; p >= 1; p-- { - f.Sort = nil - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - //fmt.Printf("%-30s\t%v\t%30s\t%30s\n", stringifyValues(set, "str3", "str1"), f.Sort, f.NextPage, f.PrevPage) - req.Equal(fmt.Sprintf("v%d", validOrderOfStr1[p]), stringifyValues(set, "str1")) - f.PageCursor = f.PrevPage - } - - req.NoError(f.Sort.Set("str3 ASC, id ASC")) - f.PageCursor = nil - - validOrderOfStr1 = []int{0, 4, 5, 6, 7, 8, 9, 1, 2} - - for p := 1; p <= len(validOrderOfStr1)-1; p++ { - if p > 1 { - f.Sort = nil - } - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - //fmt.Printf("%-30s\t%v\t%30s\t%30s\n", stringifyValues(set, "str3", "str1"), f.Sort, f.NextPage, f.PrevPage) - req.Equal(fmt.Sprintf("v%d", validOrderOfStr1[p]), stringifyValues(set, "str1")) - f.PageCursor = f.NextPage - } - - f.PageCursor = f.PrevPage - for p := len(validOrderOfStr1) - 2; p >= 1; p-- { - f.Sort = nil - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - //fmt.Printf("%-30s\t%v\t%30s\t%30s\n", stringifyValues(set, "str3", "str1"), f.Sort, f.NextPage, f.PrevPage) - req.Equal(fmt.Sprintf("v%d", validOrderOfStr1[p]), stringifyValues(set, "str1")) - f.PageCursor = f.PrevPage - } - }) - - t.Run("page navigation", func(t *testing.T) { - var ( - err error - set types.RecordSet - - req, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "str1", Value: "v1"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v2"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v3"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v4"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v5"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v6"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v7"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v8"}), - makeNew(&types.RecordValue{Name: "str1", Value: "v9"}), - ) - - f = types.RecordFilter{ - ModuleID: mod.ID, - NamespaceID: mod.NamespaceID, - } - ) - - // ascending - - req.NoError(f.Sort.Set("str1")) - f.Limit = 2 - f.IncPageNavigation = true - f.IncTotal = true - - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.NotNil(set) - req.NotNil(f.PageNavigation) - req.Equal(uint(9), f.Total) - req.Len(f.PageNavigation, 5) - - f.PageCursor = f.PageNavigation[1].Cursor - f.IncPageNavigation = false - f.IncTotal = false - set, _, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.NotNil(set) - req.Equal("v3;v4", stringifyValues(set, "str1")) - - f.PageCursor = f.PageNavigation[4].Cursor - f.IncPageNavigation = false - f.IncTotal = false - set, _, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.NotNil(set) - req.Equal("v9", stringifyValues(set, "str1")) - - // descending - - req.NoError(f.Sort.Set("str1 DESC")) - f.Limit = 2 - f.PageCursor = nil - f.IncPageNavigation = true - f.IncTotal = true - - set, f, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.NotNil(set) - req.NotNil(f.PageNavigation) - req.Equal(uint(9), f.Total) - req.Len(f.PageNavigation, 5) - - f.PageCursor = f.PageNavigation[1].Cursor - f.IncPageNavigation = false - f.IncTotal = false - set, _, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.NotNil(set) - req.Equal("v7;v6", stringifyValues(set, "str1")) - - f.PageCursor = f.PageNavigation[4].Cursor - f.IncPageNavigation = false - f.IncTotal = false - set, _, err = s.SearchComposeRecords(ctx, mod, f) - req.NoError(err) - req.NotNil(set) - req.Equal("v1", stringifyValues(set, "str1")) - }) - - t.Run("report", func(t *testing.T) { - var ( - err error - - req, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "dt1", Value: "2020-01-01"}, &types.RecordValue{Name: "num1", Value: "1"}, &types.RecordValue{Name: "str3", Value: "three"}), - makeNew(&types.RecordValue{Name: "dt1", Value: "2020-01-01"}, &types.RecordValue{Name: "num1", Value: "2"}, &types.RecordValue{Name: "str3", Value: "three"}), - makeNew(&types.RecordValue{Name: "dt1", Value: "2020-01-01"}, &types.RecordValue{Name: "num1", Value: "3"}, &types.RecordValue{Name: "str3", Value: "three"}), - makeNew(&types.RecordValue{Name: "dt1", Value: "2020-05-01"}, &types.RecordValue{Name: "num1", Value: "4"}), - makeNew(&types.RecordValue{Name: "dt1", Value: "2020-05-01"}, &types.RecordValue{Name: "num1", Value: "5"}), - - // Add one additional record with deleted values - makeNew(&types.RecordValue{Name: "dt1", Value: "2020-05-01", DeletedAt: now()}, &types.RecordValue{Name: "num1", Value: "6", DeletedAt: now()}, &types.RecordValue{Name: "str2", Value: "deleted", DeletedAt: now()}), - ) - - report []map[string]interface{} - ) - - t.Run("base", func(t *testing.T) { - report, err = s.ComposeRecordReport(ctx, mod, "MAX(num1)", "QUARTER(dt1)", "") - req.NoError(err) - req.Len(report, 3) - - // @todo find a way to compare the results - - //expected := []map[string]interface{}{ - // {"count": 3, "dimension_0": 1, "metric_0": 3}, - // {"count": 2, "dimension_0": 2, "metric_0": 5}, - // {"count": 1, "dimension_0": nil, "metric_0": nil}, - //} - // - //req.True( - // reflect.DeepEqual(report, expected), - // "report does not match expected results:\n%#v\n%#v", report, expected) - - report, err = s.ComposeRecordReport(ctx, mod, "COUNT(num1)", "YEAR(dt1)", "") - req.NoError(err) - - report, err = s.ComposeRecordReport(ctx, mod, "SUM(num1)", "DATE(dt1)", "") - req.NoError(err) - - report, err = s.ComposeRecordReport(ctx, mod, "MIN(num1)", "DATE(NOW())", "") - req.NoError(err) - - report, err = s.ComposeRecordReport(ctx, mod, "AVG(num1)", "DATE(NOW())", "") - req.NoError(err) - - // Note that not all functions are compatible across all backends - }) - - t.Run("date formatting", func(t *testing.T) { - report, err = s.ComposeRecordReport(ctx, mod, "MAX(num1)", "DATE_FORMAT(dt1, '%Y-%m-01')", "") - req.NoError(err) - req.Len(report, 3) - - report, err = s.ComposeRecordReport(ctx, mod, "MAX(num1)", "DATE_FORMAT(dt1, '%Y; %j @ %H %i')", "") - req.NoError(err) - req.Len(report, 3) - }) - - }) - - t.Run("partial value update", func(t *testing.T) { - var ( - err error - set types.RecordSet - - req, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "str1", Value: "1st"}, &types.RecordValue{Name: "num1", Value: "1"}), - makeNew(&types.RecordValue{Name: "str1", Value: "2nd"}, &types.RecordValue{Name: "num1", Value: "2"}), - makeNew(&types.RecordValue{Name: "str1", Value: "3rd"}, &types.RecordValue{Name: "num1", Value: "3"}), - ) - ) - - set, _, err = s.SearchComposeRecords(ctx, mod, types.RecordFilter{}) - req.NoError(err) - req.Equal("1st,1;2nd,2;3rd,3", stringifyValues(set, "str1", "num1")) - - ups := &types.RecordValue{RecordID: set[1].ID, Name: "num1", Value: "22"} - req.NoError(s.PartialComposeRecordValueUpdate(ctx, mod, ups)) - - set, _, err = s.SearchComposeRecords(ctx, mod, types.RecordFilter{}) - req.NoError(err) - req.Equal("1st,1;2nd,22;3rd,3", stringifyValues(set, "str1", "num1")) - - }) - - t.Run("date-time filtering", func(t *testing.T) { - var ( - err error - set types.RecordSet - - req, _ = truncAndCreate(t, - makeNew(&types.RecordValue{Name: "num1", Value: "1001"}, &types.RecordValue{Name: "datetime1", Value: "2020-10-01T00:00:01"}), - makeNew(&types.RecordValue{Name: "num1", Value: "1002"}, &types.RecordValue{Name: "datetime1", Value: "2020-10-02T00:00:02"}), - makeNew(&types.RecordValue{Name: "num1", Value: "1003"}, &types.RecordValue{Name: "datetime1", Value: "2020-10-03T00:00:03"}), - makeNew(&types.RecordValue{Name: "num1", Value: "1004"}, &types.RecordValue{Name: "datetime1", Value: "2020-10-04T00:00:03"}), - - makeNew(&types.RecordValue{Name: "num1", Value: "2001"}, &types.RecordValue{Name: "date1", Value: "2020-10-01"}), - makeNew(&types.RecordValue{Name: "num1", Value: "2002"}, &types.RecordValue{Name: "date1", Value: "2020-10-02"}), - makeNew(&types.RecordValue{Name: "num1", Value: "2003"}, &types.RecordValue{Name: "date1", Value: "2020-10-03"}), - makeNew(&types.RecordValue{Name: "num1", Value: "2004"}, &types.RecordValue{Name: "date1", Value: "2020-10-04"}), - - makeNew(&types.RecordValue{Name: "num1", Value: "3001"}, &types.RecordValue{Name: "time1", Value: "01:00:00"}), - makeNew(&types.RecordValue{Name: "num1", Value: "3002"}, &types.RecordValue{Name: "time1", Value: "02:00:00"}), - makeNew(&types.RecordValue{Name: "num1", Value: "3003"}, &types.RecordValue{Name: "time1", Value: "03:00:00"}), - makeNew(&types.RecordValue{Name: "num1", Value: "3004"}, &types.RecordValue{Name: "time1", Value: "04:00:00"}), - ) - - cases = []struct { - query string - result string - }{ - {"datetime1 = '2020-10-02T00:00:02'", "1002"}, - {"TIMESTAMP(datetime1) = TIMESTAMP('2020-10-02T00:00:02')", "1002"}, - {"date1 = '2020-10-02'", "2002"}, - {"DATE(date1) = DATE('2020-10-02')", "2002"}, - {"time1 = '02:00:00'", "3002"}, - {"TIME(time1) = TIME('02:00:00')", "3002"}, - {"datetime1 <= '2020-10-02T00:00:02'", "1001;1002"}, - {"date1 <= '2020-10-02'", "2001;2002"}, - {"time1 <= '02:00:00'", "3001;3002"}, - {"datetime1 > '2020-10-02T00:00:02'", "1003;1004"}, - {"date1 > '2020-10-02'", "2003;2004"}, - {"time1 > '02:00:00'", "3003;3004"}, - {"(TIMESTAMP(DATE_FORMAT(datetime1, '%Y-%m-%dT%H:%i:00Z')) = TIMESTAMP(DATE_FORMAT('2020-10-02T00:00:02', '%Y-%m-%dT%H:%i:00Z')))", "1002"}, - } - ) - - for _, c := range cases { - t.Run(c.query, func(t *testing.T) { - req = require.New(t) - set, _, err = s.SearchComposeRecords(ctx, mod, types.RecordFilter{Query: c.query}) - req.NoError(err) - req.Equal(c.result, stringifyValues(set, "num1")) - }) - } - }) -} diff --git a/store/tests/connection_test.go b/store/tests/connection_test.go deleted file mode 100644 index d5c731273..000000000 --- a/store/tests/connection_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package tests - -import ( - "testing" - - "github.com/cortezaproject/corteza-server/store" -) - -func testConnections(t *testing.T, s store.Connections) { - t.Skip("temporary disabled") - - // ... -} diff --git a/store/tests/dal_connections.go b/store/tests/dal_connections.go new file mode 100644 index 000000000..9c9de77bc --- /dev/null +++ b/store/tests/dal_connections.go @@ -0,0 +1,10 @@ +package tests + +import ( + "github.com/cortezaproject/corteza-server/store" + "testing" +) + +func testDalConnections(t *testing.T, s store.DalConnections) { + // @todo +} diff --git a/store/tests/dal_sensitivity_levels.go b/store/tests/dal_sensitivity_levels.go new file mode 100644 index 000000000..b1ae46c3a --- /dev/null +++ b/store/tests/dal_sensitivity_levels.go @@ -0,0 +1,10 @@ +package tests + +import ( + "github.com/cortezaproject/corteza-server/store" + "testing" +) + +func testDalSensitivityLevels(t *testing.T, s store.DalSensitivityLevels) { + // @todo +} diff --git a/store/tests/data_privacy_request_comments.go b/store/tests/data_privacy_request_comments.go new file mode 100644 index 000000000..e7e2ecfed --- /dev/null +++ b/store/tests/data_privacy_request_comments.go @@ -0,0 +1,10 @@ +package tests + +import ( + "github.com/cortezaproject/corteza-server/store" + "testing" +) + +func testDataPrivacyRequestComments(t *testing.T, s store.DataPrivacyRequestComments) { + // @todo +} diff --git a/store/tests/data_privacy_requests.go b/store/tests/data_privacy_requests.go new file mode 100644 index 000000000..c5218e567 --- /dev/null +++ b/store/tests/data_privacy_requests.go @@ -0,0 +1,10 @@ +package tests + +import ( + "github.com/cortezaproject/corteza-server/store" + "testing" +) + +func testDataPrivacyRequests(t *testing.T, s store.DataPrivacyRequests) { + // @todo +}