* fix error log when loading issues caused by a xorm bug * upgrade packages * fix fmt * fix Consistency * fix testsfor-closed-social
@ -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 |