Search code examples
godynamicendpointgoland

getrow data from a MySQL/Go table


First it reads the code so that you understand the logic of what it does, when running the stored procedure that I capture it post, it brings me a table with data which I must return, the name of the columns does bring it to me but the data of the columns does not bring me anything and I cannot create a model and the responses of the stored procedures have n number of columns with n amount of Different names, but the columns vary in having int data and string data, I need you to capture the correct data from the columns as everything works but the data from the columns does not:

package controllers

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

type RequestData struct {
    FromData map[string]interface{} `json:"fromData"`
    Call     string                 `json:"Call"`
}

func HandleDatos(c *gin.Context) {
    var requestData RequestData

    if err := c.ShouldBindJSON(&requestData); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    fmt.Printf("Ejecutando procedimiento almacenado: CALL %s\n", requestData.Call)
    fmt.Printf("Parámetros: %v\n", requestData.FromData)

    var rows *sql.Rows
    var err error

    // Verifica si FromData contiene valores
    if len(requestData.FromData) > 0 {
        // Si hay valores en FromData, crea una consulta con parámetros
        query := "CALL " + requestData.Call + "("
        params := []interface{}{}
        for _, value := range requestData.FromData {
            query += "?, "
            params = append(params, value)
        }
        query = query[:len(query)-2] + ")"

        rows, err = db.Raw(query, params...).Rows()
    } else {
        // Si no hay valores en FromData, ejecuta el procedimiento almacenado sin parámetros
        rows, err = db.Raw("CALL " + requestData.Call).Rows()
    }

    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    defer rows.Close()

    // Convierte los resultados en un mapa
    result := make(map[string]interface{})
    columns, err := rows.Columns()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    fmt.Printf("Columnas: %v\n", columns) // Punto de impresión

    data := [][]interface{}{} // Almacena los datos de filas
    for rows.Next() {
        values := make([]interface{}, len(columns))
        for i := range columns {
            values[i] = new(interface{})
        }

        if err := rows.Scan(values...); err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }

        fmt.Printf("Valores escaneados: %v\n", values) // Punto de impresión

        row := make(map[string]interface{})
        for i, col := range columns {
            val := *(values[i].(*interface{}))
            row[col] = val
        }

        fmt.Printf("Fila escaneada: %v\n", row) // Punto de impresión

        // Agrega esta fila al resultado
        data = append(data, values)
    }

    fmt.Printf("Datos finales: %v\n", data) // Punto de impresión

    if len(data) > 0 {
        result["columns"] = columns
        result["data"] = data
    } else {
        // Si no hay datos, establece un mensaje personalizado
        result["message"] = "Sin datos"
    }

    // Convierte el resultado en JSON y devuelve la respuesta
    responseJSON, err := json.Marshal(result)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, string(responseJSON))
}

This is what it returns to me where it says "columns":["idPunto","nombre"] That part is okay but the rows with the data are not what I expected:

respuesta dela api al correrla


Solution

  • Scanning rows into an interface{} does not automatically convert the SQL types to Go types. Instead, using the ColumnTypes method will get the data type of each column allowing you to dynamically allocate the correct Go type. (The following is untested and is a guide only.) e.g

    for i := range columns {
        // Use the column types to determine the appropriate scan type
        switch columnTypes[i].DatabaseTypeName() {
        case "INT", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT":
            scanArgs[i] = new(int64)
        default:
            scanArgs[i] = new(string)
        }
    
        values[i] = scanArgs[i]
    }
    

    Within your script:

    package controllers
    
    import (
        "database/sql"
        "encoding/json"
        "fmt"
        "net/http"
    
        "github.com/gin-gonic/gin"
    )
    
    type RequestData struct {
        FromData map[string]interface{} `json:"fromData"`
        Call     string                 `json:"Call"`
    }
    
    func HandleDatos(c *gin.Context) {
        var requestData RequestData
    
        if err := c.ShouldBindJSON(&requestData); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
    
        fmt.Printf("Ejecutando procedimiento almacenado: CALL %s\n", requestData.Call)
        fmt.Printf("Parámetros: %v\n", requestData.FromData)
    
        var rows *sql.Rows
        var err error
    
        // Verifica si FromData contiene valores
        if len(requestData.FromData) > 0 {
            // Si hay valores en FromData, crea una consulta con parámetros
            query := "CALL " + requestData.Call + "("
            params := []interface{}{}
            for _, value := range requestData.FromData {
                query += "?, "
                params = append(params, value)
            }
            query = query[:len(query)-2] + ")"
    
            rows, err = db.Raw(query, params...).Rows()
        } else {
            // Si no hay valores en FromData, ejecuta el procedimiento almacenado sin parámetros
            rows, err = db.Raw("CALL " + requestData.Call).Rows()
        }
    
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }
        defer rows.Close()
    
        // Convierte los resultados en un mapa
        result := make(map[string]interface{})
        columns, err := rows.Columns()
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }
    
        fmt.Printf("Columnas: %v\n", columns) // Punto de impresión
    
        data := []map[string]interface{}{} // Almacena los datos de filas
    
        // Get the column types
        columnTypes, err := rows.ColumnTypes()
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }
    
        for rows.Next() {
            values := make([]interface{}, len(columns)
            scanArgs := make([]interface{}, len(columns))
    
            for i := range columns {
                // Use the column types to determine the appropriate scan type
                switch columnTypes[i].DatabaseTypeName() {
                case "INT", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT":
                    scanArgs[i] = new(int64)
                default:
                    scanArgs[i] = new(string)
                }
    
                values[i] = scanArgs[i]
            }
    
            if err := rows.Scan(values...); err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
                return
            }
    
            fmt.Printf("Valores escaneados: %v\n", values) // Punto de impresión
    
            row := make(map[string]interface{})
            for i, col := range columns {
                // Cast the scanned values to the appropriate data types
                switch columnTypes[i].DatabaseTypeName() {
                case "INT", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT":
                    row[col] = *(scanArgs[i].(*int64))
                default:
                    row[col] = *(scanArgs[i].(*string))
                }
            }
    
            fmt.Printf("Fila escaneada: %v\n", row) // Punto de impresión
    
            // Agrega esta fila al resultado
            data = append(data, row)
        }
    
        fmt.Printf("Datos finales: %v\n", data) // Punto de impresión
    
        if len(data) > 0 {
            result["columns"] = columns
            result["data"] = data
        } else {
            // Si no hay datos, establece un mensaje personalizado
            result["message"] = "Sin datos"
        }
    
        // Convierte el resultado en JSON y devuelve la respuesta
        responseJSON, err := json.Marshal(result)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }
    
        c.JSON(http.StatusOK, string(responseJSON))
    }
    

    nb: You should be able to extend this logic for other data types might encounter.