diff --git a/autostore.go b/autostore.go new file mode 100644 index 0000000..48c3f4f --- /dev/null +++ b/autostore.go @@ -0,0 +1,325 @@ +package intimate + +import ( + "database/sql" + "encoding/binary" + "fmt" + "log" + "reflect" + "strconv" + "time" +) + +type Store struct { + db *sql.DB +} + +type Table struct { + store *Store + name string + setting interface{} + + updatesql string + selectsql string + insertsql string +} + +// const updatesql = "UPDATE %s SET %s WHERE %s = ?" + +func NewStore(uri string) *Store { + db, err := sql.Open("mysql", uri) + if err != nil { + panic(err) + } + s := &Store{db: db} + return s +} + +func (store *Store) Table(name string) *Table { + table := &Table{store: store} + table.name = name + + table.insertsql = `INSERT INTO ` + table.name + `(%s) values(%s)` + table.updatesql = `UPDATE ` + table.name + ` SET %s WHERE %s = ?` + // table.selectsql = `FROM ` + table.name + `WHERE operator` + return table +} + +type Queue struct { + table *Table + obj reflect.Type + selected string + condition string + uidname string + uididx int +} + +// OperatorType 字典Operator 标志位的类型 +type OperatorType string + +const ( + // OpOK 正常 + OpOK OperatorType = "0" + // OpWAIT 等待处理 + OpWAIT OperatorType = "1000" + // OpERROR 错误处理 + OpERROR OperatorType = "10000" +) + +func (t *Table) Queue(obj interface{}, whereCondition string) *Queue { + q := &Queue{} + q.condition = whereCondition + q.obj = reflect.TypeOf(obj) + q.table = t + + for i := 0; i < q.obj.NumField(); i++ { + field := q.obj.Field(i) + if fname, ok := field.Tag.Lookup("field"); ok { + q.selected += fname + "," + if _, ok := field.Tag.Lookup("uid"); ok { + q.uididx = i + q.uidname = fname + } + } + + } + + q.selected = q.selected[:len(q.selected)-1] + return q +} + +func (t *Table) Insert(obj interface{}) error { + ov := reflect.ValueOf(obj).Elem() + ot := reflect.TypeOf(obj) + + fieldsql := "" + argssql := "" + + var args []interface{} + for i := 0; i < ov.NumField(); i++ { + field := ov.Field(i) + ftype := ot.Elem().Field(i) + + if fname, ok := ftype.Tag.Lookup("field"); ok { + if flag, ok := ftype.Tag.Lookup("uid"); ok { + if flag == "auto" { + continue + } + } + + k := ftype.Type.Kind() + if k == reflect.Ptr || k == reflect.Interface { + if !field.IsNil() { + felem := field.Elem() + args = append(args, felem.Interface()) + fieldsql += fname + "," + argssql += "?," + } + } else { + args = append(args, field.Interface()) + fieldsql += fname + "," + argssql += "?," + } + + } + + } + + ssql := fmt.Sprintf(t.insertsql, fieldsql[:len(fieldsql)-1], argssql[:len(argssql)-1]) + _, err := t.store.db.Exec(ssql, args...) + return err +} + +func (t *Table) Update(obj interface{}) error { + + ov := reflect.ValueOf(obj).Elem() + ot := reflect.TypeOf(obj) + + fieldsql := "" + var uidname string + var uidvalue interface{} + + var args []interface{} + for i := 0; i < ov.NumField(); i++ { + field := ov.Field(i) + ftype := ot.Elem().Field(i) + + if fname, ok := ftype.Tag.Lookup("field"); ok { + if _, ok := ftype.Tag.Lookup("uid"); ok { + if uidvalue != nil { + panic(fmt.Errorf("uid must unique, %s and %s", uidname, fname)) + } + uidname = fname + uidvalue = field.Interface() + continue + } + + k := ftype.Type.Kind() + if k == reflect.Ptr || k == reflect.Interface { + if !field.IsNil() { + felem := field.Elem() + args = append(args, felem.Interface()) + fieldsql += fname + " = ?," + } + } else { + args = append(args, field.Interface()) + fieldsql += fname + " = ?," + } + + } + + } + + if uidvalue == nil { + panic(fmt.Errorf("update must contain `uid` tag")) + } + + usql := fmt.Sprintf(t.updatesql, fieldsql[:len(fieldsql)-1], uidname) + args = append(args, uidvalue) + _, err := t.store.db.Exec(usql, args...) + return err +} + +func assign(field reflect.Value, src interface{}) (bool, error) { + switch field.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + s := asString(src) + i64, err := strconv.ParseInt(s, 10, field.Type().Bits()) + if err != nil { + err = strconvErr(err) + return false, fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, field.Kind(), err) + } + field.SetInt(i64) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + + s := asString(src) + u64, err := strconv.ParseUint(s, 10, field.Type().Bits()) + if err != nil { + err = strconvErr(err) + return false, fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, field.Kind(), err) + } + field.SetUint(u64) + case reflect.Float32, reflect.Float64: + s := asString(src) + f64, err := strconv.ParseFloat(s, field.Type().Bits()) + if err != nil { + err = strconvErr(err) + return false, fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, field.Kind(), err) + } + field.SetFloat(f64) + case reflect.String: + field.SetString(string(src.([]byte))) + case reflect.Interface: + return true, nil + } + + return false, nil +} + +func convert(src interface{}, field reflect.Value, columntype *sql.ColumnType) error { + + log.Println("type:", field.Type(), ",kind:", field.Kind(), ",field:", field, "scanType:", columntype.ScanType(), "databaseType:", columntype.DatabaseTypeName()) + if field.Kind() == reflect.Ptr { + fn := field.Type().Elem() // New 一个 field.Type().Elem() . 然后判断 columntype 转化 成 NullString Time + field.Set(reflect.New(fn)) + field = field.Elem() + log.Println("type:", fn.Name(), ",kind:", field.Kind(), ",fieldtype:", field.Type()) + } + + // log.Println(field.Kind(), field, reflect.TypeOf(field).Elem().Name(), columntype.ScanType().Kind()) + + if src == nil { + return fmt.Errorf("converting NULL to %s is unsupported", field.Kind()) + } + + switch columntype.DatabaseTypeName() { + + case "TINYINT", "SMALLINT", "MEDIUMINT", "INT", "BIGINT": + isdefault, err := assign(field, src) + if err != nil { + return err + } + if isdefault { + s := asString(src) + i64, err := strconv.ParseInt(s, 10, 64) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, field.Kind(), err) + } + // reflect.New(reflect.TypeOf(i64)) + field.Set(reflect.ValueOf(i64)) + } + + case "FLOAT", "DOUBLE", "DECIMAL": + isdefault, err := assign(field, src) + if err != nil { + return err + } + if isdefault { + s := asString(src) + f64, err := strconv.ParseFloat(s, 64) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, field.Kind(), err) + } + field.Set(reflect.ValueOf(f64)) + } + + case "BINARY", "VARBINARY", "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB", "JSON": + isdefault, err := assign(field, src) + if err != nil { + return err + } + if isdefault { + field.Set(reflect.ValueOf(src.([]byte))) + } + case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT": + isdefault, err := assign(field, src) + if err != nil { + return err + } + if isdefault { + field.Set(reflect.ValueOf(string(src.([]byte)))) + } + + case "BIT": + var bits []byte = make([]byte, 8) + copy(bits, src.([]byte)) + + switch field.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + field.SetInt(int64(binary.LittleEndian.Uint64(bits))) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + field.SetUint(binary.LittleEndian.Uint64(bits)) + + case reflect.Interface: + field.Set(reflect.ValueOf(binary.LittleEndian.Uint64(bits))) + + } + + case "YEAR", "TIME", "DATE", "DATETIME", "TIMESTAMP": + s := src.(time.Time) + switch field.Interface().(type) { + case time.Time: + field.Set(reflect.ValueOf(src)) + case string: + field.SetString(s.Format(time.RFC3339Nano)) + case []byte: + field.SetBytes([]byte(s.Format(time.RFC3339Nano))) + default: + } + } + + // log.Println(fv, columntype.ScanType().Kind()) + + if iscan, ok := field.Addr().Interface().(sql.Scanner); ok { + err := iscan.Scan(src) + if err != nil { + return err + } + return nil + } + + return nil +} diff --git a/autostore_test.go b/autostore_test.go index 7e4a808..f670675 100644 --- a/autostore_test.go +++ b/autostore_test.go @@ -2,69 +2,16 @@ package intimate import ( "database/sql" - "encoding/binary" "encoding/json" "fmt" "log" "reflect" - "strconv" "testing" + "time" "github.com/davecgh/go-spew/spew" ) -type Store struct { - db *sql.DB -} - -type Table struct { - store *Store - name string - setting interface{} - - updatesql string - selectsql string - insertsql string -} - -// const updatesql = "UPDATE %s SET %s WHERE %s = ?" - -func NewStore(uri string) *Store { - db, err := sql.Open("mysql", uri) - if err != nil { - panic(err) - } - s := &Store{db: db} - return s -} - -func (store *Store) Table(name string) *Table { - table := &Table{store: store} - table.name = name - - table.insertsql = `INSERT INTO ` + table.name + `(%s) values(%s)` - table.updatesql = `UPDATE ` + table.name + ` SET %s WHERE %s = ?` - // table.selectsql = `FROM ` + table.name + `WHERE operator` - return table -} - -type Queue struct { - table *Table - obj reflect.Type - selected string - condition string - uidname string - uididx int -} - -type OperatorType string - -const ( - OP_OK OperatorType = "0" - OP_WAIT OperatorType = "1000" - OP_ERROR OperatorType = "10000" -) - func (queue *Queue) Pop() (result interface{}, err error) { db := queue.table.store.db @@ -113,7 +60,7 @@ func (queue *Queue) Pop() (result interface{}, err error) { return nil, err } - _, err = tx.Exec("UPDATE "+queue.table.name+" SET operator = "+string(OP_WAIT)+" WHERE "+queue.uidname+" = ?", fields[queue.uididx]) + _, err = tx.Exec("UPDATE "+queue.table.name+" SET operator = "+string(OpWAIT)+" WHERE "+queue.uidname+" = ?", fields[queue.uididx]) if err != nil { log.Println(err) return nil, err @@ -147,7 +94,7 @@ func TestAutoStore(t *testing.T) { streamer.Name = "streamer" streamer.Operator = 0 streamer.Bit = 0b11 - streamer.Ext = &sql.NullString{String: "ext", Valid: true} + // streamer.Ext = &sql.NullString{String: "ext", Valid: true} tag := make(map[string]interface{}) tag["json"] = true @@ -157,305 +104,22 @@ func TestAutoStore(t *testing.T) { t.Error(err) } streamer.Iface = btag + now := time.Now() + streamer.UpdateTime = &now err = store.Table("streamer").Insert(streamer) if err != nil { t.Error(err) } } -func assign(field reflect.Value, dest interface{}) (bool, error) { - switch field.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - - s := asString(dest) - i64, err := strconv.ParseInt(s, 10, field.Type().Bits()) - if err != nil { - err = strconvErr(err) - return false, fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", dest, s, field.Kind(), err) - } - field.SetInt(i64) - return false, nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - - s := asString(dest) - u64, err := strconv.ParseUint(s, 10, field.Type().Bits()) - if err != nil { - err = strconvErr(err) - return false, fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", dest, s, field.Kind(), err) - } - field.SetUint(u64) - return false, nil - case reflect.Float32, reflect.Float64: - s := asString(dest) - f64, err := strconv.ParseFloat(s, field.Type().Bits()) - if err != nil { - err = strconvErr(err) - return false, fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", dest, s, field.Kind(), err) - } - field.SetFloat(f64) - return false, nil - case reflect.String: - field.SetString(string(dest.([]byte))) - return false, nil - case reflect.Interface: - return true, nil - } - - return false, fmt.Errorf("") -} -func convert(dest interface{}, field reflect.Value, columntype *sql.ColumnType) error { - - log.Println("type:", field.Type(), ",kind:", field.Kind(), ",field:", field, "scanType:", columntype.ScanType(), "databaseType:", columntype.DatabaseTypeName()) - if field.Kind() == reflect.Ptr { - fn := field.Type().Elem() // New 一个 field.Type().Elem() . 然后判断 columntype 转化 成 NullString Time - field.Set(reflect.New(fn)) - field = field.Elem() - log.Println("type:", fn.Name(), ",kind:", field.Kind(), ",fieldtype:", field.Type()) - } - - // log.Println(field.Kind(), field, reflect.TypeOf(field).Elem().Name(), columntype.ScanType().Kind()) - - if dest == nil { - return fmt.Errorf("converting NULL to %s is unsupported", field.Kind()) - } - - switch columntype.DatabaseTypeName() { - case "TINYINT": - fallthrough - case "SMALLINT": - fallthrough - case "MEDIUMINT": - fallthrough - case "INT": - fallthrough - case "BIGINT": - - isdefault, err := assign(field, dest) - if err != nil { - return err - } - if isdefault { - s := asString(dest) - i64, err := strconv.ParseInt(s, 10, 64) - if err != nil { - err = strconvErr(err) - return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", dest, s, field.Kind(), err) - } - // reflect.New(reflect.TypeOf(i64)) - field.Set(reflect.ValueOf(i64)) - } - return nil - - case "FLOAT": - fallthrough - case "DOUBLE": - fallthrough - case "DECIMAL": - isdefault, err := assign(field, dest) - if err != nil { - return err - } - if isdefault { - s := asString(dest) - f64, err := strconv.ParseFloat(s, 64) - if err != nil { - err = strconvErr(err) - return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", dest, s, field.Kind(), err) - } - field.Set(reflect.ValueOf(f64)) - } - return nil - - case "BINARY": - fallthrough - case "VARBINARY": - fallthrough - case "TINYBLOB": - fallthrough - case "BLOB": - fallthrough - case "MEDIUMBLOB": - fallthrough - case "LONGBLOB": - fallthrough - case "JSON": - isdefault, err := assign(field, dest) - if err != nil { - return err - } - if isdefault { - field.Set(reflect.ValueOf(dest.([]byte))) - } - case "CHAR": - fallthrough - case "VARCHAR": - fallthrough - case "TINYTEXT": - fallthrough - case "TEXT": - fallthrough - case "MEDIUMTEXT": - fallthrough - case "LONGTEXT": - isdefault, err := assign(field, dest) - if err != nil { - return err - } - if isdefault { - field.Set(reflect.ValueOf(string(dest.([]byte)))) - } - return nil - case "BIT": - var bits []byte = make([]byte, 8) - copy(bits, dest.([]byte)) - switch field.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - field.SetInt(int64(binary.LittleEndian.Uint64(bits))) - return nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - field.SetUint(binary.LittleEndian.Uint64(bits)) - return nil - case reflect.Interface: - field.Set(reflect.ValueOf(binary.LittleEndian.Uint64(bits))) - return nil - } - - return nil - } - - // log.Println(fv, columntype.ScanType().Kind()) - - if iscan, ok := field.Addr().Interface().(sql.Scanner); ok { - err := iscan.Scan(dest) - if err != nil { - return err - } - return nil - } - - return nil -} - -func (t *Table) Queue(obj interface{}, whereCondition string) *Queue { - q := &Queue{} - q.condition = whereCondition - q.obj = reflect.TypeOf(obj) - q.table = t - - for i := 0; i < q.obj.NumField(); i++ { - field := q.obj.Field(i) - if fname, ok := field.Tag.Lookup("field"); ok { - q.selected += fname + "," - if _, ok := field.Tag.Lookup("uid"); ok { - q.uididx = i - q.uidname = fname - } - } - - } - - q.selected = q.selected[:len(q.selected)-1] - return q -} - -func (t *Table) Insert(obj interface{}) error { - ov := reflect.ValueOf(obj).Elem() - ot := reflect.TypeOf(obj) - - fieldsql := "" - argssql := "" - - var args []interface{} - for i := 0; i < ov.NumField(); i++ { - field := ov.Field(i) - ftype := ot.Elem().Field(i) - - if fname, ok := ftype.Tag.Lookup("field"); ok { - if flag, ok := ftype.Tag.Lookup("uid"); ok { - if flag == "auto" { - continue - } - } - - k := ftype.Type.Kind() - if k == reflect.Ptr || k == reflect.Interface { - if !field.IsNil() { - felem := field.Elem() - args = append(args, felem.Interface()) - fieldsql += fname + "," - argssql += "?," - } - } else { - args = append(args, field.Interface()) - fieldsql += fname + "," - argssql += "?," - } - - } - - } - - ssql := fmt.Sprintf(t.insertsql, fieldsql[:len(fieldsql)-1], argssql[:len(argssql)-1]) - _, err := t.store.db.Exec(ssql, args...) - return err -} - -func (t *Table) Update(obj interface{}) error { - - ov := reflect.ValueOf(obj).Elem() - ot := reflect.TypeOf(obj) - - fieldsql := "" - var uidname string - var uidvalue interface{} - - var args []interface{} - for i := 0; i < ov.NumField(); i++ { - field := ov.Field(i) - ftype := ot.Elem().Field(i) - - if fname, ok := ftype.Tag.Lookup("field"); ok { - if _, ok := ftype.Tag.Lookup("uid"); ok { - if uidvalue != nil { - panic(fmt.Errorf("uid must unique, %s and %s", uidname, fname)) - } - uidname = fname - uidvalue = field.Interface() - continue - } - - k := ftype.Type.Kind() - if k == reflect.Ptr || k == reflect.Interface { - if !field.IsNil() { - felem := field.Elem() - args = append(args, felem.Interface()) - fieldsql += fname + " = ?," - } - } else { - args = append(args, field.Interface()) - fieldsql += fname + " = ?," - } - - } - - } - - if uidvalue == nil { - panic(fmt.Errorf("update must contain `uid` tag")) - } - - usql := fmt.Sprintf(t.updatesql, fieldsql[:len(fieldsql)-1], uidname) - args = append(args, uidvalue) - _, err := t.store.db.Exec(usql, args...) - return err -} type TSreamer struct { - Uid int `field:"uid" uid:"auto"` - Name interface{} `field:"name"` - UserID *sql.NullString `field:"userid"` - Ext *sql.NullString `field:"ext"` - Iface interface{} `field:"tag"` - Bit uint64 `field:"bit"` - Operator int `field:"operator"` + Uid int `field:"uid" uid:"auto"` + Name interface{} `field:"name"` + UserID *sql.NullString `field:"userid"` + Ext *sql.NullString `field:"ext"` + Iface interface{} `field:"tag"` + Bit uint64 `field:"bit"` + Operator int `field:"operator"` + UpdateTime *time.Time `field:"update_time"` }