1
0
mirror of https://github.com/alm494/sql_proxy.git synced 2026-04-22 19:33:55 +02:00

prepared statements

This commit is contained in:
Almaz Sharipov
2025-04-01 17:40:11 +03:00
parent 4d39396d26
commit e1cead09de
6 changed files with 193 additions and 44 deletions
+8 -11
View File
@@ -153,7 +153,6 @@ func (o *DbList) Delete(id string) {
func (o *DbList) PutPreparedStatement(id string, stmt *sql.Stmt) (string, bool) {
val, ok := o.items.Load(id)
if !ok {
app.Log.Errorf("SQL connection with guid='%s' not found", id)
return "", false
}
@@ -170,15 +169,14 @@ func (o *DbList) PutPreparedStatement(id string, stmt *sql.Stmt) (string, bool)
}
// Gets SQL prepared statement
func (o *DbList) GetPreparedStatement(conn_id, stmt_id string) (*sql.Stmt, bool) {
val, ok := o.items.Load(conn_id)
func (o *DbList) GetPreparedStatement(connId, stmtId string) (*sql.Stmt, bool) {
val, ok := o.items.Load(connId)
if !ok {
app.Log.Errorf("SQL connection with guid='%s' not found", conn_id)
return nil, false
}
res := val.(*DbConn)
for i := 0; i < len(res.Stmt); i++ {
if res.Stmt[i].Id == stmt_id {
for i := range res.Stmt {
if res.Stmt[i].Id == stmtId {
return res.Stmt[i].Stmt, true
}
}
@@ -186,21 +184,20 @@ func (o *DbList) GetPreparedStatement(conn_id, stmt_id string) (*sql.Stmt, bool)
}
// Closes and deletes SQL prepared statement
func (o *DbList) ClosePreparedStatement(conn_id, stmt_id string) bool {
val, ok := o.items.Load(conn_id)
func (o *DbList) ClosePreparedStatement(connId, stmtId string) bool {
val, ok := o.items.Load(connId)
if !ok {
app.Log.Errorf("SQL connection with guid='%s' not found", conn_id)
return false
}
res := val.(*DbConn)
for i := range res.Stmt {
if res.Stmt[i].Id == stmt_id {
if res.Stmt[i].Id == stmtId {
res.Stmt[i].Stmt.Close()
res.Stmt = slices.Delete(res.Stmt, i, i+1)
break
}
}
o.items.Store(conn_id, res)
o.items.Store(connId, res)
return true
}
+68 -2
View File
@@ -6,7 +6,9 @@ servers:
- url: http://localhost/api/v1
paths:
/connection:
post:
summary: Establish SQL connection
description: First, check if an SQL connection has already been established. If not, create a new connection and add it to the application pool.
@@ -53,6 +55,7 @@ paths:
description: Bad request
/query:
get:
summary: SELECT queries
description: Use this method for any SQL query that is expected to return a result as a table, such as a SELECT statement. The resulting table is wrapped into a flexible JSON object, with columns dynamically determined based on the query.
@@ -68,7 +71,7 @@ paths:
name: SQL-Statement
schema:
type: string
description: SQL statemet (url-encoded).
description: SQL statement (url-encoded).
required: true
example: 'SELECT * FROM SALES WHERE Title LIKE "Manager %"'
@@ -102,7 +105,7 @@ paths:
name: SQL-Statement
schema:
type: string
description: SQL statemet (url-encoded).
description: SQL statement (url-encoded).
required: true
example: 'DELETE FROM SALES WHERE id = 783'
responses:
@@ -112,6 +115,69 @@ paths:
description: Bad request
'403':
description: Forbidden
/prepared:
post:
summary: Create prepared statement
description: 'Create prepared statement'
parameters:
- in: header
name: Connection-Id
schema:
type: string
description: SQL connection id as GUID in a plain text, must be obtained by /connection POST method.
required: true
example: '52f0b434-4eae-4cc6-803c-2d2f604fe16c'
- in: header
name: Prepared-Statement
schema:
type: string
description: SQL prepared statement with parameters, both for select or execute (url-encoded).
required: true
example: 'SELECT * SALES WHERE id = ? and name = ?'
responses:
'200':
description: OK
content:
text/plain:
schema:
type: string
description: return SQL prepared statement id as GUID in a plain text.
example: 'f3f0b434-e4ae-c4c6-c803-d22f504fe16c'
'400':
description: Bad request
'403':
description: Forbidden
'500':
description: Internal server error
delete:
summary: Close prepared statement
description: Close and delete prepared statement.
parameters:
- in: header
name: Connection-Id
schema:
type: string
description: SQL connection id as GUID in a plain text.
required: true
example: '52f0b434-4eae-4cc6-803c-2d2f604fe16c'
- in: header
name: Statement-Id
schema:
type: string
description: Prepared statement id as GUID in a plain text.
required: true
example: 'f3f0b434-e4ae-c4c6-c803-d22f504fe16c'
responses:
'200':
description: OK
'400':
description: Bad request
'403':
description: Forbidden
components:
schemas:
+66
View File
@@ -6,7 +6,9 @@ servers:
- url: http://localhost/api/v1
paths:
/connection:
post:
summary: Получить SQL-соединение
description: Сначала проверяет, установлено ли уже SQL-соединение. Если нет, устанавливает новое и добавляет его в пул доступных соединений.
@@ -53,6 +55,7 @@ paths:
description: Неверный запрос
/query:
get:
summary: Запросы SELECT
description: Используйте этот метод для любого SQL-запроса, от который ожидается результат в виде таблицы, например, с оператором SELECT. Полученная таблица оборачивается в JSON-объект, с колонками, определяемыми динамически на основе запроса.
@@ -112,6 +115,69 @@ paths:
description: Неверный запрос
'403':
description: Запрещено
/prepared:
post:
summary: Создать предварительно подготовленный оператор
description: Используйте этот метод для создания предварительно подготовленного оператора
parameters:
- in: header
name: Connection-Id
schema:
type: string
description: Идентификатор SQL-соединения в форме GUID, должен быть получен ранее методом POST /connection.
required: true
example: '52f0b434-4eae-4cc6-803c-2d2f604fe16c'
- in: header
name: Prepared-Statement
description: Предварительно подготовленный оператор SQL с параметрами, для выборки или изменения данных (url-кодированный).
schema:
type: string
required: true
example: 'SELECT * SALES WHERE id = ? and name = ?'
responses:
'200':
description: OK
content:
text/plain:
schema:
type: string
description: Возвращает идентификатор подготовленного оператора SQL в форме GUID..
example: 'f3f0b434-e4ae-c4c6-c803-d22f504fe16c'
'400':
description: Неверный запрос
'403':
description: Запрещено
'500':
description: Внутренняя ошибка сервера
delete:
summary: Закрыть предварительно подготовленный оператор
description: Закрыть и удалить предварительно подготовленный оператор SQL.
parameters:
- in: header
name: Connection-Id
schema:
type: string
description: Идентификатор SQL-соединения в форме GUID, должен быть получен ранее методом POST /connection.
required: true
example: '52f0b434-4eae-4cc6-803c-2d2f604fe16c'
- in: header
name: Statement-Id
schema:
type: string
description: Идентификатор предварительно подготовленного оператора SQL в форме GUID, должен быть получен ранее методом POST /prepared.
required: true
example: 'f3f0b434-e4ae-c4c6-c803-d22f504fe16c'
responses:
'200':
description: OK
'400':
description: Неверный запрос
'403':
description: Запрещено
components:
schemas:
+3 -4
View File
@@ -16,11 +16,10 @@ func CreateConnection(w http.ResponseWriter, r *http.Request) {
if connGuid, ok := db.Handler.GetByParams(&dbConnInfo); !ok {
errorResponce(w, "Failed to get SQL connection", http.StatusInternalServerError)
} else {
if _, err := w.Write([]byte(connGuid)); err != nil {
errorResponce(w, err.Error(), http.StatusInternalServerError)
}
} else if _, err := w.Write([]byte(connGuid)); err != nil {
errorResponce(w, err.Error(), http.StatusInternalServerError)
}
}
func CloseConnection(w http.ResponseWriter, r *http.Request) {
+39 -18
View File
@@ -1,58 +1,79 @@
package handlers
import (
"encoding/json"
"net/http"
"net/url"
"sql-proxy/src/app"
"sql-proxy/src/db"
"github.com/sirupsen/logrus"
)
func PrepareStatement(w http.ResponseWriter, r *http.Request) {
var requestBody map[string]any
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
errorResponce(w, "Error decoding JSON", http.StatusBadRequest)
connId := r.Header.Get("Connection-Id")
preparedStatement, err := url.QueryUnescape(r.Header.Get("Prepared-Statement"))
if err != nil || connId == "" || preparedStatement == "" {
errorResponce(w, "Bad request", http.StatusBadRequest)
return
}
defer r.Body.Close()
app.Log.WithFields(logrus.Fields{
"prepared_statement": preparedStatement,
"connection_id": connId,
}).Debug("Prepare statement received:")
conn, ok := db.Handler.GetById(requestBody["connection_id"].(string), true)
conn, ok := db.Handler.GetById(connId, true)
if !ok {
errorResponce(w, "Invalid connection id", http.StatusBadRequest)
errorResponce(w, "Invalid connection id", http.StatusForbidden)
return
}
stmt, err := conn.Prepare(requestBody["sql"].(string))
stmt, err := conn.Prepare(preparedStatement)
if err != nil {
errorResponce(w, err.Error(), http.StatusBadRequest)
}
stmt_id, ok := db.Handler.PutPreparedStatement(requestBody["connection_id"].(string), stmt)
stmtId, ok := db.Handler.PutPreparedStatement(connId, stmt)
if !ok {
errorResponce(w, err.Error(), http.StatusInternalServerError)
}
if _, err = w.Write([]byte(stmt_id)); err != nil {
if _, err = w.Write([]byte(stmtId)); err != nil {
errorResponce(w, err.Error(), http.StatusInternalServerError)
}
}
func PreparedSelect(w http.ResponseWriter, r *http.Request) {
var requestBody map[string]any
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
errorResponce(w, "Error decoding JSON", http.StatusBadRequest)
return
}
defer r.Body.Close()
// to do
}
func PreparedExecute(w http.ResponseWriter, r *http.Request) {
// to do
}
func ClosePreparedStatement(w http.ResponseWriter, r *http.Request) {
// to do
connId := r.Header.Get("Connection-Id")
stmtId := r.Header.Get("Statement-Id")
if connId == "" || stmtId == "" {
errorResponce(w, "Bad request", http.StatusBadRequest)
return
}
app.Log.WithFields(logrus.Fields{
"connection_id": connId,
"prepared_statement": stmtId,
}).Debug("Delete prepared statememt received:")
if ok := db.Handler.ClosePreparedStatement(connId, stmtId); !ok {
errorResponce(w, "Forbidden", http.StatusForbidden)
}
}
+9 -9
View File
@@ -13,21 +13,21 @@ import (
func SelectQuery(w http.ResponseWriter, r *http.Request) {
conn_id := r.Header.Get("Connection-Id")
connId := r.Header.Get("Connection-Id")
query, err := url.QueryUnescape(r.Header.Get("SQL-Statement"))
if err != nil || conn_id == "" || query == "" {
if err != nil || connId == "" || query == "" {
errorResponce(w, "Bad request", http.StatusBadRequest)
return
}
app.Log.WithFields(logrus.Fields{
"sql": query,
"connection_id": conn_id,
"connection_id": connId,
}).Debug("SQL query received:")
// Search existings connection in the pool
dbConn, ok := db.Handler.GetById(conn_id, true)
dbConn, ok := db.Handler.GetById(connId, true)
if !ok {
errorResponce(w, "Failed to get SQL connection", http.StatusForbidden)
return
@@ -59,20 +59,20 @@ func SelectQuery(w http.ResponseWriter, r *http.Request) {
func ExecuteQuery(w http.ResponseWriter, r *http.Request) {
conn_id := r.Header.Get("Connection-Id")
connId := r.Header.Get("Connection-Id")
query, err := url.QueryUnescape(r.Header.Get("SQL-Statement"))
if err != nil || conn_id == "" || query == "" {
if err != nil || connId == "" || query == "" {
errorResponce(w, "Bad request", http.StatusBadRequest)
return
}
app.Log.WithFields(logrus.Fields{
"sql": query,
"connection_id": conn_id,
"connection_id": connId,
}).Debug("SQL query received:")
dbConn, ok := db.Handler.GetById(conn_id, true)
dbConn, ok := db.Handler.GetById(connId, true)
if !ok {
errorResponce(w, "Invalid connection id", http.StatusForbidden)
return
@@ -80,7 +80,7 @@ func ExecuteQuery(w http.ResponseWriter, r *http.Request) {
app.Log.WithFields(logrus.Fields{
"sql": query,
"connection_id": conn_id,
"connection_id": connId,
}).Debug("SQL execute query received:")
_, err = dbConn.Exec(query)