mirror of https://github.com/VinGarcia/ksql.git
Merge pull request #32 from VinGarcia/add-nullable-modifier
Add nullable modifierpull/34/head v1.6.0
commit
6ff67201c9
|
@ -19,6 +19,7 @@ func init() {
|
||||||
|
|
||||||
// This one is useful for serializing/desserializing structs:
|
// This one is useful for serializing/desserializing structs:
|
||||||
modifiers.Store("json", jsonModifier)
|
modifiers.Store("json", jsonModifier)
|
||||||
|
modifiers.Store("json/nullable", jsonNullableModifier)
|
||||||
|
|
||||||
// This next two are useful for the UpdatedAt and Created fields respectively:
|
// This next two are useful for the UpdatedAt and Created fields respectively:
|
||||||
// They only work on time.Time attributes and will set the attribute to time.Now().
|
// They only work on time.Time attributes and will set the attribute to time.Now().
|
||||||
|
@ -29,6 +30,7 @@ func init() {
|
||||||
// to test the feature of skipping updates, inserts and queries.
|
// to test the feature of skipping updates, inserts and queries.
|
||||||
modifiers.Store("skipUpdates", skipUpdatesModifier)
|
modifiers.Store("skipUpdates", skipUpdatesModifier)
|
||||||
modifiers.Store("skipInserts", skipInsertsModifier)
|
modifiers.Store("skipInserts", skipInsertsModifier)
|
||||||
|
modifiers.Store("nullable", nullableModifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterAttrModifier allow users to add custom modifiers on startup
|
// RegisterAttrModifier allow users to add custom modifiers on startup
|
||||||
|
|
|
@ -39,3 +39,10 @@ var jsonModifier = ksqlmodifiers.AttrModifier{
|
||||||
return b, err
|
return b, err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jsonNullableModifier = ksqlmodifiers.AttrModifier{
|
||||||
|
Nullable: true,
|
||||||
|
|
||||||
|
Scan: jsonModifier.Scan,
|
||||||
|
Value: jsonModifier.Value,
|
||||||
|
}
|
||||||
|
|
|
@ -9,3 +9,7 @@ var skipInsertsModifier = ksqlmodifiers.AttrModifier{
|
||||||
var skipUpdatesModifier = ksqlmodifiers.AttrModifier{
|
var skipUpdatesModifier = ksqlmodifiers.AttrModifier{
|
||||||
SkipOnUpdate: true,
|
SkipOnUpdate: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nullableModifier = ksqlmodifiers.AttrModifier{
|
||||||
|
Nullable: true,
|
||||||
|
}
|
|
@ -150,11 +150,13 @@ func StructToMap(obj interface{}) (map[string]interface{}, error) {
|
||||||
field := v.Field(i)
|
field := v.Field(i)
|
||||||
ft := field.Type()
|
ft := field.Type()
|
||||||
if ft.Kind() == reflect.Ptr {
|
if ft.Kind() == reflect.Ptr {
|
||||||
if field.IsNil() {
|
if !field.IsNil() {
|
||||||
continue
|
field = field.Elem()
|
||||||
|
} else {
|
||||||
|
if !fieldInfo.Modifier.Nullable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field = field.Elem()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m[fieldInfo.ColumnName] = field.Interface()
|
m[fieldInfo.ColumnName] = field.Interface()
|
||||||
|
|
5
ksql.go
5
ksql.go
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/vingarcia/ksql/internal/modifiers"
|
"github.com/vingarcia/ksql/internal/modifiers"
|
||||||
"github.com/vingarcia/ksql/internal/structs"
|
"github.com/vingarcia/ksql/internal/structs"
|
||||||
"github.com/vingarcia/ksql/ksqlmodifiers"
|
"github.com/vingarcia/ksql/ksqlmodifiers"
|
||||||
"github.com/vingarcia/ksql/ksqltest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var selectQueryCache = initializeQueryCache()
|
var selectQueryCache = initializeQueryCache()
|
||||||
|
@ -631,7 +630,7 @@ func normalizeIDsAsMap(idNames []string, idOrMap interface{}) (idMap map[string]
|
||||||
|
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
idMap, err = ksqltest.StructToMap(idOrMap)
|
idMap, err = structs.StructToMap(idOrMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not get ID(s) from input record: %w", err)
|
return nil, fmt.Errorf("could not get ID(s) from input record: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -724,7 +723,7 @@ func buildInsertQuery(
|
||||||
info structs.StructInfo,
|
info structs.StructInfo,
|
||||||
record interface{},
|
record interface{},
|
||||||
) (query string, params []interface{}, scanValues []interface{}, err error) {
|
) (query string, params []interface{}, scanValues []interface{}, err error) {
|
||||||
recordMap, err := ksqltest.StructToMap(record)
|
recordMap, err := structs.StructToMap(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, nil, err
|
return "", nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ type AttrModifier struct {
|
||||||
SkipOnInsert bool
|
SkipOnInsert bool
|
||||||
SkipOnUpdate bool
|
SkipOnUpdate bool
|
||||||
|
|
||||||
|
// Nullable will make sure that on Insert and Patch operations
|
||||||
|
// this field will not be ignored even if it is a NULL pointer.
|
||||||
|
Nullable bool
|
||||||
|
|
||||||
// Implement these functions if you want to override the default Scan/Value behavior
|
// Implement these functions if you want to override the default Scan/Value behavior
|
||||||
// for the target attribute.
|
// for the target attribute.
|
||||||
Scan AttrScanner
|
Scan AttrScanner
|
||||||
|
|
157
test_adapters.go
157
test_adapters.go
|
@ -3070,6 +3070,151 @@ func ModifiersTest(
|
||||||
tt.AssertEqual(t, taggedUser.Name, "Marta Ribeiro")
|
tt.AssertEqual(t, taggedUser.Name, "Marta Ribeiro")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("nullable modifier", func(t *testing.T) {
|
||||||
|
t.Run("should prevent null fields from being ignored during insertions", func(t *testing.T) {
|
||||||
|
db, closer := newDBAdapter(t)
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
c := newTestDB(db, driver)
|
||||||
|
|
||||||
|
// The default value of the column "nullable_field"
|
||||||
|
// is the string: "not_null".
|
||||||
|
//
|
||||||
|
// So the tagged struct below should insert passing NULL
|
||||||
|
// and the untagged should insert not passing any value
|
||||||
|
// for this column, thus, only the second one should create
|
||||||
|
// a recording using the default value.
|
||||||
|
|
||||||
|
var taggedUser struct {
|
||||||
|
ID uint `ksql:"id"`
|
||||||
|
NullableField *string `ksql:"nullable_field,nullable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var untaggedUser struct {
|
||||||
|
ID uint `ksql:"id"`
|
||||||
|
NullableField *string `ksql:"nullable_field"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Insert(ctx, usersTable, &taggedUser)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
tt.AssertNotEqual(t, taggedUser.ID, 0)
|
||||||
|
|
||||||
|
err = c.Insert(ctx, usersTable, &untaggedUser)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
tt.AssertNotEqual(t, untaggedUser.ID, 0)
|
||||||
|
|
||||||
|
err = c.QueryOne(ctx, &taggedUser, `FROM users WHERE id = `+c.dialect.Placeholder(0), taggedUser.ID)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
err = c.QueryOne(ctx, &untaggedUser, `FROM users WHERE id = `+c.dialect.Placeholder(0), untaggedUser.ID)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
tt.AssertEqual(t, taggedUser.NullableField == nil, true)
|
||||||
|
tt.AssertEqual(t, untaggedUser.NullableField, nullable.String("not_null"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should prevent null fields from being ignored during updates", func(t *testing.T) {
|
||||||
|
db, closer := newDBAdapter(t)
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
c := newTestDB(db, driver)
|
||||||
|
|
||||||
|
type userWithNoTags struct {
|
||||||
|
ID uint `ksql:"id"`
|
||||||
|
Name string `ksql:"name"`
|
||||||
|
NullableField *string `ksql:"nullable_field"`
|
||||||
|
}
|
||||||
|
untaggedUser := userWithNoTags{
|
||||||
|
Name: "Laurinha Ribeiro",
|
||||||
|
NullableField: nullable.String("fakeValue"),
|
||||||
|
}
|
||||||
|
err := c.Insert(ctx, usersTable, &untaggedUser)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
tt.AssertNotEqual(t, untaggedUser.ID, 0)
|
||||||
|
|
||||||
|
type taggedUser struct {
|
||||||
|
ID uint `ksql:"id"`
|
||||||
|
Name string `ksql:"name"`
|
||||||
|
NullableField *string `ksql:"nullable_field,nullable"`
|
||||||
|
}
|
||||||
|
u := taggedUser{
|
||||||
|
ID: untaggedUser.ID,
|
||||||
|
Name: "Laura Ribeiro",
|
||||||
|
NullableField: nil,
|
||||||
|
}
|
||||||
|
err = c.Patch(ctx, usersTable, u)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
var untaggedUser2 userWithNoTags
|
||||||
|
err = c.QueryOne(ctx, &untaggedUser2, "FROM users WHERE id = "+c.dialect.Placeholder(0), untaggedUser.ID)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
tt.AssertEqual(t, untaggedUser2.Name, "Laura Ribeiro")
|
||||||
|
tt.AssertEqual(t, untaggedUser2.NullableField == nil, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should not alter the value on queries", func(t *testing.T) {
|
||||||
|
db, closer := newDBAdapter(t)
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
c := newTestDB(db, driver)
|
||||||
|
|
||||||
|
type userWithNoTags struct {
|
||||||
|
ID uint `ksql:"id"`
|
||||||
|
Name *string `ksql:"name"`
|
||||||
|
}
|
||||||
|
untaggedUser := userWithNoTags{
|
||||||
|
Name: nullable.String("Marta Ribeiro"),
|
||||||
|
}
|
||||||
|
err := c.Insert(ctx, usersTable, &untaggedUser)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
tt.AssertNotEqual(t, untaggedUser.ID, 0)
|
||||||
|
|
||||||
|
var taggedUser struct {
|
||||||
|
ID uint `ksql:"id"`
|
||||||
|
Name *string `ksql:"name,nullable"`
|
||||||
|
}
|
||||||
|
err = c.QueryOne(ctx, &taggedUser, "FROM users WHERE id = "+c.dialect.Placeholder(0), untaggedUser.ID)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
tt.AssertEqual(t, taggedUser.ID, untaggedUser.ID)
|
||||||
|
tt.AssertEqual(t, taggedUser.Name, nullable.String("Marta Ribeiro"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should cause no effect if used on a non pointer field", func(t *testing.T) {
|
||||||
|
db, closer := newDBAdapter(t)
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
c := newTestDB(db, driver)
|
||||||
|
|
||||||
|
type user struct {
|
||||||
|
ID uint `ksql:"id"`
|
||||||
|
Name string `ksql:"name,nullable"`
|
||||||
|
Age int `ksql:"age,nullable"`
|
||||||
|
}
|
||||||
|
u1 := user{
|
||||||
|
Name: "Marta Ribeiro",
|
||||||
|
}
|
||||||
|
err := c.Insert(ctx, usersTable, &u1)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
tt.AssertNotEqual(t, u1.ID, 0)
|
||||||
|
|
||||||
|
err = c.Patch(ctx, usersTable, &struct {
|
||||||
|
ID uint `ksql:"id"`
|
||||||
|
Age int `ksql:"age,nullable"`
|
||||||
|
}{
|
||||||
|
ID: u1.ID,
|
||||||
|
Age: 42,
|
||||||
|
})
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
|
||||||
|
var u2 user
|
||||||
|
err = c.QueryOne(ctx, &u2, "FROM users WHERE id = "+c.dialect.Placeholder(0), u1.ID)
|
||||||
|
tt.AssertNoErr(t, err)
|
||||||
|
tt.AssertEqual(t, u2.ID, u1.ID)
|
||||||
|
tt.AssertEqual(t, u2.Name, "Marta Ribeiro")
|
||||||
|
tt.AssertEqual(t, u2.Age, 42)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3317,7 +3462,8 @@ func createTables(driver string, connStr string) error {
|
||||||
name TEXT,
|
name TEXT,
|
||||||
address BLOB,
|
address BLOB,
|
||||||
created_at DATETIME,
|
created_at DATETIME,
|
||||||
updated_at DATETIME
|
updated_at DATETIME,
|
||||||
|
nullable_field TEXT DEFAULT "not_null"
|
||||||
)`)
|
)`)
|
||||||
case "postgres":
|
case "postgres":
|
||||||
_, err = db.Exec(`CREATE TABLE users (
|
_, err = db.Exec(`CREATE TABLE users (
|
||||||
|
@ -3326,7 +3472,8 @@ func createTables(driver string, connStr string) error {
|
||||||
name VARCHAR(50),
|
name VARCHAR(50),
|
||||||
address jsonb,
|
address jsonb,
|
||||||
created_at TIMESTAMP,
|
created_at TIMESTAMP,
|
||||||
updated_at TIMESTAMP
|
updated_at TIMESTAMP,
|
||||||
|
nullable_field VARCHAR(50) DEFAULT 'not_null'
|
||||||
)`)
|
)`)
|
||||||
case "mysql":
|
case "mysql":
|
||||||
_, err = db.Exec(`CREATE TABLE users (
|
_, err = db.Exec(`CREATE TABLE users (
|
||||||
|
@ -3335,7 +3482,8 @@ func createTables(driver string, connStr string) error {
|
||||||
name VARCHAR(50),
|
name VARCHAR(50),
|
||||||
address JSON,
|
address JSON,
|
||||||
created_at DATETIME,
|
created_at DATETIME,
|
||||||
updated_at DATETIME
|
updated_at DATETIME,
|
||||||
|
nullable_field VARCHAR(50) DEFAULT "not_null"
|
||||||
)`)
|
)`)
|
||||||
case "sqlserver":
|
case "sqlserver":
|
||||||
_, err = db.Exec(`CREATE TABLE users (
|
_, err = db.Exec(`CREATE TABLE users (
|
||||||
|
@ -3344,7 +3492,8 @@ func createTables(driver string, connStr string) error {
|
||||||
name VARCHAR(50),
|
name VARCHAR(50),
|
||||||
address NVARCHAR(4000),
|
address NVARCHAR(4000),
|
||||||
created_at DATETIME,
|
created_at DATETIME,
|
||||||
updated_at DATETIME
|
updated_at DATETIME,
|
||||||
|
nullable_field VARCHAR(50) DEFAULT 'not_null'
|
||||||
)`)
|
)`)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue