Refactored store codegen, replace squirrel with goqu
This commit is contained in:
@@ -1 +1,2 @@
|
||||
/build
|
||||
tmp*.cue
|
||||
|
||||
@@ -18,6 +18,3 @@ $(DEF_DOCS): $(CUE) $(JSONTPLEXEC)
|
||||
@[ "${DOCS_DIR}" ] || ( echo "DOCS_DIR is not set, run make like this: make docs DOCS_DIR=/path/to/corteza-docs"; exit 1 )
|
||||
@ echo "$(COLOUR_GREEN)Generating doc files from $@$(COLOUR_END) $(COLOUR_BLUE)(dst: $(DOCS_DIR))$(COLOUR_END)"
|
||||
@ $(CUE) eval $@ --out json | $(JSONTPLEXEC) -v -p $(ROOT_DIR)/codegen/assets/templates -b $(DOCS_DIR)
|
||||
|
||||
gen:
|
||||
@ rm $(JSONTPLEXEC)
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
package {{ .package }}
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
{{- range .imports }}
|
||||
{{ . }}
|
||||
{{- end }}
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
intAuth "github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza-server/pkg/options"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
systemTypes "github.com/cortezaproject/corteza-server/system/types"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type (
|
||||
localeAccessControl interface {
|
||||
CanManageResourceTranslations(ctx context.Context) bool
|
||||
}
|
||||
|
||||
resourceTranslationsManager struct {
|
||||
actionlog actionlog.Recorder
|
||||
locale locale.Resource
|
||||
store store.Storer
|
||||
ac localeAccessController
|
||||
}
|
||||
|
||||
localeAccessController interface {
|
||||
CanManageResourceTranslations(context.Context) bool
|
||||
}
|
||||
|
||||
ResourceTranslationsManagerService interface {
|
||||
{{- range .resources }}
|
||||
{{ .expIdent }}(ctx context.Context, {{ range .references }}{{ . }} uint64, {{ end }}) (locale.ResourceTranslationSet, error)
|
||||
{{- end }}
|
||||
|
||||
Upsert(context.Context, locale.ResourceTranslationSet) error
|
||||
Locale() locale.Resource
|
||||
}
|
||||
)
|
||||
|
||||
var ErrNotAllowedToManageResourceTranslations = errors.Unauthorized("not allowed to manage resource translations")
|
||||
|
||||
func ResourceTranslationsManager(ls locale.Resource) *resourceTranslationsManager {
|
||||
return &resourceTranslationsManager{
|
||||
actionlog: DefaultActionlog,
|
||||
store: DefaultStore,
|
||||
ac: DefaultAccessControl,
|
||||
locale: ls,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc resourceTranslationsManager) Upsert(ctx context.Context, rr locale.ResourceTranslationSet) (err error) {
|
||||
// User is allowed to manage resource translations when:
|
||||
// - managed resource translation strings are all for default language
|
||||
// or
|
||||
// - user is allowed to manage resource translations
|
||||
if rr.ContainsForeign(svc.Locale().Default().Tag) {
|
||||
if !svc.ac.CanManageResourceTranslations(ctx) {
|
||||
return ErrNotAllowedToManageResourceTranslations
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
r.Msg = locale.SanitizeMessage(r.Msg)
|
||||
}
|
||||
|
||||
// @todo validation
|
||||
|
||||
me := intAuth.GetIdentityFromContext(ctx)
|
||||
|
||||
// - group by resource
|
||||
localeByRes := make(map[string]locale.ResourceTranslationSet)
|
||||
for _, r := range rr {
|
||||
localeByRes[r.Resource] = append(localeByRes[r.Resource], r)
|
||||
}
|
||||
|
||||
// - for each resource, fetch the current state
|
||||
sysLocale := make(systemTypes.ResourceTranslationSet, 0, len(rr))
|
||||
for res, rr := range localeByRes {
|
||||
current, _, err := store.SearchResourceTranslations(ctx, svc.store, systemTypes.ResourceTranslationFilter{
|
||||
Resource: res,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get deltas and prepare upsert accordingly
|
||||
aux := current.New(rr)
|
||||
aux.Walk(func(cc *systemTypes.ResourceTranslation) error {
|
||||
cc.ID = nextID()
|
||||
cc.CreatedAt = *now()
|
||||
cc.CreatedBy = me.Identity()
|
||||
|
||||
return nil
|
||||
})
|
||||
sysLocale = append(sysLocale, aux...)
|
||||
|
||||
aux = current.Old(rr)
|
||||
_ = aux.Walk(func(cc *systemTypes.ResourceTranslation) error {
|
||||
cc.UpdatedAt = now()
|
||||
cc.UpdatedBy = me.Identity()
|
||||
return nil
|
||||
})
|
||||
sysLocale = append(sysLocale, aux...)
|
||||
}
|
||||
|
||||
err = store.UpsertResourceTranslation(ctx, svc.store, sysLocale...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload ALL resource translations
|
||||
// @todo we could probably do this more selectively and refresh only updated resources?
|
||||
_ = locale.Global().ReloadResourceTranslations(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc resourceTranslationsManager) Locale() locale.Resource {
|
||||
return svc.locale
|
||||
}
|
||||
|
||||
{{- range .resources }}
|
||||
|
||||
func (svc resourceTranslationsManager) {{ .expIdent }}(ctx context.Context, {{ range .references }}{{ . }} uint64, {{ end }}) (locale.ResourceTranslationSet, error) {
|
||||
var (
|
||||
err error
|
||||
out locale.ResourceTranslationSet
|
||||
res *types.{{ .expIdent }}
|
||||
k types.LocaleKey
|
||||
)
|
||||
|
||||
res, err = svc.load{{ .expIdent }}(ctx, svc.store, {{ range .references }}{{ . }}, {{ end }})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, tag := range svc.locale.Tags() {
|
||||
{{- range .keys}}
|
||||
{{- if not .customHandler }}
|
||||
k = types.{{ .struct }}
|
||||
out = append(out, &locale.ResourceTranslation{
|
||||
Resource: res.ResourceTranslation(),
|
||||
Lang: tag.String(),
|
||||
Key: k.Path,
|
||||
Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), k.Path),
|
||||
})
|
||||
{{ end }}
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
{{ if .extended }}
|
||||
tmp, err := svc.{{ .ident }}Extended(ctx, res)
|
||||
return append(out, tmp...), err
|
||||
{{- else }}
|
||||
return out, nil
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
{{- end }}
|
||||
|
||||
func updateTranslations(ctx context.Context, ac localeAccessControl, lsvc ResourceTranslationsManagerService, tt ...*locale.ResourceTranslation) error {
|
||||
if lsvc == nil || lsvc.Locale() == nil || lsvc.Locale().Default() == nil {
|
||||
// gracefully handle partial initializations
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
// assuming options will not change after start
|
||||
contentLang = lsvc.Locale().Default().Tag
|
||||
)
|
||||
|
||||
if options.Locale().ResourceTranslationsEnabled {
|
||||
contentLang = locale.GetContentLanguageFromContext(ctx)
|
||||
// Resource translations enabled
|
||||
if contentLang == language.Und {
|
||||
// If no content-language meta (HTTP header) info was
|
||||
// used, do not run update translations - we do not know
|
||||
// what is the language that we're sending in
|
||||
return nil
|
||||
}
|
||||
|
||||
if !lsvc.Locale().SupportedLang(contentLang) {
|
||||
// unsupported language
|
||||
return errors.InvalidData("unsupported language")
|
||||
}
|
||||
|
||||
if !ac.CanManageResourceTranslations(ctx) {
|
||||
return errors.Unauthorized("not allowed to manage resource translations")
|
||||
}
|
||||
}
|
||||
|
||||
locale.ResourceTranslationSet(tt).SetLanguage(contentLang)
|
||||
if err := lsvc.Upsert(ctx, tt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
package {{ .package }}
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"github.com/cortezaproject/corteza-server/pkg/locale"
|
||||
)
|
||||
|
||||
type (
|
||||
LocaleKey struct {
|
||||
Name string
|
||||
Resource string
|
||||
Path string
|
||||
CustomHandler string
|
||||
}
|
||||
)
|
||||
|
||||
// Types and stuff
|
||||
const (
|
||||
{{- range .resources }}
|
||||
{{ .const }} = "{{ .type }}"
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
var (
|
||||
// @todo can we remove LocaleKey struct for string constant?
|
||||
{{- range .resources }}
|
||||
{{- range .keys }}
|
||||
{{ .struct }} = LocaleKey{ Path: {{ printf "%q" .path }} }
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
{{- range .resources }}
|
||||
|
||||
// ResourceTranslation returns string representation of Locale resource for {{ .expIdent }} by calling {{ .expIdent }}ResourceTranslation fn
|
||||
//
|
||||
// Locale resource is in "{{ .type }}/..." format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (r {{ .expIdent }}) ResourceTranslation() string {
|
||||
return {{ .expIdent }}ResourceTranslation({{ if .references }}{{ range .references }}r.{{ . }},{{ end }}{{ end }})
|
||||
}
|
||||
|
||||
// {{ .expIdent }}ResourceTranslation returns string representation of Locale resource for {{ .expIdent }}
|
||||
//
|
||||
// Locale resource is in the {{ .type }}/{{- if .references }}...{{ end }} format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func {{ .expIdent }}ResourceTranslation({{ if .references }}{{ range .references }}{{ . }} uint64,{{ end }}{{ end }}) string {
|
||||
{{- if .references }}
|
||||
cpts := []interface{{"{}"}}{
|
||||
{{ .expIdent }}ResourceTranslationType,
|
||||
{{- range .references }}
|
||||
strconv.FormatUint({{ . }}, 10),
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
return fmt.Sprintf({{ .expIdent }}ResourceTranslationTpl(), cpts...)
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
func {{ .expIdent }}ResourceTranslationTpl() string {
|
||||
{{- if .references }}
|
||||
return "%s
|
||||
{{- range .references }}/%s{{- end }}"
|
||||
|
||||
{{- else }}
|
||||
return "%s"
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
func (r *{{ .expIdent }}) DecodeTranslations(tt locale.ResourceTranslationIndex) {
|
||||
var aux *locale.ResourceTranslation
|
||||
|
||||
{{- range .keys }}
|
||||
{{ if .decodeFunc }}
|
||||
{{ if not .extended }}
|
||||
r.{{ .decodeFunc }}(tt)
|
||||
{{- end}}
|
||||
{{ else }}
|
||||
if aux = tt.FindByKey({{ .struct }}.Path); aux != nil {
|
||||
r.{{ .fieldPath }} = aux.Msg
|
||||
}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
{{- if .extended }}
|
||||
r.decodeTranslations(tt)
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
func (r *{{ .expIdent }}) EncodeTranslations() (out locale.ResourceTranslationSet) {
|
||||
out = locale.ResourceTranslationSet{}
|
||||
{{- range .keys }}
|
||||
{{ if .encodeFunc }}
|
||||
{{ if not .extended }}
|
||||
out = append(out, r.{{ .encodeFunc }}()...)
|
||||
{{- end}}
|
||||
{{ else }}
|
||||
if r.{{ .fieldPath }} != "" {
|
||||
out = append(out, &locale.ResourceTranslation{
|
||||
Resource: r.ResourceTranslation(),
|
||||
Key: {{ .struct }}.Path,
|
||||
Msg: locale.SanitizeMessage(r.{{ .fieldPath }}),
|
||||
})
|
||||
}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
{{- if .extended }}
|
||||
out = append(out, r.encodeTranslations()...)
|
||||
{{- end }}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
{{- end }}
|
||||
@@ -0,0 +1,133 @@
|
||||
package store
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
{{- range $path, $alias := .imports }}
|
||||
{{ $alias }} {{ printf "%q" $path }}
|
||||
{{- end }}
|
||||
"github.com/cortezaproject/corteza-server/pkg/locale"
|
||||
"golang.org/x/text/language"
|
||||
"github.com/cortezaproject/corteza-server/pkg/report"
|
||||
)
|
||||
|
||||
{{ define "extraArgs" -}}
|
||||
{{/*This is temporary solution until we properly implement Compose Record Store*/}}
|
||||
{{- if eq . "composeRecord" }}mod *composeType.Module, {{ end -}}
|
||||
{{- end }}
|
||||
{{ define "extraParams" -}}
|
||||
{{/*This is temporary solution until we properly implement Compose Record Store*/}}
|
||||
{{- if eq . "composeRecord" }}mod, {{ end -}}
|
||||
{{- end }}
|
||||
|
||||
type (
|
||||
// Storer interface combines interfaces of all supported store interfaces
|
||||
Storer interface {
|
||||
// SetLogger sets new logging facility
|
||||
//
|
||||
// Store facility should fallback to logger.Default when no logging facility is set
|
||||
//
|
||||
// Intentionally closely coupled with Zap logger since this is not some public lib
|
||||
// and it's highly unlikely we'll support different/multiple logging "backend"
|
||||
SetLogger(*zap.Logger)
|
||||
|
||||
// Tx is a transaction handler
|
||||
Tx(context.Context, func(context.Context, Storer) error) error
|
||||
|
||||
// Upgrade store's schema to the latest version
|
||||
Upgrade(context.Context, *zap.Logger) error
|
||||
|
||||
{{- range .types }}
|
||||
{{ .expIdentPlural }}
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
{{ range .types }}
|
||||
{{ .expIdentPlural }} interface {
|
||||
Search{{ .expIdentPlural }}(ctx context.Context, {{ template "extraArgs" .ident }} f {{ .goFilterType }}) ({{ .goSetType }}, {{ .goFilterType }}, error)
|
||||
Create{{ .expIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) error
|
||||
Update{{ .expIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) error
|
||||
Upsert{{ .expIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) error
|
||||
Delete{{ .expIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) error
|
||||
{{ .api.deleteByPK.expFnIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} {{ range .api.deleteByPK.primaryKeys }}{{ .ident }} {{ .goType }},{{ end }}) error
|
||||
Truncate{{ .expIdentPlural }}(ctx context.Context, {{ template "extraArgs" .ident }}) error
|
||||
|
||||
{{- range .api.lookups }}
|
||||
{{ .expFnIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} {{ range .args }}{{ .ident }} {{ .goType }}, {{ end }}) (*{{ .returnType }}, error)
|
||||
{{- end }}
|
||||
{{- range .api.functions }}
|
||||
{{ .expFnIdent }}(ctx context.Context, {{ range .args }}{{ .ident }} {{ if .spread}}...{{ end }}{{ .goType }}, {{ end }}) ({{ range .return }}{{ . }}, {{ end }} error)
|
||||
{{- end }}
|
||||
}
|
||||
{{ end }}
|
||||
)
|
||||
|
||||
{{- range .types }}
|
||||
// Search{{ .expIdentPlural }} returns all matching {{ .expIdentPlural }} from store
|
||||
//
|
||||
// This function is auto-generated
|
||||
func Search{{ .expIdentPlural }}(ctx context.Context, s {{ .expIdentPlural }}, {{ template "extraArgs" .ident }} f {{ .goFilterType }}) ({{ .goSetType }}, {{ .goFilterType }}, error) {
|
||||
return s.Search{{ .expIdentPlural }}(ctx, {{ template "extraParams" .ident }} f)
|
||||
}
|
||||
|
||||
// Create{{ .expIdent }} creates one or more {{ .expIdentPlural }} in store
|
||||
//
|
||||
// This function is auto-generated
|
||||
func Create{{ .expIdent }}(ctx context.Context, s {{ .expIdentPlural }}, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) error {
|
||||
return s.Create{{ .expIdent }}(ctx, {{ template "extraParams" .ident }}rr...)
|
||||
}
|
||||
|
||||
// Update{{ .expIdent }} updates one or more (existing) {{ .expIdentPlural }} in store
|
||||
//
|
||||
// This function is auto-generated
|
||||
func Update{{ .expIdent }}(ctx context.Context, s {{ .expIdentPlural }}, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) error {
|
||||
return s.Update{{ .expIdent }}(ctx, {{ template "extraParams" .ident }}rr...)
|
||||
}
|
||||
|
||||
// Upsert{{ .expIdent }} creates new or updates existing one or more {{ .expIdentPlural }} in store
|
||||
//
|
||||
// This function is auto-generated
|
||||
func Upsert{{ .expIdent }}(ctx context.Context, s {{ .expIdentPlural }}, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) error {
|
||||
return s.Upsert{{ .expIdent }}(ctx, {{ template "extraParams" .ident }}rr...)
|
||||
}
|
||||
|
||||
// Delete{{ .expIdent }} deletes one or more {{ .expIdentPlural }} from store
|
||||
//
|
||||
// This function is auto-generated
|
||||
func Delete{{ .expIdent }}(ctx context.Context, s {{ .expIdentPlural }}, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) error {
|
||||
return s.Delete{{ .expIdent }}(ctx, {{ template "extraParams" .ident }}rr...)
|
||||
}
|
||||
|
||||
// Delete{{ .expIdent }}ByID deletes one or more {{ .expIdentPlural }} from store
|
||||
//
|
||||
// This function is auto-generated
|
||||
func {{ .api.deleteByPK.expFnIdent }}(ctx context.Context, s {{ .expIdentPlural }}, {{ template "extraArgs" .ident }} {{ range .api.deleteByPK.primaryKeys }}{{ .ident }} {{ .goType }},{{ end }}) error {
|
||||
return s.{{ .api.deleteByPK.expFnIdent }}(ctx, {{ template "extraParams" .ident }}{{ range .api.deleteByPK.primaryKeys }}{{ .ident }},{{ end }})
|
||||
}
|
||||
|
||||
// Truncate{{ .expIdentPlural }} Deletes all {{ .expIdentPlural }} from store
|
||||
//
|
||||
// This function is auto-generated
|
||||
func Truncate{{ .expIdentPlural }}(ctx context.Context, s {{ .expIdentPlural }}, {{ template "extraArgs" .ident }}) error {
|
||||
return s.Truncate{{ .expIdentPlural }}(ctx, {{ template "extraParams" .ident }})
|
||||
}
|
||||
|
||||
{{- range .api.lookups }}
|
||||
{{ if .description }}{{ .description }}{{ end }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
func {{ .expFnIdent }}(ctx context.Context, s {{ .expStoreIdent }}, {{ template "extraArgs" .ident }} {{ range .args }}{{ .ident }} {{ .goType }}, {{ end }}) (*{{ .returnType }}, error) {
|
||||
return s.{{ .expFnIdent }}(ctx, {{ template "extraParams" .ident }}{{ range .args }}{{ .ident }}, {{ end }})
|
||||
}
|
||||
{{- end }}
|
||||
{{- range .api.functions }}
|
||||
{{ if .description }}{{ .description }}{{ end }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
func {{ .expFnIdent }}(ctx context.Context, s {{ .expStoreIdent }}, {{ range .args }}{{ .ident }} {{ if .spread}}...{{ end }}{{ .goType }}, {{ end }}) ({{ range .return }}{{ . }}, {{ end }} error) {
|
||||
return s.{{ .expFnIdent }}(ctx, {{ range .args }}{{ .ident }}{{ if .spread}}...{{ end }}, {{ end }})
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,57 @@
|
||||
package rdbms
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/cortezaproject/corteza-server/pkg/expr"
|
||||
{{- range $path, $alias := .imports }}
|
||||
{{ $alias }} {{ printf "%q" $path }}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
type (
|
||||
{{ range .types }}
|
||||
// {{ .auxIdent }} is an auxiliary structure used for transporting to/from RDBMS store
|
||||
{{ .auxIdent }} struct {
|
||||
{{ range .auxStruct }}
|
||||
{{ .expIdent }} {{ .goType }} {{ printf "`db:%q`" .name }}
|
||||
{{- end }}
|
||||
}
|
||||
{{ end }}
|
||||
)
|
||||
|
||||
{{- range .types }}
|
||||
// encodes {{ .expIdent }} to {{ .auxIdent }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (aux *{{ .auxIdent }}) encode(res *{{ .goType }}) (_ error) {
|
||||
{{- range .auxStruct }}
|
||||
aux.{{ .expIdent }} = res.{{ .expIdent }}
|
||||
{{- end }}
|
||||
return
|
||||
}
|
||||
|
||||
// decodes {{ .expIdent }} from {{ .auxIdent }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (aux {{ .auxIdent }}) decode() (res *{{ .goType }}, _ error) {
|
||||
res = new({{ .goType }})
|
||||
{{- range .auxStruct }}
|
||||
res.{{ .expIdent }} = aux.{{ .expIdent }}
|
||||
{{- end }}
|
||||
return
|
||||
}
|
||||
|
||||
// scans row and fills {{ .auxIdent }} fields
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (aux *{{ .auxIdent }})scan(row scalable) (error) {
|
||||
return row.Scan(
|
||||
{{- range .auxStruct }}
|
||||
&aux.{{ .expIdent }},
|
||||
{{- end }}
|
||||
)
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
@@ -0,0 +1,91 @@
|
||||
package rdbms
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"strings"
|
||||
{{- range $path, $alias := .imports }}
|
||||
{{ $alias }} {{ printf "%q" $path }}
|
||||
{{- end }}
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
)
|
||||
|
||||
type (
|
||||
// extendedFilters allows special per-resource
|
||||
// filters to be attached to store
|
||||
//
|
||||
// when optional filter is set, generated filter function is NOT called automatically
|
||||
// (but can be called from the optional filter)
|
||||
extendedFilters struct {
|
||||
// Filter extensions for search/query functions
|
||||
{{ range .types }}
|
||||
|
||||
// optional {{ .ident }} filter function called after the generated function
|
||||
{{ .expIdent }} func({{ .goFilterType }}) ([]goqu.Expression, {{ .goFilterType }}, error)
|
||||
{{ end }}
|
||||
}
|
||||
)
|
||||
|
||||
{{- range .types }}
|
||||
// {{ .expIdent }}Filter returns logical expressions
|
||||
//
|
||||
// This function is called from Store.Query{{ .expIdentPlural }}() and can be extended
|
||||
// by setting Store.Filters.{{ .expIdent }}. Extension is called after all expressions
|
||||
// are generated and can choose to ignore or alter them.
|
||||
//
|
||||
// This function is auto-generated
|
||||
func {{ .expIdent }}Filter(f {{ .goFilterType }})(ee []goqu.Expression, _ {{ .goFilterType }}, err error) {
|
||||
{{ range .filter.byNilState }}
|
||||
if expr := stateNilComparison({{ printf "%q" .storeIdent }}, f.{{ .expIdent }}); expr != nil {
|
||||
ee = append(ee, expr)
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ range .filter.byFalseState }}
|
||||
if expr := stateFalseComparison({{ printf "%q" .storeIdent }}, f.{{ .expIdent }}); expr != nil {
|
||||
ee = append(ee, expr)
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ range .filter.byValue }}
|
||||
{{ if eq .goType "string" }}
|
||||
if val := strings.TrimSpace(f.{{ .expIdent }}); len(val) > 0 {
|
||||
ee = append(ee, goqu.C({{ printf "%q" .storeIdent }}).Eq(f.{{ .expIdent }}))
|
||||
}
|
||||
{{ else if eq .goType "bool" }}
|
||||
if f.{{ .expIdent }} {
|
||||
ee = append(ee, goqu.C({{ printf "%q" .storeIdent }}).IsTrue())
|
||||
}
|
||||
{{ else if eq .goType "uint64" }}
|
||||
if f.{{ .expIdent }} > 0 {
|
||||
ee = append(ee, goqu.C({{ printf "%q" .storeIdent }}).Eq(f.{{ .expIdent }}))
|
||||
}
|
||||
{{ else if eq .goType "[]uint64" }}
|
||||
if len(f.{{ .expIdent }}) > 0 {
|
||||
ee = append(ee, goqu.C({{ printf "%q" .storeIdent }}).In(f.{{ .expIdent }}))
|
||||
}
|
||||
{{ else }}
|
||||
// @todo codegen warning: filtering by {{ .expIdent }} ({{ .goType }}) not supported,
|
||||
// see rdbms.go.tpl and add an exception
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if .filter.byLabel }}
|
||||
if len(f.LabeledIDs) > 0 {
|
||||
ee = append(ee, goqu.I("id").In(f.LabeledIDs))
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ if .filter.query }}
|
||||
if f.Query != "" {
|
||||
ee = append(ee, goqu.Or(
|
||||
{{- range .filter.query }}
|
||||
goqu.C({{ printf "%q" .storeIdent }}).ILike("%" + f.Query + "%"),
|
||||
{{- end }}
|
||||
))
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
return ee, f, err
|
||||
}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,114 @@
|
||||
package rdbms
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
{{- range $path, $alias := .imports }}
|
||||
{{ $alias }} {{ printf "%q" $path }}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
var (
|
||||
{{- range .types }}
|
||||
// {{ .ident }}Table represents {{ .identPlural }} store table
|
||||
//
|
||||
// This value is auto-generated
|
||||
{{ .ident }}Table = goqu.T({{ printf "%q" .settings.rdbms.table }})
|
||||
|
||||
// {{ .ident }}SelectQuery assembles select query for fetching {{ .identPlural }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
{{ .ident }}SelectQuery = func(d goqu.DialectWrapper) *goqu.SelectDataset {
|
||||
return d.Select(
|
||||
{{- range .struct }}
|
||||
{{ printf "%q" .storeIdent }},
|
||||
{{- end }}
|
||||
).From({{ .ident }}Table)
|
||||
}
|
||||
|
||||
// {{ .ident }}InsertQuery assembles query inserting {{ .identPlural }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
{{ .ident }}InsertQuery = func(d goqu.DialectWrapper, res *{{ .goType }}) *goqu.InsertDataset {
|
||||
return d.Insert({{ .ident }}Table).
|
||||
Rows(goqu.Record{
|
||||
{{- range .struct }}
|
||||
{{ printf "%q" .storeIdent }}: res.{{ .expIdent }},
|
||||
{{- end }}
|
||||
})
|
||||
}
|
||||
|
||||
// {{ .ident }}UpsertQuery assembles (insert+on-conflict) query for replacing {{ .identPlural }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
{{ .ident }}UpsertQuery = func(d goqu.DialectWrapper, res *{{ .goType }}) *goqu.InsertDataset {
|
||||
var target = `
|
||||
{{- range .struct -}}
|
||||
{{- if .primaryKey -}}
|
||||
,
|
||||
{{- if .ignoreCase -}}
|
||||
LOWER({{- .storeIdent -}})
|
||||
{{- else -}}
|
||||
{{- .storeIdent -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}`
|
||||
|
||||
return {{ .ident }}InsertQuery(d, res).
|
||||
OnConflict(
|
||||
goqu.DoUpdate(target[1:],
|
||||
goqu.Record{
|
||||
{{- range .struct }}
|
||||
{{- if not .primaryKey }}
|
||||
{{ printf "%q" .storeIdent }}: res.{{ .expIdent }},
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// {{ .ident }}UpdateQuery assembles query for updating {{ .identPlural }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
{{ .ident }}UpdateQuery = func(d goqu.DialectWrapper, res *{{ .goType }}) *goqu.UpdateDataset {
|
||||
return d.Update({{ .ident }}Table).
|
||||
Set(goqu.Record{
|
||||
{{- range .struct }}
|
||||
{{- if not .primaryKey }}
|
||||
{{ printf "%q" .storeIdent }}: res.{{ .expIdent }},
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
}).
|
||||
Where({{ .ident }}PrimaryKeys(res))
|
||||
}
|
||||
|
||||
// {{ .ident }}DeleteQuery assembles delete query for removing {{ .identPlural }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
{{ .ident }}DeleteQuery = func(d goqu.DialectWrapper, ee ...goqu.Expression) *goqu.DeleteDataset {
|
||||
return d.Delete({{ .ident }}Table).Where(ee...)
|
||||
}
|
||||
|
||||
// {{ .ident }}DeleteQuery assembles delete query for removing {{ .identPlural }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
{{ .ident }}TruncateQuery = func(d goqu.DialectWrapper) *goqu.TruncateDataset {
|
||||
return d.Truncate({{ .ident }}Table)
|
||||
}
|
||||
|
||||
// {{ .ident }}PrimaryKeys assembles set of conditions for all primary keys
|
||||
//
|
||||
// This function is auto-generated
|
||||
{{ .ident }}PrimaryKeys = func(res *{{ .goType }}) goqu.Ex {
|
||||
return goqu.Ex{
|
||||
{{- range .struct }}
|
||||
{{- if .primaryKey }}
|
||||
{{ printf "%q" .storeIdent }}: res.{{ .expIdent }},
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
}
|
||||
}
|
||||
{{ end }}
|
||||
)
|
||||
@@ -0,0 +1,606 @@
|
||||
package rdbms
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
"github.com/doug-martin/goqu/v9/exp"
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
{{- range $path, $alias := .imports }}
|
||||
{{ $alias }} {{ printf "%q" $path }}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
{{ define "extraArgs" -}}
|
||||
{{/*This is temporary solution until we properly implement Compose Record Store*/}}
|
||||
{{- if eq . "composeRecord" }}mod *composeType.Module, {{ end -}}
|
||||
{{- end }}
|
||||
{{ define "extraParams" -}}
|
||||
{{/*This is temporary solution until we properly implement Compose Record Store*/}}
|
||||
{{- if eq . "composeRecord" }}mod, {{ end -}}
|
||||
{{- end }}
|
||||
|
||||
var (
|
||||
{{ range .types }}
|
||||
_ store.{{ .expIdentPlural }} = &Store{}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
{{- range .types }}
|
||||
// Create{{ .expIdent }} creates one or more rows in {{ .ident }} collection
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) Create{{ .expIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) (err error) {
|
||||
for i := range rr {
|
||||
if err = s.check{{ .expIdent }}Constraints(ctx, rr[i]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.Exec(ctx, {{ .ident }}InsertQuery(s.config.Dialect, rr[i])); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Update{{ .expIdent }} updates one or more existing entries in {{ .ident }} collection
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) Update{{ .expIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) (err error) {
|
||||
for i := range rr {
|
||||
if err = s.check{{ .expIdent }}Constraints(ctx, rr[i]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.Exec(ctx, {{ .ident }}UpdateQuery(s.config.Dialect, rr[i])); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Upsert{{ .expIdent }} updates one or more existing entries in {{ .ident }} collection
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) Upsert{{ .expIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) (err error) {
|
||||
for i := range rr {
|
||||
if err = s.check{{ .expIdent }}Constraints(ctx, rr[i]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.Exec(ctx, {{ .ident }}UpsertQuery(s.config.Dialect, rr[i])); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Delete{{ .expIdent }} Deletes one or more entries from {{ .ident }} collection
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) Delete{{ .expIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} rr ...*{{ .goType }}) (err error) {
|
||||
for i := range rr {
|
||||
if err = s.Exec(ctx, {{ .ident }}DeleteQuery(s.config.Dialect, {{ .ident }}PrimaryKeys(rr[i]))); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete{{ .expIdent }}ByID deletes single entry from {{ .ident }} collection
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) {{ .api.deleteByPK.expFnIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} {{ range .api.deleteByPK.primaryKeys }}{{ .ident }} {{ .goType }},{{ end }}) error {
|
||||
return s.Exec(ctx, {{ .ident }}DeleteQuery(s.config.Dialect, goqu.Ex{
|
||||
{{- range .api.deleteByPK.primaryKeys }}
|
||||
{{ printf "%q" .storeIdent }}: {{ .ident }},
|
||||
{{- end }}
|
||||
}))
|
||||
}
|
||||
|
||||
// Truncate{{ .expIdentPlural }} Deletes all rows from the {{ .ident }} collection
|
||||
func (s Store) Truncate{{ .expIdentPlural }}(ctx context.Context, {{ template "extraArgs" .ident }}) error {
|
||||
return s.Exec(ctx, {{ .ident }}TruncateQuery(s.config.Dialect))
|
||||
}
|
||||
|
||||
// Search{{ .expIdentPlural }} returns (filtered) set of {{ .expIdentPlural }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) Search{{ .expIdentPlural }}(ctx context.Context, {{ template "extraArgs" .ident }} f {{ .goFilterType }}) (set {{ .goSetType }}, _ {{ .goFilterType }}, err error) {
|
||||
{{ if .features.paging }}
|
||||
// Cleanup unwanted cursor values (only relevant is f.PageCursor, next&prev are reset and returned)
|
||||
f.PrevPage, f.NextPage = nil, nil
|
||||
|
||||
if f.PageCursor != nil {
|
||||
// Page cursor exists; we need to validate it against used sort
|
||||
// To cover the case when paging cursor is set but sorting is empty, we collect the sorting instructions
|
||||
// from the cursor.
|
||||
// This (extracted sorting info) is then returned as part of response
|
||||
if f.Sort, err = f.PageCursor.Sort(f.Sort); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure results are always sorted at least by primary keys
|
||||
if f.Sort.Get("id") == nil {
|
||||
f.Sort = append(f.Sort, &filter.SortExpr{
|
||||
Column: "id",
|
||||
Descending: f.Sort.LastDescending(),
|
||||
})
|
||||
}
|
||||
|
||||
// Cloned sorting instructions for the actual sorting
|
||||
// Original are passed to the fetchFullPageOf{{ .expIdentPlural }} fn used for cursor creation so it MUST keep the initial
|
||||
// direction information
|
||||
sort := f.Sort.Clone()
|
||||
|
||||
// When cursor for a previous page is used it's marked as reversed
|
||||
// This tells us to flip the descending flag on all used sort keys
|
||||
if f.PageCursor != nil && f.PageCursor.ROrder {
|
||||
sort.Reverse()
|
||||
}
|
||||
|
||||
set, f.PrevPage, f.NextPage, err = s.fetchFullPageOf{{ .expIdentPlural }}(
|
||||
ctx,
|
||||
f,
|
||||
)
|
||||
|
||||
f.PageCursor = nil
|
||||
if err != nil {
|
||||
return nil, f, err
|
||||
}
|
||||
{{ else }}
|
||||
set, _, err = s.Query{{ .expIdentPlural }}(ctx, f)
|
||||
if err != nil {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
return set, f, nil
|
||||
}
|
||||
|
||||
{{ if .features.paging }}
|
||||
// fetchFullPageOf{{ .expIdentPlural }} collects all requested results.
|
||||
//
|
||||
// Function applies:
|
||||
// - cursor conditions (where ...)
|
||||
// - limit
|
||||
//
|
||||
// Main responsibility of this function is to perform additional sequential queries in case when not enough results
|
||||
// are collected due to failed check on a specific row (by check fn).
|
||||
//
|
||||
// Function then moves cursor to the last item fetched
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) fetchFullPageOf{{ .expIdentPlural }}(
|
||||
ctx context.Context,
|
||||
filter {{ .goFilterType }},
|
||||
) (set []*{{ .goType }}, prev, next *filter.PagingCursor, err error) {
|
||||
var (
|
||||
aux []*{{ .goType }}
|
||||
|
||||
// When cursor for a previous page is used it's marked as reversed
|
||||
// This tells us to flip the descending flag on all used sort keys
|
||||
reversedOrder = filter.PageCursor != nil && filter.PageCursor.ROrder
|
||||
|
||||
// Copy no. of required items to limit
|
||||
// Limit will change when doing subsequent queries to fill
|
||||
// the set with all required items
|
||||
limit = filter.Limit
|
||||
|
||||
reqItems = filter.Limit
|
||||
|
||||
// cursor to prev. page is only calculated when cursor is used
|
||||
hasPrev = filter.PageCursor != nil
|
||||
|
||||
// next cursor is calculated when there are more pages to come
|
||||
hasNext bool
|
||||
|
||||
tryFilter {{ .goFilterType }}
|
||||
)
|
||||
|
||||
set = make([]*{{ .goType }}, 0, DefaultSliceCapacity)
|
||||
|
||||
for try := 0; try < MaxRefetches; try++ {
|
||||
// Copy filter
|
||||
tryFilter = filter
|
||||
|
||||
if limit > 0 {
|
||||
// fetching + 1 to peak ahead if there are more items
|
||||
// we can fetch (next-page cursor)
|
||||
tryFilter.Limit = limit + 1
|
||||
}
|
||||
|
||||
if aux, hasNext, err = s.Query{{ .expIdentPlural }}(ctx, tryFilter); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if len(aux) == 0 {
|
||||
// nothing fetched
|
||||
break
|
||||
}
|
||||
|
||||
// append fetched items
|
||||
set = append(set, aux...)
|
||||
|
||||
if reqItems == 0 || !hasNext {
|
||||
// no max requested items specified, break out
|
||||
break
|
||||
}
|
||||
|
||||
collected := uint(len(set))
|
||||
|
||||
if reqItems > collected {
|
||||
// not enough items fetched, try again with adjusted limit
|
||||
limit = reqItems - collected
|
||||
|
||||
if limit < MinEnsureFetchLimit {
|
||||
// In case limit is set very low and we've missed records in the first fetch,
|
||||
// make sure next fetch limit is a bit higher
|
||||
limit = MinEnsureFetchLimit
|
||||
}
|
||||
|
||||
// Update cursor so that it points to the last item fetched
|
||||
tryFilter.PageCursor = s.{{ .api.collectCursorValues.fnIdent }}(set[collected-1], filter.Sort...)
|
||||
|
||||
// Copy reverse flag from sorting
|
||||
tryFilter.PageCursor.LThen = filter.Sort.Reversed()
|
||||
continue
|
||||
}
|
||||
|
||||
if reqItems < collected {
|
||||
set = set[:reqItems]
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
collected := len(set)
|
||||
|
||||
if collected == 0 {
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
|
||||
if reversedOrder {
|
||||
// Fetched set needs to be reversed because we've forced a descending order to get the previous page
|
||||
for i, j := 0, collected-1; i < j; i, j = i+1, j-1 {
|
||||
set[i], set[j] = set[j], set[i]
|
||||
}
|
||||
|
||||
// when in reverse-order rules on what cursor to return change
|
||||
hasPrev, hasNext = hasNext, hasPrev
|
||||
}
|
||||
|
||||
if hasPrev {
|
||||
prev = s.{{ .api.collectCursorValues.fnIdent }}(set[0], filter.Sort...)
|
||||
prev.ROrder = true
|
||||
prev.LThen = !filter.Sort.Reversed()
|
||||
}
|
||||
|
||||
if hasNext {
|
||||
next = s.{{ .api.collectCursorValues.fnIdent }}(set[collected-1], filter.Sort...)
|
||||
next.LThen = filter.Sort.Reversed()
|
||||
}
|
||||
|
||||
return set, prev, next, nil
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
// Query{{ .expIdentPlural }} queries the database, converts and checks each row and returns collected set
|
||||
//
|
||||
// With generics, we can remove this per-resource-generated function
|
||||
// and replace it with a single utility fetcher
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) Query{{ .expIdentPlural }}(
|
||||
ctx context.Context,
|
||||
f {{ .goFilterType }},
|
||||
) (_ []*{{ .goType }}, more bool, err error) {
|
||||
var (
|
||||
{{ if .features.checkFn }}
|
||||
ok bool
|
||||
{{ end }}
|
||||
set = make([]*{{ .goType }}, 0, DefaultSliceCapacity)
|
||||
res *{{ .goType }}
|
||||
aux {{ .auxIdent }}
|
||||
rows *sql.Rows
|
||||
count uint
|
||||
expr, tExpr []goqu.Expression
|
||||
{{ if .features.sorting }}
|
||||
sortExpr []exp.OrderedExpression
|
||||
{{ end }}
|
||||
)
|
||||
|
||||
if s.config.Filters.{{ .expIdent }} != nil {
|
||||
// extended filter set
|
||||
tExpr, f, err = s.config.Filters.{{ .expIdent }}(f)
|
||||
} else {
|
||||
// using generated filter
|
||||
tExpr, f, err = {{ .expIdent }}Filter(f)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could generate filter expression for {{ .expIdent }}: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
expr = append(expr, tExpr...)
|
||||
|
||||
{{ if .features.paging }}
|
||||
// paging feature is enabled
|
||||
if f.PageCursor != nil {
|
||||
if tExpr, err = cursor(f.PageCursor); err != nil {
|
||||
return
|
||||
} else {
|
||||
expr = append(expr, tExpr...)
|
||||
}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
|
||||
query := {{ .ident }}SelectQuery(s.config.Dialect).Where(expr...)
|
||||
|
||||
{{ if .features.sorting }}
|
||||
// sorting feature is enabled
|
||||
if sortExpr, err = order(f.Sort, s.{{ .api.sortableFields.fnIdent }}()); err != nil {
|
||||
err = fmt.Errorf("could generate order expression for {{ .expIdent }}: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(sortExpr) > 0 {
|
||||
query = query.Order(sortExpr...)
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
if f.Limit > 0 {
|
||||
query = query.Limit(f.Limit)
|
||||
}
|
||||
|
||||
|
||||
rows, err = s.Query(ctx, query)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not query {{ .expIdent }}: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
err = fmt.Errorf("could not query {{ .expIdent }}: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closeError := rows.Close()
|
||||
if err == nil {
|
||||
// return error from close
|
||||
err = closeError
|
||||
}
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
if err = rows.Err(); err != nil {
|
||||
err = fmt.Errorf("could not query {{ .expIdent }}: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = aux.scan(rows); err != nil {
|
||||
err = fmt.Errorf("could not scan rows for {{ .expIdent }}: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
count++
|
||||
if res, err = aux.decode(); err != nil {
|
||||
err = fmt.Errorf("could not decode {{ .expIdent }}: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
{{ if .features.checkFn }}
|
||||
// check fn set, call it and see if it passed the test
|
||||
// if not, skip the item
|
||||
if f.Check != nil {
|
||||
if ok, err = f.Check(res); err != nil {
|
||||
return
|
||||
} else if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
|
||||
{{ if .features.paging }}
|
||||
return set, f.Limit > 0 && count >= f.Limit, err
|
||||
{{ else }}
|
||||
return set, false, err
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
{{- range .api.lookups }}
|
||||
{{ if .description }}{{ .description }}{{ end }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) {{ .expFnIdent }}(ctx context.Context, {{ template "extraArgs" .ident }} {{ range .args }}{{ .ident }} {{ .goType }}, {{ end }}) (_ *{{ .returnType }}, err error) {
|
||||
var (
|
||||
rows *sql.Rows
|
||||
aux = new({{ .auxIdent }})
|
||||
lookup = {{ .ident }}SelectQuery(s.config.Dialect).Where(
|
||||
{{- range .args }}
|
||||
{{- if .ignoreCase }}
|
||||
s.config.Functions.LOWER(goqu.I({{ printf "%q" .storeIdent }})).Eq(strings.ToLower({{ .ident }})),
|
||||
{{- else }}
|
||||
goqu.I({{ printf "%q" .storeIdent }}).Eq({{ .ident }}),
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range .nullConstraint }}
|
||||
goqu.I({{ printf "%q" . }}).IsNull(),
|
||||
{{- end }}
|
||||
).Limit(1)
|
||||
)
|
||||
|
||||
rows, err = s.Query(ctx, lookup)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closeError := rows.Close()
|
||||
if err == nil {
|
||||
// return error from close
|
||||
err = closeError
|
||||
}
|
||||
}()
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !rows.Next() {
|
||||
return nil, store.ErrNotFound.Stack(1)
|
||||
}
|
||||
|
||||
if err = aux.scan(rows); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return aux.decode()
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
{{ with .api.sortableFields }}
|
||||
// {{ .fnIdent }} returns all {{ .expIdent }} columns flagged as sortable
|
||||
//
|
||||
// With optional string arg, all columns are returned aliased
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (Store) {{ .fnIdent }}() map[string]string {
|
||||
return map[string]string{
|
||||
{{- range $k, $v := .fields }}
|
||||
{{ printf "%q: %q" $k $v }},
|
||||
{{- end }}
|
||||
}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ with .api.collectCursorValues }}
|
||||
// {{ .fnIdent }} collects values from the given resource that and sets them to the cursor
|
||||
// to be used for pagination
|
||||
//
|
||||
// Values that are collected must come from sortable, unique or primary columns/fields
|
||||
// At least one of the collected columns must be flagged as unique, otherwise fn appends primary keys at the end
|
||||
//
|
||||
// Known issue:
|
||||
// when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on
|
||||
// undeleted items)
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s Store) {{ .fnIdent }}(res *{{ .goType }}, cc ...*filter.SortExpr) *filter.PagingCursor {
|
||||
{{- if .fields }}
|
||||
var (
|
||||
cur = &filter.PagingCursor{LThen: filter.SortExprSet(cc).Reversed()}
|
||||
|
||||
hasUnique bool
|
||||
|
||||
{{ range .primaryKeys }}
|
||||
pk{{ .expIdent }} bool
|
||||
{{- end }}
|
||||
|
||||
collect = func(cc ...*filter.SortExpr) {
|
||||
for _, c := range cc {
|
||||
switch c.Column {
|
||||
{{- range .fields }}
|
||||
case {{ printf "%q" .ident }}:
|
||||
cur.Set(c.Column, res.{{ .expIdent }}, c.Descending)
|
||||
|
||||
{{- if .primaryKey }}
|
||||
pk{{ .expIdent }} = true
|
||||
{{- else if .unique }}
|
||||
hasUnique = true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
collect(cc...)
|
||||
{{- range .primaryKeys }}
|
||||
if !hasUnique || !pk{{ .expIdent }} {
|
||||
collect(&filter.SortExpr{Column: {{ printf "%q" .ident }}, Descending: {{ if .descending }}true{{ else }}false{{ end }}})
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
return cur
|
||||
{{ else }}
|
||||
return nil
|
||||
{{- end }}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{ with .api.checkConstraints }}
|
||||
// {{ .fnIdent }} performs lookups (on valid) resource to check if any of the values on unique fields
|
||||
// already exists in the store
|
||||
//
|
||||
// Using built-in constraint checking would be more performant, but unfortunately we cannot rely
|
||||
// on the full support (MySQL does not support conditional indexes)
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (s *Store) {{ .fnIdent }}(ctx context.Context, res *{{ .goType }}) (err error) {
|
||||
{{- range .checks }}
|
||||
err = func() (err error) {
|
||||
{{- range .fields }}
|
||||
{{ if eq .goType "uint64" }}
|
||||
if res.{{ .expIdent }} == 0 {
|
||||
// skip check on empty values
|
||||
return nil
|
||||
}
|
||||
{{ else }}
|
||||
// handling string type as default
|
||||
if len(res.{{ .expIdent }}) == 0 {
|
||||
// skip check on empty values
|
||||
return nil
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{- range .nullConstraint }}
|
||||
if res.{{ .expIdent }} != nil {
|
||||
// skip check if value is not nil
|
||||
return nil
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
ex, err := s.{{ .lookupFnIdent }}(ctx, {{ range .fields }}res.{{ .expIdent }},{{ end }})
|
||||
if err == nil && ex != nil && ex.ID != res.ID {
|
||||
return store.ErrNotUnique.Stack(1)
|
||||
} else if !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
{{ end }}
|
||||
return nil
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
package tests
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
)
|
||||
|
||||
func testAllGenerated(t *testing.T, s store.Storer) {
|
||||
{{ range .types }}
|
||||
t.Run({{ printf "%q" .ident }}, func(t *testing.T) {
|
||||
test{{ .expIdentPlural }}(t, s)
|
||||
})
|
||||
{{- end }}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package schema
|
||||
|
||||
#codegen: {
|
||||
#_ioSpec: {
|
||||
template: string
|
||||
output: string
|
||||
|
||||
@@ -8,6 +8,13 @@ package schema
|
||||
if output =~ "\\.adoc$" {
|
||||
syntax: "adoc"
|
||||
}
|
||||
}
|
||||
|
||||
#codegen: {
|
||||
bulk?: [...#_ioSpec]
|
||||
if bulk == _|_ {
|
||||
#_ioSpec
|
||||
}
|
||||
|
||||
payload: _
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
platform: #baseHandle
|
||||
|
||||
resources: {
|
||||
[key=_]: {"handle": key, "component": handle, "platform": platform} & #resource
|
||||
[key=_]: {"handle": key, "component": handle, "platform": platform} & #Resource
|
||||
}
|
||||
|
||||
fqrn: platform + "::" + handle
|
||||
|
||||
@@ -4,11 +4,15 @@ package schema
|
||||
ident: #baseHandle | *"corteza"
|
||||
|
||||
options: [...#optionsGroup]
|
||||
components: [...{platform: ident} & #component]
|
||||
// env-var definitions
|
||||
// options: {}
|
||||
|
||||
//
|
||||
components: [...{platform: ident} & #component]
|
||||
|
||||
resources: {
|
||||
[key=#handle]: #Resource & {
|
||||
"handle": key,
|
||||
"platform": ident
|
||||
}
|
||||
}
|
||||
|
||||
// automation: {
|
||||
// types: ....
|
||||
|
||||
+93
-60
@@ -1,6 +1,14 @@
|
||||
package schema
|
||||
|
||||
#resource: #_base & {
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
#Resource: {
|
||||
#_base
|
||||
|
||||
imports: [...{ import: string }]
|
||||
|
||||
// copy field values from #_base
|
||||
handle: handle, ident: ident, expIdent: expIdent
|
||||
|
||||
@@ -10,9 +18,40 @@ package schema
|
||||
// Fully qualified resource name
|
||||
fqrn: string | *(platform + "::" + component + ":" + handle)
|
||||
|
||||
// fields: #Fields
|
||||
struct: #Struct
|
||||
filter: {
|
||||
"expIdent": #expIdent | *"\(expIdent)Filter"
|
||||
|
||||
struct: #Struct
|
||||
|
||||
// generate filtering by-nil-state for the specified fields
|
||||
"byNilState": [...string]
|
||||
|
||||
// generate filtering by-false-state for the specified fields
|
||||
"byFalseState": [...string]
|
||||
|
||||
// generate query filter for the specified fields
|
||||
"query": [...string]
|
||||
|
||||
// filter resources by fields (eq)
|
||||
"byValue": [...string]
|
||||
}
|
||||
// operations: #Operations
|
||||
|
||||
features: {
|
||||
// filtering by label
|
||||
labels: bool | *true
|
||||
|
||||
// support pagination
|
||||
paging: bool | *true
|
||||
|
||||
// support sorting
|
||||
sorting: bool | *true
|
||||
|
||||
// support resource check function
|
||||
checkFn: bool | *true
|
||||
}
|
||||
|
||||
// All parent resources
|
||||
parents: [... #_base & {
|
||||
// copy field values from #_base
|
||||
@@ -35,64 +74,58 @@ package schema
|
||||
}
|
||||
}
|
||||
|
||||
// List of known keys for resource translation
|
||||
// locale?: {
|
||||
// [Name=_]: {
|
||||
// name: Name & #Handle
|
||||
// path: string
|
||||
// custom: bool | *false
|
||||
// }
|
||||
// }
|
||||
store?: {
|
||||
// how is this resource represented (prefixed/suffixed functions) in the store
|
||||
"ident": #ident | *ident
|
||||
"identPlural": #ident | *"\(store.ident)s"
|
||||
"expIdent": #expIdent | *strings.ToTitle(store.ident)
|
||||
"expIdentPlural": #expIdent | *"\(store.expIdent)s"
|
||||
|
||||
api?: {
|
||||
lookups: [...{
|
||||
_expFields: [ for f in fields {strings.ToTitle(struct[f].expIdent)}]
|
||||
|
||||
"expIdent": "Lookup\(store.expIdent)By" + strings.Join(_expFields, "")
|
||||
description: string | *""
|
||||
|
||||
// fields used for the lookup (must exist in the struct)
|
||||
fields: [...string]
|
||||
|
||||
// Skip null constraints
|
||||
nullConstraint: [...string]
|
||||
constraintCheck: bool | *false
|
||||
}]
|
||||
|
||||
functions: [...{
|
||||
expIdent: string
|
||||
|
||||
description: string | *""
|
||||
|
||||
args: [...{ident: #ident, goType: string, spread: bool | *false}]
|
||||
return: [...string]
|
||||
}]
|
||||
}
|
||||
|
||||
settings: {
|
||||
defaultOrder: [...{ field: string, descending: bool | *false }]
|
||||
|
||||
rdbms: {
|
||||
// use resource handle (plural) as RDBMS table name as default
|
||||
table: string | *"\(strings.Replace(handle, "-", "_", -1))s"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#fields: {
|
||||
// // Each field can be
|
||||
// [key=_]: #fields | *({name: key} & #field)
|
||||
//}
|
||||
//
|
||||
//#field: {
|
||||
// name: #expIdent
|
||||
// unique: bool | *false
|
||||
//
|
||||
// // Golang type (built-in or other)
|
||||
// type: string | *"string"
|
||||
//
|
||||
// // System fields,
|
||||
// system: bool | *false
|
||||
//
|
||||
// if name =~ "At$" {
|
||||
// type: string | *"*time.Time"
|
||||
// }
|
||||
//}
|
||||
#storeFunction: {
|
||||
expIdent: #expIdent
|
||||
args: [...string]
|
||||
return: [...string]
|
||||
}
|
||||
|
||||
//#Operations: {
|
||||
// [Operation=_]: {operation: Operation} & #Operation
|
||||
//}
|
||||
|
||||
//#Operation: {
|
||||
// name: #ExpIdent
|
||||
// description: string
|
||||
// can: string | false | *"\(name)"
|
||||
//}
|
||||
|
||||
//idField: {
|
||||
// // Expecting ID field to allways have name ID
|
||||
// name: "ID"
|
||||
// unique: true
|
||||
//
|
||||
// // Service fields,
|
||||
// // @todo We might want to have a better name for this
|
||||
// // service: true
|
||||
//
|
||||
// // @todo someday we'll replace this with the "ID" type
|
||||
// type: "uint64"
|
||||
//}
|
||||
//
|
||||
//handleField: {
|
||||
// // Expecting ID field to allways have name ID
|
||||
// name: "handle"
|
||||
// unique: true
|
||||
//
|
||||
// // @todo someday we'll replace this with the "ID" type
|
||||
// type: "string" & #handle
|
||||
//}
|
||||
#PkgResource: #Resource & {
|
||||
package: {
|
||||
ident: #ident
|
||||
import: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// More liberal then identifier, allows underscores and dots
|
||||
#handle: =~"^[A-Za-z][a-zA-Z0-9_\\-\\.]*[a-zA-Z0-9]+$"
|
||||
|
||||
// More liberal then identifier, allows underscores and dots
|
||||
// More liberal then identifier
|
||||
#baseHandle: =~"^[a-z][a-z0-9-]*[a-z0-9]+$"
|
||||
|
||||
#_base: {
|
||||
@@ -22,11 +22,17 @@ import (
|
||||
handle: #baseHandle | *"base"
|
||||
_words: strings.Replace(strings.Replace(strings.Replace(handle, "-", " ", -1), "_", " ", -1), ".", " ", -1)
|
||||
|
||||
// lowercased (unexported, golang) identifier
|
||||
// lowercase (unexported, golang) identifier
|
||||
ident: #ident | *strings.ToCamel(strings.Replace(strings.ToTitle(_words), " ", "", -1))
|
||||
|
||||
// upercased (exported, golang) identifier
|
||||
// plural
|
||||
identPlural: #ident | *"\(ident)s"
|
||||
|
||||
// uppercase (exported, golang) identifier
|
||||
expIdent: #expIdent | *strings.Replace(strings.ToTitle(_words), " ", "", -1)
|
||||
|
||||
// plural exported
|
||||
expIdentPlural: #expIdent | *"\(expIdent)s"
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
#Struct: {
|
||||
// Each field can be
|
||||
[name=_]: {"name": name} & #StructField
|
||||
}
|
||||
|
||||
// logic in struct fields is a bit different
|
||||
#StructField: {
|
||||
name: #ident
|
||||
_words: strings.Replace(strings.Replace(name, "_", " ", -1), ".", " ", -1)
|
||||
|
||||
_ident: strings.ToCamel(strings.Replace(strings.ToTitle(_words), " ", "", -1))
|
||||
|
||||
// Golang type (built-in or other)
|
||||
goType: string | *"string"
|
||||
|
||||
// lowercase (unexported, golang) identifier
|
||||
ident: #ident | *_ident
|
||||
|
||||
// uppercase (exported, golang) identifier
|
||||
expIdent: #expIdent | *strings.ToTitle(ident)
|
||||
|
||||
// store identifier
|
||||
storeIdent: #ident | *name
|
||||
store: bool | *true
|
||||
|
||||
unique: bool | *false
|
||||
sortable: bool | *false
|
||||
descending: bool | *false
|
||||
primaryKey: bool | *false
|
||||
ignoreCase: bool | *false
|
||||
|
||||
#StructJsonTag
|
||||
}
|
||||
|
||||
IdField: #StructField & {
|
||||
// Expecting ID field to always have name ID
|
||||
name: "id"
|
||||
expIdent: "ID"
|
||||
primaryKey: true
|
||||
unique: true
|
||||
|
||||
// @todo someday we'll replace this with the "ID" type
|
||||
goType: "uint64"
|
||||
}
|
||||
|
||||
HandleField: #StructField & {
|
||||
// Expecting ID field to always have name handle
|
||||
name: "handle"
|
||||
unique: true
|
||||
ignoreCase: true
|
||||
|
||||
goType: "string"
|
||||
}
|
||||
|
||||
SortableTimestampField: #StructField & {
|
||||
sortable: true
|
||||
goType: "time.Time"
|
||||
}
|
||||
|
||||
SortableTimestampNilField: #StructField & {
|
||||
sortable: true
|
||||
goType: "*time.Time"
|
||||
}
|
||||
|
||||
#StructJsonTag: {
|
||||
name: string
|
||||
|
||||
_specs: {field: string | *name, omitEmpty: bool | *false, "string": bool | *false}
|
||||
|
||||
json: string | _specs | bool | *false
|
||||
jsonTag?: string
|
||||
|
||||
// just wrap whatever we got in json
|
||||
if (json & string) != _|_ {
|
||||
jsonTag: "json:\"\(json)\""
|
||||
}
|
||||
|
||||
// json enable,d wrap with ident as a JSON prop name
|
||||
if (json & bool) != _|_ && json {
|
||||
// generic json tag
|
||||
jsonTag: "json:\"\(name)\""
|
||||
}
|
||||
|
||||
// full-specs
|
||||
if (json & bool) == _|_ && (json & _specs) != _|_ {
|
||||
_omitEmpty: string | *""
|
||||
if json.omitEmpty {
|
||||
_omitEmpty: ",omitempty"
|
||||
}
|
||||
_string: string | *""
|
||||
if json.string {
|
||||
_string: ",string"
|
||||
}
|
||||
|
||||
jsonTag: "json:\"\(json.field)\(_omitEmpty)\(_string)\""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package codegen
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"list"
|
||||
"github.com/cortezaproject/corteza-server/app"
|
||||
"github.com/cortezaproject/corteza-server/codegen/schema"
|
||||
)
|
||||
|
||||
_StoreResource: {
|
||||
res = "res": schema.#Resource
|
||||
typePkg = "typePkg": string
|
||||
|
||||
result: {
|
||||
ident: res.store.ident
|
||||
identPlural: res.store.identPlural
|
||||
expIdent: res.store.expIdent
|
||||
expIdentPlural: res.store.expIdentPlural
|
||||
goType: "\(typePkg).\(res.expIdent)"
|
||||
goSetType: "\(typePkg).\(res.expIdent)Set"
|
||||
goFilterType: "\(typePkg).\(res.filter.expIdent)"
|
||||
|
||||
struct: [ for f in res.struct if f.store {
|
||||
"ident": f.ident
|
||||
"expIdent": f.expIdent
|
||||
"storeIdent": f.storeIdent
|
||||
"name": f.name
|
||||
"primaryKey": f.primaryKey
|
||||
"ignoreCase": f.ignoreCase
|
||||
"goType": strings.Replace(f.goType, "types.", "\(typePkg).", 1)
|
||||
}]
|
||||
|
||||
filter: {
|
||||
// query fields as defined in struct
|
||||
"query": [ for name in res.filter.query {res.struct[name]}],
|
||||
|
||||
// filter by nil state as defined in filter
|
||||
"byNilState": [ for name in res.filter.byNilState {res.filter.struct[name]}]
|
||||
|
||||
// filter by false as defined in filter
|
||||
"byFalseState": [ for name in res.filter.byFalseState {res.filter.struct[name]}]
|
||||
|
||||
// filter by value as defined in filter
|
||||
// @todo this should be pulled from the struct
|
||||
"byValue": [ for name in res.filter.byValue {res.filter.struct[name]}]
|
||||
"byLabel": res.features.labels
|
||||
}
|
||||
|
||||
auxIdent: "aux\(expIdent)"
|
||||
auxStruct: struct
|
||||
|
||||
features: {
|
||||
paging: res.features.paging
|
||||
sorting: res.features.sorting
|
||||
checkFn: res.features.checkFn
|
||||
}
|
||||
|
||||
api: {
|
||||
if res.store.api != _|_ {
|
||||
_base: {
|
||||
"ident": res.store.ident
|
||||
"expStoreIdent": res.store.expIdentPlural
|
||||
"goType": goType
|
||||
"goFilterType": goFilterType
|
||||
"auxIdent": auxIdent
|
||||
}
|
||||
|
||||
deleteByPK: {
|
||||
primaryKeys: [ for f in res.struct if f.primaryKey {f} ]
|
||||
_pkExpNames: strings.Join([ for f in primaryKeys { f.expIdent } ], "")
|
||||
"expFnIdent": "Delete\(res.store.expIdent)By\(_pkExpNames)"
|
||||
}
|
||||
|
||||
lookups: [
|
||||
for l in res.store.api.lookups {
|
||||
_base
|
||||
|
||||
"expFnIdent": l.expIdent
|
||||
|
||||
if (l.description != _|_) {
|
||||
description: "// \(l.expIdent) " + strings.Join(strings.Split(l.description, "\n"), "\n// ")
|
||||
}
|
||||
|
||||
// Copy all relevant fields from the struct
|
||||
"args": [
|
||||
for name in l.fields {
|
||||
let f = res.struct[name]
|
||||
|
||||
"ident": f.ident
|
||||
"storeIdent": f.storeIdent
|
||||
"goType": f.goType
|
||||
"ignoreCase": f.ignoreCase
|
||||
},
|
||||
]
|
||||
|
||||
"nullConstraint": l.nullConstraint
|
||||
"returnType": "\(goType)"
|
||||
"collectionFnIdent": "\(res.store.ident)Collection"
|
||||
},
|
||||
]
|
||||
|
||||
// all additional store functions we need for this resource
|
||||
functions: [
|
||||
for f in res.store.api.functions {
|
||||
_base
|
||||
|
||||
"expFnIdent": f.expIdent
|
||||
|
||||
if (f.description != _|_) {
|
||||
description: "// \(f.expIdent) " + strings.Join(strings.Split(f.description, "\n"), "\n// ")
|
||||
}
|
||||
|
||||
"args": [ for a in f.args {
|
||||
"ident": a.ident
|
||||
"goType": strings.Replace(a.goType, "types.", "\(typePkg).", 1)
|
||||
"spread": a.spread
|
||||
}]
|
||||
"return": [ for r in f.return {strings.Replace(r, "types.", "\(typePkg).", 1)}]
|
||||
},
|
||||
]
|
||||
|
||||
sortableFields: {
|
||||
_base
|
||||
|
||||
"fnIdent": "sortable\(expIdent)Fields"
|
||||
|
||||
fields: {
|
||||
for f in res.struct if f.sortable || f.unique || f.primaryKey {
|
||||
{
|
||||
"\(strings.ToLower(f.name))": f.name
|
||||
"\(strings.ToLower(f.ident))": f.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectCursorValues: {
|
||||
_base
|
||||
|
||||
"fnIdent": "collect\(expIdent)CursorValues"
|
||||
|
||||
fields: [ for f in res.struct if f.sortable || f.unique || f.primaryKey {f} ]
|
||||
primaryKeys: [ for f in res.struct if f.primaryKey {f} ]
|
||||
}
|
||||
|
||||
checkConstraints: {
|
||||
_base
|
||||
|
||||
"fnIdent": "check\(expIdent)Constraints"
|
||||
|
||||
checks: [
|
||||
for lookup in res.store.api.lookups if lookup.constraintCheck {
|
||||
lookupFnIdent: lookup.expIdent
|
||||
fields: [ for name in lookup.fields {res.struct[name]}]
|
||||
nullConstraint: [
|
||||
for f in res.struct if list.Contains(lookup.nullConstraint, f.name) {
|
||||
"expIdent": f.expIdent
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settings: res.store.settings
|
||||
}
|
||||
}
|
||||
|
||||
// Codegen template payload, reused for multiple outputs
|
||||
_payload: {
|
||||
package: string | *"store"
|
||||
|
||||
imports: {
|
||||
// per-component type imports
|
||||
for cmp in app.corteza.components for res in cmp.resources if res.store != _|_ {
|
||||
"github.com/cortezaproject/corteza-server/\(cmp.ident)/types": "\(cmp.ident)Type"
|
||||
}
|
||||
|
||||
for res in app.corteza.resources if res.store != _|_ {
|
||||
"\(res.package.import)": "\(res.package.ident)Type"
|
||||
}
|
||||
|
||||
for cmp in app.corteza.components for res in cmp.resources for i in res.imports {
|
||||
"\(i.import)": ""
|
||||
}
|
||||
}
|
||||
|
||||
types: {
|
||||
// for each resource in every store with store and actions defined
|
||||
for cmp in app.corteza.components for res in cmp.resources if res.store != _|_ {
|
||||
// use _Store resource as a function (https://cuetorials.com/patterns/functions/)
|
||||
// and pass res(ource) and type-package string in as "arguments"
|
||||
"\(res.store.ident)": { _StoreResource & { "res": res, "typePkg": "\(cmp.ident)Type" } }.result
|
||||
},
|
||||
|
||||
for res in app.corteza.resources if res.store != _|_ {
|
||||
"\(res.store.ident)": { _StoreResource & { "res": res, "typePkg": "\(res.package.ident)Type" } }.result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[...schema.#codegen] &
|
||||
[
|
||||
{
|
||||
"bulk": [
|
||||
{
|
||||
"template": "gocode/store/interfaces.go.tpl"
|
||||
"output": "store/interfaces.gen.go"
|
||||
}, {
|
||||
"template": "gocode/store/rdbms/rdbms.go.tpl"
|
||||
"output": "store/adapters/rdbms/rdbms.gen.go"
|
||||
}, {
|
||||
"template": "gocode/store/rdbms/aux_types.go.tpl"
|
||||
"output": "store/adapters/rdbms/aux_types.gen.go"
|
||||
}, {
|
||||
"template": "gocode/store/rdbms/queries.go.tpl"
|
||||
"output": "store/adapters/rdbms/queries.gen.go"
|
||||
}, {
|
||||
"template": "gocode/store/rdbms/filters.go.tpl"
|
||||
"output": "store/adapters/rdbms/filters.gen.go"
|
||||
}, {
|
||||
"template": "gocode/store/tests/all.go.tpl"
|
||||
"output": "store/tests/all_test.go"
|
||||
},
|
||||
]
|
||||
|
||||
"payload": { _payload }
|
||||
},
|
||||
]
|
||||
+34
-18
@@ -7,16 +7,24 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/cli"
|
||||
)
|
||||
|
||||
type (
|
||||
inOut struct {
|
||||
Template string `json:"template"`
|
||||
Output string `json:"output"`
|
||||
Syntax string `json:"syntax"`
|
||||
}
|
||||
|
||||
task struct {
|
||||
Template string `json:"template"`
|
||||
Output string `json:"output"`
|
||||
Syntax string `json:"syntax"`
|
||||
Payload interface{} `json:"payload"`
|
||||
inOut
|
||||
|
||||
Bulk []inOut `json:"bulk"`
|
||||
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -49,32 +57,40 @@ func main() {
|
||||
err error
|
||||
)
|
||||
|
||||
print("Waiting for stdin ...\n")
|
||||
started := time.Now()
|
||||
print("Waiting for stdin ...")
|
||||
if err = input.Decode(&tasks); err != nil {
|
||||
cli.HandleError(fmt.Errorf("failed to decode input from standard input: %v", err))
|
||||
}
|
||||
|
||||
println(time.Now().Sub(started).Round(time.Second)/time.Second, "sec")
|
||||
|
||||
if tpl, err = loadTemplates(baseTemplate(), tplRootPath); err != nil {
|
||||
cli.HandleError(fmt.Errorf("failed to load templates: %v", err))
|
||||
}
|
||||
|
||||
for _, j := range tasks {
|
||||
output := path.Join(outputBase, j.Output)
|
||||
print(fmt.Sprintf("generating %s (from %s) ...", output, j.Template))
|
||||
|
||||
switch j.Syntax {
|
||||
case "go":
|
||||
err = writeFormattedGo(output, tpl.Lookup(j.Template), j.Payload)
|
||||
default:
|
||||
err = write(output, tpl.Lookup(j.Template), j.Payload)
|
||||
if len(j.Bulk) == 0 {
|
||||
j.Bulk = append(j.Bulk, j.inOut)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cli.HandleError(fmt.Errorf("failed to write template: %v", err))
|
||||
} else {
|
||||
print("done\n")
|
||||
}
|
||||
for _, o := range j.Bulk {
|
||||
output := path.Join(outputBase, o.Output)
|
||||
print(fmt.Sprintf("generating %s (from %s) ...", output, o.Template))
|
||||
|
||||
switch o.Syntax {
|
||||
case "go":
|
||||
err = writeFormattedGo(output, tpl.Lookup(o.Template), j.Payload)
|
||||
default:
|
||||
err = write(output, tpl.Lookup(o.Template), j.Payload)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cli.HandleError(fmt.Errorf("failed to write template: %v", err))
|
||||
} else {
|
||||
print("done\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,17 +47,19 @@ func loadTemplates(rTpl *template.Template, rootDir string) (*template.Template,
|
||||
func writeFormattedGo(dst string, tpl *template.Template, payload interface{}) error {
|
||||
return write(dst, tpl, payload, func(in io.ReadWriter) (out io.ReadWriter, err error) {
|
||||
var (
|
||||
bb []byte
|
||||
org, bb []byte
|
||||
)
|
||||
|
||||
if bb, err = ioutil.ReadAll(in); err != nil {
|
||||
if org, err = ioutil.ReadAll(in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if bb, err = format.Source(bb); err != nil {
|
||||
// output error and return unformatted source
|
||||
cp := bytes.NewBuffer(org)
|
||||
|
||||
if bb, err = format.Source(cp.Bytes()); err != nil {
|
||||
// output error and return un-formatted source
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%s fmt warn: %v\n", dst, err)
|
||||
return in, err
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
return bytes.NewBuffer(bb), nil
|
||||
|
||||
Reference in New Issue
Block a user