* fix error log when loading issues caused by a xorm bug * upgrade packages * fix fmt * fix Consistency * fix teststags/v1.21.12.1
| @@ -27,7 +27,7 @@ require ( | |||
| github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect | |||
| github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect | |||
| github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect | |||
| github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f | |||
| github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952 | |||
| github.com/dgrijalva/jwt-go v0.0.0-20161101193935-9ed569b5d1ac | |||
| github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect | |||
| github.com/emirpasic/gods v1.12.0 | |||
| @@ -54,10 +54,9 @@ require ( | |||
| github.com/go-macaron/session v0.0.0-20190131233854-0a0a789bf193 | |||
| github.com/go-macaron/toolbox v0.0.0-20180818072302-a77f45a7ce90 | |||
| github.com/go-redis/redis v6.15.2+incompatible | |||
| github.com/go-sql-driver/mysql v1.4.0 | |||
| github.com/go-xorm/builder v0.3.3 | |||
| github.com/go-xorm/core v0.6.0 | |||
| github.com/go-xorm/xorm v0.0.0-20190116032649-a6300f2a45e0 | |||
| github.com/go-sql-driver/mysql v1.4.1 | |||
| github.com/go-xorm/core v0.6.0 // indirect | |||
| github.com/go-xorm/xorm v0.7.3-0.20190620151208-f1b4f8368459 | |||
| github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561 | |||
| github.com/gogits/cron v0.0.0-20160810035002-7f3990acf183 | |||
| github.com/gogo/protobuf v1.2.1 // indirect | |||
| @@ -133,12 +132,12 @@ require ( | |||
| gopkg.in/redis.v2 v2.3.2 // indirect | |||
| gopkg.in/src-d/go-billy.v4 v4.3.0 | |||
| gopkg.in/src-d/go-git.v4 v4.12.0 | |||
| gopkg.in/stretchr/testify.v1 v1.2.2 // indirect | |||
| gopkg.in/testfixtures.v2 v2.5.0 | |||
| mvdan.cc/xurls/v2 v2.0.0 | |||
| strk.kbt.io/projects/go/libravatar v0.0.0-20160628055650-5eed7bff870a | |||
| xorm.io/builder v0.3.5 | |||
| xorm.io/core v0.6.3 | |||
| ) | |||
| replace ( | |||
| github.com/denisenkom/go-mssqldb => github.com/denisenkom/go-mssqldb v0.0.0-20161128230840-e32ca5036449 | |||
| github.com/go-sql-driver/mysql => github.com/go-sql-driver/mysql v0.0.0-20181218123637-c45f530f8e7f | |||
| ) | |||
| replace github.com/denisenkom/go-mssqldb => github.com/denisenkom/go-mssqldb v0.0.0-20161128230840-e32ca5036449 | |||
| @@ -1,4 +1,6 @@ | |||
| cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |||
| cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= | |||
| cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |||
| github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= | |||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | |||
| github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34 h1:UsHpWO0Elp6NaWVARdZHjiYwkhrspHVEGsyIKPb9OI8= | |||
| @@ -117,17 +119,14 @@ github.com/go-macaron/toolbox v0.0.0-20180818072302-a77f45a7ce90 h1:3wYKrRg9IjUM | |||
| github.com/go-macaron/toolbox v0.0.0-20180818072302-a77f45a7ce90/go.mod h1:Ut/NmkIMGVYlEdJBzEZgWVWG5ZpYS9BLmUgXfAgi+qM= | |||
| github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= | |||
| github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= | |||
| github.com/go-sql-driver/mysql v0.0.0-20181218123637-c45f530f8e7f h1:fbIzwEaXt5b2bl9mm+PIufKTSGKk6ZuwSSTQ7iZj7Lo= | |||
| github.com/go-sql-driver/mysql v0.0.0-20181218123637-c45f530f8e7f/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | |||
| github.com/go-xorm/builder v0.3.2/go.mod h1:v8mE3MFBgtL+RGFNfUnAMUqqfk/Y4W5KuwCFQIEpQLk= | |||
| github.com/go-xorm/builder v0.3.3 h1:v8grgrwOGv/iHXIEhIvOwHZIPLrpxRKSX8yWSMLFn/4= | |||
| github.com/go-xorm/builder v0.3.3/go.mod h1:v8mE3MFBgtL+RGFNfUnAMUqqfk/Y4W5KuwCFQIEpQLk= | |||
| github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= | |||
| github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | |||
| github.com/go-xorm/core v0.6.0 h1:tp6hX+ku4OD9khFZS8VGBDRY3kfVCtelPfmkgCyHxL0= | |||
| github.com/go-xorm/core v0.6.0/go.mod h1:d8FJ9Br8OGyQl12MCclmYBuBqqxsyeedpXciV5Myih8= | |||
| github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= | |||
| github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= | |||
| github.com/go-xorm/xorm v0.0.0-20190116032649-a6300f2a45e0 h1:GBnJjWjp2SGXBZsyZfYksyp7QocvQwf9vZQ0NRN2FXM= | |||
| github.com/go-xorm/xorm v0.0.0-20190116032649-a6300f2a45e0/go.mod h1:EHS1htMQFptzMaIHKyzqpHGw6C9Rtug75nsq6DA9unI= | |||
| github.com/go-xorm/xorm v0.7.3-0.20190620151208-f1b4f8368459 h1:JGEuhH169J7Wtm1hN/HFOGENsAq+6FDHfuhGEZj/1e4= | |||
| github.com/go-xorm/xorm v0.7.3-0.20190620151208-f1b4f8368459/go.mod h1:UK1YDlWscDspd23xW9HC24749jhvwO6riZ/HUt3gbHQ= | |||
| github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561 h1:deE7ritpK04PgtpyVOS2TYcQEld9qLCD5b5EbVNOuLA= | |||
| github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:YgYOrVn3Nj9Tq0EvjmFbphRytDj7JNRoWSStJZWDJTQ= | |||
| github.com/gogits/cron v0.0.0-20160810035002-7f3990acf183 h1:EBTlva3AOSb80G3JSwY6ZMdILEZJ1JKuewrbqrNjWuE= | |||
| @@ -170,8 +169,8 @@ github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c h1:A/PDn117UYld5m | |||
| github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c/go.mod h1:5mTb/PQNkqmq2x3IxlQZE0aSnTksJg7fg/oWmJ5SKXQ= | |||
| github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= | |||
| github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= | |||
| github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY= | |||
| github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= | |||
| github.com/jackc/pgx v3.3.0+incompatible h1:Wa90/+qsITBAPkAZjiByeIGHFcj3Ztu+VzrrIpHjL90= | |||
| github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= | |||
| github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= | |||
| github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d h1:ig/iUfDDg06RVW8OMby+GrmW6K2nPO3AFHlEIdvJSd4= | |||
| github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= | |||
| @@ -225,7 +224,6 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc | |||
| github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | |||
| github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d h1:m+dSK37rFf2fqppZhg15yI2IwC9BtucBiRwSDm9VL8g= | |||
| github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8= | |||
| github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= | |||
| github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= | |||
| github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= | |||
| github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | |||
| @@ -255,7 +253,6 @@ github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWo | |||
| github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= | |||
| github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= | |||
| github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= | |||
| github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |||
| github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | |||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |||
| @@ -328,6 +325,7 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK | |||
| go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= | |||
| go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= | |||
| golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | |||
| golang.org/x/crypto v0.0.0-20190122013713-64072686203f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | |||
| golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | |||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |||
| golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 h1:O5YqonU5IWby+w98jVUG9h7zlCWCcH4RHyPVReBmhzk= | |||
| @@ -376,6 +374,8 @@ golang.org/x/tools v0.0.0-20190618163018-fdf1049a943a/go.mod h1:/rFqwRUd4F7ZHNgw | |||
| google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | |||
| google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24= | |||
| google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | |||
| google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= | |||
| google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | |||
| gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= | |||
| gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= | |||
| gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 h1:nn6Zav2sOQHCFJHEspya8KqxhFwKci30UxHy3HXPTyQ= | |||
| @@ -419,3 +419,7 @@ mvdan.cc/xurls/v2 v2.0.0 h1:r1zSOSNS/kqtpmATyMMMvaZ4/djsesbYz5kr0+qMRWc= | |||
| mvdan.cc/xurls/v2 v2.0.0/go.mod h1:2/webFPYOXN9jp/lzuj0zuAVlF+9g4KPFJANH1oJhRU= | |||
| strk.kbt.io/projects/go/libravatar v0.0.0-20160628055650-5eed7bff870a h1:8q33ShxKXRwQ7JVd1ZnhIU3hZhwwn0Le+4fTeAackuM= | |||
| strk.kbt.io/projects/go/libravatar v0.0.0-20160628055650-5eed7bff870a/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY= | |||
| xorm.io/builder v0.3.5 h1:EilU39fvWDxjb1cDaELpYhsF+zziRBhew8xk4pngO+A= | |||
| xorm.io/builder v0.3.5/go.mod h1:ZFbByS/KxZI1FKRjL05PyJ4YrK2bcxlUaAxdum5aTR8= | |||
| xorm.io/core v0.6.3 h1:n1NhVZt1s2oLw1BZfX2ocIJsHyso259uPgg63BGr37M= | |||
| xorm.io/core v0.6.3/go.mod h1:8kz/C6arVW/O9vk3PgCiMJO2hIAm1UcuOL3dSPyZ2qo= | |||
| @@ -24,7 +24,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/builder" | |||
| "xorm.io/builder" | |||
| ) | |||
| // ActionType represents the type of an action. | |||
| @@ -39,7 +39,7 @@ func CheckConsistencyFor(t *testing.T, beansToCheck ...interface{}) { | |||
| ptrToSliceValue := reflect.New(sliceType) | |||
| ptrToSliceValue.Elem().Set(sliceValue) | |||
| assert.NoError(t, x.Where(bean).Find(ptrToSliceValue.Interface())) | |||
| assert.NoError(t, x.Table(bean).Find(ptrToSliceValue.Interface())) | |||
| sliceValue = ptrToSliceValue.Elem() | |||
| for i := 0; i < sliceValue.Len(); i++ { | |||
| @@ -19,8 +19,8 @@ import ( | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/builder" | |||
| ) | |||
| // Issue represents an issue or pull request of repository. | |||
| @@ -1428,6 +1428,7 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { | |||
| if err := sess.Find(&issues); err != nil { | |||
| return nil, fmt.Errorf("Find: %v", err) | |||
| } | |||
| sess.Close() | |||
| if err := IssueList(issues).LoadAttributes(); err != nil { | |||
| return nil, fmt.Errorf("LoadAttributes: %v", err) | |||
| @@ -15,8 +15,8 @@ import ( | |||
| "code.gitea.io/gitea/modules/markup/markdown" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/builder" | |||
| api "code.gitea.io/gitea/modules/structs" | |||
| @@ -7,9 +7,7 @@ package models | |||
| import ( | |||
| "fmt" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "github.com/go-xorm/builder" | |||
| "xorm.io/builder" | |||
| ) | |||
| // IssueList defines a list of issues | |||
| @@ -148,19 +146,17 @@ func (issues IssueList) loadLabels(e Engine) error { | |||
| var labelIssue LabelIssue | |||
| err = rows.Scan(&labelIssue) | |||
| if err != nil { | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadLabels: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadLabels: Close: %v", err1) | |||
| } | |||
| return err | |||
| } | |||
| issueLabels[labelIssue.IssueLabel.IssueID] = append(issueLabels[labelIssue.IssueLabel.IssueID], labelIssue.Label) | |||
| } | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // When there are no rows left and we try to close it. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadLabels: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadLabels: Close: %v", err1) | |||
| } | |||
| left -= limit | |||
| issueIDs = issueIDs[limit:] | |||
| @@ -241,20 +237,16 @@ func (issues IssueList) loadAssignees(e Engine) error { | |||
| var assigneeIssue AssigneeIssue | |||
| err = rows.Scan(&assigneeIssue) | |||
| if err != nil { | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadAssignees: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadAssignees: Close: %v", err1) | |||
| } | |||
| return err | |||
| } | |||
| assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee) | |||
| } | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadAssignees: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadAssignees: Close: %v", err1) | |||
| } | |||
| left -= limit | |||
| issueIDs = issueIDs[limit:] | |||
| @@ -300,19 +292,15 @@ func (issues IssueList) loadPullRequests(e Engine) error { | |||
| var pr PullRequest | |||
| err = rows.Scan(&pr) | |||
| if err != nil { | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadPullRequests: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadPullRequests: Close: %v", err1) | |||
| } | |||
| return err | |||
| } | |||
| pullRequestMaps[pr.IssueID] = &pr | |||
| } | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadPullRequests: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadPullRequests: Close: %v", err1) | |||
| } | |||
| left -= limit | |||
| issuesIDs = issuesIDs[limit:] | |||
| @@ -349,19 +337,15 @@ func (issues IssueList) loadAttachments(e Engine) (err error) { | |||
| var attachment Attachment | |||
| err = rows.Scan(&attachment) | |||
| if err != nil { | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadAttachments: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadAttachments: Close: %v", err1) | |||
| } | |||
| return err | |||
| } | |||
| attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment) | |||
| } | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadAttachments: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadAttachments: Close: %v", err1) | |||
| } | |||
| left -= limit | |||
| issuesIDs = issuesIDs[limit:] | |||
| @@ -399,19 +383,15 @@ func (issues IssueList) loadComments(e Engine, cond builder.Cond) (err error) { | |||
| var comment Comment | |||
| err = rows.Scan(&comment) | |||
| if err != nil { | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadComments: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadComments: Close: %v", err1) | |||
| } | |||
| return err | |||
| } | |||
| comments[comment.IssueID] = append(comments[comment.IssueID], &comment) | |||
| } | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadComments: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadComments: Close: %v", err1) | |||
| } | |||
| left -= limit | |||
| issuesIDs = issuesIDs[limit:] | |||
| @@ -461,19 +441,15 @@ func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) { | |||
| var totalTime totalTimesByIssue | |||
| err = rows.Scan(&totalTime) | |||
| if err != nil { | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadTotalTrackedTimes: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %v", err1) | |||
| } | |||
| return err | |||
| } | |||
| trackedTimes[totalTime.IssueID] = totalTime.Time | |||
| } | |||
| // When there are no rows left and we try to close it, xorm will complain with an error. | |||
| // Since that is not relevant for us, we can safely ignore it. | |||
| if err := rows.Close(); err != nil { | |||
| log.Error("IssueList.loadTotalTrackedTimes: Close: %v", err) | |||
| if err1 := rows.Close(); err1 != nil { | |||
| return fmt.Errorf("IssueList.loadTotalTrackedTimes: Close: %v", err1) | |||
| } | |||
| left -= limit | |||
| ids = ids[limit:] | |||
| @@ -11,8 +11,8 @@ import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/builder" | |||
| ) | |||
| // Reaction represents a reactions on issues and comments. | |||
| @@ -10,8 +10,8 @@ import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| api "code.gitea.io/gitea/modules/structs" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/builder" | |||
| ) | |||
| // TrackedTime represents a time that was spent for a specific issue. | |||
| @@ -9,7 +9,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/log" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // XORMLogBridge a logger bridge from Logger to xorm | |||
| @@ -15,8 +15,8 @@ import ( | |||
| "strings" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/core" | |||
| "code.gitea.io/gitea/modules/auth/ldap" | |||
| "code.gitea.io/gitea/modules/auth/oauth2" | |||
| @@ -8,8 +8,8 @@ import ( | |||
| "fmt" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/core" | |||
| ) | |||
| func addLoginSourceSyncEnabledColumn(x *xorm.Engine) error { | |||
| @@ -9,8 +9,8 @@ import ( | |||
| "code.gitea.io/gitea/models" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/core" | |||
| ) | |||
| func removeCommitsUnitType(x *xorm.Engine) (err error) { | |||
| @@ -5,8 +5,8 @@ | |||
| package migrations | |||
| import ( | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/builder" | |||
| ) | |||
| func clearNonusedData(x *xorm.Engine) error { | |||
| @@ -10,8 +10,8 @@ import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/core" | |||
| ) | |||
| func renameRepoIsBareToIsEmpty(x *xorm.Engine) error { | |||
| @@ -7,8 +7,8 @@ package migrations | |||
| import ( | |||
| "fmt" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/core" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/generate" | |||
| @@ -20,8 +20,8 @@ import ( | |||
| // Needed for the MySQL driver | |||
| _ "github.com/go-sql-driver/mysql" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/core" | |||
| // Needed for the Postgresql driver | |||
| _ "github.com/lib/pq" | |||
| @@ -15,8 +15,8 @@ import ( | |||
| "code.gitea.io/gitea/modules/structs" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/builder" | |||
| ) | |||
| var ( | |||
| @@ -16,7 +16,7 @@ import ( | |||
| api "code.gitea.io/gitea/modules/structs" | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/go-xorm/builder" | |||
| "xorm.io/builder" | |||
| ) | |||
| // Release represents a release of repository. | |||
| @@ -37,9 +37,9 @@ import ( | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/xorm" | |||
| ini "gopkg.in/ini.v1" | |||
| "xorm.io/builder" | |||
| ) | |||
| var repoWorkingPool = sync.NewExclusivePool() | |||
| @@ -11,7 +11,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/structs" | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/go-xorm/builder" | |||
| "xorm.io/builder" | |||
| ) | |||
| // RepositoryListDefaultPageSize is the default number of repositories | |||
| @@ -10,8 +10,8 @@ import ( | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/core" | |||
| ) | |||
| // RepoUnit describes all units of a repository | |||
| @@ -11,9 +11,9 @@ import ( | |||
| api "code.gitea.io/gitea/modules/structs" | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| // ReviewType defines the sort of feedback a review gives | |||
| @@ -25,9 +25,9 @@ import ( | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/xorm" | |||
| "golang.org/x/crypto/ssh" | |||
| "xorm.io/builder" | |||
| ) | |||
| const ( | |||
| @@ -11,7 +11,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/go-xorm/builder" | |||
| "xorm.io/builder" | |||
| ) | |||
| func init() { | |||
| @@ -18,10 +18,10 @@ import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "github.com/stretchr/testify/assert" | |||
| "gopkg.in/testfixtures.v2" | |||
| "xorm.io/core" | |||
| ) | |||
| // NonexistentID an ID that will never exist | |||
| @@ -32,11 +32,11 @@ import ( | |||
| "code.gitea.io/gitea/modules/util" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "github.com/go-xorm/xorm" | |||
| "golang.org/x/crypto/pbkdf2" | |||
| "golang.org/x/crypto/ssh" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| // UserType defines the user type | |||
| @@ -17,7 +17,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| const ( | |||
| @@ -1,10 +1,10 @@ | |||
| sudo: false | |||
| language: go | |||
| go: | |||
| - 1.7.x | |||
| - 1.8.x | |||
| - 1.9.x | |||
| - 1.10.x | |||
| - 1.11.x | |||
| - master | |||
| before_install: | |||
| @@ -35,7 +35,6 @@ Hanno Braun <mail at hannobraun.com> | |||
| Henri Yandell <flamefew at gmail.com> | |||
| Hirotaka Yamamoto <ymmt2005 at gmail.com> | |||
| ICHINOSE Shogo <shogo82148 at gmail.com> | |||
| Ilia Cimpoes <ichimpoesh at gmail.com> | |||
| INADA Naoki <songofacandy at gmail.com> | |||
| Jacek Szwec <szwec.jacek at gmail.com> | |||
| James Harr <james.harr at gmail.com> | |||
| @@ -73,9 +72,6 @@ Shuode Li <elemount at qq.com> | |||
| Soroush Pour <me at soroushjp.com> | |||
| Stan Putrya <root.vagner at gmail.com> | |||
| Stanley Gunawan <gunawan.stanley at gmail.com> | |||
| Steven Hartland <steven.hartland at multiplay.co.uk> | |||
| Thomas Wodarek <wodarekwebpage at gmail.com> | |||
| Tom Jenkinson <tom at tjenkinson.me> | |||
| Xiangyu Hu <xiangyu.hu at outlook.com> | |||
| Xiaobing Jiang <s7v7nislands at gmail.com> | |||
| Xiuming Chen <cc at cxm.cc> | |||
| @@ -91,4 +87,3 @@ Keybase Inc. | |||
| Percona LLC | |||
| Pivotal Inc. | |||
| Stripe Inc. | |||
| Multiplay Ltd. | |||
| @@ -1,3 +1,14 @@ | |||
| ## Version 1.4.1 (2018-11-14) | |||
| Bugfixes: | |||
| - Fix TIME format for binary columns (#818) | |||
| - Fix handling of empty auth plugin names (#835) | |||
| - Fix caching_sha2_password with empty password (#826) | |||
| - Fix canceled context broke mysqlConn (#862) | |||
| - Fix OldAuthSwitchRequest support (#870) | |||
| - Fix Auth Response packet for cleartext password (#887) | |||
| ## Version 1.4 (2018-06-03) | |||
| Changes: | |||
| @@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac | |||
| * Optional placeholder interpolation | |||
| ## Requirements | |||
| * Go 1.8 or higher. We aim to support the 3 latest versions of Go. | |||
| * Go 1.7 or higher. We aim to support the 3 latest versions of Go. | |||
| * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+) | |||
| --------------------------------------- | |||
| @@ -328,11 +328,11 @@ Timeout for establishing connections, aka dial timeout. The value must be a deci | |||
| ``` | |||
| Type: bool / string | |||
| Valid Values: true, false, skip-verify, preferred, <name> | |||
| Valid Values: true, false, skip-verify, <name> | |||
| Default: false | |||
| ``` | |||
| `tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side) or use `preferred` to use TLS only when advertised by the server. This is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Neither `skip-verify` nor `preferred` add any reliable security. You can use a custom TLS config after registering it with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). | |||
| `tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). | |||
| ##### `writeTimeout` | |||
| @@ -360,15 +360,13 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error { | |||
| pubKey := mc.cfg.pubKey | |||
| if pubKey == nil { | |||
| // request public key from server | |||
| data, err := mc.buf.takeSmallBuffer(4 + 1) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| data := mc.buf.takeSmallBuffer(4 + 1) | |||
| data[4] = cachingSha2PasswordRequestPublicKey | |||
| mc.writePacket(data) | |||
| // parse public key | |||
| if data, err = mc.readPacket(); err != nil { | |||
| data, err := mc.readPacket() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| @@ -22,17 +22,17 @@ const defaultBufSize = 4096 | |||
| // The buffer is similar to bufio.Reader / Writer but zero-copy-ish | |||
| // Also highly optimized for this particular use case. | |||
| type buffer struct { | |||
| buf []byte // buf is a byte buffer who's length and capacity are equal. | |||
| buf []byte | |||
| nc net.Conn | |||
| idx int | |||
| length int | |||
| timeout time.Duration | |||
| } | |||
| // newBuffer allocates and returns a new buffer. | |||
| func newBuffer(nc net.Conn) buffer { | |||
| var b [defaultBufSize]byte | |||
| return buffer{ | |||
| buf: make([]byte, defaultBufSize), | |||
| buf: b[:], | |||
| nc: nc, | |||
| } | |||
| } | |||
| @@ -105,56 +105,43 @@ func (b *buffer) readNext(need int) ([]byte, error) { | |||
| return b.buf[offset:b.idx], nil | |||
| } | |||
| // takeBuffer returns a buffer with the requested size. | |||
| // returns a buffer with the requested size. | |||
| // If possible, a slice from the existing buffer is returned. | |||
| // Otherwise a bigger buffer is made. | |||
| // Only one buffer (total) can be used at a time. | |||
| func (b *buffer) takeBuffer(length int) ([]byte, error) { | |||
| func (b *buffer) takeBuffer(length int) []byte { | |||
| if b.length > 0 { | |||
| return nil, ErrBusyBuffer | |||
| return nil | |||
| } | |||
| // test (cheap) general case first | |||
| if length <= cap(b.buf) { | |||
| return b.buf[:length], nil | |||
| if length <= defaultBufSize || length <= cap(b.buf) { | |||
| return b.buf[:length] | |||
| } | |||
| if length < maxPacketSize { | |||
| b.buf = make([]byte, length) | |||
| return b.buf, nil | |||
| return b.buf | |||
| } | |||
| // buffer is larger than we want to store. | |||
| return make([]byte, length), nil | |||
| return make([]byte, length) | |||
| } | |||
| // takeSmallBuffer is shortcut which can be used if length is | |||
| // known to be smaller than defaultBufSize. | |||
| // shortcut which can be used if the requested buffer is guaranteed to be | |||
| // smaller than defaultBufSize | |||
| // Only one buffer (total) can be used at a time. | |||
| func (b *buffer) takeSmallBuffer(length int) ([]byte, error) { | |||
| func (b *buffer) takeSmallBuffer(length int) []byte { | |||
| if b.length > 0 { | |||
| return nil, ErrBusyBuffer | |||
| return nil | |||
| } | |||
| return b.buf[:length], nil | |||
| return b.buf[:length] | |||
| } | |||
| // takeCompleteBuffer returns the complete existing buffer. | |||
| // This can be used if the necessary buffer size is unknown. | |||
| // cap and len of the returned buffer will be equal. | |||
| // Only one buffer (total) can be used at a time. | |||
| func (b *buffer) takeCompleteBuffer() ([]byte, error) { | |||
| if b.length > 0 { | |||
| return nil, ErrBusyBuffer | |||
| } | |||
| return b.buf, nil | |||
| } | |||
| // store stores buf, an updated buffer, if its suitable to do so. | |||
| func (b *buffer) store(buf []byte) error { | |||
| func (b *buffer) takeCompleteBuffer() []byte { | |||
| if b.length > 0 { | |||
| return ErrBusyBuffer | |||
| } else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) { | |||
| b.buf = buf[:cap(buf)] | |||
| return nil | |||
| } | |||
| return nil | |||
| return b.buf | |||
| } | |||
| @@ -9,8 +9,6 @@ | |||
| package mysql | |||
| import ( | |||
| "context" | |||
| "database/sql" | |||
| "database/sql/driver" | |||
| "io" | |||
| "net" | |||
| @@ -19,6 +17,16 @@ import ( | |||
| "time" | |||
| ) | |||
| // a copy of context.Context for Go 1.7 and earlier | |||
| type mysqlContext interface { | |||
| Done() <-chan struct{} | |||
| Err() error | |||
| // defined in context.Context, but not used in this driver: | |||
| // Deadline() (deadline time.Time, ok bool) | |||
| // Value(key interface{}) interface{} | |||
| } | |||
| type mysqlConn struct { | |||
| buf buffer | |||
| netConn net.Conn | |||
| @@ -35,7 +43,7 @@ type mysqlConn struct { | |||
| // for context support (Go 1.8+) | |||
| watching bool | |||
| watcher chan<- context.Context | |||
| watcher chan<- mysqlContext | |||
| closech chan struct{} | |||
| finished chan<- struct{} | |||
| canceled atomicError // set non-nil if conn is canceled | |||
| @@ -182,10 +190,10 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin | |||
| return "", driver.ErrSkip | |||
| } | |||
| buf, err := mc.buf.takeCompleteBuffer() | |||
| if err != nil { | |||
| buf := mc.buf.takeCompleteBuffer() | |||
| if buf == nil { | |||
| // can not take the buffer. Something must be wrong with the connection | |||
| errLog.Print(err) | |||
| errLog.Print(ErrBusyBuffer) | |||
| return "", ErrInvalidConn | |||
| } | |||
| buf = buf[:0] | |||
| @@ -451,193 +459,3 @@ func (mc *mysqlConn) finish() { | |||
| case <-mc.closech: | |||
| } | |||
| } | |||
| // 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 mc.markBadConn(err) | |||
| } | |||
| 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 context.Context, 1) | |||
| mc.watcher = watcher | |||
| finished := make(chan struct{}) | |||
| mc.finished = finished | |||
| go func() { | |||
| for { | |||
| var ctx context.Context | |||
| 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,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 | |||
| } | |||
| @@ -23,6 +23,11 @@ import ( | |||
| "sync" | |||
| ) | |||
| // watcher interface is used for context support (From Go 1.8) | |||
| type watcher interface { | |||
| startWatcher() | |||
| } | |||
| // MySQLDriver is exported to make the driver directly accessible. | |||
| // In general the driver is used via the database/sql package. | |||
| type MySQLDriver struct{} | |||
| @@ -50,7 +55,7 @@ func RegisterDial(net string, dial DialFunc) { | |||
| // Open new Connection. | |||
| // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how | |||
| // the DSN string is formatted | |||
| // the DSN string is formated | |||
| func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { | |||
| var err error | |||
| @@ -77,10 +82,6 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { | |||
| mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr) | |||
| } | |||
| if err != nil { | |||
| if nerr, ok := err.(net.Error); ok && nerr.Temporary() { | |||
| errLog.Print("net.Error from Dial()': ", nerr.Error()) | |||
| return nil, driver.ErrBadConn | |||
| } | |||
| return nil, err | |||
| } | |||
| @@ -95,7 +96,9 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { | |||
| } | |||
| // Call startWatcher for context support (From Go 1.8) | |||
| mc.startWatcher() | |||
| if s, ok := interface{}(mc).(watcher); ok { | |||
| s.startWatcher() | |||
| } | |||
| mc.buf = newBuffer(mc.netConn) | |||
| @@ -560,7 +560,7 @@ func parseDSNParams(cfg *Config, params string) (err error) { | |||
| } else { | |||
| cfg.TLSConfig = "false" | |||
| } | |||
| } else if vl := strings.ToLower(value); vl == "skip-verify" || vl == "preferred" { | |||
| } else if vl := strings.ToLower(value); vl == "skip-verify" { | |||
| cfg.TLSConfig = vl | |||
| cfg.tls = &tls.Config{InsecureSkipVerify: true} | |||
| } else { | |||
| @@ -51,7 +51,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) { | |||
| mc.sequence++ | |||
| // packets with length 0 terminate a previous packet which is a | |||
| // multiple of (2^24)-1 bytes long | |||
| // multiple of (2^24)−1 bytes long | |||
| if pktLen == 0 { | |||
| // there was no previous packet | |||
| if prevData == nil { | |||
| @@ -194,11 +194,7 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro | |||
| return nil, "", ErrOldProtocol | |||
| } | |||
| if mc.flags&clientSSL == 0 && mc.cfg.tls != nil { | |||
| if mc.cfg.TLSConfig == "preferred" { | |||
| mc.cfg.tls = nil | |||
| } else { | |||
| return nil, "", ErrNoTLS | |||
| } | |||
| return nil, "", ErrNoTLS | |||
| } | |||
| pos += 2 | |||
| @@ -290,10 +286,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string | |||
| } | |||
| // Calculate packet length and get buffer with that size | |||
| data, err := mc.buf.takeSmallBuffer(pktLen + 4) | |||
| if err != nil { | |||
| data := mc.buf.takeSmallBuffer(pktLen + 4) | |||
| if data == nil { | |||
| // cannot take the buffer. Something must be wrong with the connection | |||
| errLog.Print(err) | |||
| errLog.Print(ErrBusyBuffer) | |||
| return errBadConnNoWrite | |||
| } | |||
| @@ -371,10 +367,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string | |||
| // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse | |||
| func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error { | |||
| pktLen := 4 + len(authData) | |||
| data, err := mc.buf.takeSmallBuffer(pktLen) | |||
| if err != nil { | |||
| data := mc.buf.takeSmallBuffer(pktLen) | |||
| if data == nil { | |||
| // cannot take the buffer. Something must be wrong with the connection | |||
| errLog.Print(err) | |||
| errLog.Print(ErrBusyBuffer) | |||
| return errBadConnNoWrite | |||
| } | |||
| @@ -391,10 +387,10 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error { | |||
| // Reset Packet Sequence | |||
| mc.sequence = 0 | |||
| data, err := mc.buf.takeSmallBuffer(4 + 1) | |||
| if err != nil { | |||
| data := mc.buf.takeSmallBuffer(4 + 1) | |||
| if data == nil { | |||
| // cannot take the buffer. Something must be wrong with the connection | |||
| errLog.Print(err) | |||
| errLog.Print(ErrBusyBuffer) | |||
| return errBadConnNoWrite | |||
| } | |||
| @@ -410,10 +406,10 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error { | |||
| mc.sequence = 0 | |||
| pktLen := 1 + len(arg) | |||
| data, err := mc.buf.takeBuffer(pktLen + 4) | |||
| if err != nil { | |||
| data := mc.buf.takeBuffer(pktLen + 4) | |||
| if data == nil { | |||
| // cannot take the buffer. Something must be wrong with the connection | |||
| errLog.Print(err) | |||
| errLog.Print(ErrBusyBuffer) | |||
| return errBadConnNoWrite | |||
| } | |||
| @@ -431,10 +427,10 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error { | |||
| // Reset Packet Sequence | |||
| mc.sequence = 0 | |||
| data, err := mc.buf.takeSmallBuffer(4 + 1 + 4) | |||
| if err != nil { | |||
| data := mc.buf.takeSmallBuffer(4 + 1 + 4) | |||
| if data == nil { | |||
| // cannot take the buffer. Something must be wrong with the connection | |||
| errLog.Print(err) | |||
| errLog.Print(ErrBusyBuffer) | |||
| return errBadConnNoWrite | |||
| } | |||
| @@ -887,7 +883,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { | |||
| const minPktLen = 4 + 1 + 4 + 1 + 4 | |||
| mc := stmt.mc | |||
| // Determine threshold dynamically to avoid packet size shortage. | |||
| // Determine threshould dynamically to avoid packet size shortage. | |||
| longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1) | |||
| if longDataSize < 64 { | |||
| longDataSize = 64 | |||
| @@ -897,17 +893,15 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { | |||
| mc.sequence = 0 | |||
| var data []byte | |||
| var err error | |||
| if len(args) == 0 { | |||
| data, err = mc.buf.takeBuffer(minPktLen) | |||
| data = mc.buf.takeBuffer(minPktLen) | |||
| } else { | |||
| data, err = mc.buf.takeCompleteBuffer() | |||
| // In this case the len(data) == cap(data) which is used to optimise the flow below. | |||
| data = mc.buf.takeCompleteBuffer() | |||
| } | |||
| if err != nil { | |||
| if data == nil { | |||
| // cannot take the buffer. Something must be wrong with the connection | |||
| errLog.Print(err) | |||
| errLog.Print(ErrBusyBuffer) | |||
| return errBadConnNoWrite | |||
| } | |||
| @@ -933,7 +927,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { | |||
| pos := minPktLen | |||
| var nullMask []byte | |||
| if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= cap(data) { | |||
| if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) { | |||
| // buffer has to be extended but we don't know by how much so | |||
| // we depend on append after all data with known sizes fit. | |||
| // We stop at that because we deal with a lot of columns here | |||
| @@ -942,11 +936,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { | |||
| copy(tmp[:pos], data[:pos]) | |||
| data = tmp | |||
| nullMask = data[pos : pos+maskLen] | |||
| // No need to clean nullMask as make ensures that. | |||
| pos += maskLen | |||
| } else { | |||
| nullMask = data[pos : pos+maskLen] | |||
| for i := range nullMask { | |||
| for i := 0; i < maskLen; i++ { | |||
| nullMask[i] = 0 | |||
| } | |||
| pos += maskLen | |||
| @@ -1083,10 +1076,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error { | |||
| // In that case we must build the data packet with the new values buffer | |||
| if valuesCap != cap(paramValues) { | |||
| data = append(data[:pos], paramValues...) | |||
| if err = mc.buf.store(data); err != nil { | |||
| errLog.Print(err) | |||
| return errBadConnNoWrite | |||
| } | |||
| mc.buf.buf = data | |||
| } | |||
| pos += len(paramValues) | |||
| @@ -10,10 +10,8 @@ package mysql | |||
| import ( | |||
| "crypto/tls" | |||
| "database/sql" | |||
| "database/sql/driver" | |||
| "encoding/binary" | |||
| "errors" | |||
| "fmt" | |||
| "io" | |||
| "strconv" | |||
| @@ -82,7 +80,7 @@ func DeregisterTLSConfig(key string) { | |||
| func getTLSConfigClone(key string) (config *tls.Config) { | |||
| tlsConfigLock.RLock() | |||
| if v, ok := tlsConfigRegistry[key]; ok { | |||
| config = v.Clone() | |||
| config = cloneTLSConfig(v) | |||
| } | |||
| tlsConfigLock.RUnlock() | |||
| return | |||
| @@ -726,30 +724,3 @@ func (ae *atomicError) Value() error { | |||
| } | |||
| return nil | |||
| } | |||
| 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) | |||
| } | |||
| } | |||
| @@ -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" | |||
| @@ -59,8 +59,8 @@ pipeline: | |||
| image: golang:${GO_VERSION} | |||
| commands: | |||
| - go get -t -d -v ./... | |||
| - go get -u github.com/go-xorm/core | |||
| - go get -u github.com/go-xorm/builder | |||
| - go get -u xorm.io/core | |||
| - go get -u xorm.io/builder | |||
| - go build -v | |||
| when: | |||
| event: [ push, pull_request ] | |||
| @@ -28,7 +28,7 @@ Xorm is a simple and powerful ORM for Go. | |||
| * Optimistic Locking support | |||
| * SQL Builder support via [github.com/go-xorm/builder](https://github.com/go-xorm/builder) | |||
| * SQL Builder support via [xorm.io/builder](https://xorm.io/builder) | |||
| * Automatical Read/Write seperatelly | |||
| @@ -151,20 +151,20 @@ has, err := engine.Where("name = ?", name).Desc("id").Get(&user) | |||
| // SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1 | |||
| var name string | |||
| has, err := engine.Where("id = ?", id).Cols("name").Get(&name) | |||
| has, err := engine.Table(&user).Where("id = ?", id).Cols("name").Get(&name) | |||
| // SELECT name FROM user WHERE id = ? | |||
| var id int64 | |||
| has, err := engine.Where("name = ?", name).Cols("id").Get(&id) | |||
| has, err := engine.Table(&user).Where("name = ?", name).Cols("id").Get(&id) | |||
| has, err := engine.SQL("select id from user").Get(&id) | |||
| // SELECT id FROM user WHERE name = ? | |||
| var valuesMap = make(map[string]string) | |||
| has, err := engine.Where("id = ?", id).Get(&valuesMap) | |||
| has, err := engine.Table(&user).Where("id = ?", id).Get(&valuesMap) | |||
| // SELECT * FROM user WHERE id = ? | |||
| var valuesSlice = make([]interface{}, len(cols)) | |||
| has, err := engine.Where("id = ?", id).Cols(cols...).Get(&valuesSlice) | |||
| has, err := engine.Table(&user).Where("id = ?", id).Cols(cols...).Get(&valuesSlice) | |||
| // SELECT col1, col2, col3 FROM user WHERE id = ? | |||
| ``` | |||
| @@ -363,7 +363,7 @@ return session.Commit() | |||
| * Or you can use `Transaction` to replace above codes. | |||
| ```Go | |||
| res, err := engine.Transaction(func(sess *xorm.Session) (interface{}, error) { | |||
| res, err := engine.Transaction(func(session *xorm.Session) (interface{}, error) { | |||
| user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} | |||
| if _, err := session.Insert(&user1); err != nil { | |||
| return nil, err | |||
| @@ -493,4 +493,4 @@ Support this project by becoming a sponsor. Your logo will show up here with a l | |||
| ## LICENSE | |||
| BSD License [http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/) | |||
| BSD License [http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/) | |||
| @@ -153,20 +153,20 @@ has, err := engine.Where("name = ?", name).Desc("id").Get(&user) | |||
| // SELECT * FROM user WHERE name = ? ORDER BY id DESC LIMIT 1 | |||
| var name string | |||
| has, err := engine.Where("id = ?", id).Cols("name").Get(&name) | |||
| has, err := engine.Table(&user).Where("id = ?", id).Cols("name").Get(&name) | |||
| // SELECT name FROM user WHERE id = ? | |||
| var id int64 | |||
| has, err := engine.Where("name = ?", name).Cols("id").Get(&id) | |||
| has, err := engine.Table(&user).Where("name = ?", name).Cols("id").Get(&id) | |||
| has, err := engine.SQL("select id from user").Get(&id) | |||
| // SELECT id FROM user WHERE name = ? | |||
| var valuesMap = make(map[string]string) | |||
| has, err := engine.Where("id = ?", id).Get(&valuesMap) | |||
| has, err := engine.Table(&user).Where("id = ?", id).Get(&valuesMap) | |||
| // SELECT * FROM user WHERE id = ? | |||
| var valuesSlice = make([]interface{}, len(cols)) | |||
| has, err := engine.Where("id = ?", id).Cols(cols...).Get(&valuesSlice) | |||
| has, err := engine.Table(&user).Where("id = ?", id).Cols(cols...).Get(&valuesSlice) | |||
| // SELECT col1, col2, col3 FROM user WHERE id = ? | |||
| ``` | |||
| @@ -362,7 +362,7 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern | |||
| return session.Commit() | |||
| ``` | |||
| * 事物的简写方法 | |||
| * 事务的简写方法 | |||
| ```Go | |||
| res, err := engine.Transaction(func(session *xorm.Session) (interface{}, error) { | |||
| @@ -10,7 +10,7 @@ import ( | |||
| "sync" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // LRUCacher implments cache object facilities | |||
| @@ -7,7 +7,7 @@ package xorm | |||
| import ( | |||
| "sync" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| var _ core.CacheStore = NewMemoryStore() | |||
| @@ -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) | |||
| @@ -7,10 +7,11 @@ package xorm | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "net/url" | |||
| "strconv" | |||
| "strings" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| var ( | |||
| @@ -544,14 +545,23 @@ type odbcDriver struct { | |||
| } | |||
| func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { | |||
| kv := strings.Split(dataSourceName, ";") | |||
| var dbName string | |||
| for _, c := range kv { | |||
| vv := strings.Split(strings.TrimSpace(c), "=") | |||
| if len(vv) == 2 { | |||
| switch strings.ToLower(vv[0]) { | |||
| case "database": | |||
| dbName = vv[1] | |||
| if strings.HasPrefix(dataSourceName, "sqlserver://") { | |||
| u, err := url.Parse(dataSourceName) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| dbName = u.Query().Get("database") | |||
| } else { | |||
| kv := strings.Split(dataSourceName, ";") | |||
| for _, c := range kv { | |||
| vv := strings.Split(strings.TrimSpace(c), "=") | |||
| if len(vv) == 2 { | |||
| switch strings.ToLower(vv[0]) { | |||
| case "database": | |||
| dbName = vv[1] | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -13,7 +13,7 @@ import ( | |||
| "strings" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| var ( | |||
| @@ -393,6 +393,9 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column | |||
| if colType == "FLOAT UNSIGNED" { | |||
| colType = "FLOAT" | |||
| } | |||
| if colType == "DOUBLE UNSIGNED" { | |||
| colType = "DOUBLE" | |||
| } | |||
| col.Length = len1 | |||
| col.Length2 = len2 | |||
| if _, ok := core.SqlTypes[colType]; ok { | |||
| @@ -11,7 +11,7 @@ import ( | |||
| "strconv" | |||
| "strings" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| var ( | |||
| @@ -11,7 +11,7 @@ import ( | |||
| "strconv" | |||
| "strings" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // from http://www.postgresql.org/docs/current/static/sql-keywords-appendix.html | |||
| @@ -1093,6 +1093,19 @@ func (db *postgres) GetTables() ([]*core.Table, error) { | |||
| return tables, nil | |||
| } | |||
| func getIndexColName(indexdef string) []string { | |||
| var colNames []string | |||
| cs := strings.Split(indexdef, "(") | |||
| for _, v := range strings.Split(strings.Split(cs[1], ")")[0], ",") { | |||
| colNames = append(colNames, strings.Split(strings.TrimLeft(v, " "), " ")[0]) | |||
| } | |||
| return colNames | |||
| } | |||
| func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) { | |||
| args := []interface{}{tableName} | |||
| s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1") | |||
| @@ -1126,8 +1139,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) | |||
| } else { | |||
| indexType = core.IndexType | |||
| } | |||
| cs := strings.Split(indexdef, "(") | |||
| colNames = strings.Split(cs[1][0:len(cs[1])-1], ",") | |||
| colNames = getIndexColName(indexdef) | |||
| var isRegular bool | |||
| if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { | |||
| newIdxName := indexName[5+len(tableName):] | |||
| @@ -11,7 +11,7 @@ import ( | |||
| "regexp" | |||
| "strings" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| var ( | |||
| @@ -7,6 +7,7 @@ package xorm | |||
| import ( | |||
| "bufio" | |||
| "bytes" | |||
| "context" | |||
| "database/sql" | |||
| "encoding/gob" | |||
| "errors" | |||
| @@ -19,8 +20,8 @@ import ( | |||
| "sync" | |||
| "time" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| // Engine is the major struct of xorm, it means a database manager. | |||
| @@ -52,6 +53,8 @@ type Engine struct { | |||
| cachers map[string]core.Cacher | |||
| cacherLock sync.RWMutex | |||
| defaultContext context.Context | |||
| } | |||
| func (engine *Engine) setCacher(tableName string, cacher core.Cacher) { | |||
| @@ -122,6 +125,7 @@ func (engine *Engine) Logger() core.ILogger { | |||
| // SetLogger set the new logger | |||
| func (engine *Engine) SetLogger(logger core.ILogger) { | |||
| engine.logger = logger | |||
| engine.showSQL = logger.IsShowSQL() | |||
| engine.dialect.SetLogger(logger) | |||
| } | |||
| @@ -1351,31 +1355,31 @@ func (engine *Engine) DropIndexes(bean interface{}) error { | |||
| } | |||
| // Exec raw sql | |||
| func (engine *Engine) Exec(sqlorArgs ...interface{}) (sql.Result, error) { | |||
| func (engine *Engine) Exec(sqlOrArgs ...interface{}) (sql.Result, error) { | |||
| session := engine.NewSession() | |||
| defer session.Close() | |||
| return session.Exec(sqlorArgs...) | |||
| return session.Exec(sqlOrArgs...) | |||
| } | |||
| // Query a raw sql and return records as []map[string][]byte | |||
| func (engine *Engine) Query(sqlorArgs ...interface{}) (resultsSlice []map[string][]byte, err error) { | |||
| func (engine *Engine) Query(sqlOrArgs ...interface{}) (resultsSlice []map[string][]byte, err error) { | |||
| session := engine.NewSession() | |||
| defer session.Close() | |||
| return session.Query(sqlorArgs...) | |||
| return session.Query(sqlOrArgs...) | |||
| } | |||
| // QueryString runs a raw sql and return records as []map[string]string | |||
| func (engine *Engine) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) { | |||
| func (engine *Engine) QueryString(sqlOrArgs ...interface{}) ([]map[string]string, error) { | |||
| session := engine.NewSession() | |||
| defer session.Close() | |||
| return session.QueryString(sqlorArgs...) | |||
| return session.QueryString(sqlOrArgs...) | |||
| } | |||
| // QueryInterface runs a raw sql and return records as []map[string]interface{} | |||
| func (engine *Engine) QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error) { | |||
| func (engine *Engine) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error) { | |||
| session := engine.NewSession() | |||
| defer session.Close() | |||
| return session.QueryInterface(sqlorArgs...) | |||
| return session.QueryInterface(sqlOrArgs...) | |||
| } | |||
| // Insert one or more records | |||
| @@ -6,14 +6,13 @@ package xorm | |||
| import ( | |||
| "database/sql/driver" | |||
| "encoding/json" | |||
| "fmt" | |||
| "reflect" | |||
| "strings" | |||
| "time" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| func (engine *Engine) buildConds(table *core.Table, bean interface{}, | |||
| @@ -147,7 +146,7 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, | |||
| } else { | |||
| if col.SQLType.IsJson() { | |||
| if col.SQLType.IsText() { | |||
| bytes, err := json.Marshal(fieldValue.Interface()) | |||
| bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| engine.logger.Error(err) | |||
| continue | |||
| @@ -156,7 +155,7 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, | |||
| } else if col.SQLType.IsBlob() { | |||
| var bytes []byte | |||
| var err error | |||
| bytes, err = json.Marshal(fieldValue.Interface()) | |||
| bytes, err = DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| engine.logger.Error(err) | |||
| continue | |||
| @@ -195,7 +194,7 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, | |||
| } | |||
| if col.SQLType.IsText() { | |||
| bytes, err := json.Marshal(fieldValue.Interface()) | |||
| bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| engine.logger.Error(err) | |||
| continue | |||
| @@ -212,7 +211,7 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, | |||
| continue | |||
| } | |||
| } else { | |||
| bytes, err = json.Marshal(fieldValue.Interface()) | |||
| bytes, err = DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| engine.logger.Error(err) | |||
| continue | |||
| @@ -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) | |||
| } | |||
| @@ -5,9 +5,10 @@ | |||
| package xorm | |||
| import ( | |||
| "context" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // EngineGroup defines an engine group | |||
| @@ -74,6 +75,20 @@ func (eg *EngineGroup) Close() error { | |||
| return nil | |||
| } | |||
| // Context returned a group session | |||
| func (eg *EngineGroup) Context(ctx context.Context) *Session { | |||
| sess := eg.NewSession() | |||
| sess.isAutoClose = true | |||
| return sess.Context(ctx) | |||
| } | |||
| // NewSession returned a group session | |||
| func (eg *EngineGroup) NewSession() *Session { | |||
| sess := eg.Engine.NewSession() | |||
| sess.sessionType = groupSession | |||
| return sess | |||
| } | |||
| // Master returns the master engine | |||
| func (eg *EngineGroup) Master() *Engine { | |||
| return eg.Engine | |||
| @@ -9,10 +9,10 @@ import ( | |||
| "reflect" | |||
| "strings" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // TableNameWithSchema will automatically add schema prefix on table name | |||
| // tbNameWithSchema will automatically add schema prefix on table name | |||
| func (engine *Engine) tbNameWithSchema(v string) string { | |||
| // Add schema name as prefix of table name. | |||
| // Only for postgres database. | |||
| @@ -26,6 +26,8 @@ var ( | |||
| ErrNotImplemented = errors.New("Not implemented") | |||
| // ErrConditionType condition type unsupported | |||
| ErrConditionType = errors.New("Unsupported condition type") | |||
| // ErrUnSupportedSQLType parameter of SQL is not supported | |||
| ErrUnSupportedSQLType = errors.New("unsupported sql type") | |||
| ) | |||
| // ErrFieldIsNotExist columns does not exist | |||
| @@ -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 | |||
| ) | |||
| @@ -1,21 +1,24 @@ | |||
| cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= | |||
| cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | |||
| github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= | |||
| github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= | |||
| github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | |||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||
| github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs= | |||
| github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= | |||
| github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= | |||
| github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | |||
| github.com/go-xorm/builder v0.3.2 h1:pSsZQRRzJNapKEAEhigw3xLmiLPeAYv5GFlpYZ8+a5I= | |||
| github.com/go-xorm/builder v0.3.2/go.mod h1:v8mE3MFBgtL+RGFNfUnAMUqqfk/Y4W5KuwCFQIEpQLk= | |||
| github.com/go-xorm/core v0.6.0 h1:tp6hX+ku4OD9khFZS8VGBDRY3kfVCtelPfmkgCyHxL0= | |||
| github.com/go-xorm/core v0.6.0/go.mod h1:d8FJ9Br8OGyQl12MCclmYBuBqqxsyeedpXciV5Myih8= | |||
| github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952 h1:b5OnbZD49x9g+/FcYbs/vukEt8C/jUbGhCJ3uduQmu8= | |||
| github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= | |||
| github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= | |||
| github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | |||
| github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= | |||
| github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= | |||
| github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | |||
| github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= | |||
| github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | |||
| github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= | |||
| github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= | |||
| github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY= | |||
| github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= | |||
| github.com/jackc/pgx v3.3.0+incompatible h1:Wa90/+qsITBAPkAZjiByeIGHFcj3Ztu+VzrrIpHjL90= | |||
| github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= | |||
| github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | |||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | |||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |||
| @@ -23,21 +26,32 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | |||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | |||
| github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= | |||
| github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | |||
| github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= | |||
| github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= | |||
| github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= | |||
| github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |||
| github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= | |||
| github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= | |||
| github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | |||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |||
| github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= | |||
| github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= | |||
| github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= | |||
| github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= | |||
| github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | |||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |||
| github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= | |||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | |||
| github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= | |||
| github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= | |||
| golang.org/x/crypto v0.0.0-20190122013713-64072686203f h1:u1CmMhe3a44hy8VIgpInORnI01UVaUYheqR7x9BxT3c= | |||
| golang.org/x/crypto v0.0.0-20190122013713-64072686203f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | |||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |||
| google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= | |||
| google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | |||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= | |||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |||
| gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M= | |||
| gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU= | |||
| xorm.io/builder v0.3.5 h1:EilU39fvWDxjb1cDaELpYhsF+zziRBhew8xk4pngO+A= | |||
| xorm.io/builder v0.3.5/go.mod h1:ZFbByS/KxZI1FKRjL05PyJ4YrK2bcxlUaAxdum5aTR8= | |||
| xorm.io/core v0.6.2 h1:EJLcSxf336POJr670wKB55Mah9f93xzvGYzNRgnT8/Y= | |||
| xorm.io/core v0.6.2/go.mod h1:bwPIfLdm/FzWgVUH8WPVlr+uJhscvNGFcaZKXsI3n2c= | |||
| xorm.io/core v0.6.3 h1:n1NhVZt1s2oLw1BZfX2ocIJsHyso259uPgg63BGr37M= | |||
| xorm.io/core v0.6.3/go.mod h1:8kz/C6arVW/O9vk3PgCiMJO2hIAm1UcuOL3dSPyZ2qo= | |||
| @@ -12,7 +12,7 @@ import ( | |||
| "strconv" | |||
| "strings" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // str2PK convert string value to primary key value according to tp | |||
| @@ -5,11 +5,12 @@ | |||
| package xorm | |||
| import ( | |||
| "context" | |||
| "database/sql" | |||
| "reflect" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // Interface defines the interface which Engine, EngineGroup and Session will implementate. | |||
| @@ -27,7 +28,7 @@ type Interface interface { | |||
| Delete(interface{}) (int64, error) | |||
| Distinct(columns ...string) *Session | |||
| DropIndexes(bean interface{}) error | |||
| Exec(sqlOrAgrs ...interface{}) (sql.Result, error) | |||
| Exec(sqlOrArgs ...interface{}) (sql.Result, error) | |||
| Exist(bean ...interface{}) (bool, error) | |||
| Find(interface{}, ...interface{}) error | |||
| FindAndCount(interface{}, ...interface{}) (int64, error) | |||
| @@ -49,9 +50,9 @@ type Interface interface { | |||
| Omit(columns ...string) *Session | |||
| OrderBy(order string) *Session | |||
| Ping() error | |||
| Query(sqlOrAgrs ...interface{}) (resultsSlice []map[string][]byte, err error) | |||
| QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error) | |||
| QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) | |||
| Query(sqlOrArgs ...interface{}) (resultsSlice []map[string][]byte, err error) | |||
| QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error) | |||
| QueryString(sqlOrArgs ...interface{}) ([]map[string]string, error) | |||
| Rows(bean interface{}) (*Rows, error) | |||
| SetExpr(string, string) *Session | |||
| SQL(interface{}, ...interface{}) *Session | |||
| @@ -73,6 +74,7 @@ type EngineInterface interface { | |||
| Before(func(interface{})) *Session | |||
| Charset(charset string) *Session | |||
| ClearCache(...interface{}) error | |||
| Context(context.Context) *Session | |||
| CreateTables(...interface{}) error | |||
| DBMetas() ([]*core.Table, error) | |||
| Dialect() core.Dialect | |||
| @@ -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) | |||
| } | |||
| @@ -9,7 +9,7 @@ import ( | |||
| "io" | |||
| "log" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // default log options | |||
| @@ -9,16 +9,13 @@ import ( | |||
| "fmt" | |||
| "reflect" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // Rows rows wrapper a rows to | |||
| type Rows struct { | |||
| NoTypeCheck bool | |||
| session *Session | |||
| rows *core.Rows | |||
| fields []string | |||
| beanType reflect.Type | |||
| lastError error | |||
| } | |||
| @@ -57,13 +54,6 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { | |||
| return nil, err | |||
| } | |||
| rows.fields, err = rows.rows.Columns() | |||
| if err != nil { | |||
| rows.lastError = err | |||
| rows.Close() | |||
| return nil, err | |||
| } | |||
| return rows, nil | |||
| } | |||
| @@ -90,7 +80,7 @@ func (rows *Rows) Scan(bean interface{}) error { | |||
| return rows.lastError | |||
| } | |||
| if !rows.NoTypeCheck && reflect.Indirect(reflect.ValueOf(bean)).Type() != rows.beanType { | |||
| if reflect.Indirect(reflect.ValueOf(bean)).Type() != rows.beanType { | |||
| return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType) | |||
| } | |||
| @@ -98,13 +88,18 @@ func (rows *Rows) Scan(bean interface{}) error { | |||
| return err | |||
| } | |||
| scanResults, err := rows.session.row2Slice(rows.rows, rows.fields, bean) | |||
| fields, err := rows.rows.Columns() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| scanResults, err := rows.session.row2Slice(rows.rows, fields, bean) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| dataStruct := rValue(bean) | |||
| _, err = rows.session.slice2Bean(scanResults, rows.fields, bean, &dataStruct, rows.session.statement.RefTable) | |||
| _, err = rows.session.slice2Bean(scanResults, fields, bean, &dataStruct, rows.session.statement.RefTable) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| @@ -118,17 +113,9 @@ func (rows *Rows) Close() error { | |||
| defer rows.session.Close() | |||
| } | |||
| if rows.lastError == nil { | |||
| if rows.rows != nil { | |||
| rows.lastError = rows.rows.Close() | |||
| if rows.lastError != nil { | |||
| return rows.lastError | |||
| } | |||
| } | |||
| } else { | |||
| if rows.rows != nil { | |||
| defer rows.rows.Close() | |||
| } | |||
| if rows.rows != nil { | |||
| return rows.rows.Close() | |||
| } | |||
| return rows.lastError | |||
| } | |||
| @@ -5,8 +5,8 @@ | |||
| package xorm | |||
| import ( | |||
| "context" | |||
| "database/sql" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| "hash/crc32" | |||
| @@ -14,7 +14,14 @@ import ( | |||
| "strings" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| type sessionType int | |||
| const ( | |||
| engineSession sessionType = iota | |||
| groupSession | |||
| ) | |||
| // Session keep a pointer to sql.DB and provides all execution of all | |||
| @@ -51,7 +58,8 @@ type Session struct { | |||
| lastSQL string | |||
| lastSQLArgs []interface{} | |||
| err error | |||
| ctx context.Context | |||
| sessionType sessionType | |||
| } | |||
| // Clone copy all the session's content and return a new session | |||
| @@ -82,6 +90,8 @@ func (session *Session) Init() { | |||
| session.lastSQL = "" | |||
| session.lastSQLArgs = []interface{}{} | |||
| session.ctx = session.engine.defaultContext | |||
| } | |||
| // Close release the connection from pool | |||
| @@ -275,7 +285,7 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, | |||
| var has bool | |||
| stmt, has = session.stmtCache[crc] | |||
| if !has { | |||
| stmt, err = db.Prepare(sqlStr) | |||
| stmt, err = db.PrepareContext(session.ctx, sqlStr) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -480,13 +490,13 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b | |||
| continue | |||
| } | |||
| if fieldValue.CanAddr() { | |||
| err := json.Unmarshal(bs, fieldValue.Addr().Interface()) | |||
| err := DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| } else { | |||
| x := reflect.New(fieldType) | |||
| err := json.Unmarshal(bs, x.Interface()) | |||
| err := DefaultJSONHandler.Unmarshal(bs, x.Interface()) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -510,13 +520,13 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b | |||
| hasAssigned = true | |||
| if len(bs) > 0 { | |||
| if fieldValue.CanAddr() { | |||
| err := json.Unmarshal(bs, fieldValue.Addr().Interface()) | |||
| err := DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| } else { | |||
| x := reflect.New(fieldType) | |||
| err := json.Unmarshal(bs, x.Interface()) | |||
| err := DefaultJSONHandler.Unmarshal(bs, x.Interface()) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -532,7 +542,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b | |||
| hasAssigned = true | |||
| if col.SQLType.IsText() { | |||
| x := reflect.New(fieldType) | |||
| err := json.Unmarshal(vv.Bytes(), x.Interface()) | |||
| err := DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -647,7 +657,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b | |||
| hasAssigned = true | |||
| x := reflect.New(fieldType) | |||
| if len([]byte(vv.String())) > 0 { | |||
| err := json.Unmarshal([]byte(vv.String()), x.Interface()) | |||
| err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), x.Interface()) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -657,7 +667,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b | |||
| hasAssigned = true | |||
| x := reflect.New(fieldType) | |||
| if len(vv.Bytes()) > 0 { | |||
| err := json.Unmarshal(vv.Bytes(), x.Interface()) | |||
| err := DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -793,7 +803,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b | |||
| case core.Complex64Type: | |||
| var x complex64 | |||
| if len([]byte(vv.String())) > 0 { | |||
| err := json.Unmarshal([]byte(vv.String()), &x) | |||
| err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -803,7 +813,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b | |||
| case core.Complex128Type: | |||
| var x complex128 | |||
| if len([]byte(vv.String())) > 0 { | |||
| err := json.Unmarshal([]byte(vv.String()), &x) | |||
| err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -9,7 +9,7 @@ import ( | |||
| "strings" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| type incrParam struct { | |||
| @@ -4,7 +4,7 @@ | |||
| package xorm | |||
| import "github.com/go-xorm/builder" | |||
| import "xorm.io/builder" | |||
| // Sql provides raw sql input parameter. When you have a complex SQL statement | |||
| // and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. | |||
| @@ -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 | |||
| @@ -7,7 +7,6 @@ package xorm | |||
| import ( | |||
| "database/sql" | |||
| "database/sql/driver" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| "reflect" | |||
| @@ -15,7 +14,7 @@ import ( | |||
| "strings" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| func (session *Session) str2Time(col *core.Column, data string) (outTime time.Time, outErr error) { | |||
| @@ -103,7 +102,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, | |||
| case reflect.Complex64, reflect.Complex128: | |||
| x := reflect.New(fieldType) | |||
| if len(data) > 0 { | |||
| err := json.Unmarshal(data, x.Interface()) | |||
| err := DefaultJSONHandler.Unmarshal(data, x.Interface()) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return err | |||
| @@ -117,7 +116,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, | |||
| if col.SQLType.IsText() { | |||
| x := reflect.New(fieldType) | |||
| if len(data) > 0 { | |||
| err := json.Unmarshal(data, x.Interface()) | |||
| err := DefaultJSONHandler.Unmarshal(data, x.Interface()) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return err | |||
| @@ -130,7 +129,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, | |||
| } else { | |||
| x := reflect.New(fieldType) | |||
| if len(data) > 0 { | |||
| err := json.Unmarshal(data, x.Interface()) | |||
| err := DefaultJSONHandler.Unmarshal(data, x.Interface()) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return err | |||
| @@ -259,7 +258,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, | |||
| case core.Complex64Type.Kind(): | |||
| var x complex64 | |||
| if len(data) > 0 { | |||
| err := json.Unmarshal(data, &x) | |||
| err := DefaultJSONHandler.Unmarshal(data, &x) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return err | |||
| @@ -270,7 +269,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, | |||
| case core.Complex128Type.Kind(): | |||
| var x complex128 | |||
| if len(data) > 0 { | |||
| err := json.Unmarshal(data, &x) | |||
| err := DefaultJSONHandler.Unmarshal(data, &x) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return err | |||
| @@ -604,14 +603,14 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val | |||
| } | |||
| if col.SQLType.IsText() { | |||
| bytes, err := json.Marshal(fieldValue.Interface()) | |||
| bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return 0, err | |||
| } | |||
| return string(bytes), nil | |||
| } else if col.SQLType.IsBlob() { | |||
| bytes, err := json.Marshal(fieldValue.Interface()) | |||
| bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return 0, err | |||
| @@ -620,7 +619,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val | |||
| } | |||
| return nil, fmt.Errorf("Unsupported type %v", fieldValue.Type()) | |||
| case reflect.Complex64, reflect.Complex128: | |||
| bytes, err := json.Marshal(fieldValue.Interface()) | |||
| bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return 0, err | |||
| @@ -632,7 +631,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val | |||
| } | |||
| if col.SQLType.IsText() { | |||
| bytes, err := json.Marshal(fieldValue.Interface()) | |||
| bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return 0, err | |||
| @@ -645,7 +644,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val | |||
| (fieldValue.Type().Elem().Kind() == reflect.Uint8) { | |||
| bytes = fieldValue.Bytes() | |||
| } else { | |||
| bytes, err = json.Marshal(fieldValue.Interface()) | |||
| bytes, err = DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| session.engine.logger.Error(err) | |||
| return 0, err | |||
| @@ -9,7 +9,7 @@ import ( | |||
| "fmt" | |||
| "strconv" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string, args ...interface{}) error { | |||
| @@ -79,6 +79,10 @@ func (session *Session) Delete(bean interface{}) (int64, error) { | |||
| defer session.Close() | |||
| } | |||
| if session.statement.lastError != nil { | |||
| return 0, session.statement.lastError | |||
| } | |||
| if err := session.statement.setRefBean(bean); err != nil { | |||
| return 0, err | |||
| } | |||
| @@ -9,8 +9,8 @@ import ( | |||
| "fmt" | |||
| "reflect" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| // Exist returns true if the record exist otherwise return false | |||
| @@ -19,6 +19,10 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { | |||
| defer session.Close() | |||
| } | |||
| if session.statement.lastError != nil { | |||
| return false, session.statement.lastError | |||
| } | |||
| var sqlStr string | |||
| var args []interface{} | |||
| var err error | |||
| @@ -30,6 +34,8 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { | |||
| return false, ErrTableNotFound | |||
| } | |||
| tableName = session.statement.Engine.Quote(tableName) | |||
| if session.statement.cond.IsValid() { | |||
| condSQL, condArgs, err := builder.ToSQL(session.statement.cond) | |||
| if err != nil { | |||
| @@ -37,14 +43,18 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { | |||
| } | |||
| if session.engine.dialect.DBType() == core.MSSQL { | |||
| sqlStr = fmt.Sprintf("SELECT top 1 * FROM %s WHERE %s", tableName, condSQL) | |||
| sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s WHERE %s", tableName, condSQL) | |||
| } else if session.engine.dialect.DBType() == core.ORACLE { | |||
| sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE (%s) AND ROWNUM=1", tableName, condSQL) | |||
| } else { | |||
| sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE %s LIMIT 1", tableName, condSQL) | |||
| } | |||
| args = condArgs | |||
| } else { | |||
| if session.engine.dialect.DBType() == core.MSSQL { | |||
| sqlStr = fmt.Sprintf("SELECT top 1 * FROM %s", tableName) | |||
| sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s", tableName) | |||
| } else if session.engine.dialect.DBType() == core.ORACLE { | |||
| sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE ROWNUM=1", tableName) | |||
| } else { | |||
| sqlStr = fmt.Sprintf("SELECT * FROM %s LIMIT 1", tableName) | |||
| } | |||
| @@ -10,8 +10,8 @@ import ( | |||
| "reflect" | |||
| "strings" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| const ( | |||
| @@ -63,6 +63,10 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte | |||
| } | |||
| func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error { | |||
| if session.statement.lastError != nil { | |||
| return session.statement.lastError | |||
| } | |||
| sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) | |||
| if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map { | |||
| return errors.New("needs a pointer to a slice or a map") | |||
| @@ -11,7 +11,7 @@ import ( | |||
| "reflect" | |||
| "strconv" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // Get retrieve one record from database, bean's non-empty fields | |||
| @@ -24,6 +24,10 @@ func (session *Session) Get(bean interface{}) (bool, error) { | |||
| } | |||
| func (session *Session) get(bean interface{}) (bool, error) { | |||
| if session.statement.lastError != nil { | |||
| return false, session.statement.lastError | |||
| } | |||
| beanValue := reflect.ValueOf(bean) | |||
| if beanValue.Kind() != reflect.Ptr { | |||
| return false, errors.New("needs a pointer to a value") | |||
| @@ -8,10 +8,11 @@ import ( | |||
| "errors" | |||
| "fmt" | |||
| "reflect" | |||
| "sort" | |||
| "strconv" | |||
| "strings" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // Insert insert one or more beans | |||
| @@ -24,32 +25,67 @@ func (session *Session) Insert(beans ...interface{}) (int64, error) { | |||
| } | |||
| for _, bean := range beans { | |||
| sliceValue := reflect.Indirect(reflect.ValueOf(bean)) | |||
| if sliceValue.Kind() == reflect.Slice { | |||
| size := sliceValue.Len() | |||
| if size > 0 { | |||
| if session.engine.SupportInsertMany() { | |||
| cnt, err := session.innerInsertMulti(bean) | |||
| if err != nil { | |||
| return affected, err | |||
| } | |||
| affected += cnt | |||
| } else { | |||
| for i := 0; i < size; i++ { | |||
| cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) | |||
| switch bean.(type) { | |||
| case map[string]interface{}: | |||
| cnt, err := session.insertMapInterface(bean.(map[string]interface{})) | |||
| if err != nil { | |||
| return affected, err | |||
| } | |||
| affected += cnt | |||
| case []map[string]interface{}: | |||
| s := bean.([]map[string]interface{}) | |||
| session.autoResetStatement = false | |||
| for i := 0; i < len(s); i++ { | |||
| cnt, err := session.insertMapInterface(s[i]) | |||
| if err != nil { | |||
| return affected, err | |||
| } | |||
| affected += cnt | |||
| } | |||
| case map[string]string: | |||
| cnt, err := session.insertMapString(bean.(map[string]string)) | |||
| if err != nil { | |||
| return affected, err | |||
| } | |||
| affected += cnt | |||
| case []map[string]string: | |||
| s := bean.([]map[string]string) | |||
| session.autoResetStatement = false | |||
| for i := 0; i < len(s); i++ { | |||
| cnt, err := session.insertMapString(s[i]) | |||
| if err != nil { | |||
| return affected, err | |||
| } | |||
| affected += cnt | |||
| } | |||
| default: | |||
| sliceValue := reflect.Indirect(reflect.ValueOf(bean)) | |||
| if sliceValue.Kind() == reflect.Slice { | |||
| size := sliceValue.Len() | |||
| if size > 0 { | |||
| if session.engine.SupportInsertMany() { | |||
| cnt, err := session.innerInsertMulti(bean) | |||
| if err != nil { | |||
| return affected, err | |||
| } | |||
| affected += cnt | |||
| } else { | |||
| for i := 0; i < size; i++ { | |||
| cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) | |||
| if err != nil { | |||
| return affected, err | |||
| } | |||
| affected += cnt | |||
| } | |||
| } | |||
| } | |||
| } else { | |||
| cnt, err := session.innerInsert(bean) | |||
| if err != nil { | |||
| return affected, err | |||
| } | |||
| affected += cnt | |||
| } | |||
| } else { | |||
| cnt, err := session.innerInsert(bean) | |||
| if err != nil { | |||
| return affected, err | |||
| } | |||
| affected += cnt | |||
| } | |||
| } | |||
| @@ -337,21 +373,30 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { | |||
| var sqlStr string | |||
| var tableName = session.statement.TableName() | |||
| var output string | |||
| if session.engine.dialect.DBType() == core.MSSQL && len(table.AutoIncrement) > 0 { | |||
| output = fmt.Sprintf(" OUTPUT Inserted.%s", table.AutoIncrement) | |||
| } | |||
| if len(colPlaces) > 0 { | |||
| sqlStr = fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", | |||
| sqlStr = fmt.Sprintf("INSERT INTO %s (%v%v%v)%s VALUES (%v)", | |||
| session.engine.Quote(tableName), | |||
| session.engine.QuoteStr(), | |||
| strings.Join(colNames, session.engine.Quote(", ")), | |||
| session.engine.QuoteStr(), | |||
| output, | |||
| colPlaces) | |||
| } else { | |||
| if session.engine.dialect.DBType() == core.MYSQL { | |||
| sqlStr = fmt.Sprintf("INSERT INTO %s VALUES ()", session.engine.Quote(tableName)) | |||
| } else { | |||
| sqlStr = fmt.Sprintf("INSERT INTO %s DEFAULT VALUES", session.engine.Quote(tableName)) | |||
| sqlStr = fmt.Sprintf("INSERT INTO %s%s DEFAULT VALUES", session.engine.Quote(tableName), output) | |||
| } | |||
| } | |||
| if len(table.AutoIncrement) > 0 && session.engine.dialect.DBType() == core.POSTGRES { | |||
| sqlStr = sqlStr + " RETURNING " + session.engine.Quote(table.AutoIncrement) | |||
| } | |||
| handleAfterInsertProcessorFunc := func(bean interface{}) { | |||
| if session.isAutoCommit { | |||
| for _, closure := range session.afterClosures { | |||
| @@ -423,9 +468,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { | |||
| aiValue.Set(int64ToIntValue(id, aiValue.Type())) | |||
| return 1, nil | |||
| } else if session.engine.dialect.DBType() == core.POSTGRES && len(table.AutoIncrement) > 0 { | |||
| //assert table.AutoIncrement != "" | |||
| sqlStr = sqlStr + " RETURNING " + session.engine.Quote(table.AutoIncrement) | |||
| } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.DBType() == core.POSTGRES || session.engine.dialect.DBType() == core.MSSQL) { | |||
| res, err := session.queryBytes(sqlStr, args...) | |||
| if err != nil { | |||
| @@ -445,7 +488,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { | |||
| } | |||
| if len(res) < 1 { | |||
| return 0, errors.New("insert no error but not returned id") | |||
| return 0, errors.New("insert successfully but not returned id") | |||
| } | |||
| idByte := res[0][table.AutoIncrement] | |||
| @@ -622,3 +665,83 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac | |||
| } | |||
| return colNames, args, nil | |||
| } | |||
| func (session *Session) insertMapInterface(m map[string]interface{}) (int64, error) { | |||
| if len(m) == 0 { | |||
| return 0, ErrParamsType | |||
| } | |||
| var columns = make([]string, 0, len(m)) | |||
| for k := range m { | |||
| columns = append(columns, k) | |||
| } | |||
| sort.Strings(columns) | |||
| qm := strings.Repeat("?,", len(columns)) | |||
| qm = "(" + qm[:len(qm)-1] + ")" | |||
| tableName := session.statement.TableName() | |||
| if len(tableName) <= 0 { | |||
| return 0, ErrTableNotFound | |||
| } | |||
| var sql = fmt.Sprintf("INSERT INTO %s (`%s`) VALUES %s", session.engine.Quote(tableName), strings.Join(columns, "`,`"), qm) | |||
| var args = make([]interface{}, 0, len(m)) | |||
| for _, colName := range columns { | |||
| args = append(args, m[colName]) | |||
| } | |||
| if err := session.cacheInsert(tableName); err != nil { | |||
| return 0, err | |||
| } | |||
| res, err := session.exec(sql, args...) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| affected, err := res.RowsAffected() | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| return affected, nil | |||
| } | |||
| func (session *Session) insertMapString(m map[string]string) (int64, error) { | |||
| if len(m) == 0 { | |||
| return 0, ErrParamsType | |||
| } | |||
| var columns = make([]string, 0, len(m)) | |||
| for k := range m { | |||
| columns = append(columns, k) | |||
| } | |||
| sort.Strings(columns) | |||
| qm := strings.Repeat("?,", len(columns)) | |||
| qm = "(" + qm[:len(qm)-1] + ")" | |||
| tableName := session.statement.TableName() | |||
| if len(tableName) <= 0 { | |||
| return 0, ErrTableNotFound | |||
| } | |||
| var sql = fmt.Sprintf("INSERT INTO %s (`%s`) VALUES %s", session.engine.Quote(tableName), strings.Join(columns, "`,`"), qm) | |||
| var args = make([]interface{}, 0, len(m)) | |||
| for _, colName := range columns { | |||
| args = append(args, m[colName]) | |||
| } | |||
| if err := session.cacheInsert(tableName); err != nil { | |||
| return 0, err | |||
| } | |||
| res, err := session.exec(sql, args...) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| affected, err := res.RowsAffected() | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| return affected, nil | |||
| } | |||
| @@ -23,6 +23,10 @@ func (session *Session) Iterate(bean interface{}, fun IterFunc) error { | |||
| defer session.Close() | |||
| } | |||
| if session.statement.lastError != nil { | |||
| return session.statement.lastError | |||
| } | |||
| if session.statement.bufferSize > 0 { | |||
| return session.bufferIterate(bean, fun) | |||
| } | |||
| @@ -11,13 +11,13 @@ import ( | |||
| "strings" | |||
| "time" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interface{}, error) { | |||
| if len(sqlorArgs) > 0 { | |||
| return convertSQLOrArgs(sqlorArgs...) | |||
| func (session *Session) genQuerySQL(sqlOrArgs ...interface{}) (string, []interface{}, error) { | |||
| if len(sqlOrArgs) > 0 { | |||
| return convertSQLOrArgs(sqlOrArgs...) | |||
| } | |||
| if session.statement.RawSQL != "" { | |||
| @@ -78,12 +78,12 @@ func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interfa | |||
| } | |||
| // Query runs a raw sql and return records as []map[string][]byte | |||
| func (session *Session) Query(sqlorArgs ...interface{}) ([]map[string][]byte, error) { | |||
| func (session *Session) Query(sqlOrArgs ...interface{}) ([]map[string][]byte, error) { | |||
| if session.isAutoClose { | |||
| defer session.Close() | |||
| } | |||
| sqlStr, args, err := session.genQuerySQL(sqlorArgs...) | |||
| sqlStr, args, err := session.genQuerySQL(sqlOrArgs...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -227,12 +227,12 @@ func rows2SliceString(rows *core.Rows) (resultsSlice [][]string, err error) { | |||
| } | |||
| // QueryString runs a raw sql and return records as []map[string]string | |||
| func (session *Session) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) { | |||
| func (session *Session) QueryString(sqlOrArgs ...interface{}) ([]map[string]string, error) { | |||
| if session.isAutoClose { | |||
| defer session.Close() | |||
| } | |||
| sqlStr, args, err := session.genQuerySQL(sqlorArgs...) | |||
| sqlStr, args, err := session.genQuerySQL(sqlOrArgs...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -247,12 +247,12 @@ func (session *Session) QueryString(sqlorArgs ...interface{}) ([]map[string]stri | |||
| } | |||
| // QuerySliceString runs a raw sql and return records as [][]string | |||
| func (session *Session) QuerySliceString(sqlorArgs ...interface{}) ([][]string, error) { | |||
| func (session *Session) QuerySliceString(sqlOrArgs ...interface{}) ([][]string, error) { | |||
| if session.isAutoClose { | |||
| defer session.Close() | |||
| } | |||
| sqlStr, args, err := session.genQuerySQL(sqlorArgs...) | |||
| sqlStr, args, err := session.genQuerySQL(sqlOrArgs...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -300,12 +300,12 @@ func rows2Interfaces(rows *core.Rows) (resultsSlice []map[string]interface{}, er | |||
| } | |||
| // QueryInterface runs a raw sql and return records as []map[string]interface{} | |||
| func (session *Session) QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error) { | |||
| func (session *Session) QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error) { | |||
| if session.isAutoClose { | |||
| defer session.Close() | |||
| } | |||
| sqlStr, args, err := session.genQuerySQL(sqlorArgs...) | |||
| sqlStr, args, err := session.genQuerySQL(sqlOrArgs...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -9,8 +9,8 @@ import ( | |||
| "reflect" | |||
| "time" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { | |||
| @@ -49,7 +49,7 @@ func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Row | |||
| if session.isAutoCommit { | |||
| var db *core.DB | |||
| if session.engine.engineGroup != nil { | |||
| if session.sessionType == groupSession { | |||
| db = session.engine.engineGroup.Slave().DB() | |||
| } else { | |||
| db = session.DB() | |||
| @@ -62,21 +62,21 @@ func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Row | |||
| return nil, err | |||
| } | |||
| rows, err := stmt.Query(args...) | |||
| rows, err := stmt.QueryContext(session.ctx, args...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return rows, nil | |||
| } | |||
| rows, err := db.Query(sqlStr, args...) | |||
| rows, err := db.QueryContext(session.ctx, sqlStr, args...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return rows, nil | |||
| } | |||
| rows, err := session.tx.Query(sqlStr, args...) | |||
| rows, err := session.tx.QueryContext(session.ctx, sqlStr, args...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -175,7 +175,7 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er | |||
| } | |||
| if !session.isAutoCommit { | |||
| return session.tx.Exec(sqlStr, args...) | |||
| return session.tx.ExecContext(session.ctx, sqlStr, args...) | |||
| } | |||
| if session.prepareStmt { | |||
| @@ -184,24 +184,24 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er | |||
| return nil, err | |||
| } | |||
| res, err := stmt.Exec(args...) | |||
| res, err := stmt.ExecContext(session.ctx, args...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return res, nil | |||
| } | |||
| return session.DB().Exec(sqlStr, args...) | |||
| return session.DB().ExecContext(session.ctx, sqlStr, args...) | |||
| } | |||
| func convertSQLOrArgs(sqlorArgs ...interface{}) (string, []interface{}, error) { | |||
| switch sqlorArgs[0].(type) { | |||
| func convertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) { | |||
| switch sqlOrArgs[0].(type) { | |||
| case string: | |||
| return sqlorArgs[0].(string), sqlorArgs[1:], nil | |||
| return sqlOrArgs[0].(string), sqlOrArgs[1:], nil | |||
| case *builder.Builder: | |||
| return sqlorArgs[0].(*builder.Builder).ToSQL() | |||
| return sqlOrArgs[0].(*builder.Builder).ToSQL() | |||
| case builder.Builder: | |||
| bd := sqlorArgs[0].(builder.Builder) | |||
| bd := sqlOrArgs[0].(builder.Builder) | |||
| return bd.ToSQL() | |||
| } | |||
| @@ -209,16 +209,16 @@ func convertSQLOrArgs(sqlorArgs ...interface{}) (string, []interface{}, error) { | |||
| } | |||
| // Exec raw sql | |||
| func (session *Session) Exec(sqlorArgs ...interface{}) (sql.Result, error) { | |||
| func (session *Session) Exec(sqlOrArgs ...interface{}) (sql.Result, error) { | |||
| if session.isAutoClose { | |||
| defer session.Close() | |||
| } | |||
| if len(sqlorArgs) == 0 { | |||
| if len(sqlOrArgs) == 0 { | |||
| return nil, ErrUnSupportedType | |||
| } | |||
| sqlStr, args, err := convertSQLOrArgs(sqlorArgs...) | |||
| sqlStr, args, err := convertSQLOrArgs(sqlOrArgs...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -9,7 +9,7 @@ import ( | |||
| "fmt" | |||
| "strings" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| // Ping test if database is ok | |||
| @@ -19,7 +19,7 @@ func (session *Session) Ping() error { | |||
| } | |||
| session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName()) | |||
| return session.DB().Ping() | |||
| return session.DB().PingContext(session.ctx) | |||
| } | |||
| // CreateTable create a table according a bean | |||
| @@ -7,7 +7,7 @@ package xorm | |||
| // Begin a transaction | |||
| func (session *Session) Begin() error { | |||
| if session.isAutoCommit { | |||
| tx, err := session.DB().Begin() | |||
| tx, err := session.DB().BeginTx(session.ctx, nil) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| @@ -11,8 +11,8 @@ import ( | |||
| "strconv" | |||
| "strings" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string, args ...interface{}) error { | |||
| @@ -147,6 +147,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 | |||
| defer session.Close() | |||
| } | |||
| if session.statement.lastError != nil { | |||
| return 0, session.statement.lastError | |||
| } | |||
| v := rValue(bean) | |||
| t := v.Type() | |||
| @@ -240,23 +244,39 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 | |||
| } | |||
| var autoCond builder.Cond | |||
| if !session.statement.noAutoCondition && len(condiBean) > 0 { | |||
| if c, ok := condiBean[0].(map[string]interface{}); ok { | |||
| autoCond = builder.Eq(c) | |||
| } else { | |||
| ct := reflect.TypeOf(condiBean[0]) | |||
| k := ct.Kind() | |||
| if k == reflect.Ptr { | |||
| k = ct.Elem().Kind() | |||
| if !session.statement.noAutoCondition { | |||
| condBeanIsStruct := false | |||
| if len(condiBean) > 0 { | |||
| if c, ok := condiBean[0].(map[string]interface{}); ok { | |||
| autoCond = builder.Eq(c) | |||
| } else { | |||
| ct := reflect.TypeOf(condiBean[0]) | |||
| k := ct.Kind() | |||
| if k == reflect.Ptr { | |||
| k = ct.Elem().Kind() | |||
| } | |||
| if k == reflect.Struct { | |||
| var err error | |||
| autoCond, err = session.statement.buildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| condBeanIsStruct = true | |||
| } else { | |||
| return 0, ErrConditionType | |||
| } | |||
| } | |||
| if k == reflect.Struct { | |||
| var err error | |||
| autoCond, err = session.statement.buildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| if !condBeanIsStruct && table != nil { | |||
| if col := table.DeletedColumn(); col != nil && !session.statement.unscoped { // tag "deleted" is enabled | |||
| autoCond1 := session.engine.CondDeleted(session.engine.Quote(col.Name)) | |||
| if autoCond == nil { | |||
| autoCond = autoCond1 | |||
| } else { | |||
| autoCond = autoCond.And(autoCond1) | |||
| } | |||
| } else { | |||
| return 0, ErrConditionType | |||
| } | |||
| } | |||
| } | |||
| @@ -6,15 +6,14 @@ package xorm | |||
| import ( | |||
| "database/sql/driver" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| "reflect" | |||
| "strings" | |||
| "time" | |||
| "github.com/go-xorm/builder" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/builder" | |||
| "xorm.io/core" | |||
| ) | |||
| // Statement save all the sql info for executing SQL | |||
| @@ -60,6 +59,7 @@ type Statement struct { | |||
| cond builder.Cond | |||
| bufferSize int | |||
| context ContextCache | |||
| lastError error | |||
| } | |||
| // Init reset all the statement's fields | |||
| @@ -101,6 +101,7 @@ func (statement *Statement) Init() { | |||
| statement.cond = builder.NewCond() | |||
| statement.bufferSize = 0 | |||
| statement.context = nil | |||
| statement.lastError = nil | |||
| } | |||
| // NoAutoCondition if you do not want convert bean's field as query condition, then use this function | |||
| @@ -125,13 +126,13 @@ func (statement *Statement) SQL(query interface{}, args ...interface{}) *Stateme | |||
| var err error | |||
| statement.RawSQL, statement.RawParams, err = query.(*builder.Builder).ToSQL() | |||
| if err != nil { | |||
| statement.Engine.logger.Error(err) | |||
| statement.lastError = err | |||
| } | |||
| case string: | |||
| statement.RawSQL = query.(string) | |||
| statement.RawParams = args | |||
| default: | |||
| statement.Engine.logger.Error("unsupported sql type") | |||
| statement.lastError = ErrUnSupportedSQLType | |||
| } | |||
| return statement | |||
| @@ -160,7 +161,7 @@ func (statement *Statement) And(query interface{}, args ...interface{}) *Stateme | |||
| } | |||
| } | |||
| default: | |||
| // TODO: not support condition type | |||
| statement.lastError = ErrConditionType | |||
| } | |||
| return statement | |||
| @@ -406,7 +407,7 @@ func (statement *Statement) buildUpdates(bean interface{}, | |||
| } else { | |||
| // Blank struct could not be as update data | |||
| if requiredField || !isStructZero(fieldValue) { | |||
| bytes, err := json.Marshal(fieldValue.Interface()) | |||
| bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| panic(fmt.Sprintf("mashal %v failed", fieldValue.Interface())) | |||
| } | |||
| @@ -435,7 +436,7 @@ func (statement *Statement) buildUpdates(bean interface{}, | |||
| } | |||
| if col.SQLType.IsText() { | |||
| bytes, err := json.Marshal(fieldValue.Interface()) | |||
| bytes, err := DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| engine.logger.Error(err) | |||
| continue | |||
| @@ -455,7 +456,7 @@ func (statement *Statement) buildUpdates(bean interface{}, | |||
| fieldType.Elem().Kind() == reflect.Uint8 { | |||
| val = fieldValue.Slice(0, 0).Interface() | |||
| } else { | |||
| bytes, err = json.Marshal(fieldValue.Interface()) | |||
| bytes, err = DefaultJSONHandler.Marshal(fieldValue.Interface()) | |||
| if err != nil { | |||
| engine.logger.Error(err) | |||
| continue | |||
| @@ -755,9 +756,32 @@ func (statement *Statement) Join(joinOP string, tablename interface{}, condition | |||
| fmt.Fprintf(&buf, "%v JOIN ", joinOP) | |||
| } | |||
| tbName := statement.Engine.TableName(tablename, true) | |||
| switch tp := tablename.(type) { | |||
| case builder.Builder: | |||
| subSQL, subQueryArgs, err := tp.ToSQL() | |||
| if err != nil { | |||
| statement.lastError = err | |||
| return statement | |||
| } | |||
| tbs := strings.Split(tp.TableName(), ".") | |||
| var aliasName = strings.Trim(tbs[len(tbs)-1], statement.Engine.QuoteStr()) | |||
| fmt.Fprintf(&buf, "(%s) %s ON %v", subSQL, aliasName, condition) | |||
| statement.joinArgs = append(statement.joinArgs, subQueryArgs...) | |||
| case *builder.Builder: | |||
| subSQL, subQueryArgs, err := tp.ToSQL() | |||
| if err != nil { | |||
| statement.lastError = err | |||
| return statement | |||
| } | |||
| tbs := strings.Split(tp.TableName(), ".") | |||
| var aliasName = strings.Trim(tbs[len(tbs)-1], statement.Engine.QuoteStr()) | |||
| fmt.Fprintf(&buf, "(%s) %s ON %v", subSQL, aliasName, condition) | |||
| statement.joinArgs = append(statement.joinArgs, subQueryArgs...) | |||
| default: | |||
| tbName := statement.Engine.TableName(tablename, true) | |||
| fmt.Fprintf(&buf, "%s ON %v", tbName, condition) | |||
| } | |||
| fmt.Fprintf(&buf, "%s ON %v", tbName, condition) | |||
| statement.JoinStr = buf.String() | |||
| statement.joinArgs = append(statement.joinArgs, args...) | |||
| return statement | |||
| @@ -1064,7 +1088,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n | |||
| if dialect.DBType() == core.MSSQL { | |||
| if statement.LimitN > 0 { | |||
| top = fmt.Sprintf(" TOP %d ", statement.LimitN) | |||
| top = fmt.Sprintf("TOP %d ", statement.LimitN) | |||
| } | |||
| if statement.Start > 0 { | |||
| var column string | |||
| @@ -10,7 +10,7 @@ import ( | |||
| "fmt" | |||
| "log/syslog" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| var _ core.ILogger = &SyslogLogger{} | |||
| @@ -11,7 +11,7 @@ import ( | |||
| "strings" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| type tagContext struct { | |||
| @@ -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,9 +1,13 @@ | |||
| // Copyright 2017 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 ( | |||
| "reflect" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| var ( | |||
| @@ -7,6 +7,7 @@ | |||
| package xorm | |||
| import ( | |||
| "context" | |||
| "fmt" | |||
| "os" | |||
| "reflect" | |||
| @@ -14,7 +15,7 @@ import ( | |||
| "sync" | |||
| "time" | |||
| "github.com/go-xorm/core" | |||
| "xorm.io/core" | |||
| ) | |||
| const ( | |||
| @@ -85,14 +86,15 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { | |||
| } | |||
| engine := &Engine{ | |||
| db: db, | |||
| dialect: dialect, | |||
| Tables: make(map[reflect.Type]*core.Table), | |||
| mutex: &sync.RWMutex{}, | |||
| TagIdentifier: "xorm", | |||
| TZLocation: time.Local, | |||
| tagHandlers: defaultTagHandlers, | |||
| cachers: make(map[string]core.Cacher), | |||
| db: db, | |||
| dialect: dialect, | |||
| Tables: make(map[reflect.Type]*core.Table), | |||
| mutex: &sync.RWMutex{}, | |||
| TagIdentifier: "xorm", | |||
| TZLocation: time.Local, | |||
| tagHandlers: defaultTagHandlers, | |||
| cachers: make(map[string]core.Cacher), | |||
| defaultContext: context.Background(), | |||
| } | |||
| if uri.DbType == core.SQLITE { | |||
| @@ -3,7 +3,6 @@ | |||
| // license that can be found in the LICENSE file. | |||
| // +build !appengine | |||
| // +build go1.7 | |||
| package internal | |||
| @@ -130,7 +129,13 @@ func handleHTTP(w http.ResponseWriter, r *http.Request) { | |||
| flushes++ | |||
| } | |||
| c.pendingLogs.Unlock() | |||
| go c.flushLog(false) | |||
| flushed := make(chan struct{}) | |||
| go func() { | |||
| defer close(flushed) | |||
| // Force a log flush, because with very short requests we | |||
| // may not ever flush logs. | |||
| c.flushLog(true) | |||
| }() | |||
| w.Header().Set(logFlushHeader, strconv.Itoa(flushes)) | |||
| // Avoid nil Write call if c.Write is never called. | |||
| @@ -140,6 +145,9 @@ func handleHTTP(w http.ResponseWriter, r *http.Request) { | |||
| if c.outBody != nil { | |||
| w.Write(c.outBody) | |||
| } | |||
| // Wait for the last flush to complete before returning, | |||
| // otherwise the security ticket will not be valid. | |||
| <-flushed | |||
| } | |||
| func executeRequestSafely(c *context, r *http.Request) { | |||
| @@ -571,7 +579,10 @@ func logf(c *context, level int64, format string, args ...interface{}) { | |||
| Level: &level, | |||
| Message: &s, | |||
| }) | |||
| log.Print(logLevelName[level] + ": " + s) | |||
| // Only duplicate log to stderr if not running on App Engine second generation | |||
| if !IsSecondGen() { | |||
| log.Print(logLevelName[level] + ": " + s) | |||
| } | |||
| } | |||
| // flushLog attempts to flush any pending logs to the appserver. | |||
| @@ -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}) | |||
| } | |||
| @@ -4,11 +4,52 @@ | |||
| package internal | |||
| import netcontext "golang.org/x/net/context" | |||
| import ( | |||
| "os" | |||
| // These functions are implementations of the wrapper functions | |||
| // in ../appengine/identity.go. See that file for commentary. | |||
| netcontext "golang.org/x/net/context" | |||
| ) | |||
| var ( | |||
| // This is set to true in identity_classic.go, which is behind the appengine build tag. | |||
| // The appengine build tag is set for the first generation runtimes (<= Go 1.9) but not | |||
| // the second generation runtimes (>= Go 1.11), so this indicates whether we're on a | |||
| // first-gen runtime. See IsStandard below for the second-gen check. | |||
| appengineStandard bool | |||
| // This is set to true in identity_flex.go, which is behind the appenginevm build tag. | |||
| appengineFlex bool | |||
| ) | |||
| // AppID is the implementation of the wrapper function of the same name in | |||
| // ../identity.go. See that file for commentary. | |||
| func AppID(c netcontext.Context) string { | |||
| return appID(FullyQualifiedAppID(c)) | |||
| } | |||
| // IsStandard is the implementation of the wrapper function of the same name in | |||
| // ../appengine.go. See that file for commentary. | |||
| func IsStandard() bool { | |||
| // appengineStandard will be true for first-gen runtimes (<= Go 1.9) but not | |||
| // second-gen (>= Go 1.11). | |||
| return appengineStandard || IsSecondGen() | |||
| } | |||
| // IsStandard is the implementation of the wrapper function of the same name in | |||
| // ../appengine.go. See that file for commentary. | |||
| func IsSecondGen() bool { | |||
| // Second-gen runtimes set $GAE_ENV so we use that to check if we're on a second-gen runtime. | |||
| return os.Getenv("GAE_ENV") == "standard" | |||
| } | |||
| // IsFlex is the implementation of the wrapper function of the same name in | |||
| // ../appengine.go. See that file for commentary. | |||
| func IsFlex() bool { | |||
| return appengineFlex | |||
| } | |||
| // IsAppEngine is the implementation of the wrapper function of the same name in | |||
| // ../appengine.go. See that file for commentary. | |||
| func IsAppEngine() bool { | |||
| return IsStandard() || IsFlex() | |||
| } | |||
| @@ -12,6 +12,10 @@ import ( | |||
| netcontext "golang.org/x/net/context" | |||
| ) | |||
| func init() { | |||
| appengineStandard = true | |||
| } | |||
| func DefaultVersionHostname(ctx netcontext.Context) string { | |||
| c := fromContext(ctx) | |||
| if c == nil { | |||
| @@ -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 | |||
| } | |||
| @@ -11,5 +11,6 @@ import ( | |||
| ) | |||
| func Main() { | |||
| MainPath = "" | |||
| appengine_internal.Main() | |||
| } | |||
| @@ -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 | |||