e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
213 lines
5.5 KiB
Go
213 lines
5.5 KiB
Go
// Copyright 2025 Alibaba Group Holding Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
|
|
"github.com/alibaba/opensandbox/execd/pkg/jupyter/execute"
|
|
"github.com/alibaba/opensandbox/execd/pkg/log"
|
|
)
|
|
|
|
// QueryResult represents a SQL query response.
|
|
type QueryResult struct {
|
|
Columns []string `json:"columns,omitempty"`
|
|
Rows [][]any `json:"rows,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// runSQL executes SQL queries based on their type.
|
|
func (c *Controller) runSQL(ctx context.Context, request *ExecuteCodeRequest) error {
|
|
request.Hooks.OnExecuteInit(uuid.New().String())
|
|
err := c.initDB()
|
|
if err != nil {
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBInitError", EValue: err.Error()})
|
|
log.Error("DBInitError: error initializing db server: %v", err)
|
|
return err
|
|
}
|
|
|
|
err = c.db.PingContext(ctx)
|
|
if err != nil {
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBPingError", EValue: err.Error()})
|
|
log.Error("DBPingError: error pinging db server: %v", err)
|
|
return err
|
|
}
|
|
|
|
switch c.getQueryType(request.Code) {
|
|
case "SELECT":
|
|
return c.executeSelectSQLQuery(ctx, request)
|
|
default:
|
|
return c.executeUpdateSQLQuery(ctx, request)
|
|
}
|
|
}
|
|
|
|
// executeSelectSQLQuery handles SELECT statements.
|
|
func (c *Controller) executeSelectSQLQuery(ctx context.Context, request *ExecuteCodeRequest) error {
|
|
startAt := time.Now()
|
|
|
|
// The SQL runtime intentionally executes the user's submitted SQL program
|
|
// against the sandbox-local database; no trusted query template is composed here.
|
|
rows, err := c.db.QueryContext(ctx, request.Code) // lgtm[go/sql-injection]
|
|
if err != nil {
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBQueryError", EValue: err.Error()})
|
|
return nil
|
|
}
|
|
defer rows.Close()
|
|
|
|
columns, err := rows.Columns()
|
|
if err != nil {
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBQueryError", EValue: err.Error()})
|
|
return nil
|
|
}
|
|
|
|
var result [][]any
|
|
values := make([]any, len(columns))
|
|
scanArgs := make([]any, len(columns))
|
|
for i := range values {
|
|
scanArgs[i] = &values[i]
|
|
}
|
|
|
|
for rows.Next() {
|
|
err := rows.Scan(scanArgs...)
|
|
if err != nil {
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "RowScanError", EValue: err.Error()})
|
|
return nil
|
|
}
|
|
row := make([]any, len(columns))
|
|
for i, v := range values {
|
|
if v == nil {
|
|
row[i] = nil
|
|
} else {
|
|
row[i] = fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
result = append(result, row)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "RowIterationError", EValue: err.Error()})
|
|
return nil
|
|
}
|
|
|
|
queryResult := QueryResult{
|
|
Columns: columns,
|
|
Rows: result,
|
|
}
|
|
bytes, err := json.Marshal(queryResult)
|
|
if err != nil {
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "JSONMarshalError", EValue: err.Error()})
|
|
return nil
|
|
}
|
|
request.Hooks.OnExecuteResult(
|
|
map[string]any{
|
|
"text/plain": string(bytes),
|
|
},
|
|
1,
|
|
)
|
|
request.Hooks.OnExecuteComplete(time.Since(startAt))
|
|
return nil
|
|
}
|
|
|
|
// executeUpdateSQLQuery handles non-SELECT statements.
|
|
func (c *Controller) executeUpdateSQLQuery(ctx context.Context, request *ExecuteCodeRequest) error {
|
|
startAt := time.Now()
|
|
|
|
// The SQL runtime intentionally executes the user's submitted SQL program
|
|
// against the sandbox-local database; no trusted query template is composed here.
|
|
result, err := c.db.ExecContext(ctx, request.Code) // lgtm[go/sql-injection]
|
|
if err != nil {
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "DBExecError", EValue: err.Error()})
|
|
return err
|
|
}
|
|
|
|
affected, _ := result.RowsAffected()
|
|
queryResult := QueryResult{
|
|
Rows: [][]any{{affected}},
|
|
Columns: []string{"affected_rows"},
|
|
}
|
|
bytes, err := json.Marshal(queryResult)
|
|
if err != nil {
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "JSONMarshalError", EValue: err.Error()})
|
|
return err
|
|
}
|
|
request.Hooks.OnExecuteResult(
|
|
map[string]any{
|
|
"text/plain": string(bytes),
|
|
},
|
|
1,
|
|
)
|
|
request.Hooks.OnExecuteComplete(time.Since(startAt))
|
|
return nil
|
|
}
|
|
|
|
// getQueryType extracts the first token to decide which executor to use.
|
|
func (c *Controller) getQueryType(query string) string {
|
|
fields := strings.Fields(query)
|
|
if len(fields) == 0 {
|
|
return ""
|
|
}
|
|
return strings.ToUpper(fields[0])
|
|
}
|
|
|
|
// initDB lazily opens the local sandbox database.
|
|
func (c *Controller) initDB() error {
|
|
var initErr error
|
|
c.dbOnce.Do(func() {
|
|
dsn := "root:@tcp(127.0.0.1:3306)/"
|
|
db, err := sql.Open("mysql", dsn)
|
|
if err != nil {
|
|
initErr = err
|
|
return
|
|
}
|
|
|
|
err = db.Ping()
|
|
if err != nil {
|
|
initErr = err
|
|
return
|
|
}
|
|
|
|
_, err = db.Exec("CREATE DATABASE IF NOT EXISTS sandbox")
|
|
if err != nil {
|
|
initErr = err
|
|
return
|
|
}
|
|
|
|
_, err = db.Exec("USE sandbox")
|
|
if err != nil {
|
|
initErr = err
|
|
return
|
|
}
|
|
|
|
c.db = db
|
|
})
|
|
|
|
if initErr != nil {
|
|
return initErr
|
|
}
|
|
if c.db == nil {
|
|
return errors.New("db is not initialized")
|
|
}
|
|
return nil
|
|
}
|