Adding upstream version 0.28.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
88f1d47ab6
commit
e28c88ef14
933 changed files with 194711 additions and 0 deletions
217
tools/dbutils/index.go
Normal file
217
tools/dbutils/index.go
Normal file
|
@ -0,0 +1,217 @@
|
|||
package dbutils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/tokenizer"
|
||||
)
|
||||
|
||||
var (
|
||||
indexRegex = regexp.MustCompile(`(?im)create\s+(unique\s+)?\s*index\s*(if\s+not\s+exists\s+)?(\S*)\s+on\s+(\S*)\s*\(([\s\S]*)\)(?:\s*where\s+([\s\S]*))?`)
|
||||
indexColumnRegex = regexp.MustCompile(`(?im)^([\s\S]+?)(?:\s+collate\s+([\w]+))?(?:\s+(asc|desc))?$`)
|
||||
)
|
||||
|
||||
// IndexColumn represents a single parsed SQL index column.
|
||||
type IndexColumn struct {
|
||||
Name string `json:"name"` // identifier or expression
|
||||
Collate string `json:"collate"`
|
||||
Sort string `json:"sort"`
|
||||
}
|
||||
|
||||
// Index represents a single parsed SQL CREATE INDEX expression.
|
||||
type Index struct {
|
||||
SchemaName string `json:"schemaName"`
|
||||
IndexName string `json:"indexName"`
|
||||
TableName string `json:"tableName"`
|
||||
Where string `json:"where"`
|
||||
Columns []IndexColumn `json:"columns"`
|
||||
Unique bool `json:"unique"`
|
||||
Optional bool `json:"optional"`
|
||||
}
|
||||
|
||||
// IsValid checks if the current Index contains the minimum required fields to be considered valid.
|
||||
func (idx Index) IsValid() bool {
|
||||
return idx.IndexName != "" && idx.TableName != "" && len(idx.Columns) > 0
|
||||
}
|
||||
|
||||
// Build returns a "CREATE INDEX" SQL string from the current index parts.
|
||||
//
|
||||
// Returns empty string if idx.IsValid() is false.
|
||||
func (idx Index) Build() string {
|
||||
if !idx.IsValid() {
|
||||
return ""
|
||||
}
|
||||
|
||||
var str strings.Builder
|
||||
|
||||
str.WriteString("CREATE ")
|
||||
|
||||
if idx.Unique {
|
||||
str.WriteString("UNIQUE ")
|
||||
}
|
||||
|
||||
str.WriteString("INDEX ")
|
||||
|
||||
if idx.Optional {
|
||||
str.WriteString("IF NOT EXISTS ")
|
||||
}
|
||||
|
||||
if idx.SchemaName != "" {
|
||||
str.WriteString("`")
|
||||
str.WriteString(idx.SchemaName)
|
||||
str.WriteString("`.")
|
||||
}
|
||||
|
||||
str.WriteString("`")
|
||||
str.WriteString(idx.IndexName)
|
||||
str.WriteString("` ")
|
||||
|
||||
str.WriteString("ON `")
|
||||
str.WriteString(idx.TableName)
|
||||
str.WriteString("` (")
|
||||
|
||||
if len(idx.Columns) > 1 {
|
||||
str.WriteString("\n ")
|
||||
}
|
||||
|
||||
var hasCol bool
|
||||
for _, col := range idx.Columns {
|
||||
trimmedColName := strings.TrimSpace(col.Name)
|
||||
if trimmedColName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if hasCol {
|
||||
str.WriteString(",\n ")
|
||||
}
|
||||
|
||||
if strings.Contains(col.Name, "(") || strings.Contains(col.Name, " ") {
|
||||
// most likely an expression
|
||||
str.WriteString(trimmedColName)
|
||||
} else {
|
||||
// regular identifier
|
||||
str.WriteString("`")
|
||||
str.WriteString(trimmedColName)
|
||||
str.WriteString("`")
|
||||
}
|
||||
|
||||
if col.Collate != "" {
|
||||
str.WriteString(" COLLATE ")
|
||||
str.WriteString(col.Collate)
|
||||
}
|
||||
|
||||
if col.Sort != "" {
|
||||
str.WriteString(" ")
|
||||
str.WriteString(strings.ToUpper(col.Sort))
|
||||
}
|
||||
|
||||
hasCol = true
|
||||
}
|
||||
|
||||
if hasCol && len(idx.Columns) > 1 {
|
||||
str.WriteString("\n")
|
||||
}
|
||||
|
||||
str.WriteString(")")
|
||||
|
||||
if idx.Where != "" {
|
||||
str.WriteString(" WHERE ")
|
||||
str.WriteString(idx.Where)
|
||||
}
|
||||
|
||||
return str.String()
|
||||
}
|
||||
|
||||
// ParseIndex parses the provided "CREATE INDEX" SQL string into Index struct.
|
||||
func ParseIndex(createIndexExpr string) Index {
|
||||
result := Index{}
|
||||
|
||||
matches := indexRegex.FindStringSubmatch(createIndexExpr)
|
||||
if len(matches) != 7 {
|
||||
return result
|
||||
}
|
||||
|
||||
trimChars := "`\"'[]\r\n\t\f\v "
|
||||
|
||||
// Unique
|
||||
// ---
|
||||
result.Unique = strings.TrimSpace(matches[1]) != ""
|
||||
|
||||
// Optional (aka. "IF NOT EXISTS")
|
||||
// ---
|
||||
result.Optional = strings.TrimSpace(matches[2]) != ""
|
||||
|
||||
// SchemaName and IndexName
|
||||
// ---
|
||||
nameTk := tokenizer.NewFromString(matches[3])
|
||||
nameTk.Separators('.')
|
||||
|
||||
nameParts, _ := nameTk.ScanAll()
|
||||
if len(nameParts) == 2 {
|
||||
result.SchemaName = strings.Trim(nameParts[0], trimChars)
|
||||
result.IndexName = strings.Trim(nameParts[1], trimChars)
|
||||
} else {
|
||||
result.IndexName = strings.Trim(nameParts[0], trimChars)
|
||||
}
|
||||
|
||||
// TableName
|
||||
// ---
|
||||
result.TableName = strings.Trim(matches[4], trimChars)
|
||||
|
||||
// Columns
|
||||
// ---
|
||||
columnsTk := tokenizer.NewFromString(matches[5])
|
||||
columnsTk.Separators(',')
|
||||
|
||||
rawColumns, _ := columnsTk.ScanAll()
|
||||
|
||||
result.Columns = make([]IndexColumn, 0, len(rawColumns))
|
||||
|
||||
for _, col := range rawColumns {
|
||||
colMatches := indexColumnRegex.FindStringSubmatch(col)
|
||||
if len(colMatches) != 4 {
|
||||
continue
|
||||
}
|
||||
|
||||
trimmedName := strings.Trim(colMatches[1], trimChars)
|
||||
if trimmedName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
result.Columns = append(result.Columns, IndexColumn{
|
||||
Name: trimmedName,
|
||||
Collate: strings.TrimSpace(colMatches[2]),
|
||||
Sort: strings.ToUpper(colMatches[3]),
|
||||
})
|
||||
}
|
||||
|
||||
// WHERE expression
|
||||
// ---
|
||||
result.Where = strings.TrimSpace(matches[6])
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FindSingleColumnUniqueIndex returns the first matching single column unique index.
|
||||
func FindSingleColumnUniqueIndex(indexes []string, column string) (Index, bool) {
|
||||
var index Index
|
||||
|
||||
for _, idx := range indexes {
|
||||
index := ParseIndex(idx)
|
||||
if index.Unique && len(index.Columns) == 1 && strings.EqualFold(index.Columns[0].Name, column) {
|
||||
return index, true
|
||||
}
|
||||
}
|
||||
|
||||
return index, false
|
||||
}
|
||||
|
||||
// Deprecated: Use `_, ok := FindSingleColumnUniqueIndex(indexes, column)` instead.
|
||||
//
|
||||
// HasColumnUniqueIndex loosely checks whether the specified column has
|
||||
// a single column unique index (WHERE statements are ignored).
|
||||
func HasSingleColumnUniqueIndex(column string, indexes []string) bool {
|
||||
_, ok := FindSingleColumnUniqueIndex(indexes, column)
|
||||
return ok
|
||||
}
|
405
tools/dbutils/index_test.go
Normal file
405
tools/dbutils/index_test.go
Normal file
|
@ -0,0 +1,405 @@
|
|||
package dbutils_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/dbutils"
|
||||
)
|
||||
|
||||
func TestParseIndex(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
index string
|
||||
expected dbutils.Index
|
||||
}{
|
||||
// invalid
|
||||
{
|
||||
`invalid`,
|
||||
dbutils.Index{},
|
||||
},
|
||||
// simple (multiple spaces between the table and columns list)
|
||||
{
|
||||
`create index indexname on tablename (col1)`,
|
||||
dbutils.Index{
|
||||
IndexName: "indexname",
|
||||
TableName: "tablename",
|
||||
Columns: []dbutils.IndexColumn{
|
||||
{Name: "col1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
// simple (no space between the table and the columns list)
|
||||
{
|
||||
`create index indexname on tablename(col1)`,
|
||||
dbutils.Index{
|
||||
IndexName: "indexname",
|
||||
TableName: "tablename",
|
||||
Columns: []dbutils.IndexColumn{
|
||||
{Name: "col1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
// all fields
|
||||
{
|
||||
`CREATE UNIQUE INDEX IF NOT EXISTS "schemaname".[indexname] on 'tablename' (
|
||||
col0,
|
||||
` + "`" + `col1` + "`" + `,
|
||||
json_extract("col2", "$.a") asc,
|
||||
"col3" collate NOCASE,
|
||||
"col4" collate RTRIM desc
|
||||
) where test = 1`,
|
||||
dbutils.Index{
|
||||
Unique: true,
|
||||
Optional: true,
|
||||
SchemaName: "schemaname",
|
||||
IndexName: "indexname",
|
||||
TableName: "tablename",
|
||||
Columns: []dbutils.IndexColumn{
|
||||
{Name: "col0"},
|
||||
{Name: "col1"},
|
||||
{Name: `json_extract("col2", "$.a")`, Sort: "ASC"},
|
||||
{Name: `col3`, Collate: "NOCASE"},
|
||||
{Name: `col4`, Collate: "RTRIM", Sort: "DESC"},
|
||||
},
|
||||
Where: "test = 1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
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("Faild to marshalize parse result: %v", 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("Expected \n%s \ngot \n%s", expectedRaw, resultRaw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexIsValid(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
index dbutils.Index
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
dbutils.Index{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no index name",
|
||||
dbutils.Index{
|
||||
TableName: "table",
|
||||
Columns: []dbutils.IndexColumn{{Name: "col"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no table name",
|
||||
dbutils.Index{
|
||||
IndexName: "index",
|
||||
Columns: []dbutils.IndexColumn{{Name: "col"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"no columns",
|
||||
dbutils.Index{
|
||||
IndexName: "index",
|
||||
TableName: "table",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"min valid",
|
||||
dbutils.Index{
|
||||
IndexName: "index",
|
||||
TableName: "table",
|
||||
Columns: []dbutils.IndexColumn{{Name: "col"}},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"all fields",
|
||||
dbutils.Index{
|
||||
Optional: true,
|
||||
Unique: true,
|
||||
SchemaName: "schema",
|
||||
IndexName: "index",
|
||||
TableName: "table",
|
||||
Columns: []dbutils.IndexColumn{{Name: "col"}},
|
||||
Where: "test = 1 OR test = 2",
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexBuild(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
index dbutils.Index
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
dbutils.Index{},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"no index name",
|
||||
dbutils.Index{
|
||||
TableName: "table",
|
||||
Columns: []dbutils.IndexColumn{{Name: "col"}},
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"no table name",
|
||||
dbutils.Index{
|
||||
IndexName: "index",
|
||||
Columns: []dbutils.IndexColumn{{Name: "col"}},
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"no columns",
|
||||
dbutils.Index{
|
||||
IndexName: "index",
|
||||
TableName: "table",
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"min valid",
|
||||
dbutils.Index{
|
||||
IndexName: "index",
|
||||
TableName: "table",
|
||||
Columns: []dbutils.IndexColumn{{Name: "col"}},
|
||||
},
|
||||
"CREATE INDEX `index` ON `table` (`col`)",
|
||||
},
|
||||
{
|
||||
"all fields",
|
||||
dbutils.Index{
|
||||
Optional: true,
|
||||
Unique: true,
|
||||
SchemaName: "schema",
|
||||
IndexName: "index",
|
||||
TableName: "table",
|
||||
Columns: []dbutils.IndexColumn{
|
||||
{Name: "col1", Collate: "NOCASE", Sort: "asc"},
|
||||
{Name: "col2", Sort: "desc"},
|
||||
{Name: `json_extract("col3", "$.a")`, Collate: "NOCASE"},
|
||||
},
|
||||
Where: "test = 1 OR test = 2",
|
||||
},
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS `schema`.`index` ON `table` (\n `col1` COLLATE NOCASE ASC,\n `col2` DESC,\n " + `json_extract("col3", "$.a")` + " COLLATE NOCASE\n) WHERE test = 1 OR test = 2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindSingleColumnUniqueIndex(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) {
|
||||
index, exists := dbutils.FindSingleColumnUniqueIndex(s.indexes, s.column)
|
||||
if exists != s.expected {
|
||||
t.Fatalf("Expected exists %v got %v", s.expected, exists)
|
||||
}
|
||||
|
||||
if !exists && len(index.Columns) > 0 {
|
||||
t.Fatal("Expected index.Columns to be empty")
|
||||
}
|
||||
|
||||
if exists && !strings.EqualFold(index.Columns[0].Name, s.column) {
|
||||
t.Fatalf("Expected to find column %q in %v", s.column, index)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
51
tools/dbutils/json.go
Normal file
51
tools/dbutils/json.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package dbutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JSONEach returns JSON_EACH SQLite string expression with
|
||||
// some normalizations for non-json columns.
|
||||
func JSONEach(column string) string {
|
||||
// note: we are not using the new and shorter "if(x,y)" syntax for
|
||||
// compatability with custom drivers that use older SQLite version
|
||||
return fmt.Sprintf(
|
||||
`json_each(CASE WHEN iif(json_valid([[%s]]), json_type([[%s]])='array', FALSE) THEN [[%s]] ELSE json_array([[%s]]) END)`,
|
||||
column, 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 {
|
||||
// note: we are not using the new and shorter "if(x,y)" syntax for
|
||||
// compatability with custom drivers that use older SQLite version
|
||||
return fmt.Sprintf(
|
||||
`json_array_length(CASE WHEN iif(json_valid([[%s]]), json_type([[%s]])='array', FALSE) THEN [[%s]] ELSE (CASE WHEN [[%s]] = '' OR [[%s]] IS NULL THEN json_array() ELSE json_array([[%s]]) END) END)`,
|
||||
column, 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,
|
||||
)
|
||||
}
|
65
tools/dbutils/json_test.go
Normal file
65
tools/dbutils/json_test.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
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 iif(json_valid([[a.b]]), json_type([[a.b]])='array', FALSE) 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 iif(json_valid([[a.b]]), json_type([[a.b]])='array', FALSE) 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue