added back relation filter reference support

This commit is contained in:
Gani Georgiev
2024-02-19 16:55:34 +02:00
parent 4743c1ce72
commit 4937acb3e2
18 changed files with 660 additions and 169 deletions
+13
View File
@@ -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
View File
@@ -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)
}
})
}
}
+47
View File
@@ -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,
)
}
+66
View File
@@ -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)
}
})
}
}