Skip to content

Instantly share code, notes, and snippets.

@thunder-spb
Forked from Indribell/sql.go
Created June 10, 2021 10:09
Show Gist options
  • Save thunder-spb/56279a5d0b4038c46fdbb11f616fa8d2 to your computer and use it in GitHub Desktop.
Save thunder-spb/56279a5d0b4038c46fdbb11f616fa8d2 to your computer and use it in GitHub Desktop.

Revisions

  1. @Indribell Indribell revised this gist Oct 15, 2020. 1 changed file with 15 additions and 16 deletions.
    31 changes: 15 additions & 16 deletions sql.go
    Original file line number Diff line number Diff line change
    @@ -24,35 +24,34 @@ type User struct {
    Age int `db:"age" json:"age,omitempty"`
    }

    // QueryAll allows a Slice of results, to be pushed into Struct
    // QueryStruct allows a Struct or Slice with Struct as result
    //
    // Example:
    // Multi results:
    //
    // users := []User{}
    // QueryAll(&user, "SELECT id, firstname, age FROM user")
    // contacts := []Contact{}
    // QueryStruct(&contacts, "SELECT * FROM contact")
    //
    // Single result:
    // contact := Contact{}
    // QueryOne(&contact, "SELECT * FROM contact")
    //
    // user := User{}
    // QueryAll(&user, "SELECT id, firstname, age FROM user")
    //
    func (connection *Connection) QueryAll(dest interface{}, query string, args ...interface{}) error {
    func (connection *Connection) QueryStruct(dest interface{}, query string, args ...interface{}) bool {

    rows, _ := connection.sql.Query(query, args...)
    rows, err := connection.sql.Query(query, args...)
    if err != nil {
    log.Println("DB: QueryAll failed: " + err.Error())
    return false
    }
    defer rows.Close()

    err := structScan(rows, dest)
    err = structScan(rows, dest)
    if err != nil {
    log.Println("DB: QueryAll failed on error")
    log.Println(err)
    return errors.New("DB: QueryAll failed on error")
    log.Println("DB: QueryAll structScan failed: " + err.Error())
    return false
    }

    return err
    return true
    }


    // structScan will scan the results of a database query and try to merge then with the supplied struct
    //
    // Example: []Users, User
  2. @Indribell Indribell revised this gist Oct 15, 2020. 1 changed file with 17 additions and 8 deletions.
    25 changes: 17 additions & 8 deletions sql.go
    Original file line number Diff line number Diff line change
    @@ -52,6 +52,12 @@ func (connection *Connection) QueryAll(dest interface{}, query string, args ...i
    return err
    }


    // structScan will scan the results of a database query and try to merge then with the supplied struct
    //
    // Example: []Users, User
    //
    // Are both valid structs as the routing will automatically figure out if the results are a slice with struct or a pure struct
    func structScan(rows *sql.Rows, model interface{}) error {

    v := reflect.ValueOf(model)
    @@ -98,26 +104,29 @@ func structScan(rows *sql.Rows, model interface{}) error {
    }

    if isStruct == true {
    changeStruct(v, t, m[0])
    if len(m) > 0 { // Ensure we have data fields!
    changeStruct(v, t, m[0])
    }
    }

    if isSlice == true {
    var elem reflect.Value
    for _, d := range m {
    if len(m) > 0 { // Ensure we have data in the slice!
    var elem reflect.Value
    for _, d := range m {

    typ := v.Type().Elem()
    elem = reflect.New(typ).Elem()
    typ := v.Type().Elem()
    elem = reflect.New(typ).Elem()

    changeStruct(elem, typ, d)
    v.Set(reflect.Append(v, elem))
    changeStruct(elem, typ, d)
    v.Set(reflect.Append(v, elem))
    }
    }
    }

    return nil
    }

    func changeStruct(v reflect.Value, t reflect.Type, m map[string]interface{}) {

    for i := 0; i < v.NumField(); i++ {
    field := strings.Split(t.Field(i).Tag.Get("db"), ",")[0]

  3. @Indribell Indribell revised this gist Sep 28, 2020. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions sql.go
    Original file line number Diff line number Diff line change
    @@ -2,12 +2,12 @@
    // https://gist.github.com/PumpkinSeed/b4993c6ad20ea90e3da8c991a90a91e1
    //
    // 1. It was only able to extract database information, based upon a Struct.
    // The code now can deal with a struct or a slice with structs. As those are the most common information extraction, you will do
    // The code now can deal with a struct or a slice with structs.
    //
    // 2. The code relied on the json tag in the struct.
    // This is a very incorrect as you need to match the data with database fields, not your output fields.
    // You need to match the data with database fields, not the output fields.
    // This will match up more 1 to 1 as you are in controle of your Database naming and selecting of that data.
    // If a client expect different named exported json fields, ...
    // If a client expect different named exported json fields, ...
    //
    // 3. The code while designed for a single struct, kept looping over all the rows.
    // So if you tried to extract one result and did not use a limitor ( LIMIT ) in your SQL code, it will have looped the column information ..N times
  4. @Indribell Indribell revised this gist Sep 28, 2020. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions sql.go
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,3 @@
    ```
    // Fixing some of the errors in the:
    // https://gist.github.com/PumpkinSeed/b4993c6ad20ea90e3da8c991a90a91e1
    //
    @@ -148,5 +147,4 @@ func changeStruct(v reflect.Value, t reflect.Type, m map[string]interface{}) {
    }
    }
    }
    }
    ```
    }
  5. @Indribell Indribell renamed this gist Sep 28, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  6. @Indribell Indribell revised this gist Sep 28, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Extension for sql package
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    ```go
    ```
    // Fixing some of the errors in the:
    // https://gist.github.com/PumpkinSeed/b4993c6ad20ea90e3da8c991a90a91e1
    //
  7. @Indribell Indribell revised this gist Sep 28, 2020. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions Extension for sql package
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    ```go
    // Fixing some of the errors in the:
    // https://gist.github.com/PumpkinSeed/b4993c6ad20ea90e3da8c991a90a91e1
    //
    @@ -148,5 +149,4 @@ func changeStruct(v reflect.Value, t reflect.Type, m map[string]interface{}) {
    }
    }
    }


    ```
  8. @Indribell Indribell created this gist Sep 28, 2020.
    152 changes: 152 additions & 0 deletions Extension for sql package
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    // Fixing some of the errors in the:
    // https://gist.github.com/PumpkinSeed/b4993c6ad20ea90e3da8c991a90a91e1
    //
    // 1. It was only able to extract database information, based upon a Struct.
    // The code now can deal with a struct or a slice with structs. As those are the most common information extraction, you will do
    //
    // 2. The code relied on the json tag in the struct.
    // This is a very incorrect as you need to match the data with database fields, not your output fields.
    // This will match up more 1 to 1 as you are in controle of your Database naming and selecting of that data.
    // If a client expect different named exported json fields, ...
    //
    // 3. The code while designed for a single struct, kept looping over all the rows.
    // So if you tried to extract one result and did not use a limitor ( LIMIT ) in your SQL code, it will have looped the column information ..N times
    //
    // 4. Some data types like Int had been missing
    //
    // 5. Fix a few bugs


    type User struct {
    ID int `db:"id"`
    Firstname string `db:"firstname" json:"first_name"`
    Lastname string `db:"lastname" json:"last_name"`
    Age int `db:"age" json:"age,omitempty"`
    }

    // QueryAll allows a Slice of results, to be pushed into Struct
    //
    // Example:
    // Multi results:
    //
    // users := []User{}
    // QueryAll(&user, "SELECT id, firstname, age FROM user")
    //
    // Single result:
    //
    // user := User{}
    // QueryAll(&user, "SELECT id, firstname, age FROM user")
    //
    func (connection *Connection) QueryAll(dest interface{}, query string, args ...interface{}) error {

    rows, _ := connection.sql.Query(query, args...)
    defer rows.Close()

    err := structScan(rows, dest)
    if err != nil {
    log.Println("DB: QueryAll failed on error")
    log.Println(err)
    return errors.New("DB: QueryAll failed on error")
    }

    return err
    }

    func structScan(rows *sql.Rows, model interface{}) error {

    v := reflect.ValueOf(model)
    if v.Kind() != reflect.Ptr {
    return errors.New("must pass a pointer, not a value, to StructScan destination") // @todo add new error message
    }

    v = reflect.Indirect(v)
    t := v.Type()

    // Find out if what we are using is a Struct ( Query One ) or a Slice with Structs ( QueryAll )
    isStruct, isSlice := false, false
    if t.Kind() == reflect.Slice {
    isSlice = true
    } else if t.Kind() == reflect.Struct {
    isStruct = true
    }

    // Ensure we only get the column information one time!
    cols, _ := rows.Columns()
    columns := make([]interface{}, len(cols))
    columnPointers := make([]interface{}, len(cols))
    for i := range columns {
    columnPointers[i] = &columns[i]
    }

    var m []map[string]interface{}
    for rows.Next() {
    if err := rows.Scan(columnPointers...); err != nil {
    return err
    }

    x := make(map[string]interface{})
    for i, colName := range cols {
    val := columnPointers[i].(*interface{})
    x[colName] = *val
    }
    m = append(m, x)

    // If we are dealing with a struct. There is no point in looping over all the results, if they are more then one!
    if isStruct == true {
    break
    }
    }

    if isStruct == true {
    changeStruct(v, t, m[0])
    }

    if isSlice == true {
    var elem reflect.Value
    for _, d := range m {

    typ := v.Type().Elem()
    elem = reflect.New(typ).Elem()

    changeStruct(elem, typ, d)
    v.Set(reflect.Append(v, elem))
    }
    }

    return nil
    }

    func changeStruct(v reflect.Value, t reflect.Type, m map[string]interface{}) {

    for i := 0; i < v.NumField(); i++ {
    field := strings.Split(t.Field(i).Tag.Get("db"), ",")[0]

    if item, ok := m[field]; ok {
    if v.Field(i).CanSet() {
    if item != nil {
    switch v.Field(i).Kind() {
    case reflect.String:
    v.Field(i).SetString(item.(string)) // s := bytesToString(item.([]uint8))
    case reflect.Float32, reflect.Float64:
    v.Field(i).SetFloat(item.(float64))
    case reflect.Int, reflect.Int32, reflect.Int64:
    v.Field(i).SetInt(item.(int64))
    case reflect.Bool:
    v.Field(i).Set(reflect.ValueOf(!(item.(int64) == 0)))
    case reflect.Ptr:
    if reflect.ValueOf(item).Kind() == reflect.Bool {
    itemBool := item.(bool)
    v.Field(i).Set(reflect.ValueOf(&itemBool))
    }
    case reflect.Struct:
    v.Field(i).Set(reflect.ValueOf(item))
    default:
    fmt.Println(t.Field(i).Name, ": ", v.Field(i).Kind(), " - > - ", reflect.ValueOf(item).Kind()) // @todo remove after test out the Get methods
    }
    }
    }
    }
    }
    }