synced with master

This commit is contained in:
Gani Georgiev 2023-07-17 13:17:17 +03:00
commit 7d4017225c
7 changed files with 162 additions and 122 deletions

View File

@ -85,6 +85,11 @@
- (@todo docs) Added `record.ExpandedOne(rel)` and `record.ExpandedAll(rel)` helpers to retrieve casted single or multiple expand relations from the already loaded "expand" Record data. - (@todo docs) Added `record.ExpandedOne(rel)` and `record.ExpandedAll(rel)` helpers to retrieve casted single or multiple expand relations from the already loaded "expand" Record data.
## v0.16.10
- Added multiple valued fields (`relation`, `select`, `file`) normalizations to ensure that the zero-default value of a newly created multiple field is applied for already existing data ([#2930](https://github.com/pocketbase/pocketbase/issues/2930)).
## v0.16.9 ## v0.16.9
- Register the `eagerRequestDataCache` middleware only for the internal `api` group routes to avoid conflicts with custom route handlers ([#2914](https://github.com/pocketbase/pocketbase/issues/2914)). - Register the `eagerRequestDataCache` middleware only for the internal `api` group routes to avoid conflicts with custom route handlers ([#2914](https://github.com/pocketbase/pocketbase/issues/2914)).

View File

@ -174,9 +174,13 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
return dao.RunInTransaction(func(txDao *Dao) error { return dao.RunInTransaction(func(txDao *Dao) error {
for _, newField := range newCollection.Schema.Fields() { for _, newField := range newCollection.Schema.Fields() {
oldField := oldCollection.Schema.GetFieldById(newField.Id) // allow to continue even if there is no old field for the cases
if oldField == nil { // when a new field is added and there are already inserted data
continue var isOldMultiple bool
if oldField := oldCollection.Schema.GetFieldById(newField.Id); oldField != nil {
if opt, ok := oldField.Options.(schema.MultiValuer); ok {
isOldMultiple = opt.IsMultiple()
}
} }
var isNewMultiple bool var isNewMultiple bool
@ -184,11 +188,6 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
isNewMultiple = opt.IsMultiple() isNewMultiple = opt.IsMultiple()
} }
var isOldMultiple bool
if opt, ok := oldField.Options.(schema.MultiValuer); ok {
isOldMultiple = opt.IsMultiple()
}
if isOldMultiple == isNewMultiple { if isOldMultiple == isNewMultiple {
continue // no change continue // no change
} }

View File

@ -171,18 +171,31 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
opt := relManyField.Options.(*schema.RelationOptions) opt := relManyField.Options.(*schema.RelationOptions)
opt.MaxSelect = types.Pointer(1) opt.MaxSelect = types.Pointer(1)
} }
{
// new multivaluer field to check whether the array normalization
// will be applied for already inserted data
collection.Schema.AddField(&schema.SchemaField{
Name: "new_multiple",
Type: schema.FieldTypeSelect,
Options: &schema.SelectOptions{
Values: []string{"a", "b", "c"},
MaxSelect: 3,
},
})
}
if err := app.Dao().SaveCollection(collection); err != nil { if err := app.Dao().SaveCollection(collection); err != nil {
t.Fatal(err) t.Fatal(err)
} }
type expectation struct { type expectation struct {
SelectOne string `db:"select_one"` SelectOne string `db:"select_one"`
SelectMany string `db:"select_many"` SelectMany string `db:"select_many"`
FileOne string `db:"file_one"` FileOne string `db:"file_one"`
FileMany string `db:"file_many"` FileMany string `db:"file_many"`
RelOne string `db:"rel_one"` RelOne string `db:"rel_one"`
RelMany string `db:"rel_many"` RelMany string `db:"rel_many"`
NewMultiple string `db:"new_multiple"`
} }
scenarios := []struct { scenarios := []struct {
@ -192,34 +205,37 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
{ {
"imy661ixudk5izi", "imy661ixudk5izi",
expectation{ expectation{
SelectOne: `[]`, SelectOne: `[]`,
SelectMany: ``, SelectMany: ``,
FileOne: `[]`, FileOne: `[]`,
FileMany: ``, FileMany: ``,
RelOne: `[]`, RelOne: `[]`,
RelMany: ``, RelMany: ``,
NewMultiple: `[]`,
}, },
}, },
{ {
"al1h9ijdeojtsjy", "al1h9ijdeojtsjy",
expectation{ expectation{
SelectOne: `["optionB"]`, SelectOne: `["optionB"]`,
SelectMany: `optionB`, SelectMany: `optionB`,
FileOne: `["300_Jsjq7RdBgA.png"]`, FileOne: `["300_Jsjq7RdBgA.png"]`,
FileMany: ``, FileMany: ``,
RelOne: `["84nmscqy84lsi1t"]`, RelOne: `["84nmscqy84lsi1t"]`,
RelMany: `oap640cot4yru2s`, RelMany: `oap640cot4yru2s`,
NewMultiple: `[]`,
}, },
}, },
{ {
"84nmscqy84lsi1t", "84nmscqy84lsi1t",
expectation{ expectation{
SelectOne: `["optionB"]`, SelectOne: `["optionB"]`,
SelectMany: `optionC`, SelectMany: `optionC`,
FileOne: `["test_d61b33QdDU.txt"]`, FileOne: `["test_d61b33QdDU.txt"]`,
FileMany: `test_tC1Yc87DfC.txt`, FileMany: `test_tC1Yc87DfC.txt`,
RelOne: `[]`, RelOne: `[]`,
RelMany: `oap640cot4yru2s`, RelMany: `oap640cot4yru2s`,
NewMultiple: `[]`,
}, },
}, },
} }
@ -234,6 +250,7 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
"file_many", "file_many",
"rel_one", "rel_one",
"rel_many", "rel_many",
"new_multiple",
).From(collection.Name).Where(dbx.HashExp{"id": s.recordId}).One(result) ).From(collection.Name).Where(dbx.HashExp{"id": s.recordId}).One(result)
if err != nil { if err != nil {
t.Errorf("[%s] Failed to load record: %v", s.recordId, err) t.Errorf("[%s] Failed to load record: %v", s.recordId, err)

View File

@ -12,93 +12,97 @@ import (
// Normalizes old single and multiple values of MultiValuer fields (file, select, relation). // Normalizes old single and multiple values of MultiValuer fields (file, select, relation).
func init() { func init() {
AppMigrations.Register(func(db dbx.Builder) error { AppMigrations.Register(func(db dbx.Builder) error {
dao := daos.New(db) return normalizeMultivaluerFields(db)
collections := []*models.Collection{}
if err := dao.CollectionQuery().All(&collections); err != nil {
return err
}
for _, c := range collections {
if c.IsView() {
// skip view collections
continue
}
for _, f := range c.Schema.Fields() {
opt, ok := f.Options.(schema.MultiValuer)
if !ok {
continue
}
var updateQuery *dbx.Query
if opt.IsMultiple() {
updateQuery = dao.DB().NewQuery(fmt.Sprintf(
`UPDATE {{%s}} set [[%s]] = (
CASE
WHEN COALESCE([[%s]], '') = ''
THEN '[]'
ELSE (
CASE
WHEN json_valid([[%s]]) AND json_type([[%s]]) == 'array'
THEN [[%s]]
ELSE json_array([[%s]])
END
)
END
)`,
c.Name,
f.Name,
f.Name,
f.Name,
f.Name,
f.Name,
f.Name,
))
} else {
updateQuery = dao.DB().NewQuery(fmt.Sprintf(
`UPDATE {{%s}} set [[%s]] = (
CASE
WHEN COALESCE([[%s]], '[]') = '[]'
THEN ''
ELSE (
CASE
WHEN json_valid([[%s]]) AND json_type([[%s]]) == 'array'
THEN COALESCE(json_extract([[%s]], '$[#-1]'), '')
ELSE [[%s]]
END
)
END
)`,
c.Name,
f.Name,
f.Name,
f.Name,
f.Name,
f.Name,
f.Name,
))
}
if _, err := updateQuery.Execute(); err != nil {
return err
}
}
}
// trigger view query update after the records normalization
// (ignore save error in case of invalid query to allow users to change it from the UI)
for _, c := range collections {
if !c.IsView() {
continue
}
dao.SaveCollection(c)
}
return nil
}, func(db dbx.Builder) error { }, func(db dbx.Builder) error {
return nil return nil
}) })
} }
func normalizeMultivaluerFields(db dbx.Builder) error {
dao := daos.New(db)
collections := []*models.Collection{}
if err := dao.CollectionQuery().All(&collections); err != nil {
return err
}
for _, c := range collections {
if c.IsView() {
// skip view collections
continue
}
for _, f := range c.Schema.Fields() {
opt, ok := f.Options.(schema.MultiValuer)
if !ok {
continue
}
var updateQuery *dbx.Query
if opt.IsMultiple() {
updateQuery = dao.DB().NewQuery(fmt.Sprintf(
`UPDATE {{%s}} set [[%s]] = (
CASE
WHEN COALESCE([[%s]], '') = ''
THEN '[]'
ELSE (
CASE
WHEN json_valid([[%s]]) AND json_type([[%s]]) == 'array'
THEN [[%s]]
ELSE json_array([[%s]])
END
)
END
)`,
c.Name,
f.Name,
f.Name,
f.Name,
f.Name,
f.Name,
f.Name,
))
} else {
updateQuery = dao.DB().NewQuery(fmt.Sprintf(
`UPDATE {{%s}} set [[%s]] = (
CASE
WHEN COALESCE([[%s]], '[]') = '[]'
THEN ''
ELSE (
CASE
WHEN json_valid([[%s]]) AND json_type([[%s]]) == 'array'
THEN COALESCE(json_extract([[%s]], '$[#-1]'), '')
ELSE [[%s]]
END
)
END
)`,
c.Name,
f.Name,
f.Name,
f.Name,
f.Name,
f.Name,
f.Name,
))
}
if _, err := updateQuery.Execute(); err != nil {
return err
}
}
}
// trigger view query update after the records normalization
// (ignore save error in case of invalid query to allow users to change it from the UI)
for _, c := range collections {
if !c.IsView() {
continue
}
dao.SaveCollection(c)
}
return nil
}

View File

@ -0,0 +1,15 @@
package migrations
import (
"github.com/pocketbase/dbx"
)
// Renormalizes old single and multiple values of MultiValuer fields (file, select, relation)
// (see https://github.com/pocketbase/pocketbase/issues/2930).
func init() {
AppMigrations.Register(func(db dbx.Builder) error {
return normalizeMultivaluerFields(db)
}, func(db dbx.Builder) error {
return nil
})
}

View File

@ -580,8 +580,8 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
func jsonArrayLength(tableColumnPair string) string { func jsonArrayLength(tableColumnPair string) string {
return fmt.Sprintf( return fmt.Sprintf(
// note: the case is used to normalize value access for single and multiple relations. // note: the case is used to normalize value access for single and multiple relations.
`json_array_length(CASE WHEN json_valid([[%s]]) THEN [[%s]] ELSE json_array([[%s]]) END)`, `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)`,
tableColumnPair, tableColumnPair, tableColumnPair, tableColumnPair, tableColumnPair, tableColumnPair, tableColumnPair, tableColumnPair,
) )
} }

View File

@ -294,14 +294,14 @@ func TestRecordFieldResolverUpdateQuery(t *testing.T) {
"self_rel_one.rel_many_cascade.files:length != 7 &&" + "self_rel_one.rel_many_cascade.files:length != 7 &&" +
"self_rel_one.rel_many_cascade.files:length ?!= 8", "self_rel_one.rel_many_cascade.files:length ?!= 8",
false, false,
"SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `__data_demo4` ON [[__data_demo4.id]]={:TEST} LEFT JOIN `demo3` `__data_demo3` ON [[__data_demo3.id]] IN ({:TEST}, {:TEST}) LEFT JOIN `demo4` `demo4_self_rel_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_self_rel_one.rel_many_cascade]]) THEN [[demo4_self_rel_one.rel_many_cascade]] ELSE json_array([[demo4_self_rel_one.rel_many_cascade]]) END) `demo4_self_rel_one_rel_many_cascade_je` LEFT JOIN `demo3` `demo4_self_rel_one_rel_many_cascade` ON [[demo4_self_rel_one_rel_many_cascade.id]] = [[demo4_self_rel_one_rel_many_cascade_je.value]] WHERE (json_array_length(CASE WHEN json_valid([[__data_demo4.self_rel_many]]) THEN [[__data_demo4.self_rel_many]] ELSE json_array([[__data_demo4.self_rel_many]]) END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[__data_demo4.self_rel_many]]) THEN [[__data_demo4.self_rel_many]] ELSE json_array([[__data_demo4.self_rel_many]]) END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[__data_demo3.files]]) THEN [[__data_demo3.files]] ELSE json_array([[__data_demo3.files]]) END) < {:TEST} AND ((json_array_length(CASE WHEN json_valid([[__data_demo3.files]]) THEN [[__data_demo3.files]] ELSE json_array([[__data_demo3.files]]) END) < {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT json_array_length(CASE WHEN json_valid([[__data_mm_demo3.files]]) THEN [[__data_mm_demo3.files]] ELSE json_array([[__data_mm_demo3.files]]) END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo3` `__data_mm_demo3` ON `__data_mm_demo3`.`id` IN ({:TEST}, {:TEST}) WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] < {:TEST})) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_one.self_rel_many]] ELSE json_array([[demo4_self_rel_one.self_rel_many]]) END) = {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_one.self_rel_many]] ELSE json_array([[demo4_self_rel_one.self_rel_many]]) END) = {:TEST} AND ((json_array_length(CASE WHEN json_valid([[demo4_self_rel_one_rel_many_cascade.files]]) THEN [[demo4_self_rel_one_rel_many_cascade.files]] ELSE json_array([[demo4_self_rel_one_rel_many_cascade.files]]) END) != {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT json_array_length(CASE WHEN json_valid([[__mm_demo4_self_rel_one_rel_many_cascade.files]]) THEN [[__mm_demo4_self_rel_one_rel_many_cascade.files]] ELSE json_array([[__mm_demo4_self_rel_one_rel_many_cascade.files]]) END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo4` `__mm_demo4_self_rel_one` ON [[__mm_demo4_self_rel_one.id]] = [[__mm_demo4.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4_self_rel_one.rel_many_cascade]]) THEN [[__mm_demo4_self_rel_one.rel_many_cascade]] ELSE json_array([[__mm_demo4_self_rel_one.rel_many_cascade]]) END) `__mm_demo4_self_rel_one_rel_many_cascade_je` LEFT JOIN `demo3` `__mm_demo4_self_rel_one_rel_many_cascade` ON [[__mm_demo4_self_rel_one_rel_many_cascade.id]] = [[__mm_demo4_self_rel_one_rel_many_cascade_je.value]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] != {:TEST})) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one_rel_many_cascade.files]]) THEN [[demo4_self_rel_one_rel_many_cascade.files]] ELSE json_array([[demo4_self_rel_one_rel_many_cascade.files]]) END) != {:TEST})", "SELECT DISTINCT `demo4`.* FROM `demo4` LEFT JOIN `demo4` `__data_demo4` ON [[__data_demo4.id]]={:TEST} LEFT JOIN `demo3` `__data_demo3` ON [[__data_demo3.id]] IN ({:TEST}, {:TEST}) LEFT JOIN `demo4` `demo4_self_rel_one` ON [[demo4_self_rel_one.id]] = [[demo4.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[demo4_self_rel_one.rel_many_cascade]]) THEN [[demo4_self_rel_one.rel_many_cascade]] ELSE json_array([[demo4_self_rel_one.rel_many_cascade]]) END) `demo4_self_rel_one_rel_many_cascade_je` LEFT JOIN `demo3` `demo4_self_rel_one_rel_many_cascade` ON [[demo4_self_rel_one_rel_many_cascade.id]] = [[demo4_self_rel_one_rel_many_cascade_je.value]] WHERE (json_array_length(CASE WHEN json_valid([[__data_demo4.self_rel_many]]) THEN [[__data_demo4.self_rel_many]] ELSE (CASE WHEN [[__data_demo4.self_rel_many]] = '' OR [[__data_demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[__data_demo4.self_rel_many]]) END) END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[__data_demo4.self_rel_many]]) THEN [[__data_demo4.self_rel_many]] ELSE (CASE WHEN [[__data_demo4.self_rel_many]] = '' OR [[__data_demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[__data_demo4.self_rel_many]]) END) END) > {:TEST} AND json_array_length(CASE WHEN json_valid([[__data_demo3.files]]) THEN [[__data_demo3.files]] ELSE (CASE WHEN [[__data_demo3.files]] = '' OR [[__data_demo3.files]] IS NULL THEN json_array() ELSE json_array([[__data_demo3.files]]) END) END) < {:TEST} AND ((json_array_length(CASE WHEN json_valid([[__data_demo3.files]]) THEN [[__data_demo3.files]] ELSE (CASE WHEN [[__data_demo3.files]] = '' OR [[__data_demo3.files]] IS NULL THEN json_array() ELSE json_array([[__data_demo3.files]]) END) END) < {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT json_array_length(CASE WHEN json_valid([[__data_mm_demo3.files]]) THEN [[__data_mm_demo3.files]] ELSE (CASE WHEN [[__data_mm_demo3.files]] = '' OR [[__data_mm_demo3.files]] IS NULL THEN json_array() ELSE json_array([[__data_mm_demo3.files]]) END) END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo3` `__data_mm_demo3` ON `__data_mm_demo3`.`id` IN ({:TEST}, {:TEST}) WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] < {:TEST})) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[demo4_self_rel_one.self_rel_many]] = '' OR [[demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one.self_rel_many]]) END) END) = {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one.self_rel_many]]) THEN [[demo4_self_rel_one.self_rel_many]] ELSE (CASE WHEN [[demo4_self_rel_one.self_rel_many]] = '' OR [[demo4_self_rel_one.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one.self_rel_many]]) END) END) = {:TEST} AND ((json_array_length(CASE WHEN json_valid([[demo4_self_rel_one_rel_many_cascade.files]]) THEN [[demo4_self_rel_one_rel_many_cascade.files]] ELSE (CASE WHEN [[demo4_self_rel_one_rel_many_cascade.files]] = '' OR [[demo4_self_rel_one_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one_rel_many_cascade.files]]) END) END) != {:TEST}) AND (NOT EXISTS (SELECT 1 FROM (SELECT json_array_length(CASE WHEN json_valid([[__mm_demo4_self_rel_one_rel_many_cascade.files]]) THEN [[__mm_demo4_self_rel_one_rel_many_cascade.files]] ELSE (CASE WHEN [[__mm_demo4_self_rel_one_rel_many_cascade.files]] = '' OR [[__mm_demo4_self_rel_one_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[__mm_demo4_self_rel_one_rel_many_cascade.files]]) END) END) as [[multiMatchValue]] FROM `demo4` `__mm_demo4` LEFT JOIN `demo4` `__mm_demo4_self_rel_one` ON [[__mm_demo4_self_rel_one.id]] = [[__mm_demo4.self_rel_one]] LEFT JOIN json_each(CASE WHEN json_valid([[__mm_demo4_self_rel_one.rel_many_cascade]]) THEN [[__mm_demo4_self_rel_one.rel_many_cascade]] ELSE json_array([[__mm_demo4_self_rel_one.rel_many_cascade]]) END) `__mm_demo4_self_rel_one_rel_many_cascade_je` LEFT JOIN `demo3` `__mm_demo4_self_rel_one_rel_many_cascade` ON [[__mm_demo4_self_rel_one_rel_many_cascade.id]] = [[__mm_demo4_self_rel_one_rel_many_cascade_je.value]] WHERE `__mm_demo4`.`id` = `demo4`.`id`) {{__smTEST}} WHERE ((NOT ([[__smTEST.multiMatchValue]] != {:TEST})) OR ([[__smTEST.multiMatchValue]] IS NULL))))) AND json_array_length(CASE WHEN json_valid([[demo4_self_rel_one_rel_many_cascade.files]]) THEN [[demo4_self_rel_one_rel_many_cascade.files]] ELSE (CASE WHEN [[demo4_self_rel_one_rel_many_cascade.files]] = '' OR [[demo4_self_rel_one_rel_many_cascade.files]] IS NULL THEN json_array() ELSE json_array([[demo4_self_rel_one_rel_many_cascade.files]]) END) END) != {:TEST})",
}, },
{ {
"json_extract and json_array_length COALESCE equal normalizations", "json_extract and json_array_length COALESCE equal normalizations",
"demo4", "demo4",
"json_object.a.b = '' && self_rel_many:length != 2 && json_object.a.b > 3 && self_rel_many:length <= 4", "json_object.a.b = '' && self_rel_many:length != 2 && json_object.a.b > 3 && self_rel_many:length <= 4",
false, false,
"SELECT `demo4`.* FROM `demo4` WHERE ((JSON_EXTRACT([[demo4.json_object]], '$.a.b') = '' OR JSON_EXTRACT([[demo4.json_object]], '$.a.b') IS NULL) AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) != {:TEST} AND JSON_EXTRACT([[demo4.json_object]], '$.a.b') > {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE json_array([[demo4.self_rel_many]]) END) <= {:TEST})", "SELECT `demo4`.* FROM `demo4` WHERE ((JSON_EXTRACT([[demo4.json_object]], '$.a.b') = '' OR JSON_EXTRACT([[demo4.json_object]], '$.a.b') IS NULL) AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) != {:TEST} AND JSON_EXTRACT([[demo4.json_object]], '$.a.b') > {:TEST} AND json_array_length(CASE WHEN json_valid([[demo4.self_rel_many]]) THEN [[demo4.self_rel_many]] ELSE (CASE WHEN [[demo4.self_rel_many]] = '' OR [[demo4.self_rel_many]] IS NULL THEN json_array() ELSE json_array([[demo4.self_rel_many]]) END) END) <= {:TEST})",
}, },
} }