synced with master
This commit is contained in:
		
						commit
						7d4017225c
					
				|  | @ -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. | ||||
| 
 | ||||
| 
 | ||||
| ## 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 | ||||
| 
 | ||||
| - 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)). | ||||
|  |  | |||
|  | @ -174,9 +174,13 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti | |||
| 
 | ||||
| 	return dao.RunInTransaction(func(txDao *Dao) error { | ||||
| 		for _, newField := range newCollection.Schema.Fields() { | ||||
| 			oldField := oldCollection.Schema.GetFieldById(newField.Id) | ||||
| 			if oldField == nil { | ||||
| 				continue | ||||
| 			// allow to continue even if there is no old field for the cases
 | ||||
| 			// when a new field is added and there are already inserted data
 | ||||
| 			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 | ||||
|  | @ -184,11 +188,6 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti | |||
| 				isNewMultiple = opt.IsMultiple() | ||||
| 			} | ||||
| 
 | ||||
| 			var isOldMultiple bool | ||||
| 			if opt, ok := oldField.Options.(schema.MultiValuer); ok { | ||||
| 				isOldMultiple = opt.IsMultiple() | ||||
| 			} | ||||
| 
 | ||||
| 			if isOldMultiple == isNewMultiple { | ||||
| 				continue // no change
 | ||||
| 			} | ||||
|  |  | |||
|  | @ -171,18 +171,31 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) { | |||
| 		opt := relManyField.Options.(*schema.RelationOptions) | ||||
| 		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 { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	type expectation struct { | ||||
| 		SelectOne  string `db:"select_one"` | ||||
| 		SelectMany string `db:"select_many"` | ||||
| 		FileOne    string `db:"file_one"` | ||||
| 		FileMany   string `db:"file_many"` | ||||
| 		RelOne     string `db:"rel_one"` | ||||
| 		RelMany    string `db:"rel_many"` | ||||
| 		SelectOne   string `db:"select_one"` | ||||
| 		SelectMany  string `db:"select_many"` | ||||
| 		FileOne     string `db:"file_one"` | ||||
| 		FileMany    string `db:"file_many"` | ||||
| 		RelOne      string `db:"rel_one"` | ||||
| 		RelMany     string `db:"rel_many"` | ||||
| 		NewMultiple string `db:"new_multiple"` | ||||
| 	} | ||||
| 
 | ||||
| 	scenarios := []struct { | ||||
|  | @ -192,34 +205,37 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) { | |||
| 		{ | ||||
| 			"imy661ixudk5izi", | ||||
| 			expectation{ | ||||
| 				SelectOne:  `[]`, | ||||
| 				SelectMany: ``, | ||||
| 				FileOne:    `[]`, | ||||
| 				FileMany:   ``, | ||||
| 				RelOne:     `[]`, | ||||
| 				RelMany:    ``, | ||||
| 				SelectOne:   `[]`, | ||||
| 				SelectMany:  ``, | ||||
| 				FileOne:     `[]`, | ||||
| 				FileMany:    ``, | ||||
| 				RelOne:      `[]`, | ||||
| 				RelMany:     ``, | ||||
| 				NewMultiple: `[]`, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"al1h9ijdeojtsjy", | ||||
| 			expectation{ | ||||
| 				SelectOne:  `["optionB"]`, | ||||
| 				SelectMany: `optionB`, | ||||
| 				FileOne:    `["300_Jsjq7RdBgA.png"]`, | ||||
| 				FileMany:   ``, | ||||
| 				RelOne:     `["84nmscqy84lsi1t"]`, | ||||
| 				RelMany:    `oap640cot4yru2s`, | ||||
| 				SelectOne:   `["optionB"]`, | ||||
| 				SelectMany:  `optionB`, | ||||
| 				FileOne:     `["300_Jsjq7RdBgA.png"]`, | ||||
| 				FileMany:    ``, | ||||
| 				RelOne:      `["84nmscqy84lsi1t"]`, | ||||
| 				RelMany:     `oap640cot4yru2s`, | ||||
| 				NewMultiple: `[]`, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"84nmscqy84lsi1t", | ||||
| 			expectation{ | ||||
| 				SelectOne:  `["optionB"]`, | ||||
| 				SelectMany: `optionC`, | ||||
| 				FileOne:    `["test_d61b33QdDU.txt"]`, | ||||
| 				FileMany:   `test_tC1Yc87DfC.txt`, | ||||
| 				RelOne:     `[]`, | ||||
| 				RelMany:    `oap640cot4yru2s`, | ||||
| 				SelectOne:   `["optionB"]`, | ||||
| 				SelectMany:  `optionC`, | ||||
| 				FileOne:     `["test_d61b33QdDU.txt"]`, | ||||
| 				FileMany:    `test_tC1Yc87DfC.txt`, | ||||
| 				RelOne:      `[]`, | ||||
| 				RelMany:     `oap640cot4yru2s`, | ||||
| 				NewMultiple: `[]`, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | @ -234,6 +250,7 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) { | |||
| 			"file_many", | ||||
| 			"rel_one", | ||||
| 			"rel_many", | ||||
| 			"new_multiple", | ||||
| 		).From(collection.Name).Where(dbx.HashExp{"id": s.recordId}).One(result) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("[%s] Failed to load record: %v", s.recordId, err) | ||||
|  |  | |||
|  | @ -12,93 +12,97 @@ import ( | |||
| // Normalizes old single and multiple values of MultiValuer fields (file, select, relation).
 | ||||
| func init() { | ||||
| 	AppMigrations.Register(func(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 | ||||
| 		return normalizeMultivaluerFields(db) | ||||
| 	}, func(db dbx.Builder) error { | ||||
| 		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 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
| 	}) | ||||
| } | ||||
|  | @ -580,8 +580,8 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) { | |||
| func jsonArrayLength(tableColumnPair string) string { | ||||
| 	return fmt.Sprintf( | ||||
| 		// 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)`, | ||||
| 		tableColumnPair, tableColumnPair, tableColumnPair, | ||||
| 		`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, | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 ?!= 8", | ||||
| 			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", | ||||
| 			"demo4", | ||||
| 			"json_object.a.b = '' && self_rel_many:length != 2 && json_object.a.b > 3 && self_rel_many:length <= 4", | ||||
| 			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})", | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue