package sql_executor import ( "database/sql" "net/http" "strings" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type SQLController struct { db *gorm.DB } type ExecuteSQLRequest struct { SQL string `json:"sql" binding:"required"` } type ExecuteSQLResponse struct { Type string `json:"type"` Columns []string `json:"columns,omitempty"` Rows [][]interface{} `json:"rows,omitempty"` RowsAffected int64 `json:"rows_affected,omitempty"` } func NewSQLController(db *gorm.DB) *SQLController { return &SQLController{db: db} } func (c *SQLController) Execute(ctx *gin.Context) { var req ExecuteSQLRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } query := strings.TrimSpace(req.SQL) if query == "" { ctx.JSON(http.StatusBadRequest, gin.H{"error": "sql is empty"}) return } if isRowsQuery(query) { response, err := c.executeRowsQuery(query) if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, response) return } result := c.db.Exec(query) if result.Error != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()}) return } ctx.JSON(http.StatusOK, ExecuteSQLResponse{ Type: "exec", RowsAffected: result.RowsAffected, }) } func isRowsQuery(query string) bool { lower := strings.ToLower(strings.TrimSpace(query)) return strings.HasPrefix(lower, "select") || strings.HasPrefix(lower, "with") || strings.HasPrefix(lower, "pragma") } func (c *SQLController) executeRowsQuery(query string) (ExecuteSQLResponse, error) { rows, err := c.db.Raw(query).Rows() if err != nil { return ExecuteSQLResponse{}, err } defer rows.Close() columns, err := rows.Columns() if err != nil { return ExecuteSQLResponse{}, err } resultRows, err := scanRows(rows, columns) if err != nil { return ExecuteSQLResponse{}, err } return ExecuteSQLResponse{ Type: "select", Columns: columns, Rows: resultRows, }, nil } func scanRows(rows *sql.Rows, columns []string) ([][]interface{}, error) { result := make([][]interface{}, 0) for rows.Next() { values := make([]interface{}, len(columns)) valuePointers := make([]interface{}, len(columns)) for i := range values { valuePointers[i] = &values[i] } if err := rows.Scan(valuePointers...); err != nil { return nil, err } row := make([]interface{}, len(columns)) for i, value := range values { if bytes, ok := value.([]byte); ok { row[i] = string(bytes) } else { row[i] = value } } result = append(result, row) } if err := rows.Err(); err != nil { return nil, err } return result, nil }