mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-11 19:04:35 +00:00
Db service fixes and improvements: dot access, truncate table etc (#157)
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
const idKey = "id"
|
||||
const stmt = "create table if not exists %v(id text not null, data jsonb, primary key(id)); alter table %v add created_at timestamptz; alter table %v add updated_at timestamptz"
|
||||
const truncateStmt = `truncate table "%v"`
|
||||
|
||||
var re = regexp.MustCompile("^[a-zA-Z0-9_]*$")
|
||||
var c = cache.New(5*time.Minute, 10*time.Minute)
|
||||
@@ -39,6 +40,23 @@ type Db struct {
|
||||
gorm2.Helper
|
||||
}
|
||||
|
||||
func correctFieldName(s string) string {
|
||||
switch s {
|
||||
// top level fields can stay top level
|
||||
case "id": // "created_at", "updated_at", <-- these are not special fields for now
|
||||
return s
|
||||
}
|
||||
if !strings.Contains(s, ".") {
|
||||
return fmt.Sprintf("data ->> '%v'", s)
|
||||
}
|
||||
paths := strings.Split(s, ".")
|
||||
ret := "data"
|
||||
for _, path := range paths {
|
||||
ret += fmt.Sprintf(" ->> '%v'", path)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Call is a single request handler called via client.Call or the generated client code
|
||||
func (e *Db) Create(ctx context.Context, req *db.CreateRequest, rsp *db.CreateResponse) error {
|
||||
if len(req.Record.AsMap()) == 0 {
|
||||
@@ -208,13 +226,28 @@ func (e *Db) Read(ctx context.Context, req *db.ReadRequest, rsp *db.ReadResponse
|
||||
case itemNotEquals:
|
||||
op = "!="
|
||||
}
|
||||
db = db.Where(fmt.Sprintf("(data ->> '%v')::%v %v ?", query.Field, typ, op), query.Value)
|
||||
queryField := correctFieldName(query.Field)
|
||||
db = db.Where(fmt.Sprintf("(%v)::%v %v ?", queryField, typ, op), query.Value)
|
||||
}
|
||||
orderField := "created_at DESC"
|
||||
orderField := "created_at"
|
||||
if req.OrderBy != "" {
|
||||
orderField = req.OrderBy + " " + req.Order
|
||||
orderField = req.OrderBy
|
||||
}
|
||||
db = db.Order(orderField).Offset(int(req.Offset)).Limit(int(req.Limit))
|
||||
orderField = correctFieldName(orderField)
|
||||
|
||||
ordering := "asc"
|
||||
if req.Order != "" {
|
||||
switch strings.ToLower(req.Order) {
|
||||
case "asc":
|
||||
ordering = "asc"
|
||||
case "", "desc":
|
||||
ordering = "desc"
|
||||
default:
|
||||
return errors.BadRequest("db.read", "invalid ordering: "+req.Order)
|
||||
}
|
||||
}
|
||||
|
||||
db = db.Order(orderField + " " + ordering).Offset(int(req.Offset)).Limit(int(req.Limit))
|
||||
err = db.Find(&recs).Error
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -269,3 +302,25 @@ func (e *Db) Delete(ctx context.Context, req *db.DeleteRequest, rsp *db.DeleteRe
|
||||
ID: req.Id,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (e *Db) Truncate(ctx context.Context, req *db.TruncateRequest, rsp *db.TruncateResponse) error {
|
||||
tenantId, ok := tenant.FromContext(ctx)
|
||||
if !ok {
|
||||
tenantId = "micro"
|
||||
}
|
||||
if req.Table == "" {
|
||||
req.Table = "default"
|
||||
}
|
||||
tenantId = strings.Replace(strings.Replace(tenantId, "/", "_", -1), "-", "_", -1)
|
||||
tableName := tenantId + "_" + req.Table
|
||||
if !re.Match([]byte(tableName)) {
|
||||
return errors.BadRequest("db.create", fmt.Sprintf("table name %v is invalid", req.Table))
|
||||
}
|
||||
logger.Infof("Truncating table '%v'", tableName)
|
||||
|
||||
db, err := e.GetDBConn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Exec(fmt.Sprintf(truncateStmt, tableName)).Error
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ var expressions = []lexer.TokenExpr{
|
||||
{`"(?:[^"\\]|\\.)*"`, itemString},
|
||||
{"`" + `(?:[^"\\]|\\.)*` + "`", itemString},
|
||||
{`'(?:[^"\\]|\\.)*'`, itemString},
|
||||
{`[\<\>\!\=\+\-\|\&\*\/A-Za-z][A-Za-z0-9_]*`, itemFieldName},
|
||||
{`[\<\>\!\=\+\-\|\&\*\/A-Za-z][A-Za-z0-9_\.]*`, itemFieldName},
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
|
||||
@@ -8,6 +8,13 @@ import (
|
||||
"github.com/crufter/lexer"
|
||||
)
|
||||
|
||||
func TestCorrectFieldName(t *testing.T) {
|
||||
f := correctFieldName("a.b.c")
|
||||
if f != "data ->> 'a' ->> 'b' ->> 'c'" {
|
||||
t.Fatal(f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexing(t *testing.T) {
|
||||
tokens, err := lexer.Lex("a == 12", expressions)
|
||||
if err != nil {
|
||||
@@ -55,6 +62,21 @@ func TestParsing(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
tCase{
|
||||
Q: `a.b.c == 12 and name != "nandos"`,
|
||||
E: []Query{
|
||||
Query{
|
||||
Field: "a.b.c",
|
||||
Value: int64(12),
|
||||
Op: itemEquals,
|
||||
},
|
||||
Query{
|
||||
Field: "name",
|
||||
Value: "nandos",
|
||||
Op: itemNotEquals,
|
||||
},
|
||||
},
|
||||
},
|
||||
tCase{
|
||||
Q: `a == 12 and name != "nan'dos"`,
|
||||
E: []Query{
|
||||
|
||||
Reference in New Issue
Block a user