diff --git a/contrib/drivers/pgsql/pgsql_do_insert.go b/contrib/drivers/pgsql/pgsql_do_insert.go index 0e0ac31de9a..163076ed11b 100644 --- a/contrib/drivers/pgsql/pgsql_do_insert.go +++ b/contrib/drivers/pgsql/pgsql_do_insert.go @@ -9,10 +9,11 @@ package pgsql import ( "context" "database/sql" - + "fmt" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" + "strings" ) // DoInsert inserts or updates data for given table. @@ -25,10 +26,7 @@ func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list ) case gdb.InsertOptionIgnore: - return nil, gerror.NewCode( - gcode.CodeNotSupported, - `Insert ignore operation is not supported by pgsql driver`, - ) + return d.doInsertIgnore(ctx, link, table, list) case gdb.InsertOptionDefault: tableFields, err := d.GetCore().GetDB().TableFields(ctx, table) @@ -44,3 +42,39 @@ func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list } return d.Core.DoInsert(ctx, link, table, list, option) } + +// doInsertIgnore inserts a list of records into the specified table in pgsql. +// INSERT INTO () VALUES () ON CONFLICT DO NOTHING +func (d *Driver) doInsertIgnore(ctx context.Context, link gdb.Link, table string, list gdb.List) (result sql.Result, err error) { + if len(list) == 0 { + return nil, gerror.NewCode(gcode.CodeInvalidRequest, `Insert operation list is empty for pgsql driver`) + } + + var ( + one = list[0] + charL, charR = d.GetChars() + insertKeys = make([]string, 0, len(one)) + insertValues = make([]string, 0, len(one)) + queryValues = make([]interface{}, 0, len(one)) + ) + + for key, value := range one { + insertKeys = append(insertKeys, charL+key+charR) + insertValues = append(insertValues, "?") + queryValues = append(queryValues, value) + } + + sqlStr := fmt.Sprintf( + `INSERT INTO %s (%s) VALUES (%s) ON CONFLICT DO NOTHING`, + table, + strings.Join(insertKeys, ","), + strings.Join(insertValues, ","), + ) + + result, err = d.DoExec(ctx, link, sqlStr, queryValues...) + if err != nil { + return result, err + } + + return result, nil +} diff --git a/contrib/drivers/pgsql/pgsql_z_unit_db_test.go b/contrib/drivers/pgsql/pgsql_z_unit_db_test.go index 9cadd86d142..8216df44e07 100644 --- a/contrib/drivers/pgsql/pgsql_z_unit_db_test.go +++ b/contrib/drivers/pgsql/pgsql_z_unit_db_test.go @@ -383,3 +383,66 @@ int_col INT);` }) } + +func Test_DB_InsertIgnore(t *testing.T) { + table := createTable() + defer dropTable(table) + + // Insert test record + gtest.C(t, func(t *gtest.T) { + _, err := db.Insert(ctx, table, g.Map{ + "id": 1, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "T1", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + answer, err := db.GetAll(ctx, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1) + t.AssertNil(err) + t.Assert(len(answer), 1) + t.Assert(answer[0]["passport"], "t1") + t.Assert(answer[0]["password"], "25d55ad283aa400af464c76d713c07ad") + t.Assert(answer[0]["nickname"], "T1") + + // Ignore Duplicate record + result, err := db.InsertIgnore(ctx, table, g.Map{ + "id": 1, + "passport": "t1_duplicate", + "password": "duplicate_password", + "nickname": "Duplicate", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + n, _ := result.RowsAffected() + t.Assert(n, 0) + + answer, err = db.GetAll(ctx, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1) + t.AssertNil(err) + t.Assert(len(answer), 1) + t.Assert(answer[0]["passport"], "t1") + t.Assert(answer[0]["password"], "25d55ad283aa400af464c76d713c07ad") + t.Assert(answer[0]["nickname"], "T1") + + // Insert Correct Record + result, err = db.Insert(ctx, table, g.Map{ + "id": 2, + "passport": "t2", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "name_2", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + n, _ = result.RowsAffected() + t.Assert(n, 1) + + answer, err = db.GetAll(ctx, fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 2) + t.AssertNil(err) + t.Assert(len(answer), 1) + t.Assert(answer[0]["passport"], "t2") + t.Assert(answer[0]["password"], "25d55ad283aa400af464c76d713c07ad") + t.Assert(answer[0]["nickname"], "name_2") + }) +} diff --git a/util/gmeta/gmeta.go b/util/gmeta/gmeta.go index 936a22d4f20..22a616ccb83 100644 --- a/util/gmeta/gmeta.go +++ b/util/gmeta/gmeta.go @@ -8,6 +8,8 @@ package gmeta import ( + "sync" + "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/os/gstructs" ) @@ -20,18 +22,33 @@ const ( metaTypeName = "gmeta.Meta" // metaTypeName is for type string comparison. ) +// cachedMetadata stores the parsed metadata for struct types. +var cachedMetadata = sync.Map{} + // Data retrieves and returns all metadata from `object`. func Data(object interface{}) map[string]string { reflectType, err := gstructs.StructType(object) if err != nil { return nil } + + if cachedData, ok := cachedMetadata.Load(reflectType.String()); ok { + return cachedData.(map[string]string) + } + + var metadata map[string]string if field, ok := reflectType.FieldByName(metaAttributeName); ok { if field.Type.String() == metaTypeName { - return gstructs.ParseTag(string(field.Tag)) + metadata = gstructs.ParseTag(string(field.Tag)) } } - return map[string]string{} + + if metadata == nil { + metadata = map[string]string{} + } + + cachedMetadata.Store(reflectType.String(), metadata) + return metadata } // Get retrieves and returns specified metadata by `key` from `object`.