Cleaner store API

This commit is contained in:
Denis Arh
2020-08-26 14:49:33 +02:00
parent 3d4c2e2f9a
commit 361a9007bf
391 changed files with 22812 additions and 7855 deletions
+3 -11
View File
@@ -7,7 +7,6 @@ import (
"io"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"text/template"
@@ -94,20 +93,13 @@ type (
)
// Processes multiple action definitions
func procActions() ([]*actionsDef, error) {
func procActions(mm ...string) (dd []*actionsDef, err error) {
var (
f io.ReadCloser
d *actionsDef
dd = make([]*actionsDef, 0)
)
// <app>/service/<kind>_actions.yaml
mm, err := filepath.Glob(filepath.Join("*", "service", "*_actions.yaml"))
if err != nil {
return nil, fmt.Errorf("glob failed: %w", err)
}
dd = make([]*actionsDef, 0)
for _, m := range mm {
err = func() error {
if f, err = os.Open(m); err != nil {
@@ -284,7 +276,7 @@ func severityConstName(s string) string {
}
}
func genActions(tpl *template.Template, dd []*actionsDef) (err error) {
func genActions(tpl *template.Template, dd ...*actionsDef) (err error) {
var (
// Will only be generated if file does not exist previously
tplActionsGen = tpl.Lookup("actions.gen.go.tpl")
+14 -14
View File
@@ -20,40 +20,40 @@ import (
type (
// Internal API interface
{{ pubIdent $.Endpoint.Entrypoint }}API interface {
{{ export $.Endpoint.Entrypoint }}API interface {
{{- range $a := $.Endpoint.Apis }}
{{ pubIdent $a.Name }}(context.Context, *request.{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) (interface{}, error)
{{ export $a.Name }}(context.Context, *request.{{ export $.Endpoint.Entrypoint $a.Name }}) (interface{}, error)
{{- end }}
}
// HTTP API interface
{{ pubIdent .Endpoint.Entrypoint }} struct {
{{ export .Endpoint.Entrypoint }} struct {
{{- range $a := .Endpoint.Apis }}
{{ pubIdent $a.Name }} func(http.ResponseWriter, *http.Request)
{{ export $a.Name }} func(http.ResponseWriter, *http.Request)
{{- end }}
}
)
func {{ pubIdent "New" $.Endpoint.Entrypoint }}(h {{ pubIdent $.Endpoint.Entrypoint }}API) *{{ pubIdent $.Endpoint.Entrypoint }} {
return &{{ pubIdent $.Endpoint.Entrypoint }}{
func {{ export "New" $.Endpoint.Entrypoint }}(h {{ export $.Endpoint.Entrypoint }}API) *{{ export $.Endpoint.Entrypoint }} {
return &{{ export $.Endpoint.Entrypoint }}{
{{- range $a := .Endpoint.Apis }}
{{ pubIdent $a.Name }}: func(w http.ResponseWriter, r *http.Request) {
{{ export $a.Name }}: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.New{{ pubIdent $.Endpoint.Entrypoint $a.Name }}()
params := request.New{{ export $.Endpoint.Entrypoint $a.Name }}()
if err := params.Fill(r); err != nil {
logger.LogParamError("{{ pubIdent $.Endpoint.Entrypoint }}.{{ pubIdent $a.Name }}", r, err)
logger.LogParamError("{{ export $.Endpoint.Entrypoint }}.{{ export $a.Name }}", r, err)
resputil.JSON(w, err)
return
}
value, err := h.{{ pubIdent $a.Name }}(r.Context(), params)
value, err := h.{{ export $a.Name }}(r.Context(), params)
if err != nil {
logger.LogControllerError("{{ pubIdent $.Endpoint.Entrypoint }}.{{ pubIdent $a.Name }}", r, err, params.Auditable())
logger.LogControllerError("{{ export $.Endpoint.Entrypoint }}.{{ export $a.Name }}", r, err, params.Auditable())
resputil.JSON(w, err)
return
}
logger.LogControllerCall("{{ pubIdent $.Endpoint.Entrypoint }}.{{ pubIdent $a.Name }}", r, params.Auditable())
logger.LogControllerCall("{{ export $.Endpoint.Entrypoint }}.{{ export $a.Name }}", r, params.Auditable())
if !serveHTTP(value, w, r) {
resputil.JSON(w, value)
}
@@ -62,12 +62,12 @@ func {{ pubIdent "New" $.Endpoint.Entrypoint }}(h {{ pubIdent $.Endpoint.Entrypo
}
}
func (h {{ pubIdent $.Endpoint.Entrypoint }}) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
func (h {{ export $.Endpoint.Entrypoint }}) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
{{- range $a := .Endpoint.Apis }}
r.{{ pubIdent ( toLower $a.Method ) }}("{{ $.Endpoint.Path }}{{ $a.Path }}", h.{{ pubIdent $a.Name }})
r.{{ export ( toLower $a.Method ) }}("{{ $.Endpoint.Path }}{{ $a.Path }}", h.{{ export $a.Name }})
{{- end }}
})
}
+18 -18
View File
@@ -33,43 +33,43 @@ var (
type (
// Internal API interface
{{- range $a := $.Endpoint.Apis }}
{{ pubIdent $.Endpoint.Entrypoint $a.Name }} struct {
{{ export $.Endpoint.Entrypoint $a.Name }} struct {
{{- range $p := $a.Params.All }}
// {{ pubIdent $p.Name }} {{ $p.Origin }} parameter
// {{ export $p.Name }} {{ $p.Origin }} parameter
//
// {{ $p.Title }}
{{ pubIdent $p.Name }} {{ $p.Type }} {{ $p.FieldTag }}
{{ export $p.Name }} {{ $p.Type }} {{ $p.FieldTag }}
{{ end }}
}
{{ end }}
)
{{- range $a := $.Endpoint.Apis }}
// {{ pubIdent "New" $.Endpoint.Entrypoint $a.Name }} request
func {{ pubIdent "New" $.Endpoint.Entrypoint $a.Name }}() *{{ pubIdent $.Endpoint.Entrypoint $a.Name }} {
return &{{ pubIdent $.Endpoint.Entrypoint $a.Name }}{}
// {{ export "New" $.Endpoint.Entrypoint $a.Name }} request
func {{ export "New" $.Endpoint.Entrypoint $a.Name }}() *{{ export $.Endpoint.Entrypoint $a.Name }} {
return &{{ export $.Endpoint.Entrypoint $a.Name }}{}
}
// Auditable returns all auditable/loggable parameters
func (r {{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Auditable() map[string]interface{} {
func (r {{ export $.Endpoint.Entrypoint $a.Name }}) Auditable() map[string]interface{} {
return map[string]interface{}{
{{- range $p := $a.Params.All }}
"{{ $p.Name }}": r.{{ pubIdent $p.Name }},
"{{ $p.Name }}": r.{{ export $p.Name }},
{{- end }}
}
}
{{- range $p := $a.Params.All }}
// Auditable returns all auditable/loggable parameters
func (r {{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Get{{ pubIdent $p.Name }}() {{ $p.Type }} {
return r.{{ pubIdent $p.Name }}
func (r {{ export $.Endpoint.Entrypoint $a.Name }}) Get{{ export $p.Name }}() {{ $p.Type }} {
return r.{{ export $p.Name }}
}
{{- end }}
// Fill processes request and fills internal variables
func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (err error) {
func (r *{{ export $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(req.Body).Decode(r)
@@ -89,7 +89,7 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (
{{ range $p := $a.Params.Get }}
{{- if not $p.IsSlice }}
if val, ok := tmp["{{ $p.Name }}"]; ok && len(val) > 0 {
r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val[0]" }}
r.{{ export $p.Name }}, err = {{ $p.Parser "val[0]" }}
if err != nil {
return err
}
@@ -97,12 +97,12 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (
{{- end }}
{{- if $p.IsSlice }}
if val, ok := tmp["{{ $p.Name }}[]"]; ok {
r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val" }}
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
} else if val, ok := tmp["{{ $p.Name }}"]; ok {
r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val" }}
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
@@ -121,13 +121,13 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (
// POST params
{{ range $p := $a.Params.Post }}
{{ if $p.IsUpload }}
if _, r.{{ pubIdent $p.Name }}, err = req.FormFile("{{ $p.Name }}"); err != nil {
if _, r.{{ export $p.Name }}, err = req.FormFile("{{ $p.Name }}"); err != nil {
return fmt.Errorf("error processing uploaded file: %w", err)
}
{{ else }}
{{- if not $p.IsSlice }}
if val, ok := req.Form["{{ $p.Name }}"]; ok && len(val) > 0 {
r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val[0]" }}
r.{{ export $p.Name }}, err = {{ $p.Parser "val[0]" }}
if err != nil {
return err
}
@@ -135,7 +135,7 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (
{{- end }}
{{- if $p.IsSlice }}
//if val, ok := req.Form["{{ $p.Name }}[]"]; ok && len(val) > 0 {
// r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val" }}
// r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
// if err != nil {
// return err
// }
@@ -153,7 +153,7 @@ func (r *{{ pubIdent $.Endpoint.Entrypoint $a.Name }}) Fill(req *http.Request) (
// path params
{{ range $p := $a.Params.Path }}
val = chi.URLParam(req, "{{ $p.Name }}")
r.{{ pubIdent $p.Name }}, err = {{ $p.Parser "val" }}
r.{{ export $p.Name }}, err = {{ $p.Parser "val" }}
if err != nil {
return err
}
+127
View File
@@ -0,0 +1,127 @@
package store
// This file is auto-generated.
//
// Template: pkg/codegen/assets/store_base.gen.go.tpl
// Definitions: {{ .Source }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
import (
"context"
{{- range .Import }}
{{ normalizeImport . }}
{{- end }}
)
type (
{{- $Types := .Types }}
{{- $Fields := .Fields }}
{{ export .Types.Plural }} interface {
{{ if .Publish }}
{{- if .Search.Enable }}
Search{{ export $Types.Plural }}(ctx context.Context{{ template "extraArgsDef" . }}, f {{ $Types.GoFilterType }}) ({{ $Types.GoSetType }}, {{ $Types.GoFilterType }}, error)
{{- end }}
{{- range .Lookups }}
Lookup{{ export $Types.Singular }}By{{ export .Suffix }}(ctx context.Context{{ template "extraArgsDef" $ }}{{- range $field := .Fields }}, {{ cc2underscore $field }} {{ ($field | $Fields.Find).Type }}{{- end }}) (*{{ $Types.GoType }}, error)
{{- end }}
{{ if .Create.Enable }}
Create{{ export $Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error
{{- end }}
{{ if .Update.Enable }}
Update{{ export $Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error
Partial{{ export $Types.Singular }}Update(ctx context.Context{{ template "extraArgsDef" . }}, onlyColumns []string, rr ... *{{ $Types.GoType }}) error
{{- end }}
{{ if .Upsert.Enable }}
Upsert{{ export $Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error
{{- end }}
{{ if .Delete.Enable }}
Delete{{ export $Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error
Delete{{ export $Types.Singular }}By{{ template "primaryKeySuffix" $Fields }}(ctx context.Context{{ template "extraArgsDef" . }} {{ template "primaryKeyArgsDef" $Fields }}) error
{{- end }}
Truncate{{ export $Types.Plural }}(ctx context.Context{{ template "extraArgsDef" . }}) error
{{ end }}
{{- if .Functions}}
// Additional custom functions
{{- range .Functions }}
// {{ .Name }} (custom function)
{{ .Name }}(ctx context.Context{{ template "extraArgsDef" . }}) ({{ join ", " .Return }})
{{- end }}
{{- end -}}
}
)
{{/* convering scenario with non-exported main functions and no additional functions defined
where we get "imported and not used error" */}}
var _ *{{ $Types.GoType }}
var _ context.Context
{{ if .Publish }}
{{- if .Search.Enable }}
// Search{{ export $.Types.Plural }} returns all matching {{ $.Types.Plural }} from store
func Search{{ export $Types.Plural }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, f {{ $Types.GoFilterType }}) ({{ $Types.GoSetType }}, {{ $Types.GoFilterType }}, error) {
return s.Search{{ export $Types.Plural }}(ctx{{ template "extraArgsCall" . }}, f)
}
{{- end -}}
{{ range .Lookups }}
// Lookup{{ export $.Types.Singular }}By{{ export .Suffix }} {{ comment .Description true -}}
func Lookup{{ export $Types.Singular }}By{{ export .Suffix }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" $ }}{{- range $field := .Fields }}, {{ cc2underscore $field }} {{ ($field | $Fields.Find).Type }}{{- end }}) (*{{ $Types.GoType }}, error) {
return s.Lookup{{ export $Types.Singular }}By{{ export .Suffix }}(ctx{{ template "extraArgsCall" $ }}{{- range $field := .Fields }}, {{ cc2underscore $field }}{{- end }})
}
{{- end }}
// Create{{ export $.Types.Singular }} creates one or more {{ $.Types.Plural }} in store
func Create{{ export $Types.Singular }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error {
return s.Create{{ export $Types.Singular }}(ctx{{ template "extraArgsCall" . }}, rr... )
}
{{ if .Update.Enable }}
// Update{{ export $.Types.Singular }} updates one or more (existing) {{ $.Types.Plural }} in store
func Update{{ export $Types.Singular }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error {
return s.Update{{ export $Types.Singular }}(ctx{{ template "extraArgsCall" . }}, rr... )
}
// Partial{{ export $.Types.Singular }}Update updates one or more existing {{ $.Types.Plural }} in store
func Partial{{ export $Types.Singular }}Update(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, onlyColumns []string, rr ... *{{ $Types.GoType }}) error {
return s.Partial{{ export $Types.Singular }}Update(ctx{{ template "extraArgsCall" . }}, onlyColumns, rr...)
}
{{ end }}
{{ if .Upsert.Enable }}
// Upsert{{ export $.Types.Singular }} creates new or updates existing one or more {{ $.Types.Plural }} in store
func Upsert{{ export $Types.Singular }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error {
return s.Upsert{{ export $Types.Singular }}(ctx{{ template "extraArgsCall" . }}, rr... )
}
{{ end }}
{{ if .Delete.Enable }}
// Delete{{ export $.Types.Singular }} Deletes one or more {{ $.Types.Plural }} from store
func Delete{{ export $Types.Singular }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}, rr ... *{{ $Types.GoType }}) error {
return s.Delete{{ export $Types.Singular }}(ctx{{ template "extraArgsCall" . }}, rr...)
}
// Delete{{ export $.Types.Singular }}By{{ template "primaryKeySuffix" $.Fields }} Deletes {{ $.Types.Singular }} from store
func Delete{{ export $Types.Singular }}By{{ template "primaryKeySuffix" $Fields }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }} {{ template "primaryKeyArgsDef" $Fields }}) error {
return s.Delete{{ export $Types.Singular }}By{{ template "primaryKeySuffix" $Fields }}(ctx{{ template "extraArgsCall" . }}{{ template "primaryKeyArgsCall" $Fields }})
}
{{ end }}
// Truncate{{ export $.Types.Plural }} Deletes all {{ $.Types.Plural }} from store
func Truncate{{ export $Types.Plural }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}) error {
return s.Truncate{{ export $Types.Plural }}(ctx{{ template "extraArgsCall" . }})
}
{{ end }}
{{ range .Functions }}
func {{ .Name }}(ctx context.Context, s {{ export $Types.Plural }}{{ template "extraArgsDef" . }}) ({{ join ", " .Return }}) {
return s.{{ .Name }}(ctx{{ template "extraArgsCall" . }})
}
{{ end }}
-74
View File
@@ -1,74 +0,0 @@
package bulk
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// Definitions file that controls how this file is generated:
// {{ .Source }}
import (
"context"
{{- range $import := $.Import }}
{{ normalizeImport $import }}
{{- end }}
)
type (
{{ unpubIdent $.Types.Singular }}Create struct {
Done chan struct{}
res *{{ $.Types.GoType }}
err error
}
{{ unpubIdent $.Types.Singular }}Update struct {
Done chan struct{}
res *{{ $.Types.GoType }}
err error
}
{{ unpubIdent $.Types.Singular }}Remove struct {
Done chan struct{}
res *{{ $.Types.GoType }}
err error
}
)
// Create{{ pubIdent $.Types.Singular }} creates a new {{ pubIdent $.Types.Singular }}
// create job that can be pushed to store's transaction handler
func Create{{ pubIdent $.Types.Singular }}(res *{{ $.Types.GoType }}) *{{ unpubIdent $.Types.Singular }}Create {
return &{{ unpubIdent $.Types.Singular }}Create{res: res}
}
// Do Executes {{ unpubIdent $.Types.Singular }}Create job
func (j *{{ unpubIdent $.Types.Singular }}Create) Do(ctx context.Context, s storeInterface) error {
j.err = s.Create{{ pubIdent $.Types.Singular }}(ctx, j.res)
j.Done <- struct{}{}
return j.err
}
// Update{{ pubIdent $.Types.Singular }} creates a new {{ pubIdent $.Types.Singular }}
// update job that can be pushed to store's transaction handler
func Update{{ pubIdent $.Types.Singular }}(res *{{ $.Types.GoType }}) *{{ unpubIdent $.Types.Singular }}Update {
return &{{ unpubIdent $.Types.Singular }}Update{res: res}
}
// Do Executes {{ unpubIdent $.Types.Singular }}Update job
func (j *{{ unpubIdent $.Types.Singular }}Update) Do(ctx context.Context, s storeInterface) error {
j.err = s.Update{{ pubIdent $.Types.Singular }}(ctx, j.res)
j.Done <- struct{}{}
return j.err
}
// Remove{{ pubIdent $.Types.Singular }} creates a new {{ pubIdent $.Types.Singular }}
// remove job that can be pushed to store's transaction handler
func Remove{{ pubIdent $.Types.Singular }}(res *{{ $.Types.GoType }}) *{{ unpubIdent $.Types.Singular }}Remove {
return &{{ unpubIdent $.Types.Singular }}Remove{res: res}
}
// Do Executes {{ unpubIdent $.Types.Singular }}Remove job
func (j *{{ unpubIdent $.Types.Singular }}Remove) Do(ctx context.Context, s storeInterface) error {
j.err = s.Remove{{ pubIdent $.Types.Singular }}(ctx, j.res)
j.Done <- struct{}{}
return j.err
}
@@ -1,37 +0,0 @@
package {{ .Package }}
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// - {{ .Source }}
import (
"context"
{{- range .Import }}
{{ normalizeImport . }}
{{- end }}
)
type (
{{- $Types := .Types }}
{{- $Fields := .Fields }}
{{ unpubIdent .Types.Plural }}Store interface {
{{- if not .Search.Disable }}
Search{{ pubIdent $Types.Plural }}(ctx context.Context, f {{ $Types.GoFilterType }}) ({{ $Types.GoSetType }}, {{ $Types.GoFilterType }}, error)
{{- end }}
{{- range .Lookups }}
Lookup{{ pubIdent $Types.Singular }}By{{ pubIdent .Suffix }}(ctx context.Context{{- range $field := .Fields }}, {{ cc2underscore $field }} {{ ($field | $Fields.Find).Type }}{{- end }}) (*{{ $Types.GoType }}, error)
{{- end }}
Create{{ pubIdent $Types.Singular }}(ctx context.Context, rr ... *{{ $Types.GoType }}) error
Update{{ pubIdent $Types.Singular }}(ctx context.Context, rr ... *{{ $Types.GoType }}) error
PartialUpdate{{ pubIdent $Types.Singular }}(ctx context.Context, onlyColumns []string, rr ... *{{ $Types.GoType }}) error
Remove{{ pubIdent $Types.Singular }}(ctx context.Context, rr ... *{{ $Types.GoType }}) error
Remove{{ pubIdent $Types.Singular }}By{{ template "primaryKeySuffix" $Fields }}(ctx context.Context {{ template "primaryKeyArgs" $Fields }}) error
Truncate{{ pubIdent $Types.Plural }}(ctx context.Context) error
}
)
@@ -1,8 +1,8 @@
package {{ .Package }}
package store
// This file is auto-generated.
//
// Template: pkg/store_interfaces_joined.gen.go.tpl
// Template: pkg/codegen/assets/store_interfaces_joined.gen.go.tpl
// Definitions:
{{- range .Definitions }}
// - {{ .Source }}
@@ -12,11 +12,21 @@ package {{ .Package }}
// the code is regenerated.
//
import (
"context"
)
type (
// Interface combines interfaces of all supported store interfaces
storeGeneratedInterfaces interface {
Transactionable interface {
Tx(context.Context, func(context.Context, Storable) error) error
}
// Sortable interface combines interfaces of all supported store interfaces
Storable interface {
Transactionable
{{ range .Definitions -}}
{{ unpubIdent .Types.Plural }}Store
{{ export .Types.Plural }}
{{ end }}
}
)
+37 -5
View File
@@ -1,4 +1,4 @@
{{- define "primaryKeyArgs" -}}
{{- define "primaryKeyArgsDef" -}}
{{- range $field := . -}}
{{- if $field.IsPrimaryKey -}}
, {{ $field.Arg }} {{ camelCase $field.Type }}
@@ -6,12 +6,44 @@
{{- end -}}
{{- end -}}
{{- define "primaryKeyArgsCall" -}}
{{- range $field := . -}}
{{- if $field.IsPrimaryKey -}}
, {{ $field.Arg }}
{{- end -}}
{{- end -}}
{{- end -}}
{{- define "primaryKeySuffix" -}}
{{- range $field := . }}{{ if $field.IsPrimaryKey }}{{ $field.Field }}{{ end }}{{ end -}}
{{- end -}}
{{- define "partialUpdateArgs" -}}
{{- range .Args -}}
, {{ .Arg }} {{ .Type }}
{{ define "extraArgsDefFirst" }}
{{- range .Arguments -}}
_{{ .Name }} {{ .Type }},
{{- end -}}
{{- end -}}
{{ end }}
{{ define "extraArgsDef" }}
{{- range .Arguments -}}
, _{{ .Name }} {{ .Type }}
{{- end -}}
{{ end }}
{{ define "extraArgsDefTypesOnly" }}
{{- range .Arguments -}}
, {{ .Type }}
{{- end -}}
{{ end }}
{{ define "extraArgsCallFirst" }}
{{- range .Arguments -}}
_{{ .Name }},
{{- end -}}
{{ end }}
{{ define "extraArgsCall" }}
{{- range .Arguments -}}
, _{{ .Name }}
{{- end -}}
{{ end }}
+273 -111
View File
@@ -10,11 +10,13 @@ package rdbms
import (
"context"
"errors"
"database/sql"
"fmt"
"github.com/Masterminds/squirrel"
"github.com/cortezaproject/corteza-server/store"
{{- if not $.Search.DisablePaging }}
{{- if $.Search.EnablePaging }}
"github.com/cortezaproject/corteza-server/pkg/filter"
"strings"
{{- end }}
{{- range $import := $.Import }}
@@ -22,24 +24,41 @@ import (
{{- end }}
)
{{ if not $.Search.Disable }}
// Search{{ pubIdent $.Types.Plural }} returns all matching rows
var _ = errors.Is
const (
{{- if .Create.Enable }}
TriggerBefore{{ export $.Types.Singular }}Create triggerKey = "{{ unexport $.Types.Singular }}BeforeCreate"
{{- end }}
{{- if .Update.Enable }}
TriggerBefore{{ export $.Types.Singular }}Update triggerKey = "{{ unexport $.Types.Singular }}BeforeUpdate"
{{- end }}
{{- if .Upsert.Enable }}
TriggerBefore{{ export $.Types.Singular }}Upsert triggerKey = "{{ unexport $.Types.Singular }}BeforeUpsert"
{{- end }}
{{- if .Delete.Enable }}
TriggerBefore{{ export $.Types.Singular }}Delete triggerKey = "{{ unexport $.Types.Singular }}BeforeDelete"
{{- end }}
)
{{ if $.Search.Enable }}
// {{ toggleExport .Search.Export "Search" $.Types.Plural }} returns all matching rows
//
// This function calls convert{{ pubIdent $.Types.Singular }}Filter with the given
// This function calls convert{{ export $.Types.Singular }}Filter with the given
// {{ $.Types.GoFilterType }} and expects to receive a working squirrel.SelectBuilder
func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.Types.GoFilterType }}) ({{ $.Types.GoSetType }}, {{ $.Types.GoFilterType }}, error) {
func (s Store) {{ toggleExport .Search.Export "Search" $.Types.Plural }}(ctx context.Context{{ template "extraArgsDef" . }}, f {{ $.Types.GoFilterType }}) ({{ $.Types.GoSetType }}, {{ $.Types.GoFilterType }}, error) {
var scap uint
{{- if .RDBMS.CustomFilterConverter }}
q, err := s.convert{{ pubIdent $.Types.Singular }}Filter(f)
q, err := s.convert{{ export $.Types.Singular }}Filter({{ template "extraArgsCallFirst" . }}f)
if err != nil {
return nil, f, err
}
{{- else }}
q := s.Query{{ pubIdent $.Types.Plural }}()
q := s.{{ unexport $.Types.Plural }}SelectBuilder()
{{- end }}
{{ if not $.Search.DisablePaging }}
{{ if $.Search.EnablePaging }}
scap = f.Limit
// Cleanup anything we've accidentally received...
@@ -50,22 +69,8 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
reverseCursor := f.PageCursor != nil && f.PageCursor.Reverse
{{ end }}
{{ if $.Search.DisableSorting }}
{{ if not $.Search.DisablePaging }}
// Sorting is disabled in definition yaml file
// {search: {disableSorting:true}}
//
// We still need to sort the results by primary key for paging purposes
sort := store.SortExprSet{
{{ range $.Fields }}
{{- if or .IsPrimaryKey -}}
&store.SortExpr{Column: {{ printf "%q" .Column }}, {{ if .SortDescending }}Descending: true, {{ end }}},
{{- end }}
{{- end }}
}
{{ end }}
{{ else }}
if err = f.Sort.Validate(s.sortable{{ pubIdent $.Types.Singular }}Columns()...); err != nil {
{{ if $.Search.EnableSorting }}
if err := f.Sort.Validate(s.sortable{{ export $.Types.Singular }}Columns()...); err != nil {
return nil, f, fmt.Errorf("could not validate sort: %v", err)
}
@@ -88,6 +93,18 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
q = q.OrderBy(sqlSort...)
}
{{ else if $.Search.EnablePaging }}
// Sorting is disabled in definition yaml file
// {search: {enablePaging:false}}
//
// We still need to sort the results by primary key for paging purposes
sort := filter.SortExprSet{
{{ range $.Fields }}
{{- if or .IsPrimaryKey -}}
&filter.SortExpr{Column: {{ printf "%q" .Column }}, {{ if .SortDescending }}Descending: true, {{ end }}},
{{- end }}
{{- end }}
}
{{ end }}
if scap == 0 {
@@ -98,9 +115,9 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
var (
set = make([]*{{ $.Types.GoType }}, 0, scap)
{{- if $.Search.DisablePaging }}
{{- if not $.Search.EnablePaging }}
// Paging is disabled in definition yaml file
// {search: {disablePaging:true}} and this allows
// {search: {enablePaging:false}} and this allows
// a much simpler row fetching logic
fetch = func() error {
var (
@@ -113,21 +130,42 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
}
for rows.Next() {
if res, err = s.internal{{ pubIdent $.Types.Singular }}RowScanner(rows, rows.Err()); err != nil {
if rows.Err() == nil {
res, err = s.internal{{ export $.Types.Singular }}RowScanner({{ template "extraArgsCallFirst" . }}rows)
}
if err != nil {
if cerr := rows.Close(); cerr != nil {
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
}
return err
}
// If check function is set, call it and act accordingly
{{ if $.Search.EnableFilterCheckFn }}
if f.Check != nil {
if chk, err := f.Check(res); err != nil {
if cerr := rows.Close(); cerr != nil {
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
}
return err
} else if !chk {
// did not pass the check
// go with the next row
continue
}
}
{{ end -}}
set = append(set, res)
}
return rows.Close()
}
{{ else }}
// fetches rows and scans them into {{ pubIdent $.Types.GoType }} resource this is then passed to Check function on filter
// fetches rows and scans them into {{ $.Types.GoType }} resource this is then passed to Check function on filter
// to help determine if fetched resource fits or not
//
// Note that limit is passed explicitly and is not necessarily equal to filter's limit. We want
@@ -136,7 +174,7 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
// The value for cursor is used and set directly from/to the filter!
//
// It returns total number of fetched pages and modifies PageCursor value for paging
fetchPage = func(cursor *store.PagingCursor, limit uint) (fetched uint, err error) {
fetchPage = func(cursor *filter.PagingCursor, limit uint) (fetched uint, err error) {
var (
res *{{ $.Types.GoType }}
@@ -167,7 +205,12 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
for rows.Next() {
fetched++
if res, err = s.internal{{ pubIdent $.Types.Singular }}RowScanner(rows, rows.Err()); err != nil {
if rows.Err() == nil {
res, err = s.internal{{ export $.Types.Singular }}RowScanner({{ template "extraArgsCallFirst" . }}rows)
}
if err != nil {
if cerr := rows.Close(); cerr != nil {
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
}
@@ -176,7 +219,7 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
}
// If check function is set, call it and act accordingly
{{ if not $.Search.DisableFilterCheckFn }}
{{ if $.Search.EnableFilterCheckFn }}
if f.Check != nil {
var chk bool
if chk, err = f.Check(res); err != nil {
@@ -265,13 +308,13 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
if f.Limit > 0 && len(set) > 0 {
if f.PageCursor != nil && (!f.PageCursor.Reverse || lastSetFull) {
f.PrevPage = s.collect{{ pubIdent $.Types.Singular }}CursorValues(set[0], sort.Columns()...)
f.PrevPage = s.collect{{ export $.Types.Singular }}CursorValues(set[0], sort.Columns()...)
f.PrevPage.Reverse = true
}
// Less items fetched then requested by page-limit
// not very likely there's another page
f.NextPage = s.collect{{ pubIdent $.Types.Singular }}CursorValues(set[len(set)-1], sort.Columns()...)
f.NextPage = s.collect{{ export $.Types.Singular }}CursorValues(set[len(set)-1], sort.Columns()...)
}
f.PageCursor = nil
@@ -285,49 +328,69 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
{{ end }}
{{- range $lookup := $.Lookups }}
// Lookup{{ pubIdent $.Types.Singular }}By{{ pubIdent $lookup.Suffix }} {{ comment $lookup.Description true -}}
func (s Store) Lookup{{ pubIdent $.Types.Singular }}By{{ pubIdent $lookup.Suffix }}(ctx context.Context{{- range $field := $lookup.Fields }}, {{ cc2underscore $field }} {{ ($field | $.Fields.Find).Type }}{{- end }}) (*{{ $.Types.GoType }}, error) {
return s.{{ $.Types.Singular }}Lookup(ctx, squirrel.Eq{
// {{ 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) {
return s.execLookup{{ $.Types.Singular }}(ctx{{ template "extraArgsCall" $ }}, squirrel.Eq{
{{- range $field := $lookup.Fields }}
"{{ ($field | $.Fields.Find).AliasedColumn }}": {{ cc2underscore $field }},
s.preprocessColumn({{ printf "%q" ($field | $.Fields.Find).AliasedColumn }}, {{ printf "%q" ($field | $.Fields.Find).LookupFilterPreprocess }}): s.preprocessValue({{ cc2underscore $field }}, {{ printf "%q" ($field | $.Fields.Find).LookupFilterPreprocess }}),
{{- end }}
{{- range $field, $value := $lookup.Filter }}
{{ range $field, $value := $lookup.Filter }}
"{{ ($field | $.Fields.Find).AliasedColumn }}": {{ $value }},
{{- end }}
})
}
{{ end }}
// Create{{ pubIdent $.Types.Singular }} creates one or more rows in {{ $.RDBMS.Table }} table
func (s Store) Create{{ pubIdent $.Types.Singular }}(ctx context.Context, rr ... *{{ $.Types.GoType }}) (err error) {
{{ if .Create.Enable }}
// {{ toggleExport .Create.Export "Create" $.Types.Singular }} creates one or more rows in {{ $.RDBMS.Table }} table
func (s Store) {{ toggleExport .Create.Export "Create" $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $.Types.GoType }}) (err error) {
for _, res := range rr {
err = ExecuteSqlizer(ctx, s.DB(), s.Insert(s.{{ $.Types.Singular }}Table()).SetMap(s.internal{{ pubIdent $.Types.Singular }}Encoder(res)))
err = s.check{{ export $.Types.Singular }}Constraints(ctx {{ template "extraArgsCall" $ }}, res)
if err != nil {
return s.config.ErrorHandler(err)
return err
}
// err = s.{{ unexport $.Types.Singular }}Hook(ctx, TriggerBefore{{ export $.Types.Singular }}Create{{ template "extraArgsCall" . }}, res)
// if err != nil {
// return err
// }
err = s.execCreate{{ export $.Types.Plural }}(ctx, s.internal{{ export $.Types.Singular }}Encoder(res))
if err != nil {
return err
}
}
return
}
{{ end }}
// Update{{ pubIdent $.Types.Singular }} updates one or more existing rows in {{ $.RDBMS.Table }}
func (s Store) Update{{ pubIdent $.Types.Singular }}(ctx context.Context, rr ... *{{ $.Types.GoType }}) error {
return s.config.ErrorHandler(s.PartialUpdate{{ pubIdent $.Types.Singular }}(ctx, nil, rr...))
{{ if .Update.Enable }}
// {{ toggleExport .Update.Export "Update" $.Types.Singular }} updates one or more existing rows in {{ $.RDBMS.Table }}
func (s Store) {{ toggleExport .Update.Export "Update" $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $.Types.GoType }}) error {
return s.config.ErrorHandler(s.{{ toggleExport .Update.Export "Partial" $.Types.Singular "Update" }}(ctx{{ template "extraArgsCall" . }}, nil, rr...))
}
// PartialUpdate{{ pubIdent $.Types.Singular }} updates one or more existing rows in {{ $.RDBMS.Table }}
//
// It wraps the update into transaction and can perform partial update by providing list of updatable columns
func (s Store) PartialUpdate{{ pubIdent $.Types.Singular }}(ctx context.Context, onlyColumns []string, rr ... *{{ $.Types.GoType }}) (err error) {
// {{ toggleExport .Update.Export "Partial" $.Types.Singular "Update" }} updates one or more existing rows in {{ $.RDBMS.Table }}
func (s Store) {{ toggleExport .Update.Export "Partial" $.Types.Singular "Update" }}(ctx context.Context{{ template "extraArgsDef" . }}, onlyColumns []string, rr ... *{{ $.Types.GoType }}) (err error) {
for _, res := range rr {
err = s.ExecUpdate{{ pubIdent $.Types.Plural }}(
err = s.check{{ export $.Types.Singular }}Constraints(ctx {{ template "extraArgsCall" $ }}, res)
if err != nil {
return err
}
// err = s.{{ unexport $.Types.Singular }}Hook(ctx, TriggerBefore{{ export $.Types.Singular }}Update{{ template "extraArgsCall" . }}, res)
// if err != nil {
// return err
// }
err = s.execUpdate{{ export $.Types.Plural }}(
ctx,
{{ template "filterByPrimaryKeys" $.Fields }},
s.internal{{ pubIdent $.Types.Singular }}Encoder(res).Skip(
{{- range $field := $.Fields -}}
{{- if $field.IsPrimaryKey -}}
{{ printf "%q" $field.Column }},
{{- end -}}
{{ template "filterByPrimaryKeys" $.Fields.PrimaryKeyFields }},
s.internal{{ export $.Types.Singular }}Encoder(res).Skip(
{{- range $field := $.Fields.PrimaryKeyFields -}}
{{ printf "%q" $field.Column }},
{{- end -}}
).Only(onlyColumns...))
if err != nil {
@@ -337,11 +400,42 @@ func (s Store) PartialUpdate{{ pubIdent $.Types.Singular }}(ctx context.Context,
return
}
{{ end }}
// Remove{{ pubIdent $.Types.Singular }} removes one or more rows from {{ $.RDBMS.Table }} table
func (s Store) Remove{{ pubIdent $.Types.Singular }}(ctx context.Context, rr ... *{{ $.Types.GoType }}) (err error) {
{{ if .Upsert.Enable }}
// {{ toggleExport .Delete.Export "Upsert" $.Types.Singular }} updates one or more existing rows in {{ $.RDBMS.Table }}
func (s Store) {{ toggleExport .Delete.Export "Upsert" $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $.Types.GoType }}) (err error) {
for _, res := range rr {
err = ExecuteSqlizer(ctx, s.DB(), s.Delete(s.{{ $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where({{ template "filterByPrimaryKeys" $.Fields }},))
err = s.check{{ export $.Types.Singular }}Constraints(ctx {{ template "extraArgsCall" $ }}, res)
if err != nil {
return err
}
// err = s.{{ unexport $.Types.Singular }}Hook(ctx, TriggerBefore{{ export $.Types.Singular }}Upsert{{ template "extraArgsCall" . }}, res)
// if err != nil {
// return err
// }
err = s.config.ErrorHandler(s.execUpsert{{ export $.Types.Plural }}(ctx, s.internal{{ export $.Types.Singular }}Encoder(res)))
if err != nil {
return err
}
}
return nil
}
{{ end }}
{{ if .Delete.Enable }}
// {{ toggleExport .Delete.Export "Delete" $.Types.Singular }} Deletes one or more rows from {{ $.RDBMS.Table }} table
func (s Store) {{ toggleExport .Delete.Export "Delete" $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, rr ... *{{ $.Types.GoType }}) (err error) {
for _, res := range rr {
// err = s.{{ unexport $.Types.Singular }}Hook(ctx, TriggerBefore{{ export $.Types.Singular }}Delete{{ template "extraArgsCall" . }}, res)
// if err != nil {
// return err
// }
err = s.execDelete{{ export $.Types.Plural }}(ctx,{{ template "filterByPrimaryKeys" $.Fields.PrimaryKeyFields }})
if err != nil {
return s.config.ErrorHandler(err)
}
@@ -350,42 +444,90 @@ func (s Store) Remove{{ pubIdent $.Types.Singular }}(ctx context.Context, rr ...
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 }})
}
{{ end }}
// Remove{{ pubIdent $.Types.Singular }}By{{ template "primaryKeySuffix" $.Fields }} removes row from the {{ $.RDBMS.Table }} table
func (s Store) Remove{{ pubIdent $.Types.Singular }}By{{ template "primaryKeySuffix" $.Fields }}(ctx context.Context {{ template "primaryKeyArgs" $.Fields }}) error {
return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Delete(s.{{ $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where({{ template "filterByPrimaryKeysWithArgs" $.Fields }},)))
// {{ toggleExport .Truncate.Export "Truncate" $.Types.Plural }} Deletes all rows from the {{ $.RDBMS.Table }} table
func (s Store) {{ toggleExport .Truncate.Export "Truncate" $.Types.Plural }}(ctx context.Context{{ template "extraArgsDef" . }}) error {
return s.config.ErrorHandler(s.Truncate(ctx, s.{{ unexport $.Types.Singular }}Table()))
}
// Truncate{{ pubIdent $.Types.Plural }} removes all rows from the {{ $.RDBMS.Table }} table
func (s Store) Truncate{{ pubIdent $.Types.Plural }}(ctx context.Context) error {
return s.config.ErrorHandler(Truncate(ctx, s.DB(), s.{{ $.Types.Singular }}Table()))
}
// ExecUpdate{{ pubIdent $.Types.Plural }} updates all matched (by cnd) rows in {{ $.RDBMS.Table }} with given data
func (s Store) ExecUpdate{{ pubIdent $.Types.Plural }}(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error {
return s.config.ErrorHandler(ExecuteSqlizer(ctx, s.DB(), s.Update(s.{{ $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where(cnd).SetMap(set)))
}
// {{ $.Types.Singular }}Lookup prepares {{ $.Types.Singular }} query and executes it,
// execLookup{{ $.Types.Singular }} prepares {{ $.Types.Singular }} query and executes it,
// returning {{ $.Types.GoType }} (or error)
func (s Store) {{ $.Types.Singular }}Lookup(ctx context.Context, cnd squirrel.Sqlizer) (*{{ $.Types.GoType }}, error) {
return s.internal{{ $.Types.Singular }}RowScanner(s.QueryRow(ctx, s.Query{{ pubIdent $.Types.Plural }}().Where(cnd)))
}
func (s Store) execLookup{{ $.Types.Singular }}(ctx context.Context{{ template "extraArgsDef" . }}, cnd squirrel.Sqlizer) (res *{{ $.Types.GoType }}, err error) {
var (
row rowScanner
)
func (s Store) internal{{ $.Types.Singular }}RowScanner(row rowScanner, err error) (*{{ $.Types.GoType }}, error) {
row, err = s.QueryRow(ctx, s.{{ unexport $.Types.Plural }}SelectBuilder().Where(cnd))
if err != nil {
return nil, err
return
}
var res = &{{ $.Types.GoType }}{}
if _, has := s.config.RowScanners[{{ printf "%q" (unpubIdent $.Types.Singular) }}]; has {
scanner := s.config.RowScanners[{{ printf "%q" (unpubIdent $.Types.Singular) }}].(func(rowScanner, *{{ $.Types.GoType }}) error)
err = scanner(row, res)
res, err = s.internal{{ $.Types.Singular }}RowScanner({{ template "extraArgsCallFirst" . }}row)
if err != nil {
return
}
return res, nil
}
{{ if .Create.Enable }}
// execCreate{{ export $.Types.Plural }} updates all matched (by cnd) rows in {{ $.RDBMS.Table }} with given data
func (s Store) execCreate{{ export $.Types.Plural }}(ctx context.Context, payload store.Payload) error {
return s.config.ErrorHandler(s.Exec(ctx, s.InsertBuilder(s.{{ unexport $.Types.Singular }}Table()).SetMap(payload)))
}
{{ end }}
{{ if .Update.Enable }}
// execUpdate{{ export $.Types.Plural }} updates all matched (by cnd) rows in {{ $.RDBMS.Table }} with given data
func (s Store) execUpdate{{ export $.Types.Plural }}(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error {
return s.config.ErrorHandler(s.Exec(ctx, s.UpdateBuilder(s.{{ unexport $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where(cnd).SetMap(set)))
}
{{ end }}
{{ if .Upsert.Enable }}
// execUpsert{{ export $.Types.Plural }} inserts new or updates matching (by-primary-key) rows in {{ $.RDBMS.Table }} with given data
func (s Store) execUpsert{{ export $.Types.Plural }}(ctx context.Context, set store.Payload) error {
upsert, err := s.config.UpsertBuilder(
s.config,
s.{{ unexport $.Types.Singular }}Table(),
set,
{{ range $.Fields }}
{{- if or .IsPrimaryKey -}}
{{ printf "%q" .Column }},
{{ end }}
{{- end }}
)
if err != nil {
return err
}
return s.config.ErrorHandler(s.Exec(ctx, upsert))
}
{{ end }}
{{ if .Delete.Enable }}
// execDelete{{ export $.Types.Plural }} Deletes all matched (by cnd) rows in {{ $.RDBMS.Table }} with given data
func (s Store) execDelete{{ export $.Types.Plural }}(ctx context.Context, cnd squirrel.Sqlizer) error {
return s.config.ErrorHandler(s.Exec(ctx, s.DeleteBuilder(s.{{ unexport $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }})).Where(cnd)))
}
{{ end }}
func (s Store) internal{{ $.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 {
scanner := s.config.RowScanners[{{ printf "%q" (unexport $.Types.Singular) }}].(func({{ template "extraArgsDefFirst" . }}_ rowScanner, _ *{{ $.Types.GoType }}) error)
err = scanner({{ template "extraArgsCallFirst" . }}row, res)
} else {
{{- if .RDBMS.CustomRowScanner }}
err = s.scan{{ $.Types.Singular }}Row(row, res)
err = s.scan{{ $.Types.Singular }}Row({{ template "extraArgsCallFirst" . }}row, res)
{{- else }}
err = row.Scan(
{{- range $.Fields }}
@@ -406,13 +548,13 @@ func (s Store) internal{{ $.Types.Singular }}RowScanner(row rowScanner, err erro
}
}
// Query{{ pubIdent $.Types.Plural }} returns squirrel.SelectBuilder with set table and all columns
func (s Store) Query{{ pubIdent $.Types.Plural }}() squirrel.SelectBuilder {
return s.Select(s.{{ $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }}), s.{{ $.Types.Singular }}Columns({{ printf "%q" $.RDBMS.Alias }})...)
// Query{{ export $.Types.Plural }} returns squirrel.SelectBuilder with set table and all columns
func (s Store) {{ unexport $.Types.Plural }}SelectBuilder() squirrel.SelectBuilder {
return s.SelectBuilder(s.{{ unexport $.Types.Singular }}Table({{ printf "%q" .RDBMS.Alias }}), s.{{ unexport $.Types.Singular }}Columns({{ printf "%q" $.RDBMS.Alias }})...)
}
// {{ $.Types.Singular }}Table name of the db table
func (Store) {{ $.Types.Singular }}Table(aa ... string) string {
// {{ unexport $.Types.Singular }}Table name of the db table
func (Store) {{ unexport $.Types.Singular }}Table(aa ... string) string {
var alias string
if len(aa) > 0 {
alias = " AS " + aa[0]
@@ -424,7 +566,7 @@ func (Store) {{ $.Types.Singular }}Table(aa ... string) string {
// {{ $.Types.Singular }}Columns returns all defined table columns
//
// With optional string arg, all columns are returned aliased
func (Store) {{ $.Types.Singular }}Columns(aa ... string) []string {
func (Store) {{ unexport $.Types.Singular }}Columns(aa ... string) []string {
var alias string
if len(aa) > 0 {
alias = aa[0] + "."
@@ -439,7 +581,7 @@ func (Store) {{ $.Types.Singular }}Columns(aa ... string) []string {
// {{ printf "%v" .Search }}
{{ if not $.Search.DisableSorting }}
{{ if $.Search.EnableSorting }}
// sortable{{ $.Types.Singular }}Columns returns all {{ $.Types.Singular }} columns flagged as sortable
//
// With optional string arg, all columns are returned aliased
@@ -454,13 +596,13 @@ func (Store) sortable{{ $.Types.Singular }}Columns() []string {
}
{{ end }}
// internal{{ pubIdent $.Types.Singular }}Encoder encodes fields from {{ $.Types.GoType }} to store.Payload (map)
// internal{{ export $.Types.Singular }}Encoder encodes fields from {{ $.Types.GoType }} to store.Payload (map)
//
// Encoding is done by using generic approach or by calling encode{{ pubIdent $.Types.Singular }}
// Encoding is done by using generic approach or by calling encode{{ export $.Types.Singular }}
// func when rdbms.customEncoder=true
func (s Store) internal{{ pubIdent $.Types.Singular }}Encoder(res *{{ $.Types.GoType }}) store.Payload {
func (s Store) internal{{ export $.Types.Singular }}Encoder(res *{{ $.Types.GoType }}) store.Payload {
{{- if .RDBMS.CustomEncoder }}
return s.encode{{ pubIdent $.Types.Singular }}(res)
return s.encode{{ export $.Types.Singular }}(res)
{{- else }}
return store.Payload{
{{- range $.Fields }}
@@ -470,10 +612,10 @@ func (s Store) internal{{ pubIdent $.Types.Singular }}Encoder(res *{{ $.Types.Go
{{- end }}
}
{{ if not $.Search.DisablePaging }}
func (s Store) collect{{ pubIdent $.Types.Singular }}CursorValues(res *{{ $.Types.GoType }}, cc ...string) *store.PagingCursor {
{{ if $.Search.EnablePaging }}
func (s Store) collect{{ export $.Types.Singular }}CursorValues(res *{{ $.Types.GoType }}, cc ...string) *filter.PagingCursor {
var (
cursor = &store.PagingCursor{}
cursor = &filter.PagingCursor{}
hasUnique bool
@@ -507,26 +649,46 @@ func (s Store) collect{{ pubIdent $.Types.Singular }}CursorValues(res *{{ $.Type
}
{{ end }}
func (s *Store) check{{ export $.Types.Singular }}Constraints(ctx context.Context{{ template "extraArgsDef" $ }}, res *{{ $.Types.GoType }}) error {
{{- range $lookup := $.Lookups }}
{{ if $lookup.UniqueConstraintCheck }}
{
ex, err := s.{{ toggleExport $lookup.Export "Lookup" $.Types.Singular "By" $lookup.Suffix }}(ctx{{ template "extraArgsCall" $ }}{{- range $field := $lookup.Fields }}, res.{{ $field }} {{- end }})
if err == nil && ex != nil && ex.ID != res.ID {
return store.ErrNotUnique
} else if !errors.Is(err, store.ErrNotFound) {
return err
}
}
{{ end }}
{{ end }}
return nil
}
// func (s *Store) {{ unexport $.Types.Singular }}Hook(ctx context.Context, key triggerKey{{ template "extraArgsDef" . }}, res *{{ $.Types.GoType }}) error {
// if fn, has := s.config.TriggerHandlers[key]; has {
// return fn.(func (ctx context.Context, s *Store{{ template "extraArgsDef" . }}, res *{{ $.Types.GoType }}) error)(ctx, s{{ template "extraArgsCall" . }}, res)
// }
//
// return nil
// }
{{/* ************************************************************ */}}
{{- define "filterByPrimaryKeys" -}}
squirrel.Eq{
{{- range $field := . -}}
{{- if $field.IsPrimaryKey -}}
s.preprocessColumn({{ printf "%q" $field.AliasedColumn }}, {{ printf "%q" $field.LookupFilterPreprocess }}): s.preprocessValue(res.{{ $field.Field }}, {{ printf "%q" $field.LookupFilterPreprocess }}),
{{ end }}
{{- end -}}
{{ range $field := . -}}
s.preprocessColumn({{ printf "%q" $field.AliasedColumn }}, {{ printf "%q" $field.LookupFilterPreprocess }}): s.preprocessValue(res.{{ $field.Field }}, {{ printf "%q" $field.LookupFilterPreprocess }}),
{{- end }}
}
{{- end -}}
{{- define "filterByPrimaryKeysWithArgs" -}}
squirrel.Eq{
{{- range $field := . }}
{{- if $field.IsPrimaryKey -}}
s.preprocessColumn({{ printf "%q" $field.AliasedColumn }}, {{ printf "%q" $field.LookupFilterPreprocess }}): s.preprocessValue({{ $field.Arg }}, {{ printf "%q" $field.LookupFilterPreprocess }}),
{{ end }}
{{ end -}}
{{ range $field := . -}}
s.preprocessColumn({{ printf "%q" $field.AliasedColumn }}, {{ printf "%q" $field.LookupFilterPreprocess }}): s.preprocessValue({{ $field.Arg }}, {{ printf "%q" $field.LookupFilterPreprocess }}),
{{ end }}
}
{{- end -}}
+17 -11
View File
@@ -2,23 +2,29 @@ package tests
// This file is auto-generated.
//
// Template: pkg/codegen/assets/store_test_all.gen.go
// Definitions:
{{ range . }}
{{- if .Exported -}}
// - {{ .Source }}
{{ end -}}{{- end }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
import (
"context"
"github.com/stretchr/testify/require"
"github.com/cortezaproject/corteza-server/store"
"testing"
)
func testAllGenerated(t *testing.T, all interface{}) {
{{ range . }}
// Run generated tests for {{ .Types.Base }}
t.Run({{ printf "%q" .Types.Base }}, func(t *testing.T) {
var s = all.({{ unpubIdent .Types.Plural }}Store)
require.New(t).NoError(s.Truncate{{ pubIdent .Types.Plural }}(context.Background()))
test{{ pubIdent .Types.Base }}(t, s)
})
{{ end }}
func testAllGenerated(t *testing.T, s store.Storable) {
{{- range . }}
{{- if .Exported }}
// Run generated tests for {{ .Types.Base }}
t.Run({{ printf "%q" .Types.Base }}, func(t *testing.T) {
test{{ export .Types.Base }}(t, s)
})
{{ end -}}
{{ end -}}
}
+190 -59
View File
@@ -1,83 +1,214 @@
package codegen
import (
"flag"
"fmt"
"github.com/Masterminds/sprig"
"github.com/cortezaproject/corteza-server/pkg/cli"
"github.com/fsnotify/fsnotify"
"os"
"path/filepath"
"strings"
"text/template"
)
type (
definitions struct {
App string
Rest []*restDef
Actions []*actionsDef
Events []*eventsDef
Types []*typesDef
Store []*storeDef
}
)
func Proc() {
var (
err error
def = &definitions{}
watchChanges bool
beVerbose bool
tpls = template.New("").Funcs(map[string]interface{}{
"camelCase": camelCase,
"pubIdent": pubIdent,
"unpubIdent": unpubIdent,
"toLower": strings.ToLower,
"cc2underscore": cc2underscore,
"normalizeImport": normalizeImport,
"comment": func(text string, skip1st bool) string {
ll := strings.Split(text, "\n")
s := 0
out := ""
if skip1st {
s = 1
out = ll[0] + "\n"
}
fileList []string
watcher *fsnotify.Watcher
for ; s < len(ll); s++ {
out += "// " + ll[s] + "\n"
}
templatesPath = filepath.Join("pkg", "codegen", "assets", "*.tpl")
templatesSrc []string
return out
},
})
actionSrcPath = filepath.Join("*", "service", "*_actions.yaml")
actionSrc []string
actionDefs []*actionsDef
eventSrcPath = filepath.Join("*", "service", "event", "events.yaml")
eventSrc []string
eventDefs []*eventsDef
typeSrcPath = filepath.Join("*", "*", "types.yaml")
typeSrc []string
typeDefs []*typesDef
restSrcPath = filepath.Join("*", "rest.yaml")
restSrc []string
restDefs []*restDef
storeSrcPath = filepath.Join("store", "*.yaml")
storeSrc []string
storeDefs []*storeDef
tpls *template.Template
tplBase = template.New("").
Funcs(map[string]interface{}{
"camelCase": camelCase,
"export": export,
"unexport": unexport,
"toggleExport": toggleExport,
"toLower": strings.ToLower,
"cc2underscore": cc2underscore,
"normalizeImport": normalizeImport,
"comment": func(text string, skip1st bool) string {
ll := strings.Split(text, "\n")
s := 0
out := ""
if skip1st {
s = 1
out = ll[0] + "\n"
}
for ; s < len(ll); s++ {
out += "// " + ll[s] + "\n"
}
return out
},
}).
Funcs(sprig.TxtFuncMap())
output = func(format string, aa ...interface{}) {
if beVerbose {
fmt.Fprintf(os.Stdout, format, aa...)
}
}
outputErr = func(err error, format string, aa ...interface{}) bool {
if err != nil {
fmt.Fprintf(os.Stdout, format, aa...)
fmt.Fprintf(os.Stdout, "%v\n", err)
return true
}
return false
}
)
tpls = template.Must(tpls.ParseGlob("pkg/codegen/assets/*.tpl"))
flag.BoolVar(&watchChanges, "w", false, "regenerate code on template or definition change")
flag.BoolVar(&beVerbose, "v", false, "output loaded definitions, templates and outputs")
flag.Parse()
if def.Actions, err = procActions(); err != nil {
cli.HandleError(err)
} else {
cli.HandleError(genActions(tpls, def.Actions))
}
defer func() {
if watcher != nil {
watcher.Close()
}
}()
if def.Events, err = procEvents(); err != nil {
cli.HandleError(err)
} else {
cli.HandleError(genEvents(tpls, def.Events))
}
for {
fileList = make([]string, 0, 100)
if def.Types, err = procTypes(); err != nil {
cli.HandleError(err)
} else {
cli.HandleError(genTypes(tpls, def.Types))
}
templatesSrc = glob(templatesPath)
output("loaded %d templates from %s\n", len(templatesSrc), templatesPath)
if def.Rest, err = procRest(); err != nil {
cli.HandleError(err)
} else {
cli.HandleError(genRest(tpls, def.Rest))
}
actionSrc = glob(actionSrcPath)
output("loaded %d action definitions from %s\n", len(actionSrc), actionSrcPath)
if def.Store, err = procStore(); err != nil {
cli.HandleError(err)
} else {
cli.HandleError(genStore(tpls, def.Store))
eventSrc = glob(eventSrcPath)
output("loaded %d event definitions from %s\n", len(eventSrc), eventSrcPath)
typeSrc = glob(typeSrcPath)
output("loaded %d type definitions from %s\n", len(typeSrc), typeSrcPath)
restSrc = glob(restSrcPath)
output("loaded %d rest definitions from %s\n", len(restSrc), restSrcPath)
storeSrc = glob(storeSrcPath)
output("loaded %d store definitions from %s\n", len(storeSrc), storeSrcPath)
if watchChanges {
if watcher != nil {
watcher.Close()
}
watcher, err = fsnotify.NewWatcher()
cli.HandleError(err)
fileList = append(fileList, templatesSrc...)
fileList = append(fileList, actionSrc...)
fileList = append(fileList, eventSrc...)
fileList = append(fileList, typeSrc...)
fileList = append(fileList, restSrc...)
fileList = append(fileList, storeSrc...)
for _, d := range fileList {
cli.HandleError(watcher.Add(d))
}
}
func() {
tpls, err = tplBase.ParseFiles(templatesSrc...)
if outputErr(err, "could not parse templates:\n") {
return
}
if actionDefs, err = procActions(actionSrc...); err == nil {
err = genActions(tpls, actionDefs...)
}
if outputErr(err, "failed to process actions:\n") {
return
}
if eventDefs, err = procEvents(eventSrc...); err == nil {
err = genEvents(tpls, eventDefs...)
}
if outputErr(err, "failed to process events:\n") {
return
}
if typeDefs, err = procTypes(typeSrc...); err == nil {
err = genTypes(tpls, typeDefs...)
}
if outputErr(err, "failed to process types:\n") {
return
}
if restDefs, err = procRest(restSrc...); err == nil {
err = genRest(tpls, restDefs...)
}
if outputErr(err, "failed to process rest:\n") {
return
}
if storeDefs, err = procStore(storeSrc...); err == nil {
err = genStore(tpls, storeDefs...)
}
if outputErr(err, "failed to process store:\n") {
return
}
}()
if !watchChanges {
break
}
// @todo fix this (without causing too many "too-many-files" issues :)
output("waiting for changes (if you add a new file, restart codegen manually)\n")
select {
case <-watcher.Events:
case err = <-watcher.Errors:
cli.HandleError(err)
}
}
}
func glob(path string) []string {
src, err := filepath.Glob(path)
if err != nil {
cli.HandleError(fmt.Errorf("failed to glob %q: %w", path, err))
}
return src
}
+3 -12
View File
@@ -6,7 +6,6 @@ import (
"gopkg.in/yaml.v2"
"os"
"path"
"path/filepath"
"strings"
"text/template"
)
@@ -59,22 +58,14 @@ type (
}
)
func procEvents() ([]*eventsDef, error) {
func procEvents(mm ...string) (dd []*eventsDef, err error) {
// <app>/service/event/events.yaml
const (
importTypePathTpl = "github.com/cortezaproject/corteza-server/%s/types"
importAuthPath = "github.com/cortezaproject/corteza-server/pkg/auth"
)
var (
dd = make([]*eventsDef, 0)
)
mm, err := filepath.Glob(filepath.Join("*", "service", "event", "events.yaml"))
if err != nil {
return nil, fmt.Errorf("glob failed: %w", err)
}
dd = make([]*eventsDef, 0)
for _, m := range mm {
f, err := os.Open(m)
if err != nil {
@@ -156,7 +147,7 @@ func procEvents() ([]*eventsDef, error) {
return dd, nil
}
func genEvents(tpl *template.Template, dd []*eventsDef) (err error) {
func genEvents(tpl *template.Template, dd ...*eventsDef) (err error) {
var (
// Will only be generated if file does not exist previously
tplEvents = tpl.Lookup("events.go.tpl")
+4 -14
View File
@@ -5,7 +5,6 @@ import (
"gopkg.in/yaml.v2"
"os"
"path"
"path/filepath"
"strings"
"text/template"
)
@@ -59,17 +58,8 @@ type (
}
)
func procRest() ([]*restDef, error) {
// <app>/rest.yaml
var (
dd = make([]*restDef, 0)
)
mm, err := filepath.Glob(filepath.Join("*", "rest.yaml"))
if err != nil {
return nil, fmt.Errorf("glob failed: %w", err)
}
func procRest(mm ...string) (dd []*restDef, err error) {
dd = make([]*restDef, 0)
for _, m := range mm {
err = func() error {
@@ -109,7 +99,7 @@ func procRest() ([]*restDef, error) {
return dd, nil
}
func genRest(tpl *template.Template, dd []*restDef) (err error) {
func genRest(tpl *template.Template, dd ...*restDef) (err error) {
var (
// Will only be generated if file does not exist previously
tplHandler = tpl.Lookup("rest_handler.go.tpl")
@@ -212,7 +202,7 @@ func (d *restEndpointParamDef) Parser(arg string) string {
case "sqlxTypes.JSONText":
return fmt.Sprintf("payload.ParseJSONTextWithErr(%s)", arg)
case "int", "uint", "uint64", "int64", "float", "float64", "bool":
return fmt.Sprintf("payload.Parse%s(%s), nil", pubIdent(d.Type), arg)
return fmt.Sprintf("payload.Parse%s(%s), nil", export(d.Type), arg)
case "string", "[]string":
return fmt.Sprintf("%s, nil", arg)
default:
+200 -143
View File
@@ -6,7 +6,7 @@ import (
"gopkg.in/yaml.v2"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"text/template"
)
@@ -21,9 +21,6 @@ type (
Import []string `yaml:"import"`
// List of all locations where we should export the store interface to
Interface []string `yaml:"interface"`
// Tries to autogenerate type by changing it to singular and prefixing it with *types.
Types storeTypeDef `yaml:"types"`
@@ -32,12 +29,21 @@ type (
// For now, this set does not variate between different implementation
// To support that, a (sub)set will need to be defined under each implementation (rdbms, mysql, mongo...)
//
Fields storeTypeFieldSetDef `yaml:"fields"`
Lookups []*storeTypeLookups `yaml:"lookups"`
PartialUpdates []*storeTypePartialUpdate `yaml:"partialUpdates"`
RDBMS *storeTypeRdbmsDef `yaml:"rdbms"`
Fields storeTypeFieldSetDef `yaml:"fields"`
RDBMS storeTypeRdbmsDef `yaml:"rdbms"`
Functions []*storeTypeFunctionsDef `yaml:"functions"`
Arguments []*storeTypeExtraArgumentDef `yaml:"arguments"`
Search storeTypeSearchDef `yaml:"search"`
Search storeTypeSearchDef `yaml:"search"`
Lookups []*storeTypeLookups `yaml:"lookups"`
Create storeTypeCreateDef `yaml:"create"`
Update storeTypeUpdateDef `yaml:"update"`
Upsert storeTypeUpsertDef `yaml:"upsert"`
Delete storeTypeDeleteDef `yaml:"Delete"`
Truncate storeTypeTruncateDef `yaml:"truncate"`
// Make interfaces and store functions
Publish bool `yaml:"publish"`
}
storeTypeDef struct {
@@ -80,6 +86,17 @@ type (
CustomEncoder bool `yaml:"customEncoder"`
}
storeTypeFunctionsDef struct {
Name string `yaml:"name"`
Arguments []storeTypeExtraArgumentDef `yaml:"arguments"`
Return []string `yaml:"return"`
}
storeTypeExtraArgumentDef struct {
Name string
Type string
}
storeTypeFieldSetDef []*storeTypeFieldDef
storeTypeFieldDef struct {
@@ -98,7 +115,7 @@ type (
// If field name ends with ID (<base>ID), it converts that to rel_<snake-cased-base>
Column string `yaml:"column"`
// If field is flagged as PK it is used in update & remove conditions
// 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.
IsPrimaryKey bool `yaml:"isPrimaryKey"`
@@ -128,36 +145,99 @@ type (
storeTypeLookups struct {
// LookupBy<suffix>
// When not explicitly defined, it names of all fields
Suffix string `yaml:"suffix"`
Description string `yaml:"description"`
Fields []string `yaml:"fields"`
Filter map[string]string `yaml:"filter"`
fields storeTypeFieldSetDef
}
storeTypePartialUpdate struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Set map[string]string `yaml:"set"`
XX_Args []string `yaml:"args"`
fields storeTypeFieldSetDef
Export bool `yaml:"export"`
Suffix string `yaml:"suffix"`
Description string `yaml:"description"`
UniqueConstraintCheck bool `yaml:"uniqueConstraintCheck"`
Fields []string `yaml:"fields"`
Filter map[string]string `yaml:"filter"`
fields storeTypeFieldSetDef
}
storeTypeSearchDef struct {
Disable bool `yaml:"disable"`
DisablePaging bool `yaml:"disablePaging"`
DisableSorting bool `yaml:"disableSorting"`
DisableFilterCheckFn bool `yaml:"disableFilterCheckFunction"`
Enable bool `yaml:"enable"`
Export bool `yaml:"export"`
EnablePaging bool `yaml:"enablePaging"`
EnableSorting bool `yaml:"enableSorting"`
EnableFilterCheckFn bool `yaml:"enableFilterCheckFunction"`
}
storeTypeCreateDef struct {
Enable bool `yaml:"enable"`
Export bool `yaml:"export"`
}
storeTypeUpdateDef struct {
Enable bool `yaml:"enable"`
Export bool `yaml:"export"`
}
storeTypeUpsertDef struct {
Enable bool `yaml:"enable"`
Export bool `yaml:"export"`
}
storeTypeDeleteDef struct {
Enable bool `yaml:"enable"`
Export bool `yaml:"export"`
}
storeTypeTruncateDef struct {
Export bool `yaml:"export"`
}
)
var (
outputDir string = "store"
outputDir string = "store"
spaceSplit = regexp.MustCompile(`\s+`)
)
func procStore() ([]*storeDef, error) {
func procStore(mm ...string) ([]*storeDef, error) {
procDef := func(m string) (*storeDef, error) {
def := &storeDef{Source: m}
// initialize & set default
def := &storeDef{
Source: m,
RDBMS: storeTypeRdbmsDef{
CustomRowScanner: false,
CustomFilterConverter: false,
CustomEncoder: false,
},
Search: storeTypeSearchDef{
Enable: true,
Export: true,
EnablePaging: true,
EnableSorting: true,
EnableFilterCheckFn: true,
},
Create: storeTypeCreateDef{
Enable: true,
Export: true,
},
Update: storeTypeUpdateDef{
Enable: true,
Export: true,
},
Upsert: storeTypeUpsertDef{
Enable: true,
Export: true,
},
Delete: storeTypeDeleteDef{
Enable: true,
Export: true,
},
Truncate: storeTypeTruncateDef{
Export: true,
},
Publish: true,
}
f, err := os.Open(m)
if err != nil {
return nil, fmt.Errorf("%s read failed: %w", m, err)
@@ -172,18 +252,35 @@ func procStore() ([]*storeDef, error) {
def.Filename = path.Base(m)
def.Filename = def.Filename[:len(def.Filename)-5]
if def.Search.Disable {
if !def.Search.Enable {
// No use for any of that if search is disabled...
def.Search.DisablePaging = true
def.Search.DisableSorting = true
def.Search.DisableFilterCheckFn = true
def.Search.EnablePaging = false
def.Search.EnableSorting = false
def.Search.EnableFilterCheckFn = false
}
// Always generate interface in store/tests and store/bulk
def.Interface = append(def.Interface, "store/tests", "store/bulk")
if !def.Create.Enable {
// No use for any of that if operation is disabled...
def.Create.Export = false
}
if !def.Update.Enable {
// No use for any of that if operation is disabled...
def.Update.Export = false
}
if !def.Upsert.Enable {
// No use for any of that if operation is disabled...
def.Upsert.Export = false
}
if !def.Delete.Enable {
// No use for any of that if operation is disabled...
def.Delete.Export = false
}
if def.Types.Base == "" {
def.Types.Base = pubIdent(strings.Split(def.Filename, "_")...)
def.Types.Base = export(strings.Split(def.Filename, "_")...)
}
if def.Types.Singular == "" {
@@ -198,7 +295,7 @@ func procStore() ([]*storeDef, error) {
}
if def.Types.GoType == "" {
def.Types.GoType = def.Types.Package + "." + pubIdent(def.Types.Singular)
def.Types.GoType = def.Types.Package + "." + export(def.Types.Singular)
}
if def.Types.GoSetType == "" {
@@ -213,75 +310,40 @@ func procStore() ([]*storeDef, error) {
def.RDBMS.Alias = def.Types.Base[0:1]
}
var hasPrimaryKey = false
for _, f := range def.Fields {
if f.IsPrimaryKey {
hasPrimaryKey = true
break
}
}
for _, f := range def.Fields {
if !hasPrimaryKey && f.Field == "ID" {
f.IsPrimaryKey = true
f.IsSortable = true
var hasPrimaryKey = def.Fields.HasPrimaryKey()
for _, field := range def.Fields {
if !hasPrimaryKey && field.Field == "ID" {
field.IsPrimaryKey = true
field.IsSortable = true
}
// copy alias from global spec so we can
// generate aliased columsn
f.alias = def.RDBMS.Alias
field.alias = def.RDBMS.Alias
if f.Column == "" {
if field.Column == "" {
switch {
case f.Field != "ID" && strings.HasSuffix(f.Field, "ID"):
f.Column = "rel_" + cc2underscore(f.Field[:len(f.Field)-2])
case field.Field != "ID" && strings.HasSuffix(field.Field, "ID"):
field.Column = "rel_" + cc2underscore(field.Field[:len(field.Field)-2])
default:
f.Column = cc2underscore(f.Field)
field.Column = cc2underscore(field.Field)
}
}
switch {
case f.Type != "":
case field.Type != "":
// type set
case strings.HasSuffix(f.Field, "ID") || strings.HasSuffix(f.Field, "By"):
f.Type = "uint64"
case f.Field == "CreatedAt":
f.Type = "time.Time"
case strings.HasSuffix(f.Field, "At"):
f.Type = "uint64"
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"
default:
f.Type = "string"
field.Type = "string"
}
}
if len(def.PartialUpdates) > 0 && def.Fields.Find("ID") == nil {
return nil, fmt.Errorf("partial updates without ID field are not supported")
}
// Checking if filters exist in the fields
for i, p := range def.PartialUpdates {
// Check and normalize set
for f, v := range p.Set {
if def.Fields.Find(f) == nil {
return nil, fmt.Errorf("undefined field %q used in partialUpdate #%d set", f, i)
}
if v == "" {
// Set empty strings to nil
p.Set[f] = "nil"
}
}
for _, a := range p.XX_Args {
if def.Fields.Find(a) == nil {
return nil, fmt.Errorf("undefined field %q used in partialUpdate #%d arguments", a, i)
}
}
p.fields = def.Fields
}
for i, l := range def.Lookups {
if len(l.Fields) == 0 {
return nil, fmt.Errorf("define at least one lookup field in lookup #%d", i)
@@ -318,12 +380,7 @@ func procStore() ([]*storeDef, error) {
return def, nil
}
mm, err := filepath.Glob(filepath.Join(outputDir, "*.yaml"))
if err != nil {
return nil, fmt.Errorf("failed to glob: %w", err)
}
dd := []*storeDef{}
dd := make([]*storeDef, 0, len(mm))
for _, m := range mm {
def, err := procDef(m)
if err != nil {
@@ -339,11 +396,11 @@ func procStore() ([]*storeDef, error) {
// genStore generates all store related code, functions, interfaces...
//
// Templates can be found under assets/store*.tpl
func genStore(tpl *template.Template, dd []*storeDef) (err error) {
func genStore(tpl *template.Template, dd ...*storeDef) (err error) {
var (
// general interfaces
tplInterfacesJoined = tpl.Lookup("store_interfaces_joined.gen.go.tpl")
tplInterfaces = tpl.Lookup("store_interfaces.gen.go.tpl")
tplBase = tpl.Lookup("store_base.gen.go.tpl")
// general tests
tplTestAll = tpl.Lookup("store_test_all.gen.go.tpl")
@@ -357,11 +414,7 @@ func genStore(tpl *template.Template, dd []*storeDef) (err error) {
// @todo mongodb
// @todo elasticsearch
// bulk specific
tplBulk = tpl.Lookup("store_bulk.gen.go.tpl")
dst string
joinedInterface = make(map[string][]*storeDef)
dst string
)
// Output all test setup into a single file
@@ -377,40 +430,15 @@ func genStore(tpl *template.Template, dd []*storeDef) (err error) {
return
}
dst = path.Join(outputDir, "bulk", d.Filename+".gen.go")
if err = goTemplate(dst, tplBulk, d); err != nil {
return
}
// Collect and map all interface output locations
// and their corresponding definitions
for _, dst = range d.Interface {
if err = genStoreInterfaces(tplInterfaces, path.Join(dst, "store_interface_"+d.Filename+".gen.go"), path.Base(dst), d); err != nil {
return
}
if joinedInterface[dst] == nil {
joinedInterface[dst] = make([]*storeDef, 0, len(dd))
}
joinedInterface[dst] = append(joinedInterface[dst], d)
}
}
// Add joined interfaces for each interface destination
for dst, dd := range joinedInterface {
if err = genStoreInterfacesJoined(tplInterfacesJoined, path.Join(dst, "store_interface.gen.go"), path.Base(dst), dd); err != nil {
dst = path.Join(outputDir, d.Filename+".gen.go")
if err = goTemplate(dst, tplBase, d); err != nil {
return
}
}
//for _, d := range dd {
// d.Package = "tests"
// dst = path.Join("store/tests", "store_interface_"+d.Filename+".gen.go")
// if err = goTemplate(dst, tplInterfaces, d); err != nil {
// return
// }
//}
if err = genStoreInterfacesJoined(tplInterfacesJoined, path.Join("store", "interfaces.gen.go"), path.Base(dst), dd); err != nil {
return
}
return nil
}
@@ -443,8 +471,17 @@ func collectStoreDefImports(basePkg string, dd ...*storeDef) []string {
return ii
}
func (s storeTypeFieldSetDef) Find(name string) *storeTypeFieldDef {
for _, f := range s {
// Exported returns true if at least one of the functions is exported
func (d storeDef) Exported() bool {
return d.Search.Export ||
d.Create.Export ||
d.Update.Export ||
d.Upsert.Export ||
d.Delete.Export
}
func (ff storeTypeFieldSetDef) Find(name string) *storeTypeFieldDef {
for _, f := range ff {
if f.Field == name {
return f
}
@@ -453,6 +490,27 @@ func (s storeTypeFieldSetDef) Find(name string) *storeTypeFieldDef {
return nil
}
func (ff storeTypeFieldSetDef) HasPrimaryKey() bool {
for _, f := range ff {
if f.IsPrimaryKey {
return true
}
}
return false
}
func (ff storeTypeFieldSetDef) PrimaryKeyFields() storeTypeFieldSetDef {
pkSet := storeTypeFieldSetDef{}
for _, f := range ff {
if f.IsPrimaryKey {
pkSet = append(pkSet, f)
}
}
return pkSet
}
func (f storeTypeFieldDef) Arg() string {
if f.Field == "ID" {
return f.Field
@@ -465,11 +523,10 @@ func (f storeTypeFieldDef) AliasedColumn() string {
return fmt.Sprintf("%s.%s", f.alias, f.Column)
}
func (p storeTypePartialUpdate) Args() []*storeTypeFieldDef {
ff := make([]*storeTypeFieldDef, len(p.XX_Args))
for a := range p.XX_Args {
ff[a] = p.fields.Find(p.XX_Args[a])
}
return ff
// 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
var aux = (*dAux)(d)
aux.Export = true
return unmarshal(aux)
}
+12 -4
View File
@@ -84,14 +84,14 @@ func camelCase(pp ...string) (out string) {
// input, cammelcasing it and removing ident unfriendly characters
var nonIdentChars = regexp.MustCompile(`[\s\\/]+`)
func pubIdent(pp ...string) (out string) {
func export(pp ...string) (out string) {
for _, p := range pp {
if len(p) > 1 {
p = strings.ToUpper(p[:1]) + p[1:]
}
if ss := nonIdentChars.Split(p, -1); len(ss) > 1 {
p = pubIdent(ss...)
p = export(ss...)
}
out = out + p
@@ -100,11 +100,19 @@ func pubIdent(pp ...string) (out string) {
return out
}
func unpubIdent(pp ...string) (out string) {
out = pubIdent(pp...)
func unexport(pp ...string) (out string) {
out = export(pp...)
return strings.ToLower(out[:1]) + out[1:]
}
func toggleExport(e bool, pp ...string) (out string) {
if e {
return export(pp...)
}
return unexport(pp...)
}
// convets to underscore
func cc2underscore(cc string) string {
var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
+3 -11
View File
@@ -5,7 +5,6 @@ import (
"gopkg.in/yaml.v2"
"os"
"path"
"path/filepath"
"text/template"
)
@@ -28,15 +27,8 @@ type (
}
)
func procTypes() ([]*typesDef, error) {
var (
dd = make([]*typesDef, 0)
)
mm, err := filepath.Glob(filepath.Join("*", "*", "types.yaml"))
if err != nil {
return nil, fmt.Errorf("glob failed: %w", err)
}
func procTypes(mm ...string) (dd []*typesDef, err error) {
dd = make([]*typesDef, 0)
for _, m := range mm {
var (
@@ -69,7 +61,7 @@ func procTypes() ([]*typesDef, error) {
// Generates all type set files & accompanying tests
//
// generates 2 files per type definition
func genTypes(tpl *template.Template, dd []*typesDef) (err error) {
func genTypes(tpl *template.Template, dd ...*typesDef) (err error) {
var (
typeGen = tpl.Lookup("type_set.gen.go.tpl")
typeGenTest = tpl.Lookup("type_set.gen_test.go.tpl")