added back relation filter reference support
This commit is contained in:
@@ -192,3 +192,16 @@ func ParseIndex(createIndexExpr string) Index {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// HasColumnUniqueIndex loosely checks whether the specified column has
|
||||
// a single column unique index (WHERE statements are ignored).
|
||||
func HasSingleColumnUniqueIndex(column string, indexes []string) bool {
|
||||
for _, idx := range indexes {
|
||||
parsed := ParseIndex(idx)
|
||||
if parsed.Unique && len(parsed.Columns) == 1 && strings.EqualFold(parsed.Columns[0].Name, column) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
+109
-22
@@ -3,6 +3,7 @@ package dbutils_test
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/dbutils"
|
||||
@@ -68,21 +69,23 @@ func TestParseIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result := dbutils.ParseIndex(s.index)
|
||||
t.Run(fmt.Sprintf("scenario_%d", i), func(t *testing.T) {
|
||||
result := dbutils.ParseIndex(s.index)
|
||||
|
||||
resultRaw, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Fatalf("[%d] %v", i, err)
|
||||
}
|
||||
resultRaw, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Fatalf("Faild to marshalize parse result: %v", err)
|
||||
}
|
||||
|
||||
expectedRaw, err := json.Marshal(s.expected)
|
||||
if err != nil {
|
||||
t.Fatalf("[%d] %v", i, err)
|
||||
}
|
||||
expectedRaw, err := json.Marshal(s.expected)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshalize expected index: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(resultRaw, expectedRaw) {
|
||||
t.Errorf("[%d] Expected \n%s \ngot \n%s", i, expectedRaw, resultRaw)
|
||||
}
|
||||
if !bytes.Equal(resultRaw, expectedRaw) {
|
||||
t.Errorf("Expected \n%s \ngot \n%s", expectedRaw, resultRaw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,11 +149,12 @@ func TestIndexIsValid(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
result := s.index.IsValid()
|
||||
|
||||
if result != s.expected {
|
||||
t.Errorf("[%s] Expected %v, got %v", s.name, s.expected, result)
|
||||
}
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := s.index.IsValid()
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected %v, got %v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,10 +222,93 @@ func TestIndexBuild(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
result := s.index.Build()
|
||||
|
||||
if result != s.expected {
|
||||
t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, result)
|
||||
}
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := s.index.Build()
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected \n%v \ngot \n%v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasSingleColumnUniqueIndex(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
column string
|
||||
indexes []string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"empty indexes",
|
||||
"test",
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"empty column",
|
||||
"",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test`)",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"mismatched column",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test2`)",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"non unique index",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE INDEX `index1` ON `example` (`test`)",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"matching columnd and unique index",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test`)",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"multiple columns",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test`, `test2`)",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"multiple indexes",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index1` ON `example` (`test`, `test2`)",
|
||||
"CREATE UNIQUE INDEX `index2` ON `example` (`test`)",
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"partial unique index",
|
||||
"test",
|
||||
[]string{
|
||||
"CREATE UNIQUE INDEX `index` ON `example` (`test`) where test != ''",
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := dbutils.HasSingleColumnUniqueIndex(s.column, s.indexes)
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected %v got %v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package dbutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JsonEach returns JSON_EACH SQLite string expression with
|
||||
// some normalizations for non-json columns.
|
||||
func JsonEach(column string) string {
|
||||
return fmt.Sprintf(
|
||||
`json_each(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END)`,
|
||||
column, column, column,
|
||||
)
|
||||
}
|
||||
|
||||
// JsonArrayLength returns JSON_ARRAY_LENGTH SQLite string expression
|
||||
// with some normalizations for non-json columns.
|
||||
//
|
||||
// It works with both json and non-json column values.
|
||||
//
|
||||
// Returns 0 for empty string or NULL column values.
|
||||
func JsonArrayLength(column string) string {
|
||||
return fmt.Sprintf(
|
||||
`json_array_length(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE (CASE WHEN [[%s]] = '' OR [[%s]] IS NULL THEN json_array() ELSE json_array([[%s]]) END) END)`,
|
||||
column, column, column, column, column,
|
||||
)
|
||||
}
|
||||
|
||||
// JsonExtract returns a JSON_EXTRACT SQLite string expression with
|
||||
// some normalizations for non-json columns.
|
||||
func JsonExtract(column string, path string) string {
|
||||
// prefix the path with dot if it is not starting with array notation
|
||||
if path != "" && !strings.HasPrefix(path, "[") {
|
||||
path = "." + path
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
// note: the extra object wrapping is needed to workaround the cases where a json_extract is used with non-json columns.
|
||||
"(CASE WHEN json_valid([[%s]]) THEN JSON_EXTRACT([[%s]], '$%s') ELSE JSON_EXTRACT(json_object('pb', [[%s]]), '$.pb%s') END)",
|
||||
column,
|
||||
column,
|
||||
path,
|
||||
column,
|
||||
path,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package dbutils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/dbutils"
|
||||
)
|
||||
|
||||
func TestJsonEach(t *testing.T) {
|
||||
result := dbutils.JsonEach("a.b")
|
||||
|
||||
expected := "json_each(CASE WHEN json_valid([[a.b]]) THEN [[a.b]] ELSE json_array([[a.b]]) END)"
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonArrayLength(t *testing.T) {
|
||||
result := dbutils.JsonArrayLength("a.b")
|
||||
|
||||
expected := "json_array_length(CASE WHEN json_valid([[a.b]]) THEN [[a.b]] ELSE (CASE WHEN [[a.b]] = '' OR [[a.b]] IS NULL THEN json_array() ELSE json_array([[a.b]]) END) END)"
|
||||
|
||||
if result != expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonExtract(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
column string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"empty path",
|
||||
"a.b",
|
||||
"",
|
||||
"(CASE WHEN json_valid([[a.b]]) THEN JSON_EXTRACT([[a.b]], '$') ELSE JSON_EXTRACT(json_object('pb', [[a.b]]), '$.pb') END)",
|
||||
},
|
||||
{
|
||||
"starting with array index",
|
||||
"a.b",
|
||||
"[1].a[2]",
|
||||
"(CASE WHEN json_valid([[a.b]]) THEN JSON_EXTRACT([[a.b]], '$[1].a[2]') ELSE JSON_EXTRACT(json_object('pb', [[a.b]]), '$.pb[1].a[2]') END)",
|
||||
},
|
||||
{
|
||||
"starting with key",
|
||||
"a.b",
|
||||
"a.b[2].c",
|
||||
"(CASE WHEN json_valid([[a.b]]) THEN JSON_EXTRACT([[a.b]], '$.a.b[2].c') ELSE JSON_EXTRACT(json_object('pb', [[a.b]]), '$.pb.a.b[2].c') END)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := dbutils.JsonExtract(s.column, s.path)
|
||||
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user