[#2992] fixed zero-default value not being used if the field is not explicitly set when manually creating records

This commit is contained in:
Gani Georgiev
2023-07-25 20:35:29 +03:00
parent 34fe55686d
commit 1330e2e1e7
11 changed files with 6241 additions and 6054 deletions
+57 -19
View File
@@ -173,6 +173,15 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
}
return dao.RunInTransaction(func(txDao *Dao) error {
// temporary disable the schema error checks to prevent view and trigger errors
// when "altering" (aka. deleting and recreating) the non-normalized columns
if _, err := txDao.DB().NewQuery("PRAGMA writable_schema = ON").Execute(); err != nil {
return err
}
// executed with defer to make sure that the pragma is always reverted
// in case of an error and when nested transactions are used
defer txDao.DB().NewQuery("PRAGMA writable_schema = RESET").Execute()
for _, newField := range newCollection.Schema.Fields() {
// 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
@@ -192,11 +201,26 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
continue // no change
}
var updateQuery *dbx.Query
// update the column definition by:
// 1. inserting a new column with the new definition
// 2. copy normalized values from the original column to the new one
// 3. drop the original column
// 4. rename the new column to the original column
// -------------------------------------------------------
originalName := newField.Name
tempName := "_" + newField.Name + security.PseudorandomString(5)
_, err := txDao.DB().AddColumn(newCollection.Name, tempName, newField.ColDefinition()).Execute()
if err != nil {
return err
}
var copyQuery *dbx.Query
if !isOldMultiple && isNewMultiple {
// single -> multiple (convert to array)
updateQuery = txDao.DB().NewQuery(fmt.Sprintf(
copyQuery = txDao.DB().NewQuery(fmt.Sprintf(
`UPDATE {{%s}} set [[%s]] = (
CASE
WHEN COALESCE([[%s]], '') = ''
@@ -211,19 +235,19 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
END
)`,
newCollection.Name,
newField.Name,
newField.Name,
newField.Name,
newField.Name,
newField.Name,
newField.Name,
tempName,
originalName,
originalName,
originalName,
originalName,
originalName,
))
} else {
// multiple -> single (keep only the last element)
//
// note: for file fields the actual files are not deleted
// allowing additional custom handling via migration.
updateQuery = txDao.DB().NewQuery(fmt.Sprintf(
// note: for file fields the actual file objects are not
// deleted allowing additional custom handling via migration
copyQuery = txDao.DB().NewQuery(fmt.Sprintf(
`UPDATE {{%s}} set [[%s]] = (
CASE
WHEN COALESCE([[%s]], '[]') = '[]'
@@ -238,21 +262,35 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
END
)`,
newCollection.Name,
newField.Name,
newField.Name,
newField.Name,
newField.Name,
newField.Name,
newField.Name,
tempName,
originalName,
originalName,
originalName,
originalName,
originalName,
))
}
if _, err := updateQuery.Execute(); err != nil {
// copy the normalized values
if _, err := copyQuery.Execute(); err != nil {
return err
}
// drop the original column
if _, err := txDao.DB().DropColumn(newCollection.Name, originalName).Execute(); err != nil {
return err
}
// rename the new column back to the original
if _, err := txDao.DB().RenameColumn(newCollection.Name, tempName, originalName).Execute(); err != nil {
return err
}
}
return nil
// revert the pragma and reload the schema
_, revertErr := txDao.DB().NewQuery("PRAGMA writable_schema = RESET").Execute()
return revertErr
})
}
+77 -34
View File
@@ -188,7 +188,51 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
t.Fatal(err)
}
type expectation struct {
// ensures that the writable schema was reverted to its expected default
var writableSchema bool
app.Dao().DB().NewQuery("PRAGMA writable_schema").Row(&writableSchema)
if writableSchema == true {
t.Fatalf("Expected writable_schema to be OFF, got %v", writableSchema)
}
// check whether the columns DEFAULT definition was updated
// ---------------------------------------------------------------
tableInfo, err := app.Dao().TableInfo(collection.Name)
if err != nil {
t.Fatal(err)
}
tableInfoExpectations := map[string]string{
"select_one": `'[]'`,
"select_many": `''`,
"file_one": `'[]'`,
"file_many": `''`,
"rel_one": `'[]'`,
"rel_many": `''`,
"new_multiple": `'[]'`,
}
for col, dflt := range tableInfoExpectations {
t.Run("check default for "+col, func(t *testing.T) {
var row *models.TableInfoRow
for _, r := range tableInfo {
if r.Name == col {
row = r
break
}
}
if row == nil {
t.Fatalf("Missing info for column %q", col)
}
if v := row.DefaultValue.String(); v != dflt {
t.Fatalf("Expected default value %q, got %q", dflt, v)
}
})
}
// check whether the values were normalized
// ---------------------------------------------------------------
type fieldsExpectation struct {
SelectOne string `db:"select_one"`
SelectMany string `db:"select_many"`
FileOne string `db:"file_one"`
@@ -198,13 +242,13 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
NewMultiple string `db:"new_multiple"`
}
scenarios := []struct {
fieldsScenarios := []struct {
recordId string
expected expectation
expected fieldsExpectation
}{
{
"imy661ixudk5izi",
expectation{
fieldsExpectation{
SelectOne: `[]`,
SelectMany: ``,
FileOne: `[]`,
@@ -216,7 +260,7 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
},
{
"al1h9ijdeojtsjy",
expectation{
fieldsExpectation{
SelectOne: `["optionB"]`,
SelectMany: `optionB`,
FileOne: `["300_Jsjq7RdBgA.png"]`,
@@ -228,7 +272,7 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
},
{
"84nmscqy84lsi1t",
expectation{
fieldsExpectation{
SelectOne: `["optionB"]`,
SelectMany: `optionC`,
FileOne: `["test_d61b33QdDU.txt"]`,
@@ -240,37 +284,36 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
},
}
for _, s := range scenarios {
result := new(expectation)
for _, s := range fieldsScenarios {
t.Run("check fields for record "+s.recordId, func(t *testing.T) {
result := new(fieldsExpectation)
err := app.Dao().DB().Select(
"select_one",
"select_many",
"file_one",
"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)
continue
}
err := app.Dao().DB().Select(
"select_one",
"select_many",
"file_one",
"file_many",
"rel_one",
"rel_many",
"new_multiple",
).From(collection.Name).Where(dbx.HashExp{"id": s.recordId}).One(result)
if err != nil {
t.Fatalf("Failed to load record: %v", err)
}
encodedResult, err := json.Marshal(result)
if err != nil {
t.Errorf("[%s] Failed to encode result: %v", s.recordId, err)
continue
}
encodedResult, err := json.Marshal(result)
if err != nil {
t.Fatalf("Failed to encode result: %v", err)
}
encodedExpectation, err := json.Marshal(s.expected)
if err != nil {
t.Errorf("[%s] Failed to encode expectation: %v", s.recordId, err)
continue
}
encodedExpectation, err := json.Marshal(s.expected)
if err != nil {
t.Fatalf("Failed to encode expectation: %v", err)
}
if !bytes.EqualFold(encodedExpectation, encodedResult) {
t.Errorf("[%s] Expected \n%s, \ngot \n%s", s.recordId, encodedExpectation, encodedResult)
}
if !bytes.EqualFold(encodedExpectation, encodedResult) {
t.Fatalf("Expected \n%s, \ngot \n%s", encodedExpectation, encodedResult)
}
})
}
}