Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

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
}