diff --git a/pkg/codegen/assets/store_rdbms.gen.go.tpl b/pkg/codegen/assets/store_rdbms.gen.go.tpl
index a8c66c91c..897977be6 100644
--- a/pkg/codegen/assets/store_rdbms.gen.go.tpl
+++ b/pkg/codegen/assets/store_rdbms.gen.go.tpl
@@ -86,7 +86,7 @@ func (s Store) {{ toggleExport .Search.Export "Search" $.Types.Plural }}(ctx con
//
// We still need to sort the results by primary key for paging purposes
curSort := filter.SortExprSet{
- {{- range $.Fields.PrimaryKeyFields }}
+ {{- range $.RDBMS.Columns.PrimaryKeyFields }}
&filter.SortExpr{Column: {{ printf "%q" .Column }}, {{ if .SortDescending }}Descending: !reversedCursor, {{ end }}},
{{- end }}
}
@@ -157,9 +157,9 @@ func (s Store) {{ unexport "fetchFullPageOf" $.Types.Plural }} (
)
-{{ if .Fields.PrimaryKeyFields }}
+{{ if .RDBMS.Columns.PrimaryKeyFields }}
// Make sure we always end our sort by primary keys
- {{- range .Fields.PrimaryKeyFields }}
+ {{- range .RDBMS.Columns.PrimaryKeyFields }}
if sort.Get({{ printf "%q" .Column }}) == nil {
sort = append(sort, &filter.SortExpr{Column: {{ printf "%q" .Column }}})
}
@@ -175,9 +175,9 @@ func (s Store) {{ unexport "fetchFullPageOf" $.Types.Plural }} (
if q, err = setOrderBy(q, sort, s.sortable{{ export $.Types.Singular }}Columns()...); err != nil {
return nil, err
}
-{{ else if .Fields.PrimaryKeyFields }}
+{{ else if .RDBMS.Columns.PrimaryKeyFields }}
// Sort by primary keys by default
- if q, err = setOrderBy(q, sort, {{ range .Fields.PrimaryKeyFields }}"{{ .Column }}",{{ end }}); err != nil {
+ if q, err = setOrderBy(q, sort, {{ range .RDBMS.Columns.PrimaryKeyFields }}"{{ .Column }}",{{ end }}); err != nil {
return nil, err
}
{{ end }}
@@ -299,14 +299,14 @@ func (s Store) {{ export "query" $.Types.Plural }} (
{{- range $lookup := $.Lookups }}
// {{ toggleExport $lookup.Export "Lookup" $.Types.Singular "By" $lookup.Suffix }} {{ comment $lookup.Description true -}}
-func (s Store) {{ toggleExport $lookup.Export "Lookup" $.Types.Singular "By" $lookup.Suffix }}(ctx context.Context{{ template "extraArgsDef" $ }}{{- range $field := $lookup.Fields }}, {{ cc2underscore $field }} {{ ($field | $.Fields.Find).Type }}{{- end }}) (*{{ $.Types.GoType }}, error) {
+func (s Store) {{ toggleExport $lookup.Export "Lookup" $.Types.Singular "By" $lookup.Suffix }}(ctx context.Context{{ template "extraArgsDef" $ }}{{- range $field := $lookup.Fields }}, {{ cc2underscore $field }} {{ ($field | $.RDBMS.Columns.Find).Type }}{{- end }}) (*{{ $.Types.GoType }}, error) {
return s.execLookup{{ $.Types.Singular }}(ctx{{ template "extraArgsCall" $ }}, squirrel.Eq{
{{- range $field := $lookup.Fields }}
- s.preprocessColumn({{ printf "%q" ($field | $.Fields.Find).AliasedColumn }}, {{ printf "%q" ($field | $.Fields.Find).LookupFilterPreprocess }}): s.preprocessValue({{ cc2underscore $field }}, {{ printf "%q" ($field | $.Fields.Find).LookupFilterPreprocess }}),
+ s.preprocessColumn({{ printf "%q" ($field | $.RDBMS.Columns.Find).AliasedColumn }}, {{ printf "%q" ($field | $.RDBMS.Columns.Find).LookupFilterPreprocess }}): s.preprocessValue({{ cc2underscore $field }}, {{ printf "%q" ($field | $.RDBMS.Columns.Find).LookupFilterPreprocess }}),
{{- end }}
{{ range $field, $value := $lookup.Filter }}
- "{{ ($field | $.Fields.Find).AliasedColumn }}": {{ $value }},
+ "{{ ($field | $.RDBMS.Columns.Find).AliasedColumn }}": {{ $value }},
{{- end }}
})
}
@@ -360,9 +360,9 @@ func (s Store) {{ toggleExport .Update.Export "Partial" $.Types.Singular "Update
*/}}
err = s.execUpdate{{ export $.Types.Plural }}(
ctx,
- {{ template "filterByPrimaryKeys" $.Fields.PrimaryKeyFields }},
+ {{ template "filterByPrimaryKeys" $.RDBMS.Columns.PrimaryKeyFields }},
s.internal{{ export $.Types.Singular }}Encoder(res).Skip(
- {{- range $field := $.Fields.PrimaryKeyFields -}}
+ {{- range $field := $.RDBMS.Columns.PrimaryKeyFields -}}
{{ printf "%q" $field.Column }},
{{- end -}}
).Only(onlyColumns...))
@@ -411,7 +411,7 @@ func (s Store) {{ toggleExport .Delete.Export "Delete" $.Types.Singular }}(ctx c
return err
}
*/}}
- err = s.execDelete{{ export $.Types.Plural }}(ctx,{{ template "filterByPrimaryKeys" $.Fields.PrimaryKeyFields }})
+ err = s.execDelete{{ export $.Types.Plural }}(ctx,{{ template "filterByPrimaryKeys" $.RDBMS.Columns.PrimaryKeyFields }})
if err != nil {
return s.config.ErrorHandler(err)
}
@@ -420,9 +420,9 @@ func (s Store) {{ toggleExport .Delete.Export "Delete" $.Types.Singular }}(ctx c
return nil
}
-// {{ toggleExport .Delete.Export "Delete" $.Types.Singular "By" }}{{ template "primaryKeySuffix" $.Fields }} Deletes row from the {{ $.RDBMS.Table }} table
-func (s Store) {{ toggleExport .Delete.Export "Delete" $.Types.Singular "By" }}{{ template "primaryKeySuffix" $.Fields }}(ctx context.Context{{ template "extraArgsDef" . }}{{ template "primaryKeyArgsDef" $.Fields }}) error {
- return s.execDelete{{ export $.Types.Plural }}(ctx, {{ template "filterByPrimaryKeysWithArgs" $.Fields.PrimaryKeyFields }})
+// {{ toggleExport .Delete.Export "Delete" $.Types.Singular "By" }}{{ template "primaryKeySuffix" $.RDBMS.Columns }} Deletes row from the {{ $.RDBMS.Table }} table
+func (s Store) {{ toggleExport .Delete.Export "Delete" $.Types.Singular "By" }}{{ template "primaryKeySuffix" $.RDBMS.Columns }}(ctx context.Context{{ template "extraArgsDef" . }}{{ template "primaryKeyArgsDef" $.RDBMS.Columns }}) error {
+ return s.execDelete{{ export $.Types.Plural }}(ctx, {{ template "filterByPrimaryKeysWithArgs" $.RDBMS.Columns.PrimaryKeyFields }})
}
{{ end }}
@@ -478,7 +478,7 @@ func (s Store) execUpsert{{ export $.Types.Plural }}(ctx context.Context, set st
s.config,
s.{{ unexport $.Types.Singular }}Table(),
set,
-{{ range $.Fields }}
+{{ range $.RDBMS.Columns }}
{{- if or .IsPrimaryKey -}}
{{ printf "%q" .Column }},
{{ end }}
@@ -500,7 +500,7 @@ func (s Store) execDelete{{ export $.Types.Plural }}(ctx context.Context, cnd sq
}
{{ end }}
-func (s Store) internal{{ $.Types.Singular }}RowScanner({{ template "extraArgsDefFirst" . }}row rowScanner) (res *{{ $.Types.GoType }}, err error) {
+func (s Store) internal{{ export $.Types.Singular }}RowScanner({{ template "extraArgsDefFirst" . }}row rowScanner) (res *{{ $.Types.GoType }}, err error) {
res = &{{ $.Types.GoType }}{}
if _, has := s.config.RowScanners[{{ printf "%q" (unexport $.Types.Singular) }}]; has {
@@ -511,7 +511,7 @@ func (s Store) internal{{ $.Types.Singular }}RowScanner({{ template "extraArgsDe
err = s.scan{{ $.Types.Singular }}Row({{ template "extraArgsCallFirst" . }}row, res)
{{- else }}
err = row.Scan(
- {{- range $.Fields }}
+ {{- range $.RDBMS.Columns }}
&res.{{ .Field }},
{{- end }}
)
@@ -554,7 +554,7 @@ func (Store) {{ unexport $.Types.Singular }}Columns(aa ... string) []string {
}
return []string{
- {{- range $.Fields }}
+ {{- range $.RDBMS.Columns }}
alias + "{{ .Column }}",
{{- end }}
}
@@ -568,7 +568,7 @@ func (Store) {{ unexport $.Types.Singular }}Columns(aa ... string) []string {
// With optional string arg, all columns are returned aliased
func (Store) sortable{{ $.Types.Singular }}Columns() []string {
return []string{
- {{ range $.Fields }}
+ {{ range $.RDBMS.Columns }}
{{- if .IsSortable -}}
"{{ .Column }}",
{{ end -}}
@@ -586,7 +586,7 @@ func (s Store) internal{{ export $.Types.Singular }}Encoder(res *{{ $.Types.GoTy
return s.encode{{ export $.Types.Singular }}(res)
{{- else }}
return store.Payload{
- {{- range $.Fields }}
+ {{- range $.RDBMS.Columns }}
"{{ .Column }}": res.{{ .Field }},
{{- end }}
}
@@ -610,14 +610,14 @@ func (s Store) collect{{ export $.Types.Singular }}CursorValues(res *{{ $.Types.
hasUnique bool
// All known primary key columns
- {{ range $.Fields.PrimaryKeyFields }}
+ {{ range $.RDBMS.Columns.PrimaryKeyFields }}
pk{{ export .Column }} bool
{{ end }}
collect = func(cc ...string) {
for _, c := range cc {
switch c {
- {{- range $.Fields }}
+ {{- range $.RDBMS.Columns }}
{{- if or .IsSortable .IsUnique .IsPrimaryKey -}}
case "{{ .Column }}":
cursor.Set(c, res.{{ .Field }}, false)
@@ -635,8 +635,8 @@ func (s Store) collect{{ export $.Types.Singular }}CursorValues(res *{{ $.Types.
)
collect(cc...)
- if !hasUnique || !({{ range $.Fields.PrimaryKeyFields }}pk{{ export .Column }} && {{ end }} true) {
- collect({{ range $.Fields.PrimaryKeyFields }}"{{ .Column }}",{{ end }})
+ if !hasUnique || !({{ range $.RDBMS.Columns.PrimaryKeyFields }}pk{{ export .Column }} && {{ end }} true) {
+ collect({{ range $.RDBMS.Columns.PrimaryKeyFields }}"{{ .Column }}",{{ end }})
}
return cursor
@@ -658,12 +658,12 @@ func (s *Store) check{{ export $.Types.Singular }}Constraints(ctx context.Contex
{{- range $lookup := $.Lookups }}
{{ if $lookup.UniqueConstraintCheck }}
{{- range $field := $lookup.Fields }}
- {{ if eq ($field | $.Fields.Find).Type "uint64" }}
+ {{ if eq ($field | $.RDBMS.Columns.Find).Type "uint64" }}
valid = valid && res.{{ $field }} > 0
- {{ else if eq ($field | $.Fields.Find).Type "string" }}
+ {{ else if eq ($field | $.RDBMS.Columns.Find).Type "string" }}
valid = valid && len(res.{{ $field }}) > 0
{{ else }}
- // can not check field {{ $field }} with unsupported type: {{ ($field | $.Fields.Find).Type }}
+ // can not check field {{ $field }} with unsupported type: {{ ($field | $.RDBMS.Columns.Find).Type }}
{{ end }}
{{- end }}
{{- end }}
diff --git a/pkg/codegen/store.go b/pkg/codegen/store.go
index fdd0399b7..b3fd73f92 100644
--- a/pkg/codegen/store.go
+++ b/pkg/codegen/store.go
@@ -87,6 +87,11 @@ type (
CustomCursorCollector bool `yaml:"customCursorCollector"`
CustomPostLoadProcessor bool `yaml:"customPostLoadProcessor"`
CustomEncoder bool `yaml:"customEncoder"`
+
+ Columns storeTypeRdbmsColumnSetDef
+
+ // map fields to columns
+ FieldMap map[string]*storeTypeRdbmsColumnDef `yaml:"mapFields"`
}
storeTypeFunctionsDef struct {
@@ -112,12 +117,6 @@ type (
// string: default
Type string `yaml:"type"`
- // When not explicitly set, defaults to snake-cased value from field
- //
- // Exceptions:
- // If field name ends with ID (ID), it converts that to rel_
- Column string `yaml:"column"`
-
// If field is flagged as PK it is used in update & Delete conditions
// Note: if no other field is set as primary and field with ID name
// exists, that field is auto-set as primary.
@@ -141,6 +140,18 @@ type (
// @todo implementation
FullTextSearch bool `yaml:"fts"`
+ }
+
+ storeTypeRdbmsColumnSetDef []*storeTypeRdbmsColumnDef
+
+ storeTypeRdbmsColumnDef struct {
+ storeTypeFieldDef
+
+ // When not explicitly set, defaults to snake-cased value from field
+ //
+ // Exceptions:
+ // If field name ends with ID (ID), it converts that to rel_
+ Column string `yaml:"column"`
alias string
}
@@ -313,38 +324,55 @@ func procStore(mm ...string) ([]*storeDef, error) {
def.RDBMS.Alias = def.Types.Base[0:1]
}
- var hasPrimaryKey = def.Fields.HasPrimaryKey()
- for _, field := range def.Fields {
- if !hasPrimaryKey && field.Field == "ID" {
- field.IsPrimaryKey = true
- field.IsSortable = true
+ for field := range def.RDBMS.FieldMap {
+ if nil == def.Fields.Find(field) {
+ return nil, fmt.Errorf("invalid RDBMS field map: unknown field %q used", field)
}
+ }
- // copy alias from global spec so we can
- // generate aliased columsn
- field.alias = def.RDBMS.Alias
-
- if field.Column == "" {
- switch {
- case field.Field != "ID" && strings.HasSuffix(field.Field, "ID"):
- field.Column = "rel_" + cc2underscore(field.Field[:len(field.Field)-2])
- default:
- field.Column = cc2underscore(field.Field)
- }
+ var hasPrimaryKey = def.Fields.HasPrimaryKey()
+ for _, fld := range def.Fields {
+ if !hasPrimaryKey && fld.Field == "ID" {
+ fld.IsPrimaryKey = true
+ fld.IsSortable = true
}
switch {
- case field.Type != "":
+ case fld.Type != "":
// type set
- case strings.HasSuffix(field.Field, "ID") || strings.HasSuffix(field.Field, "By"):
- field.Type = "uint64"
- case field.Field == "CreatedAt":
- field.Type = "time.Time"
- case strings.HasSuffix(field.Field, "At"):
- field.Type = "uint64"
+ case strings.HasSuffix(fld.Field, "ID") || strings.HasSuffix(fld.Field, "By"):
+ fld.Type = "uint64"
+ case fld.Field == "CreatedAt":
+ fld.Type = "time.Time"
+ case strings.HasSuffix(fld.Field, "At"):
+ fld.Type = "uint64"
default:
- field.Type = "string"
+ fld.Type = "string"
}
+
+ col, ok := def.RDBMS.FieldMap[fld.Field]
+ if ok {
+ col.storeTypeFieldDef = *fld
+ } else {
+ // In the most common case when field is NOT mapped,
+ // just create new column def struct and split it in
+ col = &storeTypeRdbmsColumnDef{storeTypeFieldDef: *fld}
+ }
+
+ // Make a copy for RDBMS columns
+ col.alias = def.RDBMS.Alias
+
+ if col.Column == "" {
+ // Map common naming if needed
+ switch {
+ case fld.Field != "ID" && strings.HasSuffix(fld.Field, "ID"):
+ col.Column = "rel_" + cc2underscore(fld.Field[:len(fld.Field)-2])
+ default:
+ col.Column = cc2underscore(fld.Field)
+ }
+ }
+
+ def.RDBMS.Columns = append(def.RDBMS.Columns, col)
}
for i, l := range def.Lookups {
@@ -522,10 +550,41 @@ func (f storeTypeFieldDef) Arg() string {
return strings.ToLower(f.Field[:1]) + f.Field[1:]
}
-func (f storeTypeFieldDef) AliasedColumn() string {
+func (f storeTypeRdbmsColumnDef) AliasedColumn() string {
return fmt.Sprintf("%s.%s", f.alias, f.Column)
}
+func (ff storeTypeRdbmsColumnSetDef) Find(name string) *storeTypeRdbmsColumnDef {
+ for _, f := range ff {
+ if f.Field == name {
+ return f
+ }
+ }
+
+ return nil
+}
+
+func (ff storeTypeRdbmsColumnSetDef) HasPrimaryKey() bool {
+ for _, f := range ff {
+ if f.IsPrimaryKey {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (ff storeTypeRdbmsColumnSetDef) PrimaryKeyFields() storeTypeRdbmsColumnSetDef {
+ pkSet := storeTypeRdbmsColumnSetDef{}
+ for _, f := range ff {
+ if f.IsPrimaryKey {
+ pkSet = append(pkSet, f)
+ }
+ }
+
+ return pkSet
+}
+
// UnmarshalYAML makes sure that export flag is set to true when not explicity disabled
func (d *storeTypeLookups) UnmarshalYAML(unmarshal func(interface{}) error) error {
type dAux storeTypeLookups
diff --git a/store/actionlog.yaml b/store/actionlog.yaml
index 5f91f082c..03f0a774a 100644
--- a/store/actionlog.yaml
+++ b/store/actionlog.yaml
@@ -9,11 +9,11 @@ types:
fields:
- { field: ID, sortDescending: true }
- - { field: Timestamp, column: "ts", type: "time.Time" }
+ - { field: Timestamp, type: "time.Time" }
- { field: RequestOrigin }
- - { field: RequestID, column: "request_id" }
+ - { field: RequestID }
- { field: ActorIPAddr }
- - { field: ActorID, column: "actor_id" }
+ - { field: ActorID }
- { field: Resource }
- { field: Action }
- { field: Error }
@@ -27,6 +27,10 @@ rdbms:
customFilterConverter: true
customRowScanner: true
customEncoder: true
+ mapFields:
+ Timestamp: { column: ts }
+ RequestID: { column: request_id }
+ ActorID: { column: actor_id }
search:
enableSorting: false
diff --git a/store/compose_module_fields.yaml b/store/compose_module_fields.yaml
index 42e1b33c7..5d37ee77a 100644
--- a/store/compose_module_fields.yaml
+++ b/store/compose_module_fields.yaml
@@ -12,10 +12,10 @@ fields:
- { field: Kind }
- { field: Label }
- { field: Options, type: "types.ModuleFieldOptions" }
- - { field: Private, type: bool, column: is_private }
- - { field: Required, type: bool, column: is_required }
- - { field: Visible, type: bool, column: is_visible }
- - { field: Multi, type: bool, column: is_multi }
+ - { field: Private, type: bool }
+ - { field: Required, type: bool }
+ - { field: Visible, type: bool }
+ - { field: Multi, type: bool }
- { field: DefaultValue, type: "types.RecordValueSet" }
- { field: CreatedAt }
- { field: UpdatedAt }
@@ -32,6 +32,11 @@ rdbms:
alias: cmf
table: compose_module_field
customFilterConverter: true
+ mapFields:
+ Private: { column: is_private }
+ Required: { column: is_required }
+ Visible: { column: is_visible }
+ Multi: { column: is_multi }
search:
diff --git a/store/compose_pages.yaml b/store/compose_pages.yaml
index 6fa6e52f1..3ac5086ca 100644
--- a/store/compose_pages.yaml
+++ b/store/compose_pages.yaml
@@ -6,15 +6,15 @@ types:
fields:
- { field: ID }
- - { field: SelfID, column: "self_id" }
+ - { field: SelfID }
- { field: NamespaceID }
- { field: ModuleID }
- { field: Handle, lookupFilterPreprocessor: lower }
- { field: Title }
- { field: Description, type: "string" }
- - { field: Blocks, type: "types.PageBlocks" }
- - { field: Visible, type: bool }
- - { field: Weight, type: int }
+ - { field: Blocks, type: "types.PageBlocks" }
+ - { field: Visible, type: bool }
+ - { field: Weight, type: int }
- { field: CreatedAt, sortable: true }
- { field: UpdatedAt, sortable: true }
- { field: DeletedAt, sortable: true }
@@ -48,4 +48,6 @@ rdbms:
alias: cpg
table: compose_page
customFilterConverter: true
+ mapFields:
+ SelfID: { column: self_id }
diff --git a/store/compose_record_values.yaml b/store/compose_record_values.yaml
index fbdb85f68..7d5d4c248 100644
--- a/store/compose_record_values.yaml
+++ b/store/compose_record_values.yaml
@@ -7,7 +7,7 @@ types:
type: types.RecordValue
fields:
- - { field: RecordID, isPrimaryKey: true, column: record_id }
+ - { field: RecordID, isPrimaryKey: true }
- { field: Name, isPrimaryKey: true }
- { field: Place, type: uint, isPrimaryKey: true }
- { field: Value }
@@ -31,6 +31,8 @@ rdbms:
alias: crv
table: compose_record_value
customFilterConverter: true
+ mapFields:
+ RecordID: { column: record_id }
search:
enablePaging: false
diff --git a/store/compose_records.yaml b/store/compose_records.yaml
index c5b196972..e97353c8f 100644
--- a/store/compose_records.yaml
+++ b/store/compose_records.yaml
@@ -6,7 +6,7 @@ types:
fields:
- { field: ID }
- - { field: ModuleID, column: module_id }
+ - { field: ModuleID }
- { field: NamespaceID }
- { field: OwnedBy }
- { field: CreatedBy }
@@ -43,6 +43,8 @@ rdbms:
customSortConverter: true
customCursorCollector: true
customPostLoadProcessor: true
+ mapFields:
+ ModuleID: { column: module_id }
search:
export: false
diff --git a/store/messaging_unread.yaml b/store/messaging_unread.yaml
index 75cd2cc29..39fef518c 100644
--- a/store/messaging_unread.yaml
+++ b/store/messaging_unread.yaml
@@ -6,7 +6,7 @@ types:
fields:
- { field: ChannelID, type: uint64, isPrimaryKey: true }
- - { field: ReplyTo, type: uint64, isPrimaryKey: true, column: rel_reply_to }
+ - { field: ReplyTo, type: uint64, isPrimaryKey: true }
- { field: UserID, type: uint64, isPrimaryKey: true }
- { field: LastMessageID, type: uint64 }
- { field: Count, type: uint32 }
@@ -56,6 +56,8 @@ functions:
rdbms:
alias: mur
table: messaging_unread
+ mapFields:
+ ReplyTo: { column: rel_reply_to }
search:
enable: false
diff --git a/store/settings.yaml b/store/settings.yaml
index 1745f1e43..783193280 100644
--- a/store/settings.yaml
+++ b/store/settings.yaml
@@ -6,8 +6,8 @@ types:
filterType: types.SettingsFilter
fields:
- - { field: Name, isPrimaryKey: true }
- - { field: OwnedBy, isPrimaryKey: true, column: rel_owner }
+ - { field: Name, isPrimaryKey: true }
+ - { field: OwnedBy, isPrimaryKey: true }
- { field: Value }
- { field: UpdatedBy }
- { field: UpdatedAt }
@@ -25,4 +25,6 @@ rdbms:
alias: st
table: settings
customFilterConverter: true
+ mapFields:
+ OwnedBy: { column: rel_owner }
diff --git a/store/users.yaml b/store/users.yaml
index f5159bde5..b5a7831fb 100644
--- a/store/users.yaml
+++ b/store/users.yaml
@@ -3,11 +3,11 @@ import:
fields:
- { field: ID }
- - { field: Email, sortable: true, unique: true, fts: true, lookupFilterPreprocessor: lower }
+ - { field: Email, sortable: true, unique: true, lookupFilterPreprocessor: lower }
- { field: EmailConfirmed }
- - { field: Username, sortable: true, unique: true, fts: true, lookupFilterPreprocessor: lower }
- - { field: Name, sortable: true, fts: true }
- - { field: Handle, sortable: true, unique: true, fts: true, lookupFilterPreprocessor: lower }
+ - { field: Username, sortable: true, unique: true, lookupFilterPreprocessor: lower }
+ - { field: Name, sortable: true, }
+ - { field: Handle, sortable: true, unique: true, lookupFilterPreprocessor: lower }
- { field: Meta, type: "*types.UserMeta" }
- { field: Kind }
- { field: CreatedAt, sortable: true }