Remove obsolete compose/repository
This commit is contained in:
@@ -1,177 +0,0 @@
|
||||
package repository
|
||||
|
||||
//import (
|
||||
// "context"
|
||||
// "time"
|
||||
//
|
||||
// "github.com/Masterminds/squirrel"
|
||||
// "github.com/pkg/errors"
|
||||
// "github.com/titpetric/factory"
|
||||
//
|
||||
// "github.com/cortezaproject/corteza-server/compose/types"
|
||||
// "github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
//)
|
||||
//
|
||||
//type (
|
||||
// AttachmentRepository interface {
|
||||
// With(ctx context.Context, db *factory.DB) AttachmentRepository
|
||||
//
|
||||
// Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error)
|
||||
// FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error)
|
||||
// Create(mod *types.Attachment) (*types.Attachment, error)
|
||||
// DeleteByID(namespaceID, attachmentID uint64) error
|
||||
// }
|
||||
//
|
||||
// attachment struct {
|
||||
// *repository
|
||||
// }
|
||||
//)
|
||||
//
|
||||
//const (
|
||||
// ErrAttachmentNotFound = repositoryError("AttachmentNotFound")
|
||||
//)
|
||||
//
|
||||
//func Attachment(ctx context.Context, db *factory.DB) AttachmentRepository {
|
||||
// return (&attachment{}).With(ctx, db)
|
||||
//}
|
||||
//
|
||||
//func (r attachment) With(ctx context.Context, db *factory.DB) AttachmentRepository {
|
||||
// return &attachment{
|
||||
// repository: r.repository.With(ctx, db),
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (r attachment) table() string {
|
||||
// return "compose_attachment"
|
||||
//}
|
||||
//
|
||||
//func (r attachment) columns() []string {
|
||||
// return []string{
|
||||
// "a.id",
|
||||
// "a.rel_namespace",
|
||||
// "a.rel_owner",
|
||||
// "a.kind",
|
||||
// "a.url",
|
||||
// "a.preview_url",
|
||||
// "a.name",
|
||||
// "a.meta",
|
||||
// "a.created_at",
|
||||
// "a.updated_at",
|
||||
// "a.deleted_at",
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (r attachment) query() squirrel.SelectBuilder {
|
||||
// return squirrel.
|
||||
// Select(r.columns()...).
|
||||
// From(r.table() + " AS a").
|
||||
// Where("a.deleted_at IS NULL")
|
||||
//
|
||||
//}
|
||||
//
|
||||
//func (r attachment) FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error) {
|
||||
// return r.findOneBy(namespaceID, "id", attachmentID)
|
||||
//}
|
||||
//
|
||||
//func (r attachment) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Attachment, error) {
|
||||
// var (
|
||||
// p = &types.Attachment{}
|
||||
//
|
||||
// q = r.query().
|
||||
// Where(squirrel.Eq{field: value, "rel_namespace": namespaceID})
|
||||
//
|
||||
// err = rh.FetchOne(r.db(), q, p)
|
||||
// )
|
||||
//
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// } else if p.ID == 0 {
|
||||
// return nil, ErrAttachmentNotFound
|
||||
// }
|
||||
//
|
||||
// return p, nil
|
||||
//}
|
||||
//
|
||||
//func (r attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) {
|
||||
// f = filter
|
||||
//
|
||||
// if f.Sort == "" {
|
||||
// f.Sort = "id ASC"
|
||||
// }
|
||||
//
|
||||
// query := r.query().
|
||||
// Where(squirrel.Eq{"a.kind": f.Kind})
|
||||
//
|
||||
// if filter.NamespaceID > 0 {
|
||||
// query = query.Where("a.rel_namespace = ?", filter.NamespaceID)
|
||||
// }
|
||||
//
|
||||
// switch f.Kind {
|
||||
// case types.PageAttachment:
|
||||
// // @todo implement filtering by page
|
||||
// if f.PageID > 0 {
|
||||
// err = errors.New("filtering by pageID not implemented")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// case types.RecordAttachment:
|
||||
// query = query.
|
||||
// Join("compose_record_value AS v ON (v.ref = a.id)")
|
||||
//
|
||||
// if f.ModuleID > 0 {
|
||||
// query = query.
|
||||
// Join("compose_record AS r ON (r.id = v.record_id)").
|
||||
// Where(squirrel.Eq{"r.module_id": f.ModuleID})
|
||||
// }
|
||||
//
|
||||
// if f.RecordID > 0 {
|
||||
// query = query.Where(squirrel.Eq{"v.record_id": f.RecordID})
|
||||
// }
|
||||
//
|
||||
// if f.FieldName != "" {
|
||||
// query = query.Where(squirrel.Eq{"v.name": f.FieldName})
|
||||
// }
|
||||
//
|
||||
// default:
|
||||
// err = errors.New("unsupported kind value")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if f.Filter != "" {
|
||||
// err = errors.New("filtering by filter not implemented")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// var orderBy []string
|
||||
// if orderBy, err = rh.ParseOrder(f.Sort, r.columns()...); err != nil {
|
||||
// return
|
||||
// } else {
|
||||
// query = query.OrderBy(orderBy...)
|
||||
// }
|
||||
//
|
||||
// if f.Count, err = rh.Count(r.db(), query); err != nil || f.Count == 0 {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// return set, f, rh.FetchPaged(r.db(), query, f.PageFilter, &set)
|
||||
//}
|
||||
//
|
||||
//func (r attachment) Create(mod *types.Attachment) (*types.Attachment, error) {
|
||||
// if mod.ID == 0 {
|
||||
// mod.ID = factory.Sonyflake.NextID()
|
||||
// }
|
||||
//
|
||||
// mod.CreatedAt = time.Now()
|
||||
//
|
||||
// return mod, r.db().Insert(r.table(), mod)
|
||||
//}
|
||||
//
|
||||
//func (r attachment) DeleteByID(namespaceID, attachmentID uint64) error {
|
||||
// _, err := r.db().Exec(
|
||||
// "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?",
|
||||
// namespaceID,
|
||||
// attachmentID,
|
||||
// )
|
||||
//
|
||||
// return err
|
||||
//}
|
||||
@@ -1,151 +0,0 @@
|
||||
package repository
|
||||
|
||||
//type (
|
||||
// ChartRepository interface {
|
||||
// With(ctx context.Context, db *factory.DB) ChartRepository
|
||||
//
|
||||
// FindByID(namespaceID, chartID uint64) (*types.Chart, error)
|
||||
// FindByHandle(namespaceID uint64, handle string) (c *types.Chart, err error)
|
||||
// Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error)
|
||||
// Create(mod *types.Chart) (*types.Chart, error)
|
||||
// Update(mod *types.Chart) (*types.Chart, error)
|
||||
// DeleteByID(namespaceID, chartID uint64) error
|
||||
// }
|
||||
//
|
||||
// chart struct {
|
||||
// *repository
|
||||
// }
|
||||
//)
|
||||
//
|
||||
//const (
|
||||
// ErrChartNotFound = repositoryError("ChartNotFound")
|
||||
// ErrChartHandleNotUnique = repositoryError("ChartHandleNotUnique")
|
||||
//)
|
||||
//
|
||||
//func Chart(ctx context.Context, db *factory.DB) ChartRepository {
|
||||
// return (&chart{}).With(ctx, db)
|
||||
//}
|
||||
//
|
||||
//func (r chart) With(ctx context.Context, db *factory.DB) ChartRepository {
|
||||
// return &chart{
|
||||
// repository: r.repository.With(ctx, db),
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (r chart) table() string {
|
||||
// return "compose_chart"
|
||||
//}
|
||||
//
|
||||
//func (r chart) columns() []string {
|
||||
// return []string{
|
||||
// "id",
|
||||
// "rel_namespace",
|
||||
// "handle",
|
||||
// "name",
|
||||
// "config",
|
||||
// "created_at",
|
||||
// "updated_at",
|
||||
// "deleted_at",
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (r chart) query() squirrel.SelectBuilder {
|
||||
// return squirrel.
|
||||
// Select(r.columns()...).
|
||||
// From(r.table()).
|
||||
// Where("deleted_at IS NULL")
|
||||
//}
|
||||
//
|
||||
//func (r chart) FindByID(namespaceID, chartID uint64) (*types.Chart, error) {
|
||||
// return r.findOneBy(namespaceID, "id", chartID)
|
||||
//}
|
||||
//
|
||||
//func (r chart) FindByHandle(namespaceID uint64, handle string) (*types.Chart, error) {
|
||||
// return r.findOneBy(namespaceID, "LOWER(handle)", strings.ToLower(strings.TrimSpace(handle)))
|
||||
//}
|
||||
//
|
||||
//func (r chart) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Chart, error) {
|
||||
// var (
|
||||
// c = &types.Chart{}
|
||||
//
|
||||
// q = r.query().
|
||||
// Where(squirrel.Eq{field: value, "rel_namespace": namespaceID})
|
||||
//
|
||||
// err = rh.FetchOne(r.db(), q, c)
|
||||
// )
|
||||
//
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// } else if c.ID == 0 {
|
||||
// return nil, ErrChartNotFound
|
||||
// }
|
||||
//
|
||||
// return c, nil
|
||||
//}
|
||||
//
|
||||
//func (r chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) {
|
||||
// //f = filter
|
||||
// //
|
||||
// //if f.Sort == "" {
|
||||
// // f.Sort = "id ASC"
|
||||
// //}
|
||||
// //
|
||||
// //query := r.query()
|
||||
// //
|
||||
// //if filter.NamespaceID > 0 {
|
||||
// // query = query.Where(squirrel.Eq{"rel_namespace": filter.NamespaceID})
|
||||
// //}
|
||||
// //
|
||||
// //if f.Query != "" {
|
||||
// // q := "%" + strings.ToLower(f.Query) + "%"
|
||||
// // query = query.Where(squirrel.Or{
|
||||
// // squirrel.Like{"LOWER(name)": q},
|
||||
// // })
|
||||
// //}
|
||||
// //
|
||||
// //if f.Handle != "" {
|
||||
// // query = query.Where("LOWER(handle) = LOWER(?)", f.Handle)
|
||||
// //}
|
||||
// //
|
||||
// //if f.IsReadable != nil {
|
||||
// // query = query.Where(f.IsReadable)
|
||||
// //}
|
||||
// //
|
||||
// //var orderBy []string
|
||||
// //if orderBy, err = rh.ParseOrder(f.Sort, r.columns()...); err != nil {
|
||||
// // return
|
||||
// //} else {
|
||||
// // query = query.OrderBy(orderBy...)
|
||||
// //}
|
||||
// //
|
||||
// //if f.Count, err = rh.Count(r.db(), query); err != nil || f.Count == 0 {
|
||||
// // return
|
||||
// //}
|
||||
// //
|
||||
// //return set, f, rh.FetchPaged(r.db(), query, f.PageFilter, &set)
|
||||
// return nil, f, fmt.Errorf("deprecated")
|
||||
//}
|
||||
//
|
||||
//func (r chart) Create(mod *types.Chart) (*types.Chart, error) {
|
||||
// mod.ID = factory.Sonyflake.NextID()
|
||||
// rh.SetCurrentTimeRounded(&mod.CreatedAt)
|
||||
// mod.UpdatedAt = nil
|
||||
//
|
||||
// return mod, r.db().Insert(r.table(), mod)
|
||||
//}
|
||||
//
|
||||
//func (r chart) Update(mod *types.Chart) (*types.Chart, error) {
|
||||
// rh.SetCurrentTimeRounded(&mod.UpdatedAt)
|
||||
//
|
||||
// return mod, r.db().Update(r.table(), mod, "id")
|
||||
//}
|
||||
//
|
||||
//func (r chart) DeleteByID(namespaceID, chartID uint64) error {
|
||||
// _, err := r.db().Exec(
|
||||
// "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?",
|
||||
// namespaceID,
|
||||
// chartID,
|
||||
// )
|
||||
//
|
||||
// return err
|
||||
//}
|
||||
@@ -1,21 +0,0 @@
|
||||
package repository
|
||||
|
||||
type (
|
||||
repositoryError string
|
||||
)
|
||||
|
||||
const (
|
||||
ErrNotImplemented = repositoryError("NotImplemented")
|
||||
)
|
||||
|
||||
func (e repositoryError) Error() string {
|
||||
return e.String()
|
||||
}
|
||||
|
||||
func (e repositoryError) String() string {
|
||||
return "compose.repository." + string(e)
|
||||
}
|
||||
|
||||
func (e repositoryError) Eq(err error) bool {
|
||||
return err != nil && e.Error() == err.Error()
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
)
|
||||
|
||||
type (
|
||||
ModuleRepository interface {
|
||||
With(ctx context.Context, db *factory.DB) ModuleRepository
|
||||
|
||||
FindByID(namespaceID, moduleID uint64) (*types.Module, error)
|
||||
FindByName(namespaceID uint64, name string) (*types.Module, error)
|
||||
FindByHandle(namespaceID uint64, handle string) (*types.Module, error)
|
||||
Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error)
|
||||
FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err error)
|
||||
Create(mod *types.Module) (*types.Module, error)
|
||||
Update(mod *types.Module) (*types.Module, error)
|
||||
UpdateFields(moduleID uint64, ff types.ModuleFieldSet, hasRecords bool) (err error)
|
||||
DeleteByID(namespaceID, moduleID uint64) error
|
||||
}
|
||||
|
||||
module struct {
|
||||
*repository
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
ErrModuleNotFound = repositoryError("ModuleNotFound")
|
||||
//ErrModuleNameNotUnique = repositoryError("ModuleNameNotUnique")
|
||||
//ErrModuleHandleNotUnique = repositoryError("ModuleHandleNotUnique")
|
||||
)
|
||||
|
||||
func Module(ctx context.Context, db *factory.DB) ModuleRepository {
|
||||
return (&module{}).With(ctx, db)
|
||||
}
|
||||
|
||||
func (r module) With(ctx context.Context, db *factory.DB) ModuleRepository {
|
||||
return &module{
|
||||
repository: r.repository.With(ctx, db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r module) table() string {
|
||||
return "compose_module"
|
||||
}
|
||||
|
||||
func (r module) tableFields() string {
|
||||
return "compose_module_field"
|
||||
}
|
||||
|
||||
func (r module) columns() []string {
|
||||
return []string{
|
||||
"id",
|
||||
"rel_namespace",
|
||||
"handle",
|
||||
"name",
|
||||
"json",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
}
|
||||
}
|
||||
|
||||
func (r module) query() squirrel.SelectBuilder {
|
||||
return squirrel.
|
||||
Select(r.columns()...).
|
||||
From(r.table()).
|
||||
Where("deleted_at IS NULL")
|
||||
|
||||
}
|
||||
|
||||
func (r module) FindByID(namespaceID, moduleID uint64) (*types.Module, error) {
|
||||
return r.findOneBy(namespaceID, "id", moduleID)
|
||||
}
|
||||
|
||||
func (r module) FindByHandle(namespaceID uint64, handle string) (*types.Module, error) {
|
||||
return r.findOneBy(namespaceID, "LOWER(handle)", strings.ToLower(strings.TrimSpace(handle)))
|
||||
}
|
||||
|
||||
func (r module) FindByName(namespaceID uint64, name string) (*types.Module, error) {
|
||||
return r.findOneBy(namespaceID, "LOWER(name)", strings.ToLower(strings.TrimSpace(name)))
|
||||
}
|
||||
|
||||
func (r module) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Module, error) {
|
||||
var (
|
||||
m = &types.Module{}
|
||||
|
||||
q = r.query().
|
||||
Where(squirrel.Eq{field: value, "rel_namespace": namespaceID})
|
||||
|
||||
err = rh.FetchOne(r.db(), q, m)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if m.ID == 0 {
|
||||
return nil, ErrModuleNotFound
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (r module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) {
|
||||
//f = filter
|
||||
//
|
||||
//if f.Sort == "" {
|
||||
// f.Sort = "id ASC"
|
||||
//}
|
||||
//
|
||||
//query := r.query()
|
||||
//
|
||||
//if filter.NamespaceID > 0 {
|
||||
// query = query.Where("rel_namespace = ?", filter.NamespaceID)
|
||||
//}
|
||||
//
|
||||
//if f.Query != "" {
|
||||
// q := "%" + strings.ToLower(f.Query) + "%"
|
||||
// query = query.Where(squirrel.Or{
|
||||
// squirrel.Like{"LOWER(name)": q},
|
||||
// squirrel.Like{"LOWER(handle)": q},
|
||||
// })
|
||||
//}
|
||||
//
|
||||
//if f.Name != "" {
|
||||
// query = query.Where(squirrel.Eq{"LOWER(name)": strings.ToLower(f.Name)})
|
||||
//}
|
||||
//
|
||||
//if f.Handle != "" {
|
||||
// query = query.Where(squirrel.Eq{"LOWER(handle)": strings.ToLower(f.Handle)})
|
||||
//}
|
||||
//
|
||||
//if f.IsReadable != nil {
|
||||
// query = query.Where(f.IsReadable)
|
||||
//}
|
||||
//
|
||||
//var orderBy []string
|
||||
//if orderBy, err = rh.ParseOrder(f.Sort, r.columns()...); err != nil {
|
||||
// return
|
||||
//} else {
|
||||
// query = query.OrderBy(orderBy...)
|
||||
//}
|
||||
//
|
||||
//if f.Count, err = rh.Count(r.db(), query); err != nil || f.Count == 0 {
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//return set, f, rh.FetchPaged(r.db(), query, f.PageFilter, &set)
|
||||
return nil, f, fmt.Errorf("deprecated")
|
||||
}
|
||||
|
||||
func (r module) Create(mod *types.Module) (*types.Module, error) {
|
||||
var err error
|
||||
|
||||
mod.ID = factory.Sonyflake.NextID()
|
||||
rh.SetCurrentTimeRounded(&mod.CreatedAt)
|
||||
mod.UpdatedAt = nil
|
||||
|
||||
if err = r.db().Insert(r.table(), mod); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mod, nil
|
||||
}
|
||||
|
||||
func (r module) Update(mod *types.Module) (*types.Module, error) {
|
||||
rh.SetCurrentTimeRounded(&mod.UpdatedAt)
|
||||
|
||||
return mod, r.db().Update(r.table(), mod, "id")
|
||||
}
|
||||
|
||||
func (r module) UpdateFields(moduleID uint64, ff types.ModuleFieldSet, hasRecords bool) error {
|
||||
if existing, err := r.FindFields(moduleID); err != nil {
|
||||
return err
|
||||
} else {
|
||||
// Remove fields that do not exist anymore
|
||||
err = existing.Walk(func(e *types.ModuleField) error {
|
||||
if ff.FindByID(e.ID) == nil {
|
||||
return r.deleteFieldByID(moduleID, e.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for idx, f := range ff {
|
||||
if e := existing.FindByID(f.ID); e != nil {
|
||||
f.CreatedAt = e.CreatedAt
|
||||
|
||||
// We do not have any other code in place that would handle changes of field name and kind, so we need
|
||||
// to reset any changes made to the field.
|
||||
// @todo remove when we are able to handle field rename & type change
|
||||
if hasRecords {
|
||||
f.Name = e.Name
|
||||
f.Kind = e.Kind
|
||||
} else {
|
||||
rh.SetCurrentTimeRounded(&f.UpdatedAt)
|
||||
}
|
||||
} else {
|
||||
f.ID = 0
|
||||
}
|
||||
|
||||
if f.ID == 0 {
|
||||
f.ID = factory.Sonyflake.NextID()
|
||||
rh.SetCurrentTimeRounded(&f.CreatedAt)
|
||||
}
|
||||
|
||||
f.ModuleID = moduleID
|
||||
f.Place = idx
|
||||
f.DeletedAt = nil
|
||||
|
||||
if err := r.db().Replace(r.tableFields(), f); err != nil {
|
||||
return errors.Wrap(err, "Error updating module fields")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r module) deleteFieldByID(moduleID, fieldID uint64) error {
|
||||
_, err := r.db().Exec(
|
||||
fmt.Sprintf("DELETE FROM %s WHERE rel_module = ? AND id = ?", r.tableFields()),
|
||||
moduleID,
|
||||
fieldID,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r module) DeleteByID(namespaceID, moduleID uint64) error {
|
||||
_, err := r.db().Exec(
|
||||
fmt.Sprintf("UPDATE %s SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", r.table()),
|
||||
namespaceID,
|
||||
moduleID,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r module) FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err error) {
|
||||
if len(moduleIDs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
query := `SELECT id, rel_module, place,
|
||||
kind, name, label, options,
|
||||
is_private, is_required, is_visible, is_multi, default_value,
|
||||
created_at, updated_at, deleted_at
|
||||
FROM %s
|
||||
WHERE rel_module IN (?)
|
||||
AND deleted_at IS NULL
|
||||
ORDER BY rel_module, place`
|
||||
|
||||
query = fmt.Sprintf(query, r.tableFields())
|
||||
|
||||
if sql, args, err := sqlx.In(query, moduleIDs); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return ff, r.db().Select(&ff, sql, args...)
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
)
|
||||
|
||||
type (
|
||||
NamespaceRepository interface {
|
||||
With(ctx context.Context, db *factory.DB) NamespaceRepository
|
||||
|
||||
FindByID(id uint64) (*types.Namespace, error)
|
||||
FindBySlug(slug string) (*types.Namespace, error)
|
||||
Find(filter types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error)
|
||||
Create(mod *types.Namespace) (*types.Namespace, error)
|
||||
Update(mod *types.Namespace) (*types.Namespace, error)
|
||||
DeleteByID(id uint64) error
|
||||
}
|
||||
|
||||
namespace struct {
|
||||
*repository
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
ErrNamespaceNotFound = repositoryError("NamespaceNotFound")
|
||||
ErrNamespaceSlugNotUnique = repositoryError("NamespaceSlugNotUnique")
|
||||
ErrNamespaceInvalidSlugFormat = repositoryError("NamespaceInvalidSlugFormat")
|
||||
)
|
||||
|
||||
func Namespace(ctx context.Context, db *factory.DB) NamespaceRepository {
|
||||
return (&namespace{}).With(ctx, db)
|
||||
}
|
||||
|
||||
func (r namespace) table() string {
|
||||
return "compose_namespace"
|
||||
}
|
||||
|
||||
func (r namespace) columns() []string {
|
||||
return []string{
|
||||
"id",
|
||||
"name",
|
||||
"slug",
|
||||
"enabled",
|
||||
"meta",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
}
|
||||
}
|
||||
|
||||
func (r namespace) query() squirrel.SelectBuilder {
|
||||
return squirrel.
|
||||
Select(r.columns()...).
|
||||
From(r.table()).
|
||||
Where("deleted_at IS NULL")
|
||||
|
||||
}
|
||||
|
||||
func (r *namespace) With(ctx context.Context, db *factory.DB) NamespaceRepository {
|
||||
return &namespace{
|
||||
repository: r.repository.With(ctx, db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *namespace) FindByID(namespaceID uint64) (*types.Namespace, error) {
|
||||
return r.findOneBy("id", namespaceID)
|
||||
}
|
||||
|
||||
func (r *namespace) FindBySlug(slug string) (*types.Namespace, error) {
|
||||
return r.findOneBy("slug", slug)
|
||||
}
|
||||
|
||||
func (r *namespace) findOneBy(field string, value interface{}) (*types.Namespace, error) {
|
||||
var (
|
||||
ns = &types.Namespace{}
|
||||
|
||||
q = r.query().
|
||||
Where(squirrel.Eq{field: value})
|
||||
|
||||
err = rh.FetchOne(r.db(), q, ns)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if ns.ID == 0 {
|
||||
return nil, ErrNamespaceNotFound
|
||||
}
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (r *namespace) Find(filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error) {
|
||||
//f = filter
|
||||
//
|
||||
//if f.Sort == "" {
|
||||
// f.Sort = "id ASC"
|
||||
//}
|
||||
//
|
||||
//query := r.query()
|
||||
//if f.Query != "" {
|
||||
// q := "%" + strings.ToLower(f.Query) + "%"
|
||||
// query = query.Where(squirrel.Or{
|
||||
// squirrel.Like{"LOWER(name)": q},
|
||||
// squirrel.Like{"LOWER(slug)": q},
|
||||
// })
|
||||
//}
|
||||
//
|
||||
//if f.Slug != "" {
|
||||
// query = query.Where(squirrel.Eq{"LOWER(slug)": strings.ToLower(f.Slug)})
|
||||
//}
|
||||
//
|
||||
//if f.IsReadable != nil {
|
||||
// query = query.Where(f.IsReadable)
|
||||
//}
|
||||
//
|
||||
//var orderBy []string
|
||||
//if orderBy, err = rh.ParseOrder(f.Sort, r.columns()...); err != nil {
|
||||
// return
|
||||
//} else {
|
||||
// query = query.OrderBy(orderBy...)
|
||||
//}
|
||||
//
|
||||
//if f.Count, err = rh.Count(r.db(), query); err != nil || f.Count == 0 {
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//return set, f, rh.FetchPaged(r.db(), query, f.PageFilter, &set)
|
||||
return nil, f, fmt.Errorf("deprecated")
|
||||
}
|
||||
|
||||
func (r *namespace) Create(mod *types.Namespace) (*types.Namespace, error) {
|
||||
mod.ID = factory.Sonyflake.NextID()
|
||||
rh.SetCurrentTimeRounded(&mod.CreatedAt)
|
||||
mod.UpdatedAt = nil
|
||||
|
||||
return mod, r.db().Insert(r.table(), mod)
|
||||
}
|
||||
|
||||
func (r *namespace) Update(mod *types.Namespace) (*types.Namespace, error) {
|
||||
rh.SetCurrentTimeRounded(&mod.UpdatedAt)
|
||||
|
||||
return mod, r.db().Update(r.table(), mod, "id")
|
||||
}
|
||||
|
||||
func (r *namespace) DeleteByID(namespaceID uint64) error {
|
||||
_, err := r.db().Exec("UPDATE "+r.table()+" SET deleted_at = NOW() WHERE id = ?", namespaceID)
|
||||
return err
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/titpetric/factory"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
)
|
||||
|
||||
type (
|
||||
PageRepository interface {
|
||||
With(ctx context.Context, db *factory.DB) PageRepository
|
||||
|
||||
FindByID(namespaceID, pageID uint64) (*types.Page, error)
|
||||
FindByHandle(namespaceID uint64, handle string) (*types.Page, error)
|
||||
FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error)
|
||||
Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error)
|
||||
|
||||
Create(mod *types.Page) (*types.Page, error)
|
||||
Update(mod *types.Page) (*types.Page, error)
|
||||
DeleteByID(namespaceID, pageID uint64) error
|
||||
|
||||
Reorder(namespaceID, selfID uint64, pageIDs []uint64) error
|
||||
}
|
||||
|
||||
page struct {
|
||||
*repository
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
ErrPageNotFound = repositoryError("PageNotFound")
|
||||
ErrPageHandleNotUnique = repositoryError("PageHandleNotUnique")
|
||||
)
|
||||
|
||||
func Page(ctx context.Context, db *factory.DB) PageRepository {
|
||||
return (&page{}).With(ctx, db)
|
||||
}
|
||||
|
||||
func (r page) With(ctx context.Context, db *factory.DB) PageRepository {
|
||||
return &page{
|
||||
repository: r.repository.With(ctx, db),
|
||||
}
|
||||
}
|
||||
|
||||
func (r page) table() string {
|
||||
return "compose_page"
|
||||
}
|
||||
|
||||
func (r page) columns() []string {
|
||||
return []string{
|
||||
"id",
|
||||
"rel_namespace",
|
||||
"self_id",
|
||||
"rel_module",
|
||||
"handle",
|
||||
"title",
|
||||
"blocks",
|
||||
"description",
|
||||
"visible",
|
||||
"weight",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
}
|
||||
}
|
||||
|
||||
func (r page) query() squirrel.SelectBuilder {
|
||||
return squirrel.
|
||||
Select(r.columns()...).
|
||||
From(r.table()).
|
||||
Where("deleted_at IS NULL")
|
||||
}
|
||||
|
||||
func (r page) FindByID(namespaceID, pageID uint64) (*types.Page, error) {
|
||||
return r.findOneBy(namespaceID, "id", pageID)
|
||||
}
|
||||
|
||||
func (r page) FindByHandle(namespaceID uint64, handle string) (*types.Page, error) {
|
||||
return r.findOneBy(namespaceID, "handle", handle)
|
||||
}
|
||||
|
||||
func (r page) FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) {
|
||||
return r.findOneBy(namespaceID, "rel_module", moduleID)
|
||||
}
|
||||
|
||||
func (r page) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Page, error) {
|
||||
var (
|
||||
p = &types.Page{}
|
||||
|
||||
q = r.query().
|
||||
Where(squirrel.Eq{field: value, "rel_namespace": namespaceID})
|
||||
|
||||
err = rh.FetchOne(r.db(), q, p)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if p.ID == 0 {
|
||||
return nil, ErrPageNotFound
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (r page) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) {
|
||||
//f = filter
|
||||
//
|
||||
//if f.Sort == "" {
|
||||
// f.Sort = "id ASC"
|
||||
//}
|
||||
//
|
||||
//query := r.query()
|
||||
//
|
||||
//if filter.NamespaceID > 0 {
|
||||
// query = query.Where("rel_namespace = ?", filter.NamespaceID)
|
||||
//}
|
||||
//
|
||||
//if filter.ParentID > 0 {
|
||||
// query = query.Where("self_id = ?", filter.ParentID)
|
||||
//} else if filter.Root {
|
||||
// query = query.Where("self_id = 0")
|
||||
//}
|
||||
//
|
||||
//if f.Handle != "" {
|
||||
// query = query.Where("LOWER(handle) = ?", strings.ToLower(f.Handle))
|
||||
//}
|
||||
//
|
||||
//if f.Query != "" {
|
||||
// q := "%" + strings.ToLower(f.Query) + "%"
|
||||
// query = query.Where(squirrel.Or{
|
||||
// squirrel.Like{"LOWER(title)": q},
|
||||
// squirrel.Like{"LOWER(description)": q},
|
||||
// })
|
||||
//}
|
||||
//
|
||||
//if f.IsReadable != nil {
|
||||
// query = query.Where(f.IsReadable)
|
||||
//}
|
||||
//
|
||||
//var orderBy []string
|
||||
//if orderBy, err = rh.ParseOrder(f.Sort, r.columns()...); err != nil {
|
||||
// return
|
||||
//} else {
|
||||
// query = query.OrderBy(orderBy...)
|
||||
//}
|
||||
//
|
||||
//if f.Count, err = rh.Count(r.db(), query); err != nil || f.Count == 0 {
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//return set, f, rh.FetchPaged(r.db(), query, f.PageFilter, &set)
|
||||
return nil, f, fmt.Errorf("deprecated")
|
||||
}
|
||||
|
||||
func (r page) Reorder(namespaceID, parentID uint64, pageIDs []uint64) error {
|
||||
var (
|
||||
pageMap = map[uint64]bool{}
|
||||
filter = types.PageFilter{NamespaceID: namespaceID, ParentID: parentID}
|
||||
)
|
||||
|
||||
if pages, _, err := r.Find(filter); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
for _, page := range pages {
|
||||
pageMap[page.ID] = true
|
||||
}
|
||||
}
|
||||
weight := 1
|
||||
db := r.db()
|
||||
// honor parameter first
|
||||
for _, pageID := range pageIDs {
|
||||
if pageMap[pageID] {
|
||||
pageMap[pageID] = false
|
||||
if _, err := db.Exec("UPDATE compose_page SET weight = ? WHERE id = ? AND self_id = ?", weight, pageID, parentID); err != nil {
|
||||
return err
|
||||
}
|
||||
weight++
|
||||
}
|
||||
}
|
||||
for pageID, update := range pageMap {
|
||||
if update {
|
||||
if _, err := db.Exec("UPDATE compose_page SET weight = ? WHERE id = ? AND self_id = ?", weight, pageID, parentID); err != nil {
|
||||
return err
|
||||
}
|
||||
weight++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r page) Create(mod *types.Page) (*types.Page, error) {
|
||||
mod.ID = factory.Sonyflake.NextID()
|
||||
rh.SetCurrentTimeRounded(&mod.CreatedAt)
|
||||
mod.UpdatedAt = nil
|
||||
|
||||
return mod, r.db().Insert(r.table(), mod)
|
||||
}
|
||||
|
||||
func (r page) Update(mod *types.Page) (*types.Page, error) {
|
||||
rh.SetCurrentTimeRounded(&mod.UpdatedAt)
|
||||
|
||||
return mod, r.db().Update(r.table(), mod, "id")
|
||||
}
|
||||
|
||||
func (r page) DeleteByID(namespaceID, pageID uint64) error {
|
||||
_, err := r.db().Exec(
|
||||
"UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?",
|
||||
namespaceID,
|
||||
pageID,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1,387 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/titpetric/factory"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
RecordRepository interface {
|
||||
With(ctx context.Context, db *factory.DB) RecordRepository
|
||||
|
||||
FindByID(namespaceID, recordID uint64) (*types.Record, error)
|
||||
|
||||
Report(module *types.Module, metrics, dimensions, filter string) (results interface{}, err error)
|
||||
Find(module *types.Module, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error)
|
||||
Export(module *types.Module, filter types.RecordFilter) (set types.RecordSet, err error)
|
||||
|
||||
Create(record *types.Record) (*types.Record, error)
|
||||
Update(record *types.Record) (*types.Record, error)
|
||||
Delete(record *types.Record) error
|
||||
|
||||
RefValueLookup(moduleID uint64, field string, ref uint64) (recordID uint64, err error)
|
||||
LoadValues(fieldNames []string, IDs []uint64) (rvs types.RecordValueSet, err error)
|
||||
DeleteValues(record *types.Record) error
|
||||
UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error)
|
||||
PartialUpdateValues(rvs ...*types.RecordValue) (err error)
|
||||
}
|
||||
|
||||
record struct {
|
||||
*repository
|
||||
}
|
||||
)
|
||||
|
||||
//
|
||||
const (
|
||||
ErrRecordNotFound = repositoryError("RecordNotFound")
|
||||
)
|
||||
|
||||
//
|
||||
func Record(ctx context.Context, db *factory.DB) RecordRepository {
|
||||
return nil //.With(ctx, db)
|
||||
}
|
||||
|
||||
//
|
||||
//func (r record) With(ctx context.Context, db *factory.DB) RecordRepository {
|
||||
// return &record{
|
||||
// repository: r.repository.With(ctx, db),
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (r record) table() string {
|
||||
// return "compose_record"
|
||||
//}
|
||||
//
|
||||
//func (r record) columns() []string {
|
||||
// return []string{
|
||||
// "r.id",
|
||||
// "r.module_id",
|
||||
// "r.rel_namespace",
|
||||
// "r.owned_by",
|
||||
// "r.created_at",
|
||||
// "r.created_by",
|
||||
// "r.updated_at",
|
||||
// "r.updated_by",
|
||||
// "r.deleted_at",
|
||||
// "r.deleted_by",
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (r record) query() squirrel.SelectBuilder {
|
||||
// return squirrel.
|
||||
// Select(r.columns()...).
|
||||
// From(r.table() + " AS r")
|
||||
//}
|
||||
//
|
||||
//// @todo: update to accepted DeletedAt column semantics from Messaging
|
||||
//
|
||||
//func (r record) FindByID(namespaceID, recordID uint64) (*types.Record, error) {
|
||||
// return r.findOneBy(namespaceID, "id", recordID)
|
||||
//}
|
||||
//
|
||||
//func (r record) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Record, error) {
|
||||
// var (
|
||||
// rec = &types.Record{}
|
||||
//
|
||||
// q = r.query().
|
||||
// Where("r.deleted_at IS NULL").
|
||||
// Where(squirrel.Eq{field: value, "rel_namespace": namespaceID})
|
||||
//
|
||||
// err = rh.FetchOne(r.db(), q, rec)
|
||||
// )
|
||||
//
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// } else if rec.ID == 0 {
|
||||
// return nil, ErrRecordNotFound
|
||||
// }
|
||||
//
|
||||
// return rec, nil
|
||||
//}
|
||||
//
|
||||
//func (r record) Report(module *types.Module, metrics, dimensions, filter string) (results interface{}, err error) {
|
||||
// crb := NewRecordReportBuilder(module)
|
||||
//
|
||||
// var result = make([]map[string]interface{}, 0)
|
||||
//
|
||||
// if query, args, err := crb.Build(metrics, dimensions, filter); err != nil {
|
||||
// return nil, errors.Wrap(err, "can not generate report query")
|
||||
// } else if rows, err := r.db().Query(query, args...); err != nil {
|
||||
// return nil, errors.Wrapf(err, "can not execute report query (%s)", query)
|
||||
// } else {
|
||||
// for rows.Next() {
|
||||
// result = append(result, crb.Cast(rows))
|
||||
// }
|
||||
//
|
||||
// return result, nil
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (r record) Find(module *types.Module, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) {
|
||||
// var query squirrel.SelectBuilder
|
||||
// f = filter
|
||||
//
|
||||
// query, err = r.buildQuery(module, filter)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if f.Count, err = rh.Count(r.db(), query); err != nil || f.Count == 0 {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// return set, f, rh.FetchPaged(r.db(), query, f.PageFilter, &set)
|
||||
//}
|
||||
//
|
||||
//// Export ignores paging and does not return filter
|
||||
////
|
||||
//// @todo optimize and include value loading
|
||||
//func (r record) Export(module *types.Module, filter types.RecordFilter) (set types.RecordSet, err error) {
|
||||
// filter.PageFilter = rh.PageFilter{}
|
||||
//
|
||||
// query, err := r.buildQuery(module, filter)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// return set, rh.FetchAll(r.db(), query, &set)
|
||||
//}
|
||||
//
|
||||
//func (r record) buildQuery(module *types.Module, f types.RecordFilter) (query squirrel.SelectBuilder, err error) {
|
||||
// var (
|
||||
// joinedFields = []string{}
|
||||
// alreadyJoined = func(f string) bool {
|
||||
// for _, a := range joinedFields {
|
||||
// if a == f {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// joinedFields = append(joinedFields, f)
|
||||
// return false
|
||||
// }
|
||||
//
|
||||
// identResolver = func(i ql.Ident) (ql.Ident, error) {
|
||||
// var is bool
|
||||
// if i.Value, is = isRealRecordCol(i.Value); is {
|
||||
// i.Value += " "
|
||||
// return i, nil
|
||||
// }
|
||||
//
|
||||
// if !module.Fields.HasName(i.Value) {
|
||||
// return i, errors.Errorf("unknown field %q", i.Value)
|
||||
// }
|
||||
//
|
||||
// if !alreadyJoined(i.Value) {
|
||||
// query = query.LeftJoin(fmt.Sprintf(
|
||||
// "compose_record_value AS rv_%s ON (rv_%s.record_id = r.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)",
|
||||
// i.Value, i.Value, i.Value, i.Value,
|
||||
// ), i.Value)
|
||||
// }
|
||||
//
|
||||
// field := module.Fields.FindByName(i.Value)
|
||||
//
|
||||
// switch true {
|
||||
// case field.IsBoolean():
|
||||
// i.Value = fmt.Sprintf("(rv_%s.value NOT IN ('', '0', 'false', 'f', 'FALSE', 'F', false))", i.Value)
|
||||
// case field.IsNumeric():
|
||||
// i.Value = fmt.Sprintf("CAST(rv_%s.value AS SIGNED)", i.Value)
|
||||
// case field.IsDateTime():
|
||||
// i.Value = fmt.Sprintf("CAST(rv_%s.value AS DATETIME)", i.Value)
|
||||
// case field.IsRef():
|
||||
// i.Value = fmt.Sprintf("rv_%s.ref ", i.Value)
|
||||
// default:
|
||||
// i.Value = fmt.Sprintf("rv_%s.value ", i.Value)
|
||||
// }
|
||||
//
|
||||
// return i, nil
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// // Create query for fetching and counting records.
|
||||
// query = r.query().
|
||||
// Where("r.module_id = ?", module.ID).
|
||||
// Where("r.rel_namespace = ?", module.NamespaceID)
|
||||
//
|
||||
// // Inc/exclude deleted records according to filter settings
|
||||
// query = rh.FilterNullByState(query, "r.deleted_at", f.Deleted)
|
||||
//
|
||||
// // Parse filters.
|
||||
// if f.Query != "" {
|
||||
// var (
|
||||
// // Filter parser
|
||||
// fp = ql.NewParser()
|
||||
//
|
||||
// // Filter node
|
||||
// fn ql.ASTNode
|
||||
// )
|
||||
//
|
||||
// // Resolve all identifiers found in the query
|
||||
// // into their table/column counterparts
|
||||
// fp.OnIdent = identResolver
|
||||
//
|
||||
// if fn, err = fp.ParseExpression(f.Query); err != nil {
|
||||
// return
|
||||
// } else if filterSql, filterArgs, err := fn.ToSql(); err != nil {
|
||||
// return query, err
|
||||
// } else {
|
||||
// query = query.Where("("+filterSql+")", filterArgs...)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if f.Sort != "" {
|
||||
// var (
|
||||
// // Sort parser
|
||||
// sp = ql.NewParser()
|
||||
//
|
||||
// // Sort columns
|
||||
// sc ql.Columns
|
||||
// )
|
||||
//
|
||||
// // Resolve all identifiers found in sort
|
||||
// // into their table/column counterparts
|
||||
// sp.OnIdent = identResolver
|
||||
//
|
||||
// if sc, err = sp.ParseColumns(f.Sort); err != nil {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// query = query.OrderBy(sc.Strings()...)
|
||||
// }
|
||||
//
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//func (r record) Create(record *types.Record) (*types.Record, error) {
|
||||
// record.ID = factory.Sonyflake.NextID()
|
||||
//
|
||||
// if err := r.db().Insert("compose_record", record); err != nil {
|
||||
// return nil, errors.Wrap(err, "could not update record")
|
||||
// }
|
||||
//
|
||||
// return record, nil
|
||||
//}
|
||||
//
|
||||
//func (r record) Update(record *types.Record) (*types.Record, error) {
|
||||
// if err := r.db().Update("compose_record", record, "id"); err != nil {
|
||||
// return nil, errors.Wrap(err, "could not update record")
|
||||
// }
|
||||
//
|
||||
// return record, nil
|
||||
//}
|
||||
//
|
||||
//func (r record) Delete(record *types.Record) error {
|
||||
// _, err := r.db().Exec(
|
||||
// "UPDATE compose_record SET deleted_at = ?, deleted_by = ? WHERE rel_namespace = ? AND id = ?",
|
||||
// record.DeletedAt,
|
||||
// record.DeletedBy,
|
||||
// record.NamespaceID,
|
||||
// record.ID,
|
||||
// )
|
||||
//
|
||||
// return err
|
||||
//}
|
||||
//
|
||||
//func (r record) DeleteValues(record *types.Record) error {
|
||||
// _, err := r.db().Exec(
|
||||
// "UPDATE compose_record_value SET deleted_at = ? WHERE record_id = ?",
|
||||
// record.DeletedAt,
|
||||
// record.ID)
|
||||
//
|
||||
// return err
|
||||
//}
|
||||
//
|
||||
//func (r record) UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error) {
|
||||
// // Remove all records and prepare to be updated
|
||||
// // @todo be more selective and delete only removed and update/insert changed/new values
|
||||
// if _, err = r.db().Exec("DELETE FROM compose_record_value WHERE record_id = ?", recordID); err != nil {
|
||||
// return errors.Wrap(err, "could not remove record values")
|
||||
// }
|
||||
//
|
||||
// err = rvs.Walk(func(value *types.RecordValue) error {
|
||||
// if value.DeletedAt != nil {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// value.RecordID = recordID
|
||||
// return r.db().Insert("compose_record_value", value)
|
||||
// })
|
||||
//
|
||||
// return errors.Wrap(err, "could not insert record values")
|
||||
//
|
||||
//}
|
||||
//
|
||||
//func (r record) PartialUpdateValues(rvs ...*types.RecordValue) (err error) {
|
||||
// err = types.RecordValueSet(rvs).Walk(func(value *types.RecordValue) error {
|
||||
// return r.db().Replace("compose_record_value", value)
|
||||
// })
|
||||
//
|
||||
// return errors.Wrap(err, "could not replace record values")
|
||||
//}
|
||||
//
|
||||
//func (r record) RefValueLookup(moduleID uint64, field string, ref uint64) (recordID uint64, err error) {
|
||||
// var sql = "SELECT record_id" +
|
||||
// " FROM compose_record AS r INNER JOIN compose_record_value AS v " +
|
||||
// " WHERE r.module_id = ? " +
|
||||
// " AND v.name = ? " +
|
||||
// " AND v.ref = ? " +
|
||||
// " AND r.deleted_at IS NULL " +
|
||||
// " AND v.deleted_at IS NULL " +
|
||||
// " LIMIT 1"
|
||||
//
|
||||
// return recordID, r.db().Get(&recordID, sql, moduleID, field, ref)
|
||||
//}
|
||||
//
|
||||
//func (r record) LoadValues(fieldNames []string, IDs []uint64) (rvs types.RecordValueSet, err error) {
|
||||
// if len(fieldNames) == 0 || len(IDs) == 0 {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// var sql = "SELECT record_id, name, value, ref, place, deleted_at " +
|
||||
// " FROM compose_record_value " +
|
||||
// " WHERE record_id IN (?) " +
|
||||
// " AND name IN (?) " +
|
||||
// " AND deleted_at IS NULL " +
|
||||
// " ORDER BY record_id, place"
|
||||
//
|
||||
// if sql, args, err := sqlx.In(sql, IDs, fieldNames); err != nil {
|
||||
// return nil, err
|
||||
// } else {
|
||||
// return rvs, r.db().Select(&rvs, sql, args...)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
// Checks if field name is "real column", reformats it and returns
|
||||
func isRealRecordCol(name string) (string, bool) {
|
||||
switch name {
|
||||
case
|
||||
"recordID",
|
||||
"id":
|
||||
return "r.id", true
|
||||
case
|
||||
"module_id",
|
||||
"owned_by",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted_by",
|
||||
"deleted_at":
|
||||
return "r." + name, true
|
||||
|
||||
case
|
||||
"moduleID",
|
||||
"ownedBy",
|
||||
"createdBy",
|
||||
"createdAt",
|
||||
"updatedBy",
|
||||
"updatedAt",
|
||||
"deletedBy",
|
||||
"deletedAt":
|
||||
return "r." + name[0:len(name)-2] + "_" + strings.ToLower(name[len(name)-2:]), true
|
||||
}
|
||||
|
||||
return name, false
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/ql"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
)
|
||||
|
||||
type (
|
||||
recordReportBuilder struct {
|
||||
module *types.Module
|
||||
|
||||
// This is set by metric/column building to assist Cast()
|
||||
numerics []string
|
||||
|
||||
report squirrel.SelectBuilder
|
||||
parser *ql.Parser
|
||||
}
|
||||
)
|
||||
|
||||
// Identifiers should be names of the fields (physical table columns OR json fields, defined in module)
|
||||
func stdAggregationHandler(f ql.Function) (ql.Function, error) {
|
||||
switch strings.ToUpper(f.Name) {
|
||||
// case "COUNTD":
|
||||
// return SqlConcatExpr("COUNT(DISTINCT ", aggrFuncArgs, ")")
|
||||
//
|
||||
case "COUNT", "SUM", "MAX", "MIN", "AVG", "STD":
|
||||
return f, nil
|
||||
default:
|
||||
return f, fmt.Errorf("unsupported aggregate function %q", f.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Identifiers should be names of the fields (physical table columns OR json fields, defined in module)
|
||||
func stdFilterFuncHandler(f ql.Function) (ql.Function, error) {
|
||||
switch strings.ToUpper(f.Name) {
|
||||
case "CONCAT", "QUARTER", "YEAR", "DATE", "NOW", "DATE_ADD", "DATE_SUB", "DATE_FORMAT":
|
||||
return f, nil
|
||||
|
||||
default:
|
||||
return f, fmt.Errorf("unsupported group-by function %q", f.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func NewRecordReportBuilder(module *types.Module) *recordReportBuilder {
|
||||
var report = squirrel.
|
||||
Select().
|
||||
Column(squirrel.Alias(squirrel.Expr("COUNT(*)"), "count")).
|
||||
From("compose_record AS r").
|
||||
Where("r.deleted_at IS NULL").
|
||||
Where("r.module_id = ?", module.ID)
|
||||
|
||||
return &recordReportBuilder{
|
||||
parser: ql.NewParser(),
|
||||
module: module,
|
||||
report: report,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *recordReportBuilder) Build(metrics, dimensions, filters string) (sql string, args []interface{}, err error) {
|
||||
var joinedFields = []string{}
|
||||
var alreadyJoined = func(f string) bool {
|
||||
for _, a := range joinedFields {
|
||||
if a == f {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
joinedFields = append(joinedFields, f)
|
||||
return false
|
||||
}
|
||||
|
||||
b.parser.OnIdent = func(i ql.Ident) (ql.Ident, error) {
|
||||
var is bool
|
||||
if i.Value, is = isRealRecordCol(i.Value); is {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
if !b.module.Fields.HasName(i.Value) {
|
||||
return i, errors.Errorf("unknown field %q", i.Value)
|
||||
}
|
||||
|
||||
if !alreadyJoined(i.Value) {
|
||||
b.report = b.report.LeftJoin(fmt.Sprintf(
|
||||
"compose_record_value AS rv_%s ON (rv_%s.record_id = r.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)",
|
||||
i.Value, i.Value, i.Value, i.Value,
|
||||
), i.Value)
|
||||
}
|
||||
|
||||
// @todo switch value for ref when doing Record/Owner lookup
|
||||
i.Value = fmt.Sprintf("rv_%s.value", i.Value)
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
var columns ql.Columns
|
||||
b.parser.OnFunction = stdAggregationHandler
|
||||
if columns, err = b.parser.ParseColumns(metrics); err != nil {
|
||||
err = errors.Wrapf(err, "could not parse metrics %q", metrics)
|
||||
return
|
||||
}
|
||||
|
||||
// Add all metrics to columns
|
||||
for i, m := range columns {
|
||||
if m.Alias == "" {
|
||||
// Generate alias
|
||||
m.Alias = fmt.Sprintf("metric_%d", i)
|
||||
}
|
||||
|
||||
// Wrap to cast func to ensure numeric output
|
||||
col := squirrel.Alias(rh.SquirrelConcatExpr("CAST(", m.Expr, " AS DECIMAL(14,2))"), m.Alias)
|
||||
b.report = b.report.Column(col)
|
||||
|
||||
b.numerics = append(b.numerics, m.Alias)
|
||||
}
|
||||
|
||||
b.parser.OnFunction = stdFilterFuncHandler
|
||||
if columns, err = b.parser.ParseColumns(dimensions); err != nil {
|
||||
err = errors.Wrapf(err, "could not parse dimensions %q", dimensions)
|
||||
return
|
||||
}
|
||||
|
||||
// Add dimensions
|
||||
for i, d := range columns {
|
||||
if d.Alias == "" {
|
||||
d.Alias = fmt.Sprintf("dimension_%d", i)
|
||||
}
|
||||
|
||||
b.report = b.report.
|
||||
Column(d).
|
||||
GroupBy(d.Alias).
|
||||
OrderBy(d.Alias)
|
||||
}
|
||||
|
||||
// Use a different handler for filter functions for this
|
||||
b.parser.OnFunction = stdFilterFuncHandler
|
||||
|
||||
if len(filters) > 0 {
|
||||
var filter ql.ASTNode
|
||||
if filter, err = b.parser.ParseExpression(filters); err != nil {
|
||||
err = errors.Wrapf(err, "could not parse filters %q", filters)
|
||||
return
|
||||
}
|
||||
|
||||
// We need to wrap this one level deeper, since additional filters should
|
||||
// be evaluated as a whole.
|
||||
// For example A AND B OR C =should be> (A AND B OR C)
|
||||
// so the output becomes BASE AND (ADDITIONAL)
|
||||
b.report = b.report.Where(ql.ASTNodes{filter})
|
||||
}
|
||||
|
||||
return b.report.ToSql()
|
||||
}
|
||||
|
||||
func (b recordReportBuilder) Cast(row sqlx.ColScanner) map[string]interface{} {
|
||||
out := map[string]interface{}{}
|
||||
sqlx.MapScan(row, out)
|
||||
for k, v := range out {
|
||||
switch cv := v.(type) {
|
||||
case []uint8:
|
||||
out[k] = string(cv)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Cast all metrics to float64
|
||||
for _, fname := range b.numerics {
|
||||
switch num := out[fname].(type) {
|
||||
case string:
|
||||
out[fname], _ = strconv.ParseFloat(num, 64)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
)
|
||||
|
||||
func TestRecordReportBuilder2(t *testing.T) {
|
||||
builder := NewRecordReportBuilder(&types.Module{
|
||||
ID: 1000,
|
||||
Fields: types.ModuleFieldSet{
|
||||
&types.ModuleField{Name: "single1"},
|
||||
&types.ModuleField{Name: "multi1", Multi: true},
|
||||
&types.ModuleField{Name: "ref1", Kind: "Record"},
|
||||
&types.ModuleField{Name: "multiRef1", Kind: "Record", Multi: true},
|
||||
}},
|
||||
)
|
||||
|
||||
expected := "SELECT (COUNT(*)) AS count, (CAST(max(rv_single1.value) AS DECIMAL(14,2))) AS metric_0, " +
|
||||
"(QUARTER(rv_ref1.value)) AS dimension_0 " +
|
||||
"FROM compose_record AS r " +
|
||||
"LEFT JOIN compose_record_value AS rv_single1 ON (rv_single1.record_id = r.id AND rv_single1.name = ? AND rv_single1.deleted_at IS NULL) " +
|
||||
"LEFT JOIN compose_record_value AS rv_ref1 ON (rv_ref1.record_id = r.id AND rv_ref1.name = ? AND rv_ref1.deleted_at IS NULL) " +
|
||||
"WHERE r.deleted_at IS NULL AND r.module_id = ? AND (rv_ref1.value = 2) " +
|
||||
"GROUP BY dimension_0 " +
|
||||
"ORDER BY dimension_0"
|
||||
|
||||
sql, _, err := builder.Build("max(single1)", "QUARTER(ref1)", "ref1 = 2")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, sql)
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
)
|
||||
|
||||
func TestRecordFinder(t *testing.T) {
|
||||
r := record{}
|
||||
m := &types.Module{
|
||||
ID: 123,
|
||||
NamespaceID: 456,
|
||||
Fields: types.ModuleFieldSet{
|
||||
&types.ModuleField{Name: "foo"},
|
||||
&types.ModuleField{Name: "bar"},
|
||||
&types.ModuleField{Name: "booly", Kind: "Bool"},
|
||||
},
|
||||
}
|
||||
|
||||
ttc := []struct {
|
||||
name string
|
||||
f types.RecordFilter
|
||||
match []string
|
||||
noMatch []string
|
||||
args []interface{}
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "default filter",
|
||||
match: []string{
|
||||
"SELECT r.id, r.module_id, r.rel_namespace, r.owned_by, r.created_at, " +
|
||||
"r.created_by, r.updated_at, r.updated_by, r.deleted_at, r.deleted_by " +
|
||||
"FROM compose_record AS r " +
|
||||
"WHERE r.module_id = ? AND r.rel_namespace = ? AND r.deleted_at IS NULL",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple query",
|
||||
f: types.RecordFilter{Query: "id = 5 AND foo = 7"},
|
||||
match: []string{
|
||||
"r.id = 5",
|
||||
"rv_foo.value = 7"},
|
||||
args: []interface{}{"foo"},
|
||||
},
|
||||
{
|
||||
name: "sorting",
|
||||
f: types.RecordFilter{Sort: "id ASC, bar DESC"},
|
||||
match: []string{
|
||||
" r.id ASC",
|
||||
" rv_bar.value DESC",
|
||||
},
|
||||
args: []interface{}{"bar"},
|
||||
},
|
||||
{
|
||||
name: "exclude deleted records (def. behaviour)",
|
||||
f: types.RecordFilter{Deleted: rh.FilterStateExcluded},
|
||||
match: []string{" r.deleted_at IS "},
|
||||
},
|
||||
{
|
||||
name: "include deleted records",
|
||||
f: types.RecordFilter{Deleted: rh.FilterStateInclusive},
|
||||
noMatch: []string{" r.deleted_at IS NULL "},
|
||||
},
|
||||
{
|
||||
name: "only deleted record",
|
||||
f: types.RecordFilter{Deleted: rh.FilterStateExclusive},
|
||||
match: []string{" r.deleted_at IS NOT NULL"},
|
||||
},
|
||||
{
|
||||
name: "boolean",
|
||||
f: types.RecordFilter{Query: "booly"},
|
||||
match: []string{"(rv_booly.value NOT IN ("},
|
||||
args: []interface{}{"booly"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range ttc {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sb, err := r.buildQuery(m, tc.f)
|
||||
|
||||
if tc.err != nil {
|
||||
require.True(t, tc.err.Error() == fmt.Sprintf("%v", err), "buildQuery(%+v) did not return an expected error %q but %q", tc.f, tc.err, err)
|
||||
} else {
|
||||
require.True(t, err == nil, "buildQuery(%+v) returned an unexpected error: %v", tc.f, err)
|
||||
}
|
||||
|
||||
sql, args, err := sb.ToSql()
|
||||
|
||||
for _, m := range tc.match {
|
||||
require.True(t, strings.Contains(sql, m),
|
||||
"assertion failed; query %q \n "+
|
||||
" did not contain %q", sql, m)
|
||||
}
|
||||
|
||||
for _, m := range tc.noMatch {
|
||||
require.False(t, strings.Contains(sql, m),
|
||||
"assertion failed; query %q \n "+
|
||||
" must not contain %q", sql, m)
|
||||
}
|
||||
|
||||
tc.args = append(tc.args, m.ID, m.NamespaceID)
|
||||
require.True(t, fmt.Sprintf("%+v", args) == fmt.Sprintf("%+v", tc.args),
|
||||
"assertion failed; args %+v \n "+
|
||||
" do not match expected %+v", args, tc.args)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/titpetric/factory"
|
||||
)
|
||||
|
||||
type (
|
||||
repository struct {
|
||||
ctx context.Context
|
||||
dbh *factory.DB
|
||||
}
|
||||
)
|
||||
|
||||
// DB produces a contextual DB handle
|
||||
func DB(ctx context.Context) *factory.DB {
|
||||
return factory.Database.MustGet().With(ctx)
|
||||
}
|
||||
|
||||
// With updates repository and database contexts
|
||||
func (r *repository) With(ctx context.Context, db *factory.DB) *repository {
|
||||
return &repository{
|
||||
ctx: ctx,
|
||||
dbh: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Context returns current active repository context
|
||||
func (r *repository) Context() context.Context {
|
||||
return r.ctx
|
||||
}
|
||||
|
||||
// db returns context-aware db handle
|
||||
func (r *repository) db() *factory.DB {
|
||||
if r.dbh != nil {
|
||||
return r.dbh
|
||||
}
|
||||
return DB(r.ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user