Upgrade xorm to v1.0.3 (#12210)
Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: techknowlogick <techknowlogick@gitea.io>release/v1.15
parent
3973a4609e
commit
d08996c7b9
6
go.mod
6
go.mod
|
@ -18,7 +18,7 @@ require (
|
||||||
gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d
|
gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d
|
||||||
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7
|
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7
|
||||||
github.com/BurntSushi/toml v0.3.1
|
github.com/BurntSushi/toml v0.3.1
|
||||||
github.com/PuerkitoBio/goquery v1.5.0
|
github.com/PuerkitoBio/goquery v1.5.1
|
||||||
github.com/RoaringBitmap/roaring v0.4.23 // indirect
|
github.com/RoaringBitmap/roaring v0.4.23 // indirect
|
||||||
github.com/alecthomas/chroma v0.7.3
|
github.com/alecthomas/chroma v0.7.3
|
||||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||||
|
@ -43,7 +43,7 @@ require (
|
||||||
github.com/go-git/go-git/v5 v5.1.0
|
github.com/go-git/go-git/v5 v5.1.0
|
||||||
github.com/go-openapi/jsonreference v0.19.3 // indirect
|
github.com/go-openapi/jsonreference v0.19.3 // indirect
|
||||||
github.com/go-redis/redis v6.15.2+incompatible
|
github.com/go-redis/redis v6.15.2+incompatible
|
||||||
github.com/go-sql-driver/mysql v1.4.1
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
github.com/go-swagger/go-swagger v0.21.0
|
github.com/go-swagger/go-swagger v0.21.0
|
||||||
github.com/go-testfixtures/testfixtures/v3 v3.2.0
|
github.com/go-testfixtures/testfixtures/v3 v3.2.0
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
|
@ -120,5 +120,5 @@ require (
|
||||||
mvdan.cc/xurls/v2 v2.1.0
|
mvdan.cc/xurls/v2 v2.1.0
|
||||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||||
xorm.io/builder v0.3.7
|
xorm.io/builder v0.3.7
|
||||||
xorm.io/xorm v1.0.2
|
xorm.io/xorm v1.0.3
|
||||||
)
|
)
|
||||||
|
|
21
go.sum
21
go.sum
|
@ -49,8 +49,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
||||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
|
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
||||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
|
@ -83,8 +83,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c=
|
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c=
|
||||||
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
|
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
|
||||||
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
|
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
@ -295,6 +295,8 @@ github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDA
|
||||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
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-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-swagger/go-swagger v0.21.0 h1:AX9mdfzp6eJtUe92nFrWmbK7ocRgkCDPJs0FsSSTDlA=
|
github.com/go-swagger/go-swagger v0.21.0 h1:AX9mdfzp6eJtUe92nFrWmbK7ocRgkCDPJs0FsSSTDlA=
|
||||||
|
@ -505,6 +507,7 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||||
github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
|
github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
|
||||||
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
@ -710,10 +713,10 @@ github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||||
github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.25 h1:isv+Q6HQAmmL2Ofcmg8QauBmDPlUUnSoNhEcC940Rds=
|
github.com/yuin/goldmark v1.1.25 h1:isv+Q6HQAmmL2Ofcmg8QauBmDPlUUnSoNhEcC940Rds=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
|
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
|
|
||||||
github.com/yuin/goldmark v1.1.32 h1:5tjfNdR2ki3yYQ842+eX2sQHeiwpKJ0RnHO4IYOc4V8=
|
github.com/yuin/goldmark v1.1.32 h1:5tjfNdR2ki3yYQ842+eX2sQHeiwpKJ0RnHO4IYOc4V8=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
|
||||||
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
|
||||||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo=
|
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo=
|
||||||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60/go.mod h1:i9VhcIHN2PxXMbQrKqXNueok6QNONoPjNMoj9MygVL0=
|
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60/go.mod h1:i9VhcIHN2PxXMbQrKqXNueok6QNONoPjNMoj9MygVL0=
|
||||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||||
|
@ -785,9 +788,11 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
@ -974,5 +979,5 @@ xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
||||||
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
||||||
xorm.io/xorm v0.8.0 h1:iALxgJrX8O00f8Jk22GbZwPmxJNgssV5Mv4uc2HL9PM=
|
xorm.io/xorm v0.8.0 h1:iALxgJrX8O00f8Jk22GbZwPmxJNgssV5Mv4uc2HL9PM=
|
||||||
xorm.io/xorm v0.8.0/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=
|
xorm.io/xorm v0.8.0/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=
|
||||||
xorm.io/xorm v1.0.2 h1:kZlCh9rqd1AzGwWitcrEEqHE1h1eaZE/ujU5/2tWEtg=
|
xorm.io/xorm v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
|
||||||
xorm.io/xorm v1.0.2/go.mod h1:o4vnEsQ5V2F1/WK6w4XTwmiWJeGj82tqjAnHe44wVHY=
|
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.1
|
|
||||||
- 1.2.x
|
- 1.2.x
|
||||||
- 1.3.x
|
- 1.3.x
|
||||||
- 1.4.x
|
- 1.4.x
|
||||||
|
@ -10,7 +9,9 @@ go:
|
||||||
- 1.7.x
|
- 1.7.x
|
||||||
- 1.8.x
|
- 1.8.x
|
||||||
- 1.9.x
|
- 1.9.x
|
||||||
- "1.10.x"
|
- 1.10.x
|
||||||
- 1.11.x
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ Please note that because of the net/html dependency, goquery requires Go1.1+.
|
||||||
|
|
||||||
**Note that goquery's API is now stable, and will not break.**
|
**Note that goquery's API is now stable, and will not break.**
|
||||||
|
|
||||||
|
* **2020-02-04 (v1.5.1)** : Update module dependencies.
|
||||||
* **2018-11-15 (v1.5.0)** : Go module support (thanks @Zaba505).
|
* **2018-11-15 (v1.5.0)** : Go module support (thanks @Zaba505).
|
||||||
* **2018-06-07 (v1.4.1)** : Add `NewDocumentFromReader` examples.
|
* **2018-06-07 (v1.4.1)** : Add `NewDocumentFromReader` examples.
|
||||||
* **2018-03-24 (v1.4.0)** : Deprecate `NewDocument(url)` and `NewDocumentFromResponse(response)`.
|
* **2018-03-24 (v1.4.0)** : Deprecate `NewDocument(url)` and `NewDocumentFromResponse(response)`.
|
||||||
|
@ -138,9 +139,12 @@ func main() {
|
||||||
- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct tags.
|
- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct tags.
|
||||||
- [andybalholm/cascadia][cascadia], the CSS selector library used by goquery.
|
- [andybalholm/cascadia][cascadia], the CSS selector library used by goquery.
|
||||||
- [suntong/cascadia][cascadiacli], a command-line interface to the cascadia CSS selector library, useful to test selectors.
|
- [suntong/cascadia][cascadiacli], a command-line interface to the cascadia CSS selector library, useful to test selectors.
|
||||||
- [asciimoo/colly](https://github.com/asciimoo/colly), a lightning fast and elegant Scraping Framework
|
- [gocolly/colly](https://github.com/gocolly/colly), a lightning fast and elegant Scraping Framework
|
||||||
- [gnulnx/goperf](https://github.com/gnulnx/goperf), a website performance test tool that also fetches static assets.
|
- [gnulnx/goperf](https://github.com/gnulnx/goperf), a website performance test tool that also fetches static assets.
|
||||||
- [MontFerret/ferret](https://github.com/MontFerret/ferret), declarative web scraping.
|
- [MontFerret/ferret](https://github.com/MontFerret/ferret), declarative web scraping.
|
||||||
|
- [tacusci/berrycms](https://github.com/tacusci/berrycms), a modern simple to use CMS with easy to write plugins
|
||||||
|
- [Dataflow kit](https://github.com/slotix/dataflowkit), Web Scraping framework for Gophers.
|
||||||
|
- [Geziyor](https://github.com/geziyor/geziyor), a fast web crawling & scraping framework for Go. Supports JS rendering.
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
module github.com/PuerkitoBio/goquery
|
module github.com/PuerkitoBio/goquery
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/cascadia v1.0.0
|
github.com/andybalholm/cascadia v1.1.0
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
|
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
|
@ -5,3 +5,5 @@
|
||||||
The Cascadia package implements CSS selectors for use with the parse trees produced by the html package.
|
The Cascadia package implements CSS selectors for use with the parse trees produced by the html package.
|
||||||
|
|
||||||
To test CSS selectors without writing Go code, check out [cascadia](https://github.com/suntong/cascadia) the command line tool, a thin wrapper around this package.
|
To test CSS selectors without writing Go code, check out [cascadia](https://github.com/suntong/cascadia) the command line tool, a thin wrapper around this package.
|
||||||
|
|
||||||
|
[Refer to godoc here](https://godoc.org/github.com/andybalholm/cascadia).
|
||||||
|
|
|
@ -7,8 +7,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// a parser for CSS selectors
|
// a parser for CSS selectors
|
||||||
|
@ -56,6 +54,26 @@ func (p *parser) parseEscape() (result string, err error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toLowerASCII returns s with all ASCII capital letters lowercased.
|
||||||
|
func toLowerASCII(s string) string {
|
||||||
|
var b []byte
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if c := s[i]; 'A' <= c && c <= 'Z' {
|
||||||
|
if b == nil {
|
||||||
|
b = make([]byte, len(s))
|
||||||
|
copy(b, s)
|
||||||
|
}
|
||||||
|
b[i] = s[i] + ('a' - 'A')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b == nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
func hexDigit(c byte) bool {
|
func hexDigit(c byte) bool {
|
||||||
return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
|
return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
|
||||||
}
|
}
|
||||||
|
@ -280,92 +298,92 @@ func (p *parser) consumeClosingParenthesis() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseTypeSelector parses a type selector (one that matches by tag name).
|
// parseTypeSelector parses a type selector (one that matches by tag name).
|
||||||
func (p *parser) parseTypeSelector() (result Selector, err error) {
|
func (p *parser) parseTypeSelector() (result tagSelector, err error) {
|
||||||
tag, err := p.parseIdentifier()
|
tag, err := p.parseIdentifier()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
|
return tagSelector{tag: toLowerASCII(tag)}, nil
|
||||||
return typeSelector(tag), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseIDSelector parses a selector that matches by id attribute.
|
// parseIDSelector parses a selector that matches by id attribute.
|
||||||
func (p *parser) parseIDSelector() (Selector, error) {
|
func (p *parser) parseIDSelector() (idSelector, error) {
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, fmt.Errorf("expected id selector (#id), found EOF instead")
|
return idSelector{}, fmt.Errorf("expected id selector (#id), found EOF instead")
|
||||||
}
|
}
|
||||||
if p.s[p.i] != '#' {
|
if p.s[p.i] != '#' {
|
||||||
return nil, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i])
|
return idSelector{}, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i])
|
||||||
}
|
}
|
||||||
|
|
||||||
p.i++
|
p.i++
|
||||||
id, err := p.parseName()
|
id, err := p.parseName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return idSelector{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return attributeEqualsSelector("id", id), nil
|
return idSelector{id: id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseClassSelector parses a selector that matches by class attribute.
|
// parseClassSelector parses a selector that matches by class attribute.
|
||||||
func (p *parser) parseClassSelector() (Selector, error) {
|
func (p *parser) parseClassSelector() (classSelector, error) {
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, fmt.Errorf("expected class selector (.class), found EOF instead")
|
return classSelector{}, fmt.Errorf("expected class selector (.class), found EOF instead")
|
||||||
}
|
}
|
||||||
if p.s[p.i] != '.' {
|
if p.s[p.i] != '.' {
|
||||||
return nil, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i])
|
return classSelector{}, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i])
|
||||||
}
|
}
|
||||||
|
|
||||||
p.i++
|
p.i++
|
||||||
class, err := p.parseIdentifier()
|
class, err := p.parseIdentifier()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return classSelector{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return attributeIncludesSelector("class", class), nil
|
return classSelector{class: class}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAttributeSelector parses a selector that matches by attribute value.
|
// parseAttributeSelector parses a selector that matches by attribute value.
|
||||||
func (p *parser) parseAttributeSelector() (Selector, error) {
|
func (p *parser) parseAttributeSelector() (attrSelector, error) {
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead")
|
return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead")
|
||||||
}
|
}
|
||||||
if p.s[p.i] != '[' {
|
if p.s[p.i] != '[' {
|
||||||
return nil, fmt.Errorf("expected attribute selector ([attribute]), found '%c' instead", p.s[p.i])
|
return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found '%c' instead", p.s[p.i])
|
||||||
}
|
}
|
||||||
|
|
||||||
p.i++
|
p.i++
|
||||||
p.skipWhitespace()
|
p.skipWhitespace()
|
||||||
key, err := p.parseIdentifier()
|
key, err := p.parseIdentifier()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return attrSelector{}, err
|
||||||
}
|
}
|
||||||
|
key = toLowerASCII(key)
|
||||||
|
|
||||||
p.skipWhitespace()
|
p.skipWhitespace()
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, errors.New("unexpected EOF in attribute selector")
|
return attrSelector{}, errors.New("unexpected EOF in attribute selector")
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.s[p.i] == ']' {
|
if p.s[p.i] == ']' {
|
||||||
p.i++
|
p.i++
|
||||||
return attributeExistsSelector(key), nil
|
return attrSelector{key: key, operation: ""}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.i+2 >= len(p.s) {
|
if p.i+2 >= len(p.s) {
|
||||||
return nil, errors.New("unexpected EOF in attribute selector")
|
return attrSelector{}, errors.New("unexpected EOF in attribute selector")
|
||||||
}
|
}
|
||||||
|
|
||||||
op := p.s[p.i : p.i+2]
|
op := p.s[p.i : p.i+2]
|
||||||
if op[0] == '=' {
|
if op[0] == '=' {
|
||||||
op = "="
|
op = "="
|
||||||
} else if op[1] != '=' {
|
} else if op[1] != '=' {
|
||||||
return nil, fmt.Errorf(`expected equality operator, found "%s" instead`, op)
|
return attrSelector{}, fmt.Errorf(`expected equality operator, found "%s" instead`, op)
|
||||||
}
|
}
|
||||||
p.i += len(op)
|
p.i += len(op)
|
||||||
|
|
||||||
p.skipWhitespace()
|
p.skipWhitespace()
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, errors.New("unexpected EOF in attribute selector")
|
return attrSelector{}, errors.New("unexpected EOF in attribute selector")
|
||||||
}
|
}
|
||||||
var val string
|
var val string
|
||||||
var rx *regexp.Regexp
|
var rx *regexp.Regexp
|
||||||
|
@ -380,46 +398,32 @@ func (p *parser) parseAttributeSelector() (Selector, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return attrSelector{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.skipWhitespace()
|
p.skipWhitespace()
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, errors.New("unexpected EOF in attribute selector")
|
return attrSelector{}, errors.New("unexpected EOF in attribute selector")
|
||||||
}
|
}
|
||||||
if p.s[p.i] != ']' {
|
if p.s[p.i] != ']' {
|
||||||
return nil, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
|
return attrSelector{}, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
|
||||||
}
|
}
|
||||||
p.i++
|
p.i++
|
||||||
|
|
||||||
switch op {
|
switch op {
|
||||||
case "=":
|
case "=", "!=", "~=", "|=", "^=", "$=", "*=", "#=":
|
||||||
return attributeEqualsSelector(key, val), nil
|
return attrSelector{key: key, val: val, operation: op, regexp: rx}, nil
|
||||||
case "!=":
|
default:
|
||||||
return attributeNotEqualSelector(key, val), nil
|
return attrSelector{}, fmt.Errorf("attribute operator %q is not supported", op)
|
||||||
case "~=":
|
|
||||||
return attributeIncludesSelector(key, val), nil
|
|
||||||
case "|=":
|
|
||||||
return attributeDashmatchSelector(key, val), nil
|
|
||||||
case "^=":
|
|
||||||
return attributePrefixSelector(key, val), nil
|
|
||||||
case "$=":
|
|
||||||
return attributeSuffixSelector(key, val), nil
|
|
||||||
case "*=":
|
|
||||||
return attributeSubstringSelector(key, val), nil
|
|
||||||
case "#=":
|
|
||||||
return attributeRegexSelector(key, rx), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("attribute operator %q is not supported", op)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var errExpectedParenthesis = errors.New("expected '(' but didn't find it")
|
var errExpectedParenthesis = errors.New("expected '(' but didn't find it")
|
||||||
var errExpectedClosingParenthesis = errors.New("expected ')' but didn't find it")
|
var errExpectedClosingParenthesis = errors.New("expected ')' but didn't find it")
|
||||||
var errUnmatchedParenthesis = errors.New("unmatched '('")
|
var errUnmatchedParenthesis = errors.New("unmatched '('")
|
||||||
|
|
||||||
// parsePseudoclassSelector parses a pseudoclass selector like :not(p).
|
// parsePseudoclassSelector parses a pseudoclass selector like :not(p)
|
||||||
func (p *parser) parsePseudoclassSelector() (Selector, error) {
|
func (p *parser) parsePseudoclassSelector() (out Sel, err error) {
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, fmt.Errorf("expected pseudoclass selector (:pseudoclass), found EOF instead")
|
return nil, fmt.Errorf("expected pseudoclass selector (:pseudoclass), found EOF instead")
|
||||||
}
|
}
|
||||||
|
@ -428,40 +432,36 @@ func (p *parser) parsePseudoclassSelector() (Selector, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p.i++
|
p.i++
|
||||||
|
if p.s[p.i] == ':' { // we found a pseudo-element
|
||||||
|
p.i++
|
||||||
|
}
|
||||||
|
|
||||||
name, err := p.parseIdentifier()
|
name, err := p.parseIdentifier()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
name = toLowerASCII(name)
|
name = toLowerASCII(name)
|
||||||
|
|
||||||
switch name {
|
switch name {
|
||||||
case "not", "has", "haschild":
|
case "not", "has", "haschild":
|
||||||
if !p.consumeParenthesis() {
|
if !p.consumeParenthesis() {
|
||||||
return nil, errExpectedParenthesis
|
return out, errExpectedParenthesis
|
||||||
}
|
}
|
||||||
sel, parseErr := p.parseSelectorGroup()
|
sel, parseErr := p.parseSelectorGroup()
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, parseErr
|
return out, parseErr
|
||||||
}
|
}
|
||||||
if !p.consumeClosingParenthesis() {
|
if !p.consumeClosingParenthesis() {
|
||||||
return nil, errExpectedClosingParenthesis
|
return out, errExpectedClosingParenthesis
|
||||||
}
|
}
|
||||||
|
|
||||||
switch name {
|
out = relativePseudoClassSelector{name: name, match: sel}
|
||||||
case "not":
|
|
||||||
return negatedSelector(sel), nil
|
|
||||||
case "has":
|
|
||||||
return hasDescendantSelector(sel), nil
|
|
||||||
case "haschild":
|
|
||||||
return hasChildSelector(sel), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case "contains", "containsown":
|
case "contains", "containsown":
|
||||||
if !p.consumeParenthesis() {
|
if !p.consumeParenthesis() {
|
||||||
return nil, errExpectedParenthesis
|
return out, errExpectedParenthesis
|
||||||
}
|
}
|
||||||
if p.i == len(p.s) {
|
if p.i == len(p.s) {
|
||||||
return nil, errUnmatchedParenthesis
|
return out, errUnmatchedParenthesis
|
||||||
}
|
}
|
||||||
var val string
|
var val string
|
||||||
switch p.s[p.i] {
|
switch p.s[p.i] {
|
||||||
|
@ -471,95 +471,75 @@ func (p *parser) parsePseudoclassSelector() (Selector, error) {
|
||||||
val, err = p.parseIdentifier()
|
val, err = p.parseIdentifier()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return out, err
|
||||||
}
|
}
|
||||||
val = strings.ToLower(val)
|
val = strings.ToLower(val)
|
||||||
p.skipWhitespace()
|
p.skipWhitespace()
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, errors.New("unexpected EOF in pseudo selector")
|
return out, errors.New("unexpected EOF in pseudo selector")
|
||||||
}
|
}
|
||||||
if !p.consumeClosingParenthesis() {
|
if !p.consumeClosingParenthesis() {
|
||||||
return nil, errExpectedClosingParenthesis
|
return out, errExpectedClosingParenthesis
|
||||||
}
|
}
|
||||||
|
|
||||||
switch name {
|
out = containsPseudoClassSelector{own: name == "containsown", value: val}
|
||||||
case "contains":
|
|
||||||
return textSubstrSelector(val), nil
|
|
||||||
case "containsown":
|
|
||||||
return ownTextSubstrSelector(val), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case "matches", "matchesown":
|
case "matches", "matchesown":
|
||||||
if !p.consumeParenthesis() {
|
if !p.consumeParenthesis() {
|
||||||
return nil, errExpectedParenthesis
|
return out, errExpectedParenthesis
|
||||||
}
|
}
|
||||||
rx, err := p.parseRegex()
|
rx, err := p.parseRegex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return out, err
|
||||||
}
|
}
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, errors.New("unexpected EOF in pseudo selector")
|
return out, errors.New("unexpected EOF in pseudo selector")
|
||||||
}
|
}
|
||||||
if !p.consumeClosingParenthesis() {
|
if !p.consumeClosingParenthesis() {
|
||||||
return nil, errExpectedClosingParenthesis
|
return out, errExpectedClosingParenthesis
|
||||||
}
|
}
|
||||||
|
|
||||||
switch name {
|
out = regexpPseudoClassSelector{own: name == "matchesown", regexp: rx}
|
||||||
case "matches":
|
|
||||||
return textRegexSelector(rx), nil
|
|
||||||
case "matchesown":
|
|
||||||
return ownTextRegexSelector(rx), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type":
|
case "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type":
|
||||||
if !p.consumeParenthesis() {
|
if !p.consumeParenthesis() {
|
||||||
return nil, errExpectedParenthesis
|
return out, errExpectedParenthesis
|
||||||
}
|
}
|
||||||
a, b, err := p.parseNth()
|
a, b, err := p.parseNth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return out, err
|
||||||
}
|
}
|
||||||
if !p.consumeClosingParenthesis() {
|
if !p.consumeClosingParenthesis() {
|
||||||
return nil, errExpectedClosingParenthesis
|
return out, errExpectedClosingParenthesis
|
||||||
}
|
}
|
||||||
if a == 0 {
|
last := name == "nth-last-child" || name == "nth-last-of-type"
|
||||||
switch name {
|
ofType := name == "nth-of-type" || name == "nth-last-of-type"
|
||||||
case "nth-child":
|
out = nthPseudoClassSelector{a: a, b: b, last: last, ofType: ofType}
|
||||||
return simpleNthChildSelector(b, false), nil
|
|
||||||
case "nth-of-type":
|
|
||||||
return simpleNthChildSelector(b, true), nil
|
|
||||||
case "nth-last-child":
|
|
||||||
return simpleNthLastChildSelector(b, false), nil
|
|
||||||
case "nth-last-of-type":
|
|
||||||
return simpleNthLastChildSelector(b, true), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nthChildSelector(a, b,
|
|
||||||
name == "nth-last-child" || name == "nth-last-of-type",
|
|
||||||
name == "nth-of-type" || name == "nth-last-of-type"),
|
|
||||||
nil
|
|
||||||
|
|
||||||
case "first-child":
|
case "first-child":
|
||||||
return simpleNthChildSelector(1, false), nil
|
out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: false}
|
||||||
case "last-child":
|
case "last-child":
|
||||||
return simpleNthLastChildSelector(1, false), nil
|
out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: true}
|
||||||
case "first-of-type":
|
case "first-of-type":
|
||||||
return simpleNthChildSelector(1, true), nil
|
out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: false}
|
||||||
case "last-of-type":
|
case "last-of-type":
|
||||||
return simpleNthLastChildSelector(1, true), nil
|
out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: true}
|
||||||
case "only-child":
|
case "only-child":
|
||||||
return onlyChildSelector(false), nil
|
out = onlyChildPseudoClassSelector{ofType: false}
|
||||||
case "only-of-type":
|
case "only-of-type":
|
||||||
return onlyChildSelector(true), nil
|
out = onlyChildPseudoClassSelector{ofType: true}
|
||||||
case "input":
|
case "input":
|
||||||
return inputSelector, nil
|
out = inputPseudoClassSelector{}
|
||||||
case "empty":
|
case "empty":
|
||||||
return emptyElementSelector, nil
|
out = emptyElementPseudoClassSelector{}
|
||||||
case "root":
|
case "root":
|
||||||
return rootSelector, nil
|
out = rootPseudoClassSelector{}
|
||||||
|
case "after", "backdrop", "before", "cue", "first-letter", "first-line", "grammar-error", "marker", "placeholder", "selection", "spelling-error":
|
||||||
|
return out, errors.New("pseudo-elements are not yet supported")
|
||||||
|
default:
|
||||||
|
return out, fmt.Errorf("unknown pseudoclass or pseudoelement :%s", name)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
return nil, fmt.Errorf("unknown pseudoclass :%s", name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseInteger parses a decimal integer.
|
// parseInteger parses a decimal integer.
|
||||||
|
@ -705,8 +685,8 @@ invalid:
|
||||||
|
|
||||||
// parseSimpleSelectorSequence parses a selector sequence that applies to
|
// parseSimpleSelectorSequence parses a selector sequence that applies to
|
||||||
// a single element.
|
// a single element.
|
||||||
func (p *parser) parseSimpleSelectorSequence() (Selector, error) {
|
func (p *parser) parseSimpleSelectorSequence() (Sel, error) {
|
||||||
var result Selector
|
var selectors []Sel
|
||||||
|
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return nil, errors.New("expected selector, found EOF instead")
|
return nil, errors.New("expected selector, found EOF instead")
|
||||||
|
@ -723,13 +703,15 @@ func (p *parser) parseSimpleSelectorSequence() (Selector, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result = r
|
selectors = append(selectors, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
for p.i < len(p.s) {
|
for p.i < len(p.s) {
|
||||||
var ns Selector
|
var (
|
||||||
var err error
|
ns Sel
|
||||||
|
err error
|
||||||
|
)
|
||||||
switch p.s[p.i] {
|
switch p.s[p.i] {
|
||||||
case '#':
|
case '#':
|
||||||
ns, err = p.parseIDSelector()
|
ns, err = p.parseIDSelector()
|
||||||
|
@ -745,37 +727,33 @@ loop:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if result == nil {
|
|
||||||
result = ns
|
|
||||||
} else {
|
|
||||||
result = intersectionSelector(result, ns)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if result == nil {
|
selectors = append(selectors, ns)
|
||||||
result = func(n *html.Node) bool {
|
|
||||||
return n.Type == html.ElementNode
|
|
||||||
}
|
}
|
||||||
|
if len(selectors) == 1 { // no need wrap the selectors in compoundSelector
|
||||||
|
return selectors[0], nil
|
||||||
}
|
}
|
||||||
|
return compoundSelector{selectors: selectors}, nil
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSelector parses a selector that may include combinators.
|
// parseSelector parses a selector that may include combinators.
|
||||||
func (p *parser) parseSelector() (result Selector, err error) {
|
func (p *parser) parseSelector() (Sel, error) {
|
||||||
p.skipWhitespace()
|
p.skipWhitespace()
|
||||||
result, err = p.parseSimpleSelectorSequence()
|
result, err := p.parseSimpleSelectorSequence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var combinator byte
|
var (
|
||||||
|
combinator byte
|
||||||
|
c Sel
|
||||||
|
)
|
||||||
if p.skipWhitespace() {
|
if p.skipWhitespace() {
|
||||||
combinator = ' '
|
combinator = ' '
|
||||||
}
|
}
|
||||||
if p.i >= len(p.s) {
|
if p.i >= len(p.s) {
|
||||||
return
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch p.s[p.i] {
|
switch p.s[p.i] {
|
||||||
|
@ -785,51 +763,39 @@ func (p *parser) parseSelector() (result Selector, err error) {
|
||||||
p.skipWhitespace()
|
p.skipWhitespace()
|
||||||
case ',', ')':
|
case ',', ')':
|
||||||
// These characters can't begin a selector, but they can legally occur after one.
|
// These characters can't begin a selector, but they can legally occur after one.
|
||||||
return
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if combinator == 0 {
|
if combinator == 0 {
|
||||||
return
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := p.parseSimpleSelectorSequence()
|
c, err = p.parseSimpleSelectorSequence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
result = combinedSelector{first: result, combinator: combinator, second: c}
|
||||||
switch combinator {
|
|
||||||
case ' ':
|
|
||||||
result = descendantSelector(result, c)
|
|
||||||
case '>':
|
|
||||||
result = childSelector(result, c)
|
|
||||||
case '+':
|
|
||||||
result = siblingSelector(result, c, true)
|
|
||||||
case '~':
|
|
||||||
result = siblingSelector(result, c, false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSelectorGroup parses a group of selectors, separated by commas.
|
// parseSelectorGroup parses a group of selectors, separated by commas.
|
||||||
func (p *parser) parseSelectorGroup() (result Selector, err error) {
|
func (p *parser) parseSelectorGroup() (SelectorGroup, error) {
|
||||||
result, err = p.parseSelector()
|
current, err := p.parseSelector()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
result := SelectorGroup{current}
|
||||||
|
|
||||||
for p.i < len(p.s) {
|
for p.i < len(p.s) {
|
||||||
if p.s[p.i] != ',' {
|
if p.s[p.i] != ',' {
|
||||||
return result, nil
|
break
|
||||||
}
|
}
|
||||||
p.i++
|
p.i++
|
||||||
c, err := p.parseSelector()
|
c, err := p.parseSelector()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result = unionSelector(result, c)
|
result = append(result, c)
|
||||||
}
|
}
|
||||||
|
return result, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,36 +9,37 @@ import (
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
)
|
)
|
||||||
|
|
||||||
// the Selector type, and functions for creating them
|
// Matcher is the interface for basic selector functionality.
|
||||||
|
// Match returns whether a selector matches n.
|
||||||
// A Selector is a function which tells whether a node matches or not.
|
type Matcher interface {
|
||||||
type Selector func(*html.Node) bool
|
Match(n *html.Node) bool
|
||||||
|
|
||||||
// hasChildMatch returns whether n has any child that matches a.
|
|
||||||
func hasChildMatch(n *html.Node, a Selector) bool {
|
|
||||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
||||||
if a(c) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasDescendantMatch performs a depth-first search of n's descendants,
|
// Sel is the interface for all the functionality provided by selectors.
|
||||||
// testing whether any of them match a. It returns true as soon as a match is
|
// It is currently the same as Matcher, but other methods may be added in the
|
||||||
// found, or false if no match is found.
|
// future.
|
||||||
func hasDescendantMatch(n *html.Node, a Selector) bool {
|
type Sel interface {
|
||||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
Matcher
|
||||||
if a(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) {
|
Specificity() Specificity
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile parses a selector and returns, if successful, a Selector object
|
// Parse parses a selector.
|
||||||
// that can be used to match against html.Node objects.
|
func Parse(sel string) (Sel, error) {
|
||||||
func Compile(sel string) (Selector, error) {
|
p := &parser{s: sel}
|
||||||
|
compiled, err := p.parseSelector()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.i < len(sel) {
|
||||||
|
return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return compiled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseGroup parses a selector, or a group of selectors separated by commas.
|
||||||
|
func ParseGroup(sel string) (SelectorGroup, error) {
|
||||||
p := &parser{s: sel}
|
p := &parser{s: sel}
|
||||||
compiled, err := p.parseSelectorGroup()
|
compiled, err := p.parseSelectorGroup()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -52,6 +53,23 @@ func Compile(sel string) (Selector, error) {
|
||||||
return compiled, nil
|
return compiled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A Selector is a function which tells whether a node matches or not.
|
||||||
|
//
|
||||||
|
// This type is maintained for compatibility; I recommend using the newer and
|
||||||
|
// more idiomatic interfaces Sel and Matcher.
|
||||||
|
type Selector func(*html.Node) bool
|
||||||
|
|
||||||
|
// Compile parses a selector and returns, if successful, a Selector object
|
||||||
|
// that can be used to match against html.Node objects.
|
||||||
|
func Compile(sel string) (Selector, error) {
|
||||||
|
compiled, err := ParseGroup(sel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Selector(compiled.Match), nil
|
||||||
|
}
|
||||||
|
|
||||||
// MustCompile is like Compile, but panics instead of returning an error.
|
// MustCompile is like Compile, but panics instead of returning an error.
|
||||||
func MustCompile(sel string) Selector {
|
func MustCompile(sel string) Selector {
|
||||||
compiled, err := Compile(sel)
|
compiled, err := Compile(sel)
|
||||||
|
@ -79,6 +97,23 @@ func (s Selector) matchAllInto(n *html.Node, storage []*html.Node) []*html.Node
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func queryInto(n *html.Node, m Matcher, storage []*html.Node) []*html.Node {
|
||||||
|
for child := n.FirstChild; child != nil; child = child.NextSibling {
|
||||||
|
if m.Match(child) {
|
||||||
|
storage = append(storage, child)
|
||||||
|
}
|
||||||
|
storage = queryInto(child, m, storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return storage
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryAll returns a slice of all the nodes that match m, from the descendants
|
||||||
|
// of n.
|
||||||
|
func QueryAll(n *html.Node, m Matcher) []*html.Node {
|
||||||
|
return queryInto(n, m, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Match returns true if the node matches the selector.
|
// Match returns true if the node matches the selector.
|
||||||
func (s Selector) Match(n *html.Node) bool {
|
func (s Selector) Match(n *html.Node) bool {
|
||||||
return s(n)
|
return s(n)
|
||||||
|
@ -99,6 +134,21 @@ func (s Selector) MatchFirst(n *html.Node) *html.Node {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Query returns the first node that matches m, from the descendants of n.
|
||||||
|
// If none matches, it returns nil.
|
||||||
|
func Query(n *html.Node, m Matcher) *html.Node {
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
if m.Match(c) {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if matched := Query(c, m); matched != nil {
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Filter returns the nodes in nodes that match the selector.
|
// Filter returns the nodes in nodes that match the selector.
|
||||||
func (s Selector) Filter(nodes []*html.Node) (result []*html.Node) {
|
func (s Selector) Filter(nodes []*html.Node) (result []*html.Node) {
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
|
@ -109,39 +159,93 @@ func (s Selector) Filter(nodes []*html.Node) (result []*html.Node) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// typeSelector returns a Selector that matches elements with a given tag name.
|
// Filter returns the nodes that match m.
|
||||||
func typeSelector(tag string) Selector {
|
func Filter(nodes []*html.Node, m Matcher) (result []*html.Node) {
|
||||||
tag = toLowerASCII(tag)
|
for _, n := range nodes {
|
||||||
return func(n *html.Node) bool {
|
if m.Match(n) {
|
||||||
return n.Type == html.ElementNode && n.Data == tag
|
result = append(result, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
type tagSelector struct {
|
||||||
|
tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches elements with a given tag name.
|
||||||
|
func (t tagSelector) Match(n *html.Node) bool {
|
||||||
|
return n.Type == html.ElementNode && n.Data == t.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c tagSelector) Specificity() Specificity {
|
||||||
|
return Specificity{0, 0, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
type classSelector struct {
|
||||||
|
class string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches elements by class attribute.
|
||||||
|
func (t classSelector) Match(n *html.Node) bool {
|
||||||
|
return matchAttribute(n, "class", func(s string) bool {
|
||||||
|
return matchInclude(t.class, s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c classSelector) Specificity() Specificity {
|
||||||
|
return Specificity{0, 1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type idSelector struct {
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches elements by id attribute.
|
||||||
|
func (t idSelector) Match(n *html.Node) bool {
|
||||||
|
return matchAttribute(n, "id", func(s string) bool {
|
||||||
|
return s == t.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c idSelector) Specificity() Specificity {
|
||||||
|
return Specificity{1, 0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type attrSelector struct {
|
||||||
|
key, val, operation string
|
||||||
|
regexp *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches elements by attribute value.
|
||||||
|
func (t attrSelector) Match(n *html.Node) bool {
|
||||||
|
switch t.operation {
|
||||||
|
case "":
|
||||||
|
return matchAttribute(n, t.key, func(string) bool { return true })
|
||||||
|
case "=":
|
||||||
|
return matchAttribute(n, t.key, func(s string) bool { return s == t.val })
|
||||||
|
case "!=":
|
||||||
|
return attributeNotEqualMatch(t.key, t.val, n)
|
||||||
|
case "~=":
|
||||||
|
// matches elements where the attribute named key is a whitespace-separated list that includes val.
|
||||||
|
return matchAttribute(n, t.key, func(s string) bool { return matchInclude(t.val, s) })
|
||||||
|
case "|=":
|
||||||
|
return attributeDashMatch(t.key, t.val, n)
|
||||||
|
case "^=":
|
||||||
|
return attributePrefixMatch(t.key, t.val, n)
|
||||||
|
case "$=":
|
||||||
|
return attributeSuffixMatch(t.key, t.val, n)
|
||||||
|
case "*=":
|
||||||
|
return attributeSubstringMatch(t.key, t.val, n)
|
||||||
|
case "#=":
|
||||||
|
return attributeRegexMatch(t.key, t.regexp, n)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsuported operation : %s", t.operation))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// toLowerASCII returns s with all ASCII capital letters lowercased.
|
// matches elements where the attribute named key satisifes the function f.
|
||||||
func toLowerASCII(s string) string {
|
func matchAttribute(n *html.Node, key string, f func(string) bool) bool {
|
||||||
var b []byte
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
if c := s[i]; 'A' <= c && c <= 'Z' {
|
|
||||||
if b == nil {
|
|
||||||
b = make([]byte, len(s))
|
|
||||||
copy(b, s)
|
|
||||||
}
|
|
||||||
b[i] = s[i] + ('a' - 'A')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b == nil {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// attributeSelector returns a Selector that matches elements
|
|
||||||
// where the attribute named key satisifes the function f.
|
|
||||||
func attributeSelector(key string, f func(string) bool) Selector {
|
|
||||||
key = toLowerASCII(key)
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if n.Type != html.ElementNode {
|
if n.Type != html.ElementNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -152,28 +256,10 @@ func attributeSelector(key string, f func(string) bool) Selector {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// attributeExistsSelector returns a Selector that matches elements that have
|
// attributeNotEqualMatch matches elements where
|
||||||
// an attribute named key.
|
|
||||||
func attributeExistsSelector(key string) Selector {
|
|
||||||
return attributeSelector(key, func(string) bool { return true })
|
|
||||||
}
|
|
||||||
|
|
||||||
// attributeEqualsSelector returns a Selector that matches elements where
|
|
||||||
// the attribute named key has the value val.
|
|
||||||
func attributeEqualsSelector(key, val string) Selector {
|
|
||||||
return attributeSelector(key,
|
|
||||||
func(s string) bool {
|
|
||||||
return s == val
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// attributeNotEqualSelector returns a Selector that matches elements where
|
|
||||||
// the attribute named key does not have the value val.
|
// the attribute named key does not have the value val.
|
||||||
func attributeNotEqualSelector(key, val string) Selector {
|
func attributeNotEqualMatch(key, val string, n *html.Node) bool {
|
||||||
key = toLowerASCII(key)
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if n.Type != html.ElementNode {
|
if n.Type != html.ElementNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -184,13 +270,9 @@ func attributeNotEqualSelector(key, val string) Selector {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// attributeIncludesSelector returns a Selector that matches elements where
|
// returns true if s is a whitespace-separated list that includes val.
|
||||||
// the attribute named key is a whitespace-separated list that includes val.
|
func matchInclude(val, s string) bool {
|
||||||
func attributeIncludesSelector(key, val string) Selector {
|
|
||||||
return attributeSelector(key,
|
|
||||||
func(s string) bool {
|
|
||||||
for s != "" {
|
for s != "" {
|
||||||
i := strings.IndexAny(s, " \t\r\n\f")
|
i := strings.IndexAny(s, " \t\r\n\f")
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
|
@ -202,13 +284,11 @@ func attributeIncludesSelector(key, val string) Selector {
|
||||||
s = s[i+1:]
|
s = s[i+1:]
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// attributeDashmatchSelector returns a Selector that matches elements where
|
// matches elements where the attribute named key equals val or starts with val plus a hyphen.
|
||||||
// the attribute named key equals val or starts with val plus a hyphen.
|
func attributeDashMatch(key, val string, n *html.Node) bool {
|
||||||
func attributeDashmatchSelector(key, val string) Selector {
|
return matchAttribute(n, key,
|
||||||
return attributeSelector(key,
|
|
||||||
func(s string) bool {
|
func(s string) bool {
|
||||||
if s == val {
|
if s == val {
|
||||||
return true
|
return true
|
||||||
|
@ -223,10 +303,10 @@ func attributeDashmatchSelector(key, val string) Selector {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// attributePrefixSelector returns a Selector that matches elements where
|
// attributePrefixMatch returns a Selector that matches elements where
|
||||||
// the attribute named key starts with val.
|
// the attribute named key starts with val.
|
||||||
func attributePrefixSelector(key, val string) Selector {
|
func attributePrefixMatch(key, val string, n *html.Node) bool {
|
||||||
return attributeSelector(key,
|
return matchAttribute(n, key,
|
||||||
func(s string) bool {
|
func(s string) bool {
|
||||||
if strings.TrimSpace(s) == "" {
|
if strings.TrimSpace(s) == "" {
|
||||||
return false
|
return false
|
||||||
|
@ -235,10 +315,10 @@ func attributePrefixSelector(key, val string) Selector {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// attributeSuffixSelector returns a Selector that matches elements where
|
// attributeSuffixMatch matches elements where
|
||||||
// the attribute named key ends with val.
|
// the attribute named key ends with val.
|
||||||
func attributeSuffixSelector(key, val string) Selector {
|
func attributeSuffixMatch(key, val string, n *html.Node) bool {
|
||||||
return attributeSelector(key,
|
return matchAttribute(n, key,
|
||||||
func(s string) bool {
|
func(s string) bool {
|
||||||
if strings.TrimSpace(s) == "" {
|
if strings.TrimSpace(s) == "" {
|
||||||
return false
|
return false
|
||||||
|
@ -247,10 +327,10 @@ func attributeSuffixSelector(key, val string) Selector {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// attributeSubstringSelector returns a Selector that matches nodes where
|
// attributeSubstringMatch matches nodes where
|
||||||
// the attribute named key contains val.
|
// the attribute named key contains val.
|
||||||
func attributeSubstringSelector(key, val string) Selector {
|
func attributeSubstringMatch(key, val string, n *html.Node) bool {
|
||||||
return attributeSelector(key,
|
return matchAttribute(n, key,
|
||||||
func(s string) bool {
|
func(s string) bool {
|
||||||
if strings.TrimSpace(s) == "" {
|
if strings.TrimSpace(s) == "" {
|
||||||
return false
|
return false
|
||||||
|
@ -259,41 +339,120 @@ func attributeSubstringSelector(key, val string) Selector {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// attributeRegexSelector returns a Selector that matches nodes where
|
// attributeRegexMatch matches nodes where
|
||||||
// the attribute named key matches the regular expression rx
|
// the attribute named key matches the regular expression rx
|
||||||
func attributeRegexSelector(key string, rx *regexp.Regexp) Selector {
|
func attributeRegexMatch(key string, rx *regexp.Regexp, n *html.Node) bool {
|
||||||
return attributeSelector(key,
|
return matchAttribute(n, key,
|
||||||
func(s string) bool {
|
func(s string) bool {
|
||||||
return rx.MatchString(s)
|
return rx.MatchString(s)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// intersectionSelector returns a selector that matches nodes that match
|
func (c attrSelector) Specificity() Specificity {
|
||||||
// both a and b.
|
return Specificity{0, 1, 0}
|
||||||
func intersectionSelector(a, b Selector) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
return a(n) && b(n)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// unionSelector returns a selector that matches elements that match
|
// ---------------- Pseudo class selectors ----------------
|
||||||
// either a or b.
|
// we use severals concrete types of pseudo-class selectors
|
||||||
func unionSelector(a, b Selector) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
type relativePseudoClassSelector struct {
|
||||||
return a(n) || b(n)
|
name string // one of "not", "has", "haschild"
|
||||||
}
|
match SelectorGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// negatedSelector returns a selector that matches elements that do not match a.
|
func (s relativePseudoClassSelector) Match(n *html.Node) bool {
|
||||||
func negatedSelector(a Selector) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if n.Type != html.ElementNode {
|
if n.Type != html.ElementNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return !a(n)
|
switch s.name {
|
||||||
|
case "not":
|
||||||
|
// matches elements that do not match a.
|
||||||
|
return !s.match.Match(n)
|
||||||
|
case "has":
|
||||||
|
// matches elements with any descendant that matches a.
|
||||||
|
return hasDescendantMatch(n, s.match)
|
||||||
|
case "haschild":
|
||||||
|
// matches elements with a child that matches a.
|
||||||
|
return hasChildMatch(n, s.match)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported relative pseudo class selector : %s", s.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasChildMatch returns whether n has any child that matches a.
|
||||||
|
func hasChildMatch(n *html.Node, a Matcher) bool {
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
if a.Match(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasDescendantMatch performs a depth-first search of n's descendants,
|
||||||
|
// testing whether any of them match a. It returns true as soon as a match is
|
||||||
|
// found, or false if no match is found.
|
||||||
|
func hasDescendantMatch(n *html.Node, a Matcher) bool {
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
if a.Match(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specificity returns the specificity of the most specific selectors
|
||||||
|
// in the pseudo-class arguments.
|
||||||
|
// See https://www.w3.org/TR/selectors/#specificity-rules
|
||||||
|
func (s relativePseudoClassSelector) Specificity() Specificity {
|
||||||
|
var max Specificity
|
||||||
|
for _, sel := range s.match {
|
||||||
|
newSpe := sel.Specificity()
|
||||||
|
if max.Less(newSpe) {
|
||||||
|
max = newSpe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
type containsPseudoClassSelector struct {
|
||||||
|
own bool
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s containsPseudoClassSelector) Match(n *html.Node) bool {
|
||||||
|
var text string
|
||||||
|
if s.own {
|
||||||
|
// matches nodes that directly contain the given text
|
||||||
|
text = strings.ToLower(nodeOwnText(n))
|
||||||
|
} else {
|
||||||
|
// matches nodes that contain the given text.
|
||||||
|
text = strings.ToLower(nodeText(n))
|
||||||
|
}
|
||||||
|
return strings.Contains(text, s.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s containsPseudoClassSelector) Specificity() Specificity {
|
||||||
|
return Specificity{0, 1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type regexpPseudoClassSelector struct {
|
||||||
|
own bool
|
||||||
|
regexp *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s regexpPseudoClassSelector) Match(n *html.Node) bool {
|
||||||
|
var text string
|
||||||
|
if s.own {
|
||||||
|
// matches nodes whose text directly matches the specified regular expression
|
||||||
|
text = nodeOwnText(n)
|
||||||
|
} else {
|
||||||
|
// matches nodes whose text matches the specified regular expression
|
||||||
|
text = nodeText(n)
|
||||||
|
}
|
||||||
|
return s.regexp.MatchString(text)
|
||||||
|
}
|
||||||
|
|
||||||
// writeNodeText writes the text contained in n and its descendants to b.
|
// writeNodeText writes the text contained in n and its descendants to b.
|
||||||
func writeNodeText(n *html.Node, b *bytes.Buffer) {
|
func writeNodeText(n *html.Node, b *bytes.Buffer) {
|
||||||
switch n.Type {
|
switch n.Type {
|
||||||
|
@ -325,67 +484,30 @@ func nodeOwnText(n *html.Node) string {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// textSubstrSelector returns a selector that matches nodes that
|
func (s regexpPseudoClassSelector) Specificity() Specificity {
|
||||||
// contain the given text.
|
return Specificity{0, 1, 0}
|
||||||
func textSubstrSelector(val string) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
text := strings.ToLower(nodeText(n))
|
|
||||||
return strings.Contains(text, val)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ownTextSubstrSelector returns a selector that matches nodes that
|
type nthPseudoClassSelector struct {
|
||||||
// directly contain the given text
|
a, b int
|
||||||
func ownTextSubstrSelector(val string) Selector {
|
last, ofType bool
|
||||||
return func(n *html.Node) bool {
|
|
||||||
text := strings.ToLower(nodeOwnText(n))
|
|
||||||
return strings.Contains(text, val)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// textRegexSelector returns a selector that matches nodes whose text matches
|
func (s nthPseudoClassSelector) Match(n *html.Node) bool {
|
||||||
// the specified regular expression
|
if s.a == 0 {
|
||||||
func textRegexSelector(rx *regexp.Regexp) Selector {
|
if s.last {
|
||||||
return func(n *html.Node) bool {
|
return simpleNthLastChildMatch(s.b, s.ofType, n)
|
||||||
return rx.MatchString(nodeText(n))
|
} else {
|
||||||
|
return simpleNthChildMatch(s.b, s.ofType, n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nthChildMatch(s.a, s.b, s.last, s.ofType, n)
|
||||||
|
}
|
||||||
|
|
||||||
// ownTextRegexSelector returns a selector that matches nodes whose text
|
// nthChildMatch implements :nth-child(an+b).
|
||||||
// directly matches the specified regular expression
|
|
||||||
func ownTextRegexSelector(rx *regexp.Regexp) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
return rx.MatchString(nodeOwnText(n))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasChildSelector returns a selector that matches elements
|
|
||||||
// with a child that matches a.
|
|
||||||
func hasChildSelector(a Selector) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if n.Type != html.ElementNode {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return hasChildMatch(n, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasDescendantSelector returns a selector that matches elements
|
|
||||||
// with any descendant that matches a.
|
|
||||||
func hasDescendantSelector(a Selector) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if n.Type != html.ElementNode {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return hasDescendantMatch(n, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nthChildSelector returns a selector that implements :nth-child(an+b).
|
|
||||||
// If last is true, implements :nth-last-child instead.
|
// If last is true, implements :nth-last-child instead.
|
||||||
// If ofType is true, implements :nth-of-type instead.
|
// If ofType is true, implements :nth-of-type instead.
|
||||||
func nthChildSelector(a, b int, last, ofType bool) Selector {
|
func nthChildMatch(a, b int, last, ofType bool, n *html.Node) bool {
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if n.Type != html.ElementNode {
|
if n.Type != html.ElementNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -430,12 +552,10 @@ func nthChildSelector(a, b int, last, ofType bool) Selector {
|
||||||
|
|
||||||
return i%a == 0 && i/a >= 0
|
return i%a == 0 && i/a >= 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// simpleNthChildSelector returns a selector that implements :nth-child(b).
|
// simpleNthChildMatch implements :nth-child(b).
|
||||||
// If ofType is true, implements :nth-of-type instead.
|
// If ofType is true, implements :nth-of-type instead.
|
||||||
func simpleNthChildSelector(b int, ofType bool) Selector {
|
func simpleNthChildMatch(b int, ofType bool, n *html.Node) bool {
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if n.Type != html.ElementNode {
|
if n.Type != html.ElementNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -464,13 +584,10 @@ func simpleNthChildSelector(b int, ofType bool) Selector {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// simpleNthLastChildSelector returns a selector that implements
|
// simpleNthLastChildMatch implements :nth-last-child(b).
|
||||||
// :nth-last-child(b). If ofType is true, implements :nth-last-of-type
|
// If ofType is true, implements :nth-last-of-type instead.
|
||||||
// instead.
|
func simpleNthLastChildMatch(b int, ofType bool, n *html.Node) bool {
|
||||||
func simpleNthLastChildSelector(b int, ofType bool) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if n.Type != html.ElementNode {
|
if n.Type != html.ElementNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -499,12 +616,20 @@ func simpleNthLastChildSelector(b int, ofType bool) Selector {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Specificity for nth-child pseudo-class.
|
||||||
|
// Does not support a list of selectors
|
||||||
|
func (s nthPseudoClassSelector) Specificity() Specificity {
|
||||||
|
return Specificity{0, 1, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
// onlyChildSelector returns a selector that implements :only-child.
|
type onlyChildPseudoClassSelector struct {
|
||||||
// If ofType is true, it implements :only-of-type instead.
|
ofType bool
|
||||||
func onlyChildSelector(ofType bool) Selector {
|
}
|
||||||
return func(n *html.Node) bool {
|
|
||||||
|
// Match implements :only-child.
|
||||||
|
// If `ofType` is true, it implements :only-of-type instead.
|
||||||
|
func (s onlyChildPseudoClassSelector) Match(n *html.Node) bool {
|
||||||
if n.Type != html.ElementNode {
|
if n.Type != html.ElementNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -520,7 +645,7 @@ func onlyChildSelector(ofType bool) Selector {
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
for c := parent.FirstChild; c != nil; c = c.NextSibling {
|
for c := parent.FirstChild; c != nil; c = c.NextSibling {
|
||||||
if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
|
if (c.Type != html.ElementNode) || (s.ofType && c.Data != n.Data) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
|
@ -531,15 +656,26 @@ func onlyChildSelector(ofType bool) Selector {
|
||||||
|
|
||||||
return count == 1
|
return count == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s onlyChildPseudoClassSelector) Specificity() Specificity {
|
||||||
|
return Specificity{0, 1, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputSelector is a Selector that matches input, select, textarea and button elements.
|
type inputPseudoClassSelector struct{}
|
||||||
func inputSelector(n *html.Node) bool {
|
|
||||||
|
// Matches input, select, textarea and button elements.
|
||||||
|
func (s inputPseudoClassSelector) Match(n *html.Node) bool {
|
||||||
return n.Type == html.ElementNode && (n.Data == "input" || n.Data == "select" || n.Data == "textarea" || n.Data == "button")
|
return n.Type == html.ElementNode && (n.Data == "input" || n.Data == "select" || n.Data == "textarea" || n.Data == "button")
|
||||||
}
|
}
|
||||||
|
|
||||||
// emptyElementSelector is a Selector that matches empty elements.
|
func (s inputPseudoClassSelector) Specificity() Specificity {
|
||||||
func emptyElementSelector(n *html.Node) bool {
|
return Specificity{0, 1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type emptyElementPseudoClassSelector struct{}
|
||||||
|
|
||||||
|
// Matches empty elements.
|
||||||
|
func (s emptyElementPseudoClassSelector) Match(n *html.Node) bool {
|
||||||
if n.Type != html.ElementNode {
|
if n.Type != html.ElementNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -554,64 +690,14 @@ func emptyElementSelector(n *html.Node) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// descendantSelector returns a Selector that matches an element if
|
func (s emptyElementPseudoClassSelector) Specificity() Specificity {
|
||||||
// it matches d and has an ancestor that matches a.
|
return Specificity{0, 1, 0}
|
||||||
func descendantSelector(a, d Selector) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if !d(n) {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for p := n.Parent; p != nil; p = p.Parent {
|
type rootPseudoClassSelector struct{}
|
||||||
if a(p) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
// Match implements :root
|
||||||
}
|
func (s rootPseudoClassSelector) Match(n *html.Node) bool {
|
||||||
}
|
|
||||||
|
|
||||||
// childSelector returns a Selector that matches an element if
|
|
||||||
// it matches d and its parent matches a.
|
|
||||||
func childSelector(a, d Selector) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
return d(n) && n.Parent != nil && a(n.Parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// siblingSelector returns a Selector that matches an element
|
|
||||||
// if it matches s2 and in is preceded by an element that matches s1.
|
|
||||||
// If adjacent is true, the sibling must be immediately before the element.
|
|
||||||
func siblingSelector(s1, s2 Selector, adjacent bool) Selector {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
if !s2(n) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if adjacent {
|
|
||||||
for n = n.PrevSibling; n != nil; n = n.PrevSibling {
|
|
||||||
if n.Type == html.TextNode || n.Type == html.CommentNode {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return s1(n)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk backwards looking for element that matches s1
|
|
||||||
for c := n.PrevSibling; c != nil; c = c.PrevSibling {
|
|
||||||
if s1(c) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rootSelector implements :root
|
|
||||||
func rootSelector(n *html.Node) bool {
|
|
||||||
if n.Type != html.ElementNode {
|
if n.Type != html.ElementNode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -620,3 +706,128 @@ func rootSelector(n *html.Node) bool {
|
||||||
}
|
}
|
||||||
return n.Parent.Type == html.DocumentNode
|
return n.Parent.Type == html.DocumentNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s rootPseudoClassSelector) Specificity() Specificity {
|
||||||
|
return Specificity{0, 1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type compoundSelector struct {
|
||||||
|
selectors []Sel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches elements if each sub-selectors matches.
|
||||||
|
func (t compoundSelector) Match(n *html.Node) bool {
|
||||||
|
if len(t.selectors) == 0 {
|
||||||
|
return n.Type == html.ElementNode
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sel := range t.selectors {
|
||||||
|
if !sel.Match(n) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s compoundSelector) Specificity() Specificity {
|
||||||
|
var out Specificity
|
||||||
|
for _, sel := range s.selectors {
|
||||||
|
out = out.Add(sel.Specificity())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
type combinedSelector struct {
|
||||||
|
first Sel
|
||||||
|
combinator byte
|
||||||
|
second Sel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t combinedSelector) Match(n *html.Node) bool {
|
||||||
|
if t.first == nil {
|
||||||
|
return false // maybe we should panic
|
||||||
|
}
|
||||||
|
switch t.combinator {
|
||||||
|
case 0:
|
||||||
|
return t.first.Match(n)
|
||||||
|
case ' ':
|
||||||
|
return descendantMatch(t.first, t.second, n)
|
||||||
|
case '>':
|
||||||
|
return childMatch(t.first, t.second, n)
|
||||||
|
case '+':
|
||||||
|
return siblingMatch(t.first, t.second, true, n)
|
||||||
|
case '~':
|
||||||
|
return siblingMatch(t.first, t.second, false, n)
|
||||||
|
default:
|
||||||
|
panic("unknown combinator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches an element if it matches d and has an ancestor that matches a.
|
||||||
|
func descendantMatch(a, d Matcher, n *html.Node) bool {
|
||||||
|
if !d.Match(n) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for p := n.Parent; p != nil; p = p.Parent {
|
||||||
|
if a.Match(p) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches an element if it matches d and its parent matches a.
|
||||||
|
func childMatch(a, d Matcher, n *html.Node) bool {
|
||||||
|
return d.Match(n) && n.Parent != nil && a.Match(n.Parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// matches an element if it matches s2 and is preceded by an element that matches s1.
|
||||||
|
// If adjacent is true, the sibling must be immediately before the element.
|
||||||
|
func siblingMatch(s1, s2 Matcher, adjacent bool, n *html.Node) bool {
|
||||||
|
if !s2.Match(n) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if adjacent {
|
||||||
|
for n = n.PrevSibling; n != nil; n = n.PrevSibling {
|
||||||
|
if n.Type == html.TextNode || n.Type == html.CommentNode {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return s1.Match(n)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk backwards looking for element that matches s1
|
||||||
|
for c := n.PrevSibling; c != nil; c = c.PrevSibling {
|
||||||
|
if s1.Match(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s combinedSelector) Specificity() Specificity {
|
||||||
|
spec := s.first.Specificity()
|
||||||
|
if s.second != nil {
|
||||||
|
spec = spec.Add(s.second.Specificity())
|
||||||
|
}
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SelectorGroup is a list of selectors, which matches if any of the
|
||||||
|
// individual selectors matches.
|
||||||
|
type SelectorGroup []Sel
|
||||||
|
|
||||||
|
// Match returns true if the node matches one of the single selectors.
|
||||||
|
func (s SelectorGroup) Match(n *html.Node) bool {
|
||||||
|
for _, sel := range s {
|
||||||
|
if sel.Match(n) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package cascadia
|
||||||
|
|
||||||
|
// Specificity is the CSS specificity as defined in
|
||||||
|
// https://www.w3.org/TR/selectors/#specificity-rules
|
||||||
|
// with the convention Specificity = [A,B,C].
|
||||||
|
type Specificity [3]int
|
||||||
|
|
||||||
|
// returns `true` if s < other (strictly), false otherwise
|
||||||
|
func (s Specificity) Less(other Specificity) bool {
|
||||||
|
for i := range s {
|
||||||
|
if s[i] < other[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s[i] > other[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Specificity) Add(other Specificity) Specificity {
|
||||||
|
for i, sp := range other {
|
||||||
|
s[i] += sp
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.7.x
|
|
||||||
- 1.8.x
|
|
||||||
- 1.9.x
|
|
||||||
- 1.10.x
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
- master
|
- master
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
|
@ -99,6 +99,28 @@ matrix:
|
||||||
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||||
- export MYSQL_TEST_CONCURRENT=1
|
- export MYSQL_TEST_CONCURRENT=1
|
||||||
|
|
||||||
|
- os: osx
|
||||||
|
osx_image: xcode10.1
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- mysql
|
||||||
|
update: true
|
||||||
|
go: 1.12.x
|
||||||
|
before_install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
before_script:
|
||||||
|
- echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB\nlocal_infile=1" >> /usr/local/etc/my.cnf
|
||||||
|
- mysql.server start
|
||||||
|
- mysql -uroot -e 'CREATE USER gotest IDENTIFIED BY "secret"'
|
||||||
|
- mysql -uroot -e 'GRANT ALL ON *.* TO gotest'
|
||||||
|
- mysql -uroot -e 'create database gotest;'
|
||||||
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
- export MYSQL_TEST_PASS=secret
|
||||||
|
- export MYSQL_TEST_ADDR=127.0.0.1:3306
|
||||||
|
- export MYSQL_TEST_CONCURRENT=1
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go test -v -covermode=count -coverprofile=coverage.out
|
- go test -v -covermode=count -coverprofile=coverage.out
|
||||||
- go vet ./...
|
- go vet ./...
|
||||||
|
|
|
@ -27,6 +27,7 @@ Daniël van Eeden <git at myname.nl>
|
||||||
Dave Protasowski <dprotaso at gmail.com>
|
Dave Protasowski <dprotaso at gmail.com>
|
||||||
DisposaBoy <disposaboy at dby.me>
|
DisposaBoy <disposaboy at dby.me>
|
||||||
Egor Smolyakov <egorsmkv at gmail.com>
|
Egor Smolyakov <egorsmkv at gmail.com>
|
||||||
|
Erwan Martin <hello at erwan.io>
|
||||||
Evan Shaw <evan at vendhq.com>
|
Evan Shaw <evan at vendhq.com>
|
||||||
Frederick Mayle <frederickmayle at gmail.com>
|
Frederick Mayle <frederickmayle at gmail.com>
|
||||||
Gustavo Kristic <gkristic at gmail.com>
|
Gustavo Kristic <gkristic at gmail.com>
|
||||||
|
@ -34,12 +35,16 @@ Hajime Nakagami <nakagami at gmail.com>
|
||||||
Hanno Braun <mail at hannobraun.com>
|
Hanno Braun <mail at hannobraun.com>
|
||||||
Henri Yandell <flamefew at gmail.com>
|
Henri Yandell <flamefew at gmail.com>
|
||||||
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
||||||
|
Huyiguang <hyg at webterren.com>
|
||||||
ICHINOSE Shogo <shogo82148 at gmail.com>
|
ICHINOSE Shogo <shogo82148 at gmail.com>
|
||||||
|
Ilia Cimpoes <ichimpoesh at gmail.com>
|
||||||
INADA Naoki <songofacandy at gmail.com>
|
INADA Naoki <songofacandy at gmail.com>
|
||||||
Jacek Szwec <szwec.jacek at gmail.com>
|
Jacek Szwec <szwec.jacek at gmail.com>
|
||||||
James Harr <james.harr at gmail.com>
|
James Harr <james.harr at gmail.com>
|
||||||
Jeff Hodges <jeff at somethingsimilar.com>
|
Jeff Hodges <jeff at somethingsimilar.com>
|
||||||
Jeffrey Charles <jeffreycharles at gmail.com>
|
Jeffrey Charles <jeffreycharles at gmail.com>
|
||||||
|
Jerome Meyer <jxmeyer at gmail.com>
|
||||||
|
Jiajia Zhong <zhong2plus at gmail.com>
|
||||||
Jian Zhen <zhenjl at gmail.com>
|
Jian Zhen <zhenjl at gmail.com>
|
||||||
Joshua Prunier <joshua.prunier at gmail.com>
|
Joshua Prunier <joshua.prunier at gmail.com>
|
||||||
Julien Lefevre <julien.lefevr at gmail.com>
|
Julien Lefevre <julien.lefevr at gmail.com>
|
||||||
|
@ -58,6 +63,7 @@ Lucas Liu <extrafliu at gmail.com>
|
||||||
Luke Scott <luke at webconnex.com>
|
Luke Scott <luke at webconnex.com>
|
||||||
Maciej Zimnoch <maciej.zimnoch at codilime.com>
|
Maciej Zimnoch <maciej.zimnoch at codilime.com>
|
||||||
Michael Woolnough <michael.woolnough at gmail.com>
|
Michael Woolnough <michael.woolnough at gmail.com>
|
||||||
|
Nathanial Murphy <nathanial.murphy at gmail.com>
|
||||||
Nicola Peduzzi <thenikso at gmail.com>
|
Nicola Peduzzi <thenikso at gmail.com>
|
||||||
Olivier Mengué <dolmen at cpan.org>
|
Olivier Mengué <dolmen at cpan.org>
|
||||||
oscarzhao <oscarzhaosl at gmail.com>
|
oscarzhao <oscarzhaosl at gmail.com>
|
||||||
|
@ -69,9 +75,15 @@ Richard Wilkes <wilkes at me.com>
|
||||||
Robert Russell <robert at rrbrussell.com>
|
Robert Russell <robert at rrbrussell.com>
|
||||||
Runrioter Wung <runrioter at gmail.com>
|
Runrioter Wung <runrioter at gmail.com>
|
||||||
Shuode Li <elemount at qq.com>
|
Shuode Li <elemount at qq.com>
|
||||||
|
Simon J Mudd <sjmudd at pobox.com>
|
||||||
Soroush Pour <me at soroushjp.com>
|
Soroush Pour <me at soroushjp.com>
|
||||||
Stan Putrya <root.vagner at gmail.com>
|
Stan Putrya <root.vagner at gmail.com>
|
||||||
Stanley Gunawan <gunawan.stanley at gmail.com>
|
Stanley Gunawan <gunawan.stanley at gmail.com>
|
||||||
|
Steven Hartland <steven.hartland at multiplay.co.uk>
|
||||||
|
Thomas Wodarek <wodarekwebpage at gmail.com>
|
||||||
|
Tim Ruffles <timruffles at gmail.com>
|
||||||
|
Tom Jenkinson <tom at tjenkinson.me>
|
||||||
|
Vladimir Kovpak <cn007b at gmail.com>
|
||||||
Xiangyu Hu <xiangyu.hu at outlook.com>
|
Xiangyu Hu <xiangyu.hu at outlook.com>
|
||||||
Xiaobing Jiang <s7v7nislands at gmail.com>
|
Xiaobing Jiang <s7v7nislands at gmail.com>
|
||||||
Xiuming Chen <cc at cxm.cc>
|
Xiuming Chen <cc at cxm.cc>
|
||||||
|
@ -81,9 +93,13 @@ Zhenye Xie <xiezhenye at gmail.com>
|
||||||
|
|
||||||
Barracuda Networks, Inc.
|
Barracuda Networks, Inc.
|
||||||
Counting Ltd.
|
Counting Ltd.
|
||||||
|
DigitalOcean Inc.
|
||||||
|
Facebook Inc.
|
||||||
|
GitHub Inc.
|
||||||
Google Inc.
|
Google Inc.
|
||||||
InfoSum Ltd.
|
InfoSum Ltd.
|
||||||
Keybase Inc.
|
Keybase Inc.
|
||||||
|
Multiplay Ltd.
|
||||||
Percona LLC
|
Percona LLC
|
||||||
Pivotal Inc.
|
Pivotal Inc.
|
||||||
Stripe Inc.
|
Stripe Inc.
|
||||||
|
|
|
@ -1,3 +1,31 @@
|
||||||
|
## Version 1.5 (2020-01-07)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Dropped support Go 1.9 and lower (#823, #829, #886, #1016, #1017)
|
||||||
|
- Improve buffer handling (#890)
|
||||||
|
- Document potentially insecure TLS configs (#901)
|
||||||
|
- Use a double-buffering scheme to prevent data races (#943)
|
||||||
|
- Pass uint64 values without converting them to string (#838, #955)
|
||||||
|
- Update collations and make utf8mb4 default (#877, #1054)
|
||||||
|
- Make NullTime compatible with sql.NullTime in Go 1.13+ (#995)
|
||||||
|
- Removed CloudSQL support (#993, #1007)
|
||||||
|
- Add Go Module support (#1003)
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- Implement support of optional TLS (#900)
|
||||||
|
- Check connection liveness (#934, #964, #997, #1048, #1051, #1052)
|
||||||
|
- Implement Connector Interface (#941, #958, #1020, #1035)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Mark connections as bad on error during ping (#875)
|
||||||
|
- Mark connections as bad on error during dial (#867)
|
||||||
|
- Fix connection leak caused by rapid context cancellation (#1024)
|
||||||
|
- Mark connections as bad on error during Conn.Prepare (#1030)
|
||||||
|
|
||||||
|
|
||||||
## Version 1.4.1 (2018-11-14)
|
## Version 1.4.1 (2018-11-14)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
# Contributing Guidelines
|
|
||||||
|
|
||||||
## Reporting Issues
|
|
||||||
|
|
||||||
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
|
|
||||||
|
|
||||||
## Contributing Code
|
|
||||||
|
|
||||||
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
|
|
||||||
Don't forget to add yourself to the AUTHORS file.
|
|
||||||
|
|
||||||
### Code Review
|
|
||||||
|
|
||||||
Everyone is invited to review and comment on pull requests.
|
|
||||||
If it looks fine to you, comment with "LGTM" (Looks good to me).
|
|
||||||
|
|
||||||
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
|
|
||||||
|
|
||||||
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
|
|
||||||
|
|
||||||
## Development Ideas
|
|
||||||
|
|
||||||
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
|
|
|
@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
|
||||||
* Optional placeholder interpolation
|
* Optional placeholder interpolation
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* Go 1.7 or higher. We aim to support the 3 latest versions of Go.
|
* Go 1.10 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+)
|
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
@ -166,18 +166,34 @@ Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If
|
||||||
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
|
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
|
||||||
Unless you need the fallback behavior, please use `collation` instead.
|
Unless you need the fallback behavior, please use `collation` instead.
|
||||||
|
|
||||||
|
##### `checkConnLiveness`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: bool
|
||||||
|
Valid Values: true, false
|
||||||
|
Default: true
|
||||||
|
```
|
||||||
|
|
||||||
|
On supported platforms connections retrieved from the connection pool are checked for liveness before using them. If the check fails, the respective connection is marked as bad and the query retried with another connection.
|
||||||
|
`checkConnLiveness=false` disables this liveness check of connections.
|
||||||
|
|
||||||
##### `collation`
|
##### `collation`
|
||||||
|
|
||||||
```
|
```
|
||||||
Type: string
|
Type: string
|
||||||
Valid Values: <name>
|
Valid Values: <name>
|
||||||
Default: utf8_general_ci
|
Default: utf8mb4_general_ci
|
||||||
```
|
```
|
||||||
|
|
||||||
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
|
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
|
||||||
|
|
||||||
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
|
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
|
||||||
|
|
||||||
|
The default collation (`utf8mb4_general_ci`) is supported from MySQL 5.5. You should use an older collation (e.g. `utf8_general_ci`) for older MySQL.
|
||||||
|
|
||||||
|
Collations for charset "ucs2", "utf16", "utf16le", and "utf32" can not be used ([ref](https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset)).
|
||||||
|
|
||||||
|
|
||||||
##### `clientFoundRows`
|
##### `clientFoundRows`
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -328,11 +344,11 @@ Timeout for establishing connections, aka dial timeout. The value must be a deci
|
||||||
|
|
||||||
```
|
```
|
||||||
Type: bool / string
|
Type: bool / string
|
||||||
Valid Values: true, false, skip-verify, <name>
|
Valid Values: true, false, skip-verify, preferred, <name>
|
||||||
Default: false
|
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). Use a custom value registered 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) 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).
|
||||||
|
|
||||||
|
|
||||||
##### `writeTimeout`
|
##### `writeTimeout`
|
||||||
|
@ -391,14 +407,9 @@ TCP on a remote host, e.g. Amazon RDS:
|
||||||
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
|
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
|
||||||
```
|
```
|
||||||
|
|
||||||
Google Cloud SQL on App Engine (First Generation MySQL Server):
|
Google Cloud SQL on App Engine:
|
||||||
```
|
```
|
||||||
user@cloudsql(project-id:instance-name)/dbname
|
user:password@unix(/cloudsql/project-id:region-name:instance-name)/dbname
|
||||||
```
|
|
||||||
|
|
||||||
Google Cloud SQL on App Engine (Second Generation MySQL Server):
|
|
||||||
```
|
|
||||||
user@cloudsql(project-id:regionname:instance-name)/dbname
|
|
||||||
```
|
```
|
||||||
|
|
||||||
TCP using default port (3306) on localhost:
|
TCP using default port (3306) on localhost:
|
||||||
|
@ -444,7 +455,7 @@ See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/my
|
||||||
### `time.Time` support
|
### `time.Time` support
|
||||||
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
|
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
|
||||||
|
|
||||||
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical equivalent in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
||||||
|
|
||||||
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
||||||
|
|
||||||
|
@ -452,13 +463,13 @@ Alternatively you can use the [`NullTime`](https://godoc.org/github.com/go-sql-d
|
||||||
|
|
||||||
|
|
||||||
### Unicode support
|
### Unicode support
|
||||||
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
|
Since version 1.5 Go-MySQL-Driver automatically uses the collation ` utf8mb4_general_ci` by default.
|
||||||
|
|
||||||
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
|
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
|
||||||
|
|
||||||
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
|
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
|
||||||
|
|
||||||
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
|
See http://dev.mysql.com/doc/refman/8.0/en/charset-unicode.html for more details on MySQL's Unicode support.
|
||||||
|
|
||||||
## Testing / Development
|
## Testing / Development
|
||||||
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
||||||
|
|
|
@ -360,13 +360,15 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
|
||||||
pubKey := mc.cfg.pubKey
|
pubKey := mc.cfg.pubKey
|
||||||
if pubKey == nil {
|
if pubKey == nil {
|
||||||
// request public key from server
|
// request public key from server
|
||||||
data := mc.buf.takeSmallBuffer(4 + 1)
|
data, err := mc.buf.takeSmallBuffer(4 + 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
data[4] = cachingSha2PasswordRequestPublicKey
|
data[4] = cachingSha2PasswordRequestPublicKey
|
||||||
mc.writePacket(data)
|
mc.writePacket(data)
|
||||||
|
|
||||||
// parse public key
|
// parse public key
|
||||||
data, err := mc.readPacket()
|
if data, err = mc.readPacket(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,47 +15,69 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultBufSize = 4096
|
const defaultBufSize = 4096
|
||||||
|
const maxCachedBufSize = 256 * 1024
|
||||||
|
|
||||||
// A buffer which is used for both reading and writing.
|
// A buffer which is used for both reading and writing.
|
||||||
// This is possible since communication on each connection is synchronous.
|
// This is possible since communication on each connection is synchronous.
|
||||||
// In other words, we can't write and read simultaneously on the same connection.
|
// In other words, we can't write and read simultaneously on the same connection.
|
||||||
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
||||||
// Also highly optimized for this particular use case.
|
// Also highly optimized for this particular use case.
|
||||||
|
// This buffer is backed by two byte slices in a double-buffering scheme
|
||||||
type buffer struct {
|
type buffer struct {
|
||||||
buf []byte
|
buf []byte // buf is a byte buffer who's length and capacity are equal.
|
||||||
nc net.Conn
|
nc net.Conn
|
||||||
idx int
|
idx int
|
||||||
length int
|
length int
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
dbuf [2][]byte // dbuf is an array with the two byte slices that back this buffer
|
||||||
|
flipcnt uint // flipccnt is the current buffer counter for double-buffering
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newBuffer allocates and returns a new buffer.
|
||||||
func newBuffer(nc net.Conn) buffer {
|
func newBuffer(nc net.Conn) buffer {
|
||||||
var b [defaultBufSize]byte
|
fg := make([]byte, defaultBufSize)
|
||||||
return buffer{
|
return buffer{
|
||||||
buf: b[:],
|
buf: fg,
|
||||||
nc: nc,
|
nc: nc,
|
||||||
|
dbuf: [2][]byte{fg, nil},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flip replaces the active buffer with the background buffer
|
||||||
|
// this is a delayed flip that simply increases the buffer counter;
|
||||||
|
// the actual flip will be performed the next time we call `buffer.fill`
|
||||||
|
func (b *buffer) flip() {
|
||||||
|
b.flipcnt += 1
|
||||||
|
}
|
||||||
|
|
||||||
// fill reads into the buffer until at least _need_ bytes are in it
|
// fill reads into the buffer until at least _need_ bytes are in it
|
||||||
func (b *buffer) fill(need int) error {
|
func (b *buffer) fill(need int) error {
|
||||||
n := b.length
|
n := b.length
|
||||||
|
// fill data into its double-buffering target: if we've called
|
||||||
|
// flip on this buffer, we'll be copying to the background buffer,
|
||||||
|
// and then filling it with network data; otherwise we'll just move
|
||||||
|
// the contents of the current buffer to the front before filling it
|
||||||
|
dest := b.dbuf[b.flipcnt&1]
|
||||||
|
|
||||||
// move existing data to the beginning
|
// grow buffer if necessary to fit the whole packet.
|
||||||
if n > 0 && b.idx > 0 {
|
if need > len(dest) {
|
||||||
copy(b.buf[0:n], b.buf[b.idx:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// grow buffer if necessary
|
|
||||||
// TODO: let the buffer shrink again at some point
|
|
||||||
// Maybe keep the org buf slice and swap back?
|
|
||||||
if need > len(b.buf) {
|
|
||||||
// Round up to the next multiple of the default size
|
// Round up to the next multiple of the default size
|
||||||
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
|
dest = make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
|
||||||
copy(newBuf, b.buf)
|
|
||||||
b.buf = newBuf
|
// if the allocated buffer is not too large, move it to backing storage
|
||||||
|
// to prevent extra allocations on applications that perform large reads
|
||||||
|
if len(dest) <= maxCachedBufSize {
|
||||||
|
b.dbuf[b.flipcnt&1] = dest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we're filling the fg buffer, move the existing data to the start of it.
|
||||||
|
// if we're filling the bg buffer, copy over the data
|
||||||
|
if n > 0 {
|
||||||
|
copy(dest[:n], b.buf[b.idx:])
|
||||||
|
}
|
||||||
|
|
||||||
|
b.buf = dest
|
||||||
b.idx = 0
|
b.idx = 0
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -105,43 +127,56 @@ func (b *buffer) readNext(need int) ([]byte, error) {
|
||||||
return b.buf[offset:b.idx], nil
|
return b.buf[offset:b.idx], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a buffer with the requested size.
|
// takeBuffer returns a buffer with the requested size.
|
||||||
// If possible, a slice from the existing buffer is returned.
|
// If possible, a slice from the existing buffer is returned.
|
||||||
// Otherwise a bigger buffer is made.
|
// Otherwise a bigger buffer is made.
|
||||||
// Only one buffer (total) can be used at a time.
|
// Only one buffer (total) can be used at a time.
|
||||||
func (b *buffer) takeBuffer(length int) []byte {
|
func (b *buffer) takeBuffer(length int) ([]byte, error) {
|
||||||
if b.length > 0 {
|
if b.length > 0 {
|
||||||
return nil
|
return nil, ErrBusyBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// test (cheap) general case first
|
// test (cheap) general case first
|
||||||
if length <= defaultBufSize || length <= cap(b.buf) {
|
if length <= cap(b.buf) {
|
||||||
return b.buf[:length]
|
return b.buf[:length], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if length < maxPacketSize {
|
if length < maxPacketSize {
|
||||||
b.buf = make([]byte, length)
|
b.buf = make([]byte, length)
|
||||||
return b.buf
|
return b.buf, nil
|
||||||
}
|
|
||||||
return make([]byte, length)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// shortcut which can be used if the requested buffer is guaranteed to be
|
// buffer is larger than we want to store.
|
||||||
// smaller than defaultBufSize
|
return make([]byte, length), nil
|
||||||
// Only one buffer (total) can be used at a time.
|
|
||||||
func (b *buffer) takeSmallBuffer(length int) []byte {
|
|
||||||
if b.length > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return b.buf[:length]
|
|
||||||
|
// takeSmallBuffer is shortcut which can be used if length is
|
||||||
|
// known to be smaller than defaultBufSize.
|
||||||
|
// Only one buffer (total) can be used at a time.
|
||||||
|
func (b *buffer) takeSmallBuffer(length int) ([]byte, error) {
|
||||||
|
if b.length > 0 {
|
||||||
|
return nil, ErrBusyBuffer
|
||||||
|
}
|
||||||
|
return b.buf[:length], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// takeCompleteBuffer returns the complete existing buffer.
|
// takeCompleteBuffer returns the complete existing buffer.
|
||||||
// This can be used if the necessary buffer size is unknown.
|
// 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.
|
// Only one buffer (total) can be used at a time.
|
||||||
func (b *buffer) takeCompleteBuffer() []byte {
|
func (b *buffer) takeCompleteBuffer() ([]byte, error) {
|
||||||
if b.length > 0 {
|
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 {
|
||||||
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,12 +8,18 @@
|
||||||
|
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
const defaultCollation = "utf8_general_ci"
|
const defaultCollation = "utf8mb4_general_ci"
|
||||||
const binaryCollation = "binary"
|
const binaryCollation = "binary"
|
||||||
|
|
||||||
// A list of available collations mapped to the internal ID.
|
// A list of available collations mapped to the internal ID.
|
||||||
// To update this map use the following MySQL query:
|
// To update this map use the following MySQL query:
|
||||||
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
|
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS WHERE ID<256 ORDER BY ID
|
||||||
|
//
|
||||||
|
// Handshake packet have only 1 byte for collation_id. So we can't use collations with ID > 255.
|
||||||
|
//
|
||||||
|
// ucs2, utf16, and utf32 can't be used for connection charset.
|
||||||
|
// https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html#charset-connection-impermissible-client-charset
|
||||||
|
// They are commented out to reduce this map.
|
||||||
var collations = map[string]byte{
|
var collations = map[string]byte{
|
||||||
"big5_chinese_ci": 1,
|
"big5_chinese_ci": 1,
|
||||||
"latin2_czech_cs": 2,
|
"latin2_czech_cs": 2,
|
||||||
|
@ -48,7 +54,7 @@ var collations = map[string]byte{
|
||||||
"armscii8_general_ci": 32,
|
"armscii8_general_ci": 32,
|
||||||
"utf8_general_ci": 33,
|
"utf8_general_ci": 33,
|
||||||
"cp1250_czech_cs": 34,
|
"cp1250_czech_cs": 34,
|
||||||
"ucs2_general_ci": 35,
|
//"ucs2_general_ci": 35,
|
||||||
"cp866_general_ci": 36,
|
"cp866_general_ci": 36,
|
||||||
"keybcs2_general_ci": 37,
|
"keybcs2_general_ci": 37,
|
||||||
"macce_general_ci": 38,
|
"macce_general_ci": 38,
|
||||||
|
@ -67,15 +73,15 @@ var collations = map[string]byte{
|
||||||
"cp1251_general_ci": 51,
|
"cp1251_general_ci": 51,
|
||||||
"cp1251_general_cs": 52,
|
"cp1251_general_cs": 52,
|
||||||
"macroman_bin": 53,
|
"macroman_bin": 53,
|
||||||
"utf16_general_ci": 54,
|
//"utf16_general_ci": 54,
|
||||||
"utf16_bin": 55,
|
//"utf16_bin": 55,
|
||||||
"utf16le_general_ci": 56,
|
//"utf16le_general_ci": 56,
|
||||||
"cp1256_general_ci": 57,
|
"cp1256_general_ci": 57,
|
||||||
"cp1257_bin": 58,
|
"cp1257_bin": 58,
|
||||||
"cp1257_general_ci": 59,
|
"cp1257_general_ci": 59,
|
||||||
"utf32_general_ci": 60,
|
//"utf32_general_ci": 60,
|
||||||
"utf32_bin": 61,
|
//"utf32_bin": 61,
|
||||||
"utf16le_bin": 62,
|
//"utf16le_bin": 62,
|
||||||
"binary": 63,
|
"binary": 63,
|
||||||
"armscii8_bin": 64,
|
"armscii8_bin": 64,
|
||||||
"ascii_bin": 65,
|
"ascii_bin": 65,
|
||||||
|
@ -89,6 +95,7 @@ var collations = map[string]byte{
|
||||||
"keybcs2_bin": 73,
|
"keybcs2_bin": 73,
|
||||||
"koi8r_bin": 74,
|
"koi8r_bin": 74,
|
||||||
"koi8u_bin": 75,
|
"koi8u_bin": 75,
|
||||||
|
"utf8_tolower_ci": 76,
|
||||||
"latin2_bin": 77,
|
"latin2_bin": 77,
|
||||||
"latin5_bin": 78,
|
"latin5_bin": 78,
|
||||||
"latin7_bin": 79,
|
"latin7_bin": 79,
|
||||||
|
@ -102,7 +109,7 @@ var collations = map[string]byte{
|
||||||
"gbk_bin": 87,
|
"gbk_bin": 87,
|
||||||
"sjis_bin": 88,
|
"sjis_bin": 88,
|
||||||
"tis620_bin": 89,
|
"tis620_bin": 89,
|
||||||
"ucs2_bin": 90,
|
//"ucs2_bin": 90,
|
||||||
"ujis_bin": 91,
|
"ujis_bin": 91,
|
||||||
"geostd8_general_ci": 92,
|
"geostd8_general_ci": 92,
|
||||||
"geostd8_bin": 93,
|
"geostd8_bin": 93,
|
||||||
|
@ -112,79 +119,79 @@ var collations = map[string]byte{
|
||||||
"eucjpms_japanese_ci": 97,
|
"eucjpms_japanese_ci": 97,
|
||||||
"eucjpms_bin": 98,
|
"eucjpms_bin": 98,
|
||||||
"cp1250_polish_ci": 99,
|
"cp1250_polish_ci": 99,
|
||||||
"utf16_unicode_ci": 101,
|
//"utf16_unicode_ci": 101,
|
||||||
"utf16_icelandic_ci": 102,
|
//"utf16_icelandic_ci": 102,
|
||||||
"utf16_latvian_ci": 103,
|
//"utf16_latvian_ci": 103,
|
||||||
"utf16_romanian_ci": 104,
|
//"utf16_romanian_ci": 104,
|
||||||
"utf16_slovenian_ci": 105,
|
//"utf16_slovenian_ci": 105,
|
||||||
"utf16_polish_ci": 106,
|
//"utf16_polish_ci": 106,
|
||||||
"utf16_estonian_ci": 107,
|
//"utf16_estonian_ci": 107,
|
||||||
"utf16_spanish_ci": 108,
|
//"utf16_spanish_ci": 108,
|
||||||
"utf16_swedish_ci": 109,
|
//"utf16_swedish_ci": 109,
|
||||||
"utf16_turkish_ci": 110,
|
//"utf16_turkish_ci": 110,
|
||||||
"utf16_czech_ci": 111,
|
//"utf16_czech_ci": 111,
|
||||||
"utf16_danish_ci": 112,
|
//"utf16_danish_ci": 112,
|
||||||
"utf16_lithuanian_ci": 113,
|
//"utf16_lithuanian_ci": 113,
|
||||||
"utf16_slovak_ci": 114,
|
//"utf16_slovak_ci": 114,
|
||||||
"utf16_spanish2_ci": 115,
|
//"utf16_spanish2_ci": 115,
|
||||||
"utf16_roman_ci": 116,
|
//"utf16_roman_ci": 116,
|
||||||
"utf16_persian_ci": 117,
|
//"utf16_persian_ci": 117,
|
||||||
"utf16_esperanto_ci": 118,
|
//"utf16_esperanto_ci": 118,
|
||||||
"utf16_hungarian_ci": 119,
|
//"utf16_hungarian_ci": 119,
|
||||||
"utf16_sinhala_ci": 120,
|
//"utf16_sinhala_ci": 120,
|
||||||
"utf16_german2_ci": 121,
|
//"utf16_german2_ci": 121,
|
||||||
"utf16_croatian_ci": 122,
|
//"utf16_croatian_ci": 122,
|
||||||
"utf16_unicode_520_ci": 123,
|
//"utf16_unicode_520_ci": 123,
|
||||||
"utf16_vietnamese_ci": 124,
|
//"utf16_vietnamese_ci": 124,
|
||||||
"ucs2_unicode_ci": 128,
|
//"ucs2_unicode_ci": 128,
|
||||||
"ucs2_icelandic_ci": 129,
|
//"ucs2_icelandic_ci": 129,
|
||||||
"ucs2_latvian_ci": 130,
|
//"ucs2_latvian_ci": 130,
|
||||||
"ucs2_romanian_ci": 131,
|
//"ucs2_romanian_ci": 131,
|
||||||
"ucs2_slovenian_ci": 132,
|
//"ucs2_slovenian_ci": 132,
|
||||||
"ucs2_polish_ci": 133,
|
//"ucs2_polish_ci": 133,
|
||||||
"ucs2_estonian_ci": 134,
|
//"ucs2_estonian_ci": 134,
|
||||||
"ucs2_spanish_ci": 135,
|
//"ucs2_spanish_ci": 135,
|
||||||
"ucs2_swedish_ci": 136,
|
//"ucs2_swedish_ci": 136,
|
||||||
"ucs2_turkish_ci": 137,
|
//"ucs2_turkish_ci": 137,
|
||||||
"ucs2_czech_ci": 138,
|
//"ucs2_czech_ci": 138,
|
||||||
"ucs2_danish_ci": 139,
|
//"ucs2_danish_ci": 139,
|
||||||
"ucs2_lithuanian_ci": 140,
|
//"ucs2_lithuanian_ci": 140,
|
||||||
"ucs2_slovak_ci": 141,
|
//"ucs2_slovak_ci": 141,
|
||||||
"ucs2_spanish2_ci": 142,
|
//"ucs2_spanish2_ci": 142,
|
||||||
"ucs2_roman_ci": 143,
|
//"ucs2_roman_ci": 143,
|
||||||
"ucs2_persian_ci": 144,
|
//"ucs2_persian_ci": 144,
|
||||||
"ucs2_esperanto_ci": 145,
|
//"ucs2_esperanto_ci": 145,
|
||||||
"ucs2_hungarian_ci": 146,
|
//"ucs2_hungarian_ci": 146,
|
||||||
"ucs2_sinhala_ci": 147,
|
//"ucs2_sinhala_ci": 147,
|
||||||
"ucs2_german2_ci": 148,
|
//"ucs2_german2_ci": 148,
|
||||||
"ucs2_croatian_ci": 149,
|
//"ucs2_croatian_ci": 149,
|
||||||
"ucs2_unicode_520_ci": 150,
|
//"ucs2_unicode_520_ci": 150,
|
||||||
"ucs2_vietnamese_ci": 151,
|
//"ucs2_vietnamese_ci": 151,
|
||||||
"ucs2_general_mysql500_ci": 159,
|
//"ucs2_general_mysql500_ci": 159,
|
||||||
"utf32_unicode_ci": 160,
|
//"utf32_unicode_ci": 160,
|
||||||
"utf32_icelandic_ci": 161,
|
//"utf32_icelandic_ci": 161,
|
||||||
"utf32_latvian_ci": 162,
|
//"utf32_latvian_ci": 162,
|
||||||
"utf32_romanian_ci": 163,
|
//"utf32_romanian_ci": 163,
|
||||||
"utf32_slovenian_ci": 164,
|
//"utf32_slovenian_ci": 164,
|
||||||
"utf32_polish_ci": 165,
|
//"utf32_polish_ci": 165,
|
||||||
"utf32_estonian_ci": 166,
|
//"utf32_estonian_ci": 166,
|
||||||
"utf32_spanish_ci": 167,
|
//"utf32_spanish_ci": 167,
|
||||||
"utf32_swedish_ci": 168,
|
//"utf32_swedish_ci": 168,
|
||||||
"utf32_turkish_ci": 169,
|
//"utf32_turkish_ci": 169,
|
||||||
"utf32_czech_ci": 170,
|
//"utf32_czech_ci": 170,
|
||||||
"utf32_danish_ci": 171,
|
//"utf32_danish_ci": 171,
|
||||||
"utf32_lithuanian_ci": 172,
|
//"utf32_lithuanian_ci": 172,
|
||||||
"utf32_slovak_ci": 173,
|
//"utf32_slovak_ci": 173,
|
||||||
"utf32_spanish2_ci": 174,
|
//"utf32_spanish2_ci": 174,
|
||||||
"utf32_roman_ci": 175,
|
//"utf32_roman_ci": 175,
|
||||||
"utf32_persian_ci": 176,
|
//"utf32_persian_ci": 176,
|
||||||
"utf32_esperanto_ci": 177,
|
//"utf32_esperanto_ci": 177,
|
||||||
"utf32_hungarian_ci": 178,
|
//"utf32_hungarian_ci": 178,
|
||||||
"utf32_sinhala_ci": 179,
|
//"utf32_sinhala_ci": 179,
|
||||||
"utf32_german2_ci": 180,
|
//"utf32_german2_ci": 180,
|
||||||
"utf32_croatian_ci": 181,
|
//"utf32_croatian_ci": 181,
|
||||||
"utf32_unicode_520_ci": 182,
|
//"utf32_unicode_520_ci": 182,
|
||||||
"utf32_vietnamese_ci": 183,
|
//"utf32_vietnamese_ci": 183,
|
||||||
"utf8_unicode_ci": 192,
|
"utf8_unicode_ci": 192,
|
||||||
"utf8_icelandic_ci": 193,
|
"utf8_icelandic_ci": 193,
|
||||||
"utf8_latvian_ci": 194,
|
"utf8_latvian_ci": 194,
|
||||||
|
@ -234,6 +241,10 @@ var collations = map[string]byte{
|
||||||
"utf8mb4_croatian_ci": 245,
|
"utf8mb4_croatian_ci": 245,
|
||||||
"utf8mb4_unicode_520_ci": 246,
|
"utf8mb4_unicode_520_ci": 246,
|
||||||
"utf8mb4_vietnamese_ci": 247,
|
"utf8mb4_vietnamese_ci": 247,
|
||||||
|
"gb18030_chinese_ci": 248,
|
||||||
|
"gb18030_bin": 249,
|
||||||
|
"gb18030_unicode_520_ci": 250,
|
||||||
|
"utf8mb4_0900_ai_ci": 255,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A blacklist of collations which is unsafe to interpolate parameters.
|
// A blacklist of collations which is unsafe to interpolate parameters.
|
||||||
|
@ -248,4 +259,7 @@ var unsafeCollations = map[string]bool{
|
||||||
"sjis_bin": true,
|
"sjis_bin": true,
|
||||||
"cp932_japanese_ci": true,
|
"cp932_japanese_ci": true,
|
||||||
"cp932_bin": true,
|
"cp932_bin": true,
|
||||||
|
"gb18030_chinese_ci": true,
|
||||||
|
"gb18030_bin": true,
|
||||||
|
"gb18030_unicode_520_ci": true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2019 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 linux darwin dragonfly freebsd netbsd openbsd solaris illumos
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errUnexpectedRead = errors.New("unexpected read from socket")
|
||||||
|
|
||||||
|
func connCheck(conn net.Conn) error {
|
||||||
|
var sysErr error
|
||||||
|
|
||||||
|
sysConn, ok := conn.(syscall.Conn)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rawConn, err := sysConn.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rawConn.Read(func(fd uintptr) bool {
|
||||||
|
var buf [1]byte
|
||||||
|
n, err := syscall.Read(int(fd), buf[:])
|
||||||
|
switch {
|
||||||
|
case n == 0 && err == nil:
|
||||||
|
sysErr = io.EOF
|
||||||
|
case n > 0:
|
||||||
|
sysErr = errUnexpectedRead
|
||||||
|
case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
|
||||||
|
sysErr = nil
|
||||||
|
default:
|
||||||
|
sysErr = err
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sysErr
|
||||||
|
}
|
|
@ -1,19 +1,17 @@
|
||||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
//
|
//
|
||||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
// Copyright 2019 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
//
|
//
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
// 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,
|
// 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/.
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
// +build appengine
|
// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
|
||||||
|
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import "net"
|
||||||
"google.golang.org/appengine/cloudsql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func connCheck(conn net.Conn) error {
|
||||||
RegisterDial("cloudsql", cloudsql.Dial)
|
return nil
|
||||||
}
|
}
|
|
@ -9,6 +9,8 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
@ -17,19 +19,10 @@ import (
|
||||||
"time"
|
"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 {
|
type mysqlConn struct {
|
||||||
buf buffer
|
buf buffer
|
||||||
netConn net.Conn
|
netConn net.Conn
|
||||||
|
rawConn net.Conn // underlying connection when netConn is TLS connection.
|
||||||
affectedRows uint64
|
affectedRows uint64
|
||||||
insertId uint64
|
insertId uint64
|
||||||
cfg *Config
|
cfg *Config
|
||||||
|
@ -40,10 +33,11 @@ type mysqlConn struct {
|
||||||
status statusFlag
|
status statusFlag
|
||||||
sequence uint8
|
sequence uint8
|
||||||
parseTime bool
|
parseTime bool
|
||||||
|
reset bool // set when the Go SQL package calls ResetSession
|
||||||
|
|
||||||
// for context support (Go 1.8+)
|
// for context support (Go 1.8+)
|
||||||
watching bool
|
watching bool
|
||||||
watcher chan<- mysqlContext
|
watcher chan<- context.Context
|
||||||
closech chan struct{}
|
closech chan struct{}
|
||||||
finished chan<- struct{}
|
finished chan<- struct{}
|
||||||
canceled atomicError // set non-nil if conn is canceled
|
canceled atomicError // set non-nil if conn is canceled
|
||||||
|
@ -160,7 +154,9 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
// Send command
|
// Send command
|
||||||
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, mc.markBadConn(err)
|
// STMT_PREPARE is safe to retry. So we can return ErrBadConn here.
|
||||||
|
errLog.Print(err)
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt := &mysqlStmt{
|
stmt := &mysqlStmt{
|
||||||
|
@ -190,10 +186,10 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
|
||||||
return "", driver.ErrSkip
|
return "", driver.ErrSkip
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := mc.buf.takeCompleteBuffer()
|
buf, err := mc.buf.takeCompleteBuffer()
|
||||||
if buf == nil {
|
if err != nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// can not take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return "", ErrInvalidConn
|
return "", ErrInvalidConn
|
||||||
}
|
}
|
||||||
buf = buf[:0]
|
buf = buf[:0]
|
||||||
|
@ -219,6 +215,9 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
|
||||||
switch v := arg.(type) {
|
switch v := arg.(type) {
|
||||||
case int64:
|
case int64:
|
||||||
buf = strconv.AppendInt(buf, v, 10)
|
buf = strconv.AppendInt(buf, v, 10)
|
||||||
|
case uint64:
|
||||||
|
// Handle uint64 explicitly because our custom ConvertValue emits unsigned values
|
||||||
|
buf = strconv.AppendUint(buf, v, 10)
|
||||||
case float64:
|
case float64:
|
||||||
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
|
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
|
||||||
case bool:
|
case bool:
|
||||||
|
@ -459,3 +458,194 @@ func (mc *mysqlConn) finish() {
|
||||||
case <-mc.closech:
|
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
|
||||||
|
}
|
||||||
|
mc.reset = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,207 +0,0 @@
|
||||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
|
||||||
//
|
|
||||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
||||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package mysql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"database/sql/driver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ping implements driver.Pinger interface
|
|
||||||
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
|
|
||||||
if mc.closed.IsSet() {
|
|
||||||
errLog.Print(ErrInvalidConn)
|
|
||||||
return driver.ErrBadConn
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = mc.watchCancel(ctx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer mc.finish()
|
|
||||||
|
|
||||||
if err = mc.writeCommandPacket(comPing); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return mc.readResultOK()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BeginTx implements driver.ConnBeginTx interface
|
|
||||||
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
|
||||||
if err := mc.watchCancel(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer mc.finish()
|
|
||||||
|
|
||||||
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
|
|
||||||
level, err := mapIsolationLevel(opts.Isolation)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mc.begin(opts.ReadOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
|
||||||
dargs, err := namedValueToValue(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := mc.watchCancel(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := mc.query(query, dargs)
|
|
||||||
if err != nil {
|
|
||||||
mc.finish()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows.finish = mc.finish
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
|
||||||
dargs, err := namedValueToValue(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := mc.watchCancel(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer mc.finish()
|
|
||||||
|
|
||||||
return mc.Exec(query, dargs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
|
||||||
if err := mc.watchCancel(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := mc.Prepare(query)
|
|
||||||
mc.finish()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
default:
|
|
||||||
case <-ctx.Done():
|
|
||||||
stmt.Close()
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
return stmt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
|
||||||
dargs, err := namedValueToValue(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stmt.mc.watchCancel(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := stmt.query(dargs)
|
|
||||||
if err != nil {
|
|
||||||
stmt.mc.finish()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows.finish = stmt.mc.finish
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
|
||||||
dargs, err := namedValueToValue(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stmt.mc.watchCancel(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer stmt.mc.finish()
|
|
||||||
|
|
||||||
return stmt.Exec(dargs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
|
|
||||||
if mc.watching {
|
|
||||||
// Reach here if canceled,
|
|
||||||
// so the connection is already invalid
|
|
||||||
mc.cleanup()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// When ctx is already cancelled, don't watch it.
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// When ctx is not cancellable, don't watch it.
|
|
||||||
if ctx.Done() == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// When watcher is not alive, can't watch it.
|
|
||||||
if mc.watcher == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mc.watching = true
|
|
||||||
mc.watcher <- ctx
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mysqlConn) startWatcher() {
|
|
||||||
watcher := make(chan mysqlContext, 1)
|
|
||||||
mc.watcher = watcher
|
|
||||||
finished := make(chan struct{})
|
|
||||||
mc.finished = finished
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
var ctx mysqlContext
|
|
||||||
select {
|
|
||||||
case ctx = <-watcher:
|
|
||||||
case <-mc.closech:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
mc.cancel(ctx.Err())
|
|
||||||
case <-finished:
|
|
||||||
case <-mc.closech:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
|
|
||||||
nv.Value, err = converter{}.ConvertValue(nv.Value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetSession implements driver.SessionResetter.
|
|
||||||
// (From Go 1.10)
|
|
||||||
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
|
|
||||||
if mc.closed.IsSet() {
|
|
||||||
return driver.ErrBadConn
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2018 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/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type connector struct {
|
||||||
|
cfg *Config // immutable private copy.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect implements driver.Connector interface.
|
||||||
|
// Connect returns a connection to the database.
|
||||||
|
func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// New mysqlConn
|
||||||
|
mc := &mysqlConn{
|
||||||
|
maxAllowedPacket: maxPacketSize,
|
||||||
|
maxWriteSize: maxPacketSize - 1,
|
||||||
|
closech: make(chan struct{}),
|
||||||
|
cfg: c.cfg,
|
||||||
|
}
|
||||||
|
mc.parseTime = mc.cfg.ParseTime
|
||||||
|
|
||||||
|
// Connect to Server
|
||||||
|
dialsLock.RLock()
|
||||||
|
dial, ok := dials[mc.cfg.Net]
|
||||||
|
dialsLock.RUnlock()
|
||||||
|
if ok {
|
||||||
|
dctx := ctx
|
||||||
|
if mc.cfg.Timeout > 0 {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
dctx, cancel = context.WithTimeout(ctx, c.cfg.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
mc.netConn, err = dial(dctx, mc.cfg.Addr)
|
||||||
|
} else {
|
||||||
|
nd := net.Dialer{Timeout: mc.cfg.Timeout}
|
||||||
|
mc.netConn, err = nd.DialContext(ctx, mc.cfg.Net, mc.cfg.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable TCP Keepalives on TCP connections
|
||||||
|
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
||||||
|
if err := tc.SetKeepAlive(true); err != nil {
|
||||||
|
// Don't send COM_QUIT before handshake.
|
||||||
|
mc.netConn.Close()
|
||||||
|
mc.netConn = nil
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call startWatcher for context support (From Go 1.8)
|
||||||
|
mc.startWatcher()
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
mc.buf = newBuffer(mc.netConn)
|
||||||
|
|
||||||
|
// Set I/O timeouts
|
||||||
|
mc.buf.timeout = mc.cfg.ReadTimeout
|
||||||
|
mc.writeTimeout = mc.cfg.WriteTimeout
|
||||||
|
|
||||||
|
// Reading Handshake Initialization Packet
|
||||||
|
authData, plugin, err := mc.readHandshakePacket()
|
||||||
|
if err != nil {
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if plugin == "" {
|
||||||
|
plugin = defaultAuthPlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send Client Authentication Packet
|
||||||
|
authResp, err := mc.auth(authData, plugin)
|
||||||
|
if err != nil {
|
||||||
|
// try the default auth plugin, if using the requested plugin failed
|
||||||
|
errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
|
||||||
|
plugin = defaultAuthPlugin
|
||||||
|
authResp, err = mc.auth(authData, plugin)
|
||||||
|
if err != nil {
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle response to auth packet, switch methods if possible
|
||||||
|
if err = mc.handleAuthResult(authData, plugin); err != nil {
|
||||||
|
// Authentication failed and MySQL has already closed the connection
|
||||||
|
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
|
||||||
|
// Do not send COM_QUIT, just cleanup and return the error.
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if mc.cfg.MaxAllowedPacket > 0 {
|
||||||
|
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
|
||||||
|
} else {
|
||||||
|
// Get max allowed packet size
|
||||||
|
maxap, err := mc.getSystemVar("max_allowed_packet")
|
||||||
|
if err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mc.maxAllowedPacket = stringToInt(maxap) - 1
|
||||||
|
}
|
||||||
|
if mc.maxAllowedPacket < maxPacketSize {
|
||||||
|
mc.maxWriteSize = mc.maxAllowedPacket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle DSN Params
|
||||||
|
err = mc.handleParams()
|
||||||
|
if err != nil {
|
||||||
|
mc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver implements driver.Connector interface.
|
||||||
|
// Driver returns &MySQLDriver{}.
|
||||||
|
func (c *connector) Driver() driver.Driver {
|
||||||
|
return &MySQLDriver{}
|
||||||
|
}
|
|
@ -17,156 +17,91 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"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.
|
// MySQLDriver is exported to make the driver directly accessible.
|
||||||
// In general the driver is used via the database/sql package.
|
// In general the driver is used via the database/sql package.
|
||||||
type MySQLDriver struct{}
|
type MySQLDriver struct{}
|
||||||
|
|
||||||
// DialFunc is a function which can be used to establish the network connection.
|
// DialFunc is a function which can be used to establish the network connection.
|
||||||
// Custom dial functions must be registered with RegisterDial
|
// Custom dial functions must be registered with RegisterDial
|
||||||
|
//
|
||||||
|
// Deprecated: users should register a DialContextFunc instead
|
||||||
type DialFunc func(addr string) (net.Conn, error)
|
type DialFunc func(addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// DialContextFunc is a function which can be used to establish the network connection.
|
||||||
|
// Custom dial functions must be registered with RegisterDialContext
|
||||||
|
type DialContextFunc func(ctx context.Context, addr string) (net.Conn, error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialsLock sync.RWMutex
|
dialsLock sync.RWMutex
|
||||||
dials map[string]DialFunc
|
dials map[string]DialContextFunc
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterDial registers a custom dial function. It can then be used by the
|
// RegisterDialContext registers a custom dial function. It can then be used by the
|
||||||
// network address mynet(addr), where mynet is the registered new network.
|
// network address mynet(addr), where mynet is the registered new network.
|
||||||
// addr is passed as a parameter to the dial function.
|
// The current context for the connection and its address is passed to the dial function.
|
||||||
func RegisterDial(net string, dial DialFunc) {
|
func RegisterDialContext(net string, dial DialContextFunc) {
|
||||||
dialsLock.Lock()
|
dialsLock.Lock()
|
||||||
defer dialsLock.Unlock()
|
defer dialsLock.Unlock()
|
||||||
if dials == nil {
|
if dials == nil {
|
||||||
dials = make(map[string]DialFunc)
|
dials = make(map[string]DialContextFunc)
|
||||||
}
|
}
|
||||||
dials[net] = dial
|
dials[net] = dial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterDial registers a custom dial function. It can then be used by the
|
||||||
|
// network address mynet(addr), where mynet is the registered new network.
|
||||||
|
// addr is passed as a parameter to the dial function.
|
||||||
|
//
|
||||||
|
// Deprecated: users should call RegisterDialContext instead
|
||||||
|
func RegisterDial(network string, dial DialFunc) {
|
||||||
|
RegisterDialContext(network, func(_ context.Context, addr string) (net.Conn, error) {
|
||||||
|
return dial(addr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Open new Connection.
|
// Open new Connection.
|
||||||
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
||||||
// the DSN string is formated
|
// the DSN string is formatted
|
||||||
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
var err error
|
cfg, err := ParseDSN(dsn)
|
||||||
|
|
||||||
// New mysqlConn
|
|
||||||
mc := &mysqlConn{
|
|
||||||
maxAllowedPacket: maxPacketSize,
|
|
||||||
maxWriteSize: maxPacketSize - 1,
|
|
||||||
closech: make(chan struct{}),
|
|
||||||
}
|
|
||||||
mc.cfg, err = ParseDSN(dsn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mc.parseTime = mc.cfg.ParseTime
|
c := &connector{
|
||||||
|
cfg: cfg,
|
||||||
// Connect to Server
|
|
||||||
dialsLock.RLock()
|
|
||||||
dial, ok := dials[mc.cfg.Net]
|
|
||||||
dialsLock.RUnlock()
|
|
||||||
if ok {
|
|
||||||
mc.netConn, err = dial(mc.cfg.Addr)
|
|
||||||
} else {
|
|
||||||
nd := net.Dialer{Timeout: mc.cfg.Timeout}
|
|
||||||
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
return c.Connect(context.Background())
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable TCP Keepalives on TCP connections
|
|
||||||
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
|
||||||
if err := tc.SetKeepAlive(true); err != nil {
|
|
||||||
// Don't send COM_QUIT before handshake.
|
|
||||||
mc.netConn.Close()
|
|
||||||
mc.netConn = nil
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call startWatcher for context support (From Go 1.8)
|
|
||||||
if s, ok := interface{}(mc).(watcher); ok {
|
|
||||||
s.startWatcher()
|
|
||||||
}
|
|
||||||
|
|
||||||
mc.buf = newBuffer(mc.netConn)
|
|
||||||
|
|
||||||
// Set I/O timeouts
|
|
||||||
mc.buf.timeout = mc.cfg.ReadTimeout
|
|
||||||
mc.writeTimeout = mc.cfg.WriteTimeout
|
|
||||||
|
|
||||||
// Reading Handshake Initialization Packet
|
|
||||||
authData, plugin, err := mc.readHandshakePacket()
|
|
||||||
if err != nil {
|
|
||||||
mc.cleanup()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if plugin == "" {
|
|
||||||
plugin = defaultAuthPlugin
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send Client Authentication Packet
|
|
||||||
authResp, err := mc.auth(authData, plugin)
|
|
||||||
if err != nil {
|
|
||||||
// try the default auth plugin, if using the requested plugin failed
|
|
||||||
errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
|
|
||||||
plugin = defaultAuthPlugin
|
|
||||||
authResp, err = mc.auth(authData, plugin)
|
|
||||||
if err != nil {
|
|
||||||
mc.cleanup()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
|
|
||||||
mc.cleanup()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle response to auth packet, switch methods if possible
|
|
||||||
if err = mc.handleAuthResult(authData, plugin); err != nil {
|
|
||||||
// Authentication failed and MySQL has already closed the connection
|
|
||||||
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
|
|
||||||
// Do not send COM_QUIT, just cleanup and return the error.
|
|
||||||
mc.cleanup()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if mc.cfg.MaxAllowedPacket > 0 {
|
|
||||||
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
|
|
||||||
} else {
|
|
||||||
// Get max allowed packet size
|
|
||||||
maxap, err := mc.getSystemVar("max_allowed_packet")
|
|
||||||
if err != nil {
|
|
||||||
mc.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mc.maxAllowedPacket = stringToInt(maxap) - 1
|
|
||||||
}
|
|
||||||
if mc.maxAllowedPacket < maxPacketSize {
|
|
||||||
mc.maxWriteSize = mc.maxAllowedPacket
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle DSN Params
|
|
||||||
err = mc.handleParams()
|
|
||||||
if err != nil {
|
|
||||||
mc.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mc, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sql.Register("mysql", &MySQLDriver{})
|
sql.Register("mysql", &MySQLDriver{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConnector returns new driver.Connector.
|
||||||
|
func NewConnector(cfg *Config) (driver.Connector, error) {
|
||||||
|
cfg = cfg.Clone()
|
||||||
|
// normalize the contents of cfg so calls to NewConnector have the same
|
||||||
|
// behavior as MySQLDriver.OpenConnector
|
||||||
|
if err := cfg.normalize(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &connector{cfg: cfg}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenConnector implements driver.DriverContext.
|
||||||
|
func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &connector{
|
||||||
|
cfg: cfg,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -54,6 +55,7 @@ type Config struct {
|
||||||
AllowCleartextPasswords bool // Allows the cleartext client side plugin
|
AllowCleartextPasswords bool // Allows the cleartext client side plugin
|
||||||
AllowNativePasswords bool // Allows the native password authentication method
|
AllowNativePasswords bool // Allows the native password authentication method
|
||||||
AllowOldPasswords bool // Allows the old insecure password method
|
AllowOldPasswords bool // Allows the old insecure password method
|
||||||
|
CheckConnLiveness bool // Check connections for liveness before using them
|
||||||
ClientFoundRows bool // Return number of matching rows instead of rows changed
|
ClientFoundRows bool // Return number of matching rows instead of rows changed
|
||||||
ColumnsWithAlias bool // Prepend table alias to column names
|
ColumnsWithAlias bool // Prepend table alias to column names
|
||||||
InterpolateParams bool // Interpolate placeholders into query string
|
InterpolateParams bool // Interpolate placeholders into query string
|
||||||
|
@ -69,9 +71,30 @@ func NewConfig() *Config {
|
||||||
Loc: time.UTC,
|
Loc: time.UTC,
|
||||||
MaxAllowedPacket: defaultMaxAllowedPacket,
|
MaxAllowedPacket: defaultMaxAllowedPacket,
|
||||||
AllowNativePasswords: true,
|
AllowNativePasswords: true,
|
||||||
|
CheckConnLiveness: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) Clone() *Config {
|
||||||
|
cp := *cfg
|
||||||
|
if cp.tls != nil {
|
||||||
|
cp.tls = cfg.tls.Clone()
|
||||||
|
}
|
||||||
|
if len(cp.Params) > 0 {
|
||||||
|
cp.Params = make(map[string]string, len(cfg.Params))
|
||||||
|
for k, v := range cfg.Params {
|
||||||
|
cp.Params[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.pubKey != nil {
|
||||||
|
cp.pubKey = &rsa.PublicKey{
|
||||||
|
N: new(big.Int).Set(cfg.pubKey.N),
|
||||||
|
E: cfg.pubKey.E,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &cp
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *Config) normalize() error {
|
func (cfg *Config) normalize() error {
|
||||||
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
||||||
return errInvalidDSNUnsafeCollation
|
return errInvalidDSNUnsafeCollation
|
||||||
|
@ -92,23 +115,54 @@ func (cfg *Config) normalize() error {
|
||||||
default:
|
default:
|
||||||
return errors.New("default addr for network '" + cfg.Net + "' unknown")
|
return errors.New("default addr for network '" + cfg.Net + "' unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if cfg.Net == "tcp" {
|
} else if cfg.Net == "tcp" {
|
||||||
cfg.Addr = ensureHavePort(cfg.Addr)
|
cfg.Addr = ensureHavePort(cfg.Addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.tls != nil {
|
switch cfg.TLSConfig {
|
||||||
if cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
|
case "false", "":
|
||||||
|
// don't set anything
|
||||||
|
case "true":
|
||||||
|
cfg.tls = &tls.Config{}
|
||||||
|
case "skip-verify", "preferred":
|
||||||
|
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
default:
|
||||||
|
cfg.tls = getTLSConfigClone(cfg.TLSConfig)
|
||||||
|
if cfg.tls == nil {
|
||||||
|
return errors.New("invalid value / unknown config name: " + cfg.TLSConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.tls != nil && cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
|
||||||
host, _, err := net.SplitHostPort(cfg.Addr)
|
host, _, err := net.SplitHostPort(cfg.Addr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
cfg.tls.ServerName = host
|
cfg.tls.ServerName = host
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.ServerPubKey != "" {
|
||||||
|
cfg.pubKey = getServerPubKey(cfg.ServerPubKey)
|
||||||
|
if cfg.pubKey == nil {
|
||||||
|
return errors.New("invalid value / unknown server pub key name: " + cfg.ServerPubKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeDSNParam(buf *bytes.Buffer, hasParam *bool, name, value string) {
|
||||||
|
buf.Grow(1 + len(name) + 1 + len(value))
|
||||||
|
if !*hasParam {
|
||||||
|
*hasParam = true
|
||||||
|
buf.WriteByte('?')
|
||||||
|
} else {
|
||||||
|
buf.WriteByte('&')
|
||||||
|
}
|
||||||
|
buf.WriteString(name)
|
||||||
|
buf.WriteByte('=')
|
||||||
|
buf.WriteString(value)
|
||||||
|
}
|
||||||
|
|
||||||
// FormatDSN formats the given Config into a DSN string which can be passed to
|
// FormatDSN formats the given Config into a DSN string which can be passed to
|
||||||
// the driver.
|
// the driver.
|
||||||
func (cfg *Config) FormatDSN() string {
|
func (cfg *Config) FormatDSN() string {
|
||||||
|
@ -147,165 +201,75 @@ func (cfg *Config) FormatDSN() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.AllowCleartextPasswords {
|
if cfg.AllowCleartextPasswords {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true")
|
||||||
buf.WriteString("&allowCleartextPasswords=true")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?allowCleartextPasswords=true")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.AllowNativePasswords {
|
if !cfg.AllowNativePasswords {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "allowNativePasswords", "false")
|
||||||
buf.WriteString("&allowNativePasswords=false")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?allowNativePasswords=false")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.AllowOldPasswords {
|
if cfg.AllowOldPasswords {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "allowOldPasswords", "true")
|
||||||
buf.WriteString("&allowOldPasswords=true")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?allowOldPasswords=true")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !cfg.CheckConnLiveness {
|
||||||
|
writeDSNParam(&buf, &hasParam, "checkConnLiveness", "false")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.ClientFoundRows {
|
if cfg.ClientFoundRows {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "clientFoundRows", "true")
|
||||||
buf.WriteString("&clientFoundRows=true")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?clientFoundRows=true")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
|
if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "collation", col)
|
||||||
buf.WriteString("&collation=")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?collation=")
|
|
||||||
}
|
|
||||||
buf.WriteString(col)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.ColumnsWithAlias {
|
if cfg.ColumnsWithAlias {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "columnsWithAlias", "true")
|
||||||
buf.WriteString("&columnsWithAlias=true")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?columnsWithAlias=true")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.InterpolateParams {
|
if cfg.InterpolateParams {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "interpolateParams", "true")
|
||||||
buf.WriteString("&interpolateParams=true")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?interpolateParams=true")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Loc != time.UTC && cfg.Loc != nil {
|
if cfg.Loc != time.UTC && cfg.Loc != nil {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "loc", url.QueryEscape(cfg.Loc.String()))
|
||||||
buf.WriteString("&loc=")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?loc=")
|
|
||||||
}
|
|
||||||
buf.WriteString(url.QueryEscape(cfg.Loc.String()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.MultiStatements {
|
if cfg.MultiStatements {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "multiStatements", "true")
|
||||||
buf.WriteString("&multiStatements=true")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?multiStatements=true")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.ParseTime {
|
if cfg.ParseTime {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "parseTime", "true")
|
||||||
buf.WriteString("&parseTime=true")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?parseTime=true")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.ReadTimeout > 0 {
|
if cfg.ReadTimeout > 0 {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "readTimeout", cfg.ReadTimeout.String())
|
||||||
buf.WriteString("&readTimeout=")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?readTimeout=")
|
|
||||||
}
|
|
||||||
buf.WriteString(cfg.ReadTimeout.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.RejectReadOnly {
|
if cfg.RejectReadOnly {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "rejectReadOnly", "true")
|
||||||
buf.WriteString("&rejectReadOnly=true")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?rejectReadOnly=true")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.ServerPubKey) > 0 {
|
if len(cfg.ServerPubKey) > 0 {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "serverPubKey", url.QueryEscape(cfg.ServerPubKey))
|
||||||
buf.WriteString("&serverPubKey=")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?serverPubKey=")
|
|
||||||
}
|
|
||||||
buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Timeout > 0 {
|
if cfg.Timeout > 0 {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "timeout", cfg.Timeout.String())
|
||||||
buf.WriteString("&timeout=")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?timeout=")
|
|
||||||
}
|
|
||||||
buf.WriteString(cfg.Timeout.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.TLSConfig) > 0 {
|
if len(cfg.TLSConfig) > 0 {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "tls", url.QueryEscape(cfg.TLSConfig))
|
||||||
buf.WriteString("&tls=")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?tls=")
|
|
||||||
}
|
|
||||||
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.WriteTimeout > 0 {
|
if cfg.WriteTimeout > 0 {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "writeTimeout", cfg.WriteTimeout.String())
|
||||||
buf.WriteString("&writeTimeout=")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?writeTimeout=")
|
|
||||||
}
|
|
||||||
buf.WriteString(cfg.WriteTimeout.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
|
if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, "maxAllowedPacket", strconv.Itoa(cfg.MaxAllowedPacket))
|
||||||
buf.WriteString("&maxAllowedPacket=")
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteString("?maxAllowedPacket=")
|
|
||||||
}
|
|
||||||
buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// other params
|
// other params
|
||||||
|
@ -316,16 +280,7 @@ func (cfg *Config) FormatDSN() string {
|
||||||
}
|
}
|
||||||
sort.Strings(params)
|
sort.Strings(params)
|
||||||
for _, param := range params {
|
for _, param := range params {
|
||||||
if hasParam {
|
writeDSNParam(&buf, &hasParam, param, url.QueryEscape(cfg.Params[param]))
|
||||||
buf.WriteByte('&')
|
|
||||||
} else {
|
|
||||||
hasParam = true
|
|
||||||
buf.WriteByte('?')
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteString(param)
|
|
||||||
buf.WriteByte('=')
|
|
||||||
buf.WriteString(url.QueryEscape(cfg.Params[param]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,6 +407,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
return errors.New("invalid bool value: " + value)
|
return errors.New("invalid bool value: " + value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check connections for Liveness before using them
|
||||||
|
case "checkConnLiveness":
|
||||||
|
var isBool bool
|
||||||
|
cfg.CheckConnLiveness, isBool = readBool(value)
|
||||||
|
if !isBool {
|
||||||
|
return errors.New("invalid bool value: " + value)
|
||||||
|
}
|
||||||
|
|
||||||
// Switch "rowsAffected" mode
|
// Switch "rowsAffected" mode
|
||||||
case "clientFoundRows":
|
case "clientFoundRows":
|
||||||
var isBool bool
|
var isBool bool
|
||||||
|
@ -531,13 +494,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid value for server pub key name: %v", err)
|
return fmt.Errorf("invalid value for server pub key name: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pubKey := getServerPubKey(name); pubKey != nil {
|
|
||||||
cfg.ServerPubKey = name
|
cfg.ServerPubKey = name
|
||||||
cfg.pubKey = pubKey
|
|
||||||
} else {
|
|
||||||
return errors.New("invalid value / unknown server pub key name: " + name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strict mode
|
// Strict mode
|
||||||
case "strict":
|
case "strict":
|
||||||
|
@ -556,25 +513,17 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
if isBool {
|
if isBool {
|
||||||
if boolValue {
|
if boolValue {
|
||||||
cfg.TLSConfig = "true"
|
cfg.TLSConfig = "true"
|
||||||
cfg.tls = &tls.Config{}
|
|
||||||
} else {
|
} else {
|
||||||
cfg.TLSConfig = "false"
|
cfg.TLSConfig = "false"
|
||||||
}
|
}
|
||||||
} else if vl := strings.ToLower(value); vl == "skip-verify" {
|
} else if vl := strings.ToLower(value); vl == "skip-verify" || vl == "preferred" {
|
||||||
cfg.TLSConfig = vl
|
cfg.TLSConfig = vl
|
||||||
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
|
||||||
} else {
|
} else {
|
||||||
name, err := url.QueryUnescape(value)
|
name, err := url.QueryUnescape(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid value for TLS config name: %v", err)
|
return fmt.Errorf("invalid value for TLS config name: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
|
|
||||||
cfg.TLSConfig = name
|
cfg.TLSConfig = name
|
||||||
cfg.tls = tlsConfig
|
|
||||||
} else {
|
|
||||||
return errors.New("invalid value / unknown config name: " + name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// I/O write Timeout
|
// I/O write Timeout
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/go-sql-driver/mysql
|
||||||
|
|
||||||
|
go 1.10
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 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/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
// The value type must be time.Time or string / []byte (formatted time-string),
|
||||||
|
// otherwise Scan fails.
|
||||||
|
func (nt *NullTime) Scan(value interface{}) (err error) {
|
||||||
|
if value == nil {
|
||||||
|
nt.Time, nt.Valid = time.Time{}, false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case time.Time:
|
||||||
|
nt.Time, nt.Valid = v, true
|
||||||
|
return
|
||||||
|
case []byte:
|
||||||
|
nt.Time, err = parseDateTime(string(v), time.UTC)
|
||||||
|
nt.Valid = (err == nil)
|
||||||
|
return
|
||||||
|
case string:
|
||||||
|
nt.Time, err = parseDateTime(v, time.UTC)
|
||||||
|
nt.Valid = (err == nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nt.Valid = false
|
||||||
|
return fmt.Errorf("Can't convert %T to time.Time", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (nt NullTime) Value() (driver.Value, error) {
|
||||||
|
if !nt.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nt.Time, nil
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 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.13
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NullTime represents a time.Time that may be NULL.
|
||||||
|
// NullTime implements the Scanner interface so
|
||||||
|
// it can be used as a scan destination:
|
||||||
|
//
|
||||||
|
// var nt NullTime
|
||||||
|
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||||
|
// ...
|
||||||
|
// if nt.Valid {
|
||||||
|
// // use nt.Time
|
||||||
|
// } else {
|
||||||
|
// // NULL value
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This NullTime implementation is not driver-specific
|
||||||
|
type NullTime sql.NullTime
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2013 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.13
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NullTime represents a time.Time that may be NULL.
|
||||||
|
// NullTime implements the Scanner interface so
|
||||||
|
// it can be used as a scan destination:
|
||||||
|
//
|
||||||
|
// var nt NullTime
|
||||||
|
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||||
|
// ...
|
||||||
|
// if nt.Valid {
|
||||||
|
// // use nt.Time
|
||||||
|
// } else {
|
||||||
|
// // NULL value
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This NullTime implementation is not driver-specific
|
||||||
|
type NullTime struct {
|
||||||
|
Time time.Time
|
||||||
|
Valid bool // Valid is true if Time is not NULL
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
|
||||||
mc.sequence++
|
mc.sequence++
|
||||||
|
|
||||||
// packets with length 0 terminate a previous packet which is a
|
// 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 {
|
if pktLen == 0 {
|
||||||
// there was no previous packet
|
// there was no previous packet
|
||||||
if prevData == nil {
|
if prevData == nil {
|
||||||
|
@ -96,6 +96,35 @@ func (mc *mysqlConn) writePacket(data []byte) error {
|
||||||
return ErrPktTooLarge
|
return ErrPktTooLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform a stale connection check. We only perform this check for
|
||||||
|
// the first query on a connection that has been checked out of the
|
||||||
|
// connection pool: a fresh connection from the pool is more likely
|
||||||
|
// to be stale, and it has not performed any previous writes that
|
||||||
|
// could cause data corruption, so it's safe to return ErrBadConn
|
||||||
|
// if the check fails.
|
||||||
|
if mc.reset {
|
||||||
|
mc.reset = false
|
||||||
|
conn := mc.netConn
|
||||||
|
if mc.rawConn != nil {
|
||||||
|
conn = mc.rawConn
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
// If this connection has a ReadTimeout which we've been setting on
|
||||||
|
// reads, reset it to its default value before we attempt a non-blocking
|
||||||
|
// read, otherwise the scheduler will just time us out before we can read
|
||||||
|
if mc.cfg.ReadTimeout != 0 {
|
||||||
|
err = conn.SetReadDeadline(time.Time{})
|
||||||
|
}
|
||||||
|
if err == nil && mc.cfg.CheckConnLiveness {
|
||||||
|
err = connCheck(conn)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
errLog.Print("closing bad idle connection: ", err)
|
||||||
|
mc.Close()
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var size int
|
var size int
|
||||||
if pktLen >= maxPacketSize {
|
if pktLen >= maxPacketSize {
|
||||||
|
@ -194,8 +223,12 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
|
||||||
return nil, "", ErrOldProtocol
|
return nil, "", ErrOldProtocol
|
||||||
}
|
}
|
||||||
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
|
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
|
pos += 2
|
||||||
|
|
||||||
if len(data) > pos {
|
if len(data) > pos {
|
||||||
|
@ -286,10 +319,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate packet length and get buffer with that size
|
// Calculate packet length and get buffer with that size
|
||||||
data := mc.buf.takeSmallBuffer(pktLen + 4)
|
data, err := mc.buf.takeSmallBuffer(pktLen + 4)
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// cannot take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return errBadConnNoWrite
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,6 +361,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
mc.rawConn = mc.netConn
|
||||||
mc.netConn = tlsConn
|
mc.netConn = tlsConn
|
||||||
mc.buf.nc = tlsConn
|
mc.buf.nc = tlsConn
|
||||||
}
|
}
|
||||||
|
@ -367,10 +401,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
||||||
func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
|
func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
|
||||||
pktLen := 4 + len(authData)
|
pktLen := 4 + len(authData)
|
||||||
data := mc.buf.takeSmallBuffer(pktLen)
|
data, err := mc.buf.takeSmallBuffer(pktLen)
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// cannot take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return errBadConnNoWrite
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,10 +421,10 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
|
||||||
// Reset Packet Sequence
|
// Reset Packet Sequence
|
||||||
mc.sequence = 0
|
mc.sequence = 0
|
||||||
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + 1)
|
data, err := mc.buf.takeSmallBuffer(4 + 1)
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// cannot take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return errBadConnNoWrite
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,10 +440,10 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
|
||||||
mc.sequence = 0
|
mc.sequence = 0
|
||||||
|
|
||||||
pktLen := 1 + len(arg)
|
pktLen := 1 + len(arg)
|
||||||
data := mc.buf.takeBuffer(pktLen + 4)
|
data, err := mc.buf.takeBuffer(pktLen + 4)
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// cannot take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return errBadConnNoWrite
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,10 +461,10 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
|
||||||
// Reset Packet Sequence
|
// Reset Packet Sequence
|
||||||
mc.sequence = 0
|
mc.sequence = 0
|
||||||
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + 1 + 4)
|
data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// cannot take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return errBadConnNoWrite
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -883,7 +917,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
const minPktLen = 4 + 1 + 4 + 1 + 4
|
const minPktLen = 4 + 1 + 4 + 1 + 4
|
||||||
mc := stmt.mc
|
mc := stmt.mc
|
||||||
|
|
||||||
// Determine threshould dynamically to avoid packet size shortage.
|
// Determine threshold dynamically to avoid packet size shortage.
|
||||||
longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
|
longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
|
||||||
if longDataSize < 64 {
|
if longDataSize < 64 {
|
||||||
longDataSize = 64
|
longDataSize = 64
|
||||||
|
@ -893,15 +927,17 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
mc.sequence = 0
|
mc.sequence = 0
|
||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
data = mc.buf.takeBuffer(minPktLen)
|
data, err = mc.buf.takeBuffer(minPktLen)
|
||||||
} else {
|
} else {
|
||||||
data = mc.buf.takeCompleteBuffer()
|
data, err = mc.buf.takeCompleteBuffer()
|
||||||
|
// In this case the len(data) == cap(data) which is used to optimise the flow below.
|
||||||
}
|
}
|
||||||
if data == nil {
|
if err != nil {
|
||||||
// cannot take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(err)
|
||||||
return errBadConnNoWrite
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -927,7 +963,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
pos := minPktLen
|
pos := minPktLen
|
||||||
|
|
||||||
var nullMask []byte
|
var nullMask []byte
|
||||||
if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) {
|
if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= cap(data) {
|
||||||
// buffer has to be extended but we don't know by how much so
|
// 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 depend on append after all data with known sizes fit.
|
||||||
// We stop at that because we deal with a lot of columns here
|
// We stop at that because we deal with a lot of columns here
|
||||||
|
@ -936,10 +972,11 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
copy(tmp[:pos], data[:pos])
|
copy(tmp[:pos], data[:pos])
|
||||||
data = tmp
|
data = tmp
|
||||||
nullMask = data[pos : pos+maskLen]
|
nullMask = data[pos : pos+maskLen]
|
||||||
|
// No need to clean nullMask as make ensures that.
|
||||||
pos += maskLen
|
pos += maskLen
|
||||||
} else {
|
} else {
|
||||||
nullMask = data[pos : pos+maskLen]
|
nullMask = data[pos : pos+maskLen]
|
||||||
for i := 0; i < maskLen; i++ {
|
for i := range nullMask {
|
||||||
nullMask[i] = 0
|
nullMask[i] = 0
|
||||||
}
|
}
|
||||||
pos += maskLen
|
pos += maskLen
|
||||||
|
@ -984,6 +1021,22 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case uint64:
|
||||||
|
paramTypes[i+i] = byte(fieldTypeLongLong)
|
||||||
|
paramTypes[i+i+1] = 0x80 // type is unsigned
|
||||||
|
|
||||||
|
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
||||||
|
paramValues = paramValues[:len(paramValues)+8]
|
||||||
|
binary.LittleEndian.PutUint64(
|
||||||
|
paramValues[len(paramValues)-8:],
|
||||||
|
uint64(v),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
paramValues = append(paramValues,
|
||||||
|
uint64ToBytes(uint64(v))...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
case float64:
|
case float64:
|
||||||
paramTypes[i+i] = byte(fieldTypeDouble)
|
paramTypes[i+i] = byte(fieldTypeDouble)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
@ -1076,7 +1129,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
// In that case we must build the data packet with the new values buffer
|
// In that case we must build the data packet with the new values buffer
|
||||||
if valuesCap != cap(paramValues) {
|
if valuesCap != cap(paramValues) {
|
||||||
data = append(data[:pos], paramValues...)
|
data = append(data[:pos], paramValues...)
|
||||||
mc.buf.buf = data
|
if err = mc.buf.store(data); err != nil {
|
||||||
|
errLog.Print(err)
|
||||||
|
return errBadConnNoWrite
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pos += len(paramValues)
|
pos += len(paramValues)
|
||||||
|
|
|
@ -111,6 +111,13 @@ func (rows *mysqlRows) Close() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flip the buffer for this connection if we need to drain it.
|
||||||
|
// note that for a successful query (i.e. one where rows.next()
|
||||||
|
// has been called until it returns false), `rows.mc` will be nil
|
||||||
|
// by the time the user calls `(*Rows).Close`, so we won't reach this
|
||||||
|
// see: https://github.com/golang/go/commit/651ddbdb5056ded455f47f9c494c67b389622a47
|
||||||
|
mc.buf.flip()
|
||||||
|
|
||||||
// Remove unread packets from stream
|
// Remove unread packets from stream
|
||||||
if !rows.rs.done {
|
if !rows.rs.done {
|
||||||
err = mc.readUntilEOF()
|
err = mc.readUntilEOF()
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mysqlStmt struct {
|
type mysqlStmt struct {
|
||||||
|
@ -164,14 +163,8 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
|
||||||
}
|
}
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
return rv.Int(), nil
|
return rv.Int(), nil
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
return int64(rv.Uint()), nil
|
return rv.Uint(), nil
|
||||||
case reflect.Uint64:
|
|
||||||
u64 := rv.Uint()
|
|
||||||
if u64 >= 1<<63 {
|
|
||||||
return strconv.FormatUint(u64, 10), nil
|
|
||||||
}
|
|
||||||
return int64(u64), nil
|
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return rv.Float(), nil
|
return rv.Float(), nil
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
|
|
|
@ -10,8 +10,10 @@ package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -54,7 +56,7 @@ var (
|
||||||
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
||||||
//
|
//
|
||||||
func RegisterTLSConfig(key string, config *tls.Config) error {
|
func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||||
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
|
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" {
|
||||||
return fmt.Errorf("key '%s' is reserved", key)
|
return fmt.Errorf("key '%s' is reserved", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +82,7 @@ func DeregisterTLSConfig(key string) {
|
||||||
func getTLSConfigClone(key string) (config *tls.Config) {
|
func getTLSConfigClone(key string) (config *tls.Config) {
|
||||||
tlsConfigLock.RLock()
|
tlsConfigLock.RLock()
|
||||||
if v, ok := tlsConfigRegistry[key]; ok {
|
if v, ok := tlsConfigRegistry[key]; ok {
|
||||||
config = cloneTLSConfig(v)
|
config = v.Clone()
|
||||||
}
|
}
|
||||||
tlsConfigLock.RUnlock()
|
tlsConfigLock.RUnlock()
|
||||||
return
|
return
|
||||||
|
@ -104,60 +106,6 @@ func readBool(input string) (value bool, valid bool) {
|
||||||
* Time related utils *
|
* Time related utils *
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
// NullTime represents a time.Time that may be NULL.
|
|
||||||
// NullTime implements the Scanner interface so
|
|
||||||
// it can be used as a scan destination:
|
|
||||||
//
|
|
||||||
// var nt NullTime
|
|
||||||
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
|
||||||
// ...
|
|
||||||
// if nt.Valid {
|
|
||||||
// // use nt.Time
|
|
||||||
// } else {
|
|
||||||
// // NULL value
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This NullTime implementation is not driver-specific
|
|
||||||
type NullTime struct {
|
|
||||||
Time time.Time
|
|
||||||
Valid bool // Valid is true if Time is not NULL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan implements the Scanner interface.
|
|
||||||
// The value type must be time.Time or string / []byte (formatted time-string),
|
|
||||||
// otherwise Scan fails.
|
|
||||||
func (nt *NullTime) Scan(value interface{}) (err error) {
|
|
||||||
if value == nil {
|
|
||||||
nt.Time, nt.Valid = time.Time{}, false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := value.(type) {
|
|
||||||
case time.Time:
|
|
||||||
nt.Time, nt.Valid = v, true
|
|
||||||
return
|
|
||||||
case []byte:
|
|
||||||
nt.Time, err = parseDateTime(string(v), time.UTC)
|
|
||||||
nt.Valid = (err == nil)
|
|
||||||
return
|
|
||||||
case string:
|
|
||||||
nt.Time, err = parseDateTime(v, time.UTC)
|
|
||||||
nt.Valid = (err == nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nt.Valid = false
|
|
||||||
return fmt.Errorf("Can't convert %T to time.Time", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements the driver Valuer interface.
|
|
||||||
func (nt NullTime) Value() (driver.Value, error) {
|
|
||||||
if !nt.Valid {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nt.Time, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
||||||
base := "0000-00-00 00:00:00.0000000"
|
base := "0000-00-00 00:00:00.0000000"
|
||||||
switch len(str) {
|
switch len(str) {
|
||||||
|
@ -682,7 +630,7 @@ type atomicBool struct {
|
||||||
value uint32
|
value uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSet returns wether the current boolean value is true
|
// IsSet returns whether the current boolean value is true
|
||||||
func (ab *atomicBool) IsSet() bool {
|
func (ab *atomicBool) IsSet() bool {
|
||||||
return atomic.LoadUint32(&ab.value) > 0
|
return atomic.LoadUint32(&ab.value) > 0
|
||||||
}
|
}
|
||||||
|
@ -696,7 +644,7 @@ func (ab *atomicBool) Set(value bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrySet sets the value of the bool and returns wether the value changed
|
// TrySet sets the value of the bool and returns whether the value changed
|
||||||
func (ab *atomicBool) TrySet(value bool) bool {
|
func (ab *atomicBool) TrySet(value bool) bool {
|
||||||
if value {
|
if value {
|
||||||
return atomic.SwapUint32(&ab.value, 1) == 0
|
return atomic.SwapUint32(&ab.value, 1) == 0
|
||||||
|
@ -724,3 +672,30 @@ func (ae *atomicError) Value() error {
|
||||||
}
|
}
|
||||||
return nil
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
// 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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// 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,62 +0,0 @@
|
||||||
// Copyright 2013 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.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package cloudsql exposes access to Google Cloud SQL databases.
|
|
||||||
|
|
||||||
This package does not work in App Engine "flexible environment".
|
|
||||||
|
|
||||||
This package is intended for MySQL drivers to make App Engine-specific
|
|
||||||
connections. Applications should use this package through database/sql:
|
|
||||||
Select a pure Go MySQL driver that supports this package, and use sql.Open
|
|
||||||
with protocol "cloudsql" and an address of the Cloud SQL instance.
|
|
||||||
|
|
||||||
A Go MySQL driver that has been tested to work well with Cloud SQL
|
|
||||||
is the go-sql-driver:
|
|
||||||
import "database/sql"
|
|
||||||
import _ "github.com/go-sql-driver/mysql"
|
|
||||||
|
|
||||||
db, err := sql.Open("mysql", "user@cloudsql(project-id:instance-name)/dbname")
|
|
||||||
|
|
||||||
|
|
||||||
Another driver that works well with Cloud SQL is the mymysql driver:
|
|
||||||
import "database/sql"
|
|
||||||
import _ "github.com/ziutek/mymysql/godrv"
|
|
||||||
|
|
||||||
db, err := sql.Open("mymysql", "cloudsql:instance-name*dbname/user/password")
|
|
||||||
|
|
||||||
|
|
||||||
Using either of these drivers, you can perform a standard SQL query.
|
|
||||||
This example assumes there is a table named 'users' with
|
|
||||||
columns 'first_name' and 'last_name':
|
|
||||||
|
|
||||||
rows, err := db.Query("SELECT first_name, last_name FROM users")
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf(ctx, "db.Query: %v", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var firstName string
|
|
||||||
var lastName string
|
|
||||||
if err := rows.Scan(&firstName, &lastName); err != nil {
|
|
||||||
log.Errorf(ctx, "rows.Scan: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Infof(ctx, "First: %v - Last: %v", firstName, lastName)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
log.Errorf(ctx, "Row error: %v", err)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
package cloudsql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dial connects to the named Cloud SQL instance.
|
|
||||||
func Dial(instance string) (net.Conn, error) {
|
|
||||||
return connect(instance)
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
// Copyright 2013 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
|
|
||||||
|
|
||||||
package cloudsql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"appengine/cloudsql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func connect(instance string) (net.Conn, error) {
|
|
||||||
return cloudsql.Dial(instance)
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright 2013 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
|
|
||||||
|
|
||||||
package cloudsql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func connect(instance string) (net.Conn, error) {
|
|
||||||
return nil, errors.New(`cloudsql: not supported in App Engine "flexible environment"`)
|
|
||||||
}
|
|
|
@ -52,7 +52,7 @@ gitea.com/macaron/toolbox
|
||||||
# github.com/BurntSushi/toml v0.3.1
|
# github.com/BurntSushi/toml v0.3.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/BurntSushi/toml
|
github.com/BurntSushi/toml
|
||||||
# github.com/PuerkitoBio/goquery v1.5.0
|
# github.com/PuerkitoBio/goquery v1.5.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/PuerkitoBio/goquery
|
github.com/PuerkitoBio/goquery
|
||||||
# github.com/PuerkitoBio/purell v1.1.1
|
# github.com/PuerkitoBio/purell v1.1.1
|
||||||
|
@ -96,7 +96,7 @@ github.com/alecthomas/chroma/lexers/y
|
||||||
github.com/alecthomas/chroma/styles
|
github.com/alecthomas/chroma/styles
|
||||||
# github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6
|
# github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6
|
||||||
github.com/andybalholm/brotli
|
github.com/andybalholm/brotli
|
||||||
# github.com/andybalholm/cascadia v1.0.0
|
# github.com/andybalholm/cascadia v1.1.0
|
||||||
github.com/andybalholm/cascadia
|
github.com/andybalholm/cascadia
|
||||||
# github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239
|
# github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239
|
||||||
github.com/anmitsu/go-shlex
|
github.com/anmitsu/go-shlex
|
||||||
|
@ -356,7 +356,7 @@ github.com/go-redis/redis/internal/hashtag
|
||||||
github.com/go-redis/redis/internal/pool
|
github.com/go-redis/redis/internal/pool
|
||||||
github.com/go-redis/redis/internal/proto
|
github.com/go-redis/redis/internal/proto
|
||||||
github.com/go-redis/redis/internal/util
|
github.com/go-redis/redis/internal/util
|
||||||
# github.com/go-sql-driver/mysql v1.4.1
|
# github.com/go-sql-driver/mysql v1.5.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/go-sql-driver/mysql
|
github.com/go-sql-driver/mysql
|
||||||
# github.com/go-stack/stack v1.8.0
|
# github.com/go-stack/stack v1.8.0
|
||||||
|
@ -850,7 +850,6 @@ golang.org/x/xerrors/internal
|
||||||
# google.golang.org/appengine v1.6.5
|
# google.golang.org/appengine v1.6.5
|
||||||
## explicit
|
## explicit
|
||||||
google.golang.org/appengine
|
google.golang.org/appengine
|
||||||
google.golang.org/appengine/cloudsql
|
|
||||||
google.golang.org/appengine/internal
|
google.golang.org/appengine/internal
|
||||||
google.golang.org/appengine/internal/app_identity
|
google.golang.org/appengine/internal/app_identity
|
||||||
google.golang.org/appengine/internal/base
|
google.golang.org/appengine/internal/base
|
||||||
|
@ -917,7 +916,7 @@ strk.kbt.io/projects/go/libravatar
|
||||||
# xorm.io/builder v0.3.7
|
# xorm.io/builder v0.3.7
|
||||||
## explicit
|
## explicit
|
||||||
xorm.io/builder
|
xorm.io/builder
|
||||||
# xorm.io/xorm v1.0.2
|
# xorm.io/xorm v1.0.3
|
||||||
## explicit
|
## explicit
|
||||||
xorm.io/xorm
|
xorm.io/xorm
|
||||||
xorm.io/xorm/caches
|
xorm.io/xorm/caches
|
||||||
|
|
|
@ -3,6 +3,28 @@
|
||||||
This changelog goes through all the changes that have been made in each release
|
This changelog goes through all the changes that have been made in each release
|
||||||
without substantial changes to our git log.
|
without substantial changes to our git log.
|
||||||
|
|
||||||
|
## [1.0.3](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1281) - 2020-07-10
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix dump of sqlite (#1639)
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Fix index name parsing in SQLite dialect (#1737)
|
||||||
|
* add hooks for Commit and Rollback (#1733)
|
||||||
|
|
||||||
|
## [1.0.2](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1261) - 2020-06-16
|
||||||
|
|
||||||
|
* FEATURES
|
||||||
|
* Add Hook (#1644)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix bug when ID used but no reference table given (#1709)
|
||||||
|
* Fix find and count bug (#1651)
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* chore: improve snakeCasedName performance (#1688)
|
||||||
|
* Fix find with another struct (#1666)
|
||||||
|
* fix GetColumns missing ordinal position (#1660)
|
||||||
|
* MISC
|
||||||
|
* chore: improve titleCasedName performance (#1691)
|
||||||
|
|
||||||
## [1.0.1](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1253) - 2020-03-25
|
## [1.0.1](https://gitea.com/xorm/xorm/pulls?q=&type=all&state=closed&milestone=1253) - 2020-03-25
|
||||||
|
|
||||||
* BUGFIXES
|
* BUGFIXES
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
|
|
||||||
Xorm is a simple and powerful ORM for Go.
|
Xorm is a simple and powerful ORM for Go.
|
||||||
|
|
||||||
[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm)
|
[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3)
|
||||||
[![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm)
|
|
||||||
[![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3)
|
|
||||||
|
|
||||||
## Notice
|
## Notice
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
|
|
||||||
xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。
|
xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。
|
||||||
|
|
||||||
[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm)
|
[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3)
|
||||||
[![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm)
|
|
||||||
[![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3)
|
|
||||||
|
|
||||||
## Notice
|
## Notice
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ var (
|
||||||
type Tx struct {
|
type Tx struct {
|
||||||
*sql.Tx
|
*sql.Tx
|
||||||
db *DB
|
db *DB
|
||||||
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
||||||
|
@ -32,13 +33,41 @@ func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
||||||
if err := db.afterProcess(hookCtx); err != nil {
|
if err := db.afterProcess(hookCtx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Tx{tx, db}, nil
|
return &Tx{tx, db, ctx}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) Begin() (*Tx, error) {
|
func (db *DB) Begin() (*Tx, error) {
|
||||||
return db.BeginTx(context.Background(), nil)
|
return db.BeginTx(context.Background(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) Commit() error {
|
||||||
|
hookCtx := contexts.NewContextHook(tx.ctx, "COMMIT", nil)
|
||||||
|
ctx, err := tx.db.beforeProcess(hookCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Tx.Commit()
|
||||||
|
hookCtx.End(ctx, nil, err)
|
||||||
|
if err := tx.db.afterProcess(hookCtx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) Rollback() error {
|
||||||
|
hookCtx := contexts.NewContextHook(tx.ctx, "ROLLBACK", nil)
|
||||||
|
ctx, err := tx.db.beforeProcess(hookCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Tx.Rollback()
|
||||||
|
hookCtx.End(ctx, nil, err)
|
||||||
|
if err := tx.db.afterProcess(hookCtx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) {
|
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) {
|
||||||
names := make(map[string]int)
|
names := make(map[string]int)
|
||||||
var i int
|
var i int
|
||||||
|
|
|
@ -231,6 +231,7 @@ func (db *mssql) SQLType(c *schemas.Column) string {
|
||||||
} else if strings.EqualFold(c.Default, "false") {
|
} else if strings.EqualFold(c.Default, "false") {
|
||||||
c.Default = "0"
|
c.Default = "0"
|
||||||
}
|
}
|
||||||
|
return res
|
||||||
case schemas.Serial:
|
case schemas.Serial:
|
||||||
c.IsAutoIncrement = true
|
c.IsAutoIncrement = true
|
||||||
c.IsPrimaryKey = true
|
c.IsPrimaryKey = true
|
||||||
|
@ -270,8 +271,8 @@ func (db *mssql) SQLType(c *schemas.Column) string {
|
||||||
res = t
|
res = t
|
||||||
}
|
}
|
||||||
|
|
||||||
if res == schemas.Int {
|
if res == schemas.Int || res == schemas.Bit || res == schemas.DateTime {
|
||||||
return schemas.Int
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
hasLen1 := (c.Length > 0)
|
hasLen1 := (c.Length > 0)
|
||||||
|
|
|
@ -483,7 +483,7 @@ func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableNa
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []")
|
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []'\"")
|
||||||
var isRegular bool
|
var isRegular bool
|
||||||
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
|
||||||
index.Name = indexName[5+len(tableName):]
|
index.Name = indexName[5+len(tableName):]
|
||||||
|
|
|
@ -143,10 +143,10 @@ func (engine *Engine) Logger() log.ContextLogger {
|
||||||
func (engine *Engine) SetLogger(logger interface{}) {
|
func (engine *Engine) SetLogger(logger interface{}) {
|
||||||
var realLogger log.ContextLogger
|
var realLogger log.ContextLogger
|
||||||
switch t := logger.(type) {
|
switch t := logger.(type) {
|
||||||
case log.Logger:
|
|
||||||
realLogger = log.NewLoggerAdapter(t)
|
|
||||||
case log.ContextLogger:
|
case log.ContextLogger:
|
||||||
realLogger = t
|
realLogger = t
|
||||||
|
case log.Logger:
|
||||||
|
realLogger = log.NewLoggerAdapter(t)
|
||||||
}
|
}
|
||||||
engine.logger = realLogger
|
engine.logger = realLogger
|
||||||
engine.DB().Logger = realLogger
|
engine.DB().Logger = realLogger
|
||||||
|
@ -412,6 +412,82 @@ func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
|
||||||
return engine.dumpTables(tables, w, tp...)
|
return engine.dumpTables(tables, w, tp...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatColumnValue(dstDialect dialects.Dialect, d interface{}, col *schemas.Column) string {
|
||||||
|
if d == nil {
|
||||||
|
return "NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
if dq, ok := d.(bool); ok && (dstDialect.URI().DBType == schemas.SQLITE ||
|
||||||
|
dstDialect.URI().DBType == schemas.MSSQL) {
|
||||||
|
if dq {
|
||||||
|
return "1"
|
||||||
|
}
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
if col.SQLType.IsText() {
|
||||||
|
var v = fmt.Sprintf("%s", d)
|
||||||
|
return "'" + strings.Replace(v, "'", "''", -1) + "'"
|
||||||
|
} else if col.SQLType.IsTime() {
|
||||||
|
var v = fmt.Sprintf("%s", d)
|
||||||
|
if strings.HasSuffix(v, " +0000 UTC") {
|
||||||
|
return fmt.Sprintf("'%s'", v[0:len(v)-len(" +0000 UTC")])
|
||||||
|
} else if strings.HasSuffix(v, " +0000 +0000") {
|
||||||
|
return fmt.Sprintf("'%s'", v[0:len(v)-len(" +0000 +0000")])
|
||||||
|
}
|
||||||
|
return "'" + strings.Replace(v, "'", "''", -1) + "'"
|
||||||
|
} else if col.SQLType.IsBlob() {
|
||||||
|
if reflect.TypeOf(d).Kind() == reflect.Slice {
|
||||||
|
return fmt.Sprintf("%s", dstDialect.FormatBytes(d.([]byte)))
|
||||||
|
} else if reflect.TypeOf(d).Kind() == reflect.String {
|
||||||
|
return fmt.Sprintf("'%s'", d.(string))
|
||||||
|
}
|
||||||
|
} else if col.SQLType.IsNumeric() {
|
||||||
|
switch reflect.TypeOf(d).Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
if col.SQLType.Name == schemas.Bool {
|
||||||
|
return fmt.Sprintf("%v", strconv.FormatBool(d.([]byte)[0] != byte('0')))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s", string(d.([]byte)))
|
||||||
|
case reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
if col.SQLType.Name == schemas.Bool {
|
||||||
|
v := reflect.ValueOf(d).Int() > 0
|
||||||
|
if dstDialect.URI().DBType == schemas.SQLITE {
|
||||||
|
if v {
|
||||||
|
return "1"
|
||||||
|
}
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", strconv.FormatBool(v))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", d)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
if col.SQLType.Name == schemas.Bool {
|
||||||
|
v := reflect.ValueOf(d).Uint() > 0
|
||||||
|
if dstDialect.URI().DBType == schemas.SQLITE {
|
||||||
|
if v {
|
||||||
|
return "1"
|
||||||
|
}
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", strconv.FormatBool(v))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", d)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%v", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := fmt.Sprintf("%v", d)
|
||||||
|
if strings.Contains(s, ":") || strings.Contains(s, "-") {
|
||||||
|
if strings.HasSuffix(s, " +0000 UTC") {
|
||||||
|
return fmt.Sprintf("'%s'", s[0:len(s)-len(" +0000 UTC")])
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("'%s'", s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// dumpTables dump database all table structs and data to w with specify db type
|
// dumpTables dump database all table structs and data to w with specify db type
|
||||||
func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
|
func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
|
||||||
var dstDialect dialects.Dialect
|
var dstDialect dialects.Dialect
|
||||||
|
@ -424,7 +500,10 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := engine.dialect.URI()
|
uri := engine.dialect.URI()
|
||||||
destURI := *uri
|
destURI := dialects.URI{
|
||||||
|
DBType: tp[0],
|
||||||
|
DBName: uri.DBName,
|
||||||
|
}
|
||||||
dstDialect.Init(&destURI)
|
dstDialect.Init(&destURI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,59 +574,9 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
|
||||||
if col == nil {
|
if col == nil {
|
||||||
return errors.New("unknow column error")
|
return errors.New("unknow column error")
|
||||||
}
|
}
|
||||||
|
temp += "," + formatColumnValue(dstDialect, d, col)
|
||||||
if d == nil {
|
|
||||||
temp += ", NULL"
|
|
||||||
} else if col.SQLType.IsText() || col.SQLType.IsTime() {
|
|
||||||
var v = fmt.Sprintf("%s", d)
|
|
||||||
if strings.HasSuffix(v, " +0000 UTC") {
|
|
||||||
temp += fmt.Sprintf(", '%s'", v[0:len(v)-len(" +0000 UTC")])
|
|
||||||
} else {
|
|
||||||
temp += ", '" + strings.Replace(v, "'", "''", -1) + "'"
|
|
||||||
}
|
}
|
||||||
} else if col.SQLType.IsBlob() {
|
_, err = io.WriteString(w, temp[1:]+");\n")
|
||||||
if reflect.TypeOf(d).Kind() == reflect.Slice {
|
|
||||||
temp += fmt.Sprintf(", %s", dstDialect.FormatBytes(d.([]byte)))
|
|
||||||
} else if reflect.TypeOf(d).Kind() == reflect.String {
|
|
||||||
temp += fmt.Sprintf(", '%s'", d.(string))
|
|
||||||
}
|
|
||||||
} else if col.SQLType.IsNumeric() {
|
|
||||||
switch reflect.TypeOf(d).Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
if col.SQLType.Name == schemas.Bool {
|
|
||||||
temp += fmt.Sprintf(", %v", strconv.FormatBool(d.([]byte)[0] != byte('0')))
|
|
||||||
} else {
|
|
||||||
temp += fmt.Sprintf(", %s", string(d.([]byte)))
|
|
||||||
}
|
|
||||||
case reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
if col.SQLType.Name == schemas.Bool {
|
|
||||||
temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Int() > 0))
|
|
||||||
} else {
|
|
||||||
temp += fmt.Sprintf(", %v", d)
|
|
||||||
}
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
if col.SQLType.Name == schemas.Bool {
|
|
||||||
temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Uint() > 0))
|
|
||||||
} else {
|
|
||||||
temp += fmt.Sprintf(", %v", d)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
temp += fmt.Sprintf(", %v", d)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s := fmt.Sprintf("%v", d)
|
|
||||||
if strings.Contains(s, ":") || strings.Contains(s, "-") {
|
|
||||||
if strings.HasSuffix(s, " +0000 UTC") {
|
|
||||||
temp += fmt.Sprintf(", '%s'", s[0:len(s)-len(" +0000 UTC")])
|
|
||||||
} else {
|
|
||||||
temp += fmt.Sprintf(", '%s'", s)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
temp += fmt.Sprintf(", %s", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = io.WriteString(w, temp[2:]+");\n")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1263,11 +1292,11 @@ func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interf
|
||||||
|
|
||||||
result, err := f(session)
|
result, err := f(session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := session.Commit(); err != nil {
|
if err := session.Commit(); err != nil {
|
||||||
return nil, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
|
@ -3,14 +3,12 @@ module xorm.io/xorm
|
||||||
go 1.11
|
go 1.11
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4
|
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc
|
||||||
github.com/go-sql-driver/mysql v1.4.1
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/lib/pq v1.7.0
|
||||||
github.com/lib/pq v1.0.0
|
github.com/mattn/go-sqlite3 v1.14.0
|
||||||
github.com/mattn/go-sqlite3 v1.10.0
|
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
github.com/syndtr/goleveldb v1.0.0
|
github.com/syndtr/goleveldb v1.0.0
|
||||||
github.com/ziutek/mymysql v1.5.4
|
github.com/ziutek/mymysql v1.5.4
|
||||||
google.golang.org/appengine v1.6.0 // indirect
|
|
||||||
xorm.io/builder v0.3.7
|
xorm.io/builder v0.3.7
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,90 +1,33 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
|
|
||||||
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
|
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
|
||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
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/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
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=
|
|
||||||
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.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/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
@ -93,60 +36,20 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd
|
||||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
|
|
||||||
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
@ -154,8 +57,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
|
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
|
||||||
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
|
|
|
@ -206,7 +206,11 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
|
||||||
colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
|
colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
|
||||||
col := table.UpdatedColumn()
|
col := table.UpdatedColumn()
|
||||||
val, t := session.engine.nowTime(col)
|
val, t := session.engine.nowTime(col)
|
||||||
|
if session.engine.dialect.URI().DBType == schemas.ORACLE {
|
||||||
|
args = append(args, t)
|
||||||
|
} else {
|
||||||
args = append(args, val)
|
args = append(args, val)
|
||||||
|
}
|
||||||
|
|
||||||
var colName = col.Name
|
var colName = col.Name
|
||||||
if isStruct {
|
if isStruct {
|
||||||
|
|
Loading…
Reference in New Issue