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 }