diff --git a/server/.env.example b/server/.env.example index 7cab7ab7f..eaabe8d08 100644 --- a/server/.env.example +++ b/server/.env.example @@ -39,7 +39,7 @@ ############################################################################### # Allow insecure (invalid, expired TLS/SSL certificates) connections. -# +# # [IMPORTANT] # ==== # We strongly recommend keeping this value set to false except for local development or demos. @@ -209,7 +209,7 @@ ############################################################################### # Password for the web console endpoint. When running in dev environment, password is not required. -# +# # Corteza intentionally sets default password to random chars to prevent security incidents. # Type: string # Default: @@ -296,7 +296,7 @@ # Email sending # # Configure your local SMTP server or use one of the available providers. -# +# # These values are copied to settings when the server starts and can be managed from the administration console. # We recommend you remove these values after they are copied to settings. # If server detects difference between these options and settings, it shows a warning in the log on server start. @@ -436,7 +436,7 @@ ############################################################################### # Password security allows you to disable constraints to which passwords must conform to. -# +# # [CAUTION] # ==== # Disabling password security can be useful for development environments as it removes the need for complex passwords. @@ -448,12 +448,12 @@ ############################################################################### # Algoritm to be use for JWT signature. -# +# # Supported valus: # - HS256, HS384, HS512 # - PS256, PS384, PS512, # - RS256, RS384, RS512 -# +# # Provide shared secret string for HS256, HS384, HS512 and full private key or path to the file PS* and RS* algorithms. # Type: string # Default: HS512 @@ -462,7 +462,7 @@ ############################################################################### # Secret used for signing JWT tokens. # Value is used only when HS256, HS384 or HS512 algorithm is used. -# +# # [IMPORTANT] # ==== # If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables. @@ -486,7 +486,7 @@ ############################################################################### # Lifetime of the refresh token. Should be much longer than lifetime of the access token. -# +# # Refresh tokens are used to exchange expired access tokens with new ones. # Type: time.Duration # Default: 72h @@ -494,7 +494,7 @@ ############################################################################### # Redirect URL to be sent with OAuth2 authentication request to provider -# +# # `provider` placeholder is replaced with the actual value when used. # Type: string # Default: @@ -502,7 +502,7 @@ ############################################################################### # Secret used for securing cookies -# +# # [IMPORTANT] # ==== # If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables. @@ -545,21 +545,21 @@ ############################################################################### # Maximum time user is allowed to stay idle when logged in without "remember-me" option and before session is expired. -# +# # Recomended value is between an hour and a day. -# +# # [IMPORTANT] # ==== # This affects only profile (/auth) pages. Using applications (admin, compose, ...) does not prolong the session. # ==== -# +# # Type: time.Duration # Default: 24h # AUTH_SESSION_LIFETIME=24h ############################################################################### # Duration of the session in /auth lasts when user logs-in with "remember-me" option. -# +# # If set to 0, "remember-me" option is removed. # Type: time.Duration # Default: 8640h @@ -586,7 +586,7 @@ ############################################################################### # Secret used for securing CSRF protection -# +# # [IMPORTANT] # ==== # If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables. @@ -616,19 +616,19 @@ ############################################################################### # Handle for OAuth2 client used for automatic redirect from /auth/oauth2/go endpoint. -# +# # This simplifies configuration for OAuth2 flow for Corteza Web applications as it removes # the need to suply redirection URL and client ID (oauth2/go endpoint does that internally) -# +# # Type: string # Default: corteza-webapp # AUTH_DEFAULT_CLIENT=corteza-webapp ############################################################################### # Path to js, css, images and template source files -# +# # When corteza starts, if path exists it tries to load template files from it. -# +# # When empty path is set (default value), embedded files are used. # Type: string # Default: @@ -637,7 +637,7 @@ ############################################################################### # When enabled, corteza reloads template before every execution. # Enable this for debugging or when developing auth templates. -# +# # Should be disabled in production where templates do not change between server restarts. # Type: bool # Default: @@ -815,15 +815,17 @@ ############################################################################### # List of compa delimited languages (language tags) to enable. # In case when an enabled language can not be loaded, error is logged. -# +# # When loading language configurations (config.xml) from the configured path(s). -# +# # Type: string # Default: en # LOCALE_LANGUAGES=en ############################################################################### -# One or more paths to locale config and translation files, separated by colon +# One or more paths to locale config and translation files, separated by colon +# +# When with LOCALE_DEVELOPMENT_MODE=true, default value for path is ../../locale # Type: string # Default: # LOCALE_PATH= @@ -832,7 +834,7 @@ # Name of the query string parameter used to pass the language tag (it overrides Accept-Language header). # Set it to empty string to disable detection from the query string. # This parameter is ignored if only one language is enabled -# +# # Type: string # Default: lng # LOCALE_QUERY_STRING_PARAM=lng @@ -863,9 +865,9 @@ ############################################################################### # Disables json format for logging and enables more human-readable output with colors. -# +# # Disable for production. -# +# # Type: bool # Default: # LOG_DEBUG= @@ -873,11 +875,11 @@ ############################################################################### # Minimum logging level. If set to "warn", # Levels warn, error, dpanic panic and fatal will be logged. -# +# # Recommended value for production: warn -# +# # Possible values: debug, info, warn, error, dpanic, panic, fatal -# +# # Type: string # Default: warn # LOG_LEVEL=warn @@ -885,25 +887,25 @@ ############################################################################### # Log filtering rules by level and name (log-level:log-namespace). # Please note that level (LOG_LEVEL) is applied before filter and it affects the final output! -# +# # Leave unset for production. -# +# # Example: # `warn+:* *:auth,workflow.*` # Log warnings, errors, panic, fatals. Everything from auth and workflow is logged. -# -# +# +# # See more examples and documentation here: https://github.com/moul/zapfilter -# +# # Type: string # Default: # LOG_FILTER= ############################################################################### # Set to true to see where the logging was called from. -# +# # Disable for production. -# +# # Type: bool # Default: # LOG_INCLUDE_CALLER= @@ -911,9 +913,9 @@ ############################################################################### # Include stack-trace when logging at a specified level or below. # Disable for production. -# +# # Possible values: debug, info, warn, error, dpanic, panic, fatal -# +# # Type: string # Default: dpanic # LOG_STACKTRACE_LEVEL=dpanic @@ -1008,11 +1010,11 @@ # # Provisioning allows you to configure a {PRODUCT_NAME} instance when deployed. # It occurs automatically after the {PRODUCT_NAME} server starts. -# +# # [IMPORTANT] # ==== # We recommend you to keep provisioning enabled as it simplifies version updates by updating the database and updating settings. -# +# # If you're doing local development or some debugging, you can disable this. # ==== # @@ -1038,7 +1040,7 @@ # ==== # These parameters help in the development and testing process. # When you are deploying to production, these should be disabled to improve performance and reduce storage usage. -# +# # You should configure external services such as Sentry or ELK to keep track of logs and error reports. # ==== # @@ -1144,7 +1146,7 @@ # Delay system startup # # You can configure these options to defer API execution until another external (HTTP) service is up and running. -# +# # [ TIP ] # ==== # Delaying API execution can come in handy in complex setups where execution order is important. @@ -1168,7 +1170,7 @@ ############################################################################### # Space delimited list of hosts and/or URLs to probe. # Host format: `host` or `host:443` (port will default to 80). -# +# # [NOTE] # ==== # Services are probed in parallel. diff --git a/server/codegen/assets/templates/gocode/store/rdbms/rdbms.go.tpl b/server/codegen/assets/templates/gocode/store/rdbms/rdbms.go.tpl index 51a103969..4685a9f07 100644 --- a/server/codegen/assets/templates/gocode/store/rdbms/rdbms.go.tpl +++ b/server/codegen/assets/templates/gocode/store/rdbms/rdbms.go.tpl @@ -10,6 +10,7 @@ import ( "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" + "github.com/modern-go/reflect2" "github.com/cortezaproject/corteza/server/pkg/errors" "github.com/cortezaproject/corteza/server/pkg/filter" "github.com/cortezaproject/corteza/server/store" @@ -551,19 +552,35 @@ func (s *Store) {{ .fnIdent }}(res *{{ .goType }}, cc ...*filter.SortExpr) *filt {{- end }} collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { {{- range .fields }} case {{ printf "%q" .ident }}: - cur.Set(c.Column, res.{{ .expIdent }}, c.Descending) - {{- if .primaryKey }} - pk{{ .expIdent }} = true - {{- else if .unique }} - hasUnique = true - {{- end }} + pk{{ .expIdent }} = true + {{- else if .unique }} + hasUnique = true + {{- end }} + return res.{{ .expIdent }} {{- end }} } + return nil + } + + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) + } } } ) diff --git a/server/pkg/filter/pagination.go b/server/pkg/filter/pagination.go index 960cdc8f7..d126b73d7 100644 --- a/server/pkg/filter/pagination.go +++ b/server/pkg/filter/pagination.go @@ -46,9 +46,11 @@ type ( } PagingCursor struct { - keys []string - values []interface{} - desc []bool + keys []string + kk [][]string + values []interface{} + modifier []string + desc []bool // sort in desc order ROrder bool @@ -109,6 +111,10 @@ func (p *PagingCursor) Walk(fn func(string, interface{}, bool)) { } func (p *PagingCursor) Set(k string, v interface{}, d bool) { + p.SetModifier(k, v, d, "", k) +} + +func (p *PagingCursor) SetModifier(k string, v interface{}, d bool, m string, kk ...string) { for i, key := range p.keys { if key == k { p.values[i] = v @@ -118,6 +124,8 @@ func (p *PagingCursor) Set(k string, v interface{}, d bool) { p.keys = append(p.keys, k) p.values = append(p.values, v) + p.kk = append(p.kk, kk) + p.modifier = append(p.modifier, m) p.desc = append(p.desc, d) } @@ -129,6 +137,14 @@ func (p *PagingCursor) Values() []interface{} { return p.values } +func (p *PagingCursor) KK() [][]string { + return p.kk +} + +func (p *PagingCursor) Modifiers() []string { + return p.modifier +} + func (p *PagingCursor) IsLThen() bool { return p.LThen } @@ -177,13 +193,17 @@ func (p *PagingCursor) String() string { func (p *PagingCursor) MarshalJSON() ([]byte, error) { buf, err := json.Marshal(struct { K []string + KK [][]string V []interface{} + M []string D []bool R bool LT bool }{ p.keys, + p.kk, p.values, + p.modifier, p.desc, p.ROrder, p.LThen, @@ -209,7 +229,9 @@ func (p *PagingCursor) UnmarshalJSON(in []byte) error { var ( aux struct { K []string + KK [][]string V []pagingCursorValue + M []string D []bool R bool LT bool @@ -235,6 +257,8 @@ func (p *PagingCursor) UnmarshalJSON(in []byte) error { } p.keys = aux.K + p.kk = aux.KK + p.modifier = aux.M p.desc = aux.D p.ROrder = aux.R p.LThen = aux.LT @@ -277,6 +301,8 @@ func (p *PagingCursor) Sort(sort SortExprSet) (SortExprSet, error) { for k := range p.keys { sort = append(sort, &SortExpr{ Column: p.keys[k], + columns: p.kk[k], + modifier: p.modifier[k], Descending: p.desc[k], }) } @@ -363,7 +389,7 @@ func (cur *PagingCursor) ToAST(identLookup func(i string) (string, error), castF op := ltOp[lt] - //// Typecast the value so comparison can work properly + // // Typecast the value so comparison can work properly // Either BOTH (field and value) are NULL or field is grater-then value base := &ql.ASTNode{ diff --git a/server/pkg/filter/sorting.go b/server/pkg/filter/sorting.go index 34e440e38..02604e861 100644 --- a/server/pkg/filter/sorting.go +++ b/server/pkg/filter/sorting.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "regexp" "strings" "github.com/cortezaproject/corteza/server/pkg/slice" @@ -19,6 +18,8 @@ type ( SortExpr struct { Column string + columns []string + modifier string Descending bool // NullsFirst bool } @@ -26,6 +27,10 @@ type ( SortExprSet []*SortExpr ) +const ( + COALESCE = "coalesce" +) + func NewSorting(sort string) (s Sorting, err error) { s = Sorting{} @@ -40,16 +45,7 @@ func (s *Sorting) OrderBy() SortExprSet { return s.Sort } -// parses sort string -// -// We allow a simplified version of what SQL supports, so: -// "( ), ..." -// -// Unlike before, we do not use pkg/ql for parsing this as we do not allow -// any complex sorting expressions func parseSort(in string) (set SortExprSet, err error) { - exprMatcher := regexp.MustCompile(`([0-9a-zA-Z_\.]+)(\s+(asc|ASC|desc|DESC))?`) - set = SortExprSet{} if in == "" { return @@ -60,24 +56,89 @@ func parseSort(in string) (set SortExprSet, err error) { return } - for _, expr := range strings.Split(in, ",") { - mm := exprMatcher.FindStringSubmatch(strings.TrimSpace(expr)) + aux := splitCommaParenthesis(in) - o := &SortExpr{} - switch { - case len(mm) == 0: - return nil, fmt.Errorf("invalid sort expression") - case len(mm) >= 2: - o.Column = mm[1] - fallthrough - case len(mm) >= 4: - o.Descending = strings.ToUpper(mm[3]) == "DESC" + // iterate over aux, split each part and generate SortExpr + // if string contains ( and ) then extract modifier before ( + // and optional direction after the closing ) + // if there are no parenthesis, then the whole string is the column name with the desc/asc at the end + for _, s := range aux { + var ( + modifier string + columns []string + sortExpr = &SortExpr{} + + toLower = strings.ToLower + hasSuffix = strings.HasSuffix + indexByte = strings.IndexByte + ) + + // extract modifier + if idx := indexByte(s, '('); idx > 0 { + modifier = s[:idx] + s = s[idx+1:] } - set = append(set, o) + // extract columns + idx := indexByte(s, ')') + + // extract desc + if hasSuffix(toLower(s), " desc") { + sortExpr.Descending = true + s = s[:len(s)-5] + } else if hasSuffix(toLower(s), " asc") { + sortExpr.Descending = false + s = s[:len(s)-4] + } + + if idx > 0 { + columns = splitCommaParenthesis(s[:idx]) + s = s[idx+1:] + } else { + columns = []string{s} + } + + sortExpr.SetColumns(columns...) + err = sortExpr.SetModifier(modifier) + if err != nil { + return + } + + set = append(set, sortExpr) } - return set, nil + return +} + +// splitCommaParenthesis can split a string into a slice of strings +// string is separated by commas but can have parenthesis with commas inside +// and the commas inside the parenthesis should not be considered separators +// and the parenthesis should be removed from the output +func splitCommaParenthesis(in string) (out []string) { + var ( + depth int + start int + ) + + for i, r := range in { + switch r { + case '(': + depth++ + case ')': + depth-- + case ',': + if depth == 0 { + out = append(out, strings.TrimSpace(in[start:i])) + start = i + 1 + } + } + } + + if start < len(in) { + out = append(out, strings.TrimSpace(in[start:])) + } + + return } // UnmarshalJSON parses sort expression when passed inside JSON @@ -133,6 +194,8 @@ func (set SortExprSet) Clone() (out SortExprSet) { for i := range set { out[i] = &SortExpr{ Column: set[i].Column, + columns: set[i].columns, + modifier: set[i].modifier, Descending: set[i].Descending, } } @@ -178,7 +241,11 @@ func (set SortExprSet) Columns() []string { func (set SortExprSet) String() string { out := make([]string, len(set)) for i := range set { - out[i] = set[i].Column + if set[i].modifier != "" { + out[i] = fmt.Sprintf("%s(%s)", set[i].modifier, strings.Join(set[i].columns, ",")) + } else { + out[i] = set[i].Column + } if set[i].Descending { out[i] += " DESC" @@ -188,3 +255,42 @@ func (set SortExprSet) String() string { return strings.Join(out, ", ") } + +func (s *SortExpr) Columns() []string { + if len(s.columns) == 0 && s.Column != "" { + s.columns = []string{s.Column} + } + return s.columns +} + +func (s *SortExpr) SetColumns(columns ...string) { + // @todo this can be improved by supporting multiple columns even without modifier + // fallback to single column + if s.modifier == "" && len(columns) == 1 { + s.Column = columns[0] + } + + s.columns = append(s.columns, columns...) +} + +func (s *SortExpr) Modifier() string { + if s == nil { + return "" + } + return s.modifier +} + +func (s *SortExpr) SetModifier(modifier string) error { + if s == nil { + return nil + } + switch strings.ToLower(modifier) { + case COALESCE: + s.modifier = modifier + default: + if len(s.modifier) > 0 { + return fmt.Errorf("invalid modifier %q", modifier) + } + } + return nil +} diff --git a/server/pkg/filter/sorting_test.go b/server/pkg/filter/sorting_test.go index 2272193f7..c6090643b 100644 --- a/server/pkg/filter/sorting_test.go +++ b/server/pkg/filter/sorting_test.go @@ -17,22 +17,44 @@ func Test_parseSort(t *testing.T) { { "one simple column", "name", - SortExprSet{&SortExpr{Column: "name"}}, + SortExprSet{ + &SortExpr{ + columns: []string{"name"}, + Column: "name", + }, + }, false, }, { "one simple column, descending", "name desc", - SortExprSet{&SortExpr{Column: "name", Descending: true}}, + SortExprSet{ + &SortExpr{ + columns: []string{"name"}, + Column: "name", + Descending: true}, + }, false, }, { "combo", "name desc, email asc, age desc", SortExprSet{ - &SortExpr{Column: "name", Descending: true}, - &SortExpr{Column: "email", Descending: false}, - &SortExpr{Column: "age", Descending: true}, + &SortExpr{ + columns: []string{"name"}, + Column: "name", + Descending: true, + }, + &SortExpr{ + columns: []string{"email"}, + Column: "email", + Descending: false, + }, + &SortExpr{ + columns: []string{"age"}, + Column: "age", + Descending: true, + }, }, false, }, @@ -42,6 +64,30 @@ func Test_parseSort(t *testing.T) { SortExprSet{}, false, }, + { + "COALESCE with multiple column", + "COALESCE(updated_at,created_at)", + SortExprSet{ + &SortExpr{ + modifier: "COALESCE", + columns: []string{"updated_at", "created_at"}, + Descending: false, + }, + }, + false, + }, + { + "COALESCE with multiple column, descending", + "COALESCE(updated_at,created_at) desc", + SortExprSet{ + &SortExpr{ + modifier: "COALESCE", + columns: []string{"updated_at", "created_at"}, + Descending: true, + }, + }, + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/server/store/adapters/rdbms/cursor.go b/server/store/adapters/rdbms/cursor.go index edbc5848f..880566906 100644 --- a/server/store/adapters/rdbms/cursor.go +++ b/server/store/adapters/rdbms/cursor.go @@ -19,6 +19,8 @@ type ( pagingCursor interface { Keys() []string + KK() [][]string + Modifiers() []string Values() []interface{} IsLThen() bool Desc() []bool @@ -121,6 +123,8 @@ func (c *cursorCondition) sql() (cnd string, err error) { var ( cc = c.cur.Keys() + kk = c.cur.KK() + mm = c.cur.Modifiers() vv = c.cur.Values() ltOp = map[bool]string{ @@ -168,6 +172,17 @@ func (c *cursorCondition) sql() (cnd string, err error) { if v, ok := c.sortableCols[strings.ToLower(cc[i])]; ok { colName = v } + + // update column name as per the modifier if needed + if len(kk[i]) > 0 && strings.ToLower(mm[i]) == filter.COALESCE { + var tmp []string + for _, k := range kk[i] { + if v, ok := c.sortableCols[strings.ToLower(k)]; ok { + tmp = append(tmp, v) + } + } + colName = fmt.Sprintf("COALESCE(%s)", strings.Join(tmp, ", ")) + } } km, err := c.keyMapper(colName) @@ -291,7 +306,7 @@ func CursorExpression( op := ltOp[lt] - //// Typecast the value so comparison can work properly + // // Typecast the value so comparison can work properly // Either BOTH (field and value) are NULL or field is grater-then value base := exp.NewExpressionList( diff --git a/server/store/adapters/rdbms/filter.go b/server/store/adapters/rdbms/filter.go index 473f8a2de..2de146f6f 100644 --- a/server/store/adapters/rdbms/filter.go +++ b/server/store/adapters/rdbms/filter.go @@ -90,22 +90,22 @@ func DefaultFilters() (f *extendedFilters) { case composeType.RecordAttachment: panic("@todo pending implementation") - //query = query. + // query = query. // Join("compose_record_value AS v ON (v.ref = a.id)") // - //if f.ModuleID > 0 { + // if f.ModuleID > 0 { // query = query. // Join("compose_record AS r ON (r.id = v.record_id)"). // Where(squirrel.Eq{"r.module_id": f.ModuleID}) - //} + // } // - //if f.RecordID > 0 { + // if f.RecordID > 0 { // query = query.Where(squirrel.Eq{"v.record_id": f.RecordID}) - //} + // } // - //if f.FieldName != "" { + // if f.FieldName != "" { // query = query.Where(squirrel.Eq{"v.name": f.FieldName}) - //} + // } default: err = fmt.Errorf("unsupported kind value") @@ -269,7 +269,7 @@ func DefaultFilters() (f *extendedFilters) { } // Always sort by ID descending - //query = query.OrderBy("id DESC") + // query = query.OrderBy("id DESC") if f.FromTimestamp != nil { ee = append(ee, goqu.C("ts").Gte(f.ToTimestamp)) @@ -351,22 +351,19 @@ func Order(sort filter.SortExprSet, sortables map[string]string) (oo []exp.Order func order(sort filter.SortExprSet, sortables map[string]string) (oo []exp.OrderedExpression, err error) { var ( - has bool - col string + sortExpr goqu.Expression ) for _, s := range sort { - if len(sortables) > 0 { - if col, has = sortables[strings.ToLower(s.Column)]; !has { - return nil, fmt.Errorf("column %q is not sortable", s.Column) - } - s.Column = col + sortExpr, err = generateSorting(sortables, s) + if err != nil { + return } if s.Descending { - oo = append(oo, exp.NewOrderedExpression(goqu.I(s.Column), exp.DescSortDir, exp.NoNullsSortType)) + oo = append(oo, exp.NewOrderedExpression(sortExpr, exp.DescSortDir, exp.NoNullsSortType)) } else { - oo = append(oo, exp.NewOrderedExpression(goqu.I(s.Column), exp.AscDir, exp.NoNullsSortType)) + oo = append(oo, exp.NewOrderedExpression(sortExpr, exp.AscDir, exp.NoNullsSortType)) } } @@ -403,3 +400,51 @@ func stateFalseComparison(lit string, fs filter.State) goqu.Expression { return nil } } + +// @todo: Currently we have for support for MsSQL, MySql, PSQL, SQLite drivers, +// this changes is supported by all DB but we need to move to store.driver +// generateSorting verify and converts given sorting to literal if required +func generateSorting(sortables map[string]string, s *filter.SortExpr) (out goqu.Expression, err error) { + const COALESCE string = "coalesce" + var ( + val string + has bool + + literalCols []interface{} + + toLower = strings.ToLower + hasSorting = func(column string) (string, bool) { + v, ok := sortables[toLower(column)] + return v, ok + } + ) + + if len(sortables) > 0 && s != nil { + for _, col := range s.Columns() { + val, has = hasSorting(col) + if has { + if out == nil { + out = goqu.I(val) + } + literalCols = append(literalCols, goqu.I(val)) + } else { + if len(s.Columns()) == 1 { + err = fmt.Errorf("invalid column name: %s", col) + } else { + err = fmt.Errorf("column %q is not sortable in %s sorting", col, s.Columns()) + } + return nil, err + } + } + + if toLower(s.Modifier()) == COALESCE { + if len(literalCols) > 0 { + out = goqu.COALESCE(literalCols...) + } else { + return nil, fmt.Errorf("invalid sorting with %s modifier and columns: %s", s.Modifier(), s.Columns()) + } + } + } + + return +} diff --git a/server/store/adapters/rdbms/filter_test.go b/server/store/adapters/rdbms/filter_test.go new file mode 100644 index 000000000..01140b365 --- /dev/null +++ b/server/store/adapters/rdbms/filter_test.go @@ -0,0 +1,89 @@ +package rdbms + +import ( + "github.com/cortezaproject/corteza/server/pkg/filter" + "github.com/doug-martin/goqu/v9" + "reflect" + "testing" +) + +func Test_generateSorting(t *testing.T) { + tests := []struct { + name string + sort filter.SortExpr + columns []string + modifier string + sortables map[string]string + wantOut goqu.Expression + wantErr bool + }{ + { + name: "single column sorting", + sort: filter.SortExpr{Column: "createdat"}, + sortables: map[string]string{ + "createdat": "created_at", + }, + wantOut: goqu.I("created_at"), + wantErr: false, + }, + { + name: "path sorting", + sort: filter.SortExpr{Column: "user.createdat"}, + sortables: map[string]string{ + "user.createdat": "user.created_at", + }, + wantOut: goqu.I("user.created_at"), + wantErr: false, + }, + { + name: "coalesce single column sorting", + sort: filter.SortExpr{}, + columns: []string{"createdat"}, + modifier: "coalesce", + sortables: map[string]string{ + "createdat": "created_at", + "updatedat": "updated_at", + }, + wantOut: goqu.COALESCE(goqu.I("created_at")), + wantErr: false, + }, + { + name: "coalesce multiple column sorting", + sort: filter.SortExpr{}, + columns: []string{"createdat", "updatedat"}, + modifier: "coalesce", + sortables: map[string]string{ + "createdat": "created_at", + "updatedat": "updated_at", + }, + wantOut: goqu.COALESCE(goqu.I("created_at"), goqu.I("updated_at")), + wantErr: false, + }, + { + name: "invalid coalesce multiple column sorting", + sort: filter.SortExpr{}, + columns: []string{"createdat", "deletedat"}, + modifier: "coalesce", + sortables: map[string]string{ + "createdat": "created_at", + "updatedat": "updated_at", + }, + wantOut: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.sort.SetColumns(tt.columns...) + _ = tt.sort.SetModifier(tt.modifier) + gotOut, err := generateSorting(tt.sortables, &tt.sort) + if (err != nil) != tt.wantErr { + t.Errorf("generateSorting() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotOut, tt.wantOut) { + t.Errorf("generateSorting() gotOut = %v, want %v", gotOut, tt.wantOut) + } + }) + } +} diff --git a/server/store/adapters/rdbms/rdbms.gen.go b/server/store/adapters/rdbms/rdbms.gen.go index 2f73a9919..de39a7758 100644 --- a/server/store/adapters/rdbms/rdbms.gen.go +++ b/server/store/adapters/rdbms/rdbms.gen.go @@ -26,6 +26,7 @@ import ( systemType "github.com/cortezaproject/corteza/server/system/types" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" + "github.com/modern-go/reflect2" ) var ( @@ -334,13 +335,29 @@ func (s *Store) collectActionlogCursorValues(res *actionlogType.Action, cc ...*f pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "timestamp": - cur.Set(c.Column, res.Timestamp, c.Descending) + return res.Timestamp + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -883,25 +900,41 @@ func (s *Store) collectApigwFilterCursorValues(res *systemType.ApigwFilter, cc . pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "route": - cur.Set(c.Column, res.Route, c.Descending) + return res.Route case "weight": - cur.Set(c.Column, res.Weight, c.Descending) + return res.Weight case "kind": - cur.Set(c.Column, res.Kind, c.Descending) + return res.Kind case "enabled": - cur.Set(c.Column, res.Enabled, c.Descending) + return res.Enabled case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -1448,25 +1481,41 @@ func (s *Store) collectApigwRouteCursorValues(res *systemType.ApigwRoute, cc ... pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "endpoint": - cur.Set(c.Column, res.Endpoint, c.Descending) + return res.Endpoint case "method": - cur.Set(c.Column, res.Method, c.Descending) + return res.Method case "enabled": - cur.Set(c.Column, res.Enabled, c.Descending) + return res.Enabled case "group": - cur.Set(c.Column, res.Group, c.Descending) + return res.Group case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -1970,23 +2019,39 @@ func (s *Store) collectApplicationCursorValues(res *systemType.Application, cc . pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "enabled": - cur.Set(c.Column, res.Enabled, c.Descending) + return res.Enabled case "weight": - cur.Set(c.Column, res.Weight, c.Descending) + return res.Weight case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -2487,21 +2552,37 @@ func (s *Store) collectAttachmentCursorValues(res *systemType.Attachment, cc ... pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "kind": - cur.Set(c.Column, res.Kind, c.Descending) + return res.Kind case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -3052,28 +3133,44 @@ func (s *Store) collectAuthClientCursorValues(res *systemType.AuthClient, cc ... pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "enabled": - cur.Set(c.Column, res.Enabled, c.Descending) + return res.Enabled case "trusted": - cur.Set(c.Column, res.Trusted, c.Descending) + return res.Trusted case "validFrom": - cur.Set(c.Column, res.ValidFrom, c.Descending) + return res.ValidFrom case "expiresAt": - cur.Set(c.Column, res.ExpiresAt, c.Descending) + return res.ExpiresAt case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -3384,16 +3481,32 @@ func (s *Store) collectAuthConfirmedClientCursorValues(res *systemType.AuthConfi pkClientID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "userID": - cur.Set(c.Column, res.UserID, c.Descending) pkUserID = true + return res.UserID case "clientID": - cur.Set(c.Column, res.ClientID, c.Descending) pkClientID = true + return res.ClientID case "confirmedAt": - cur.Set(c.Column, res.ConfirmedAt, c.Descending) + return res.ConfirmedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -3796,15 +3909,31 @@ func (s *Store) collectAuthOa2tokenCursorValues(res *systemType.AuthOa2token, cc pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "expiresAt": - cur.Set(c.Column, res.ExpiresAt, c.Descending) + return res.ExpiresAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -4084,15 +4213,31 @@ func (s *Store) collectAuthSessionCursorValues(res *systemType.AuthSession, cc . pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "expiresAt": - cur.Set(c.Column, res.ExpiresAt, c.Descending) + return res.ExpiresAt case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -4602,27 +4747,43 @@ func (s *Store) collectAutomationSessionCursorValues(res *automationType.Session pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "workflowID": - cur.Set(c.Column, res.WorkflowID, c.Descending) + return res.WorkflowID case "status": - cur.Set(c.Column, res.Status, c.Descending) + return res.Status case "eventType": - cur.Set(c.Column, res.EventType, c.Descending) + return res.EventType case "resourceType": - cur.Set(c.Column, res.ResourceType, c.Descending) + return res.ResourceType case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "purgeAt": - cur.Set(c.Column, res.PurgeAt, c.Descending) + return res.PurgeAt case "suspendedAt": - cur.Set(c.Column, res.SuspendedAt, c.Descending) + return res.SuspendedAt case "completedAt": - cur.Set(c.Column, res.CompletedAt, c.Descending) + return res.CompletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -5130,25 +5291,41 @@ func (s *Store) collectAutomationTriggerCursorValues(res *automationType.Trigger pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "workflowID": - cur.Set(c.Column, res.WorkflowID, c.Descending) + return res.WorkflowID case "enabled": - cur.Set(c.Column, res.Enabled, c.Descending) + return res.Enabled case "resourceType": - cur.Set(c.Column, res.ResourceType, c.Descending) + return res.ResourceType case "eventType": - cur.Set(c.Column, res.EventType, c.Descending) + return res.EventType case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -5694,22 +5871,38 @@ func (s *Store) collectAutomationWorkflowCursorValues(res *automationType.Workfl pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "enabled": - cur.Set(c.Column, res.Enabled, c.Descending) + return res.Enabled case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -6239,23 +6432,39 @@ func (s *Store) collectComposeAttachmentCursorValues(res *composeType.Attachment pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "ownerID": - cur.Set(c.Column, res.OwnerID, c.Descending) + return res.OwnerID case "kind": - cur.Set(c.Column, res.Kind, c.Descending) + return res.Kind case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -6800,22 +7009,38 @@ func (s *Store) collectComposeChartCursorValues(res *composeType.Chart, cc ...*f pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -7402,22 +7627,38 @@ func (s *Store) collectComposeModuleCursorValues(res *composeType.Module, cc ... pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -7777,25 +8018,41 @@ func (s *Store) collectComposeModuleFieldCursorValues(res *composeType.ModuleFie pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "place": - cur.Set(c.Column, res.Place, c.Descending) + return res.Place case "kind": - cur.Set(c.Column, res.Kind, c.Descending) + return res.Kind case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "label": - cur.Set(c.Column, res.Label, c.Descending) + return res.Label case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -8371,21 +8628,37 @@ func (s *Store) collectComposeNamespaceCursorValues(res *composeType.Namespace, pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "slug": - cur.Set(c.Column, res.Slug, c.Descending) + return res.Slug case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -9002,26 +9275,42 @@ func (s *Store) collectComposePageCursorValues(res *composeType.Page, cc ...*fil pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "title": - cur.Set(c.Column, res.Title, c.Descending) + return res.Title case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "selfID": - cur.Set(c.Column, res.SelfID, c.Descending) + return res.SelfID case "weight": - cur.Set(c.Column, res.Weight, c.Descending) + return res.Weight case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -9309,21 +9598,37 @@ func (s *Store) collectCredentialCursorValues(res *systemType.Credential, cc ... pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt case "lastUsedAt": - cur.Set(c.Column, res.LastUsedAt, c.Descending) + return res.LastUsedAt case "expiresAt": - cur.Set(c.Column, res.ExpiresAt, c.Descending) + return res.ExpiresAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -9869,22 +10174,38 @@ func (s *Store) collectDalConnectionCursorValues(res *systemType.DalConnection, pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "type": - cur.Set(c.Column, res.Type, c.Descending) + return res.Type case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -10414,22 +10735,38 @@ func (s *Store) collectDalSensitivityLevelCursorValues(res *systemType.DalSensit pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "level": - cur.Set(c.Column, res.Level, c.Descending) + return res.Level case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -10936,25 +11273,41 @@ func (s *Store) collectDataPrivacyRequestCursorValues(res *systemType.DataPrivac pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "kind": - cur.Set(c.Column, res.Kind, c.Descending) + return res.Kind case "status": - cur.Set(c.Column, res.Status, c.Descending) + return res.Status case "requestedAt": - cur.Set(c.Column, res.RequestedAt, c.Descending) + return res.RequestedAt case "completedAt": - cur.Set(c.Column, res.CompletedAt, c.Descending) + return res.CompletedAt case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -11413,17 +11766,33 @@ func (s *Store) collectDataPrivacyRequestCommentCursorValues(res *systemType.Dat pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -11928,24 +12297,40 @@ func (s *Store) collectFederationExposedModuleCursorValues(res *federationType.E pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "nodeID": - cur.Set(c.Column, res.NodeID, c.Descending) + return res.NodeID case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -12480,17 +12865,33 @@ func (s *Store) collectFederationModuleMappingCursorValues(res *federationType.M hasUnique bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "nodeID": - cur.Set(c.Column, res.NodeID, c.Descending) hasUnique = true + return res.NodeID case "federationModuleID": - cur.Set(c.Column, res.FederationModuleID, c.Descending) + return res.FederationModuleID case "composeModuleID": - cur.Set(c.Column, res.ComposeModuleID, c.Descending) + return res.ComposeModuleID case "composeNamespaceID": - cur.Set(c.Column, res.ComposeNamespaceID, c.Descending) + return res.ComposeNamespaceID + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -12871,27 +13272,43 @@ func (s *Store) collectFederationNodeCursorValues(res *federationType.Node, cc . pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "sharedNodeID": - cur.Set(c.Column, res.SharedNodeID, c.Descending) + return res.SharedNodeID case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "baseURL": - cur.Set(c.Column, res.BaseURL, c.Descending) + return res.BaseURL case "status": - cur.Set(c.Column, res.Status, c.Descending) + return res.Status case "contact": - cur.Set(c.Column, res.Contact, c.Descending) + return res.Contact case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -13440,19 +13857,35 @@ func (s *Store) collectFederationNodeSyncCursorValues(res *federationType.NodeSy pkNodeID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "nodeID": - cur.Set(c.Column, res.NodeID, c.Descending) pkNodeID = true + return res.NodeID case "moduleID": - cur.Set(c.Column, res.ModuleID, c.Descending) + return res.ModuleID case "syncType": - cur.Set(c.Column, res.SyncType, c.Descending) + return res.SyncType case "syncStatus": - cur.Set(c.Column, res.SyncStatus, c.Descending) + return res.SyncStatus case "timeOfAction": - cur.Set(c.Column, res.TimeOfAction, c.Descending) + return res.TimeOfAction + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -13959,26 +14392,42 @@ func (s *Store) collectFederationSharedModuleCursorValues(res *federationType.Sh pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "nodeID": - cur.Set(c.Column, res.NodeID, c.Descending) + return res.NodeID case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "externalFederationModuleID": - cur.Set(c.Column, res.ExternalFederationModuleID, c.Descending) + return res.ExternalFederationModuleID case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -14268,20 +14717,36 @@ func (s *Store) collectFlagCursorValues(res *flagType.Flag, cc ...*filter.SortEx pkName bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "kind": - cur.Set(c.Column, res.Kind, c.Descending) pkKind = true + return res.Kind case "resourceID": - cur.Set(c.Column, res.ResourceID, c.Descending) pkResourceID = true + return res.ResourceID case "ownedBy": - cur.Set(c.Column, res.OwnedBy, c.Descending) pkOwnedBy = true + return res.OwnedBy case "name": - cur.Set(c.Column, res.Name, c.Descending) pkName = true + return res.Name + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -14575,17 +15040,33 @@ func (s *Store) collectLabelCursorValues(res *labelsType.Label, cc ...*filter.So pkName bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "kind": - cur.Set(c.Column, res.Kind, c.Descending) pkKind = true + return res.Kind case "resourceID": - cur.Set(c.Column, res.ResourceID, c.Descending) pkResourceID = true + return res.ResourceID case "name": - cur.Set(c.Column, res.Name, c.Descending) pkName = true + return res.Name + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -15132,21 +15613,37 @@ func (s *Store) collectQueueCursorValues(res *systemType.Queue, cc ...*filter.So pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "consumer": - cur.Set(c.Column, res.Consumer, c.Descending) + return res.Consumer case "queue": - cur.Set(c.Column, res.Queue, c.Descending) + return res.Queue case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -15590,17 +16087,33 @@ func (s *Store) collectQueueMessageCursorValues(res *systemType.QueueMessage, cc pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "queue": - cur.Set(c.Column, res.Queue, c.Descending) + return res.Queue case "created": - cur.Set(c.Column, res.Created, c.Descending) + return res.Created case "processed": - cur.Set(c.Column, res.Processed, c.Descending) + return res.Processed + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -15843,17 +16356,33 @@ func (s *Store) collectRbacRuleCursorValues(res *rbacType.Rule, cc ...*filter.So pkOperation bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "roleID": - cur.Set(c.Column, res.RoleID, c.Descending) pkRoleID = true + return res.RoleID case "resource": - cur.Set(c.Column, res.Resource, c.Descending) pkResource = true + return res.Resource case "operation": - cur.Set(c.Column, res.Operation, c.Descending) pkOperation = true + return res.Operation + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -16365,25 +16894,41 @@ func (s *Store) collectReminderCursorValues(res *systemType.Reminder, cc ...*fil pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "resource": - cur.Set(c.Column, res.Resource, c.Descending) + return res.Resource case "assignedAt": - cur.Set(c.Column, res.AssignedAt, c.Descending) + return res.AssignedAt case "dismissedAt": - cur.Set(c.Column, res.DismissedAt, c.Descending) + return res.DismissedAt case "remindAt": - cur.Set(c.Column, res.RemindAt, c.Descending) + return res.RemindAt case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -16928,20 +17473,36 @@ func (s *Store) collectReportCursorValues(res *systemType.Report, cc ...*filter. pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -17205,13 +17766,29 @@ func (s *Store) collectResourceActivityCursorValues(res *discoveryType.ResourceA pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "timestamp": - cur.Set(c.Column, res.Timestamp, c.Descending) + return res.Timestamp + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -17699,17 +18276,33 @@ func (s *Store) collectResourceTranslationCursorValues(res *systemType.ResourceT pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -18300,24 +18893,40 @@ func (s *Store) collectRoleCursorValues(res *systemType.Role, cc ...*filter.Sort pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "archivedAt": - cur.Set(c.Column, res.ArchivedAt, c.Descending) + return res.ArchivedAt case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -18612,14 +19221,30 @@ func (s *Store) collectRoleMemberCursorValues(res *systemType.RoleMember, cc ... pkRoleID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "userID": - cur.Set(c.Column, res.UserID, c.Descending) pkUserID = true + return res.UserID case "roleID": - cur.Set(c.Column, res.RoleID, c.Descending) pkRoleID = true + return res.RoleID + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -18905,16 +19530,32 @@ func (s *Store) collectSettingValueCursorValues(res *systemType.SettingValue, cc pkName bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "ownedBy": - cur.Set(c.Column, res.OwnedBy, c.Descending) pkOwnedBy = true + return res.OwnedBy case "name": - cur.Set(c.Column, res.Name, c.Descending) pkName = true + return res.Name case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -19467,28 +20108,44 @@ func (s *Store) collectTemplateCursorValues(res *systemType.Template, cc ...*fil pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "language": - cur.Set(c.Column, res.Language, c.Descending) + return res.Language case "type": - cur.Set(c.Column, res.Type, c.Descending) + return res.Type case "template": - cur.Set(c.Column, res.Template, c.Descending) + return res.Template case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt case "lastUsedAt": - cur.Set(c.Column, res.LastUsedAt, c.Descending) + return res.LastUsedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } } @@ -20152,32 +20809,48 @@ func (s *Store) collectUserCursorValues(res *systemType.User, cc ...*filter.Sort pkID bool collect = func(cc ...*filter.SortExpr) { - for _, c := range cc { - switch c.Column { + getVal := func(col string) interface{} { + switch col { case "id": - cur.Set(c.Column, res.ID, c.Descending) pkID = true + return res.ID case "email": - cur.Set(c.Column, res.Email, c.Descending) hasUnique = true + return res.Email case "username": - cur.Set(c.Column, res.Username, c.Descending) hasUnique = true + return res.Username case "name": - cur.Set(c.Column, res.Name, c.Descending) + return res.Name case "handle": - cur.Set(c.Column, res.Handle, c.Descending) hasUnique = true + return res.Handle case "kind": - cur.Set(c.Column, res.Kind, c.Descending) + return res.Kind case "suspendedAt": - cur.Set(c.Column, res.SuspendedAt, c.Descending) + return res.SuspendedAt case "createdAt": - cur.Set(c.Column, res.CreatedAt, c.Descending) + return res.CreatedAt case "updatedAt": - cur.Set(c.Column, res.UpdatedAt, c.Descending) + return res.UpdatedAt case "deletedAt": - cur.Set(c.Column, res.DeletedAt, c.Descending) + return res.DeletedAt + } + return nil + } + + for _, c := range cc { + switch c.Modifier() { + case filter.COALESCE: + var val interface{} + for _, col := range c.Columns() { + if reflect2.IsNil(val) { + val = getVal(col) + } + } + cur.SetModifier(c.Column, val, c.Descending, c.Modifier(), c.Columns()...) + default: + cur.Set(c.Column, getVal(c.Column), c.Descending) } } }