add libsql support
This commit is contained in:
@@ -8,9 +8,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"go.sour.is/pkg/mercury"
|
||||
"go.sour.is/pkg/ident"
|
||||
"go.sour.is/pkg/rsql"
|
||||
"go.sour.is/pkg/mercury"
|
||||
"go.sour.is/pkg/set"
|
||||
)
|
||||
|
||||
@@ -20,8 +19,9 @@ const (
|
||||
mercuryHost = "mercury.host"
|
||||
appDotEnviron = "mercury.environ"
|
||||
)
|
||||
|
||||
var (
|
||||
mercuryPolicy = func(id string) string { return "mercury.@" + id + ".policy" }
|
||||
mercuryPolicy = func(id string) string { return "mercury.@" + id + ".policy" }
|
||||
)
|
||||
|
||||
func Register(name string, cfg mercury.SpaceMap) {
|
||||
@@ -41,8 +41,13 @@ type mercuryEnviron struct {
|
||||
lookup func(context.Context, ident.Ident) (mercury.Rules, error)
|
||||
}
|
||||
|
||||
func getSearch(spec mercury.Search) mercury.NamespaceSearch {
|
||||
return spec.NamespaceSearch
|
||||
}
|
||||
|
||||
// Index returns nil
|
||||
func (app *mercuryEnviron) GetIndex(ctx context.Context, search mercury.NamespaceSearch, _ *rsql.Program) (lis mercury.Config, err error) {
|
||||
func (app *mercuryEnviron) GetIndex(ctx context.Context, spec mercury.Search) (lis mercury.Config, err error) {
|
||||
search := getSearch(spec)
|
||||
|
||||
if search.Match(mercurySource) {
|
||||
for _, s := range app.cfg.ToArray() {
|
||||
@@ -74,7 +79,9 @@ func (app *mercuryEnviron) GetIndex(ctx context.Context, search mercury.Namespac
|
||||
}
|
||||
|
||||
// Objects returns nil
|
||||
func (app *mercuryEnviron) GetConfig(ctx context.Context, search mercury.NamespaceSearch, _ *rsql.Program, _ []string) (lis mercury.Config, err error) {
|
||||
func (app *mercuryEnviron) GetConfig(ctx context.Context, spec mercury.Search) (lis mercury.Config, err error) {
|
||||
search := getSearch(spec)
|
||||
|
||||
if search.Match(mercurySource) {
|
||||
for _, s := range app.cfg.ToArray() {
|
||||
if search.Match(s.Space) {
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package mercury_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/matryer/is"
|
||||
"go.sour.is/pkg/mercury"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
func TestNamespaceParse(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in string
|
||||
out string
|
||||
args []any
|
||||
}{
|
||||
{
|
||||
in: "d42.bgp.kapha.*;trace:d42.bgp.kapha",
|
||||
out: "(column LIKE ? OR ? LIKE column || '%')",
|
||||
args: []any{"d42.bgp.kapha.%", "d42.bgp.kapha"},
|
||||
},
|
||||
|
||||
{
|
||||
in: "d42.bgp.kapha.*,d42.bgp.kapha",
|
||||
out: "(column LIKE ? OR column = ?)",
|
||||
args: []any{"d42.bgp.kapha.%", "d42.bgp.kapha"},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||
is := is.New(t)
|
||||
out := mercury.ParseNamespace(tt.in)
|
||||
sql, args, err := getWhere(out).ToSql()
|
||||
is.NoErr(err)
|
||||
is.Equal(sql, tt.out)
|
||||
is.Equal(args, tt.args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getWhere(search mercury.NamespaceSearch) sq.Sqlizer {
|
||||
var where sq.Or
|
||||
space := "column"
|
||||
for _, m := range search {
|
||||
switch m.(type) {
|
||||
case mercury.NamespaceNode:
|
||||
where = append(where, sq.Eq{space: m.Value()})
|
||||
case mercury.NamespaceStar:
|
||||
where = append(where, sq.Like{space: m.Value()})
|
||||
case mercury.NamespaceTrace:
|
||||
e := sq.Expr(`? LIKE `+space+` || '%'`, m.Value())
|
||||
where = append(where, e)
|
||||
}
|
||||
}
|
||||
return where
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package mercury
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -10,16 +11,15 @@ import (
|
||||
|
||||
"go.sour.is/pkg/ident"
|
||||
"go.sour.is/pkg/lg"
|
||||
"go.sour.is/pkg/rsql"
|
||||
"go.sour.is/pkg/set"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type GetIndex interface {
|
||||
GetIndex(context.Context, NamespaceSearch, *rsql.Program) (Config, error)
|
||||
GetIndex(context.Context, Search) (Config, error)
|
||||
}
|
||||
type GetConfig interface {
|
||||
GetConfig(context.Context, NamespaceSearch, *rsql.Program, []string) (Config, error)
|
||||
GetConfig(context.Context, Search) (Config, error)
|
||||
}
|
||||
type WriteConfig interface {
|
||||
WriteConfig(context.Context, Config) error
|
||||
@@ -60,7 +60,7 @@ func (reg *registry) accessFilter(rules Rules, lis Config) (out Config, err erro
|
||||
// HandlerItem a single handler matching
|
||||
type matcher[T any] struct {
|
||||
Name string
|
||||
Match NamespaceSearch
|
||||
Match Search
|
||||
Priority int
|
||||
Handler T
|
||||
}
|
||||
@@ -122,17 +122,23 @@ func (r *registry) Register(name string, h func(*Space) any) {
|
||||
func (r *registry) Configure(m SpaceMap) error {
|
||||
r.resetMatchers()
|
||||
for space, c := range m {
|
||||
log.Println("configure: ", space)
|
||||
|
||||
if strings.HasPrefix(space, "mercury.source.") {
|
||||
space = strings.TrimPrefix(space, "mercury.source.")
|
||||
handler, name, _ := strings.Cut(space, ".")
|
||||
matches := c.FirstValue("match")
|
||||
readonly := c.HasTag("readonly")
|
||||
for _, match := range matches.Values {
|
||||
ps := strings.Fields(match)
|
||||
priority, err := strconv.Atoi(ps[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.add(name, handler, ps[1], priority, c)
|
||||
err = r.add(name, handler, strings.Join(ps[1:],"|"), priority, c, readonly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +152,10 @@ func (r *registry) Configure(m SpaceMap) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.add(name, handler, ps[1], priority, c)
|
||||
err = r.add(name, handler, strings.Join(ps[1:],"|"), priority, c, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,8 +165,8 @@ func (r *registry) Configure(m SpaceMap) error {
|
||||
}
|
||||
|
||||
// Register add a handler to registry
|
||||
func (r *registry) add(name, handler, match string, priority int, cfg *Space) error {
|
||||
// log.Infos("mercury regster", "match", match, "pri", priority)
|
||||
func (r *registry) add(name, handler, match string, priority int, cfg *Space, readonly bool) error {
|
||||
log.Println("mercury regster", "match", match, "pri", priority)
|
||||
mkHandler, ok := r.handlers[handler]
|
||||
if !ok {
|
||||
return fmt.Errorf("handler not registered: %s", handler)
|
||||
@@ -173,61 +182,68 @@ func (r *registry) add(name, handler, match string, priority int, cfg *Space) er
|
||||
if hdlr, ok := hdlr.(GetIndex); ok {
|
||||
r.matchers.getIndex = append(
|
||||
r.matchers.getIndex,
|
||||
matcher[GetIndex]{Name: name, Match: ParseNamespace(match), Priority: priority, Handler: hdlr},
|
||||
matcher[GetIndex]{Name: name, Match: ParseSearch(match), Priority: priority, Handler: hdlr},
|
||||
)
|
||||
}
|
||||
if hdlr, ok := hdlr.(GetConfig); ok {
|
||||
r.matchers.getConfig = append(
|
||||
r.matchers.getConfig,
|
||||
matcher[GetConfig]{Name: name, Match: ParseNamespace(match), Priority: priority, Handler: hdlr},
|
||||
matcher[GetConfig]{Name: name, Match: ParseSearch(match), Priority: priority, Handler: hdlr},
|
||||
)
|
||||
}
|
||||
|
||||
if hdlr, ok := hdlr.(WriteConfig); ok {
|
||||
if hdlr, ok := hdlr.(WriteConfig); !readonly && ok {
|
||||
|
||||
r.matchers.writeConfig = append(
|
||||
r.matchers.writeConfig,
|
||||
matcher[WriteConfig]{Name: name, Match: ParseNamespace(match), Priority: priority, Handler: hdlr},
|
||||
matcher[WriteConfig]{Name: name, Match: ParseSearch(match), Priority: priority, Handler: hdlr},
|
||||
)
|
||||
}
|
||||
if hdlr, ok := hdlr.(GetRules); ok {
|
||||
r.matchers.getRules = append(
|
||||
r.matchers.getRules,
|
||||
matcher[GetRules]{Name: name, Match: ParseNamespace(match), Priority: priority, Handler: hdlr},
|
||||
matcher[GetRules]{Name: name, Match: ParseSearch(match), Priority: priority, Handler: hdlr},
|
||||
)
|
||||
}
|
||||
if hdlr, ok := hdlr.(GetNotify); ok {
|
||||
r.matchers.getNotify = append(
|
||||
r.matchers.getNotify,
|
||||
matcher[GetNotify]{Name: name, Match: ParseNamespace(match), Priority: priority, Handler: hdlr},
|
||||
matcher[GetNotify]{Name: name, Match: ParseSearch(match), Priority: priority, Handler: hdlr},
|
||||
)
|
||||
}
|
||||
if hdlr, ok := hdlr.(SendNotify); ok {
|
||||
r.matchers.sendNotify = append(
|
||||
r.matchers.sendNotify,
|
||||
matcher[SendNotify]{Name: name, Match: ParseNamespace(match), Priority: priority, Handler: hdlr},
|
||||
matcher[SendNotify]{Name: name, Match: ParseSearch(match), Priority: priority, Handler: hdlr},
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIndex query each handler that match namespace.
|
||||
func (r *registry) GetIndex(ctx context.Context, match, search string) (c Config, err error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
func getMatches(search Search, matchers matchers) []Search {
|
||||
matches := make([]Search, len(matchers.getIndex))
|
||||
|
||||
spec := ParseNamespace(match)
|
||||
pgm := rsql.DefaultParse(search)
|
||||
matches := make([]NamespaceSearch, len(r.matchers.getIndex))
|
||||
|
||||
for _, n := range spec {
|
||||
for i, hdlr := range r.matchers.getIndex {
|
||||
for _, n := range search.NamespaceSearch {
|
||||
for i, hdlr := range matchers.getIndex {
|
||||
if hdlr.Match.Match(n.Raw()) {
|
||||
matches[i] = append(matches[i], n)
|
||||
matches[i].NamespaceSearch = append(matches[i].NamespaceSearch, n)
|
||||
matches[i].Count = search.Count
|
||||
matches[i].Cursor = search.Cursor // need to decode cursor for the match
|
||||
matches[i].Fields = search.Fields
|
||||
matches[i].Find = search.Find
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// GetIndex query each handler that match namespace.
|
||||
func (r *registry) GetIndex(ctx context.Context, search Search) (c Config, err error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
matches := getMatches(search, r.matchers)
|
||||
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
slots := make(chan Config, len(r.matchers.getConfig))
|
||||
@@ -248,7 +264,7 @@ func (r *registry) GetIndex(ctx context.Context, match, search string) (c Config
|
||||
|
||||
wg.Go(func() error {
|
||||
span.AddEvent(fmt.Sprintf("INDEX %s %s", hdlr.Name, hdlr.Match))
|
||||
lis, err := hdlr.Handler.GetIndex(ctx, matches[i], pgm)
|
||||
lis, err := hdlr.Handler.GetIndex(ctx, matches[i])
|
||||
slots <- lis
|
||||
return err
|
||||
})
|
||||
@@ -265,31 +281,19 @@ func (r *registry) GetIndex(ctx context.Context, match, search string) (c Config
|
||||
// Search query each handler with a key=value search
|
||||
|
||||
// GetConfig query each handler that match for fully qualified namespaces.
|
||||
func (r *registry) GetConfig(ctx context.Context, match, search, fields string) (Config, error) {
|
||||
func (r *registry) GetConfig(ctx context.Context, search Search) (Config, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
spec := ParseNamespace(match)
|
||||
pgm := rsql.DefaultParse(search)
|
||||
flds := strings.Split(fields, ",")
|
||||
|
||||
matches := make([]NamespaceSearch, len(r.matchers.getConfig))
|
||||
|
||||
for _, n := range spec {
|
||||
for i, hdlr := range r.matchers.getConfig {
|
||||
if hdlr.Match.Match(n.Raw()) {
|
||||
matches[i] = append(matches[i], n)
|
||||
}
|
||||
}
|
||||
}
|
||||
matches := getMatches(search, r.matchers)
|
||||
|
||||
m := make(SpaceMap)
|
||||
for i, hdlr := range r.matchers.getConfig {
|
||||
if len(matches[i]) == 0 {
|
||||
if len(matches[i].NamespaceSearch) == 0 {
|
||||
continue
|
||||
}
|
||||
span.AddEvent(fmt.Sprintf("QUERY %s %s", hdlr.Name, hdlr.Match))
|
||||
lis, err := hdlr.Handler.GetConfig(ctx, matches[i], pgm, flds)
|
||||
lis, err := hdlr.Handler.GetConfig(ctx, matches[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -70,12 +70,12 @@ func (s *root) configV1(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
log.Print("SPC: ", space)
|
||||
ns := ParseNamespace(space)
|
||||
ns := ParseSearch(space)
|
||||
log.Print("PRE: ", ns)
|
||||
//ns = rules.ReduceSearch(ns)
|
||||
log.Print("POST: ", ns)
|
||||
|
||||
lis, err := Registry.GetConfig(ctx, ns.String(), "", "")
|
||||
lis, err := Registry.GetConfig(ctx, ns)
|
||||
if err != nil {
|
||||
http.Error(w, "ERR: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -250,11 +250,11 @@ func (s *root) indexV1(w http.ResponseWriter, r *http.Request) {
|
||||
space = "*"
|
||||
}
|
||||
|
||||
ns := ParseNamespace(space)
|
||||
ns = rules.ReduceSearch(ns)
|
||||
ns := ParseSearch(space)
|
||||
ns.NamespaceSearch = rules.ReduceSearch(ns.NamespaceSearch)
|
||||
span.AddEvent(ns.String())
|
||||
|
||||
lis, err := Registry.GetIndex(ctx, ns.String(), "")
|
||||
lis, err := Registry.GetIndex(ctx, ns)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
http.Error(w, "ERR: "+err.Error(), http.StatusInternalServerError)
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
package mercury
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NamespaceSpec implements a parsed namespace search
|
||||
// Search implements a parsed namespace search
|
||||
// It parses the input and generates an AST to inform the driver how to select values.
|
||||
// * => all spaces
|
||||
// mercury.* => all prefixed with `mercury.`
|
||||
// mercury.config => only space `mercury.config`
|
||||
// mercury.source.*#readonly => all prefixed with `mercury.source.` AND has tag `readonly`
|
||||
// test.*|mercury.* => all prefixed with `test.` AND `mercury.`
|
||||
// test.* find bin=eq=bar => all prefixed with `test.` AND has an attribute bin that equals bar
|
||||
// test.* fields foo,bin => all prefixed with `test.` only show fields foo and bin
|
||||
// - count 20 => start a cursor with 20 results
|
||||
// - count 20 after <cursor> => continue after cursor for 20 results
|
||||
// cursor encodes start points for each of the matched sources
|
||||
type Search struct {
|
||||
NamespaceSearch
|
||||
Find []ops
|
||||
Fields []string
|
||||
Count uint64
|
||||
Offset uint64
|
||||
Cursor string
|
||||
}
|
||||
|
||||
type NamespaceSpec interface {
|
||||
Value() string
|
||||
String() string
|
||||
@@ -17,8 +39,10 @@ type NamespaceSpec interface {
|
||||
type NamespaceSearch []NamespaceSpec
|
||||
|
||||
// ParseNamespace returns a list of parsed values
|
||||
func ParseNamespace(ns string) (lis NamespaceSearch) {
|
||||
for _, part := range strings.Split(ns, ";") {
|
||||
func ParseSearch(text string) (search Search) {
|
||||
ns, text, _ := strings.Cut(text, " ")
|
||||
var lis NamespaceSearch
|
||||
for _, part := range strings.Split(ns, "|") {
|
||||
if strings.HasPrefix(part, "trace:") {
|
||||
lis = append(lis, NamespaceTrace(part[6:]))
|
||||
} else if strings.Contains(part, "*") {
|
||||
@@ -27,6 +51,40 @@ func ParseNamespace(ns string) (lis NamespaceSearch) {
|
||||
lis = append(lis, NamespaceNode(part))
|
||||
}
|
||||
}
|
||||
search.NamespaceSearch = lis
|
||||
|
||||
field, text, next := strings.Cut(text, " ")
|
||||
text = strings.TrimSpace(text)
|
||||
for next {
|
||||
switch strings.ToLower(field) {
|
||||
case "find":
|
||||
field, text, _ = strings.Cut(text, " ")
|
||||
text = strings.TrimSpace(text)
|
||||
search.Find = simpleParse(field)
|
||||
|
||||
case "fields":
|
||||
field, text, _ = strings.Cut(text, " ")
|
||||
text = strings.TrimSpace(text)
|
||||
search.Fields = strings.Split(field, ",")
|
||||
|
||||
case "count":
|
||||
field, text, _ = strings.Cut(text, " ")
|
||||
text = strings.TrimSpace(text)
|
||||
search.Count, _ = strconv.ParseUint(field, 10, 64)
|
||||
|
||||
case "offset":
|
||||
field, text, _ = strings.Cut(text, " ")
|
||||
text = strings.TrimSpace(text)
|
||||
search.Offset, _ = strconv.ParseUint(field, 10, 64)
|
||||
|
||||
case "after":
|
||||
field, text, _ = strings.Cut(text, " ")
|
||||
text = strings.TrimSpace(text)
|
||||
search.Cursor = field
|
||||
}
|
||||
field, text, next = strings.Cut(text, " ")
|
||||
text = strings.TrimSpace(text)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -117,3 +175,28 @@ func match(n NamespaceSpec, s string) bool {
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
type ops struct {
|
||||
Left string
|
||||
Op string
|
||||
Right string
|
||||
}
|
||||
|
||||
func simpleParse(in string) (out []ops) {
|
||||
items := strings.Split(in, ",")
|
||||
for _, i := range items {
|
||||
log.Println(i)
|
||||
eq := strings.Split(i, "=")
|
||||
switch len(eq) {
|
||||
case 2:
|
||||
out = append(out, ops{eq[0], "eq", eq[1]})
|
||||
case 3:
|
||||
if eq[1] == "" {
|
||||
eq[1] = "eq"
|
||||
}
|
||||
out = append(out, ops{eq[0], eq[1], eq[2]})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
109
mercury/spec_test.go
Normal file
109
mercury/spec_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package mercury_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/matryer/is"
|
||||
"go.sour.is/pkg/mercury"
|
||||
"go.sour.is/pkg/mercury/sql"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
var MAX_FILTER int = 40
|
||||
|
||||
func TestNamespaceParse(t *testing.T) {
|
||||
var tests = []struct {
|
||||
getWhere func(mercury.Search) sq.Sqlizer
|
||||
in string
|
||||
out string
|
||||
args []any
|
||||
}{
|
||||
{
|
||||
getWhere: getWhere,
|
||||
in: "d42.bgp.kapha.*|trace:d42.bgp.kapha",
|
||||
out: "(column LIKE ? OR ? LIKE column || '%')",
|
||||
args: []any{"d42.bgp.kapha.%", "d42.bgp.kapha"},
|
||||
},
|
||||
|
||||
{
|
||||
getWhere: getWhere,
|
||||
in: "d42.bgp.kapha.*|d42.bgp.kapha",
|
||||
out: "(column LIKE ? OR column = ?)",
|
||||
args: []any{"d42.bgp.kapha.%", "d42.bgp.kapha"},
|
||||
},
|
||||
|
||||
{
|
||||
getWhere: mkWhere(t, sql.GetWhereSQ),
|
||||
in: "d42.bgp.kapha.* find active=eq=true",
|
||||
out: `SELECT * FROM spaces JOIN ( SELECT DISTINCT id FROM mercury_values mv, json_each(mv."values") vs WHERE (json_valid("values") AND name = ? AND vs.value = ?) ) r000 USING (id) WHERE (space LIKE ?)`,
|
||||
args: []any{"active", "true", "d42.bgp.kapha.%"},
|
||||
},
|
||||
|
||||
{
|
||||
getWhere: mkWhere(t, sql.GetWhereSQ),
|
||||
in: "d42.bgp.kapha.* count 10 offset 5",
|
||||
out: `SELECT * FROM spaces WHERE (space LIKE ?) LIMIT 10 OFFSET 5`,
|
||||
args: []any{"d42.bgp.kapha.%"},
|
||||
},
|
||||
|
||||
{
|
||||
getWhere: mkWhere(t, sql.GetWhereSQ),
|
||||
in: "d42.bgp.kapha.* fields a,b,c",
|
||||
out: `SELECT * FROM spaces WHERE (space LIKE ?)`,
|
||||
args: []any{"d42.bgp.kapha.%"},
|
||||
},
|
||||
|
||||
{
|
||||
getWhere: mkWhere(t, sql.GetWhereSQ),
|
||||
in: "dn42.* find @type=in=[person,net]",
|
||||
out: `SELECT `,
|
||||
args: []any{"d42.bgp.kapha.%"},
|
||||
},
|
||||
}
|
||||
|
||||
//SELECT * FROM spaces JOIN ( SELECT DISTINCT id FROM mercury_values mv, json_valid("values") vs WHERE (json_valid("values") AND name = ? AND vs.value = ?) ) r000 USING (id) WHERE (space LIKE ?) !=
|
||||
//SELECT * FROM spaces JOIN ( SELECT DISTINCT mv.id FROM mercury_values mv, json_each(mv."values") vs WHERE (json_valid("values") AND name = ? AND vs.value = ?) ) r000 USING (id) WHERE (space LIKE ?)
|
||||
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
|
||||
is := is.New(t)
|
||||
out := mercury.ParseSearch(tt.in)
|
||||
sql, args, err := tt.getWhere(out).ToSql()
|
||||
is.NoErr(err)
|
||||
is.Equal(sql, tt.out)
|
||||
is.Equal(args, tt.args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getWhere(search mercury.Search) sq.Sqlizer {
|
||||
var where sq.Or
|
||||
space := "column"
|
||||
for _, m := range search.NamespaceSearch {
|
||||
switch m.(type) {
|
||||
case mercury.NamespaceNode:
|
||||
where = append(where, sq.Eq{space: m.Value()})
|
||||
case mercury.NamespaceStar:
|
||||
where = append(where, sq.Like{space: m.Value()})
|
||||
case mercury.NamespaceTrace:
|
||||
e := sq.Expr(`? LIKE `+space+` || '%'`, m.Value())
|
||||
where = append(where, e)
|
||||
}
|
||||
}
|
||||
return where
|
||||
}
|
||||
|
||||
func mkWhere(t *testing.T, where func(search mercury.Search) (func(sq.SelectBuilder) sq.SelectBuilder, error)) func(search mercury.Search) sq.Sqlizer {
|
||||
t.Helper()
|
||||
|
||||
return func(search mercury.Search) sq.Sqlizer {
|
||||
w, err := where(search)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fail()
|
||||
}
|
||||
return w(sq.Select("*").From("spaces"))
|
||||
}
|
||||
}
|
||||
@@ -43,9 +43,7 @@ func listScan(e *[]string, ends [2]rune) scanFn {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, s := range splitComma(string(str)) {
|
||||
*e = append(*e, s)
|
||||
}
|
||||
*e = append(*e, splitComma(string(str))...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func (pgm *sqlHandler) GetNotify(ctx context.Context, event string) (lis mercury
|
||||
Where(squirrel.Eq{"event": event}).
|
||||
PlaceholderFormat(squirrel.Dollar).
|
||||
RunWith(pgm.db).
|
||||
QueryContext(context.TODO())
|
||||
QueryContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -2,6 +2,7 @@ package sql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
"go.nhat.io/otelsql"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
|
||||
@@ -9,25 +10,28 @@ import (
|
||||
|
||||
func openDB(driver, dsn string) (*sql.DB, error) {
|
||||
system := semconv.DBSystemPostgreSQL
|
||||
if driver == "sqlite" {
|
||||
if driver == "sqlite" || strings.HasPrefix(driver, "libsql") {
|
||||
system = semconv.DBSystemSqlite
|
||||
}
|
||||
|
||||
// Register the otelsql wrapper for the provided postgres driver.
|
||||
driverName, err := otelsql.Register(driver,
|
||||
otelsql.AllowRoot(),
|
||||
otelsql.TraceQueryWithoutArgs(),
|
||||
otelsql.TraceRowsClose(),
|
||||
otelsql.TraceRowsAffected(),
|
||||
// otelsql.WithDatabaseName("my_database"), // Optional.
|
||||
otelsql.WithSystem(system), // Optional.
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
if driver == "postgres" {
|
||||
var err error
|
||||
// Register the otelsql wrapper for the provided postgres driver.
|
||||
driver, err = otelsql.Register(driver,
|
||||
otelsql.AllowRoot(),
|
||||
otelsql.TraceQueryWithoutArgs(),
|
||||
otelsql.TraceRowsClose(),
|
||||
otelsql.TraceRowsAffected(),
|
||||
// otelsql.WithDatabaseName("my_database"), // Optional.
|
||||
otelsql.WithSystem(system), // Optional.
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Connect to a Postgres database using the postgres driver wrapper.
|
||||
db, err := sql.Open(driverName, dsn)
|
||||
db, err := sql.Open(driver, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,21 +3,27 @@ package sql
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"go.sour.is/pkg/lg"
|
||||
"go.sour.is/pkg/mercury"
|
||||
"go.sour.is/pkg/rsql"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
var MAX_FILTER int = 40
|
||||
|
||||
type sqlHandler struct {
|
||||
name string
|
||||
db *sql.DB
|
||||
paceholderFormat sq.PlaceholderFormat
|
||||
listFormat [2]rune
|
||||
readonly bool
|
||||
getWhere func(search mercury.Search) (func(sq.SelectBuilder) sq.SelectBuilder, error)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -27,11 +33,13 @@ var (
|
||||
_ mercury.WriteConfig = (*sqlHandler)(nil)
|
||||
)
|
||||
|
||||
func Register() {
|
||||
func Register() func(context.Context) error {
|
||||
var hdlrs []*sqlHandler
|
||||
mercury.Registry.Register("sql", func(s *mercury.Space) any {
|
||||
var dsn string
|
||||
var opts strings.Builder
|
||||
var dbtype string
|
||||
var readonly bool = slices.Contains(s.Tags, "readonly")
|
||||
for _, c := range s.List {
|
||||
if c.Name == "match" {
|
||||
continue
|
||||
@@ -49,7 +57,6 @@ func Register() {
|
||||
if dsn == "" {
|
||||
dsn = opts.String()
|
||||
}
|
||||
|
||||
db, err := openDB(dbtype, dsn)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -58,31 +65,47 @@ func Register() {
|
||||
return err
|
||||
}
|
||||
switch dbtype {
|
||||
case "sqlite":
|
||||
return &sqlHandler{db, sq.Dollar, [2]rune{'[', ']'}}
|
||||
case "sqlite", "libsql", "libsql+embed":
|
||||
h := &sqlHandler{s.Space, db, sq.Question, [2]rune{'[', ']'}, readonly, GetWhereSQ}
|
||||
hdlrs = append(hdlrs, h)
|
||||
return h
|
||||
case "postgres":
|
||||
return &sqlHandler{db, sq.Dollar, [2]rune{'{', '}'}}
|
||||
h := &sqlHandler{s.Space, db, sq.Dollar, [2]rune{'{', '}'}, readonly, GetWherePG}
|
||||
hdlrs = append(hdlrs, h)
|
||||
return h
|
||||
default:
|
||||
return fmt.Errorf("unsupported dbtype: %s", dbtype)
|
||||
}
|
||||
})
|
||||
|
||||
return func(ctx context.Context) error {
|
||||
var errs error
|
||||
|
||||
for _, h := range hdlrs {
|
||||
// if err = ctx.Err(); err != nil {
|
||||
// return errors.Join(errs, err)
|
||||
// }
|
||||
errs = errors.Join(errs, h.db.Close())
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
}
|
||||
|
||||
type Space struct {
|
||||
mercury.Space
|
||||
ID uint64
|
||||
id uint64
|
||||
}
|
||||
type Value struct {
|
||||
mercury.Value
|
||||
ID uint64
|
||||
id uint64
|
||||
}
|
||||
|
||||
func (p *sqlHandler) GetIndex(ctx context.Context, search mercury.NamespaceSearch, pgm *rsql.Program) (mercury.Config, error) {
|
||||
func (p *sqlHandler) GetIndex(ctx context.Context, search mercury.Search) (mercury.Config, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
cols := rsql.GetDbColumns(mercury.Space{})
|
||||
where, err := getWhere(search, cols)
|
||||
where, err := p.getWhere(search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -100,28 +123,40 @@ func (p *sqlHandler) GetIndex(ctx context.Context, search mercury.NamespaceSearc
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (p *sqlHandler) GetConfig(ctx context.Context, search mercury.NamespaceSearch, pgm *rsql.Program, fields []string) (mercury.Config, error) {
|
||||
func (p *sqlHandler) GetConfig(ctx context.Context, search mercury.Search) (config mercury.Config, err error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
idx, err := p.GetIndex(ctx, search, pgm)
|
||||
where, err := p.getWhere(search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spaceMap := make(map[string]int, len(idx))
|
||||
for u, s := range idx {
|
||||
spaceMap[s.Space] = u
|
||||
lis, err := p.listSpace(ctx, nil, where)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
where, err := getWhere(search, rsql.GetDbColumns(mercury.Value{}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(lis) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
query := sq.Select(`"space"`, `"name"`, `"seq"`, `"notes"`, `"tags"`, `"values"`).
|
||||
From("mercury_registry_vw").
|
||||
Where(where).
|
||||
OrderBy("space asc", "name asc").
|
||||
|
||||
spaceIDX := make([]uint64, len(lis))
|
||||
spaceMap := make(map[uint64]int, len(lis))
|
||||
config = make(mercury.Config, len(lis))
|
||||
for i, s := range lis {
|
||||
spaceIDX[i] = s.id
|
||||
config[i] = &s.Space
|
||||
spaceMap[s.id] = i
|
||||
}
|
||||
|
||||
query := sq.Select(`"id"`, `"name"`, `"seq"`, `"notes"`, `"tags"`, `"values"`).
|
||||
From("mercury_values").
|
||||
Where(sq.Eq{"id": spaceIDX}).
|
||||
OrderBy("id asc", "seq asc").
|
||||
PlaceholderFormat(p.paceholderFormat)
|
||||
|
||||
span.AddEvent(p.name)
|
||||
span.AddEvent(lg.LogQuery(query.ToSql()))
|
||||
rows, err := query.RunWith(p.db).
|
||||
QueryContext(ctx)
|
||||
@@ -133,10 +168,10 @@ func (p *sqlHandler) GetConfig(ctx context.Context, search mercury.NamespaceSear
|
||||
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var s mercury.Value
|
||||
var s Value
|
||||
|
||||
err = rows.Scan(
|
||||
&s.Space,
|
||||
&s.id,
|
||||
&s.Name,
|
||||
&s.Seq,
|
||||
listScan(&s.Notes, p.listFormat),
|
||||
@@ -146,19 +181,20 @@ func (p *sqlHandler) GetConfig(ctx context.Context, search mercury.NamespaceSear
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u, ok := spaceMap[s.Space]; ok {
|
||||
idx[u].List = append(idx[u].List, s)
|
||||
if u, ok := spaceMap[s.id]; ok {
|
||||
lis[u].List = append(lis[u].List, s.Value)
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
span.RecordError(err)
|
||||
|
||||
span.AddEvent(fmt.Sprint("read index ", len(idx)))
|
||||
return idx, err
|
||||
span.AddEvent(fmt.Sprint("read index ", len(lis)))
|
||||
// log.Println(config.String())
|
||||
return config, err
|
||||
}
|
||||
|
||||
func (p *sqlHandler) listSpace(ctx context.Context, tx sq.BaseRunner, where sq.Sqlizer) ([]*Space, error) {
|
||||
func (p *sqlHandler) listSpace(ctx context.Context, tx sq.BaseRunner, where func(sq.SelectBuilder) sq.SelectBuilder) ([]*Space, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
@@ -168,9 +204,11 @@ func (p *sqlHandler) listSpace(ctx context.Context, tx sq.BaseRunner, where sq.S
|
||||
|
||||
query := sq.Select(`"id"`, `"space"`, `"notes"`, `"tags"`, `"trailer"`).
|
||||
From("mercury_spaces").
|
||||
Where(where).
|
||||
OrderBy("space asc").
|
||||
PlaceholderFormat(sq.Dollar)
|
||||
PlaceholderFormat(p.paceholderFormat)
|
||||
query = where(query)
|
||||
|
||||
span.AddEvent(p.name)
|
||||
span.AddEvent(lg.LogQuery(query.ToSql()))
|
||||
rows, err := query.RunWith(tx).
|
||||
QueryContext(ctx)
|
||||
@@ -185,7 +223,7 @@ func (p *sqlHandler) listSpace(ctx context.Context, tx sq.BaseRunner, where sq.S
|
||||
for rows.Next() {
|
||||
var s Space
|
||||
err = rows.Scan(
|
||||
&s.ID,
|
||||
&s.id,
|
||||
&s.Space.Space,
|
||||
listScan(&s.Space.Notes, p.listFormat),
|
||||
listScan(&s.Space.Tags, p.listFormat),
|
||||
@@ -209,6 +247,10 @@ func (p *sqlHandler) WriteConfig(ctx context.Context, config mercury.Config) (er
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
if p.readonly {
|
||||
return fmt.Errorf("readonly database")
|
||||
}
|
||||
|
||||
// Delete spaces that are present in input but are empty.
|
||||
deleteSpaces := make(map[string]struct{})
|
||||
|
||||
@@ -233,7 +275,8 @@ func (p *sqlHandler) WriteConfig(ctx context.Context, config mercury.Config) (er
|
||||
}()
|
||||
|
||||
// get current spaces
|
||||
lis, err := p.listSpace(ctx, tx, sq.Eq{"space": maps.Keys(names)})
|
||||
where := func(qry sq.SelectBuilder) sq.SelectBuilder { return qry.Where(sq.Eq{"space": maps.Keys(names)}) }
|
||||
lis, err := p.listSpace(ctx, tx, where)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -250,12 +293,12 @@ func (p *sqlHandler) WriteConfig(ctx context.Context, config mercury.Config) (er
|
||||
currentNames[spaceName] = struct{}{}
|
||||
|
||||
if _, ok := deleteSpaces[spaceName]; ok {
|
||||
deleteIDs = append(deleteIDs, s.ID)
|
||||
deleteIDs = append(deleteIDs, s.id)
|
||||
continue
|
||||
}
|
||||
|
||||
updateSpaces = append(updateSpaces, config[names[spaceName]])
|
||||
updateIDs = append(updateIDs, s.ID)
|
||||
updateIDs = append(updateIDs, s.id)
|
||||
}
|
||||
for _, s := range config {
|
||||
spaceName := s.Space
|
||||
@@ -266,7 +309,7 @@ func (p *sqlHandler) WriteConfig(ctx context.Context, config mercury.Config) (er
|
||||
|
||||
// delete spaces
|
||||
if ids := deleteIDs; len(ids) > 0 {
|
||||
_, err = sq.Delete("mercury_spaces").Where(sq.Eq{"id": ids}).RunWith(tx).PlaceholderFormat(sq.Dollar).ExecContext(ctx)
|
||||
_, err = sq.Delete("mercury_spaces").Where(sq.Eq{"id": ids}).RunWith(tx).PlaceholderFormat(p.paceholderFormat).ExecContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -274,7 +317,7 @@ func (p *sqlHandler) WriteConfig(ctx context.Context, config mercury.Config) (er
|
||||
|
||||
// delete values
|
||||
if ids := append(updateIDs, deleteIDs...); len(ids) > 0 {
|
||||
_, err = sq.Delete("mercury_values").Where(sq.Eq{"id": ids}).RunWith(tx).PlaceholderFormat(sq.Dollar).ExecContext(ctx)
|
||||
_, err = sq.Delete("mercury_values").Where(sq.Eq{"id": ids}).RunWith(tx).PlaceholderFormat(p.paceholderFormat).ExecContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -289,7 +332,8 @@ func (p *sqlHandler) WriteConfig(ctx context.Context, config mercury.Config) (er
|
||||
Set("tags", listValue(u.Tags, p.listFormat)).
|
||||
Set("notes", listValue(u.Notes, p.listFormat)).
|
||||
Set("trailer", listValue(u.Trailer, p.listFormat)).
|
||||
PlaceholderFormat(sq.Dollar)
|
||||
PlaceholderFormat(p.paceholderFormat)
|
||||
span.AddEvent(p.name)
|
||||
span.AddEvent(lg.LogQuery(query.ToSql()))
|
||||
_, err := query.RunWith(tx).ExecContext(ctx)
|
||||
|
||||
@@ -298,7 +342,7 @@ func (p *sqlHandler) WriteConfig(ctx context.Context, config mercury.Config) (er
|
||||
}
|
||||
// log.Debugf("UPDATED %d SPACES", len(updateSpaces))
|
||||
for _, v := range u.List {
|
||||
newValues = append(newValues, &Value{Value: v, ID: updateIDs[i]})
|
||||
newValues = append(newValues, &Value{Value: v, id: updateIDs[i]})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,15 +350,16 @@ func (p *sqlHandler) WriteConfig(ctx context.Context, config mercury.Config) (er
|
||||
for _, s := range insertSpaces {
|
||||
var id uint64
|
||||
query := sq.Insert("mercury_spaces").
|
||||
PlaceholderFormat(sq.Dollar).
|
||||
PlaceholderFormat(p.paceholderFormat).
|
||||
Columns("space", "tags", "notes", "trailer").
|
||||
Values(
|
||||
s.Space,
|
||||
listValue(s.Tags, p.listFormat),
|
||||
s.Space,
|
||||
listValue(s.Tags, p.listFormat),
|
||||
listValue(s.Notes, p.listFormat),
|
||||
listValue(s.Trailer, p.listFormat),
|
||||
).
|
||||
).
|
||||
Suffix("RETURNING \"id\"")
|
||||
span.AddEvent(p.name)
|
||||
span.AddEvent(lg.LogQuery(query.ToSql()))
|
||||
|
||||
err := query.
|
||||
@@ -322,12 +367,13 @@ func (p *sqlHandler) WriteConfig(ctx context.Context, config mercury.Config) (er
|
||||
QueryRowContext(ctx).
|
||||
Scan(&id)
|
||||
if err != nil {
|
||||
span.AddEvent(p.name)
|
||||
s, v, _ := query.ToSql()
|
||||
log.Println(s, v, err)
|
||||
return err
|
||||
}
|
||||
for _, v := range s.List {
|
||||
newValues = append(newValues, &Value{Value: v, ID: id})
|
||||
newValues = append(newValues, &Value{Value: v, id: id})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +399,7 @@ func (p *sqlHandler) writeValues(ctx context.Context, tx sq.BaseRunner, lis []*V
|
||||
newInsert := func() sq.InsertBuilder {
|
||||
return sq.Insert("mercury_values").
|
||||
RunWith(tx).
|
||||
PlaceholderFormat(sq.Dollar).
|
||||
PlaceholderFormat(p.paceholderFormat).
|
||||
Columns(
|
||||
`"id"`,
|
||||
`"seq"`,
|
||||
@@ -367,7 +413,7 @@ func (p *sqlHandler) writeValues(ctx context.Context, tx sq.BaseRunner, lis []*V
|
||||
insert := newInsert()
|
||||
for i, s := range lis {
|
||||
insert = insert.Values(
|
||||
s.ID,
|
||||
s.id,
|
||||
s.Seq,
|
||||
s.Name,
|
||||
listValue(s.Values, p.listFormat),
|
||||
@@ -378,7 +424,7 @@ func (p *sqlHandler) writeValues(ctx context.Context, tx sq.BaseRunner, lis []*V
|
||||
|
||||
if i > 0 && i%chunk == 0 {
|
||||
// log.Debugf("inserting %v rows into %v", i%chunk, d.Table)
|
||||
// log.Debug(insert.ToSql())
|
||||
span.AddEvent(p.name)
|
||||
span.AddEvent(lg.LogQuery(insert.ToSql()))
|
||||
|
||||
_, err = insert.ExecContext(ctx)
|
||||
@@ -392,7 +438,7 @@ func (p *sqlHandler) writeValues(ctx context.Context, tx sq.BaseRunner, lis []*V
|
||||
}
|
||||
if len(lis)%chunk > 0 {
|
||||
// log.Debugf("inserting %v rows into %v", len(lis)%chunk, d.Table)
|
||||
// log.Debug(insert.ToSql())
|
||||
span.AddEvent(p.name)
|
||||
span.AddEvent(lg.LogQuery(insert.ToSql()))
|
||||
|
||||
_, err = insert.ExecContext(ctx)
|
||||
@@ -405,13 +451,11 @@ func (p *sqlHandler) writeValues(ctx context.Context, tx sq.BaseRunner, lis []*V
|
||||
return
|
||||
}
|
||||
|
||||
func getWhere(search mercury.NamespaceSearch, d *rsql.DbColumns) (sq.Sqlizer, error) {
|
||||
func GetWherePG(search mercury.Search) (func(sq.SelectBuilder) sq.SelectBuilder, error) {
|
||||
var where sq.Or
|
||||
space, err := d.Col("space")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range search {
|
||||
space := "space"
|
||||
|
||||
for _, m := range search.NamespaceSearch {
|
||||
switch m.(type) {
|
||||
case mercury.NamespaceNode:
|
||||
where = append(where, sq.Eq{space: m.Value()})
|
||||
@@ -422,5 +466,129 @@ func getWhere(search mercury.NamespaceSearch, d *rsql.DbColumns) (sq.Sqlizer, er
|
||||
where = append(where, e)
|
||||
}
|
||||
}
|
||||
return where, nil
|
||||
|
||||
var joins []sq.SelectBuilder
|
||||
for i, o := range search.Find {
|
||||
log.Println(o)
|
||||
if i > MAX_FILTER {
|
||||
err := fmt.Errorf("too many filters [%d]", MAX_FILTER)
|
||||
return nil, err
|
||||
}
|
||||
q := sq.Select("DISTINCT id").From("mercury_values")
|
||||
|
||||
switch o.Op {
|
||||
case "key":
|
||||
q = q.Where(sq.Eq{"name": o.Left})
|
||||
case "nkey":
|
||||
q = q.Where(sq.NotEq{"name": o.Left})
|
||||
case "eq":
|
||||
q = q.Where("name = ? AND ? = any (values)", o.Left, o.Right)
|
||||
case "neq":
|
||||
q = q.Where("name = ? AND ? != any (values)", o.Left, o.Right)
|
||||
|
||||
case "gt":
|
||||
q = q.Where("name = ? AND ? > any (values)", o.Left, o.Right)
|
||||
case "lt":
|
||||
q = q.Where("name = ? AND ? < any (values)", o.Left, o.Right)
|
||||
case "ge":
|
||||
q = q.Where("name = ? AND ? >= any (values)", o.Left, o.Right)
|
||||
case "le":
|
||||
q = q.Where("name = ? AND ? <= any (values)", o.Left, o.Right)
|
||||
|
||||
// case "like":
|
||||
// q = q.Where("name = ? AND value LIKE ?", o.Left, o.Right)
|
||||
// case "in":
|
||||
// q = q.Where(sq.Eq{"name": o.Left, "value": strings.Split(o.Right, " ")})
|
||||
}
|
||||
joins = append(joins, q)
|
||||
}
|
||||
|
||||
return func(s sq.SelectBuilder) sq.SelectBuilder {
|
||||
for i, q := range joins {
|
||||
s = s.JoinClause(q.Prefix("JOIN (").Suffix(fmt.Sprintf(`) r%03d USING (id)`, i)))
|
||||
}
|
||||
|
||||
if search.Count > 0 {
|
||||
s = s.Limit(search.Count)
|
||||
}
|
||||
return s.Where(where)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetWhereSQ(search mercury.Search) (func(sq.SelectBuilder) sq.SelectBuilder, error) {
|
||||
var where sq.Or
|
||||
|
||||
var errs error
|
||||
id := "id"
|
||||
space := "space"
|
||||
name := "name"
|
||||
values_each := `json_valid("values")`
|
||||
values_valid := `json_valid("values")`
|
||||
|
||||
if errs != nil {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
for _, m := range search.NamespaceSearch {
|
||||
switch m.(type) {
|
||||
case mercury.NamespaceNode:
|
||||
where = append(where, sq.Eq{space: m.Value()})
|
||||
case mercury.NamespaceStar:
|
||||
where = append(where, sq.Like{space: m.Value()})
|
||||
case mercury.NamespaceTrace:
|
||||
e := sq.Expr(`? LIKE `+space+` || '%'`, m.Value())
|
||||
where = append(where, e)
|
||||
}
|
||||
}
|
||||
|
||||
var joins []sq.SelectBuilder
|
||||
for i, o := range search.Find {
|
||||
log.Println(o)
|
||||
if i > MAX_FILTER {
|
||||
err := fmt.Errorf("too many filters [%d]", MAX_FILTER)
|
||||
return nil, err
|
||||
}
|
||||
q := sq.Select("DISTINCT " + id).From(`mercury_values mv, ` + values_each + ` vs`)
|
||||
|
||||
switch o.Op {
|
||||
case "key":
|
||||
q = q.Where(sq.Eq{name: o.Left})
|
||||
case "nkey":
|
||||
q = q.Where(sq.NotEq{name: o.Left})
|
||||
case "eq":
|
||||
q = q.Where(sq.And{sq.Expr(values_valid), sq.Eq{name: o.Left, `vs.value`: o.Right}})
|
||||
case "neq":
|
||||
q = q.Where(sq.And{sq.Expr(values_valid), sq.Eq{name: o.Left}, sq.NotEq{`vs.value`: o.Right}})
|
||||
|
||||
case "gt":
|
||||
q = q.Where(sq.And{sq.Expr(values_valid), sq.Eq{name: o.Left}, sq.Gt{`vs.value`: o.Right}})
|
||||
case "lt":
|
||||
q = q.Where(sq.And{sq.Expr(values_valid), sq.Eq{name: o.Left}, sq.Lt{`vs.value`: o.Right}})
|
||||
case "ge":
|
||||
q = q.Where(sq.And{sq.Expr(values_valid), sq.Eq{name: o.Left}, sq.GtOrEq{`vs.value`: o.Right}})
|
||||
case "le":
|
||||
q = q.Where(sq.And{sq.Expr(values_valid), sq.Eq{name: o.Left}, sq.LtOrEq{`vs.value`: o.Right}})
|
||||
case "like":
|
||||
q = q.Where(sq.And{sq.Expr(values_valid), sq.Eq{name: o.Left}, sq.Like{`vs.value`: o.Right}})
|
||||
case "in":
|
||||
q = q.Where(sq.Eq{name: o.Left, "vs.value": strings.Split(o.Right, " ")})
|
||||
}
|
||||
joins = append(joins, q)
|
||||
}
|
||||
|
||||
return func(s sq.SelectBuilder) sq.SelectBuilder {
|
||||
for i, q := range joins {
|
||||
s = s.JoinClause(q.Prefix("JOIN (").Suffix(fmt.Sprintf(`) r%03d USING (id)`, i)))
|
||||
}
|
||||
|
||||
if search.Count > 0 {
|
||||
s = s.Limit(search.Count)
|
||||
}
|
||||
|
||||
if search.Offset > 0 {
|
||||
s = s.Offset(search.Offset)
|
||||
}
|
||||
|
||||
return s.Where(where)
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user