mirror of https://github.com/go-gitea/gitea
Fix error log when loading issues caused by a xorm bug (#7271)
* fix error log when loading issues caused by a xorm bug * upgrade packages * fix fmt * fix Consistency * fix testspull/7281/head^2
parent
baefea311f
commit
aa7c34cf86
@ -0,0 +1,207 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"context" |
||||
"database/sql" |
||||
"database/sql/driver" |
||||
) |
||||
|
||||
// Ping implements driver.Pinger interface
|
||||
func (mc *mysqlConn) Ping(ctx context.Context) (err error) { |
||||
if mc.closed.IsSet() { |
||||
errLog.Print(ErrInvalidConn) |
||||
return driver.ErrBadConn |
||||
} |
||||
|
||||
if err = mc.watchCancel(ctx); err != nil { |
||||
return |
||||
} |
||||
defer mc.finish() |
||||
|
||||
if err = mc.writeCommandPacket(comPing); err != nil { |
||||
return |
||||
} |
||||
|
||||
return mc.readResultOK() |
||||
} |
||||
|
||||
// BeginTx implements driver.ConnBeginTx interface
|
||||
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { |
||||
if err := mc.watchCancel(ctx); err != nil { |
||||
return nil, err |
||||
} |
||||
defer mc.finish() |
||||
|
||||
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault { |
||||
level, err := mapIsolationLevel(opts.Isolation) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
return mc.begin(opts.ReadOnly) |
||||
} |
||||
|
||||
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { |
||||
dargs, err := namedValueToValue(args) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if err := mc.watchCancel(ctx); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
rows, err := mc.query(query, dargs) |
||||
if err != nil { |
||||
mc.finish() |
||||
return nil, err |
||||
} |
||||
rows.finish = mc.finish |
||||
return rows, err |
||||
} |
||||
|
||||
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { |
||||
dargs, err := namedValueToValue(args) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if err := mc.watchCancel(ctx); err != nil { |
||||
return nil, err |
||||
} |
||||
defer mc.finish() |
||||
|
||||
return mc.Exec(query, dargs) |
||||
} |
||||
|
||||
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { |
||||
if err := mc.watchCancel(ctx); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
stmt, err := mc.Prepare(query) |
||||
mc.finish() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
select { |
||||
default: |
||||
case <-ctx.Done(): |
||||
stmt.Close() |
||||
return nil, ctx.Err() |
||||
} |
||||
return stmt, nil |
||||
} |
||||
|
||||
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { |
||||
dargs, err := namedValueToValue(args) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if err := stmt.mc.watchCancel(ctx); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
rows, err := stmt.query(dargs) |
||||
if err != nil { |
||||
stmt.mc.finish() |
||||
return nil, err |
||||
} |
||||
rows.finish = stmt.mc.finish |
||||
return rows, err |
||||
} |
||||
|
||||
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { |
||||
dargs, err := namedValueToValue(args) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if err := stmt.mc.watchCancel(ctx); err != nil { |
||||
return nil, err |
||||
} |
||||
defer stmt.mc.finish() |
||||
|
||||
return stmt.Exec(dargs) |
||||
} |
||||
|
||||
func (mc *mysqlConn) watchCancel(ctx context.Context) error { |
||||
if mc.watching { |
||||
// Reach here if canceled,
|
||||
// so the connection is already invalid
|
||||
mc.cleanup() |
||||
return nil |
||||
} |
||||
// When ctx is already cancelled, don't watch it.
|
||||
if err := ctx.Err(); err != nil { |
||||
return err |
||||
} |
||||
// When ctx is not cancellable, don't watch it.
|
||||
if ctx.Done() == nil { |
||||
return nil |
||||
} |
||||
// When watcher is not alive, can't watch it.
|
||||
if mc.watcher == nil { |
||||
return nil |
||||
} |
||||
|
||||
mc.watching = true |
||||
mc.watcher <- ctx |
||||
return nil |
||||
} |
||||
|
||||
func (mc *mysqlConn) startWatcher() { |
||||
watcher := make(chan mysqlContext, 1) |
||||
mc.watcher = watcher |
||||
finished := make(chan struct{}) |
||||
mc.finished = finished |
||||
go func() { |
||||
for { |
||||
var ctx mysqlContext |
||||
select { |
||||
case ctx = <-watcher: |
||||
case <-mc.closech: |
||||
return |
||||
} |
||||
|
||||
select { |
||||
case <-ctx.Done(): |
||||
mc.cancel(ctx.Err()) |
||||
case <-finished: |
||||
case <-mc.closech: |
||||
return |
||||
} |
||||
} |
||||
}() |
||||
} |
||||
|
||||
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) { |
||||
nv.Value, err = converter{}.ConvertValue(nv.Value) |
||||
return |
||||
} |
||||
|
||||
// ResetSession implements driver.SessionResetter.
|
||||
// (From Go 1.10)
|
||||
func (mc *mysqlConn) ResetSession(ctx context.Context) error { |
||||
if mc.closed.IsSet() { |
||||
return driver.ErrBadConn |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,40 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build go1.7
|
||||
// +build !go1.8
|
||||
|
||||
package mysql |
||||
|
||||
import "crypto/tls" |
||||
|
||||
func cloneTLSConfig(c *tls.Config) *tls.Config { |
||||
return &tls.Config{ |
||||
Rand: c.Rand, |
||||
Time: c.Time, |
||||
Certificates: c.Certificates, |
||||
NameToCertificate: c.NameToCertificate, |
||||
GetCertificate: c.GetCertificate, |
||||
RootCAs: c.RootCAs, |
||||
NextProtos: c.NextProtos, |
||||
ServerName: c.ServerName, |
||||
ClientAuth: c.ClientAuth, |
||||
ClientCAs: c.ClientCAs, |
||||
InsecureSkipVerify: c.InsecureSkipVerify, |
||||
CipherSuites: c.CipherSuites, |
||||
PreferServerCipherSuites: c.PreferServerCipherSuites, |
||||
SessionTicketsDisabled: c.SessionTicketsDisabled, |
||||
SessionTicketKey: c.SessionTicketKey, |
||||
ClientSessionCache: c.ClientSessionCache, |
||||
MinVersion: c.MinVersion, |
||||
MaxVersion: c.MaxVersion, |
||||
CurvePreferences: c.CurvePreferences, |
||||
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled, |
||||
Renegotiation: c.Renegotiation, |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"database/sql" |
||||
"database/sql/driver" |
||||
"errors" |
||||
"fmt" |
||||
) |
||||
|
||||
func cloneTLSConfig(c *tls.Config) *tls.Config { |
||||
return c.Clone() |
||||
} |
||||
|
||||
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) { |
||||
dargs := make([]driver.Value, len(named)) |
||||
for n, param := range named { |
||||
if len(param.Name) > 0 { |
||||
// TODO: support the use of Named Parameters #561
|
||||
return nil, errors.New("mysql: driver does not support the use of Named Parameters") |
||||
} |
||||
dargs[n] = param.Value |
||||
} |
||||
return dargs, nil |
||||
} |
||||
|
||||
func mapIsolationLevel(level driver.IsolationLevel) (string, error) { |
||||
switch sql.IsolationLevel(level) { |
||||
case sql.LevelRepeatableRead: |
||||
return "REPEATABLE READ", nil |
||||
case sql.LevelReadCommitted: |
||||
return "READ COMMITTED", nil |
||||
case sql.LevelReadUncommitted: |
||||
return "READ UNCOMMITTED", nil |
||||
case sql.LevelSerializable: |
||||
return "SERIALIZABLE", nil |
||||
default: |
||||
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level) |
||||
} |
||||
} |
@ -1 +0,0 @@ |
||||
module "github.com/go-xorm/builder" |
@ -1,15 +0,0 @@ |
||||
dependencies: |
||||
override: |
||||
# './...' is a relative pattern which means all subdirectories |
||||
- go get -t -d -v ./... |
||||
- go build -v |
||||
|
||||
database: |
||||
override: |
||||
- mysql -u root -e "CREATE DATABASE core_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" |
||||
|
||||
test: |
||||
override: |
||||
# './...' is a relative pattern which means all subdirectories |
||||
- go test -v -race |
||||
- go test -v -race --dbtype=sqlite3 |
@ -1,401 +0,0 @@ |
||||
package core |
||||
|
||||
import ( |
||||
"database/sql" |
||||
"database/sql/driver" |
||||
"errors" |
||||
"fmt" |
||||
"reflect" |
||||
"regexp" |
||||
"sync" |
||||
) |
||||
|
||||
var ( |
||||
DefaultCacheSize = 200 |
||||
) |
||||
|
||||
func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { |
||||
vv := reflect.ValueOf(mp) |
||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { |
||||
return "", []interface{}{}, ErrNoMapPointer |
||||
} |
||||
|
||||
args := make([]interface{}, 0, len(vv.Elem().MapKeys())) |
||||
var err error |
||||
query = re.ReplaceAllStringFunc(query, func(src string) string { |
||||
v := vv.Elem().MapIndex(reflect.ValueOf(src[1:])) |
||||
if !v.IsValid() { |
||||
err = fmt.Errorf("map key %s is missing", src[1:]) |
||||
} else { |
||||
args = append(args, v.Interface()) |
||||
} |
||||
return "?" |
||||
}) |
||||
|
||||
return query, args, err |
||||
} |
||||
|
||||
func StructToSlice(query string, st interface{}) (string, []interface{}, error) { |
||||
vv := reflect.ValueOf(st) |
||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { |
||||
return "", []interface{}{}, ErrNoStructPointer |
||||
} |
||||
|
||||
args := make([]interface{}, 0) |
||||
var err error |
||||
query = re.ReplaceAllStringFunc(query, func(src string) string { |
||||
fv := vv.Elem().FieldByName(src[1:]).Interface() |
||||
if v, ok := fv.(driver.Valuer); ok { |
||||
var value driver.Value |
||||
value, err = v.Value() |
||||
if err != nil { |
||||
return "?" |
||||
} |
||||
args = append(args, value) |
||||
} else { |
||||
args = append(args, fv) |
||||
} |
||||
return "?" |
||||
}) |
||||
if err != nil { |
||||
return "", []interface{}{}, err |
||||
} |
||||
return query, args, nil |
||||
} |
||||
|
||||
type cacheStruct struct { |
||||
value reflect.Value |
||||
idx int |
||||
} |
||||
|
||||
type DB struct { |
||||
*sql.DB |
||||
Mapper IMapper |
||||
reflectCache map[reflect.Type]*cacheStruct |
||||
reflectCacheMutex sync.RWMutex |
||||
} |
||||
|
||||
func Open(driverName, dataSourceName string) (*DB, error) { |
||||
db, err := sql.Open(driverName, dataSourceName) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &DB{ |
||||
DB: db, |
||||
Mapper: NewCacheMapper(&SnakeMapper{}), |
||||
reflectCache: make(map[reflect.Type]*cacheStruct), |
||||
}, nil |
||||
} |
||||
|
||||
func FromDB(db *sql.DB) *DB { |
||||
return &DB{ |
||||
DB: db, |
||||
Mapper: NewCacheMapper(&SnakeMapper{}), |
||||
reflectCache: make(map[reflect.Type]*cacheStruct), |
||||
} |
||||
} |
||||
|
||||
func (db *DB) reflectNew(typ reflect.Type) reflect.Value { |
||||
db.reflectCacheMutex.Lock() |
||||
defer db.reflectCacheMutex.Unlock() |
||||
cs, ok := db.reflectCache[typ] |
||||
if !ok || cs.idx+1 > DefaultCacheSize-1 { |
||||
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0} |
||||
db.reflectCache[typ] = cs |
||||
} else { |
||||
cs.idx = cs.idx + 1 |
||||
} |
||||
return cs.value.Index(cs.idx).Addr() |
||||
} |
||||
|
||||
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { |
||||
rows, err := db.DB.Query(query, args...) |
||||
if err != nil { |
||||
if rows != nil { |
||||
rows.Close() |
||||
} |
||||
return nil, err |
||||
} |
||||
return &Rows{rows, db}, nil |
||||
} |
||||
|
||||
func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { |
||||
query, args, err := MapToSlice(query, mp) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return db.Query(query, args...) |
||||
} |
||||
|
||||
func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) { |
||||
query, args, err := StructToSlice(query, st) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return db.Query(query, args...) |
||||
} |
||||
|
||||
func (db *DB) QueryRow(query string, args ...interface{}) *Row { |
||||
rows, err := db.Query(query, args...) |
||||
if err != nil { |
||||
return &Row{nil, err} |
||||
} |
||||
return &Row{rows, nil} |
||||
} |
||||
|
||||
func (db *DB) QueryRowMap(query string, mp interface{}) *Row { |
||||
query, args, err := MapToSlice(query, mp) |
||||
if err != nil { |
||||
return &Row{nil, err} |
||||
} |
||||
return db.QueryRow(query, args...) |
||||
} |
||||
|
||||
func (db *DB) QueryRowStruct(query string, st interface{}) *Row { |
||||
query, args, err := StructToSlice(query, st) |
||||
if err != nil { |
||||
return &Row{nil, err} |
||||
} |
||||
return db.QueryRow(query, args...) |
||||
} |
||||
|
||||
type Stmt struct { |
||||
*sql.Stmt |
||||
db *DB |
||||
names map[string]int |
||||
} |
||||
|
||||
func (db *DB) Prepare(query string) (*Stmt, error) { |
||||
names := make(map[string]int) |
||||
var i int |
||||
query = re.ReplaceAllStringFunc(query, func(src string) string { |
||||
names[src[1:]] = i |
||||
i += 1 |
||||
return "?" |
||||
}) |
||||
|
||||
stmt, err := db.DB.Prepare(query) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &Stmt{stmt, db, names}, nil |
||||
} |
||||
|
||||
func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) { |
||||
vv := reflect.ValueOf(mp) |
||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { |
||||
return nil, errors.New("mp should be a map's pointer") |
||||
} |
||||
|
||||
args := make([]interface{}, len(s.names)) |
||||
for k, i := range s.names { |
||||
args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() |
||||
} |
||||
return s.Stmt.Exec(args...) |
||||
} |
||||
|
||||
func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) { |
||||
vv := reflect.ValueOf(st) |
||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { |
||||
return nil, errors.New("mp should be a map's pointer") |
||||
} |
||||
|
||||
args := make([]interface{}, len(s.names)) |
||||
for k, i := range s.names { |
||||
args[i] = vv.Elem().FieldByName(k).Interface() |
||||
} |
||||
return s.Stmt.Exec(args...) |
||||
} |
||||
|
||||
func (s *Stmt) Query(args ...interface{}) (*Rows, error) { |
||||
rows, err := s.Stmt.Query(args...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &Rows{rows, s.db}, nil |
||||
} |
||||
|
||||
func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) { |
||||
vv := reflect.ValueOf(mp) |
||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { |
||||
return nil, errors.New("mp should be a map's pointer") |
||||
} |
||||
|
||||
args := make([]interface{}, len(s.names)) |
||||
for k, i := range s.names { |
||||
args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() |
||||
} |
||||
|
||||
return s.Query(args...) |
||||
} |
||||
|
||||
func (s *Stmt) QueryStruct(st interface{}) (*Rows, error) { |
||||
vv := reflect.ValueOf(st) |
||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { |
||||
return nil, errors.New("mp should be a map's pointer") |
||||
} |
||||
|
||||
args := make([]interface{}, len(s.names)) |
||||
for k, i := range s.names { |
||||
args[i] = vv.Elem().FieldByName(k).Interface() |
||||
} |
||||
|
||||
return s.Query(args...) |
||||
} |
||||
|
||||
func (s *Stmt) QueryRow(args ...interface{}) *Row { |
||||
rows, err := s.Query(args...) |
||||
return &Row{rows, err} |
||||
} |
||||
|
||||
func (s *Stmt) QueryRowMap(mp interface{}) *Row { |
||||
vv := reflect.ValueOf(mp) |
||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { |
||||
return &Row{nil, errors.New("mp should be a map's pointer")} |
||||
} |
||||
|
||||
args := make([]interface{}, len(s.names)) |
||||
for k, i := range s.names { |
||||
args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() |
||||
} |
||||
|
||||
return s.QueryRow(args...) |
||||
} |
||||
|
||||
func (s *Stmt) QueryRowStruct(st interface{}) *Row { |
||||
vv := reflect.ValueOf(st) |
||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { |
||||
return &Row{nil, errors.New("st should be a struct's pointer")} |
||||
} |
||||
|
||||
args := make([]interface{}, len(s.names)) |
||||
for k, i := range s.names { |
||||
args[i] = vv.Elem().FieldByName(k).Interface() |
||||
} |
||||
|
||||
return s.QueryRow(args...) |
||||
} |
||||
|
||||
var ( |
||||
re = regexp.MustCompile(`[?](\w+)`) |
||||
) |
||||
|
||||
// insert into (name) values (?)
|
||||
// insert into (name) values (?name)
|
||||
func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) { |
||||
query, args, err := MapToSlice(query, mp) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return db.DB.Exec(query, args...) |
||||
} |
||||
|
||||
func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) { |
||||
query, args, err := StructToSlice(query, st) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return db.DB.Exec(query, args...) |
||||
} |
||||
|
||||
type EmptyScanner struct { |
||||
} |
||||
|
||||
func (EmptyScanner) Scan(src interface{}) error { |
||||
return nil |
||||
} |
||||
|
||||
type Tx struct { |
||||
*sql.Tx |
||||
db *DB |
||||
} |
||||
|
||||
func (db *DB) Begin() (*Tx, error) { |
||||
tx, err := db.DB.Begin() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &Tx{tx, db}, nil |
||||
} |
||||
|
||||
func (tx *Tx) Prepare(query string) (*Stmt, error) { |
||||
names := make(map[string]int) |
||||
var i int |
||||
query = re.ReplaceAllStringFunc(query, func(src string) string { |
||||
names[src[1:]] = i |
||||
i += 1 |
||||
return "?" |
||||
}) |
||||
|
||||
stmt, err := tx.Tx.Prepare(query) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &Stmt{stmt, tx.db, names}, nil |
||||
} |
||||
|
||||
func (tx *Tx) Stmt(stmt *Stmt) *Stmt { |
||||
// TODO:
|
||||
return stmt |
||||
} |
||||
|
||||
func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) { |
||||
query, args, err := MapToSlice(query, mp) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return tx.Tx.Exec(query, args...) |
||||
} |
||||
|
||||
func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { |
||||
query, args, err := StructToSlice(query, st) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return tx.Tx.Exec(query, args...) |
||||
} |
||||
|
||||
func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { |
||||
rows, err := tx.Tx.Query(query, args...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &Rows{rows, tx.db}, nil |
||||
} |
||||
|
||||
func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) { |
||||
query, args, err := MapToSlice(query, mp) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return tx.Query(query, args...) |
||||
} |
||||
|
||||
func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) { |
||||
query, args, err := StructToSlice(query, st) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return tx.Query(query, args...) |
||||
} |
||||
|
||||
func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { |
||||
rows, err := tx.Query(query, args...) |
||||
return &Row{rows, err} |
||||
} |
||||
|
||||
func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row { |
||||
query, args, err := MapToSlice(query, mp) |
||||
if err != nil { |
||||
return &Row{nil, err} |
||||
} |
||||
return tx.QueryRow(query, args...) |
||||
} |
||||
|
||||
func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row { |
||||
query, args, err := StructToSlice(query, st) |
||||
if err != nil { |
||||
return &Row{nil, err} |
||||
} |
||||
return tx.QueryRow(query, args...) |
||||
} |
@ -1 +0,0 @@ |
||||
module "github.com/go-xorm/core" |
@ -1,41 +0,0 @@ |
||||
dependencies: |
||||
override: |
||||
# './...' is a relative pattern which means all subdirectories |
||||
- go get -t -d -v ./... |
||||
- go get -t -d -v github.com/go-xorm/tests |
||||
- go get -u github.com/go-xorm/core |
||||
- go get -u github.com/go-xorm/builder |
||||
- go build -v |
||||
|
||||
database: |
||||
override: |
||||
- mysql -u root -e "CREATE DATABASE xorm_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" |
||||
- mysql -u root -e "CREATE DATABASE xorm_test1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" |
||||
- mysql -u root -e "CREATE DATABASE xorm_test2 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" |
||||
- mysql -u root -e "CREATE DATABASE xorm_test3 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" |
||||
- createdb -p 5432 -e -U postgres xorm_test |
||||
- createdb -p 5432 -e -U postgres xorm_test1 |
||||
- createdb -p 5432 -e -U postgres xorm_test2 |
||||
- createdb -p 5432 -e -U postgres xorm_test3 |
||||
- psql xorm_test postgres -c "create schema xorm" |
||||
|
||||
test: |
||||
override: |
||||
# './...' is a relative pattern which means all subdirectories |
||||
- go get -u github.com/wadey/gocovmerge |
||||
- go test -v -race -db="sqlite3" -conn_str="./test.db" -coverprofile=coverage1-1.txt -covermode=atomic |
||||
- go test -v -race -db="sqlite3" -conn_str="./test.db" -cache=true -coverprofile=coverage1-2.txt -covermode=atomic |
||||
- go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -coverprofile=coverage2-1.txt -covermode=atomic |
||||
- go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -cache=true -coverprofile=coverage2-2.txt -covermode=atomic |
||||
- go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -coverprofile=coverage3-1.txt -covermode=atomic |
||||
- go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -cache=true -coverprofile=coverage3-2.txt -covermode=atomic |
||||
- go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -coverprofile=coverage4-1.txt -covermode=atomic |
||||
- go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic |
||||
- go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic |
||||
- go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic |
||||
- gocovmerge coverage1-1.txt coverage1-2.txt coverage2-1.txt coverage2-2.txt coverage3-1.txt coverage3-2.txt coverage4-1.txt coverage4-2.txt coverage5-1.txt coverage5-2.txt > coverage.txt |
||||
- cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./sqlite3.sh |
||||
- cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./mysql.sh |
||||
- cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./postgres.sh |
||||
post: |
||||
- bash <(curl -s https://codecov.io/bash) |
@ -0,0 +1,28 @@ |
||||
// Copyright 2019 The Xorm Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package xorm |
||||
|
||||
import "context" |
||||
|
||||
// Context creates a session with the context
|
||||
func (engine *Engine) Context(ctx context.Context) *Session { |
||||
session := engine.NewSession() |
||||
session.isAutoClose = true |
||||
return session.Context(ctx) |
||||
} |
||||
|
||||
// SetDefaultContext set the default context
|
||||
func (engine *Engine) SetDefaultContext(ctx context.Context) { |
||||
engine.defaultContext = ctx |
||||
} |
||||
|
||||
// PingContext tests if database is alive
|
||||
func (engine *Engine) PingContext(ctx context.Context) error { |
||||
session := engine.NewSession() |
||||
defer session.Close() |
||||
return session.PingContext(ctx) |
||||
} |
@ -1,24 +1,24 @@ |
||||
module github.com/go-xorm/xorm |
||||
|
||||
require ( |
||||
cloud.google.com/go v0.34.0 // indirect |
||||
github.com/cockroachdb/apd v1.1.0 // indirect |
||||
github.com/davecgh/go-spew v1.1.1 // indirect |
||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f |
||||
github.com/go-sql-driver/mysql v1.4.0 |
||||
github.com/go-xorm/builder v0.3.2 |
||||
github.com/go-xorm/core v0.6.0 |
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a // indirect |
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952 |
||||
github.com/go-sql-driver/mysql v1.4.1 |
||||
github.com/google/go-cmp v0.2.0 // indirect |
||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect |
||||
github.com/jackc/pgx v3.2.0+incompatible |
||||
github.com/jackc/pgx v3.3.0+incompatible |
||||
github.com/kr/pretty v0.1.0 // indirect |
||||
github.com/lib/pq v1.0.0 |
||||
github.com/mattn/go-sqlite3 v1.9.0 |
||||
github.com/pkg/errors v0.8.0 // indirect |
||||
github.com/pmezard/go-difflib v1.0.0 // indirect |
||||
github.com/mattn/go-sqlite3 v1.10.0 |
||||
github.com/pkg/errors v0.8.1 // indirect |
||||
github.com/satori/go.uuid v1.2.0 // indirect |
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect |
||||
github.com/stretchr/testify v1.2.2 |
||||
github.com/stretchr/testify v1.3.0 |
||||
github.com/ziutek/mymysql v1.5.4 |
||||
golang.org/x/crypto v0.0.0-20190122013713-64072686203f // indirect |
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect |
||||
gopkg.in/stretchr/testify.v1 v1.2.2 |
||||
xorm.io/builder v0.3.5 |
||||
xorm.io/core v0.6.3 |
||||
) |
||||
|
@ -0,0 +1,31 @@ |
||||
// Copyright 2019 The Xorm Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xorm |
||||
|
||||
import "encoding/json" |
||||
|
||||
// JSONInterface represents an interface to handle json data
|
||||
type JSONInterface interface { |
||||
Marshal(v interface{}) ([]byte, error) |
||||
Unmarshal(data []byte, v interface{}) error |
||||
} |
||||
|
||||
var ( |
||||
// DefaultJSONHandler default json handler
|
||||
DefaultJSONHandler JSONInterface = StdJSON{} |
||||
) |
||||
|
||||
// StdJSON implements JSONInterface via encoding/json
|
||||
type StdJSON struct{} |
||||
|
||||
// Marshal implements JSONInterface
|
||||
func (StdJSON) Marshal(v interface{}) ([]byte, error) { |
||||
return json.Marshal(v) |
||||
} |
||||
|
||||
// Unmarshal implements JSONInterface
|
||||
func (StdJSON) Unmarshal(data []byte, v interface{}) error { |
||||
return json.Unmarshal(data, v) |
||||
} |
@ -1,18 +1,15 @@ |
||||
// Copyright 2017 The Xorm Authors. All rights reserved.
|
||||
// Copyright 2019 The Xorm Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package xorm |
||||
|
||||
import "context" |
||||
|
||||
// PingContext tests if database is alive
|
||||
func (engine *Engine) PingContext(ctx context.Context) error { |
||||
session := engine.NewSession() |
||||
defer session.Close() |
||||
return session.PingContext(ctx) |
||||
// Context sets the context on this session
|
||||
func (session *Session) Context(ctx context.Context) *Session { |
||||
session.ctx = ctx |
||||
return session |
||||
} |
||||
|
||||
// PingContext test if database is ok
|
@ -1 +1 @@ |
||||
go test -db=mssql -conn_str="server=192.168.1.58;user id=sa;password=123456;database=xorm_test" |
||||
go test -db=mssql -conn_str="server=localhost;user id=sa;password=yourStrong(!)Password;database=xorm_test" |
@ -0,0 +1 @@ |
||||
go test -db=mysql -conn_str="root:@tcp(localhost:4000)/xorm_test" -ignore_select_update=true |
@ -1,682 +0,0 @@ |
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
// +build !go1.7
|
||||
|
||||
package internal |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"log" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"runtime" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"github.com/golang/protobuf/proto" |
||||
netcontext "golang.org/x/net/context" |
||||
|
||||
basepb "google.golang.org/appengine/internal/base" |
||||
logpb "google.golang.org/appengine/internal/log" |
||||
remotepb "google.golang.org/appengine/internal/remote_api" |
||||
) |
||||
|
||||
const ( |
||||
apiPath = "/rpc_http" |
||||
defaultTicketSuffix = "/default.20150612t184001.0" |
||||
) |
||||
|
||||
var ( |
||||
// Incoming headers.
|
||||
ticketHeader = http.CanonicalHeaderKey("X-AppEngine-API-Ticket") |
||||
dapperHeader = http.CanonicalHeaderKey("X-Google-DapperTraceInfo") |
||||
traceHeader = http.CanonicalHeaderKey("X-Cloud-Trace-Context") |
||||
curNamespaceHeader = http.CanonicalHeaderKey("X-AppEngine-Current-Namespace") |
||||
userIPHeader = http.CanonicalHeaderKey("X-AppEngine-User-IP") |
||||
remoteAddrHeader = http.CanonicalHeaderKey("X-AppEngine-Remote-Addr") |
||||
|
||||
// Outgoing headers.
|
||||
apiEndpointHeader = http.CanonicalHeaderKey("X-Google-RPC-Service-Endpoint") |
||||
apiEndpointHeaderValue = []string{"app-engine-apis"} |
||||
apiMethodHeader = http.CanonicalHeaderKey("X-Google-RPC-Service-Method") |
||||
apiMethodHeaderValue = []string{"/VMRemoteAPI.CallRemoteAPI"} |
||||
apiDeadlineHeader = http.CanonicalHeaderKey("X-Google-RPC-Service-Deadline") |
||||
apiContentType = http.CanonicalHeaderKey("Content-Type") |
||||
apiContentTypeValue = []string{"application/octet-stream"} |
||||
logFlushHeader = http.CanonicalHeaderKey("X-AppEngine-Log-Flush-Count") |
||||
|
||||
apiHTTPClient = &http.Client{ |
||||
Transport: &http.Transport{ |
||||
Proxy: http.ProxyFromEnvironment, |
||||
Dial: limitDial, |
||||
}, |
||||
} |
||||
|
||||
defaultTicketOnce sync.Once |
||||
defaultTicket string |
||||
) |
||||
|
||||
func apiURL() *url.URL { |
||||
host, port := "appengine.googleapis.internal", "10001" |
||||
if h := os.Getenv("API_HOST"); h != "" { |
||||
host = h |
||||
} |
||||
if p := os.Getenv("API_PORT"); p != "" { |
||||
port = p |
||||
} |
||||
return &url.URL{ |
||||
Scheme: "http", |
||||
Host: host + ":" + port, |
||||
Path: apiPath, |
||||
} |
||||
} |
||||
|
||||
func handleHTTP(w http.ResponseWriter, r *http.Request) { |
||||
c := &context{ |
||||
req: r, |
||||
outHeader: w.Header(), |
||||
apiURL: apiURL(), |
||||
} |
||||
stopFlushing := make(chan int) |
||||
|
||||
ctxs.Lock() |
||||
ctxs.m[r] = c |
||||
ctxs.Unlock() |
||||
defer func() { |
||||
ctxs.Lock() |
||||
delete(ctxs.m, r) |
||||
ctxs.Unlock() |
||||
}() |
||||
|
||||
// Patch up RemoteAddr so it looks reasonable.
|
||||
if addr := r.Header.Get(userIPHeader); addr != "" { |
||||
r.RemoteAddr = addr |
||||
} else if addr = r.Header.Get(remoteAddrHeader); addr != "" { |
||||
r.RemoteAddr = addr |
||||
} else { |
||||
// Should not normally reach here, but pick a sensible default anyway.
|
||||
r.RemoteAddr = "127.0.0.1" |
||||
} |
||||
// The address in the headers will most likely be of these forms:
|
||||
// 123.123.123.123
|
||||
// 2001:db8::1
|
||||
// net/http.Request.RemoteAddr is specified to be in "IP:port" form.
|
||||
if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil { |
||||
// Assume the remote address is only a host; add a default port.
|
||||
r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80") |
||||
} |
||||
|
||||
// Start goroutine responsible for flushing app logs.
|
||||
// This is done after adding c to ctx.m (and stopped before removing it)
|
||||
// because flushing logs requires making an API call.
|
||||
go c.logFlusher(stopFlushing) |
||||
|
||||
executeRequestSafely(c, r) |
||||
c.outHeader = nil // make sure header changes aren't respected any more
|
||||
|
||||
stopFlushing <- 1 // any logging beyond this point will be dropped
|
||||
|
||||
// Flush any pending logs asynchronously.
|
||||
c.pendingLogs.Lock() |
||||
flushes := c.pendingLogs.flushes |
||||
if len(c.pendingLogs.lines) > 0 { |
||||
flushes++ |
||||
} |
||||
c.pendingLogs.Unlock() |
||||
go c.flushLog(false) |
||||
w.Header().Set(logFlushHeader, strconv.Itoa(flushes)) |
||||
|
||||
// Avoid nil Write call if c.Write is never called.
|
||||
if c.outCode != 0 { |
||||
w.WriteHeader(c.outCode) |
||||
} |
||||
if c.outBody != nil { |
||||
w.Write(c.outBody) |
||||
} |
||||
} |
||||
|
||||
func executeRequestSafely(c *context, r *http.Request) { |
||||
defer func() { |
||||
if x := recover(); x != nil { |
||||
logf(c, 4, "%s", renderPanic(x)) // 4 == critical
|
||||
c.outCode = 500 |
||||
} |
||||
}() |
||||
|
||||
http.DefaultServeMux.ServeHTTP(c, r) |
||||
} |
||||
|
||||
func renderPanic(x interface{}) string { |
||||
buf := make([]byte, 16<<10) // 16 KB should be plenty
|
||||
buf = buf[:runtime.Stack(buf, false)] |
||||
|
||||
// Remove the first few stack frames:
|
||||
// this func
|
||||
// the recover closure in the caller
|
||||
// That will root the stack trace at the site of the panic.
|
||||
const ( |
||||
skipStart = "internal.renderPanic" |
||||
skipFrames = 2 |
||||
) |
||||
start := bytes.Index(buf, []byte(skipStart)) |
||||
p := start |
||||
for i := 0; i < skipFrames*2 && p+1 < len(buf); i++ { |
||||
p = bytes.IndexByte(buf[p+1:], '\n') + p + 1 |
||||
if p < 0 { |
||||
break |
||||
} |
||||
} |
||||
if p >= 0 { |
||||
// buf[start:p+1] is the block to remove.
|
||||
// Copy buf[p+1:] over buf[start:] and shrink buf.
|
||||
copy(buf[start:], buf[p+1:]) |
||||
buf = buf[:len(buf)-(p+1-start)] |
||||
} |
||||
|
||||
// Add panic heading.
|
||||
head := fmt.Sprintf("panic: %v\n\n", x) |
||||
if len(head) > len(buf) { |
||||
// Extremely unlikely to happen.
|
||||
return head |
||||
} |
||||
copy(buf[len(head):], buf) |
||||
copy(buf, head) |
||||
|
||||
return string(buf) |
||||
} |
||||
|
||||
var ctxs = struct { |
||||
sync.Mutex |
||||
m map[*http.Request]*context |
||||
bg *context // background context, lazily initialized
|
||||
// dec is used by tests to decorate the netcontext.Context returned
|
||||
// for a given request. This allows tests to add overrides (such as
|
||||
// WithAppIDOverride) to the context. The map is nil outside tests.
|
||||
dec map[*http.Request]func(netcontext.Context) netcontext.Context |
||||
}{ |
||||
m: make(map[*http.Request]*context), |
||||
} |
||||
|
||||
// context represents the context of an in-flight HTTP request.
|
||||
// It implements the appengine.Context and http.ResponseWriter interfaces.
|
||||
type context struct { |
||||
req *http.Request |
||||
|
||||
outCode int |
||||
outHeader http.Header |
||||
outBody []byte |
||||
|
||||
pendingLogs struct { |
||||
sync.Mutex |
||||
lines []*logpb.UserAppLogLine |
||||
flushes int |
||||
} |
||||
|
||||
apiURL *url.URL |
||||
} |
||||
|
||||
var contextKey = "holds a *context" |
||||
|
||||
// fromContext returns the App Engine context or nil if ctx is not
|
||||
// derived from an App Engine context.
|
||||
func fromContext(ctx netcontext.Context) *context { |
||||
c, _ := ctx.Value(&contextKey).(*context) |
||||
return c |
||||
} |
||||
|
||||
func withContext(parent netcontext.Context, c *context) netcontext.Context { |
||||
ctx := netcontext.WithValue(parent, &contextKey, c) |
||||
if ns := c.req.Header.Get(curNamespaceHeader); ns != "" { |
||||
ctx = withNamespace(ctx, ns) |
||||
} |
||||
return ctx |
||||
} |
||||
|
||||
func toContext(c *context) netcontext.Context { |
||||
return withContext(netcontext.Background(), c) |
||||
} |
||||
|
||||
func IncomingHeaders(ctx netcontext.Context) http.Header { |
||||
if c := fromContext(ctx); c != nil { |
||||
return c.req.Header |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func ReqContext(req *http.Request) netcontext.Context { |
||||
return WithContext(netcontext.Background(), req) |
||||
} |
||||
|
||||
func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context { |
||||
ctxs.Lock() |
||||
c := ctxs.m[req] |
||||
d := ctxs.dec[req] |
||||
ctxs.Unlock() |
||||
|
||||
if d != nil { |
||||
parent = d(parent) |
||||
} |
||||
|
||||
if c == nil { |
||||
// Someone passed in an http.Request that is not in-flight.
|
||||
// We panic here rather than panicking at a later point
|
||||
// so that stack traces will be more sensible.
|
||||
log.Panic("appengine: NewContext passed an unknown http.Request") |
||||
} |
||||
return withContext(parent, c) |
||||
} |
||||
|
||||
// DefaultTicket returns a ticket used for background context or dev_appserver.
|
||||
func DefaultTicket() string { |
||||
defaultTicketOnce.Do(func() { |
||||
if IsDevAppServer() { |
||||
defaultTicket = "testapp" + defaultTicketSuffix |
||||
return |
||||
} |
||||
appID := partitionlessAppID() |
||||
escAppID := strings.Replace(strings.Replace(appID, ":", "_", -1), ".", "_", -1) |
||||
majVersion := VersionID(nil) |
||||
if i := strings.Index(majVersion, "."); i > 0 { |
||||
majVersion = majVersion[:i] |
||||
} |
||||
defaultTicket = fmt.Sprintf("%s/%s.%s.%s", escAppID, ModuleName(nil), majVersion, InstanceID()) |
||||
}) |
||||
return defaultTicket |
||||
} |
||||
|
||||
func BackgroundContext() netcontext.Context { |
||||
ctxs.Lock() |
||||
defer ctxs.Unlock() |
||||
|
||||
if ctxs.bg != nil { |
||||
return toContext(ctxs.bg) |
||||
} |
||||
|
||||
// Compute background security ticket.
|
||||
ticket := DefaultTicket() |
||||
|
||||
ctxs.bg = &context{ |
||||
req: &http.Request{ |
||||
Header: http.Header{ |
||||
ticketHeader: []string{ticket}, |
||||
}, |
||||
}, |
||||
apiURL: apiURL(), |
||||
} |
||||
|
||||
// TODO(dsymonds): Wire up the shutdown handler to do a final flush.
|
||||
go ctxs.bg.logFlusher(make(chan int)) |
||||
|
||||
return toContext(ctxs.bg) |
||||
} |
||||
|
||||
// RegisterTestRequest registers the HTTP request req for testing, such that
|
||||
// any API calls are sent to the provided URL. It returns a closure to delete
|
||||
// the registration.
|
||||
// It should only be used by aetest package.
|
||||
func RegisterTestRequest(req *http.Request, apiURL *url.URL, decorate func(netcontext.Context) netcontext.Context) (*http.Request, func()) { |
||||
c := &context{ |
||||
req: req, |
||||
apiURL: apiURL, |
||||
} |
||||
ctxs.Lock() |
||||
defer ctxs.Unlock() |
||||
if _, ok := ctxs.m[req]; ok { |
||||
log.Panic("req already associated with context") |
||||
} |
||||
if _, ok := ctxs.dec[req]; ok { |
||||
log.Panic("req already associated with context") |
||||
} |
||||
if ctxs.dec == nil { |
||||
ctxs.dec = make(map[*http.Request]func(netcontext.Context) netcontext.Context) |
||||
} |
||||
ctxs.m[req] = c |
||||
ctxs.dec[req] = decorate |
||||
|
||||
return req, func() { |
||||
ctxs.Lock() |
||||
delete(ctxs.m, req) |
||||
delete(ctxs.dec, req) |
||||
ctxs.Unlock() |
||||
} |
||||
} |
||||
|
||||
var errTimeout = &CallError{ |
||||
Detail: "Deadline exceeded", |
||||
Code: int32(remotepb.RpcError_CANCELLED), |
||||
Timeout: true, |
||||
} |
||||
|
||||
func (c *context) Header() http.Header { return c.outHeader } |
||||
|
||||
// Copied from $GOROOT/src/pkg/net/http/transfer.go. Some response status
|
||||
// codes do not permit a response body (nor response entity headers such as
|
||||
// Content-Length, Content-Type, etc).
|
||||
func bodyAllowedForStatus(status int) bool { |
||||
switch { |
||||
case status >= 100 && status <= 199: |
||||
return false |
||||
case status == 204: |
||||
return false |
||||
case status == 304: |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func (c *context) Write(b []byte) (int, error) { |
||||
if c.outCode == 0 { |
||||
c.WriteHeader(http.StatusOK) |
||||
} |
||||
if len(b) > 0 && !bodyAllowedForStatus(c.outCode) { |
||||
return 0, http.ErrBodyNotAllowed |
||||
} |
||||
c.outBody = append(c.outBody, b...) |
||||
return len(b), nil |
||||
} |
||||
|
||||
func (c *context) WriteHeader(code int) { |
||||
if c.outCode != 0 { |
||||
logf(c, 3, "WriteHeader called multiple times on request.") // error level
|
||||
return |
||||
} |
||||
c.outCode = code |
||||
} |
||||
|
||||
func (c *context) post(body []byte, timeout time.Duration) (b []byte, err error) { |
||||
hreq := &http.Request{ |
||||
Method: "POST", |
||||
URL: c.apiURL, |
||||
Header: http.Header{ |
||||
apiEndpointHeader: apiEndpointHeaderValue, |
||||
apiMethodHeader: apiMethodHeaderValue, |
||||
apiContentType: apiContentTypeValue, |
||||
apiDeadlineHeader: []string{strconv.FormatFloat(timeout.Seconds(), 'f', -1, 64)}, |
||||
}, |
||||
Body: ioutil.NopCloser(bytes.NewReader(body)), |
||||
ContentLength: int64(len(body)), |
||||
Host: c.apiURL.Host, |
||||
} |
||||
if info := c.req.Header.Get(dapperHeader); info != "" { |
||||
hreq.Header.Set(dapperHeader, info) |
||||
} |
||||
if info := c.req.Header.Get(traceHeader); info != "" { |
||||
hreq.Header.Set(traceHeader, info) |
||||
} |
||||
|
||||
tr := apiHTTPClient.Transport.(*http.Transport) |
||||
|
||||
var timedOut int32 // atomic; set to 1 if timed out
|
||||
t := time.AfterFunc(timeout, func() { |
||||
atomic.StoreInt32(&timedOut, 1) |
||||
tr.CancelRequest(hreq) |
||||
}) |
||||
defer t.Stop() |
||||
defer func() { |
||||
// Check if timeout was exceeded.
|
||||
if atomic.LoadInt32(&timedOut) != 0 { |
||||
err = errTimeout |
||||
} |
||||
}() |
||||
|
||||
hresp, err := apiHTTPClient.Do(hreq) |
||||
if err != nil { |
||||
return nil, &CallError{ |
||||
Detail: fmt.Sprintf("service bridge HTTP failed: %v", err), |
||||
Code: int32(remotepb.RpcError_UNKNOWN), |
||||
} |
||||
} |
||||
defer hresp.Body.Close() |
||||
hrespBody, err := ioutil.ReadAll(hresp.Body) |
||||
if hresp.StatusCode != 200 { |
||||
return nil, &CallError{ |
||||
Detail: fmt.Sprintf("service bridge returned HTTP %d (%q)", hresp.StatusCode, hrespBody), |
||||
Code: int32(remotepb.RpcError_UNKNOWN), |
||||
} |
||||
} |
||||
if err != nil { |
||||
return nil, &CallError{ |
||||
Detail: fmt.Sprintf("service bridge response bad: %v", err), |
||||
Code: int32(remotepb.RpcError_UNKNOWN), |
||||
} |
||||
} |
||||
return hrespBody, nil |
||||
} |
||||
|
||||
func Call(ctx netcontext.Context, service, method string, in, out proto.Message) error { |
||||
if ns := NamespaceFromContext(ctx); ns != "" { |
||||
if fn, ok := NamespaceMods[service]; ok { |
||||
fn(in, ns) |
||||
} |
||||
} |
||||
|
||||
if f, ctx, ok := callOverrideFromContext(ctx); ok { |
||||
return f(ctx, service, method, in, out) |
||||
} |
||||
|
||||
// Handle already-done contexts quickly.
|
||||
select { |
||||
case <-ctx.Done(): |
||||
return ctx.Err() |
||||
default: |
||||
} |
||||
|
||||
c := fromContext(ctx) |
||||
if c == nil { |
||||
// Give a good error message rather than a panic lower down.
|
||||
return errNotAppEngineContext |
||||
} |
||||
|
||||
// Apply transaction modifications if we're in a transaction.
|
||||
if t := transactionFromContext(ctx); t != nil { |
||||
if t.finished { |
||||
return errors.New("transaction context has expired") |
||||
} |
||||
applyTransaction(in, &t.transaction) |
||||
} |
||||
|
||||
// Default RPC timeout is 60s.
|
||||
timeout := 60 * time.Second |
||||
if deadline, ok := ctx.Deadline(); ok { |
||||
timeout = deadline.Sub(time.Now()) |
||||
} |
||||
|
||||
data, err := proto.Marshal(in) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
ticket := c.req.Header.Get(ticketHeader) |
||||
// Use a test ticket under test environment.
|
||||
if ticket == "" { |
||||
if appid := ctx.Value(&appIDOverrideKey); appid != nil { |
||||
ticket = appid.(string) + defaultTicketSuffix |
||||
} |
||||
} |
||||
// Fall back to use background ticket when the request ticket is not available in Flex or dev_appserver.
|
||||
if ticket == "" { |
||||
ticket = DefaultTicket() |
||||
} |
||||
req := &remotepb.Request{ |
||||
ServiceName: &service, |
||||
Method: &method, |
||||
Request: data, |
||||
RequestId: &ticket, |
||||
} |
||||
hreqBody, err := proto.Marshal(req) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
hrespBody, err := c.post(hreqBody, timeout) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
res := &remotepb.Response{} |
||||
if err := proto.Unmarshal(hrespBody, res); err != nil { |
||||
return err |
||||
} |
||||
if res.RpcError != nil { |
||||
ce := &CallError{ |
||||
Detail: res.RpcError.GetDetail(), |
||||
Code: *res.RpcError.Code, |
||||
} |
||||
switch remotepb.RpcError_ErrorCode(ce.Code) { |
||||
case remotepb.RpcError_CANCELLED, remotepb.RpcError_DEADLINE_EXCEEDED: |
||||
ce.Timeout = true |
||||
} |
||||
return ce |
||||
} |
||||
if res.ApplicationError != nil { |
||||
return &APIError{ |
||||
Service: *req.ServiceName, |
||||
Detail: res.ApplicationError.GetDetail(), |
||||
Code: *res.ApplicationError.Code, |
||||
} |
||||
} |
||||
if res.Exception != nil || res.JavaException != nil { |
||||
// This shouldn't happen, but let's be defensive.
|
||||
return &CallError{ |
||||
Detail: "service bridge returned exception", |
||||
Code: int32(remotepb.RpcError_UNKNOWN), |
||||
} |
||||
} |
||||
return proto.Unmarshal(res.Response, out) |
||||
} |
||||
|
||||
func (c *context) Request() *http.Request { |
||||
return c.req |
||||
} |
||||
|
||||
func (c *context) addLogLine(ll *logpb.UserAppLogLine) { |
||||
// Truncate long log lines.
|
||||
// TODO(dsymonds): Check if this is still necessary.
|
||||
const lim = 8 << 10 |
||||
if len(*ll.Message) > lim { |
||||
suffix := fmt.Sprintf("...(length %d)", len(*ll.Message)) |
||||
ll.Message = proto.String((*ll.Message)[:lim-len(suffix)] + suffix) |
||||
} |
||||
|
||||
c.pendingLogs.Lock() |
||||
c.pendingLogs.lines = append(c.pendingLogs.lines, ll) |
||||
c.pendingLogs.Unlock() |
||||
} |
||||
|
||||
var logLevelName = map[int64]string{ |
||||
0: "DEBUG", |
||||
1: "INFO", |
||||
2: "WARNING", |
||||
3: "ERROR", |
||||
4: "CRITICAL", |
||||
} |
||||
|
||||
func logf(c *context, level int64, format string, args ...interface{}) { |
||||
if c == nil { |
||||
panic("not an App Engine context") |
||||
} |
||||
s := fmt.Sprintf(format, args...) |
||||
s = strings.TrimRight(s, "\n") // Remove any trailing newline characters.
|
||||
c.addLogLine(&logpb.UserAppLogLine{ |
||||
TimestampUsec: proto.Int64(time.Now().UnixNano() / 1e3), |
||||
Level: &level, |
||||
Message: &s, |
||||
}) |
||||
log.Print(logLevelName[level] + ": " + s) |
||||
} |
||||
|
||||
// flushLog attempts to flush any pending logs to the appserver.
|
||||
// It should not be called concurrently.
|
||||
func (c *context) flushLog(force bool) (flushed bool) { |
||||
c.pendingLogs.Lock() |
||||
// Grab up to 30 MB. We can get away with up to 32 MB, but let's be cautious.
|
||||
n, rem := 0, 30<<20 |
||||
for ; n < len(c.pendingLogs.lines); n++ { |
||||
ll := c.pendingLogs.lines[n] |
||||
// Each log line will require about 3 bytes of overhead.
|
||||
nb := proto.Size(ll) + 3 |
||||
if nb > rem { |
||||
break |
||||
} |
||||
rem -= nb |
||||
} |
||||
lines := c.pendingLogs.lines[:n] |
||||
c.pendingLogs.lines = c.pendingLogs.lines[n:] |
||||
c.pendingLogs.Unlock() |
||||
|
||||
if len(lines) == 0 && !force { |
||||
// Nothing to flush.
|
||||
return false |
||||
} |
||||
|
||||
rescueLogs := false |
||||
defer func() { |
||||
if rescueLogs { |
||||
c.pendingLogs.Lock() |
||||
c.pendingLogs.lines = append(lines, c.pendingLogs.lines...) |
||||
c.pendingLogs.Unlock() |
||||
} |
||||
}() |
||||
|
||||
buf, err := proto.Marshal(&logpb.UserAppLogGroup{ |
||||
LogLine: lines, |
||||
}) |
||||
if err != nil { |
||||
log.Printf("internal.flushLog: marshaling UserAppLogGroup: %v", err) |
||||
rescueLogs = true |
||||
return false |
||||
} |
||||
|
||||
req := &logpb.FlushRequest{ |
||||
Logs: buf, |
||||
} |
||||
res := &basepb.VoidProto{} |
||||
c.pendingLogs.Lock() |
||||
c.pendingLogs.flushes++ |
||||
c.pendingLogs.Unlock() |
||||
if err := Call(toContext(c), "logservice", "Flush", req, res); err != nil { |
||||
log.Printf("internal.flushLog: Flush RPC: %v", err) |
||||
rescueLogs = true |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
const ( |
||||
// Log flushing parameters.
|
||||
flushInterval = 1 * time.Second |
||||
forceFlushInterval = 60 * time.Second |
||||
) |
||||
|
||||
func (c *context) logFlusher(stop <-chan int) { |
||||
lastFlush := time.Now() |
||||
tick := time.NewTicker(flushInterval) |
||||
for { |
||||
select { |
||||
case <-stop: |
||||
// Request finished.
|
||||
tick.Stop() |
||||
return |
||||
case <-tick.C: |
||||
force := time.Now().Sub(lastFlush) > forceFlushInterval |
||||
if c.flushLog(force) { |
||||
lastFlush = time.Now() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func ContextForTesting(req *http.Request) netcontext.Context { |
||||
return toContext(&context{req: req}) |
||||
} |
@ -0,0 +1,11 @@ |
||||
// Copyright 2018 Google LLC. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appenginevm
|
||||
|
||||
package internal |
||||
|
||||
func init() { |
||||
appengineFlex = true |
||||
} |
@ -0,0 +1,7 @@ |
||||
package internal |
||||
|
||||
// MainPath stores the file path of the main package. On App Engine Standard
|
||||
// using Go version 1.9 and below, this will be unset. On App Engine Flex and
|
||||
// App Engine Standard second-gen (Go 1.11 and above), this will be the
|
||||
// filepath to package main.
|
||||
var MainPath string |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue