2024-08-17 16:53:58 +00:00
|
|
|
package handler
|
2024-08-14 12:02:20 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2024-08-15 04:23:36 +00:00
|
|
|
"reflect"
|
2024-08-14 12:02:20 +00:00
|
|
|
|
2024-08-20 19:10:21 +00:00
|
|
|
"github.com/nesterow/dal/pkg/adapter"
|
|
|
|
"github.com/nesterow/dal/pkg/proto"
|
2024-08-14 12:02:20 +00:00
|
|
|
)
|
|
|
|
|
2024-08-15 07:10:49 +00:00
|
|
|
/*
|
|
|
|
QueryHandler is a http.Handler that reads a proto.Request from the request body,
|
|
|
|
parses it into a query, executes the query on the provided db and writes the
|
|
|
|
result to the response body.
|
|
|
|
- The request body is expected to be in msgpack format (proto.Request).
|
|
|
|
- The response body is written in msgpack format.
|
|
|
|
- The respose is a stream of rows (proto.Row), where the first row is the column names.
|
|
|
|
- The columns are sorted alphabetically, so it is client's responsibility to match them and sort as needed.
|
|
|
|
*/
|
2024-08-14 12:02:20 +00:00
|
|
|
func QueryHandler(db adapter.DBAdapter) http.Handler {
|
2024-08-15 07:06:11 +00:00
|
|
|
dialect := adapter.GetDialect(db.Type)
|
2024-08-14 12:02:20 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
2024-08-15 11:31:56 +00:00
|
|
|
body, err := io.ReadAll(r.Body)
|
2024-08-14 12:02:20 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req := proto.Request{}
|
|
|
|
req.UnmarshalMsg(body)
|
|
|
|
|
|
|
|
query, err := req.Parse(dialect)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-15 15:52:21 +00:00
|
|
|
if query.Exec {
|
|
|
|
result, err := db.Exec(query)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/x-msgpack")
|
|
|
|
ra, _ := result.RowsAffected()
|
|
|
|
la, _ := result.LastInsertId()
|
|
|
|
res := proto.Response{
|
|
|
|
Id: 0,
|
|
|
|
RowsAffected: ra,
|
|
|
|
LastInsertId: la,
|
|
|
|
}
|
|
|
|
out, _ := res.MarshalMsg(nil)
|
|
|
|
w.Write(out)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-14 12:02:20 +00:00
|
|
|
rows, err := db.Query(query)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2024-08-15 04:23:36 +00:00
|
|
|
defer rows.Close()
|
2024-08-14 12:02:20 +00:00
|
|
|
|
2024-08-15 11:31:56 +00:00
|
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
2024-08-14 12:02:20 +00:00
|
|
|
w.Header().Set("Content-Type", "application/x-msgpack")
|
2024-08-15 15:52:21 +00:00
|
|
|
|
2024-08-15 11:31:56 +00:00
|
|
|
flusher, ok := w.(http.Flusher)
|
|
|
|
if !ok {
|
|
|
|
http.Error(w, "expected http.ResponseWriter to be an http.Flusher", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2024-08-15 04:23:36 +00:00
|
|
|
columns, _ := rows.Columns()
|
|
|
|
types, _ := rows.ColumnTypes()
|
|
|
|
cols, _ := proto.MarshalRow(columns)
|
|
|
|
w.Write(cols)
|
2024-08-15 11:31:56 +00:00
|
|
|
flusher.Flush()
|
2024-08-15 04:23:36 +00:00
|
|
|
|
2024-08-14 12:02:20 +00:00
|
|
|
for rows.Next() {
|
2024-08-15 04:23:36 +00:00
|
|
|
data := make([]interface{}, len(columns))
|
|
|
|
for i := range data {
|
|
|
|
typ := reflect.New(types[i].ScanType()).Interface()
|
|
|
|
data[i] = &typ
|
|
|
|
}
|
|
|
|
rows.Scan(data...)
|
|
|
|
cols, _ := proto.MarshalRow(data)
|
|
|
|
w.Write(cols)
|
2024-08-15 11:31:56 +00:00
|
|
|
flusher.Flush()
|
2024-08-14 12:02:20 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|