增加websocket支持
This commit is contained in:
45
go.mod
45
go.mod
@@ -3,47 +3,44 @@ module git.huangwc.com/pig/pig-farm-controller
|
|||||||
go 1.23
|
go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.0.0
|
||||||
golang.org/x/crypto v0.27.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
golang.org/x/crypto v0.14.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gorm.io/driver/postgres v1.5.9
|
gorm.io/driver/postgres v1.5.5
|
||||||
gorm.io/gorm v1.25.10
|
gorm.io/gorm v1.25.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/net v0.25.0 // indirect
|
golang.org/x/net v0.10.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/text v0.18.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
105
go.sum
105
go.sum
@@ -1,44 +1,43 @@
|
|||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
@@ -46,24 +45,23 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
|
|||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
@@ -71,38 +69,36 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
|
|||||||
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
@@ -111,9 +107,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
gorm.io/driver/postgres v1.5.5 h1:r1VBTQQrOAlUux3JI9V7rdxVWBPPnzxa315qNJUzmjI=
|
||||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
gorm.io/driver/postgres v1.5.5/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
@@ -13,9 +13,12 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/config"
|
"git.huangwc.com/pig/pig-farm-controller/internal/config"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller/device"
|
"git.huangwc.com/pig/pig-farm-controller/internal/controller/device"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller/operation"
|
"git.huangwc.com/pig/pig-farm-controller/internal/controller/operation"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/controller/remote"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller/user"
|
"git.huangwc.com/pig/pig-farm-controller/internal/controller/user"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/service"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/websocket"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,16 +43,25 @@ type API struct {
|
|||||||
// deviceController 设备控制控制器
|
// deviceController 设备控制控制器
|
||||||
deviceController *device.Controller
|
deviceController *device.Controller
|
||||||
|
|
||||||
|
// remoteController 远程控制控制器
|
||||||
|
remoteController *remote.Controller
|
||||||
|
|
||||||
// authMiddleware 鉴权中间件
|
// authMiddleware 鉴权中间件
|
||||||
authMiddleware *middleware.AuthMiddleware
|
authMiddleware *middleware.AuthMiddleware
|
||||||
|
|
||||||
|
// websocketManager WebSocket管理器
|
||||||
|
websocketManager *websocket.Manager
|
||||||
|
|
||||||
|
// websocketService WebSocket服务
|
||||||
|
websocketService *service.WebSocketService
|
||||||
|
|
||||||
// logger 日志记录器
|
// logger 日志记录器
|
||||||
logger *logs.Logger
|
logger *logs.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPI 创建并返回一个新的API实例
|
// NewAPI 创建并返回一个新的API实例
|
||||||
// 初始化Gin引擎和相关配置
|
// 初始化Gin引擎和相关配置
|
||||||
func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRepo repository.OperationHistoryRepo, deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo) *API {
|
func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRepo repository.OperationHistoryRepo, deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService) *API {
|
||||||
// 设置Gin为发布模式
|
// 设置Gin为发布模式
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
@@ -80,7 +92,13 @@ func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRe
|
|||||||
operationController := operation.NewController(operationHistoryRepo)
|
operationController := operation.NewController(operationHistoryRepo)
|
||||||
|
|
||||||
// 创建设备控制控制器
|
// 创建设备控制控制器
|
||||||
deviceController := device.NewController(deviceControlRepo, deviceRepo)
|
deviceController := device.NewController(deviceControlRepo, deviceRepo, websocketService)
|
||||||
|
|
||||||
|
// 创建WebSocket管理器
|
||||||
|
websocketManager := websocket.NewManager(websocketService)
|
||||||
|
|
||||||
|
// 创建远程控制控制器
|
||||||
|
remoteController := remote.NewController(websocketService)
|
||||||
|
|
||||||
// 创建鉴权中间件
|
// 创建鉴权中间件
|
||||||
authMiddleware := middleware.NewAuthMiddleware(userRepo)
|
authMiddleware := middleware.NewAuthMiddleware(userRepo)
|
||||||
@@ -91,7 +109,10 @@ func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRe
|
|||||||
userController: userController,
|
userController: userController,
|
||||||
operationController: operationController,
|
operationController: operationController,
|
||||||
deviceController: deviceController,
|
deviceController: deviceController,
|
||||||
|
remoteController: remoteController,
|
||||||
authMiddleware: authMiddleware,
|
authMiddleware: authMiddleware,
|
||||||
|
websocketManager: websocketManager,
|
||||||
|
websocketService: websocketService,
|
||||||
logger: logs.NewLogger(),
|
logger: logs.NewLogger(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +167,9 @@ func (a *API) setupRoutes() {
|
|||||||
// 基础路由示例
|
// 基础路由示例
|
||||||
a.engine.GET("/health", a.healthHandler)
|
a.engine.GET("/health", a.healthHandler)
|
||||||
|
|
||||||
|
// WebSocket路由
|
||||||
|
a.engine.GET("/ws/device", a.websocketManager.HandleConnection)
|
||||||
|
|
||||||
// 用户相关路由
|
// 用户相关路由
|
||||||
userGroup := a.engine.Group("/api/v1/user")
|
userGroup := a.engine.Group("/api/v1/user")
|
||||||
{
|
{
|
||||||
@@ -170,6 +194,13 @@ func (a *API) setupRoutes() {
|
|||||||
{
|
{
|
||||||
deviceGroup.POST("/switch", a.deviceController.Switch)
|
deviceGroup.POST("/switch", a.deviceController.Switch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 远程控制相关路由
|
||||||
|
remoteGroup := protectedGroup.Group("/remote")
|
||||||
|
{
|
||||||
|
remoteGroup.POST("/command", a.remoteController.SendCommand)
|
||||||
|
remoteGroup.GET("/devices", a.remoteController.ListConnectedDevices)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 添加更多路由
|
// TODO: 添加更多路由
|
||||||
|
|||||||
180
internal/api/websocket.go
Normal file
180
internal/api/websocket.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
// Package api 提供统一的API接口层
|
||||||
|
// 负责处理所有外部请求,包括HTTP和WebSocket接口
|
||||||
|
// 将请求路由到相应的服务层进行处理
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebSocket消息类型常量
|
||||||
|
const (
|
||||||
|
// MessageTypeCommand 平台向设备发送的指令
|
||||||
|
MessageTypeCommand = "command"
|
||||||
|
|
||||||
|
// MessageTypeResponse 设备向平台发送的响应
|
||||||
|
MessageTypeResponse = "response"
|
||||||
|
|
||||||
|
// MessageTypeHeartbeat 心跳消息
|
||||||
|
MessageTypeHeartbeat = "heartbeat"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebSocketMessage WebSocket消息结构
|
||||||
|
type WebSocketMessage struct {
|
||||||
|
// Type 消息类型
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// DeviceID 设备ID
|
||||||
|
DeviceID string `json:"device_id,omitempty"`
|
||||||
|
|
||||||
|
// Command 指令内容
|
||||||
|
Command string `json:"command,omitempty"`
|
||||||
|
|
||||||
|
// Data 消息数据
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
|
||||||
|
// Timestamp 时间戳
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebSocketManager WebSocket管理器
|
||||||
|
type WebSocketManager struct {
|
||||||
|
// websocketService WebSocket服务
|
||||||
|
websocketService *service.WebSocketService
|
||||||
|
|
||||||
|
// logger 日志记录器
|
||||||
|
logger *logs.Logger
|
||||||
|
|
||||||
|
// upgrader WebSocket升级器
|
||||||
|
upgrader websocket.Upgrader
|
||||||
|
|
||||||
|
// mutex 互斥锁
|
||||||
|
mutex sync.RWMutex
|
||||||
|
|
||||||
|
// connections 设备连接映射
|
||||||
|
connections map[string]*websocket.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWebSocketManager 创建WebSocket管理器实例
|
||||||
|
func NewWebSocketManager(websocketService *service.WebSocketService) *WebSocketManager {
|
||||||
|
return &WebSocketManager{
|
||||||
|
websocketService: websocketService,
|
||||||
|
logger: logs.NewLogger(),
|
||||||
|
upgrader: websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
// 允许所有跨域请求
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connections: make(map[string]*websocket.Conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleConnection 处理WebSocket连接
|
||||||
|
func (wm *WebSocketManager) HandleConnection(c *gin.Context) {
|
||||||
|
// 升级HTTP连接到WebSocket
|
||||||
|
conn, err := wm.upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
wm.logger.Error("WebSocket连接升级失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备ID
|
||||||
|
deviceID := c.Query("device_id")
|
||||||
|
if deviceID == "" {
|
||||||
|
wm.logger.Error("缺少设备ID参数")
|
||||||
|
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.ClosePolicyViolation, "缺少设备ID参数"))
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加连接到映射
|
||||||
|
wm.mutex.Lock()
|
||||||
|
wm.connections[deviceID] = conn
|
||||||
|
wm.mutex.Unlock()
|
||||||
|
|
||||||
|
wm.logger.Info("设备 " + deviceID + " 已连接")
|
||||||
|
|
||||||
|
// 发送连接成功消息
|
||||||
|
successMsg := service.WebSocketMessage{
|
||||||
|
Type: "system",
|
||||||
|
Command: "connected",
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
conn.WriteJSON(successMsg)
|
||||||
|
|
||||||
|
// 处理消息循环
|
||||||
|
for {
|
||||||
|
// 读取消息
|
||||||
|
messageType, message, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
wm.logger.Error("读取设备 " + deviceID + " 消息失败: " + err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只处理文本消息
|
||||||
|
if messageType != websocket.TextMessage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理设备消息
|
||||||
|
if err := wm.websocketService.HandleMessage(deviceID, message); err != nil {
|
||||||
|
wm.logger.Error("处理设备 " + deviceID + " 消息失败: " + err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接断开时清理
|
||||||
|
wm.mutex.Lock()
|
||||||
|
delete(wm.connections, deviceID)
|
||||||
|
wm.mutex.Unlock()
|
||||||
|
|
||||||
|
conn.Close()
|
||||||
|
wm.logger.Info("设备 " + deviceID + " 已断开连接")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendCommand 向指定设备发送指令
|
||||||
|
func (wm *WebSocketManager) SendCommand(deviceID, command string, data interface{}) error {
|
||||||
|
wm.mutex.RLock()
|
||||||
|
conn, exists := wm.connections[deviceID]
|
||||||
|
wm.mutex.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return wm.websocketService.SendCommand(deviceID, command, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造消息
|
||||||
|
msg := service.WebSocketMessage{
|
||||||
|
Type: service.MessageTypeCommand,
|
||||||
|
Command: command,
|
||||||
|
Data: data,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
if err := conn.WriteJSON(msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConnectedDevices 获取已连接的设备列表
|
||||||
|
func (wm *WebSocketManager) GetConnectedDevices() []string {
|
||||||
|
wm.mutex.RLock()
|
||||||
|
defer wm.mutex.RUnlock()
|
||||||
|
|
||||||
|
devices := make([]string, 0, len(wm.connections))
|
||||||
|
for deviceID := range wm.connections {
|
||||||
|
devices = append(devices, deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller"
|
"git.huangwc.com/pig/pig-farm-controller/internal/controller"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/model"
|
"git.huangwc.com/pig/pig-farm-controller/internal/model"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/service"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -15,14 +16,16 @@ import (
|
|||||||
type Controller struct {
|
type Controller struct {
|
||||||
deviceControlRepo repository.DeviceControlRepo
|
deviceControlRepo repository.DeviceControlRepo
|
||||||
deviceRepo repository.DeviceRepo
|
deviceRepo repository.DeviceRepo
|
||||||
|
websocketService *service.WebSocketService
|
||||||
logger *logs.Logger
|
logger *logs.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewController 创建设备控制控制器实例
|
// NewController 创建设备控制控制器实例
|
||||||
func NewController(deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo) *Controller {
|
func NewController(deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService) *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
deviceControlRepo: deviceControlRepo,
|
deviceControlRepo: deviceControlRepo,
|
||||||
deviceRepo: deviceRepo,
|
deviceRepo: deviceRepo,
|
||||||
|
websocketService: websocketService,
|
||||||
logger: logs.NewLogger(),
|
logger: logs.NewLogger(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,8 +66,20 @@ func (c *Controller) Switch(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 实际的设备控制逻辑
|
// 通过WebSocket向中继设备发送控制指令
|
||||||
// 这里暂时用TODO代替具体逻辑
|
// 这里假设中继设备ID为"relay-001",在实际应用中应该根据设备层级结构动态获取
|
||||||
|
controlData := map[string]interface{}{
|
||||||
|
"device_type": req.DeviceType,
|
||||||
|
"device_id": req.DeviceID,
|
||||||
|
"action": req.Action,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.websocketService.SendCommand("relay-001", "control_device", controlData)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("通过WebSocket发送设备控制指令失败: " + err.Error())
|
||||||
|
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "设备控制失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 创建设备控制记录
|
// 创建设备控制记录
|
||||||
if err := c.createDeviceControlRecord(
|
if err := c.createDeviceControlRecord(
|
||||||
|
|||||||
@@ -1,16 +1,94 @@
|
|||||||
// Package remote 提供远程管理功能
|
// Package remote 提供远程设备控制相关功能的控制器
|
||||||
// 实现手机端和Web端的远程监控和控制能力
|
// 实现平台向中继设备发送指令等操作
|
||||||
// 通过API接口层提供远程访问能力
|
|
||||||
package remote
|
package remote
|
||||||
|
|
||||||
// RemoteController 远程控制器
|
import (
|
||||||
// 管理远程访问和控制的逻辑
|
"git.huangwc.com/pig/pig-farm-controller/internal/controller"
|
||||||
type RemoteController struct {
|
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||||
// TODO: 定义远程控制器结构
|
"git.huangwc.com/pig/pig-farm-controller/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Controller 远程控制控制器
|
||||||
|
type Controller struct {
|
||||||
|
websocketService *service.WebSocketService
|
||||||
|
logger *logs.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRemoteController 创建并返回一个新的远程控制器实例
|
// NewController 创建远程控制控制器实例
|
||||||
func NewRemoteController() *RemoteController {
|
func NewController(websocketService *service.WebSocketService) *Controller {
|
||||||
// TODO: 实现远程控制器初始化
|
return &Controller{
|
||||||
return nil
|
websocketService: websocketService,
|
||||||
|
logger: logs.NewLogger(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendCommandRequest 发送指令请求结构体
|
||||||
|
type SendCommandRequest struct {
|
||||||
|
DeviceID string `json:"device_id" binding:"required"`
|
||||||
|
Command string `json:"command" binding:"required"`
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendCommandResponseData 发送指令响应数据结构体
|
||||||
|
type SendCommandResponseData struct {
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
Command string `json:"command"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendCommand 向设备发送指令接口
|
||||||
|
// @Summary 向设备发送指令
|
||||||
|
// @Description 平台向指定设备发送控制指令
|
||||||
|
// @Tags remote
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body SendCommandRequest true "指令请求"
|
||||||
|
// @Success 200 {object} controller.APIResponse{data=SendCommandResponseData}
|
||||||
|
// @Router /api/v1/remote/command [post]
|
||||||
|
func (c *Controller) SendCommand(ctx *gin.Context) {
|
||||||
|
var req SendCommandRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过WebSocket服务向设备发送指令
|
||||||
|
err := c.websocketService.SendCommand(req.DeviceID, req.Command, req.Data)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("发送指令失败: " + err.Error())
|
||||||
|
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "发送指令失败: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := SendCommandResponseData{
|
||||||
|
DeviceID: req.DeviceID,
|
||||||
|
Command: req.Command,
|
||||||
|
Status: "sent",
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.SendSuccessResponse(ctx, "指令发送成功", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConnectedDevicesResponseData 获取已连接设备列表响应数据结构体
|
||||||
|
type ListConnectedDevicesResponseData struct {
|
||||||
|
Devices []string `json:"devices"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConnectedDevices 获取已连接设备列表接口
|
||||||
|
// @Summary 获取已连接设备列表
|
||||||
|
// @Description 获取当前通过WebSocket连接到平台的设备列表
|
||||||
|
// @Tags remote
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} controller.APIResponse{data=ListConnectedDevicesResponseData}
|
||||||
|
// @Router /api/v1/remote/devices [get]
|
||||||
|
func (c *Controller) ListConnectedDevices(ctx *gin.Context) {
|
||||||
|
// 获取已连接的设备列表
|
||||||
|
devices := c.websocketService.GetConnectedDevices()
|
||||||
|
|
||||||
|
data := ListConnectedDevicesResponseData{
|
||||||
|
Devices: devices,
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.SendSuccessResponse(ctx, "获取设备列表成功", data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/api"
|
"git.huangwc.com/pig/pig-farm-controller/internal/api"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/config"
|
"git.huangwc.com/pig/pig-farm-controller/internal/config"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/service"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/storage/db"
|
"git.huangwc.com/pig/pig-farm-controller/internal/storage/db"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/task"
|
"git.huangwc.com/pig/pig-farm-controller/internal/task"
|
||||||
@@ -38,6 +39,9 @@ type Application struct {
|
|||||||
// DeviceRepo 设备仓库实例
|
// DeviceRepo 设备仓库实例
|
||||||
DeviceRepo repository.DeviceRepo
|
DeviceRepo repository.DeviceRepo
|
||||||
|
|
||||||
|
// WebSocketService WebSocket服务实例
|
||||||
|
WebSocketService *service.WebSocketService
|
||||||
|
|
||||||
// Config 应用配置
|
// Config 应用配置
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
|
|
||||||
@@ -71,8 +75,11 @@ func NewApplication(cfg *config.Config) *Application {
|
|||||||
// 初始化设备仓库
|
// 初始化设备仓库
|
||||||
deviceRepo := repository.NewDeviceRepo(store.GetDB())
|
deviceRepo := repository.NewDeviceRepo(store.GetDB())
|
||||||
|
|
||||||
|
// 初始化WebSocket服务
|
||||||
|
websocketService := service.NewWebSocketService()
|
||||||
|
|
||||||
// 初始化API组件
|
// 初始化API组件
|
||||||
apiInstance := api.NewAPI(cfg, userRepo, operationHistoryRepo, deviceControlRepo, deviceRepo)
|
apiInstance := api.NewAPI(cfg, userRepo, operationHistoryRepo, deviceControlRepo, deviceRepo, websocketService)
|
||||||
|
|
||||||
// 初始化任务执行器组件(使用5个工作协程)
|
// 初始化任务执行器组件(使用5个工作协程)
|
||||||
taskExecutor := task.NewExecutor(5)
|
taskExecutor := task.NewExecutor(5)
|
||||||
@@ -85,6 +92,7 @@ func NewApplication(cfg *config.Config) *Application {
|
|||||||
OperationHistoryRepo: operationHistoryRepo,
|
OperationHistoryRepo: operationHistoryRepo,
|
||||||
DeviceControlRepo: deviceControlRepo,
|
DeviceControlRepo: deviceControlRepo,
|
||||||
DeviceRepo: deviceRepo,
|
DeviceRepo: deviceRepo,
|
||||||
|
WebSocketService: websocketService,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
logger: logs.NewLogger(),
|
logger: logs.NewLogger(),
|
||||||
}
|
}
|
||||||
|
|||||||
174
internal/core/websocket.go
Normal file
174
internal/core/websocket.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
// Package core 提供WebSocket服务功能
|
||||||
|
// 实现中继设备和平台之间的双向通信
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/model"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebSocket消息类型常量
|
||||||
|
const (
|
||||||
|
// MessageTypeCommand 平台向设备发送的指令
|
||||||
|
MessageTypeCommand = "command"
|
||||||
|
|
||||||
|
// MessageTypeResponse 设备向平台发送的响应
|
||||||
|
MessageTypeResponse = "response"
|
||||||
|
|
||||||
|
// MessageTypeHeartbeat 心跳消息
|
||||||
|
MessageTypeHeartbeat = "heartbeat"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebSocketMessage WebSocket消息结构
|
||||||
|
type WebSocketMessage struct {
|
||||||
|
// Type 消息类型
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// DeviceID 设备ID
|
||||||
|
DeviceID string `json:"device_id,omitempty"`
|
||||||
|
|
||||||
|
// Command 指令内容
|
||||||
|
Command string `json:"command,omitempty"`
|
||||||
|
|
||||||
|
// Data 消息数据
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
|
||||||
|
// Timestamp 时间戳
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceConnection 设备连接信息
|
||||||
|
type DeviceConnection struct {
|
||||||
|
// DeviceID 设备ID
|
||||||
|
DeviceID string
|
||||||
|
|
||||||
|
// Connection WebSocket连接
|
||||||
|
Connection *websocket.Conn
|
||||||
|
|
||||||
|
// LastHeartbeat 最后心跳时间
|
||||||
|
LastHeartbeat time.Time
|
||||||
|
|
||||||
|
// DeviceInfo 设备信息
|
||||||
|
DeviceInfo *model.Device
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebSocketService WebSocket服务
|
||||||
|
type WebSocketService struct {
|
||||||
|
// connections 设备连接映射
|
||||||
|
connections map[string]*DeviceConnection
|
||||||
|
|
||||||
|
// mutex 互斥锁
|
||||||
|
mutex sync.RWMutex
|
||||||
|
|
||||||
|
// logger 日志记录器
|
||||||
|
logger *logs.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWebSocketService 创建WebSocket服务实例
|
||||||
|
func NewWebSocketService() *WebSocketService {
|
||||||
|
return &WebSocketService{
|
||||||
|
connections: make(map[string]*DeviceConnection),
|
||||||
|
logger: logs.NewLogger(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddConnection 添加设备连接
|
||||||
|
func (ws *WebSocketService) AddConnection(deviceID string, conn *websocket.Conn) {
|
||||||
|
ws.mutex.Lock()
|
||||||
|
defer ws.mutex.Unlock()
|
||||||
|
|
||||||
|
ws.connections[deviceID] = &DeviceConnection{
|
||||||
|
DeviceID: deviceID,
|
||||||
|
Connection: conn,
|
||||||
|
LastHeartbeat: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.logger.Info(fmt.Sprintf("设备 %s 已连接", deviceID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveConnection 移除设备连接
|
||||||
|
func (ws *WebSocketService) RemoveConnection(deviceID string) {
|
||||||
|
ws.mutex.Lock()
|
||||||
|
defer ws.mutex.Unlock()
|
||||||
|
|
||||||
|
delete(ws.connections, deviceID)
|
||||||
|
|
||||||
|
ws.logger.Info(fmt.Sprintf("设备 %s 已断开连接", deviceID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendCommand 向指定设备发送指令
|
||||||
|
func (ws *WebSocketService) SendCommand(deviceID, command string, data interface{}) error {
|
||||||
|
ws.mutex.RLock()
|
||||||
|
deviceConn, exists := ws.connections[deviceID]
|
||||||
|
ws.mutex.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("设备 %s 未连接", deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造消息
|
||||||
|
msg := WebSocketMessage{
|
||||||
|
Type: MessageTypeCommand,
|
||||||
|
Command: command,
|
||||||
|
Data: data,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
if err := deviceConn.Connection.WriteJSON(msg); err != nil {
|
||||||
|
return fmt.Errorf("向设备 %s 发送指令失败: %v", deviceID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConnectedDevices 获取已连接的设备列表
|
||||||
|
func (ws *WebSocketService) GetConnectedDevices() []string {
|
||||||
|
ws.mutex.RLock()
|
||||||
|
defer ws.mutex.RUnlock()
|
||||||
|
|
||||||
|
devices := make([]string, 0, len(ws.connections))
|
||||||
|
for deviceID := range ws.connections {
|
||||||
|
devices = append(devices, deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleMessage 处理来自设备的消息
|
||||||
|
func (ws *WebSocketService) HandleMessage(deviceID string, message []byte) error {
|
||||||
|
// 解析消息
|
||||||
|
var msg WebSocketMessage
|
||||||
|
if err := json.Unmarshal(message, &msg); err != nil {
|
||||||
|
return fmt.Errorf("解析设备 %s 消息失败: %v", deviceID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新心跳时间
|
||||||
|
if msg.Type == MessageTypeHeartbeat {
|
||||||
|
ws.mutex.Lock()
|
||||||
|
if deviceConn, exists := ws.connections[deviceID]; exists {
|
||||||
|
deviceConn.LastHeartbeat = time.Now()
|
||||||
|
}
|
||||||
|
ws.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录消息日志
|
||||||
|
ws.logger.Info(fmt.Sprintf("收到来自设备 %s 的消息: %v", deviceID, msg))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceConnection 获取设备连接信息
|
||||||
|
func (ws *WebSocketService) GetDeviceConnection(deviceID string) (*DeviceConnection, bool) {
|
||||||
|
ws.mutex.RLock()
|
||||||
|
defer ws.mutex.RUnlock()
|
||||||
|
|
||||||
|
deviceConn, exists := ws.connections[deviceID]
|
||||||
|
return deviceConn, exists
|
||||||
|
}
|
||||||
170
internal/service/websocket.go
Normal file
170
internal/service/websocket.go
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
// Package service 提供WebSocket服务功能
|
||||||
|
// 实现中继设备和平台之间的双向通信
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebSocket消息类型常量
|
||||||
|
const (
|
||||||
|
// MessageTypeCommand 平台向设备发送的指令
|
||||||
|
MessageTypeCommand = "command"
|
||||||
|
|
||||||
|
// MessageTypeResponse 设备向平台发送的响应
|
||||||
|
MessageTypeResponse = "response"
|
||||||
|
|
||||||
|
// MessageTypeHeartbeat 心跳消息
|
||||||
|
MessageTypeHeartbeat = "heartbeat"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebSocketMessage WebSocket消息结构
|
||||||
|
type WebSocketMessage struct {
|
||||||
|
// Type 消息类型
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// DeviceID 设备ID
|
||||||
|
DeviceID string `json:"device_id,omitempty"`
|
||||||
|
|
||||||
|
// Command 指令内容
|
||||||
|
Command string `json:"command,omitempty"`
|
||||||
|
|
||||||
|
// Data 消息数据
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
|
||||||
|
// Timestamp 时间戳
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeviceConnection 设备连接信息
|
||||||
|
type DeviceConnection struct {
|
||||||
|
// DeviceID 设备ID
|
||||||
|
DeviceID string
|
||||||
|
|
||||||
|
// Connection WebSocket连接
|
||||||
|
Connection *websocket.Conn
|
||||||
|
|
||||||
|
// LastHeartbeat 最后心跳时间
|
||||||
|
LastHeartbeat time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebSocketService WebSocket服务
|
||||||
|
type WebSocketService struct {
|
||||||
|
// connections 设备连接映射
|
||||||
|
connections map[string]*DeviceConnection
|
||||||
|
|
||||||
|
// mutex 互斥锁
|
||||||
|
mutex sync.RWMutex
|
||||||
|
|
||||||
|
// logger 日志记录器
|
||||||
|
logger *logs.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWebSocketService 创建WebSocket服务实例
|
||||||
|
func NewWebSocketService() *WebSocketService {
|
||||||
|
return &WebSocketService{
|
||||||
|
connections: make(map[string]*DeviceConnection),
|
||||||
|
logger: logs.NewLogger(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddConnection 添加设备连接
|
||||||
|
func (ws *WebSocketService) AddConnection(deviceID string, conn *websocket.Conn) {
|
||||||
|
ws.mutex.Lock()
|
||||||
|
defer ws.mutex.Unlock()
|
||||||
|
|
||||||
|
ws.connections[deviceID] = &DeviceConnection{
|
||||||
|
DeviceID: deviceID,
|
||||||
|
Connection: conn,
|
||||||
|
LastHeartbeat: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.logger.Info(fmt.Sprintf("设备 %s 已连接", deviceID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveConnection 移除设备连接
|
||||||
|
func (ws *WebSocketService) RemoveConnection(deviceID string) {
|
||||||
|
ws.mutex.Lock()
|
||||||
|
defer ws.mutex.Unlock()
|
||||||
|
|
||||||
|
delete(ws.connections, deviceID)
|
||||||
|
|
||||||
|
ws.logger.Info(fmt.Sprintf("设备 %s 已断开连接", deviceID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendCommand 向指定设备发送指令
|
||||||
|
func (ws *WebSocketService) SendCommand(deviceID, command string, data interface{}) error {
|
||||||
|
ws.mutex.RLock()
|
||||||
|
deviceConn, exists := ws.connections[deviceID]
|
||||||
|
ws.mutex.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("设备 %s 未连接", deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造消息
|
||||||
|
msg := WebSocketMessage{
|
||||||
|
Type: MessageTypeCommand,
|
||||||
|
Command: command,
|
||||||
|
Data: data,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
if err := deviceConn.Connection.WriteJSON(msg); err != nil {
|
||||||
|
return fmt.Errorf("向设备 %s 发送指令失败: %v", deviceID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConnectedDevices 获取已连接的设备列表
|
||||||
|
func (ws *WebSocketService) GetConnectedDevices() []string {
|
||||||
|
ws.mutex.RLock()
|
||||||
|
defer ws.mutex.RUnlock()
|
||||||
|
|
||||||
|
devices := make([]string, 0, len(ws.connections))
|
||||||
|
for deviceID := range ws.connections {
|
||||||
|
devices = append(devices, deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleMessage 处理来自设备的消息
|
||||||
|
func (ws *WebSocketService) HandleMessage(deviceID string, message []byte) error {
|
||||||
|
// 解析消息
|
||||||
|
var msg WebSocketMessage
|
||||||
|
if err := json.Unmarshal(message, &msg); err != nil {
|
||||||
|
return fmt.Errorf("解析设备 %s 消息失败: %v", deviceID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新心跳时间
|
||||||
|
if msg.Type == MessageTypeHeartbeat {
|
||||||
|
ws.mutex.Lock()
|
||||||
|
if deviceConn, exists := ws.connections[deviceID]; exists {
|
||||||
|
deviceConn.LastHeartbeat = time.Now()
|
||||||
|
}
|
||||||
|
ws.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录消息日志
|
||||||
|
ws.logger.Info(fmt.Sprintf("收到来自设备 %s 的消息: %v", deviceID, msg))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceConnection 获取设备连接信息
|
||||||
|
func (ws *WebSocketService) GetDeviceConnection(deviceID string) (*DeviceConnection, bool) {
|
||||||
|
ws.mutex.RLock()
|
||||||
|
defer ws.mutex.RUnlock()
|
||||||
|
|
||||||
|
deviceConn, exists := ws.connections[deviceID]
|
||||||
|
return deviceConn, exists
|
||||||
|
}
|
||||||
149
internal/websocket/manager.go
Normal file
149
internal/websocket/manager.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// Package websocket 提供WebSocket通信功能
|
||||||
|
// 实现中继设备和平台之间的双向通信
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager WebSocket管理器
|
||||||
|
type Manager struct {
|
||||||
|
// websocketService WebSocket服务
|
||||||
|
websocketService *service.WebSocketService
|
||||||
|
|
||||||
|
// logger 日志记录器
|
||||||
|
logger *logs.Logger
|
||||||
|
|
||||||
|
// upgrader WebSocket升级器
|
||||||
|
upgrader websocket.Upgrader
|
||||||
|
|
||||||
|
// mutex 互斥锁
|
||||||
|
mutex sync.RWMutex
|
||||||
|
|
||||||
|
// connections 设备连接映射
|
||||||
|
connections map[string]*websocket.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager 创建WebSocket管理器实例
|
||||||
|
func NewManager(websocketService *service.WebSocketService) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
websocketService: websocketService,
|
||||||
|
logger: logs.NewLogger(),
|
||||||
|
upgrader: websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
// 允许所有跨域请求
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connections: make(map[string]*websocket.Conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleConnection 处理WebSocket连接
|
||||||
|
func (wm *Manager) HandleConnection(c *gin.Context) {
|
||||||
|
// 升级HTTP连接到WebSocket
|
||||||
|
conn, err := wm.upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
wm.logger.Error("WebSocket连接升级失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备ID
|
||||||
|
deviceID := c.Query("device_id")
|
||||||
|
if deviceID == "" {
|
||||||
|
wm.logger.Error("缺少设备ID参数")
|
||||||
|
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.ClosePolicyViolation, "缺少设备ID参数"))
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加连接到映射
|
||||||
|
wm.mutex.Lock()
|
||||||
|
wm.connections[deviceID] = conn
|
||||||
|
wm.mutex.Unlock()
|
||||||
|
|
||||||
|
wm.logger.Info("设备 " + deviceID + " 已连接")
|
||||||
|
|
||||||
|
// 发送连接成功消息
|
||||||
|
successMsg := service.WebSocketMessage{
|
||||||
|
Type: "system",
|
||||||
|
Command: "connected",
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
conn.WriteJSON(successMsg)
|
||||||
|
|
||||||
|
// 处理消息循环
|
||||||
|
for {
|
||||||
|
// 读取消息
|
||||||
|
messageType, message, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
wm.logger.Error("读取设备 " + deviceID + " 消息失败: " + err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只处理文本消息
|
||||||
|
if messageType != websocket.TextMessage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理设备消息
|
||||||
|
if err := wm.websocketService.HandleMessage(deviceID, message); err != nil {
|
||||||
|
wm.logger.Error("处理设备 " + deviceID + " 消息失败: " + err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接断开时清理
|
||||||
|
wm.mutex.Lock()
|
||||||
|
delete(wm.connections, deviceID)
|
||||||
|
wm.mutex.Unlock()
|
||||||
|
|
||||||
|
conn.Close()
|
||||||
|
wm.logger.Info("设备 " + deviceID + " 已断开连接")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendCommand 向指定设备发送指令
|
||||||
|
func (wm *Manager) SendCommand(deviceID, command string, data interface{}) error {
|
||||||
|
wm.mutex.RLock()
|
||||||
|
conn, exists := wm.connections[deviceID]
|
||||||
|
wm.mutex.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return wm.websocketService.SendCommand(deviceID, command, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造消息
|
||||||
|
msg := service.WebSocketMessage{
|
||||||
|
Type: service.MessageTypeCommand,
|
||||||
|
Command: command,
|
||||||
|
Data: data,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
if err := conn.WriteJSON(msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConnectedDevices 获取已连接的设备列表
|
||||||
|
func (wm *Manager) GetConnectedDevices() []string {
|
||||||
|
wm.mutex.RLock()
|
||||||
|
defer wm.mutex.RUnlock()
|
||||||
|
|
||||||
|
devices := make([]string, 0, len(wm.connections))
|
||||||
|
for deviceID := range wm.connections {
|
||||||
|
devices = append(devices, deviceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices
|
||||||
|
}
|
||||||
7
vendor/github.com/bytedance/sonic/.gitmodules
generated
vendored
7
vendor/github.com/bytedance/sonic/.gitmodules
generated
vendored
@@ -1,6 +1,3 @@
|
|||||||
[submodule "cloudwego"]
|
[submodule "tools/asm2asm"]
|
||||||
path = tools/asm2asm
|
path = tools/asm2asm
|
||||||
url = https://github.com/cloudwego/asm2asm.git
|
url = https://github.com/chenzhuoyu/asm2asm
|
||||||
[submodule "tools/simde"]
|
|
||||||
path = tools/simde
|
|
||||||
url = https://github.com/simd-everywhere/simde.git
|
|
||||||
|
|||||||
121
vendor/github.com/bytedance/sonic/README.md
generated
vendored
121
vendor/github.com/bytedance/sonic/README.md
generated
vendored
@@ -5,27 +5,18 @@ English | [中文](README_ZH_CN.md)
|
|||||||
A blazingly fast JSON serializing & deserializing library, accelerated by JIT (just-in-time compiling) and SIMD (single-instruction-multiple-data).
|
A blazingly fast JSON serializing & deserializing library, accelerated by JIT (just-in-time compiling) and SIMD (single-instruction-multiple-data).
|
||||||
|
|
||||||
## Requirement
|
## Requirement
|
||||||
|
- Go 1.15~1.20
|
||||||
- Go 1.16~1.22
|
- Linux/MacOS/Windows
|
||||||
- Linux / MacOS / Windows(need go1.17 above)
|
|
||||||
- Amd64 ARCH
|
- Amd64 ARCH
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Runtime object binding without code generation
|
- Runtime object binding without code generation
|
||||||
- Complete APIs for JSON value manipulation
|
- Complete APIs for JSON value manipulation
|
||||||
- Fast, fast, fast!
|
- Fast, fast, fast!
|
||||||
|
|
||||||
## APIs
|
|
||||||
|
|
||||||
see [go.dev](https://pkg.go.dev/github.com/bytedance/sonic)
|
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
For **all sizes** of json and **all scenarios** of usage, **Sonic performs best**.
|
For **all sizes** of json and **all scenarios** of usage, **Sonic performs best**.
|
||||||
|
|
||||||
- [Medium](https://github.com/bytedance/sonic/blob/main/decoder/testdata_test.go#L19) (13KB, 300+ key, 6 layers)
|
- [Medium](https://github.com/bytedance/sonic/blob/main/decoder/testdata_test.go#L19) (13KB, 300+ key, 6 layers)
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
goversion: 1.17.1
|
goversion: 1.17.1
|
||||||
goos: darwin
|
goos: darwin
|
||||||
@@ -85,21 +76,15 @@ BenchmarkSetOne_Jsoniter-16 79475 ns/op 163.8
|
|||||||
BenchmarkSetOne_Parallel_Sonic-16 850.9 ns/op 15305.31 MB/s 1584 B/op 17 allocs/op
|
BenchmarkSetOne_Parallel_Sonic-16 850.9 ns/op 15305.31 MB/s 1584 B/op 17 allocs/op
|
||||||
BenchmarkSetOne_Parallel_Sjson-16 18194 ns/op 715.77 MB/s 52247 B/op 9 allocs/op
|
BenchmarkSetOne_Parallel_Sjson-16 18194 ns/op 715.77 MB/s 52247 B/op 9 allocs/op
|
||||||
BenchmarkSetOne_Parallel_Jsoniter-16 33560 ns/op 388.05 MB/s 45892 B/op 964 allocs/op
|
BenchmarkSetOne_Parallel_Jsoniter-16 33560 ns/op 388.05 MB/s 45892 B/op 964 allocs/op
|
||||||
BenchmarkLoadNode/LoadAll()-16 11384 ns/op 1143.93 MB/s 6307 B/op 25 allocs/op
|
|
||||||
BenchmarkLoadNode_Parallel/LoadAll()-16 5493 ns/op 2370.68 MB/s 7145 B/op 25 allocs/op
|
|
||||||
BenchmarkLoadNode/Interface()-16 17722 ns/op 734.85 MB/s 13323 B/op 88 allocs/op
|
|
||||||
BenchmarkLoadNode_Parallel/Interface()-16 10330 ns/op 1260.70 MB/s 15178 B/op 88 allocs/op
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- [Small](https://github.com/bytedance/sonic/blob/main/testdata/small.go) (400B, 11 keys, 3 layers)
|
- [Small](https://github.com/bytedance/sonic/blob/main/testdata/small.go) (400B, 11 keys, 3 layers)
|
||||||

|

|
||||||
- [Large](https://github.com/bytedance/sonic/blob/main/testdata/twitter.json) (635KB, 10000+ key, 6 layers)
|
- [Large](https://github.com/bytedance/sonic/blob/main/testdata/twitter.json) (635KB, 10000+ key, 6 layers)
|
||||||

|

|
||||||
|
|
||||||
See [bench.sh](https://github.com/bytedance/sonic/blob/main/scripts/bench.sh) for benchmark codes.
|
See [bench.sh](https://github.com/bytedance/sonic/blob/main/bench.sh) for benchmark codes.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
See [INTRODUCTION.md](./docs/INTRODUCTION.md).
|
See [INTRODUCTION.md](./docs/INTRODUCTION.md).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -107,7 +92,6 @@ See [INTRODUCTION.md](./docs/INTRODUCTION.md).
|
|||||||
### Marshal/Unmarshal
|
### Marshal/Unmarshal
|
||||||
|
|
||||||
Default behaviors are mostly consistent with `encoding/json`, except HTML escaping form (see [Escape HTML](https://github.com/bytedance/sonic/blob/main/README.md#escape-html)) and `SortKeys` feature (optional support see [Sort Keys](https://github.com/bytedance/sonic/blob/main/README.md#sort-keys)) that is **NOT** in conformity to [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259).
|
Default behaviors are mostly consistent with `encoding/json`, except HTML escaping form (see [Escape HTML](https://github.com/bytedance/sonic/blob/main/README.md#escape-html)) and `SortKeys` feature (optional support see [Sort Keys](https://github.com/bytedance/sonic/blob/main/README.md#sort-keys)) that is **NOT** in conformity to [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -119,11 +103,8 @@ err := sonic.Unmarshal(output, &data)
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Streaming IO
|
### Streaming IO
|
||||||
|
Sonic supports decoding json from `io.Reader` or encoding objects into `io.`Writer`, aims at handling multiple values as well as reducing memory consumption.
|
||||||
Sonic supports decoding json from `io.Reader` or encoding objects into `io.Writer`, aims at handling multiple values as well as reducing memory consumption.
|
|
||||||
|
|
||||||
- encoder
|
- encoder
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var o1 = map[string]interface{}{
|
var o1 = map[string]interface{}{
|
||||||
"a": "b",
|
"a": "b",
|
||||||
@@ -138,9 +119,7 @@ fmt.Println(w.String())
|
|||||||
// {"a":"b"}
|
// {"a":"b"}
|
||||||
// 1
|
// 1
|
||||||
```
|
```
|
||||||
|
|
||||||
- decoder
|
- decoder
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var o = map[string]interface{}{}
|
var o = map[string]interface{}{}
|
||||||
var r = strings.NewReader(`{"a":"b"}{"1":"2"}`)
|
var r = strings.NewReader(`{"a":"b"}{"1":"2"}`)
|
||||||
@@ -153,7 +132,6 @@ fmt.Printf("%+v", o)
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Use Number/Use Int64
|
### Use Number/Use Int64
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic/decoder"
|
import "github.com/bytedance/sonic/decoder"
|
||||||
|
|
||||||
@@ -182,9 +160,7 @@ fm := root.Interface().(float64) // jn == jm
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Sort Keys
|
### Sort Keys
|
||||||
|
|
||||||
On account of the performance loss from sorting (roughly 10%), sonic doesn't enable this feature by default. If your component depends on it to work (like [zstd](https://github.com/facebook/zstd)), Use it like this:
|
On account of the performance loss from sorting (roughly 10%), sonic doesn't enable this feature by default. If your component depends on it to work (like [zstd](https://github.com/facebook/zstd)), Use it like this:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
import "github.com/bytedance/sonic/encoder"
|
import "github.com/bytedance/sonic/encoder"
|
||||||
@@ -197,26 +173,19 @@ v, err := encoder.Encode(m, encoder.SortMapKeys)
|
|||||||
var root := sonic.Get(JSON)
|
var root := sonic.Get(JSON)
|
||||||
err := root.SortKeys()
|
err := root.SortKeys()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Escape HTML
|
### Escape HTML
|
||||||
|
|
||||||
On account of the performance loss (roughly 15%), sonic doesn't enable this feature by default. You can use `encoder.EscapeHTML` option to open this feature (align with `encoding/json.HTMLEscape`).
|
On account of the performance loss (roughly 15%), sonic doesn't enable this feature by default. You can use `encoder.EscapeHTML` option to open this feature (align with `encoding/json.HTMLEscape`).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
v := map[string]string{"&&":"<>"}
|
v := map[string]string{"&&":"<>"}
|
||||||
ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"}}`
|
ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"}}`
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compact Format
|
### Compact Format
|
||||||
|
|
||||||
Sonic encodes primitive objects (struct/map...) as compact-format JSON by default, except marshaling `json.RawMessage` or `json.Marshaler`: sonic ensures validating their output JSON but **DONOT** compacting them for performance concerns. We provide the option `encoder.CompactMarshaler` to add compacting process.
|
Sonic encodes primitive objects (struct/map...) as compact-format JSON by default, except marshaling `json.RawMessage` or `json.Marshaler`: sonic ensures validating their output JSON but **DONOT** compacting them for performance concerns. We provide the option `encoder.CompactMarshaler` to add compacting process.
|
||||||
|
|
||||||
### Print Error
|
### Print Error
|
||||||
|
|
||||||
If there invalid syntax in input JSON, sonic will return `decoder.SyntaxError`, which supports pretty-printing of error position
|
If there invalid syntax in input JSON, sonic will return `decoder.SyntaxError`, which supports pretty-printing of error position
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
import "github.com/bytedance/sonic/decoder"
|
import "github.com/bytedance/sonic/decoder"
|
||||||
@@ -242,9 +211,7 @@ if err != nil {
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Mismatched Types [Sonic v1.6.0]
|
#### Mismatched Types [Sonic v1.6.0]
|
||||||
|
|
||||||
If there a **mismatch-typed** value for a given key, sonic will report `decoder.MismatchTypeError` (if there are many, report the last one), but still skip wrong the value and keep decoding next JSON.
|
If there a **mismatch-typed** value for a given key, sonic will report `decoder.MismatchTypeError` (if there are many, report the last one), but still skip wrong the value and keep decoding next JSON.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
import "github.com/bytedance/sonic/decoder"
|
import "github.com/bytedance/sonic/decoder"
|
||||||
@@ -257,15 +224,10 @@ err := UnmarshalString(`{"A":"1","B":1}`, &data)
|
|||||||
println(err.Error()) // Mismatch type int with value string "at index 5: mismatched type with value\n\n\t{\"A\":\"1\",\"B\":1}\n\t.....^.........\n"
|
println(err.Error()) // Mismatch type int with value string "at index 5: mismatched type with value\n\n\t{\"A\":\"1\",\"B\":1}\n\t.....^.........\n"
|
||||||
fmt.Printf("%+v", data) // {A:0 B:1}
|
fmt.Printf("%+v", data) // {A:0 B:1}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ast.Node
|
### Ast.Node
|
||||||
|
|
||||||
Sonic/ast.Node is a completely self-contained AST for JSON. It implements serialization and deserialization both and provides robust APIs for obtaining and modification of generic data.
|
Sonic/ast.Node is a completely self-contained AST for JSON. It implements serialization and deserialization both and provides robust APIs for obtaining and modification of generic data.
|
||||||
|
|
||||||
#### Get/Index
|
#### Get/Index
|
||||||
|
|
||||||
Search partial JSON by given paths, which must be non-negative integer or string, or nil
|
Search partial JSON by given paths, which must be non-negative integer or string, or nil
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -279,13 +241,10 @@ raw := root.Raw() // == string(input)
|
|||||||
root, err := sonic.Get(input, "key1", 1, "key2")
|
root, err := sonic.Get(input, "key1", 1, "key2")
|
||||||
sub := root.Get("key3").Index(2).Int64() // == 3
|
sub := root.Get("key3").Index(2).Int64() // == 3
|
||||||
```
|
```
|
||||||
|
|
||||||
**Tip**: since `Index()` uses offset to locate data, which is much faster than scanning like `Get()`, we suggest you use it as much as possible. And sonic also provides another API `IndexOrGet()` to underlying use offset as well as ensure the key is matched.
|
**Tip**: since `Index()` uses offset to locate data, which is much faster than scanning like `Get()`, we suggest you use it as much as possible. And sonic also provides another API `IndexOrGet()` to underlying use offset as well as ensure the key is matched.
|
||||||
|
|
||||||
#### Set/Unset
|
#### Set/Unset
|
||||||
|
|
||||||
Modify the json content by Set()/Unset()
|
Modify the json content by Set()/Unset()
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -302,9 +261,7 @@ println(root.Get("key4").Check()) // "value not exist"
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Serialize
|
#### Serialize
|
||||||
|
|
||||||
To encode `ast.Node` as json, use `MarshalJson()` or `json.Marshal()` (MUST pass the node's pointer)
|
To encode `ast.Node` as json, use `MarshalJson()` or `json.Marshal()` (MUST pass the node's pointer)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -318,7 +275,6 @@ println(string(buf) == string(exp)) // true
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### APIs
|
#### APIs
|
||||||
|
|
||||||
- validation: `Check()`, `Error()`, `Valid()`, `Exist()`
|
- validation: `Check()`, `Error()`, `Valid()`, `Exist()`
|
||||||
- searching: `Index()`, `Get()`, `IndexPair()`, `IndexOrGet()`, `GetByPath()`
|
- searching: `Index()`, `Get()`, `IndexPair()`, `IndexOrGet()`, `GetByPath()`
|
||||||
- go-type casting: `Int64()`, `Float64()`, `String()`, `Number()`, `Bool()`, `Map[UseNumber|UseNode]()`, `Array[UseNumber|UseNode]()`, `Interface[UseNumber|UseNode]()`
|
- go-type casting: `Int64()`, `Float64()`, `String()`, `Number()`, `Bool()`, `Map[UseNumber|UseNode]()`, `Array[UseNumber|UseNode]()`, `Interface[UseNumber|UseNode]()`
|
||||||
@@ -326,55 +282,13 @@ println(string(buf) == string(exp)) // true
|
|||||||
- iteration: `Values()`, `Properties()`, `ForEach()`, `SortKeys()`
|
- iteration: `Values()`, `Properties()`, `ForEach()`, `SortKeys()`
|
||||||
- modification: `Set()`, `SetByIndex()`, `Add()`
|
- modification: `Set()`, `SetByIndex()`, `Add()`
|
||||||
|
|
||||||
### Ast.Visitor
|
|
||||||
|
|
||||||
Sonic provides an advanced API for fully parsing JSON into non-standard types (neither `struct` not `map[string]interface{}`) without using any intermediate representation (`ast.Node` or `interface{}`). For example, you might have the following types which are like `interface{}` but actually not `interface{}`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type UserNode interface {}
|
|
||||||
|
|
||||||
// the following types implement the UserNode interface.
|
|
||||||
type (
|
|
||||||
UserNull struct{}
|
|
||||||
UserBool struct{ Value bool }
|
|
||||||
UserInt64 struct{ Value int64 }
|
|
||||||
UserFloat64 struct{ Value float64 }
|
|
||||||
UserString struct{ Value string }
|
|
||||||
UserObject struct{ Value map[string]UserNode }
|
|
||||||
UserArray struct{ Value []UserNode }
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Sonic provides the following API to return **the preorder traversal of a JSON AST**. The `ast.Visitor` is a SAX style interface which is used in some C++ JSON library. You should implement `ast.Visitor` by yourself and pass it to `ast.Preorder()` method. In your visitor you can make your custom types to represent JSON values. There may be an O(n) space container (such as stack) in your visitor to record the object / array hierarchy.
|
|
||||||
|
|
||||||
```go
|
|
||||||
func Preorder(str string, visitor Visitor, opts *VisitorOptions) error
|
|
||||||
|
|
||||||
type Visitor interface {
|
|
||||||
OnNull() error
|
|
||||||
OnBool(v bool) error
|
|
||||||
OnString(v string) error
|
|
||||||
OnInt64(v int64, n json.Number) error
|
|
||||||
OnFloat64(v float64, n json.Number) error
|
|
||||||
OnObjectBegin(capacity int) error
|
|
||||||
OnObjectKey(key string) error
|
|
||||||
OnObjectEnd() error
|
|
||||||
OnArrayBegin(capacity int) error
|
|
||||||
OnArrayEnd() error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See [ast/visitor.go](https://github.com/bytedance/sonic/blob/main/ast/visitor.go) for detailed usage. We also implement a demo visitor for `UserNode` in [ast/visitor_test.go](https://github.com/bytedance/sonic/blob/main/ast/visitor_test.go).
|
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Sonic **DOES NOT** ensure to support all environments, due to the difficulty of developing high-performance codes. For developers who use sonic to build their applications in different environments, we have the following suggestions:
|
Sonic **DOES NOT** ensure to support all environments, due to the difficulty of developing high-performance codes. For developers who use sonic to build their applications in different environments, we have the following suggestions:
|
||||||
|
|
||||||
- Developing on **Mac M1**: Make sure you have Rosetta 2 installed on your machine, and set `GOARCH=amd64` when building your application. Rosetta 2 can automatically translate x86 binaries to arm64 binaries and run x86 applications on Mac M1.
|
- Developing on **Mac M1**: Make sure you have Rosetta 2 installed on your machine, and set `GOARCH=amd64` when building your application. Rosetta 2 can automatically translate x86 binaries to arm64 binaries and run x86 applications on Mac M1.
|
||||||
- Developing on **Linux arm64**: You can install qemu and use the `qemu-x86_64 -cpu max` command to convert x86 binaries to amr64 binaries for applications built with sonic. The qemu can achieve a similar transfer effect to Rosetta 2 on Mac M1.
|
- Developing on **Linux arm64**: You can install qemu and use the `qemu-x86_64 -cpu max` command to convert x86 binaries to amr64 binaries for applications built with sonic. The qemu can achieve a similar transfer effect to Rosetta 2 on Mac M1.
|
||||||
|
|
||||||
For developers who want to use sonic on Linux arm64 without qemu, or those who want to handle JSON strictly consistent with `encoding/json`, we provide some compatible APIs as `sonic.API`
|
For developers who want to use sonic on Linux arm64 without qemu, or those who want to handle JSON strictly consistent with `encoding/json`, we provide some compatible APIs as `sonic.API`
|
||||||
|
|
||||||
- `ConfigDefault`: the sonic's default config (`EscapeHTML=false`,`SortKeys=false`...) to run on sonic-supporting environment. It will fall back to `encoding/json` with the corresponding config, and some options like `SortKeys=false` will be invalid.
|
- `ConfigDefault`: the sonic's default config (`EscapeHTML=false`,`SortKeys=false`...) to run on sonic-supporting environment. It will fall back to `encoding/json` with the corresponding config, and some options like `SortKeys=false` will be invalid.
|
||||||
- `ConfigStd`: the std-compatible config (`EscapeHTML=true`,`SortKeys=true`...) to run on sonic-supporting environment. It will fall back to `encoding/json`.
|
- `ConfigStd`: the std-compatible config (`EscapeHTML=true`,`SortKeys=true`...) to run on sonic-supporting environment. It will fall back to `encoding/json`.
|
||||||
- `ConfigFastest`: the fastest config (`NoQuoteTextMarshaler=true`) to run on sonic-supporting environment. It will fall back to `encoding/json` with the corresponding config, and some options will be invalid.
|
- `ConfigFastest`: the fastest config (`NoQuoteTextMarshaler=true`) to run on sonic-supporting environment. It will fall back to `encoding/json` with the corresponding config, and some options will be invalid.
|
||||||
@@ -382,9 +296,7 @@ For developers who want to use sonic on Linux arm64 without qemu, or those who w
|
|||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
### Pretouch
|
### Pretouch
|
||||||
|
|
||||||
Since Sonic uses [golang-asm](https://github.com/twitchyliquid64/golang-asm) as a JIT assembler, which is NOT very suitable for runtime compiling, first-hit running of a huge schema may cause request-timeout or even process-OOM. For better stability, we advise **using `Pretouch()` for huge-schema or compact-memory applications** before `Marshal()/Unmarshal()`.
|
Since Sonic uses [golang-asm](https://github.com/twitchyliquid64/golang-asm) as a JIT assembler, which is NOT very suitable for runtime compiling, first-hit running of a huge schema may cause request-timeout or even process-OOM. For better stability, we advise **using `Pretouch()` for huge-schema or compact-memory applications** before `Marshal()/Unmarshal()`.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -410,23 +322,17 @@ func init() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Copy string
|
### Copy string
|
||||||
|
When decoding **string values without any escaped characters**, sonic references them from the origin JSON buffer instead of mallocing a new buffer to copy. This helps a lot for CPU performance but may leave the whole JSON buffer in memory as long as the decoded objects are being used. In practice, we found the extra memory introduced by referring JSON buffer is usually 20% ~ 80% of decoded objects. Once an application holds these objects for a long time (for example, cache the decoded objects for reusing), its in-use memory on the server may go up. We provide the option `decoder.CopyString()` for users to choose not to reference the JSON buffer, which may cause a decline in CPU performance to some degree.
|
||||||
When decoding **string values without any escaped characters**, sonic references them from the origin JSON buffer instead of mallocing a new buffer to copy. This helps a lot for CPU performance but may leave the whole JSON buffer in memory as long as the decoded objects are being used. In practice, we found the extra memory introduced by referring JSON buffer is usually 20% ~ 80% of decoded objects. Once an application holds these objects for a long time (for example, cache the decoded objects for reusing), its in-use memory on the server may go up. - `Config.CopyString`/`decoder.CopyString()`: We provide the option for `Decode()` / `Unmarshal()` users to choose not to reference the JSON buffer, which may cause a decline in CPU performance to some degree.
|
|
||||||
|
|
||||||
- `GetFromStringNoCopy()`: For memory safety, `sonic.Get()` / `sonic.GetFromString()` now copies return JSON. If users want to get json more quickly and not care about memory usage, you can use `GetFromStringNoCopy()` to return a JSON directly referenced from source.
|
|
||||||
|
|
||||||
### Pass string or []byte?
|
### Pass string or []byte?
|
||||||
|
|
||||||
For alignment to `encoding/json`, we provide API to pass `[]byte` as an argument, but the string-to-bytes copy is conducted at the same time considering safety, which may lose performance when the origin JSON is huge. Therefore, you can use `UnmarshalString()` and `GetFromString()` to pass a string, as long as your origin data is a string or **nocopy-cast** is safe for your []byte. We also provide API `MarshalString()` for convenient **nocopy-cast** of encoded JSON []byte, which is safe since sonic's output bytes is always duplicated and unique.
|
For alignment to `encoding/json`, we provide API to pass `[]byte` as an argument, but the string-to-bytes copy is conducted at the same time considering safety, which may lose performance when the origin JSON is huge. Therefore, you can use `UnmarshalString()` and `GetFromString()` to pass a string, as long as your origin data is a string or **nocopy-cast** is safe for your []byte. We also provide API `MarshalString()` for convenient **nocopy-cast** of encoded JSON []byte, which is safe since sonic's output bytes is always duplicated and unique.
|
||||||
|
|
||||||
### Accelerate `encoding.TextMarshaler`
|
### Accelerate `encoding.TextMarshaler`
|
||||||
|
|
||||||
To ensure data security, sonic.Encoder quotes and escapes string values from `encoding.TextMarshaler` interfaces by default, which may degrade performance much if most of your data is in form of them. We provide `encoder.NoQuoteTextMarshaler` to skip these operations, which means you **MUST** ensure their output string escaped and quoted following [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259).
|
To ensure data security, sonic.Encoder quotes and escapes string values from `encoding.TextMarshaler` interfaces by default, which may degrade performance much if most of your data is in form of them. We provide `encoder.NoQuoteTextMarshaler` to skip these operations, which means you **MUST** ensure their output string escaped and quoted following [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259).
|
||||||
|
|
||||||
|
|
||||||
### Better performance for generic data
|
### Better performance for generic data
|
||||||
|
|
||||||
In **fully-parsed** scenario, `Unmarshal()` performs better than `Get()`+`Node.Interface()`. But if you only have a part of the schema for specific json, you can combine `Get()` and `Unmarshal()` together:
|
In **fully-parsed** scenario, `Unmarshal()` performs better than `Get()`+`Node.Interface()`. But if you only have a part of the schema for specific json, you can combine `Get()` and `Unmarshal()` together:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -434,9 +340,7 @@ node, err := sonic.GetFromString(_TwitterJson, "statuses", 3, "user")
|
|||||||
var user User // your partial schema...
|
var user User // your partial schema...
|
||||||
err = sonic.UnmarshalString(node.Raw(), &user)
|
err = sonic.UnmarshalString(node.Raw(), &user)
|
||||||
```
|
```
|
||||||
|
|
||||||
Even if you don't have any schema, use `ast.Node` as the container of generic values instead of `map` or `interface`:
|
Even if you don't have any schema, use `ast.Node` as the container of generic values instead of `map` or `interface`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -447,25 +351,12 @@ err = user.Check()
|
|||||||
// err = user.LoadAll() // only call this when you want to use 'user' concurrently...
|
// err = user.LoadAll() // only call this when you want to use 'user' concurrently...
|
||||||
go someFunc(user)
|
go someFunc(user)
|
||||||
```
|
```
|
||||||
|
|
||||||
Why? Because `ast.Node` stores its children using `array`:
|
Why? Because `ast.Node` stores its children using `array`:
|
||||||
|
|
||||||
- `Array`'s performance is **much better** than `Map` when Inserting (Deserialize) and Scanning (Serialize) data;
|
- `Array`'s performance is **much better** than `Map` when Inserting (Deserialize) and Scanning (Serialize) data;
|
||||||
- **Hashing** (`map[x]`) is not as efficient as **Indexing** (`array[x]`), which `ast.Node` can conduct on **both array and object**;
|
- **Hashing** (`map[x]`) is not as efficient as **Indexing** (`array[x]`), which `ast.Node` can conduct on **both array and object**;
|
||||||
- Using `Interface()`/`Map()` means Sonic must parse all the underlying values, while `ast.Node` can parse them **on demand**.
|
- Using `Interface()`/`Map()` means Sonic must parse all the underlying values, while `ast.Node` can parse them **on demand**.
|
||||||
|
|
||||||
**CAUTION:** `ast.Node` **DOESN'T** ensure concurrent security directly, due to its **lazy-load** design. However, you can call `Node.Load()`/`Node.LoadAll()` to achieve that, which may bring performance reduction while it still works faster than converting to `map` or `interface{}`
|
**CAUTION:** `ast.Node` **DOESN'T** ensure concurrent security directly, due to its **lazy-load** design. However, you can call `Node.Load()`/`Node.LoadAll()` to achieve that, which may bring performance reduction while it still works faster than converting to `map` or `interface{}`
|
||||||
|
|
||||||
### Ast.Node or Ast.Visitor?
|
|
||||||
|
|
||||||
For generic data, `ast.Node` should be enough for your needs in most cases.
|
|
||||||
|
|
||||||
However, `ast.Node` is designed for partially processing JSON string. It has some special designs such as lazy-load which might not be suitable for directly parsing the whole JSON string like `Unmarshal()`. Although `ast.Node` is better then `map` or `interface{}`, it's also a kind of intermediate representation after all if your final types are customized and you have to convert the above types to your custom types after parsing.
|
|
||||||
|
|
||||||
For better performance, in previous case the `ast.Visitor` will be the better choice. It performs JSON decoding like `Unmarshal()` and you can directly use your final types to represents a JSON AST without any intermediate representations.
|
|
||||||
|
|
||||||
But `ast.Visitor` is not a very handy API. You might need to write a lot of code to implement your visitor and carefully maintain the tree hierarchy during decoding. Please read the comments in [ast/visitor.go](https://github.com/bytedance/sonic/blob/main/ast/visitor.go) carefully if you decide to use this API.
|
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
Sonic is a subproject of [CloudWeGo](https://www.cloudwego.io/). We are committed to building a cloud native ecosystem.
|
Sonic is a subproject of [CloudWeGo](https://www.cloudwego.io/). We are committed to building a cloud native ecosystem.
|
||||||
|
|||||||
97
vendor/github.com/bytedance/sonic/README_ZH_CN.md
generated
vendored
97
vendor/github.com/bytedance/sonic/README_ZH_CN.md
generated
vendored
@@ -6,14 +6,10 @@
|
|||||||
|
|
||||||
## 依赖
|
## 依赖
|
||||||
|
|
||||||
- Go 1.16~1.22
|
- Go 1.15~1.20
|
||||||
- Linux / MacOS / Windows(需要 Go1.17 以上)
|
- Linux/MacOS/Windows
|
||||||
- Amd64 架构
|
- Amd64 架构
|
||||||
|
|
||||||
## 接口
|
|
||||||
|
|
||||||
详见 [go.dev](https://pkg.go.dev/github.com/bytedance/sonic)
|
|
||||||
|
|
||||||
## 特色
|
## 特色
|
||||||
|
|
||||||
- 运行时对象绑定,无需代码生成
|
- 运行时对象绑定,无需代码生成
|
||||||
@@ -23,9 +19,7 @@
|
|||||||
## 基准测试
|
## 基准测试
|
||||||
|
|
||||||
对于**所有大小**的 json 和**所有使用场景**, **Sonic 表现均为最佳**。
|
对于**所有大小**的 json 和**所有使用场景**, **Sonic 表现均为最佳**。
|
||||||
|
|
||||||
- [中型](https://github.com/bytedance/sonic/blob/main/decoder/testdata_test.go#L19) (13kB, 300+ 键, 6 层)
|
- [中型](https://github.com/bytedance/sonic/blob/main/decoder/testdata_test.go#L19) (13kB, 300+ 键, 6 层)
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
goversion: 1.17.1
|
goversion: 1.17.1
|
||||||
goos: darwin
|
goos: darwin
|
||||||
@@ -85,18 +79,13 @@ BenchmarkSetOne_Jsoniter-16 79475 ns/op 163.8
|
|||||||
BenchmarkSetOne_Parallel_Sonic-16 850.9 ns/op 15305.31 MB/s 1584 B/op 17 allocs/op
|
BenchmarkSetOne_Parallel_Sonic-16 850.9 ns/op 15305.31 MB/s 1584 B/op 17 allocs/op
|
||||||
BenchmarkSetOne_Parallel_Sjson-16 18194 ns/op 715.77 MB/s 52247 B/op 9 allocs/op
|
BenchmarkSetOne_Parallel_Sjson-16 18194 ns/op 715.77 MB/s 52247 B/op 9 allocs/op
|
||||||
BenchmarkSetOne_Parallel_Jsoniter-16 33560 ns/op 388.05 MB/s 45892 B/op 964 allocs/op
|
BenchmarkSetOne_Parallel_Jsoniter-16 33560 ns/op 388.05 MB/s 45892 B/op 964 allocs/op
|
||||||
BenchmarkLoadNode/LoadAll()-16 11384 ns/op 1143.93 MB/s 6307 B/op 25 allocs/op
|
|
||||||
BenchmarkLoadNode_Parallel/LoadAll()-16 5493 ns/op 2370.68 MB/s 7145 B/op 25 allocs/op
|
|
||||||
BenchmarkLoadNode/Interface()-16 17722 ns/op 734.85 MB/s 13323 B/op 88 allocs/op
|
|
||||||
BenchmarkLoadNode_Parallel/Interface()-16 10330 ns/op 1260.70 MB/s 15178 B/op 88 allocs/op
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- [小型](https://github.com/bytedance/sonic/blob/main/testdata/small.go) (400B, 11 个键, 3 层)
|
- [小型](https://github.com/bytedance/sonic/blob/main/testdata/small.go) (400B, 11 个键, 3 层)
|
||||||

|

|
||||||
- [大型](https://github.com/bytedance/sonic/blob/main/testdata/twitter.json) (635kB, 10000+ 个键, 6 层)
|
- [大型](https://github.com/bytedance/sonic/blob/main/testdata/twitter.json) (635kB, 10000+ 个键, 6 层)
|
||||||

|

|
||||||
|
|
||||||
要查看基准测试代码,请参阅 [bench.sh](https://github.com/bytedance/sonic/blob/main/scripts/bench.sh) 。
|
要查看基准测试代码,请参阅 [bench.sh](https://github.com/bytedance/sonic/blob/main/bench.sh) 。
|
||||||
|
|
||||||
## 工作原理
|
## 工作原理
|
||||||
|
|
||||||
@@ -107,7 +96,6 @@ BenchmarkLoadNode_Parallel/Interface()-16 10330 ns/op 1260.7
|
|||||||
### 序列化/反序列化
|
### 序列化/反序列化
|
||||||
|
|
||||||
默认的行为基本上与 `encoding/json` 相一致,除了 HTML 转义形式(参见 [Escape HTML](https://github.com/bytedance/sonic/blob/main/README.md#escape-html)) 和 `SortKeys` 功能(参见 [Sort Keys](https://github.com/bytedance/sonic/blob/main/README.md#sort-keys))**没有**遵循 [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259) 。
|
默认的行为基本上与 `encoding/json` 相一致,除了 HTML 转义形式(参见 [Escape HTML](https://github.com/bytedance/sonic/blob/main/README.md#escape-html)) 和 `SortKeys` 功能(参见 [Sort Keys](https://github.com/bytedance/sonic/blob/main/README.md#sort-keys))**没有**遵循 [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259) 。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -121,9 +109,7 @@ err := sonic.Unmarshal(output, &data)
|
|||||||
### 流式输入输出
|
### 流式输入输出
|
||||||
|
|
||||||
Sonic 支持解码 `io.Reader` 中输入的 json,或将对象编码为 json 后输出至 `io.Writer`,以处理多个值并减少内存消耗。
|
Sonic 支持解码 `io.Reader` 中输入的 json,或将对象编码为 json 后输出至 `io.Writer`,以处理多个值并减少内存消耗。
|
||||||
|
|
||||||
- 编码器
|
- 编码器
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var o1 = map[string]interface{}{
|
var o1 = map[string]interface{}{
|
||||||
"a": "b",
|
"a": "b",
|
||||||
@@ -138,9 +124,7 @@ fmt.Println(w.String())
|
|||||||
// {"a":"b"}
|
// {"a":"b"}
|
||||||
// 1
|
// 1
|
||||||
```
|
```
|
||||||
|
|
||||||
- 解码器
|
- 解码器
|
||||||
|
|
||||||
```go
|
```go
|
||||||
var o = map[string]interface{}{}
|
var o = map[string]interface{}{}
|
||||||
var r = strings.NewReader(`{"a":"b"}{"1":"2"}`)
|
var r = strings.NewReader(`{"a":"b"}{"1":"2"}`)
|
||||||
@@ -184,7 +168,6 @@ fm := root.Interface().(float64) // jn == jm
|
|||||||
### 对键排序
|
### 对键排序
|
||||||
|
|
||||||
考虑到排序带来的性能损失(约 10% ), sonic 默认不会启用这个功能。如果你的组件依赖这个行为(如 [zstd](https://github.com/facebook/zstd)) ,可以仿照下面的例子:
|
考虑到排序带来的性能损失(约 10% ), sonic 默认不会启用这个功能。如果你的组件依赖这个行为(如 [zstd](https://github.com/facebook/zstd)) ,可以仿照下面的例子:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
import "github.com/bytedance/sonic/encoder"
|
import "github.com/bytedance/sonic/encoder"
|
||||||
@@ -201,7 +184,6 @@ err := root.SortKeys()
|
|||||||
### HTML 转义
|
### HTML 转义
|
||||||
|
|
||||||
考虑到性能损失(约15%), sonic 默认不会启用这个功能。你可以使用 `encoder.EscapeHTML` 选项来开启(与 `encoding/json.HTMLEscape` 行为一致)。
|
考虑到性能损失(约15%), sonic 默认不会启用这个功能。你可以使用 `encoder.EscapeHTML` 选项来开启(与 `encoding/json.HTMLEscape` 行为一致)。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -210,13 +192,11 @@ ret, err := Encode(v, EscapeHTML) // ret == `{"\u0026\u0026":{"X":"\u003c\u003e"
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 紧凑格式
|
### 紧凑格式
|
||||||
|
|
||||||
Sonic 默认将基本类型( `struct` , `map` 等)编码为紧凑格式的 JSON ,除非使用 `json.RawMessage` or `json.Marshaler` 进行编码: sonic 确保输出的 JSON 合法,但出于性能考虑,**不会**加工成紧凑格式。我们提供选项 `encoder.CompactMarshaler` 来添加此过程,
|
Sonic 默认将基本类型( `struct` , `map` 等)编码为紧凑格式的 JSON ,除非使用 `json.RawMessage` or `json.Marshaler` 进行编码: sonic 确保输出的 JSON 合法,但出于性能考虑,**不会**加工成紧凑格式。我们提供选项 `encoder.CompactMarshaler` 来添加此过程,
|
||||||
|
|
||||||
### 打印错误
|
### 打印错误
|
||||||
|
|
||||||
如果输入的 JSON 存在无效的语法,sonic 将返回 `decoder.SyntaxError`,该错误支持错误位置的美化输出。
|
如果输入的 JSON 存在无效的语法,sonic 将返回 `decoder.SyntaxError`,该错误支持错误位置的美化输出。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
import "github.com/bytedance/sonic/decoder"
|
import "github.com/bytedance/sonic/decoder"
|
||||||
@@ -244,7 +224,6 @@ if err != nil {
|
|||||||
#### 类型不匹配 [Sonic v1.6.0]
|
#### 类型不匹配 [Sonic v1.6.0]
|
||||||
|
|
||||||
如果给定键中存在**类型不匹配**的值, sonic 会抛出 `decoder.MismatchTypeError` (如果有多个,只会报告最后一个),但仍会跳过错误的值并解码下一个 JSON 。
|
如果给定键中存在**类型不匹配**的值, sonic 会抛出 `decoder.MismatchTypeError` (如果有多个,只会报告最后一个),但仍会跳过错误的值并解码下一个 JSON 。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
import "github.com/bytedance/sonic/decoder"
|
import "github.com/bytedance/sonic/decoder"
|
||||||
@@ -257,7 +236,6 @@ err := UnmarshalString(`{"A":"1","B":1}`, &data)
|
|||||||
println(err.Error()) // Mismatch type int with value string "at index 5: mismatched type with value\n\n\t{\"A\":\"1\",\"B\":1}\n\t.....^.........\n"
|
println(err.Error()) // Mismatch type int with value string "at index 5: mismatched type with value\n\n\t{\"A\":\"1\",\"B\":1}\n\t.....^.........\n"
|
||||||
fmt.Printf("%+v", data) // {A:0 B:1}
|
fmt.Printf("%+v", data) // {A:0 B:1}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `Ast.Node`
|
### `Ast.Node`
|
||||||
|
|
||||||
Sonic/ast.Node 是完全独立的 JSON 抽象语法树库。它实现了序列化和反序列化,并提供了获取和修改通用数据的鲁棒的 API。
|
Sonic/ast.Node 是完全独立的 JSON 抽象语法树库。它实现了序列化和反序列化,并提供了获取和修改通用数据的鲁棒的 API。
|
||||||
@@ -265,7 +243,6 @@ Sonic/ast.Node 是完全独立的 JSON 抽象语法树库。它实现了序列
|
|||||||
#### 查找/索引
|
#### 查找/索引
|
||||||
|
|
||||||
通过给定的路径搜索 JSON 片段,路径必须为非负整数,字符串或 `nil` 。
|
通过给定的路径搜索 JSON 片段,路径必须为非负整数,字符串或 `nil` 。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -279,13 +256,11 @@ raw := root.Raw() // == string(input)
|
|||||||
root, err := sonic.Get(input, "key1", 1, "key2")
|
root, err := sonic.Get(input, "key1", 1, "key2")
|
||||||
sub := root.Get("key3").Index(2).Int64() // == 3
|
sub := root.Get("key3").Index(2).Int64() // == 3
|
||||||
```
|
```
|
||||||
|
|
||||||
**注意**:由于 `Index()` 使用偏移量来定位数据,比使用扫描的 `Get()` 要快的多,建议尽可能的使用 `Index` 。 Sonic 也提供了另一个 API, `IndexOrGet()` ,以偏移量为基础并且也确保键的匹配。
|
**注意**:由于 `Index()` 使用偏移量来定位数据,比使用扫描的 `Get()` 要快的多,建议尽可能的使用 `Index` 。 Sonic 也提供了另一个 API, `IndexOrGet()` ,以偏移量为基础并且也确保键的匹配。
|
||||||
|
|
||||||
#### 修改
|
#### 修改
|
||||||
|
|
||||||
使用 `Set()` / `Unset()` 修改 json 的内容
|
使用 ` Set()` / `Unset()` 修改 json 的内容
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -302,9 +277,7 @@ println(root.Get("key4").Check()) // "value not exist"
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 序列化
|
#### 序列化
|
||||||
|
|
||||||
要将 `ast.Node` 编码为 json ,使用 `MarshalJson()` 或者 `json.Marshal()` (必须传递指向节点的指针)
|
要将 `ast.Node` 编码为 json ,使用 `MarshalJson()` 或者 `json.Marshal()` (必须传递指向节点的指针)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -318,7 +291,6 @@ println(string(buf) == string(exp)) // true
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### APIs
|
#### APIs
|
||||||
|
|
||||||
- 合法性检查: `Check()`, `Error()`, `Valid()`, `Exist()`
|
- 合法性检查: `Check()`, `Error()`, `Valid()`, `Exist()`
|
||||||
- 索引: `Index()`, `Get()`, `IndexPair()`, `IndexOrGet()`, `GetByPath()`
|
- 索引: `Index()`, `Get()`, `IndexPair()`, `IndexOrGet()`, `GetByPath()`
|
||||||
- 转换至 go 内置类型: `Int64()`, `Float64()`, `String()`, `Number()`, `Bool()`, `Map[UseNumber|UseNode]()`, `Array[UseNumber|UseNode]()`, `Interface[UseNumber|UseNode]()`
|
- 转换至 go 内置类型: `Int64()`, `Float64()`, `String()`, `Number()`, `Bool()`, `Map[UseNumber|UseNode]()`, `Array[UseNumber|UseNode]()`, `Interface[UseNumber|UseNode]()`
|
||||||
@@ -326,55 +298,13 @@ println(string(buf) == string(exp)) // true
|
|||||||
- 迭代: `Values()`, `Properties()`, `ForEach()`, `SortKeys()`
|
- 迭代: `Values()`, `Properties()`, `ForEach()`, `SortKeys()`
|
||||||
- 修改: `Set()`, `SetByIndex()`, `Add()`
|
- 修改: `Set()`, `SetByIndex()`, `Add()`
|
||||||
|
|
||||||
### `Ast.Visitor`
|
|
||||||
|
|
||||||
Sonic 提供了一个高级的 API 用于直接全量解析 JSON 到非标准容器里 (既不是 `struct` 也不是 `map[string]interface{}`) 且不需要借助任何中间表示 (`ast.Node` 或 `interface{}`)。举个例子,你可能定义了下述的类型,它们看起来像 `interface{}`,但实际上并不是:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type UserNode interface {}
|
|
||||||
|
|
||||||
// the following types implement the UserNode interface.
|
|
||||||
type (
|
|
||||||
UserNull struct{}
|
|
||||||
UserBool struct{ Value bool }
|
|
||||||
UserInt64 struct{ Value int64 }
|
|
||||||
UserFloat64 struct{ Value float64 }
|
|
||||||
UserString struct{ Value string }
|
|
||||||
UserObject struct{ Value map[string]UserNode }
|
|
||||||
UserArray struct{ Value []UserNode }
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Sonic 提供了下述的 API 来返回 **“对 JSON AST 的前序遍历”**。`ast.Visitor` 是一个 SAX 风格的接口,这在某些 C++ 的 JSON 解析库中被使用到。你需要自己实现一个 `ast.Visitor`,将它传递给 `ast.Preorder()` 方法。在你的实现中你可以使用自定义的类型来表示 JSON 的值。在你的 `ast.Visitor` 中,可能需要有一个 O(n) 空间复杂度的容器(比如说栈)来记录 object / array 的层级。
|
|
||||||
|
|
||||||
```go
|
|
||||||
func Preorder(str string, visitor Visitor, opts *VisitorOptions) error
|
|
||||||
|
|
||||||
type Visitor interface {
|
|
||||||
OnNull() error
|
|
||||||
OnBool(v bool) error
|
|
||||||
OnString(v string) error
|
|
||||||
OnInt64(v int64, n json.Number) error
|
|
||||||
OnFloat64(v float64, n json.Number) error
|
|
||||||
OnObjectBegin(capacity int) error
|
|
||||||
OnObjectKey(key string) error
|
|
||||||
OnObjectEnd() error
|
|
||||||
OnArrayBegin(capacity int) error
|
|
||||||
OnArrayEnd() error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
详细用法参看 [ast/visitor.go](https://github.com/bytedance/sonic/blob/main/ast/visitor.go),我们还为 `UserNode` 实现了一个示例 `ast.Visitor`,你可以在 [ast/visitor_test.go](https://github.com/bytedance/sonic/blob/main/ast/visitor_test.go) 中找到它。
|
|
||||||
|
|
||||||
## 兼容性
|
## 兼容性
|
||||||
|
|
||||||
由于开发高性能代码的困难性, Sonic **不**保证对所有环境的支持。对于在不同环境中使用 Sonic 构建应用程序的开发者,我们有以下建议:
|
由于开发高性能代码的困难性, Sonic **不**保证对所有环境的支持。对于在不同环境中使用 Sonic 构建应用程序的开发者,我们有以下建议:
|
||||||
|
|
||||||
- 在 **Mac M1** 上开发:确保在您的计算机上安装了 Rosetta 2,并在构建时设置 `GOARCH=amd64` 。 Rosetta 2 可以自动将 x86 二进制文件转换为 arm64 二进制文件,并在 Mac M1 上运行 x86 应用程序。
|
- 在 **Mac M1** 上开发:确保在您的计算机上安装了 Rosetta 2,并在构建时设置 `GOARCH=amd64` 。 Rosetta 2 可以自动将 x86 二进制文件转换为 arm64 二进制文件,并在 Mac M1 上运行 x86 应用程序。
|
||||||
- 在 **Linux arm64** 上开发:您可以安装 qemu 并使用 `qemu-x86_64 -cpu max` 命令来将 x86 二进制文件转换为 arm64 二进制文件。qemu可以实现与Mac M1上的Rosetta 2类似的转换效果。
|
- 在 **Linux arm64** 上开发:您可以安装 qemu 并使用 `qemu-x86_64 -cpu max` 命令来将 x86 二进制文件转换为 arm64 二进制文件。qemu可以实现与Mac M1上的Rosetta 2类似的转换效果。
|
||||||
|
|
||||||
对于希望在不使用 qemu 下使用 sonic 的开发者,或者希望处理 JSON 时与 `encoding/JSON` 严格保持一致的开发者,我们在 `sonic.API` 中提供了一些兼容性 API
|
对于希望在不使用 qemu 下使用 sonic 的开发者,或者希望处理 JSON 时与 `encoding/JSON` 严格保持一致的开发者,我们在 `sonic.API` 中提供了一些兼容性 API
|
||||||
|
|
||||||
- `ConfigDefault`: 在支持 sonic 的环境下 sonic 的默认配置(`EscapeHTML=false`,`SortKeys=false`等)。行为与具有相应配置的 `encoding/json` 一致,一些选项,如 `SortKeys=false` 将无效。
|
- `ConfigDefault`: 在支持 sonic 的环境下 sonic 的默认配置(`EscapeHTML=false`,`SortKeys=false`等)。行为与具有相应配置的 `encoding/json` 一致,一些选项,如 `SortKeys=false` 将无效。
|
||||||
- `ConfigStd`: 在支持 sonic 的环境下与标准库兼容的配置(`EscapeHTML=true`,`SortKeys=true`等)。行为与 `encoding/json` 一致。
|
- `ConfigStd`: 在支持 sonic 的环境下与标准库兼容的配置(`EscapeHTML=true`,`SortKeys=true`等)。行为与 `encoding/json` 一致。
|
||||||
- `ConfigFastest`: 在支持 sonic 的环境下运行最快的配置(`NoQuoteTextMarshaler=true`)。行为与具有相应配置的 `encoding/json` 一致,某些选项将无效。
|
- `ConfigFastest`: 在支持 sonic 的环境下运行最快的配置(`NoQuoteTextMarshaler=true`)。行为与具有相应配置的 `encoding/json` 一致,某些选项将无效。
|
||||||
@@ -382,9 +312,7 @@ type Visitor interface {
|
|||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
### 预热
|
### 预热
|
||||||
|
|
||||||
由于 Sonic 使用 [golang-asm](https://github.com/twitchyliquid64/golang-asm) 作为 JIT 汇编器,这个库并不适用于运行时编译,第一次运行一个大型模式可能会导致请求超时甚至进程内存溢出。为了更好地稳定性,我们建议在运行大型模式或在内存有限的应用中,在使用 `Marshal()/Unmarshal()` 前运行 `Pretouch()`。
|
由于 Sonic 使用 [golang-asm](https://github.com/twitchyliquid64/golang-asm) 作为 JIT 汇编器,这个库并不适用于运行时编译,第一次运行一个大型模式可能会导致请求超时甚至进程内存溢出。为了更好地稳定性,我们建议在运行大型模式或在内存有限的应用中,在使用 `Marshal()/Unmarshal()` 前运行 `Pretouch()`。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -414,17 +342,16 @@ func init() {
|
|||||||
当解码 **没有转义字符的字符串**时, sonic 会从原始的 JSON 缓冲区内引用而不是复制到新的一个缓冲区中。这对 CPU 的性能方面很有帮助,但是可能因此在解码后对象仍在使用的时候将整个 JSON 缓冲区保留在内存中。实践中我们发现,通过引用 JSON 缓冲区引入的额外内存通常是解码后对象的 20% 至 80% ,一旦应用长期保留这些对象(如缓存以备重用),服务器所使用的内存可能会增加。我们提供了选项 `decoder.CopyString()` 供用户选择,不引用 JSON 缓冲区。这可能在一定程度上降低 CPU 性能。
|
当解码 **没有转义字符的字符串**时, sonic 会从原始的 JSON 缓冲区内引用而不是复制到新的一个缓冲区中。这对 CPU 的性能方面很有帮助,但是可能因此在解码后对象仍在使用的时候将整个 JSON 缓冲区保留在内存中。实践中我们发现,通过引用 JSON 缓冲区引入的额外内存通常是解码后对象的 20% 至 80% ,一旦应用长期保留这些对象(如缓存以备重用),服务器所使用的内存可能会增加。我们提供了选项 `decoder.CopyString()` 供用户选择,不引用 JSON 缓冲区。这可能在一定程度上降低 CPU 性能。
|
||||||
|
|
||||||
### 传递字符串还是字节数组?
|
### 传递字符串还是字节数组?
|
||||||
|
|
||||||
为了和 `encoding/json` 保持一致,我们提供了传递 `[]byte` 作为参数的 API ,但考虑到安全性,字符串到字节的复制是同时进行的,这在原始 JSON 非常大时可能会导致性能损失。因此,你可以使用 `UnmarshalString()` 和 `GetFromString()` 来传递字符串,只要你的原始数据是字符串,或**零拷贝类型转换**对于你的字节数组是安全的。我们也提供了 `MarshalString()` 的 API ,以便对编码的 JSON 字节数组进行**零拷贝类型转换**,因为 sonic 输出的字节始终是重复并且唯一的,所以这样是安全的。
|
为了和 `encoding/json` 保持一致,我们提供了传递 `[]byte` 作为参数的 API ,但考虑到安全性,字符串到字节的复制是同时进行的,这在原始 JSON 非常大时可能会导致性能损失。因此,你可以使用 `UnmarshalString()` 和 `GetFromString()` 来传递字符串,只要你的原始数据是字符串,或**零拷贝类型转换**对于你的字节数组是安全的。我们也提供了 `MarshalString()` 的 API ,以便对编码的 JSON 字节数组进行**零拷贝类型转换**,因为 sonic 输出的字节始终是重复并且唯一的,所以这样是安全的。
|
||||||
|
|
||||||
### 加速 `encoding.TextMarshaler`
|
### 加速 `encoding.TextMarshaler`
|
||||||
|
|
||||||
为了保证数据安全性, `sonic.Encoder` 默认会对来自 `encoding.TextMarshaler` 接口的字符串进行引用和转义,如果大部分数据都是这种形式那可能会导致很大的性能损失。我们提供了 `encoder.NoQuoteTextMarshaler` 选项来跳过这些操作,但你**必须**保证他们的输出字符串依照 [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259) 进行了转义和引用。
|
为了保证数据安全性, `sonic.Encoder` 默认会对来自 `encoding.TextMarshaler` 接口的字符串进行引用和转义,如果大部分数据都是这种形式那可能会导致很大的性能损失。我们提供了 `encoder.NoQuoteTextMarshaler` 选项来跳过这些操作,但你**必须**保证他们的输出字符串依照 [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259) 进行了转义和引用。
|
||||||
|
|
||||||
|
|
||||||
### 泛型的性能优化
|
### 泛型的性能优化
|
||||||
|
|
||||||
在 **完全解析**的场景下, `Unmarshal()` 表现得比 `Get()`+`Node.Interface()` 更好。但是如果你只有特定 JSON 的部分模式,你可以将 `Get()` 和 `Unmarshal()` 结合使用:
|
在 **完全解析**的场景下, `Unmarshal()` 表现得比 `Get()`+`Node.Interface()` 更好。但是如果你只有特定 JSON 的部分模式,你可以将 `Get()` 和 `Unmarshal()` 结合使用:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -432,9 +359,7 @@ node, err := sonic.GetFromString(_TwitterJson, "statuses", 3, "user")
|
|||||||
var user User // your partial schema...
|
var user User // your partial schema...
|
||||||
err = sonic.UnmarshalString(node.Raw(), &user)
|
err = sonic.UnmarshalString(node.Raw(), &user)
|
||||||
```
|
```
|
||||||
|
|
||||||
甚至如果你没有任何模式,可以用 `ast.Node` 代替 `map` 或 `interface` 作为泛型的容器:
|
甚至如果你没有任何模式,可以用 `ast.Node` 代替 `map` 或 `interface` 作为泛型的容器:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/bytedance/sonic"
|
import "github.com/bytedance/sonic"
|
||||||
|
|
||||||
@@ -445,25 +370,13 @@ err = user.Check()
|
|||||||
// err = user.LoadAll() // only call this when you want to use 'user' concurrently...
|
// err = user.LoadAll() // only call this when you want to use 'user' concurrently...
|
||||||
go someFunc(user)
|
go someFunc(user)
|
||||||
```
|
```
|
||||||
|
|
||||||
为什么?因为 `ast.Node` 使用 `array` 来存储其子节点:
|
为什么?因为 `ast.Node` 使用 `array` 来存储其子节点:
|
||||||
|
|
||||||
- 在插入(反序列化)和扫描(序列化)数据时,`Array` 的性能比 `Map` **好得多**;
|
- 在插入(反序列化)和扫描(序列化)数据时,`Array` 的性能比 `Map` **好得多**;
|
||||||
- **哈希**(`map[x]`)的效率不如**索引**(`array[x]`)高效,而 `ast.Node` 可以在数组和对象上使用索引;
|
- **哈希**(`map[x]`)的效率不如**索引**(`array[x]`)高效,而 `ast.Node` 可以在数组和对象上使用索引;
|
||||||
- 使用 `Interface()` / `Map()` 意味着 sonic 必须解析所有的底层值,而 `ast.Node` 可以**按需解析**它们。
|
- 使用 `Interface()` / `Map()` 意味着 sonic 必须解析所有的底层值,而 `ast.Node` 可以**按需解析**它们。
|
||||||
|
|
||||||
**注意**:由于 `ast.Node` 的惰性加载设计,其**不能**直接保证并发安全性,但你可以调用 `Node.Load()` / `Node.LoadAll()` 来实现并发安全。尽管可能会带来性能损失,但仍比转换成 `map` 或 `interface{}` 更为高效。
|
**注意**:由于 `ast.Node` 的惰性加载设计,其**不能**直接保证并发安全性,但你可以调用 `Node.Load()` / `Node.LoadAll()` 来实现并发安全。尽管可能会带来性能损失,但仍比转换成 `map` 或 `interface{}` 更为高效。
|
||||||
|
|
||||||
### 使用 `ast.Node` 还是 `ast.Visitor`?
|
|
||||||
|
|
||||||
对于泛型数据的解析,`ast.Node` 在大多数场景上应该能够满足你的需求。
|
|
||||||
|
|
||||||
然而,`ast.Node` 是一种针对部分解析 JSON 而设计的泛型容器,它包含一些特殊设计,比如惰性加载,如果你希望像 `Unmarshal()` 那样直接解析整个 JSON,这些设计可能并不合适。尽管 `ast.Node` 相较于 `map` 或 `interface{}` 来说是更好的一种泛型容器,但它毕竟也是一种中间表示,如果你的最终类型是自定义的,你还得在解析完成后将上述类型转化成你自定义的类型。
|
|
||||||
|
|
||||||
在上述场景中,如果想要有更极致的性能,`ast.Visitor` 会是更好的选择。它采用和 `Unmarshal()` 类似的形式解析 JSON,并且你可以直接使用你的最终类型去表示 JSON AST,而不需要经过额外的任何中间表示。
|
|
||||||
|
|
||||||
但是,`ast.Visitor` 并不是一个很易用的 API。你可能需要写大量的代码去实现自己的 `ast.Visitor`,并且需要在解析过程中仔细维护树的层级。如果你决定要使用这个 API,请先仔细阅读 [ast/visitor.go](https://github.com/bytedance/sonic/blob/main/ast/visitor.go) 中的注释。
|
|
||||||
|
|
||||||
## 社区
|
## 社区
|
||||||
|
|
||||||
Sonic 是 [CloudWeGo](https://www.cloudwego.io/) 下的一个子项目。我们致力于构建云原生生态系统。
|
Sonic 是 [CloudWeGo](https://www.cloudwego.io/) 下的一个子项目。我们致力于构建云原生生态系统。
|
||||||
|
|||||||
44
vendor/github.com/bytedance/sonic/api.go
generated
vendored
44
vendor/github.com/bytedance/sonic/api.go
generated
vendored
@@ -20,7 +20,6 @@ import (
|
|||||||
`io`
|
`io`
|
||||||
|
|
||||||
`github.com/bytedance/sonic/ast`
|
`github.com/bytedance/sonic/ast`
|
||||||
`github.com/bytedance/sonic/internal/rt`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is a combination of sonic/encoder.Options and sonic/decoder.Options
|
// Config is a combination of sonic/encoder.Options and sonic/decoder.Options
|
||||||
@@ -70,17 +69,10 @@ type Config struct {
|
|||||||
// ValidateString indicates decoder and encoder to valid string values: decoder will return errors
|
// ValidateString indicates decoder and encoder to valid string values: decoder will return errors
|
||||||
// when unescaped control chars(\u0000-\u001f) in the string value of JSON.
|
// when unescaped control chars(\u0000-\u001f) in the string value of JSON.
|
||||||
ValidateString bool
|
ValidateString bool
|
||||||
|
|
||||||
// NoValidateJSONMarshaler indicates that the encoder should not validate the output string
|
|
||||||
// after encoding the JSONMarshaler to JSON.
|
|
||||||
NoValidateJSONMarshaler bool
|
|
||||||
|
|
||||||
// NoEncoderNewline indicates that the encoder should not add a newline after every message
|
|
||||||
NoEncoderNewline bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ConfigDefault is the default config of APIs, aiming at efficiency and safety.
|
// ConfigDefault is the default config of APIs, aiming at efficiency and safty.
|
||||||
ConfigDefault = Config{}.Froze()
|
ConfigDefault = Config{}.Froze()
|
||||||
|
|
||||||
// ConfigStd is the standard config of APIs, aiming at being compatible with encoding/json.
|
// ConfigStd is the standard config of APIs, aiming at being compatible with encoding/json.
|
||||||
@@ -95,7 +87,6 @@ var (
|
|||||||
// ConfigFastest is the fastest config of APIs, aiming at speed.
|
// ConfigFastest is the fastest config of APIs, aiming at speed.
|
||||||
ConfigFastest = Config{
|
ConfigFastest = Config{
|
||||||
NoQuoteTextMarshaler: true,
|
NoQuoteTextMarshaler: true,
|
||||||
NoValidateJSONMarshaler: true,
|
|
||||||
}.Froze()
|
}.Froze()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,7 +109,7 @@ type API interface {
|
|||||||
NewEncoder(writer io.Writer) Encoder
|
NewEncoder(writer io.Writer) Encoder
|
||||||
// NewDecoder create a Decoder holding reader
|
// NewDecoder create a Decoder holding reader
|
||||||
NewDecoder(reader io.Reader) Decoder
|
NewDecoder(reader io.Reader) Decoder
|
||||||
// Valid validates the JSON-encoded bytes and reports if it is valid
|
// Valid validates the JSON-encoded bytes and reportes if it is valid
|
||||||
Valid(data []byte) bool
|
Valid(data []byte) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,41 +165,22 @@ func UnmarshalString(buf string, val interface{}) error {
|
|||||||
return ConfigDefault.UnmarshalFromString(buf, val)
|
return ConfigDefault.UnmarshalFromString(buf, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get searches and locates the given path from src json,
|
// Get searches the given path from json,
|
||||||
// and returns a ast.Node representing the partially json.
|
// and returns its representing ast.Node.
|
||||||
//
|
//
|
||||||
// Each path arg must be integer or string:
|
// Each path arg must be integer or string:
|
||||||
// - Integer is target index(>=0), means searching current node as array.
|
// - Integer is target index(>=0), means searching current node as array.
|
||||||
// - String is target key, means searching current node as object.
|
// - String is target key, means searching current node as object.
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Notice: It expects the src json is **Well-formed** and **Immutable** when calling,
|
// Note, the api expects the json is well-formed at least,
|
||||||
// otherwise it may return unexpected result.
|
// otherwise it may return unexpected result.
|
||||||
// Considering memory safety, the returned JSON is **Copied** from the input
|
|
||||||
func Get(src []byte, path ...interface{}) (ast.Node, error) {
|
func Get(src []byte, path ...interface{}) (ast.Node, error) {
|
||||||
return GetCopyFromString(rt.Mem2Str(src), path...)
|
return GetFromString(string(src), path...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFromString is same with Get except src is string.
|
// GetFromString is same with Get except src is string,
|
||||||
//
|
// which can reduce unnecessary memory copy.
|
||||||
// WARNING: The returned JSON is **Referenced** from the input.
|
|
||||||
// Caching or long-time holding the returned node may cause OOM.
|
|
||||||
// If your src is big, consider use GetFromStringCopy().
|
|
||||||
func GetFromString(src string, path ...interface{}) (ast.Node, error) {
|
func GetFromString(src string, path ...interface{}) (ast.Node, error) {
|
||||||
return ast.NewSearcher(src).GetByPath(path...)
|
return ast.NewSearcher(src).GetByPath(path...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCopyFromString is same with Get except src is string
|
|
||||||
func GetCopyFromString(src string, path ...interface{}) (ast.Node, error) {
|
|
||||||
return ast.NewSearcher(src).GetByPathCopy(path...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid reports whether data is a valid JSON encoding.
|
|
||||||
func Valid(data []byte) bool {
|
|
||||||
return ConfigDefault.Valid(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid reports whether data is a valid JSON encoding.
|
|
||||||
func ValidString(data string) bool {
|
|
||||||
return ConfigDefault.Valid(rt.Str2Mem(data))
|
|
||||||
}
|
|
||||||
|
|||||||
84
vendor/github.com/bytedance/sonic/ast/api_compat.go
generated
vendored
84
vendor/github.com/bytedance/sonic/ast/api_compat.go
generated
vendored
@@ -1,40 +1,36 @@
|
|||||||
// +build !amd64,!arm64 go1.23 !go1.16 arm64,!go1.20
|
// +build !amd64 go1.21
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2022 ByteDance Inc.
|
* Copyright 2022 ByteDance Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
`encoding/base64`
|
||||||
`encoding/json`
|
`encoding/json`
|
||||||
`unicode/utf8`
|
`fmt`
|
||||||
|
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
`github.com/bytedance/sonic/internal/rt`
|
`github.com/bytedance/sonic/internal/rt`
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
println("WARNING:(ast) sonic only supports Go1.16~1.22, but your environment is not suitable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func quote(buf *[]byte, val string) {
|
func quote(buf *[]byte, val string) {
|
||||||
quoteString(buf, val)
|
quoteString(buf, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// unquote unescapes a internal JSON string (it doesn't count quotas at the begining and end)
|
|
||||||
func unquote(src string) (string, types.ParsingError) {
|
func unquote(src string) (string, types.ParsingError) {
|
||||||
sp := rt.IndexChar(src, -1)
|
sp := rt.IndexChar(src, -1)
|
||||||
out, ok := unquoteBytes(rt.BytesFrom(sp, len(src)+2, len(src)+2))
|
out, ok := unquoteBytes(rt.BytesFrom(sp, len(src)+2, len(src)+2))
|
||||||
@@ -44,9 +40,16 @@ func unquote(src string) (string, types.ParsingError) {
|
|||||||
return rt.Mem2Str(out), 0
|
return rt.Mem2Str(out), 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeBase64(src string) ([]byte, error) {
|
||||||
|
return base64.StdEncoding.DecodeString(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBase64(src []byte) string {
|
||||||
|
return base64.StdEncoding.EncodeToString(src)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *Parser) decodeValue() (val types.JsonState) {
|
func (self *Parser) decodeValue() (val types.JsonState) {
|
||||||
e, v := decodeValue(self.s, self.p, self.dbuf == nil)
|
e, v := decodeValue(self.s, self.p)
|
||||||
if e < 0 {
|
if e < 0 {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
@@ -81,34 +84,37 @@ func (self *Node) encodeInterface(buf *[]byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Parser) getByPath(validate bool, path ...interface{}) (int, types.ParsingError) {
|
func (self *Searcher) GetByPath(path ...interface{}) (Node, error) {
|
||||||
|
self.parser.p = 0
|
||||||
|
|
||||||
|
var err types.ParsingError
|
||||||
for _, p := range path {
|
for _, p := range path {
|
||||||
if idx, ok := p.(int); ok && idx >= 0 {
|
if idx, ok := p.(int); ok && idx >= 0 {
|
||||||
if err := self.searchIndex(idx); err != 0 {
|
if err = self.parser.searchIndex(idx); err != 0 {
|
||||||
return self.p, err
|
return Node{}, self.parser.ExportError(err)
|
||||||
}
|
}
|
||||||
} else if key, ok := p.(string); ok {
|
} else if key, ok := p.(string); ok {
|
||||||
if err := self.searchKey(key); err != 0 {
|
if err = self.parser.searchKey(key); err != 0 {
|
||||||
return self.p, err
|
return Node{}, self.parser.ExportError(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic("path must be either int(>=0) or string")
|
panic("path must be either int(>=0) or string")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var start int
|
var start = self.parser.p
|
||||||
var e types.ParsingError
|
if start, err = self.parser.skip(); err != 0 {
|
||||||
if validate {
|
return Node{}, self.parser.ExportError(err)
|
||||||
start, e = self.skip()
|
|
||||||
} else {
|
|
||||||
start, e = self.skipFast()
|
|
||||||
}
|
}
|
||||||
if e != 0 {
|
ns := len(self.parser.s)
|
||||||
return self.p, e
|
if self.parser.p > ns || start >= ns || start>=self.parser.p {
|
||||||
|
return Node{}, fmt.Errorf("skip %d char out of json boundary", start)
|
||||||
}
|
}
|
||||||
return start, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func validate_utf8(str string) bool {
|
t := switchRawType(self.parser.s[start])
|
||||||
return utf8.ValidString(str)
|
if t == _V_NONE {
|
||||||
|
return Node{}, self.parser.ExportError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRawNode(self.parser.s[start:self.parser.p], t), nil
|
||||||
}
|
}
|
||||||
45
vendor/github.com/bytedance/sonic/ast/decode.go
generated
vendored
45
vendor/github.com/bytedance/sonic/ast/decode.go
generated
vendored
@@ -220,7 +220,7 @@ func decodeFloat64(src string, pos int) (ret int, v float64, err error) {
|
|||||||
return ret, v, nil
|
return ret, v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeValue(src string, pos int, skipnum bool) (ret int, v types.JsonState) {
|
func decodeValue(src string, pos int) (ret int, v types.JsonState) {
|
||||||
pos = skipBlank(src, pos)
|
pos = skipBlank(src, pos)
|
||||||
if pos < 0 {
|
if pos < 0 {
|
||||||
return pos, types.JsonState{Vt: types.ValueType(pos)}
|
return pos, types.JsonState{Vt: types.ValueType(pos)}
|
||||||
@@ -256,14 +256,6 @@ func decodeValue(src string, pos int, skipnum bool) (ret int, v types.JsonState)
|
|||||||
}
|
}
|
||||||
return ret, types.JsonState{Vt: types.V_FALSE}
|
return ret, types.JsonState{Vt: types.V_FALSE}
|
||||||
case '-', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
case '-', '+', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
if skipnum {
|
|
||||||
ret = skipNumber(src, pos)
|
|
||||||
if ret >= 0 {
|
|
||||||
return ret, types.JsonState{Vt: types.V_DOUBLE, Iv: 0, Ep: pos}
|
|
||||||
} else {
|
|
||||||
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var iv int64
|
var iv int64
|
||||||
ret, iv, _ = decodeInt64(src, pos)
|
ret, iv, _ = decodeInt64(src, pos)
|
||||||
if ret >= 0 {
|
if ret >= 0 {
|
||||||
@@ -278,8 +270,6 @@ func decodeValue(src string, pos int, skipnum bool) (ret int, v types.JsonState)
|
|||||||
} else {
|
} else {
|
||||||
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
return ret, types.JsonState{Vt: types.ValueType(ret)}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return -int(types.ERR_INVALID_CHAR), types.JsonState{Vt:-types.ValueType(types.ERR_INVALID_CHAR)}
|
return -int(types.ERR_INVALID_CHAR), types.JsonState{Vt:-types.ValueType(types.ERR_INVALID_CHAR)}
|
||||||
}
|
}
|
||||||
@@ -583,36 +573,3 @@ func skipArray(src string, pos int) (ret int, start int) {
|
|||||||
pos++
|
pos++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeString decodes a JSON string from pos and return golang string.
|
|
||||||
// - needEsc indicates if to unescaped escaping chars
|
|
||||||
// - hasEsc tells if the returned string has escaping chars
|
|
||||||
// - validStr enables validating UTF8 charset
|
|
||||||
//
|
|
||||||
func _DecodeString(src string, pos int, needEsc bool, validStr bool) (v string, ret int, hasEsc bool) {
|
|
||||||
p := NewParserObj(src)
|
|
||||||
p.p = pos
|
|
||||||
switch val := p.decodeValue(); val.Vt {
|
|
||||||
case types.V_STRING:
|
|
||||||
str := p.s[val.Iv : p.p-1]
|
|
||||||
if validStr && !validate_utf8(str) {
|
|
||||||
return "", -int(types.ERR_INVALID_UTF8), false
|
|
||||||
}
|
|
||||||
/* fast path: no escape sequence */
|
|
||||||
if val.Ep == -1 {
|
|
||||||
return str, p.p, false
|
|
||||||
} else if !needEsc {
|
|
||||||
return str, p.p, true
|
|
||||||
}
|
|
||||||
/* unquote the string */
|
|
||||||
out, err := unquote(str)
|
|
||||||
/* check for errors */
|
|
||||||
if err != 0 {
|
|
||||||
return "", -int(err), true
|
|
||||||
} else {
|
|
||||||
return out, p.p, true
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return "", -int(_ERR_UNSUPPORT_TYPE), false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
42
vendor/github.com/bytedance/sonic/ast/encode.go
generated
vendored
42
vendor/github.com/bytedance/sonic/ast/encode.go
generated
vendored
@@ -19,6 +19,8 @@ package ast
|
|||||||
import (
|
import (
|
||||||
`sync`
|
`sync`
|
||||||
`unicode/utf8`
|
`unicode/utf8`
|
||||||
|
|
||||||
|
`github.com/bytedance/sonic/internal/rt`
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -163,18 +165,18 @@ func (self *Node) encodeFalse(buf *[]byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *Node) encodeNumber(buf *[]byte) error {
|
func (self *Node) encodeNumber(buf *[]byte) error {
|
||||||
str := self.toString()
|
str := rt.StrFrom(self.p, self.v)
|
||||||
*buf = append(*buf, str...)
|
*buf = append(*buf, str...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Node) encodeString(buf *[]byte) error {
|
func (self *Node) encodeString(buf *[]byte) error {
|
||||||
if self.l == 0 {
|
if self.v == 0 {
|
||||||
*buf = append(*buf, '"', '"')
|
*buf = append(*buf, '"', '"')
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
quote(buf, self.toString())
|
quote(buf, rt.StrFrom(self.p, self.v))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,17 +195,16 @@ func (self *Node) encodeArray(buf *[]byte) error {
|
|||||||
|
|
||||||
*buf = append(*buf, '[')
|
*buf = append(*buf, '[')
|
||||||
|
|
||||||
var started bool
|
var p = (*Node)(self.p)
|
||||||
for i := 0; i < nb; i++ {
|
err := p.encode(buf)
|
||||||
n := self.nodeAt(i)
|
if err != nil {
|
||||||
if !n.Exists() {
|
return err
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if started {
|
for i := 1; i < nb; i++ {
|
||||||
*buf = append(*buf, ',')
|
*buf = append(*buf, ',')
|
||||||
}
|
p = p.unsafe_next()
|
||||||
started = true
|
err := p.encode(buf)
|
||||||
if err := n.encode(buf); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,17 +240,16 @@ func (self *Node) encodeObject(buf *[]byte) error {
|
|||||||
|
|
||||||
*buf = append(*buf, '{')
|
*buf = append(*buf, '{')
|
||||||
|
|
||||||
var started bool
|
var p = (*Pair)(self.p)
|
||||||
for i := 0; i < nb; i++ {
|
err := p.encode(buf)
|
||||||
n := self.pairAt(i)
|
if err != nil {
|
||||||
if n == nil || !n.Value.Exists() {
|
return err
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if started {
|
for i := 1; i < nb; i++ {
|
||||||
*buf = append(*buf, ',')
|
*buf = append(*buf, ',')
|
||||||
}
|
p = p.unsafe_next()
|
||||||
started = true
|
err := p.encode(buf)
|
||||||
if err := n.encode(buf); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
vendor/github.com/bytedance/sonic/ast/error.go
generated
vendored
38
vendor/github.com/bytedance/sonic/ast/error.go
generated
vendored
@@ -8,33 +8,6 @@ import (
|
|||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func newError(err types.ParsingError, msg string) *Node {
|
|
||||||
return &Node{
|
|
||||||
t: V_ERROR,
|
|
||||||
l: uint(err),
|
|
||||||
p: unsafe.Pointer(&msg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns error message if the node is invalid
|
|
||||||
func (self Node) Error() string {
|
|
||||||
if self.t != V_ERROR {
|
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return *(*string)(self.p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSyntaxError(err SyntaxError) *Node {
|
|
||||||
msg := err.Description()
|
|
||||||
return &Node{
|
|
||||||
t: V_ERROR,
|
|
||||||
l: uint(err.Code),
|
|
||||||
p: unsafe.Pointer(&msg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Parser) syntaxError(err types.ParsingError) SyntaxError {
|
func (self *Parser) syntaxError(err types.ParsingError) SyntaxError {
|
||||||
return SyntaxError{
|
return SyntaxError{
|
||||||
Pos : self.p,
|
Pos : self.p,
|
||||||
@@ -43,18 +16,13 @@ func (self *Parser) syntaxError(err types.ParsingError) SyntaxError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func unwrapError(err error) *Node {
|
func newSyntaxError(err SyntaxError) *Node {
|
||||||
if se, ok := err.(*Node); ok {
|
msg := err.Description()
|
||||||
return se
|
|
||||||
}else if sse, ok := err.(Node); ok {
|
|
||||||
return &sse
|
|
||||||
} else {
|
|
||||||
msg := err.Error()
|
|
||||||
return &Node{
|
return &Node{
|
||||||
t: V_ERROR,
|
t: V_ERROR,
|
||||||
|
v: int64(err.Code),
|
||||||
p: unsafe.Pointer(&msg),
|
p: unsafe.Pointer(&msg),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SyntaxError struct {
|
type SyntaxError struct {
|
||||||
|
|||||||
87
vendor/github.com/bytedance/sonic/ast/iterator.go
generated
vendored
87
vendor/github.com/bytedance/sonic/ast/iterator.go
generated
vendored
@@ -32,11 +32,7 @@ func (self *Node) Values() (ListIterator, error) {
|
|||||||
if err := self.should(types.V_ARRAY, "an array"); err != nil {
|
if err := self.should(types.V_ARRAY, "an array"); err != nil {
|
||||||
return ListIterator{}, err
|
return ListIterator{}, err
|
||||||
}
|
}
|
||||||
return self.values(), nil
|
return ListIterator{Iterator{p: self}}, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Node) values() ListIterator {
|
|
||||||
return ListIterator{Iterator{p: self}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properties returns iterator for object's children traversal
|
// Properties returns iterator for object's children traversal
|
||||||
@@ -44,11 +40,7 @@ func (self *Node) Properties() (ObjectIterator, error) {
|
|||||||
if err := self.should(types.V_OBJECT, "an object"); err != nil {
|
if err := self.should(types.V_OBJECT, "an object"); err != nil {
|
||||||
return ObjectIterator{}, err
|
return ObjectIterator{}, err
|
||||||
}
|
}
|
||||||
return self.properties(), nil
|
return ObjectIterator{Iterator{p: self}}, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Node) properties() ObjectIterator {
|
|
||||||
return ObjectIterator{Iterator{p: self}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Iterator struct {
|
type Iterator struct {
|
||||||
@@ -90,54 +82,26 @@ type ObjectIterator struct {
|
|||||||
Iterator
|
Iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ListIterator) next() *Node {
|
|
||||||
next_start:
|
|
||||||
if !self.HasNext() {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
n := self.p.nodeAt(self.i)
|
|
||||||
self.i++
|
|
||||||
if !n.Exists() {
|
|
||||||
goto next_start
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next scans through children of underlying V_ARRAY,
|
// Next scans through children of underlying V_ARRAY,
|
||||||
// copies each child to v, and returns .HasNext().
|
// copies each child to v, and returns .HasNext().
|
||||||
func (self *ListIterator) Next(v *Node) bool {
|
func (self *ListIterator) Next(v *Node) bool {
|
||||||
n := self.next()
|
|
||||||
if n == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
*v = *n
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ObjectIterator) next() *Pair {
|
|
||||||
next_start:
|
|
||||||
if !self.HasNext() {
|
if !self.HasNext() {
|
||||||
return nil
|
return false
|
||||||
} else {
|
} else {
|
||||||
n := self.p.pairAt(self.i)
|
*v, self.i = *self.p.nodeAt(self.i), self.i + 1
|
||||||
self.i++
|
return true
|
||||||
if n == nil || !n.Value.Exists() {
|
|
||||||
goto next_start
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next scans through children of underlying V_OBJECT,
|
// Next scans through children of underlying V_OBJECT,
|
||||||
// copies each child to v, and returns .HasNext().
|
// copies each child to v, and returns .HasNext().
|
||||||
func (self *ObjectIterator) Next(p *Pair) bool {
|
func (self *ObjectIterator) Next(p *Pair) bool {
|
||||||
n := self.next()
|
if !self.HasNext() {
|
||||||
if n == nil {
|
|
||||||
return false
|
return false
|
||||||
}
|
} else {
|
||||||
*p = *n
|
*p, self.i = *self.p.pairAt(self.i), self.i + 1
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sequence represents scanning path of single-layer nodes.
|
// Sequence represents scanning path of single-layer nodes.
|
||||||
@@ -165,39 +129,36 @@ type Scanner func(path Sequence, node *Node) bool
|
|||||||
//
|
//
|
||||||
// Especailly, if the node is not V_ARRAY or V_OBJECT,
|
// Especailly, if the node is not V_ARRAY or V_OBJECT,
|
||||||
// the node itself will be returned and Sequence.Index == -1.
|
// the node itself will be returned and Sequence.Index == -1.
|
||||||
//
|
|
||||||
// NOTICE: A unsetted node WON'T trigger sc, but its index still counts into Path.Index
|
|
||||||
func (self *Node) ForEach(sc Scanner) error {
|
func (self *Node) ForEach(sc Scanner) error {
|
||||||
switch self.itype() {
|
switch self.itype() {
|
||||||
case types.V_ARRAY:
|
case types.V_ARRAY:
|
||||||
iter, err := self.Values()
|
ns, err := self.UnsafeArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v := iter.next()
|
for i := range ns {
|
||||||
for v != nil {
|
if !sc(Sequence{i, nil}, &ns[i]) {
|
||||||
if !sc(Sequence{iter.i-1, nil}, v) {
|
return err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
v = iter.next()
|
|
||||||
}
|
}
|
||||||
case types.V_OBJECT:
|
case types.V_OBJECT:
|
||||||
iter, err := self.Properties()
|
ns, err := self.UnsafeMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v := iter.next()
|
for i := range ns {
|
||||||
for v != nil {
|
if !sc(Sequence{i, &ns[i].Key}, &ns[i].Value) {
|
||||||
if !sc(Sequence{iter.i-1, &v.Key}, &v.Value) {
|
return err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
v = iter.next()
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if self.Check() != nil {
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
sc(Sequence{-1, nil}, self)
|
sc(Sequence{-1, nil}, self)
|
||||||
}
|
}
|
||||||
return nil
|
return self.Check()
|
||||||
|
}
|
||||||
|
|
||||||
|
type PairSlice []Pair
|
||||||
|
|
||||||
|
func (self PairSlice) Sort() {
|
||||||
|
radixQsort(self, 0, maxDepth(len(self)))
|
||||||
}
|
}
|
||||||
840
vendor/github.com/bytedance/sonic/ast/node.go
generated
vendored
840
vendor/github.com/bytedance/sonic/ast/node.go
generated
vendored
File diff suppressed because it is too large
Load Diff
100
vendor/github.com/bytedance/sonic/ast/parser.go
generated
vendored
100
vendor/github.com/bytedance/sonic/ast/parser.go
generated
vendored
@@ -18,15 +18,11 @@ package ast
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
`fmt`
|
`fmt`
|
||||||
|
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
`github.com/bytedance/sonic/internal/rt`
|
`github.com/bytedance/sonic/internal/rt`
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const _DEFAULT_NODE_CAP int = 16
|
||||||
_DEFAULT_NODE_CAP int = 8
|
|
||||||
_APPEND_GROW_SHIFT = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_ERR_NOT_FOUND types.ParsingError = 33
|
_ERR_NOT_FOUND types.ParsingError = 33
|
||||||
@@ -34,10 +30,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrNotExist means both key and value doesn't exist
|
|
||||||
ErrNotExist error = newError(_ERR_NOT_FOUND, "value not exists")
|
ErrNotExist error = newError(_ERR_NOT_FOUND, "value not exists")
|
||||||
|
|
||||||
// ErrUnsupportType means API on the node is unsupported
|
|
||||||
ErrUnsupportType error = newError(_ERR_UNSUPPORT_TYPE, "unsupported type")
|
ErrUnsupportType error = newError(_ERR_UNSUPPORT_TYPE, "unsupported type")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,7 +39,6 @@ type Parser struct {
|
|||||||
s string
|
s string
|
||||||
noLazy bool
|
noLazy bool
|
||||||
skipValue bool
|
skipValue bool
|
||||||
dbuf *byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parser Private Methods **/
|
/** Parser Private Methods **/
|
||||||
@@ -115,7 +107,7 @@ func (self *Parser) lspace(sp int) int {
|
|||||||
return sp
|
return sp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Parser) decodeArray(ret *linkedNodes) (Node, types.ParsingError) {
|
func (self *Parser) decodeArray(ret []Node) (Node, types.ParsingError) {
|
||||||
sp := self.p
|
sp := self.p
|
||||||
ns := len(self.s)
|
ns := len(self.s)
|
||||||
|
|
||||||
@@ -127,7 +119,7 @@ func (self *Parser) decodeArray(ret *linkedNodes) (Node, types.ParsingError) {
|
|||||||
/* check for empty array */
|
/* check for empty array */
|
||||||
if self.s[self.p] == ']' {
|
if self.s[self.p] == ']' {
|
||||||
self.p++
|
self.p++
|
||||||
return Node{t: types.V_ARRAY}, 0
|
return emptyArrayNode, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/* allocate array space and parse every element */
|
/* allocate array space and parse every element */
|
||||||
@@ -157,7 +149,7 @@ func (self *Parser) decodeArray(ret *linkedNodes) (Node, types.ParsingError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* add the value to result */
|
/* add the value to result */
|
||||||
ret.Push(val)
|
ret = append(ret, val)
|
||||||
self.p = self.lspace(self.p)
|
self.p = self.lspace(self.p)
|
||||||
|
|
||||||
/* check for EOF */
|
/* check for EOF */
|
||||||
@@ -168,17 +160,17 @@ func (self *Parser) decodeArray(ret *linkedNodes) (Node, types.ParsingError) {
|
|||||||
/* check for the next character */
|
/* check for the next character */
|
||||||
switch self.s[self.p] {
|
switch self.s[self.p] {
|
||||||
case ',' : self.p++
|
case ',' : self.p++
|
||||||
case ']' : self.p++; return newArray(ret), 0
|
case ']' : self.p++; return NewArray(ret), 0
|
||||||
default:
|
default:
|
||||||
// if val.isLazy() {
|
if val.isLazy() {
|
||||||
// return newLazyArray(self, ret), 0
|
return newLazyArray(self, ret), 0
|
||||||
// }
|
}
|
||||||
return Node{}, types.ERR_INVALID_CHAR
|
return Node{}, types.ERR_INVALID_CHAR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Parser) decodeObject(ret *linkedPairs) (Node, types.ParsingError) {
|
func (self *Parser) decodeObject(ret []Pair) (Node, types.ParsingError) {
|
||||||
sp := self.p
|
sp := self.p
|
||||||
ns := len(self.s)
|
ns := len(self.s)
|
||||||
|
|
||||||
@@ -190,7 +182,7 @@ func (self *Parser) decodeObject(ret *linkedPairs) (Node, types.ParsingError) {
|
|||||||
/* check for empty object */
|
/* check for empty object */
|
||||||
if self.s[self.p] == '}' {
|
if self.s[self.p] == '}' {
|
||||||
self.p++
|
self.p++
|
||||||
return Node{t: types.V_OBJECT}, 0
|
return emptyObjectNode, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/* decode each pair */
|
/* decode each pair */
|
||||||
@@ -243,8 +235,7 @@ func (self *Parser) decodeObject(ret *linkedPairs) (Node, types.ParsingError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* add the value to result */
|
/* add the value to result */
|
||||||
// FIXME: ret's address may change here, thus previous referred node in ret may be invalid !!
|
ret = append(ret, Pair{Key: key, Value: val})
|
||||||
ret.Push(Pair{Key: key, Value: val})
|
|
||||||
self.p = self.lspace(self.p)
|
self.p = self.lspace(self.p)
|
||||||
|
|
||||||
/* check for EOF */
|
/* check for EOF */
|
||||||
@@ -255,11 +246,11 @@ func (self *Parser) decodeObject(ret *linkedPairs) (Node, types.ParsingError) {
|
|||||||
/* check for the next character */
|
/* check for the next character */
|
||||||
switch self.s[self.p] {
|
switch self.s[self.p] {
|
||||||
case ',' : self.p++
|
case ',' : self.p++
|
||||||
case '}' : self.p++; return newObject(ret), 0
|
case '}' : self.p++; return NewObject(ret), 0
|
||||||
default:
|
default:
|
||||||
// if val.isLazy() {
|
if val.isLazy() {
|
||||||
// return newLazyObject(self, ret), 0
|
return newLazyObject(self, ret), 0
|
||||||
// }
|
}
|
||||||
return Node{}, types.ERR_INVALID_CHAR
|
return Node{}, types.ERR_INVALID_CHAR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,23 +290,15 @@ func (self *Parser) Parse() (Node, types.ParsingError) {
|
|||||||
case types.V_FALSE : return falseNode, 0
|
case types.V_FALSE : return falseNode, 0
|
||||||
case types.V_STRING : return self.decodeString(val.Iv, val.Ep)
|
case types.V_STRING : return self.decodeString(val.Iv, val.Ep)
|
||||||
case types.V_ARRAY:
|
case types.V_ARRAY:
|
||||||
if p := skipBlank(self.s, self.p); p >= self.p && self.s[p] == ']' {
|
|
||||||
self.p = p + 1
|
|
||||||
return Node{t: types.V_ARRAY}, 0
|
|
||||||
}
|
|
||||||
if self.noLazy {
|
if self.noLazy {
|
||||||
return self.decodeArray(new(linkedNodes))
|
return self.decodeArray(make([]Node, 0, _DEFAULT_NODE_CAP))
|
||||||
}
|
}
|
||||||
return newLazyArray(self), 0
|
return newLazyArray(self, make([]Node, 0, _DEFAULT_NODE_CAP)), 0
|
||||||
case types.V_OBJECT:
|
case types.V_OBJECT:
|
||||||
if p := skipBlank(self.s, self.p); p >= self.p && self.s[p] == '}' {
|
|
||||||
self.p = p + 1
|
|
||||||
return Node{t: types.V_OBJECT}, 0
|
|
||||||
}
|
|
||||||
if self.noLazy {
|
if self.noLazy {
|
||||||
return self.decodeObject(new(linkedPairs))
|
return self.decodeObject(make([]Pair, 0, _DEFAULT_NODE_CAP))
|
||||||
}
|
}
|
||||||
return newLazyObject(self), 0
|
return newLazyObject(self, make([]Pair, 0, _DEFAULT_NODE_CAP)), 0
|
||||||
case types.V_DOUBLE : return NewNumber(self.s[val.Ep:self.p]), 0
|
case types.V_DOUBLE : return NewNumber(self.s[val.Ep:self.p]), 0
|
||||||
case types.V_INTEGER : return NewNumber(self.s[val.Ep:self.p]), 0
|
case types.V_INTEGER : return NewNumber(self.s[val.Ep:self.p]), 0
|
||||||
default : return Node{}, types.ParsingError(-val.Vt)
|
default : return Node{}, types.ParsingError(-val.Vt)
|
||||||
@@ -446,7 +429,7 @@ func (self *Node) skipNextNode() *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parser, stack := self.getParserAndArrayStack()
|
parser, stack := self.getParserAndArrayStack()
|
||||||
ret := &stack.v
|
ret := stack.v
|
||||||
sp := parser.p
|
sp := parser.p
|
||||||
ns := len(parser.s)
|
ns := len(parser.s)
|
||||||
|
|
||||||
@@ -475,8 +458,7 @@ func (self *Node) skipNextNode() *Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* add the value to result */
|
/* add the value to result */
|
||||||
ret.Push(val)
|
ret = append(ret, val)
|
||||||
self.l++
|
|
||||||
parser.p = parser.lspace(parser.p)
|
parser.p = parser.lspace(parser.p)
|
||||||
|
|
||||||
/* check for EOF */
|
/* check for EOF */
|
||||||
@@ -488,11 +470,12 @@ func (self *Node) skipNextNode() *Node {
|
|||||||
switch parser.s[parser.p] {
|
switch parser.s[parser.p] {
|
||||||
case ',':
|
case ',':
|
||||||
parser.p++
|
parser.p++
|
||||||
return ret.At(ret.Len()-1)
|
self.setLazyArray(parser, ret)
|
||||||
|
return &ret[len(ret)-1]
|
||||||
case ']':
|
case ']':
|
||||||
parser.p++
|
parser.p++
|
||||||
self.setArray(ret)
|
self.setArray(ret)
|
||||||
return ret.At(ret.Len()-1)
|
return &ret[len(ret)-1]
|
||||||
default:
|
default:
|
||||||
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
|
return newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))
|
||||||
}
|
}
|
||||||
@@ -504,7 +487,7 @@ func (self *Node) skipNextPair() (*Pair) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parser, stack := self.getParserAndObjectStack()
|
parser, stack := self.getParserAndObjectStack()
|
||||||
ret := &stack.v
|
ret := stack.v
|
||||||
sp := parser.p
|
sp := parser.p
|
||||||
ns := len(parser.s)
|
ns := len(parser.s)
|
||||||
|
|
||||||
@@ -558,8 +541,7 @@ func (self *Node) skipNextPair() (*Pair) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* add the value to result */
|
/* add the value to result */
|
||||||
ret.Push(Pair{Key: key, Value: val})
|
ret = append(ret, Pair{Key: key, Value: val})
|
||||||
self.l++
|
|
||||||
parser.p = parser.lspace(parser.p)
|
parser.p = parser.lspace(parser.p)
|
||||||
|
|
||||||
/* check for EOF */
|
/* check for EOF */
|
||||||
@@ -571,11 +553,12 @@ func (self *Node) skipNextPair() (*Pair) {
|
|||||||
switch parser.s[parser.p] {
|
switch parser.s[parser.p] {
|
||||||
case ',':
|
case ',':
|
||||||
parser.p++
|
parser.p++
|
||||||
return ret.At(ret.Len()-1)
|
self.setLazyObject(parser, ret)
|
||||||
|
return &ret[len(ret)-1]
|
||||||
case '}':
|
case '}':
|
||||||
parser.p++
|
parser.p++
|
||||||
self.setObject(ret)
|
self.setObject(ret)
|
||||||
return ret.At(ret.Len()-1)
|
return &ret[len(ret)-1]
|
||||||
default:
|
default:
|
||||||
return &Pair{key, *newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))}
|
return &Pair{key, *newSyntaxError(parser.syntaxError(types.ERR_INVALID_CHAR))}
|
||||||
}
|
}
|
||||||
@@ -618,30 +601,10 @@ func LoadsUseNumber(src string) (int, interface{}, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParser returns pointer of new allocated parser
|
|
||||||
func NewParser(src string) *Parser {
|
func NewParser(src string) *Parser {
|
||||||
return &Parser{s: src}
|
return &Parser{s: src}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParser returns new allocated parser
|
|
||||||
func NewParserObj(src string) Parser {
|
|
||||||
return Parser{s: src}
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeNumber controls if parser decodes the number values instead of skip them
|
|
||||||
// WARN: once you set decodeNumber(true), please set decodeNumber(false) before you drop the parser
|
|
||||||
// otherwise the memory CANNOT be reused
|
|
||||||
func (self *Parser) decodeNumber(decode bool) {
|
|
||||||
if !decode && self.dbuf != nil {
|
|
||||||
types.FreeDbuf(self.dbuf)
|
|
||||||
self.dbuf = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if decode && self.dbuf == nil {
|
|
||||||
self.dbuf = types.NewDbuf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExportError converts types.ParsingError to std Error
|
// ExportError converts types.ParsingError to std Error
|
||||||
func (self *Parser) ExportError(err types.ParsingError) error {
|
func (self *Parser) ExportError(err types.ParsingError) error {
|
||||||
if err == _ERR_NOT_FOUND {
|
if err == _ERR_NOT_FOUND {
|
||||||
@@ -653,8 +616,3 @@ func (self *Parser) ExportError(err types.ParsingError) error {
|
|||||||
Code: err,
|
Code: err,
|
||||||
}.Description())
|
}.Description())
|
||||||
}
|
}
|
||||||
|
|
||||||
func backward(src string, i int) int {
|
|
||||||
for ; i>=0 && isSpace(src[i]); i-- {}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|||||||
108
vendor/github.com/bytedance/sonic/ast/search.go
generated
vendored
108
vendor/github.com/bytedance/sonic/ast/search.go
generated
vendored
@@ -16,11 +16,6 @@
|
|||||||
|
|
||||||
package ast
|
package ast
|
||||||
|
|
||||||
import (
|
|
||||||
`github.com/bytedance/sonic/internal/rt`
|
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
|
||||||
)
|
|
||||||
|
|
||||||
type Searcher struct {
|
type Searcher struct {
|
||||||
parser Parser
|
parser Parser
|
||||||
}
|
}
|
||||||
@@ -33,106 +28,3 @@ func NewSearcher(str string) *Searcher {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByPathCopy search in depth from top json and returns a **Copied** json node at the path location
|
|
||||||
func (self *Searcher) GetByPathCopy(path ...interface{}) (Node, error) {
|
|
||||||
return self.getByPath(true, true, path...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetByPathNoCopy search in depth from top json and returns a **Referenced** json node at the path location
|
|
||||||
//
|
|
||||||
// WARN: this search directly refer partial json from top json, which has faster speed,
|
|
||||||
// may consumes more memory.
|
|
||||||
func (self *Searcher) GetByPath(path ...interface{}) (Node, error) {
|
|
||||||
return self.getByPath(false, true, path...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Searcher) getByPath(copystring bool, validate bool, path ...interface{}) (Node, error) {
|
|
||||||
var err types.ParsingError
|
|
||||||
var start int
|
|
||||||
|
|
||||||
self.parser.p = 0
|
|
||||||
start, err = self.parser.getByPath(validate, path...)
|
|
||||||
if err != 0 {
|
|
||||||
// for compatibility with old version
|
|
||||||
if err == types.ERR_NOT_FOUND {
|
|
||||||
return Node{}, ErrNotExist
|
|
||||||
}
|
|
||||||
if err == types.ERR_UNSUPPORT_TYPE {
|
|
||||||
panic("path must be either int(>=0) or string")
|
|
||||||
}
|
|
||||||
return Node{}, self.parser.syntaxError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t := switchRawType(self.parser.s[start])
|
|
||||||
if t == _V_NONE {
|
|
||||||
return Node{}, self.parser.ExportError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy string to reducing memory usage
|
|
||||||
var raw string
|
|
||||||
if copystring {
|
|
||||||
raw = rt.Mem2Str([]byte(self.parser.s[start:self.parser.p]))
|
|
||||||
} else {
|
|
||||||
raw = self.parser.s[start:self.parser.p]
|
|
||||||
}
|
|
||||||
return newRawNode(raw, t), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetByPath searches a path and returns relaction and types of target
|
|
||||||
func _GetByPath(src string, path ...interface{}) (start int, end int, typ int, err error) {
|
|
||||||
p := NewParserObj(src)
|
|
||||||
s, e := p.getByPath(false, path...)
|
|
||||||
if e != 0 {
|
|
||||||
// for compatibility with old version
|
|
||||||
if e == types.ERR_NOT_FOUND {
|
|
||||||
return -1, -1, 0, ErrNotExist
|
|
||||||
}
|
|
||||||
if e == types.ERR_UNSUPPORT_TYPE {
|
|
||||||
panic("path must be either int(>=0) or string")
|
|
||||||
}
|
|
||||||
return -1, -1, 0, p.syntaxError(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
t := switchRawType(p.s[s])
|
|
||||||
if t == _V_NONE {
|
|
||||||
return -1, -1, 0, ErrNotExist
|
|
||||||
}
|
|
||||||
if t == _V_NUMBER {
|
|
||||||
p.p = 1 + backward(p.s, p.p-1)
|
|
||||||
}
|
|
||||||
return s, p.p, int(t), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidSyntax check if a json has a valid JSON syntax,
|
|
||||||
// while not validate UTF-8 charset
|
|
||||||
func _ValidSyntax(json string) bool {
|
|
||||||
p := NewParserObj(json)
|
|
||||||
_, e := p.skip()
|
|
||||||
if e != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if skipBlank(p.s, p.p) != -int(types.ERR_EOF) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkipFast skip a json value in fast-skip algs,
|
|
||||||
// while not strictly validate JSON syntax and UTF-8 charset.
|
|
||||||
func _SkipFast(src string, i int) (int, int, error) {
|
|
||||||
p := NewParserObj(src)
|
|
||||||
p.p = i
|
|
||||||
s, e := p.skipFast()
|
|
||||||
if e != 0 {
|
|
||||||
return -1, -1, p.ExportError(e)
|
|
||||||
}
|
|
||||||
t := switchRawType(p.s[s])
|
|
||||||
if t == _V_NONE {
|
|
||||||
return -1, -1, ErrNotExist
|
|
||||||
}
|
|
||||||
if t == _V_NUMBER {
|
|
||||||
p.p = 1 + backward(p.s, p.p-1)
|
|
||||||
}
|
|
||||||
return s, p.p, nil
|
|
||||||
}
|
|
||||||
|
|||||||
2
vendor/github.com/bytedance/sonic/compat.go
generated
vendored
2
vendor/github.com/bytedance/sonic/compat.go
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
// +build !amd64 !go1.16 go1.23
|
// +build !amd64 go1.21
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2021 ByteDance Inc.
|
* Copyright 2021 ByteDance Inc.
|
||||||
|
|||||||
68
vendor/github.com/bytedance/sonic/decoder/decoder_compat.go
generated
vendored
68
vendor/github.com/bytedance/sonic/decoder/decoder_compat.go
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
// +build !amd64 !go1.16 go1.23
|
// +build !amd64 go1.21
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023 ByteDance Inc.
|
* Copyright 2023 ByteDance Inc.
|
||||||
@@ -14,34 +14,28 @@
|
|||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package decoder
|
package decoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`bytes`
|
|
||||||
`encoding/json`
|
`encoding/json`
|
||||||
`io`
|
`bytes`
|
||||||
`reflect`
|
`reflect`
|
||||||
`unsafe`
|
|
||||||
|
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
`github.com/bytedance/sonic/option`
|
`github.com/bytedance/sonic/option`
|
||||||
|
`io`
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
println("WARNING: sonic only supports Go1.16~1.22 && CPU amd64, but your environment is not suitable")
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_F_use_int64 = 0
|
_F_use_int64 = iota
|
||||||
_F_disable_urc = 2
|
_F_use_number
|
||||||
_F_disable_unknown = 3
|
_F_disable_urc
|
||||||
_F_copy_string = 4
|
_F_disable_unknown
|
||||||
|
_F_copy_string
|
||||||
|
_F_validate_string
|
||||||
|
|
||||||
_F_use_number = types.B_USE_NUMBER
|
_F_allow_control = 31
|
||||||
_F_validate_string = types.B_VALIDATE_STRING
|
|
||||||
_F_allow_control = types.B_ALLOW_CONTROL
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options uint64
|
type Options uint64
|
||||||
@@ -112,10 +106,10 @@ func (self *Decoder) CheckTrailings() error {
|
|||||||
func (self *Decoder) Decode(val interface{}) error {
|
func (self *Decoder) Decode(val interface{}) error {
|
||||||
r := bytes.NewBufferString(self.s)
|
r := bytes.NewBufferString(self.s)
|
||||||
dec := json.NewDecoder(r)
|
dec := json.NewDecoder(r)
|
||||||
if (self.f & uint64(OptionUseNumber)) != 0 {
|
if (self.f | uint64(OptionUseNumber)) != 0 {
|
||||||
dec.UseNumber()
|
dec.UseNumber()
|
||||||
}
|
}
|
||||||
if (self.f & uint64(OptionDisableUnknown)) != 0 {
|
if (self.f | uint64(OptionDisableUnknown)) != 0 {
|
||||||
dec.DisallowUnknownFields()
|
dec.DisallowUnknownFields()
|
||||||
}
|
}
|
||||||
return dec.Decode(val)
|
return dec.Decode(val)
|
||||||
@@ -169,26 +163,34 @@ func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type StreamDecoder = json.Decoder
|
type StreamDecoder struct {
|
||||||
|
r io.Reader
|
||||||
|
buf []byte
|
||||||
|
scanp int
|
||||||
|
scanned int64
|
||||||
|
err error
|
||||||
|
Decoder
|
||||||
|
}
|
||||||
|
|
||||||
// NewStreamDecoder adapts to encoding/json.NewDecoder API.
|
// NewStreamDecoder adapts to encoding/json.NewDecoder API.
|
||||||
//
|
//
|
||||||
// NewStreamDecoder returns a new decoder that reads from r.
|
// NewStreamDecoder returns a new decoder that reads from r.
|
||||||
func NewStreamDecoder(r io.Reader) *StreamDecoder {
|
func NewStreamDecoder(r io.Reader) *StreamDecoder {
|
||||||
return json.NewDecoder(r)
|
return &StreamDecoder{r : r}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyntaxError represents json syntax error
|
// Decode decodes input stream into val with corresponding data.
|
||||||
type SyntaxError json.SyntaxError
|
// Redundantly bytes may be read and left in its buffer, and can be used at next call.
|
||||||
|
// Either io error from underlying io.Reader (except io.EOF)
|
||||||
// Description
|
// or syntax error from data will be recorded and stop subsequently decoding.
|
||||||
func (s SyntaxError) Description() string {
|
func (self *StreamDecoder) Decode(val interface{}) (err error) {
|
||||||
return (*json.SyntaxError)(unsafe.Pointer(&s)).Error()
|
dec := json.NewDecoder(self.r)
|
||||||
}
|
if (self.f | uint64(OptionUseNumber)) != 0 {
|
||||||
// Error
|
dec.UseNumber()
|
||||||
func (s SyntaxError) Error() string {
|
}
|
||||||
return (*json.SyntaxError)(unsafe.Pointer(&s)).Error()
|
if (self.f | uint64(OptionDisableUnknown)) != 0 {
|
||||||
|
dec.DisallowUnknownFields()
|
||||||
|
}
|
||||||
|
return dec.Decode(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MismatchTypeError represents dismatching between json and object
|
|
||||||
type MismatchTypeError json.UnmarshalTypeError
|
|
||||||
55
vendor/github.com/bytedance/sonic/encoder/encoder_compat.go
generated
vendored
55
vendor/github.com/bytedance/sonic/encoder/encoder_compat.go
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
// +build !amd64 !go1.16 go1.23
|
// +build !amd64 go1.21
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023 ByteDance Inc.
|
* Copyright 2023 ByteDance Inc.
|
||||||
@@ -27,13 +27,6 @@ import (
|
|||||||
`github.com/bytedance/sonic/option`
|
`github.com/bytedance/sonic/option`
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
println("WARNING:(encoder) sonic only supports Go1.16~1.22 && CPU amd64, but your environment is not suitable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableFallback indicates if encoder use fallback
|
|
||||||
const EnableFallback = true
|
|
||||||
|
|
||||||
// Options is a set of encoding options.
|
// Options is a set of encoding options.
|
||||||
type Options uint64
|
type Options uint64
|
||||||
|
|
||||||
@@ -44,8 +37,6 @@ const (
|
|||||||
bitNoQuoteTextMarshaler
|
bitNoQuoteTextMarshaler
|
||||||
bitNoNullSliceOrMap
|
bitNoNullSliceOrMap
|
||||||
bitValidateString
|
bitValidateString
|
||||||
bitNoValidateJSONMarshaler
|
|
||||||
bitNoEncoderNewline
|
|
||||||
|
|
||||||
// used for recursive compile
|
// used for recursive compile
|
||||||
bitPointerValue = 63
|
bitPointerValue = 63
|
||||||
@@ -78,13 +69,6 @@ const (
|
|||||||
// before encoding it into JSON.
|
// before encoding it into JSON.
|
||||||
ValidateString Options = 1 << bitValidateString
|
ValidateString Options = 1 << bitValidateString
|
||||||
|
|
||||||
// NoValidateJSONMarshaler indicates that the encoder should not validate the output string
|
|
||||||
// after encoding the JSONMarshaler to JSON.
|
|
||||||
NoValidateJSONMarshaler Options = 1 << bitNoValidateJSONMarshaler
|
|
||||||
|
|
||||||
// NoEncoderNewline indicates that the encoder should not add a newline after every message
|
|
||||||
NoEncoderNewline Options = 1 << bitNoEncoderNewline
|
|
||||||
|
|
||||||
// CompatibleWithStd is used to be compatible with std encoder.
|
// CompatibleWithStd is used to be compatible with std encoder.
|
||||||
CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler
|
CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler
|
||||||
)
|
)
|
||||||
@@ -128,24 +112,6 @@ func (self *Encoder) SetValidateString(f bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNoValidateJSONMarshaler specifies if option NoValidateJSONMarshaler opens
|
|
||||||
func (self *Encoder) SetNoValidateJSONMarshaler(f bool) {
|
|
||||||
if f {
|
|
||||||
self.Opts |= NoValidateJSONMarshaler
|
|
||||||
} else {
|
|
||||||
self.Opts &= ^NoValidateJSONMarshaler
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNoEncoderNewline specifies if option NoEncoderNewline opens
|
|
||||||
func (self *Encoder) SetNoEncoderNewline(f bool) {
|
|
||||||
if f {
|
|
||||||
self.Opts |= NoEncoderNewline
|
|
||||||
} else {
|
|
||||||
self.Opts &= ^NoEncoderNewline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCompactMarshaler specifies if option CompactMarshaler opens
|
// SetCompactMarshaler specifies if option CompactMarshaler opens
|
||||||
func (self *Encoder) SetCompactMarshaler(f bool) {
|
func (self *Encoder) SetCompactMarshaler(f bool) {
|
||||||
if f {
|
if f {
|
||||||
@@ -199,10 +165,6 @@ func EncodeInto(buf *[]byte, val interface{}, opts Options) error {
|
|||||||
enc.SetEscapeHTML((opts & EscapeHTML) != 0)
|
enc.SetEscapeHTML((opts & EscapeHTML) != 0)
|
||||||
err := enc.Encode(val)
|
err := enc.Encode(val)
|
||||||
*buf = w.Bytes()
|
*buf = w.Bytes()
|
||||||
l := len(*buf)
|
|
||||||
if l > 0 && (opts & NoEncoderNewline != 0) && (*buf)[l-1] == '\n' {
|
|
||||||
*buf = (*buf)[:l-1]
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,12 +212,23 @@ func Valid(data []byte) (ok bool, start int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StreamEncoder uses io.Writer as
|
// StreamEncoder uses io.Writer as
|
||||||
type StreamEncoder = json.Encoder
|
type StreamEncoder struct {
|
||||||
|
w io.Writer
|
||||||
|
Encoder
|
||||||
|
}
|
||||||
|
|
||||||
// NewStreamEncoder adapts to encoding/json.NewDecoder API.
|
// NewStreamEncoder adapts to encoding/json.NewDecoder API.
|
||||||
//
|
//
|
||||||
// NewStreamEncoder returns a new encoder that write to w.
|
// NewStreamEncoder returns a new encoder that write to w.
|
||||||
func NewStreamEncoder(w io.Writer) *StreamEncoder {
|
func NewStreamEncoder(w io.Writer) *StreamEncoder {
|
||||||
return json.NewEncoder(w)
|
return &StreamEncoder{w: w}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encode encodes interface{} as JSON to io.Writer
|
||||||
|
func (enc *StreamEncoder) Encode(val interface{}) (err error) {
|
||||||
|
jenc := json.NewEncoder(enc.w)
|
||||||
|
jenc.SetEscapeHTML((enc.Opts & EscapeHTML) != 0)
|
||||||
|
jenc.SetIndent(enc.prefix, enc.indent)
|
||||||
|
err = jenc.Encode(val)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
2
vendor/github.com/bytedance/sonic/internal/encoder/compiler.go
generated
vendored
2
vendor/github.com/bytedance/sonic/internal/encoder/compiler.go
generated
vendored
@@ -831,7 +831,7 @@ func (self *_Compiler) compileStructFieldZero(p *_Program, vt reflect.Type) {
|
|||||||
case reflect.Float32 : p.add(_OP_is_zero_4)
|
case reflect.Float32 : p.add(_OP_is_zero_4)
|
||||||
case reflect.Float64 : p.add(_OP_is_zero_8)
|
case reflect.Float64 : p.add(_OP_is_zero_8)
|
||||||
case reflect.String : p.add(_OP_is_nil_p1)
|
case reflect.String : p.add(_OP_is_nil_p1)
|
||||||
case reflect.Interface : p.add(_OP_is_nil)
|
case reflect.Interface : p.add(_OP_is_nil_p1)
|
||||||
case reflect.Map : p.add(_OP_is_zero_map)
|
case reflect.Map : p.add(_OP_is_zero_map)
|
||||||
case reflect.Ptr : p.add(_OP_is_nil)
|
case reflect.Ptr : p.add(_OP_is_nil)
|
||||||
case reflect.Slice : p.add(_OP_is_nil_p1)
|
case reflect.Slice : p.add(_OP_is_nil_p1)
|
||||||
|
|||||||
31
vendor/github.com/bytedance/sonic/internal/encoder/encoder.go
generated
vendored
31
vendor/github.com/bytedance/sonic/internal/encoder/encoder.go
generated
vendored
@@ -40,8 +40,6 @@ const (
|
|||||||
bitNoQuoteTextMarshaler
|
bitNoQuoteTextMarshaler
|
||||||
bitNoNullSliceOrMap
|
bitNoNullSliceOrMap
|
||||||
bitValidateString
|
bitValidateString
|
||||||
bitNoValidateJSONMarshaler
|
|
||||||
bitNoEncoderNewline
|
|
||||||
|
|
||||||
// used for recursive compile
|
// used for recursive compile
|
||||||
bitPointerValue = 63
|
bitPointerValue = 63
|
||||||
@@ -74,13 +72,6 @@ const (
|
|||||||
// before encoding it into JSON.
|
// before encoding it into JSON.
|
||||||
ValidateString Options = 1 << bitValidateString
|
ValidateString Options = 1 << bitValidateString
|
||||||
|
|
||||||
// NoValidateJSONMarshaler indicates that the encoder should not validate the output string
|
|
||||||
// after encoding the JSONMarshaler to JSON.
|
|
||||||
NoValidateJSONMarshaler Options = 1 << bitNoValidateJSONMarshaler
|
|
||||||
|
|
||||||
// NoEncoderNewline indicates that the encoder should not add a newline after every message
|
|
||||||
NoEncoderNewline Options = 1 << bitNoEncoderNewline
|
|
||||||
|
|
||||||
// CompatibleWithStd is used to be compatible with std encoder.
|
// CompatibleWithStd is used to be compatible with std encoder.
|
||||||
CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler
|
CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler
|
||||||
)
|
)
|
||||||
@@ -124,25 +115,6 @@ func (self *Encoder) SetValidateString(f bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNoValidateJSONMarshaler specifies if option NoValidateJSONMarshaler opens
|
|
||||||
func (self *Encoder) SetNoValidateJSONMarshaler(f bool) {
|
|
||||||
if f {
|
|
||||||
self.Opts |= NoValidateJSONMarshaler
|
|
||||||
} else {
|
|
||||||
self.Opts &= ^NoValidateJSONMarshaler
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNoEncoderNewline specifies if option NoEncoderNewline opens
|
|
||||||
func (self *Encoder) SetNoEncoderNewline(f bool) {
|
|
||||||
if f {
|
|
||||||
self.Opts |= NoEncoderNewline
|
|
||||||
} else {
|
|
||||||
self.Opts &= ^NoEncoderNewline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// SetCompactMarshaler specifies if option CompactMarshaler opens
|
// SetCompactMarshaler specifies if option CompactMarshaler opens
|
||||||
func (self *Encoder) SetCompactMarshaler(f bool) {
|
func (self *Encoder) SetCompactMarshaler(f bool) {
|
||||||
if f {
|
if f {
|
||||||
@@ -320,6 +292,7 @@ func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
|
|||||||
cfg := option.DefaultCompileOptions()
|
cfg := option.DefaultCompileOptions()
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&cfg)
|
opt(&cfg)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return pretouchRec(map[reflect.Type]uint8{vt: 0}, cfg)
|
return pretouchRec(map[reflect.Type]uint8{vt: 0}, cfg)
|
||||||
}
|
}
|
||||||
@@ -337,7 +310,7 @@ func Valid(data []byte) (ok bool, start int) {
|
|||||||
s := rt.Mem2Str(data)
|
s := rt.Mem2Str(data)
|
||||||
p := 0
|
p := 0
|
||||||
m := types.NewStateMachine()
|
m := types.NewStateMachine()
|
||||||
ret := native.ValidateOne(&s, &p, m, types.F_VALIDATE_STRING)
|
ret := native.ValidateOne(&s, &p, m)
|
||||||
types.FreeStateMachine(m)
|
types.FreeStateMachine(m)
|
||||||
|
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
|
|||||||
9
vendor/github.com/bytedance/sonic/internal/encoder/stream.go
generated
vendored
9
vendor/github.com/bytedance/sonic/internal/encoder/stream.go
generated
vendored
@@ -36,8 +36,7 @@ func NewStreamEncoder(w io.Writer) *StreamEncoder {
|
|||||||
|
|
||||||
// Encode encodes interface{} as JSON to io.Writer
|
// Encode encodes interface{} as JSON to io.Writer
|
||||||
func (enc *StreamEncoder) Encode(val interface{}) (err error) {
|
func (enc *StreamEncoder) Encode(val interface{}) (err error) {
|
||||||
buf := newBytes()
|
out := newBytes()
|
||||||
out := buf
|
|
||||||
|
|
||||||
/* encode into the buffer */
|
/* encode into the buffer */
|
||||||
err = EncodeInto(&out, val, enc.Opts)
|
err = EncodeInto(&out, val, enc.Opts)
|
||||||
@@ -55,9 +54,7 @@ func (enc *StreamEncoder) Encode(val interface{}) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// according to standard library, terminate each value with a newline...
|
// according to standard library, terminate each value with a newline...
|
||||||
if enc.Opts & NoEncoderNewline == 0 {
|
|
||||||
buf.WriteByte('\n')
|
buf.WriteByte('\n')
|
||||||
}
|
|
||||||
|
|
||||||
/* copy into io.Writer */
|
/* copy into io.Writer */
|
||||||
_, err = io.Copy(enc.w, buf)
|
_, err = io.Copy(enc.w, buf)
|
||||||
@@ -78,12 +75,10 @@ func (enc *StreamEncoder) Encode(val interface{}) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// according to standard library, terminate each value with a newline...
|
// according to standard library, terminate each value with a newline...
|
||||||
if enc.Opts & NoEncoderNewline == 0 {
|
|
||||||
enc.w.Write([]byte{'\n'})
|
enc.w.Write([]byte{'\n'})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
free_bytes:
|
free_bytes:
|
||||||
freeBytes(buf)
|
freeBytes(out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
12
vendor/github.com/bytedance/sonic/internal/jit/assembler_amd64.go
generated
vendored
12
vendor/github.com/bytedance/sonic/internal/jit/assembler_amd64.go
generated
vendored
@@ -72,6 +72,18 @@ func (self *BaseAssembler) NOPn(n int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *BaseAssembler) StorePtr(ptr int64, to obj.Addr, tmp obj.Addr) {
|
||||||
|
if (to.Type != obj.TYPE_MEM) || (tmp.Type != obj.TYPE_REG) {
|
||||||
|
panic("must store imm to memory, tmp must be register")
|
||||||
|
}
|
||||||
|
if (ptr >> 32) != 0 {
|
||||||
|
self.Emit("MOVQ", Imm(ptr), tmp)
|
||||||
|
self.Emit("MOVQ", tmp, to)
|
||||||
|
} else {
|
||||||
|
self.Emit("MOVQ", Imm(ptr), to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (self *BaseAssembler) Byte(v ...byte) {
|
func (self *BaseAssembler) Byte(v ...byte) {
|
||||||
for ; len(v) >= 8; v = v[8:] { self.From("QUAD", Imm(rt.Get64(v))) }
|
for ; len(v) >= 8; v = v[8:] { self.From("QUAD", Imm(rt.Get64(v))) }
|
||||||
for ; len(v) >= 4; v = v[4:] { self.From("LONG", Imm(int64(rt.Get32(v)))) }
|
for ; len(v) >= 4; v = v[4:] { self.From("LONG", Imm(int64(rt.Get32(v)))) }
|
||||||
|
|||||||
7
vendor/github.com/bytedance/sonic/internal/jit/runtime.go
generated
vendored
7
vendor/github.com/bytedance/sonic/internal/jit/runtime.go
generated
vendored
@@ -24,6 +24,11 @@ import (
|
|||||||
`github.com/twitchyliquid64/golang-asm/obj`
|
`github.com/twitchyliquid64/golang-asm/obj`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
//go:linkname getitab runtime.getitab
|
||||||
|
//goland:noinspection ALL
|
||||||
|
func getitab(inter *rt.GoType, typ *rt.GoType, canfail bool) *rt.GoItab
|
||||||
|
|
||||||
func Func(f interface{}) obj.Addr {
|
func Func(f interface{}) obj.Addr {
|
||||||
if p := rt.UnpackEface(f); p.Type.Kind() != reflect.Func {
|
if p := rt.UnpackEface(f); p.Type.Kind() != reflect.Func {
|
||||||
panic("f is not a function")
|
panic("f is not a function")
|
||||||
@@ -37,7 +42,7 @@ func Type(t reflect.Type) obj.Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Itab(i *rt.GoType, t reflect.Type) obj.Addr {
|
func Itab(i *rt.GoType, t reflect.Type) obj.Addr {
|
||||||
return Imm(int64(uintptr(unsafe.Pointer(rt.Getitab(rt.IfaceType(i), rt.UnpackType(t), false)))))
|
return Imm(int64(uintptr(unsafe.Pointer(getitab(i, rt.UnpackType(t), false)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Gitab(i *rt.GoItab) obj.Addr {
|
func Gitab(i *rt.GoItab) obj.Addr {
|
||||||
|
|||||||
209
vendor/github.com/bytedance/sonic/internal/native/dispatch_amd64.go
generated
vendored
209
vendor/github.com/bytedance/sonic/internal/native/dispatch_amd64.go
generated
vendored
@@ -24,10 +24,12 @@ import (
|
|||||||
`github.com/bytedance/sonic/internal/native/avx2`
|
`github.com/bytedance/sonic/internal/native/avx2`
|
||||||
`github.com/bytedance/sonic/internal/native/sse`
|
`github.com/bytedance/sonic/internal/native/sse`
|
||||||
`github.com/bytedance/sonic/internal/native/types`
|
`github.com/bytedance/sonic/internal/native/types`
|
||||||
`github.com/bytedance/sonic/internal/rt`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const MaxFrameSize uintptr = 400
|
const (
|
||||||
|
MaxFrameSize uintptr = 400
|
||||||
|
BufPaddingSize int = 64
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
S_f64toa uintptr
|
S_f64toa uintptr
|
||||||
@@ -59,212 +61,133 @@ var (
|
|||||||
S_skip_number uintptr
|
S_skip_number uintptr
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
//go:nosplit
|
||||||
__Quote func(s unsafe.Pointer, nb int, dp unsafe.Pointer, dn unsafe.Pointer, flags uint64) int
|
//go:noescape
|
||||||
|
//goland:noinspection GoUnusedParameter
|
||||||
__Unquote func(s unsafe.Pointer, nb int, dp unsafe.Pointer, ep unsafe.Pointer, flags uint64) int
|
func Quote(s unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int, flags uint64) int
|
||||||
|
|
||||||
__HTMLEscape func(s unsafe.Pointer, nb int, dp unsafe.Pointer, dn unsafe.Pointer) int
|
|
||||||
|
|
||||||
__Value func(s unsafe.Pointer, n int, p int, v unsafe.Pointer, flags uint64) int
|
|
||||||
|
|
||||||
__SkipOne func(s unsafe.Pointer, p unsafe.Pointer, m unsafe.Pointer, flags uint64) int
|
|
||||||
|
|
||||||
__SkipOneFast func(s unsafe.Pointer, p unsafe.Pointer) int
|
|
||||||
|
|
||||||
__GetByPath func(s unsafe.Pointer, p unsafe.Pointer, path unsafe.Pointer, m unsafe.Pointer) int
|
|
||||||
|
|
||||||
__ValidateOne func(s unsafe.Pointer, p unsafe.Pointer, m unsafe.Pointer, flags uint64) int
|
|
||||||
|
|
||||||
__I64toa func(out unsafe.Pointer, val int64) (ret int)
|
|
||||||
|
|
||||||
__U64toa func(out unsafe.Pointer, val uint64) (ret int)
|
|
||||||
|
|
||||||
__F64toa func(out unsafe.Pointer, val float64) (ret int)
|
|
||||||
|
|
||||||
__F32toa func(out unsafe.Pointer, val float32) (ret int)
|
|
||||||
|
|
||||||
__ValidateUTF8 func(s unsafe.Pointer, p unsafe.Pointer, m unsafe.Pointer) (ret int)
|
|
||||||
|
|
||||||
__ValidateUTF8Fast func(s unsafe.Pointer) (ret int)
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func Quote(s unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int, flags uint64) int {
|
//go:noescape
|
||||||
return __Quote(rt.NoEscape(unsafe.Pointer(s)), nb, rt.NoEscape(unsafe.Pointer(dp)), rt.NoEscape(unsafe.Pointer(dn)), flags)
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func Unquote(s unsafe.Pointer, nb int, dp unsafe.Pointer, ep *int, flags uint64) int
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func Unquote(s unsafe.Pointer, nb int, dp unsafe.Pointer, ep *int, flags uint64) int {
|
//go:noescape
|
||||||
return __Unquote(rt.NoEscape(unsafe.Pointer(s)), nb, rt.NoEscape(unsafe.Pointer(dp)), rt.NoEscape(unsafe.Pointer(ep)), flags)
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func HTMLEscape(s unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int) int
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func HTMLEscape(s unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int) int {
|
//go:noescape
|
||||||
return __HTMLEscape(rt.NoEscape(unsafe.Pointer(s)), nb, rt.NoEscape(unsafe.Pointer(dp)), rt.NoEscape(unsafe.Pointer(dn)))
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func Value(s unsafe.Pointer, n int, p int, v *types.JsonState, flags uint64) int
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func Value(s unsafe.Pointer, n int, p int, v *types.JsonState, flags uint64) int {
|
//go:noescape
|
||||||
return __Value(rt.NoEscape(unsafe.Pointer(s)), n, p, rt.NoEscape(unsafe.Pointer(v)), flags)
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func SkipOne(s *string, p *int, m *types.StateMachine, flags uint64) int
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func SkipOne(s *string, p *int, m *types.StateMachine, flags uint64) int {
|
//go:noescape
|
||||||
return __SkipOne(rt.NoEscape(unsafe.Pointer(s)), rt.NoEscape(unsafe.Pointer(p)), rt.NoEscape(unsafe.Pointer(m)), flags)
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func SkipOneFast(s *string, p *int) int
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func SkipOneFast(s *string, p *int) int {
|
//go:noescape
|
||||||
return __SkipOneFast(rt.NoEscape(unsafe.Pointer(s)), rt.NoEscape(unsafe.Pointer(p)))
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func GetByPath(s *string, p *int, path *[]interface{}, m *types.StateMachine) int
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func GetByPath(s *string, p *int, path *[]interface{}, m *types.StateMachine) int {
|
//go:noescape
|
||||||
return __GetByPath(rt.NoEscape(unsafe.Pointer(s)), rt.NoEscape(unsafe.Pointer(p)), rt.NoEscape(unsafe.Pointer(path)), rt.NoEscape(unsafe.Pointer(m)))
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func ValidateOne(s *string, p *int, m *types.StateMachine) int
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func ValidateOne(s *string, p *int, m *types.StateMachine, flags uint64) int {
|
//go:noescape
|
||||||
return __ValidateOne(rt.NoEscape(unsafe.Pointer(s)), rt.NoEscape(unsafe.Pointer(p)), rt.NoEscape(unsafe.Pointer(m)), flags)
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func I64toa(out *byte, val int64) (ret int)
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func I64toa(out *byte, val int64) (ret int) {
|
//go:noescape
|
||||||
return __I64toa(rt.NoEscape(unsafe.Pointer(out)), val)
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func U64toa(out *byte, val uint64) (ret int)
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func U64toa(out *byte, val uint64) (ret int) {
|
//go:noescape
|
||||||
return __U64toa(rt.NoEscape(unsafe.Pointer(out)), val)
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func F64toa(out *byte, val float64) (ret int)
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func F64toa(out *byte, val float64) (ret int) {
|
//go:noescape
|
||||||
return __F64toa(rt.NoEscape(unsafe.Pointer(out)), val)
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func ValidateUTF8(s *string, p *int, m *types.StateMachine) (ret int)
|
||||||
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func F32toa(out *byte, val float32) (ret int) {
|
//go:noescape
|
||||||
return __F32toa(rt.NoEscape(unsafe.Pointer(out)), val)
|
//goland:noinspection GoUnusedParameter
|
||||||
}
|
func ValidateUTF8Fast(s *string) (ret int)
|
||||||
|
|
||||||
//go:nosplit
|
|
||||||
func ValidateUTF8(s *string, p *int, m *types.StateMachine) (ret int) {
|
|
||||||
return __ValidateUTF8(rt.NoEscape(unsafe.Pointer(s)), rt.NoEscape(unsafe.Pointer(p)), rt.NoEscape(unsafe.Pointer(m)))
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:nosplit
|
|
||||||
func ValidateUTF8Fast(s *string) (ret int) {
|
|
||||||
return __ValidateUTF8Fast(rt.NoEscape(unsafe.Pointer(s)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func useSSE() {
|
|
||||||
sse.Use()
|
|
||||||
S_f64toa = sse.S_f64toa
|
|
||||||
__F64toa = sse.F_f64toa
|
|
||||||
S_f32toa = sse.S_f32toa
|
|
||||||
__F64toa = sse.F_f64toa
|
|
||||||
S_i64toa = sse.S_i64toa
|
|
||||||
__I64toa = sse.F_i64toa
|
|
||||||
S_u64toa = sse.S_u64toa
|
|
||||||
__U64toa = sse.F_u64toa
|
|
||||||
S_lspace = sse.S_lspace
|
|
||||||
S_quote = sse.S_quote
|
|
||||||
__Quote = sse.F_quote
|
|
||||||
S_unquote = sse.S_unquote
|
|
||||||
__Unquote = sse.F_unquote
|
|
||||||
S_value = sse.S_value
|
|
||||||
__Value = sse.F_value
|
|
||||||
S_vstring = sse.S_vstring
|
|
||||||
S_vnumber = sse.S_vnumber
|
|
||||||
S_vsigned = sse.S_vsigned
|
|
||||||
S_vunsigned = sse.S_vunsigned
|
|
||||||
S_skip_one = sse.S_skip_one
|
|
||||||
__SkipOne = sse.F_skip_one
|
|
||||||
__SkipOneFast = sse.F_skip_one_fast
|
|
||||||
S_skip_array = sse.S_skip_array
|
|
||||||
S_skip_object = sse.S_skip_object
|
|
||||||
S_skip_number = sse.S_skip_number
|
|
||||||
S_get_by_path = sse.S_get_by_path
|
|
||||||
__GetByPath = sse.F_get_by_path
|
|
||||||
__HTMLEscape = sse.F_html_escape
|
|
||||||
__ValidateOne = sse.F_validate_one
|
|
||||||
__ValidateUTF8= sse.F_validate_utf8
|
|
||||||
__ValidateUTF8Fast = sse.F_validate_utf8_fast
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func useAVX() {
|
func useAVX() {
|
||||||
avx.Use()
|
|
||||||
S_f64toa = avx.S_f64toa
|
S_f64toa = avx.S_f64toa
|
||||||
__F64toa = avx.F_f64toa
|
|
||||||
S_f32toa = avx.S_f32toa
|
S_f32toa = avx.S_f32toa
|
||||||
__F64toa = avx.F_f64toa
|
|
||||||
S_i64toa = avx.S_i64toa
|
S_i64toa = avx.S_i64toa
|
||||||
__I64toa = avx.F_i64toa
|
|
||||||
S_u64toa = avx.S_u64toa
|
S_u64toa = avx.S_u64toa
|
||||||
__U64toa = avx.F_u64toa
|
|
||||||
S_lspace = avx.S_lspace
|
S_lspace = avx.S_lspace
|
||||||
S_quote = avx.S_quote
|
S_quote = avx.S_quote
|
||||||
__Quote = avx.F_quote
|
|
||||||
S_unquote = avx.S_unquote
|
S_unquote = avx.S_unquote
|
||||||
__Unquote = avx.F_unquote
|
|
||||||
S_value = avx.S_value
|
S_value = avx.S_value
|
||||||
__Value = avx.F_value
|
|
||||||
S_vstring = avx.S_vstring
|
S_vstring = avx.S_vstring
|
||||||
S_vnumber = avx.S_vnumber
|
S_vnumber = avx.S_vnumber
|
||||||
S_vsigned = avx.S_vsigned
|
S_vsigned = avx.S_vsigned
|
||||||
S_vunsigned = avx.S_vunsigned
|
S_vunsigned = avx.S_vunsigned
|
||||||
S_skip_one = avx.S_skip_one
|
S_skip_one = avx.S_skip_one
|
||||||
__SkipOne = avx.F_skip_one
|
S_skip_one_fast = avx.S_skip_one_fast
|
||||||
__SkipOneFast = avx.F_skip_one_fast
|
|
||||||
S_skip_array = avx.S_skip_array
|
S_skip_array = avx.S_skip_array
|
||||||
S_skip_object = avx.S_skip_object
|
S_skip_object = avx.S_skip_object
|
||||||
S_skip_number = avx.S_skip_number
|
S_skip_number = avx.S_skip_number
|
||||||
S_get_by_path = avx.S_get_by_path
|
S_get_by_path = avx.S_get_by_path
|
||||||
__GetByPath = avx.F_get_by_path
|
|
||||||
__HTMLEscape = avx.F_html_escape
|
|
||||||
__ValidateOne = avx.F_validate_one
|
|
||||||
__ValidateUTF8= avx.F_validate_utf8
|
|
||||||
__ValidateUTF8Fast = avx.F_validate_utf8_fast
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func useAVX2() {
|
func useAVX2() {
|
||||||
avx2.Use()
|
|
||||||
S_f64toa = avx2.S_f64toa
|
S_f64toa = avx2.S_f64toa
|
||||||
__F64toa = avx2.F_f64toa
|
|
||||||
S_f32toa = avx2.S_f32toa
|
S_f32toa = avx2.S_f32toa
|
||||||
__F64toa = avx2.F_f64toa
|
|
||||||
S_i64toa = avx2.S_i64toa
|
S_i64toa = avx2.S_i64toa
|
||||||
__I64toa = avx2.F_i64toa
|
|
||||||
S_u64toa = avx2.S_u64toa
|
S_u64toa = avx2.S_u64toa
|
||||||
__U64toa = avx2.F_u64toa
|
|
||||||
S_lspace = avx2.S_lspace
|
S_lspace = avx2.S_lspace
|
||||||
S_quote = avx2.S_quote
|
S_quote = avx2.S_quote
|
||||||
__Quote = avx2.F_quote
|
|
||||||
S_unquote = avx2.S_unquote
|
S_unquote = avx2.S_unquote
|
||||||
__Unquote = avx2.F_unquote
|
|
||||||
S_value = avx2.S_value
|
S_value = avx2.S_value
|
||||||
__Value = avx2.F_value
|
|
||||||
S_vstring = avx2.S_vstring
|
S_vstring = avx2.S_vstring
|
||||||
S_vnumber = avx2.S_vnumber
|
S_vnumber = avx2.S_vnumber
|
||||||
S_vsigned = avx2.S_vsigned
|
S_vsigned = avx2.S_vsigned
|
||||||
S_vunsigned = avx2.S_vunsigned
|
S_vunsigned = avx2.S_vunsigned
|
||||||
S_skip_one = avx2.S_skip_one
|
S_skip_one = avx2.S_skip_one
|
||||||
__SkipOne = avx2.F_skip_one
|
S_skip_one_fast = avx2.S_skip_one_fast
|
||||||
__SkipOneFast = avx2.F_skip_one_fast
|
|
||||||
S_skip_array = avx2.S_skip_array
|
S_skip_array = avx2.S_skip_array
|
||||||
S_skip_object = avx2.S_skip_object
|
S_skip_object = avx2.S_skip_object
|
||||||
S_skip_number = avx2.S_skip_number
|
S_skip_number = avx2.S_skip_number
|
||||||
S_get_by_path = avx2.S_get_by_path
|
S_get_by_path = avx2.S_get_by_path
|
||||||
__GetByPath = avx2.F_get_by_path
|
|
||||||
__HTMLEscape = avx2.F_html_escape
|
|
||||||
__ValidateOne = avx2.F_validate_one
|
|
||||||
__ValidateUTF8= avx2.F_validate_utf8
|
|
||||||
__ValidateUTF8Fast = avx2.F_validate_utf8_fast
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func useSSE() {
|
||||||
|
S_f64toa = sse.S_f64toa
|
||||||
|
S_f32toa = sse.S_f32toa
|
||||||
|
S_i64toa = sse.S_i64toa
|
||||||
|
S_u64toa = sse.S_u64toa
|
||||||
|
S_lspace = sse.S_lspace
|
||||||
|
S_quote = sse.S_quote
|
||||||
|
S_unquote = sse.S_unquote
|
||||||
|
S_value = sse.S_value
|
||||||
|
S_vstring = sse.S_vstring
|
||||||
|
S_vnumber = sse.S_vnumber
|
||||||
|
S_vsigned = sse.S_vsigned
|
||||||
|
S_vunsigned = sse.S_vunsigned
|
||||||
|
S_skip_one = sse.S_skip_one
|
||||||
|
S_skip_one_fast = sse.S_skip_one_fast
|
||||||
|
S_skip_array = sse.S_skip_array
|
||||||
|
S_skip_object = sse.S_skip_object
|
||||||
|
S_skip_number = sse.S_skip_number
|
||||||
|
S_get_by_path = sse.S_get_by_path
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if cpu.HasAVX2 {
|
if cpu.HasAVX2 {
|
||||||
|
|||||||
26
vendor/github.com/bytedance/sonic/internal/native/types/types.go
generated
vendored
26
vendor/github.com/bytedance/sonic/internal/native/types/types.go
generated
vendored
@@ -19,7 +19,6 @@ package types
|
|||||||
import (
|
import (
|
||||||
`fmt`
|
`fmt`
|
||||||
`sync`
|
`sync`
|
||||||
`unsafe`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ValueType int
|
type ValueType int
|
||||||
@@ -29,8 +28,6 @@ type SearchingError uint
|
|||||||
// NOTE: !NOT MODIFIED ONLY.
|
// NOTE: !NOT MODIFIED ONLY.
|
||||||
// This definitions are followed in native/types.h.
|
// This definitions are followed in native/types.h.
|
||||||
|
|
||||||
const BufPaddingSize int = 64
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
V_EOF ValueType = 1
|
V_EOF ValueType = 1
|
||||||
V_NULL ValueType = 2
|
V_NULL ValueType = 2
|
||||||
@@ -49,23 +46,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// for native.Unquote() flags
|
|
||||||
B_DOUBLE_UNQUOTE = 0
|
B_DOUBLE_UNQUOTE = 0
|
||||||
B_UNICODE_REPLACE = 1
|
B_UNICODE_REPLACE = 1
|
||||||
|
|
||||||
// for native.Value() flags
|
|
||||||
B_USE_NUMBER = 1
|
|
||||||
B_VALIDATE_STRING = 5
|
B_VALIDATE_STRING = 5
|
||||||
B_ALLOW_CONTROL = 31
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
F_DOUBLE_UNQUOTE = 1 << B_DOUBLE_UNQUOTE
|
F_DOUBLE_UNQUOTE = 1 << B_DOUBLE_UNQUOTE
|
||||||
F_UNICODE_REPLACE = 1 << B_UNICODE_REPLACE
|
F_UNICODE_REPLACE = 1 << B_UNICODE_REPLACE
|
||||||
|
|
||||||
F_USE_NUMBER = 1 << B_USE_NUMBER
|
|
||||||
F_VALIDATE_STRING = 1 << B_VALIDATE_STRING
|
F_VALIDATE_STRING = 1 << B_VALIDATE_STRING
|
||||||
F_ALLOW_CONTROL = 1 << B_ALLOW_CONTROL
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -147,18 +136,3 @@ func FreeStateMachine(fsm *StateMachine) {
|
|||||||
stackPool.Put(fsm)
|
stackPool.Put(fsm)
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaxDigitNums = 800
|
|
||||||
|
|
||||||
var digitPool = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return (*byte)(unsafe.Pointer(&[MaxDigitNums]byte{}))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDbuf() *byte {
|
|
||||||
return digitPool.Get().(*byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FreeDbuf(p *byte) {
|
|
||||||
digitPool.Put(p)
|
|
||||||
}
|
|
||||||
|
|||||||
3
vendor/github.com/bytedance/sonic/internal/rt/asm_amd64.s
generated
vendored
3
vendor/github.com/bytedance/sonic/internal/rt/asm_amd64.s
generated
vendored
@@ -1,4 +1,5 @@
|
|||||||
// +build !noasm,amd64 !appengine,amd64
|
// +build !noasm !appengine
|
||||||
|
// Code generated by asm2asm, DO NOT EDIT·
|
||||||
|
|
||||||
#include "go_asm.h"
|
#include "go_asm.h"
|
||||||
#include "funcdata.h"
|
#include "funcdata.h"
|
||||||
|
|||||||
15
vendor/github.com/bytedance/sonic/internal/rt/fastmem.go
generated
vendored
15
vendor/github.com/bytedance/sonic/internal/rt/fastmem.go
generated
vendored
@@ -66,16 +66,15 @@ func FuncAddr(f interface{}) unsafe.Pointer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:nocheckptr
|
|
||||||
func IndexChar(src string, index int) unsafe.Pointer {
|
func IndexChar(src string, index int) unsafe.Pointer {
|
||||||
return unsafe.Pointer(uintptr((*GoString)(unsafe.Pointer(&src)).Ptr) + uintptr(index))
|
return unsafe.Pointer(uintptr((*GoString)(unsafe.Pointer(&src)).Ptr) + uintptr(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:nocheckptr
|
|
||||||
func IndexByte(ptr []byte, index int) unsafe.Pointer {
|
func IndexByte(ptr []byte, index int) unsafe.Pointer {
|
||||||
return unsafe.Pointer(uintptr((*GoSlice)(unsafe.Pointer(&ptr)).Ptr) + uintptr(index))
|
return unsafe.Pointer(uintptr((*GoSlice)(unsafe.Pointer(&ptr)).Ptr) + uintptr(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:nosplit
|
||||||
func GuardSlice(buf *[]byte, n int) {
|
func GuardSlice(buf *[]byte, n int) {
|
||||||
c := cap(*buf)
|
c := cap(*buf)
|
||||||
l := len(*buf)
|
l := len(*buf)
|
||||||
@@ -111,15 +110,3 @@ func StrFrom(p unsafe.Pointer, n int64) (s string) {
|
|||||||
(*GoString)(unsafe.Pointer(&s)).Len = int(n)
|
(*GoString)(unsafe.Pointer(&s)).Len = int(n)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoEscape hides a pointer from escape analysis. NoEscape is
|
|
||||||
// the identity function but escape analysis doesn't think the
|
|
||||||
// output depends on the input. NoEscape is inlined and currently
|
|
||||||
// compiles down to zero instructions.
|
|
||||||
// USE CAREFULLY!
|
|
||||||
//go:nosplit
|
|
||||||
//goland:noinspection GoVetUnsafePointer
|
|
||||||
func NoEscape(p unsafe.Pointer) unsafe.Pointer {
|
|
||||||
x := uintptr(p)
|
|
||||||
return unsafe.Pointer(x ^ 0)
|
|
||||||
}
|
|
||||||
30
vendor/github.com/bytedance/sonic/internal/rt/fastvalue.go
generated
vendored
30
vendor/github.com/bytedance/sonic/internal/rt/fastvalue.go
generated
vendored
@@ -211,33 +211,3 @@ func findReflectRtypeItab() *GoItab {
|
|||||||
v := reflect.TypeOf(struct{}{})
|
v := reflect.TypeOf(struct{}{})
|
||||||
return (*GoIface)(unsafe.Pointer(&v)).Itab
|
return (*GoIface)(unsafe.Pointer(&v)).Itab
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssertI2I2(t *GoType, i GoIface) (r GoIface) {
|
|
||||||
inter := IfaceType(t)
|
|
||||||
tab := i.Itab
|
|
||||||
if tab == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (*GoInterfaceType)(tab.it) != inter {
|
|
||||||
tab = Getitab(inter, tab.Vt, true)
|
|
||||||
if tab == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.Itab = tab
|
|
||||||
r.Value = i.Value
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:noescape
|
|
||||||
//go:linkname Getitab runtime.getitab
|
|
||||||
func Getitab(inter *GoInterfaceType, typ *GoType, canfail bool) *GoItab
|
|
||||||
|
|
||||||
|
|
||||||
func GetFuncPC(fn interface{}) uintptr {
|
|
||||||
ft := UnpackEface(fn)
|
|
||||||
if ft.Type.Kind() != reflect.Func {
|
|
||||||
panic("not a function")
|
|
||||||
}
|
|
||||||
return *(*uintptr)(ft.Value)
|
|
||||||
}
|
|
||||||
6
vendor/github.com/bytedance/sonic/internal/rt/int48.go
generated
vendored
6
vendor/github.com/bytedance/sonic/internal/rt/int48.go
generated
vendored
@@ -17,12 +17,12 @@
|
|||||||
package rt
|
package rt
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MinInt48 int64 = -(1 << 47)
|
MinInt48 = -(1 << 47)
|
||||||
MaxInt48 int64 = +(1 << 47) - 1
|
MaxInt48 = +(1 << 47) - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func PackInt(v int) uint64 {
|
func PackInt(v int) uint64 {
|
||||||
if u := uint64(v); int64(v) < MinInt48 || int64(v) > MaxInt48 {
|
if u := uint64(v); v < MinInt48 || v > MaxInt48 {
|
||||||
panic("int48 out of range")
|
panic("int48 out of range")
|
||||||
} else {
|
} else {
|
||||||
return ((u >> 63) << 47) | (u & 0x00007fffffffffff)
|
return ((u >> 63) << 47) | (u & 0x00007fffffffffff)
|
||||||
|
|||||||
101
vendor/github.com/bytedance/sonic/loader/funcdata.go
generated
vendored
101
vendor/github.com/bytedance/sonic/loader/funcdata.go
generated
vendored
@@ -42,13 +42,6 @@ const (
|
|||||||
_SUB_BUCKETSIZE = _BUCKETSIZE / _SUBBUCKETS
|
_SUB_BUCKETSIZE = _BUCKETSIZE / _SUBBUCKETS
|
||||||
)
|
)
|
||||||
|
|
||||||
// Note: This list must match the list in runtime/symtab.go.
|
|
||||||
const (
|
|
||||||
FuncFlag_TOPFRAME = 1 << iota
|
|
||||||
FuncFlag_SPWRITE
|
|
||||||
FuncFlag_ASM
|
|
||||||
)
|
|
||||||
|
|
||||||
// PCDATA and FUNCDATA table indexes.
|
// PCDATA and FUNCDATA table indexes.
|
||||||
//
|
//
|
||||||
// See funcdata.h and $GROOT/src/cmd/internal/objabi/funcdata.go.
|
// See funcdata.h and $GROOT/src/cmd/internal/objabi/funcdata.go.
|
||||||
@@ -149,97 +142,3 @@ func funcNameParts(name string) (string, string, string) {
|
|||||||
}
|
}
|
||||||
return name[:i], "[...]", name[j+1:]
|
return name[:i], "[...]", name[j+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// func name table format:
|
|
||||||
// nameOff[0] -> namePartA namePartB namePartC \x00
|
|
||||||
// nameOff[1] -> namePartA namePartB namePartC \x00
|
|
||||||
// ...
|
|
||||||
func makeFuncnameTab(funcs []Func) (tab []byte, offs []int32) {
|
|
||||||
offs = make([]int32, len(funcs))
|
|
||||||
offset := 1
|
|
||||||
tab = []byte{0}
|
|
||||||
|
|
||||||
for i, f := range funcs {
|
|
||||||
offs[i] = int32(offset)
|
|
||||||
|
|
||||||
a, b, c := funcNameParts(f.Name)
|
|
||||||
tab = append(tab, a...)
|
|
||||||
tab = append(tab, b...)
|
|
||||||
tab = append(tab, c...)
|
|
||||||
tab = append(tab, 0)
|
|
||||||
offset += len(a) + len(b) + len(c) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CU table format:
|
|
||||||
// cuOffsets[0] -> filetabOffset[0] filetabOffset[1] ... filetabOffset[len(CUs[0].fileNames)-1]
|
|
||||||
// cuOffsets[1] -> filetabOffset[len(CUs[0].fileNames)] ... filetabOffset[len(CUs[0].fileNames) + len(CUs[1].fileNames)-1]
|
|
||||||
// ...
|
|
||||||
//
|
|
||||||
// file name table format:
|
|
||||||
// filetabOffset[0] -> CUs[0].fileNames[0] \x00
|
|
||||||
// ...
|
|
||||||
// filetabOffset[len(CUs[0]-1)] -> CUs[0].fileNames[len(CUs[0].fileNames)-1] \x00
|
|
||||||
// ...
|
|
||||||
// filetabOffset[SUM(CUs,fileNames)-1] -> CUs[len(CU)-1].fileNames[len(CUs[len(CU)-1].fileNames)-1] \x00
|
|
||||||
func makeFilenametab(cus []compilationUnit) (cutab []uint32, filetab []byte, cuOffsets []uint32) {
|
|
||||||
cuOffsets = make([]uint32, len(cus))
|
|
||||||
cuOffset := 0
|
|
||||||
fileOffset := 0
|
|
||||||
|
|
||||||
for i, cu := range cus {
|
|
||||||
cuOffsets[i] = uint32(cuOffset)
|
|
||||||
|
|
||||||
for _, name := range cu.fileNames {
|
|
||||||
cutab = append(cutab, uint32(fileOffset))
|
|
||||||
|
|
||||||
fileOffset += len(name) + 1
|
|
||||||
filetab = append(filetab, name...)
|
|
||||||
filetab = append(filetab, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
cuOffset += len(cu.fileNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeFuncdata(out *[]byte, funcs []Func) (fstart int, funcdataOffs [][]uint32) {
|
|
||||||
fstart = len(*out)
|
|
||||||
*out = append(*out, byte(0))
|
|
||||||
offs := uint32(1)
|
|
||||||
|
|
||||||
funcdataOffs = make([][]uint32, len(funcs))
|
|
||||||
for i, f := range funcs {
|
|
||||||
|
|
||||||
var writer = func(fd encoding.BinaryMarshaler) {
|
|
||||||
var ab []byte
|
|
||||||
var err error
|
|
||||||
if fd != nil {
|
|
||||||
ab, err = fd.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
funcdataOffs[i] = append(funcdataOffs[i], offs)
|
|
||||||
} else {
|
|
||||||
ab = []byte{0}
|
|
||||||
funcdataOffs[i] = append(funcdataOffs[i], _INVALID_FUNCDATA_OFFSET)
|
|
||||||
}
|
|
||||||
*out = append(*out, ab...)
|
|
||||||
offs += uint32(len(ab))
|
|
||||||
}
|
|
||||||
|
|
||||||
writer(f.ArgsPointerMaps)
|
|
||||||
writer(f.LocalsPointerMaps)
|
|
||||||
writer(f.StackObjects)
|
|
||||||
writer(f.InlTree)
|
|
||||||
writer(f.OpenCodedDeferInfo)
|
|
||||||
writer(f.ArgInfo)
|
|
||||||
writer(f.ArgLiveInfo)
|
|
||||||
writer(f.WrapInfo)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
432
vendor/github.com/bytedance/sonic/loader/funcdata_go118.go
generated
vendored
432
vendor/github.com/bytedance/sonic/loader/funcdata_go118.go
generated
vendored
@@ -1,5 +1,4 @@
|
|||||||
// go:build go1.18 && !go1.20
|
// go:build go1.18 && !go1.20
|
||||||
//go:build go1.18 && !go1.20
|
|
||||||
// +build go1.18,!go1.20
|
// +build go1.18,!go1.20
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -21,13 +20,32 @@
|
|||||||
package loader
|
package loader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/bytedance/sonic/loader/internal/rt`
|
`encoding`
|
||||||
|
`os`
|
||||||
|
`unsafe`
|
||||||
|
|
||||||
|
`github.com/bytedance/sonic/internal/rt`
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_Magic uint32 = 0xfffffff0
|
_Magic uint32 = 0xfffffff0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type pcHeader struct {
|
||||||
|
magic uint32 // 0xFFFFFFF0
|
||||||
|
pad1, pad2 uint8 // 0,0
|
||||||
|
minLC uint8 // min instruction size
|
||||||
|
ptrSize uint8 // size of a ptr in bytes
|
||||||
|
nfunc int // number of functions in the module
|
||||||
|
nfiles uint // number of entries in the file tab
|
||||||
|
textStart uintptr // base for function entry PC offsets in this module, equal to moduledata.text
|
||||||
|
funcnameOffset uintptr // offset to the funcnametab variable from pcHeader
|
||||||
|
cuOffset uintptr // offset to the cutab variable from pcHeader
|
||||||
|
filetabOffset uintptr // offset to the filetab variable from pcHeader
|
||||||
|
pctabOffset uintptr // offset to the pctab variable from pcHeader
|
||||||
|
pclnOffset uintptr // offset to the pclntab variable from pcHeader
|
||||||
|
}
|
||||||
|
|
||||||
type moduledata struct {
|
type moduledata struct {
|
||||||
pcHeader *pcHeader
|
pcHeader *pcHeader
|
||||||
funcnametab []byte
|
funcnametab []byte
|
||||||
@@ -111,3 +129,413 @@ type _func struct {
|
|||||||
//
|
//
|
||||||
// funcdata [nfuncdata]uint32
|
// funcdata [nfuncdata]uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type funcTab struct {
|
||||||
|
entry uint32
|
||||||
|
funcoff uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type bitVector struct {
|
||||||
|
n int32 // # of bits
|
||||||
|
bytedata *uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type ptabEntry struct {
|
||||||
|
name int32
|
||||||
|
typ int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type textSection struct {
|
||||||
|
vaddr uintptr // prelinked section vaddr
|
||||||
|
end uintptr // vaddr + section length
|
||||||
|
baseaddr uintptr // relocated section address
|
||||||
|
}
|
||||||
|
|
||||||
|
type modulehash struct {
|
||||||
|
modulename string
|
||||||
|
linktimehash string
|
||||||
|
runtimehash *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// findfuncbucket is an array of these structures.
|
||||||
|
// Each bucket represents 4096 bytes of the text segment.
|
||||||
|
// Each subbucket represents 256 bytes of the text segment.
|
||||||
|
// To find a function given a pc, locate the bucket and subbucket for
|
||||||
|
// that pc. Add together the idx and subbucket value to obtain a
|
||||||
|
// function index. Then scan the functab array starting at that
|
||||||
|
// index to find the target function.
|
||||||
|
// This table uses 20 bytes for every 4096 bytes of code, or ~0.5% overhead.
|
||||||
|
type findfuncbucket struct {
|
||||||
|
idx uint32
|
||||||
|
_SUBBUCKETS [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// func name table format:
|
||||||
|
// nameOff[0] -> namePartA namePartB namePartC \x00
|
||||||
|
// nameOff[1] -> namePartA namePartB namePartC \x00
|
||||||
|
// ...
|
||||||
|
func makeFuncnameTab(funcs []Func) (tab []byte, offs []int32) {
|
||||||
|
offs = make([]int32, len(funcs))
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
for i, f := range funcs {
|
||||||
|
offs[i] = int32(offset)
|
||||||
|
|
||||||
|
a, b, c := funcNameParts(f.Name)
|
||||||
|
tab = append(tab, a...)
|
||||||
|
tab = append(tab, b...)
|
||||||
|
tab = append(tab, c...)
|
||||||
|
tab = append(tab, 0)
|
||||||
|
offset += len(a) + len(b) + len(c) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type compilationUnit struct {
|
||||||
|
fileNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CU table format:
|
||||||
|
// cuOffsets[0] -> filetabOffset[0] filetabOffset[1] ... filetabOffset[len(CUs[0].fileNames)-1]
|
||||||
|
// cuOffsets[1] -> filetabOffset[len(CUs[0].fileNames)] ... filetabOffset[len(CUs[0].fileNames) + len(CUs[1].fileNames)-1]
|
||||||
|
// ...
|
||||||
|
//
|
||||||
|
// file name table format:
|
||||||
|
// filetabOffset[0] -> CUs[0].fileNames[0] \x00
|
||||||
|
// ...
|
||||||
|
// filetabOffset[len(CUs[0]-1)] -> CUs[0].fileNames[len(CUs[0].fileNames)-1] \x00
|
||||||
|
// ...
|
||||||
|
// filetabOffset[SUM(CUs,fileNames)-1] -> CUs[len(CU)-1].fileNames[len(CUs[len(CU)-1].fileNames)-1] \x00
|
||||||
|
func makeFilenametab(cus []compilationUnit) (cutab []uint32, filetab []byte, cuOffsets []uint32) {
|
||||||
|
cuOffsets = make([]uint32, len(cus))
|
||||||
|
cuOffset := 0
|
||||||
|
fileOffset := 0
|
||||||
|
|
||||||
|
for i, cu := range cus {
|
||||||
|
cuOffsets[i] = uint32(cuOffset)
|
||||||
|
|
||||||
|
for _, name := range cu.fileNames {
|
||||||
|
cutab = append(cutab, uint32(fileOffset))
|
||||||
|
|
||||||
|
fileOffset += len(name) + 1
|
||||||
|
filetab = append(filetab, name...)
|
||||||
|
filetab = append(filetab, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
cuOffset += len(cu.fileNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFuncdata(out *[]byte, funcs []Func) (fstart int, funcdataOffs [][]uint32) {
|
||||||
|
fstart = len(*out)
|
||||||
|
*out = append(*out, byte(0))
|
||||||
|
offs := uint32(1)
|
||||||
|
|
||||||
|
funcdataOffs = make([][]uint32, len(funcs))
|
||||||
|
for i, f := range funcs {
|
||||||
|
|
||||||
|
var writer = func(fd encoding.BinaryMarshaler) {
|
||||||
|
var ab []byte
|
||||||
|
var err error
|
||||||
|
if fd != nil {
|
||||||
|
ab, err = fd.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
funcdataOffs[i] = append(funcdataOffs[i], offs)
|
||||||
|
} else {
|
||||||
|
ab = []byte{0}
|
||||||
|
funcdataOffs[i] = append(funcdataOffs[i], _INVALID_FUNCDATA_OFFSET)
|
||||||
|
}
|
||||||
|
*out = append(*out, ab...)
|
||||||
|
offs += uint32(len(ab))
|
||||||
|
}
|
||||||
|
|
||||||
|
writer(f.ArgsPointerMaps)
|
||||||
|
writer(f.LocalsPointerMaps)
|
||||||
|
writer(f.StackObjects)
|
||||||
|
writer(f.InlTree)
|
||||||
|
writer(f.OpenCodedDeferInfo)
|
||||||
|
writer(f.ArgInfo)
|
||||||
|
writer(f.ArgLiveInfo)
|
||||||
|
writer(f.WrapInfo)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFtab(funcs []_func, lastFuncSize uint32) (ftab []funcTab) {
|
||||||
|
// Allocate space for the pc->func table. This structure consists of a pc offset
|
||||||
|
// and an offset to the func structure. After that, we have a single pc
|
||||||
|
// value that marks the end of the last function in the binary.
|
||||||
|
var size int64 = int64(len(funcs)*2*4 + 4)
|
||||||
|
var startLocations = make([]uint32, len(funcs))
|
||||||
|
for i, f := range funcs {
|
||||||
|
size = rnd(size, int64(_PtrSize))
|
||||||
|
//writePCToFunc
|
||||||
|
startLocations[i] = uint32(size)
|
||||||
|
size += int64(uint8(_FUNC_SIZE)+f.nfuncdata*4+uint8(f.npcdata)*4)
|
||||||
|
}
|
||||||
|
|
||||||
|
ftab = make([]funcTab, 0, len(funcs)+1)
|
||||||
|
|
||||||
|
// write a map of pc->func info offsets
|
||||||
|
for i, f := range funcs {
|
||||||
|
ftab = append(ftab, funcTab{uint32(f.entryOff), uint32(startLocations[i])})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final entry of table is just end pc offset.
|
||||||
|
lastFunc := funcs[len(funcs)-1]
|
||||||
|
ftab = append(ftab, funcTab{uint32(lastFunc.entryOff + lastFuncSize), 0})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pcln table format: [...]funcTab + [...]_Func
|
||||||
|
func makePclntable(funcs []_func, lastFuncSize uint32, pcdataOffs [][]uint32, funcdataOffs [][]uint32) (pclntab []byte) {
|
||||||
|
// Allocate space for the pc->func table. This structure consists of a pc offset
|
||||||
|
// and an offset to the func structure. After that, we have a single pc
|
||||||
|
// value that marks the end of the last function in the binary.
|
||||||
|
var size int64 = int64(len(funcs)*2*4 + 4)
|
||||||
|
var startLocations = make([]uint32, len(funcs))
|
||||||
|
for i := range funcs {
|
||||||
|
size = rnd(size, int64(_PtrSize))
|
||||||
|
//writePCToFunc
|
||||||
|
startLocations[i] = uint32(size)
|
||||||
|
size += int64(int(_FUNC_SIZE)+len(funcdataOffs[i])*4+len(pcdataOffs[i])*4)
|
||||||
|
}
|
||||||
|
|
||||||
|
pclntab = make([]byte, size, size)
|
||||||
|
|
||||||
|
// write a map of pc->func info offsets
|
||||||
|
offs := 0
|
||||||
|
for i, f := range funcs {
|
||||||
|
byteOrder.PutUint32(pclntab[offs:offs+4], uint32(f.entryOff))
|
||||||
|
byteOrder.PutUint32(pclntab[offs+4:offs+8], uint32(startLocations[i]))
|
||||||
|
offs += 8
|
||||||
|
}
|
||||||
|
// Final entry of table is just end pc offset.
|
||||||
|
lastFunc := funcs[len(funcs)-1]
|
||||||
|
byteOrder.PutUint32(pclntab[offs:offs+4], uint32(lastFunc.entryOff+lastFuncSize))
|
||||||
|
|
||||||
|
// write func info table
|
||||||
|
for i, f := range funcs {
|
||||||
|
off := startLocations[i]
|
||||||
|
|
||||||
|
// write _func structure to pclntab
|
||||||
|
fb := rt.BytesFrom(unsafe.Pointer(&f), int(_FUNC_SIZE), int(_FUNC_SIZE))
|
||||||
|
copy(pclntab[off:off+uint32(_FUNC_SIZE)], fb)
|
||||||
|
off += uint32(_FUNC_SIZE)
|
||||||
|
|
||||||
|
// NOTICE: _func.pcdata always starts from PcUnsafePoint, which is index 3
|
||||||
|
for j := 3; j < len(pcdataOffs[i]); j++ {
|
||||||
|
byteOrder.PutUint32(pclntab[off:off+4], uint32(pcdataOffs[i][j]))
|
||||||
|
off += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcdata refs as offsets from gofunc
|
||||||
|
for _, funcdata := range funcdataOffs[i] {
|
||||||
|
byteOrder.PutUint32(pclntab[off:off+4], uint32(funcdata))
|
||||||
|
off += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// findfunc table used to map pc to belonging func,
|
||||||
|
// returns the index in the func table.
|
||||||
|
//
|
||||||
|
// All text section are divided into buckets sized _BUCKETSIZE(4K):
|
||||||
|
// every bucket is divided into _SUBBUCKETS sized _SUB_BUCKETSIZE(64),
|
||||||
|
// and it has a base idx to plus the offset stored in jth subbucket.
|
||||||
|
// see findfunc() in runtime/symtab.go
|
||||||
|
func writeFindfunctab(out *[]byte, ftab []funcTab) (start int) {
|
||||||
|
start = len(*out)
|
||||||
|
|
||||||
|
max := ftab[len(ftab)-1].entry
|
||||||
|
min := ftab[0].entry
|
||||||
|
nbuckets := (max - min + _BUCKETSIZE - 1) / _BUCKETSIZE
|
||||||
|
n := (max - min + _SUB_BUCKETSIZE - 1) / _SUB_BUCKETSIZE
|
||||||
|
|
||||||
|
tab := make([]findfuncbucket, 0, nbuckets)
|
||||||
|
var s, e = 0, 0
|
||||||
|
for i := 0; i<int(nbuckets); i++ {
|
||||||
|
var pc = min + uint32((i+1)*_BUCKETSIZE)
|
||||||
|
// find the end func of the bucket
|
||||||
|
for ; e < len(ftab)-1 && ftab[e+1].entry <= pc; e++ {}
|
||||||
|
// store the start func of the bucket
|
||||||
|
var fb = findfuncbucket{idx: uint32(s)}
|
||||||
|
|
||||||
|
for j := 0; j<_SUBBUCKETS && (i*_SUBBUCKETS+j)<int(n); j++ {
|
||||||
|
pc = min + uint32(i*_BUCKETSIZE) + uint32((j+1)*_SUB_BUCKETSIZE)
|
||||||
|
var ss = s
|
||||||
|
// find the end func of the subbucket
|
||||||
|
for ; ss < len(ftab)-1 && ftab[ss+1].entry <= pc; ss++ {}
|
||||||
|
// store the start func of the subbucket
|
||||||
|
fb._SUBBUCKETS[j] = byte(uint32(s) - fb.idx)
|
||||||
|
s = ss
|
||||||
|
}
|
||||||
|
s = e
|
||||||
|
tab = append(tab, fb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write findfuncbucket
|
||||||
|
if len(tab) > 0 {
|
||||||
|
size := int(unsafe.Sizeof(findfuncbucket{}))*len(tab)
|
||||||
|
*out = append(*out, rt.BytesFrom(unsafe.Pointer(&tab[0]), size, size)...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeModuledata(name string, filenames []string, funcs []Func, text []byte) (mod *moduledata) {
|
||||||
|
mod = new(moduledata)
|
||||||
|
mod.modulename = name
|
||||||
|
|
||||||
|
// make filename table
|
||||||
|
cu := make([]string, 0, len(filenames))
|
||||||
|
for _, f := range filenames {
|
||||||
|
cu = append(cu, f)
|
||||||
|
}
|
||||||
|
cutab, filetab, cuOffs := makeFilenametab([]compilationUnit{{cu}})
|
||||||
|
mod.cutab = cutab
|
||||||
|
mod.filetab = filetab
|
||||||
|
|
||||||
|
// make funcname table
|
||||||
|
funcnametab, nameOffs := makeFuncnameTab(funcs)
|
||||||
|
mod.funcnametab = funcnametab
|
||||||
|
|
||||||
|
// make pcdata table
|
||||||
|
// NOTICE: _func only use offset to index pcdata, thus no need mmap() pcdata
|
||||||
|
pctab, pcdataOffs, _funcs := makePctab(funcs, cuOffs, nameOffs)
|
||||||
|
mod.pctab = pctab
|
||||||
|
|
||||||
|
// write func data
|
||||||
|
// NOTICE: _func use mod.gofunc+offset to directly point funcdata, thus need cache funcdata
|
||||||
|
// TODO: estimate accurate capacity
|
||||||
|
cache := make([]byte, 0, len(funcs)*int(_PtrSize))
|
||||||
|
fstart, funcdataOffs := writeFuncdata(&cache, funcs)
|
||||||
|
|
||||||
|
// make pc->func (binary search) func table
|
||||||
|
lastFuncsize := funcs[len(funcs)-1].TextSize
|
||||||
|
ftab := makeFtab(_funcs, lastFuncsize)
|
||||||
|
mod.ftab = ftab
|
||||||
|
|
||||||
|
// write pc->func (modmap) findfunc table
|
||||||
|
ffstart := writeFindfunctab(&cache, ftab)
|
||||||
|
|
||||||
|
// make pclnt table
|
||||||
|
pclntab := makePclntable(_funcs, lastFuncsize, pcdataOffs, funcdataOffs)
|
||||||
|
mod.pclntable = pclntab
|
||||||
|
|
||||||
|
// mmap() text and funcdata segements
|
||||||
|
p := os.Getpagesize()
|
||||||
|
size := int(rnd(int64(len(text)), int64(p)))
|
||||||
|
addr := mmap(size)
|
||||||
|
// copy the machine code
|
||||||
|
s := rt.BytesFrom(unsafe.Pointer(addr), len(text), size)
|
||||||
|
copy(s, text)
|
||||||
|
// make it executable
|
||||||
|
mprotect(addr, size)
|
||||||
|
|
||||||
|
// assign addresses
|
||||||
|
mod.text = addr
|
||||||
|
mod.etext = addr + uintptr(size)
|
||||||
|
mod.minpc = addr
|
||||||
|
mod.maxpc = addr + uintptr(len(text))
|
||||||
|
|
||||||
|
// cache funcdata and findfuncbucket
|
||||||
|
moduleCache.Lock()
|
||||||
|
moduleCache.m[mod] = cache
|
||||||
|
moduleCache.Unlock()
|
||||||
|
mod.gofunc = uintptr(unsafe.Pointer(&cache[fstart]))
|
||||||
|
mod.findfunctab = uintptr(unsafe.Pointer(&cache[ffstart]))
|
||||||
|
|
||||||
|
// make pc header
|
||||||
|
mod.pcHeader = &pcHeader {
|
||||||
|
magic : _Magic,
|
||||||
|
minLC : _MinLC,
|
||||||
|
ptrSize : _PtrSize,
|
||||||
|
nfunc : len(funcs),
|
||||||
|
nfiles: uint(len(cu)),
|
||||||
|
textStart: mod.text,
|
||||||
|
funcnameOffset: getOffsetOf(moduledata{}, "funcnametab"),
|
||||||
|
cuOffset: getOffsetOf(moduledata{}, "cutab"),
|
||||||
|
filetabOffset: getOffsetOf(moduledata{}, "filetab"),
|
||||||
|
pctabOffset: getOffsetOf(moduledata{}, "pctab"),
|
||||||
|
pclnOffset: getOffsetOf(moduledata{}, "pclntable"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// sepecial case: gcdata and gcbss must by non-empty
|
||||||
|
mod.gcdata = uintptr(unsafe.Pointer(&emptyByte))
|
||||||
|
mod.gcbss = uintptr(unsafe.Pointer(&emptyByte))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// makePctab generates pcdelta->valuedelta tables for functions,
|
||||||
|
// and returns the table and the entry offset of every kind pcdata in the table.
|
||||||
|
func makePctab(funcs []Func, cuOffset []uint32, nameOffset []int32) (pctab []byte, pcdataOffs [][]uint32, _funcs []_func) {
|
||||||
|
_funcs = make([]_func, len(funcs))
|
||||||
|
|
||||||
|
// Pctab offsets of 0 are considered invalid in the runtime. We respect
|
||||||
|
// that by just padding a single byte at the beginning of runtime.pctab,
|
||||||
|
// that way no real offsets can be zero.
|
||||||
|
pctab = make([]byte, 1, 12*len(funcs)+1)
|
||||||
|
pcdataOffs = make([][]uint32, len(funcs))
|
||||||
|
|
||||||
|
for i, f := range funcs {
|
||||||
|
_f := &_funcs[i]
|
||||||
|
|
||||||
|
var writer = func(pc *Pcdata) {
|
||||||
|
var ab []byte
|
||||||
|
var err error
|
||||||
|
if pc != nil {
|
||||||
|
ab, err = pc.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
pcdataOffs[i] = append(pcdataOffs[i], uint32(len(pctab)))
|
||||||
|
} else {
|
||||||
|
ab = []byte{0}
|
||||||
|
pcdataOffs[i] = append(pcdataOffs[i], _PCDATA_INVALID_OFFSET)
|
||||||
|
}
|
||||||
|
pctab = append(pctab, ab...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Pcsp != nil {
|
||||||
|
_f.pcsp = uint32(len(pctab))
|
||||||
|
}
|
||||||
|
writer(f.Pcsp)
|
||||||
|
if f.Pcfile != nil {
|
||||||
|
_f.pcfile = uint32(len(pctab))
|
||||||
|
}
|
||||||
|
writer(f.Pcfile)
|
||||||
|
if f.Pcline != nil {
|
||||||
|
_f.pcln = uint32(len(pctab))
|
||||||
|
}
|
||||||
|
writer(f.Pcline)
|
||||||
|
writer(f.PcUnsafePoint)
|
||||||
|
writer(f.PcStackMapIndex)
|
||||||
|
writer(f.PcInlTreeIndex)
|
||||||
|
writer(f.PcArgLiveIndex)
|
||||||
|
|
||||||
|
_f.entryOff = f.EntryOff
|
||||||
|
_f.nameOff = nameOffset[i]
|
||||||
|
_f.args = f.ArgsSize
|
||||||
|
_f.deferreturn = f.DeferReturn
|
||||||
|
// NOTICE: _func.pcdata is always as [PCDATA_UnsafePoint(0) : PCDATA_ArgLiveIndex(3)]
|
||||||
|
_f.npcdata = uint32(_N_PCDATA)
|
||||||
|
_f.cuOffset = cuOffset[i]
|
||||||
|
_f.funcID = f.ID
|
||||||
|
_f.flag = f.Flag
|
||||||
|
_f.nfuncdata = uint8(_N_FUNCDATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFunction(name string, pc uintptr, textSize uintptr, fp int, args int, size uintptr, argptrs uintptr, localptrs uintptr) {}
|
||||||
433
vendor/github.com/bytedance/sonic/loader/funcdata_go120.go
generated
vendored
433
vendor/github.com/bytedance/sonic/loader/funcdata_go120.go
generated
vendored
@@ -20,7 +20,11 @@
|
|||||||
package loader
|
package loader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/bytedance/sonic/loader/internal/rt`
|
`encoding`
|
||||||
|
`os`
|
||||||
|
`unsafe`
|
||||||
|
|
||||||
|
`github.com/bytedance/sonic/internal/rt`
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -47,6 +51,8 @@ type moduledata struct {
|
|||||||
end, gcdata, gcbss uintptr
|
end, gcdata, gcbss uintptr
|
||||||
types, etypes uintptr
|
types, etypes uintptr
|
||||||
rodata uintptr
|
rodata uintptr
|
||||||
|
|
||||||
|
// TODO: generate funcinfo object to memory
|
||||||
gofunc uintptr // go.func.* is actual funcinfo object in image
|
gofunc uintptr // go.func.* is actual funcinfo object in image
|
||||||
|
|
||||||
textsectmap []textSection // see runtime/symtab.go: textAddr()
|
textsectmap []textSection // see runtime/symtab.go: textAddr()
|
||||||
@@ -112,3 +118,428 @@ type _func struct {
|
|||||||
//
|
//
|
||||||
// funcdata [nfuncdata]uint32
|
// funcdata [nfuncdata]uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type funcTab struct {
|
||||||
|
entry uint32
|
||||||
|
funcoff uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type pcHeader struct {
|
||||||
|
magic uint32 // 0xFFFFFFF0
|
||||||
|
pad1, pad2 uint8 // 0,0
|
||||||
|
minLC uint8 // min instruction size
|
||||||
|
ptrSize uint8 // size of a ptr in bytes
|
||||||
|
nfunc int // number of functions in the module
|
||||||
|
nfiles uint // number of entries in the file tab
|
||||||
|
textStart uintptr // base for function entry PC offsets in this module, equal to moduledata.text
|
||||||
|
funcnameOffset uintptr // offset to the funcnametab variable from pcHeader
|
||||||
|
cuOffset uintptr // offset to the cutab variable from pcHeader
|
||||||
|
filetabOffset uintptr // offset to the filetab variable from pcHeader
|
||||||
|
pctabOffset uintptr // offset to the pctab variable from pcHeader
|
||||||
|
pclnOffset uintptr // offset to the pclntab variable from pcHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
type bitVector struct {
|
||||||
|
n int32 // # of bits
|
||||||
|
bytedata *uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type ptabEntry struct {
|
||||||
|
name int32
|
||||||
|
typ int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type textSection struct {
|
||||||
|
vaddr uintptr // prelinked section vaddr
|
||||||
|
end uintptr // vaddr + section length
|
||||||
|
baseaddr uintptr // relocated section address
|
||||||
|
}
|
||||||
|
|
||||||
|
type modulehash struct {
|
||||||
|
modulename string
|
||||||
|
linktimehash string
|
||||||
|
runtimehash *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// findfuncbucket is an array of these structures.
|
||||||
|
// Each bucket represents 4096 bytes of the text segment.
|
||||||
|
// Each subbucket represents 256 bytes of the text segment.
|
||||||
|
// To find a function given a pc, locate the bucket and subbucket for
|
||||||
|
// that pc. Add together the idx and subbucket value to obtain a
|
||||||
|
// function index. Then scan the functab array starting at that
|
||||||
|
// index to find the target function.
|
||||||
|
// This table uses 20 bytes for every 4096 bytes of code, or ~0.5% overhead.
|
||||||
|
type findfuncbucket struct {
|
||||||
|
idx uint32
|
||||||
|
_SUBBUCKETS [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// func name table format:
|
||||||
|
// nameOff[0] -> namePartA namePartB namePartC \x00
|
||||||
|
// nameOff[1] -> namePartA namePartB namePartC \x00
|
||||||
|
// ...
|
||||||
|
func makeFuncnameTab(funcs []Func) (tab []byte, offs []int32) {
|
||||||
|
offs = make([]int32, len(funcs))
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
for i, f := range funcs {
|
||||||
|
offs[i] = int32(offset)
|
||||||
|
|
||||||
|
a, b, c := funcNameParts(f.Name)
|
||||||
|
tab = append(tab, a...)
|
||||||
|
tab = append(tab, b...)
|
||||||
|
tab = append(tab, c...)
|
||||||
|
tab = append(tab, 0)
|
||||||
|
offset += len(a) + len(b) + len(c) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type compilationUnit struct {
|
||||||
|
fileNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CU table format:
|
||||||
|
// cuOffsets[0] -> filetabOffset[0] filetabOffset[1] ... filetabOffset[len(CUs[0].fileNames)-1]
|
||||||
|
// cuOffsets[1] -> filetabOffset[len(CUs[0].fileNames)] ... filetabOffset[len(CUs[0].fileNames) + len(CUs[1].fileNames)-1]
|
||||||
|
// ...
|
||||||
|
//
|
||||||
|
// file name table format:
|
||||||
|
// filetabOffset[0] -> CUs[0].fileNames[0] \x00
|
||||||
|
// ...
|
||||||
|
// filetabOffset[len(CUs[0]-1)] -> CUs[0].fileNames[len(CUs[0].fileNames)-1] \x00
|
||||||
|
// ...
|
||||||
|
// filetabOffset[SUM(CUs,fileNames)-1] -> CUs[len(CU)-1].fileNames[len(CUs[len(CU)-1].fileNames)-1] \x00
|
||||||
|
func makeFilenametab(cus []compilationUnit) (cutab []uint32, filetab []byte, cuOffsets []uint32) {
|
||||||
|
cuOffsets = make([]uint32, len(cus))
|
||||||
|
cuOffset := 0
|
||||||
|
fileOffset := 0
|
||||||
|
|
||||||
|
for i, cu := range cus {
|
||||||
|
cuOffsets[i] = uint32(cuOffset)
|
||||||
|
|
||||||
|
for _, name := range cu.fileNames {
|
||||||
|
cutab = append(cutab, uint32(fileOffset))
|
||||||
|
|
||||||
|
fileOffset += len(name) + 1
|
||||||
|
filetab = append(filetab, name...)
|
||||||
|
filetab = append(filetab, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
cuOffset += len(cu.fileNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFuncdata(out *[]byte, funcs []Func) (fstart int, funcdataOffs [][]uint32) {
|
||||||
|
fstart = len(*out)
|
||||||
|
*out = append(*out, byte(0))
|
||||||
|
offs := uint32(1)
|
||||||
|
|
||||||
|
funcdataOffs = make([][]uint32, len(funcs))
|
||||||
|
for i, f := range funcs {
|
||||||
|
|
||||||
|
var writer = func(fd encoding.BinaryMarshaler) {
|
||||||
|
var ab []byte
|
||||||
|
var err error
|
||||||
|
if fd != nil {
|
||||||
|
ab, err = fd.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
funcdataOffs[i] = append(funcdataOffs[i], offs)
|
||||||
|
} else {
|
||||||
|
ab = []byte{0}
|
||||||
|
funcdataOffs[i] = append(funcdataOffs[i], _INVALID_FUNCDATA_OFFSET)
|
||||||
|
}
|
||||||
|
*out = append(*out, ab...)
|
||||||
|
offs += uint32(len(ab))
|
||||||
|
}
|
||||||
|
|
||||||
|
writer(f.ArgsPointerMaps)
|
||||||
|
writer(f.LocalsPointerMaps)
|
||||||
|
writer(f.StackObjects)
|
||||||
|
writer(f.InlTree)
|
||||||
|
writer(f.OpenCodedDeferInfo)
|
||||||
|
writer(f.ArgInfo)
|
||||||
|
writer(f.ArgLiveInfo)
|
||||||
|
writer(f.WrapInfo)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFtab(funcs []_func, lastFuncSize uint32) (ftab []funcTab) {
|
||||||
|
// Allocate space for the pc->func table. This structure consists of a pc offset
|
||||||
|
// and an offset to the func structure. After that, we have a single pc
|
||||||
|
// value that marks the end of the last function in the binary.
|
||||||
|
var size int64 = int64(len(funcs)*2*4 + 4)
|
||||||
|
var startLocations = make([]uint32, len(funcs))
|
||||||
|
for i, f := range funcs {
|
||||||
|
size = rnd(size, int64(_PtrSize))
|
||||||
|
//writePCToFunc
|
||||||
|
startLocations[i] = uint32(size)
|
||||||
|
size += int64(uint8(_FUNC_SIZE)+f.nfuncdata*4+uint8(f.npcdata)*4)
|
||||||
|
}
|
||||||
|
|
||||||
|
ftab = make([]funcTab, 0, len(funcs)+1)
|
||||||
|
|
||||||
|
// write a map of pc->func info offsets
|
||||||
|
for i, f := range funcs {
|
||||||
|
ftab = append(ftab, funcTab{uint32(f.entryOff), uint32(startLocations[i])})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final entry of table is just end pc offset.
|
||||||
|
lastFunc := funcs[len(funcs)-1]
|
||||||
|
ftab = append(ftab, funcTab{uint32(lastFunc.entryOff + lastFuncSize), 0})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pcln table format: [...]funcTab + [...]_Func
|
||||||
|
func makePclntable(funcs []_func, lastFuncSize uint32, pcdataOffs [][]uint32, funcdataOffs [][]uint32) (pclntab []byte) {
|
||||||
|
// Allocate space for the pc->func table. This structure consists of a pc offset
|
||||||
|
// and an offset to the func structure. After that, we have a single pc
|
||||||
|
// value that marks the end of the last function in the binary.
|
||||||
|
var size int64 = int64(len(funcs)*2*4 + 4)
|
||||||
|
var startLocations = make([]uint32, len(funcs))
|
||||||
|
for i := range funcs {
|
||||||
|
size = rnd(size, int64(_PtrSize))
|
||||||
|
//writePCToFunc
|
||||||
|
startLocations[i] = uint32(size)
|
||||||
|
size += int64(int(_FUNC_SIZE)+len(funcdataOffs[i])*4+len(pcdataOffs[i])*4)
|
||||||
|
}
|
||||||
|
|
||||||
|
pclntab = make([]byte, size, size)
|
||||||
|
|
||||||
|
// write a map of pc->func info offsets
|
||||||
|
offs := 0
|
||||||
|
for i, f := range funcs {
|
||||||
|
byteOrder.PutUint32(pclntab[offs:offs+4], uint32(f.entryOff))
|
||||||
|
byteOrder.PutUint32(pclntab[offs+4:offs+8], uint32(startLocations[i]))
|
||||||
|
offs += 8
|
||||||
|
}
|
||||||
|
// Final entry of table is just end pc offset.
|
||||||
|
lastFunc := funcs[len(funcs)-1]
|
||||||
|
byteOrder.PutUint32(pclntab[offs:offs+4], uint32(lastFunc.entryOff+lastFuncSize))
|
||||||
|
|
||||||
|
// write func info table
|
||||||
|
for i, f := range funcs {
|
||||||
|
off := startLocations[i]
|
||||||
|
|
||||||
|
// write _func structure to pclntab
|
||||||
|
fb := rt.BytesFrom(unsafe.Pointer(&f), int(_FUNC_SIZE), int(_FUNC_SIZE))
|
||||||
|
copy(pclntab[off:off+uint32(_FUNC_SIZE)], fb)
|
||||||
|
off += uint32(_FUNC_SIZE)
|
||||||
|
|
||||||
|
// NOTICE: _func.pcdata always starts from PcUnsafePoint, which is index 3
|
||||||
|
for j := 3; j < len(pcdataOffs[i]); j++ {
|
||||||
|
byteOrder.PutUint32(pclntab[off:off+4], uint32(pcdataOffs[i][j]))
|
||||||
|
off += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcdata refs as offsets from gofunc
|
||||||
|
for _, funcdata := range funcdataOffs[i] {
|
||||||
|
byteOrder.PutUint32(pclntab[off:off+4], uint32(funcdata))
|
||||||
|
off += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// findfunc table used to map pc to belonging func,
|
||||||
|
// returns the index in the func table.
|
||||||
|
//
|
||||||
|
// All text section are divided into buckets sized _BUCKETSIZE(4K):
|
||||||
|
// every bucket is divided into _SUBBUCKETS sized _SUB_BUCKETSIZE(64),
|
||||||
|
// and it has a base idx to plus the offset stored in jth subbucket.
|
||||||
|
// see findfunc() in runtime/symtab.go
|
||||||
|
func writeFindfunctab(out *[]byte, ftab []funcTab) (start int) {
|
||||||
|
start = len(*out)
|
||||||
|
|
||||||
|
max := ftab[len(ftab)-1].entry
|
||||||
|
min := ftab[0].entry
|
||||||
|
nbuckets := (max - min + _BUCKETSIZE - 1) / _BUCKETSIZE
|
||||||
|
n := (max - min + _SUB_BUCKETSIZE - 1) / _SUB_BUCKETSIZE
|
||||||
|
|
||||||
|
tab := make([]findfuncbucket, 0, nbuckets)
|
||||||
|
var s, e = 0, 0
|
||||||
|
for i := 0; i<int(nbuckets); i++ {
|
||||||
|
var pc = min + uint32((i+1)*_BUCKETSIZE)
|
||||||
|
// find the end func of the bucket
|
||||||
|
for ; e < len(ftab)-1 && ftab[e+1].entry <= pc; e++ {}
|
||||||
|
// store the start func of the bucket
|
||||||
|
var fb = findfuncbucket{idx: uint32(s)}
|
||||||
|
|
||||||
|
for j := 0; j<_SUBBUCKETS && (i*_SUBBUCKETS+j)<int(n); j++ {
|
||||||
|
pc = min + uint32(i*_BUCKETSIZE) + uint32((j+1)*_SUB_BUCKETSIZE)
|
||||||
|
var ss = s
|
||||||
|
// find the end func of the subbucket
|
||||||
|
for ; ss < len(ftab)-1 && ftab[ss+1].entry <= pc; ss++ {}
|
||||||
|
// store the start func of the subbucket
|
||||||
|
fb._SUBBUCKETS[j] = byte(uint32(s) - fb.idx)
|
||||||
|
s = ss
|
||||||
|
}
|
||||||
|
s = e
|
||||||
|
tab = append(tab, fb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write findfuncbucket
|
||||||
|
if len(tab) > 0 {
|
||||||
|
size := int(unsafe.Sizeof(findfuncbucket{}))*len(tab)
|
||||||
|
*out = append(*out, rt.BytesFrom(unsafe.Pointer(&tab[0]), size, size)...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeModuledata(name string, filenames []string, funcs []Func, text []byte) (mod *moduledata) {
|
||||||
|
mod = new(moduledata)
|
||||||
|
mod.modulename = name
|
||||||
|
|
||||||
|
// make filename table
|
||||||
|
cu := make([]string, 0, len(filenames))
|
||||||
|
for _, f := range filenames {
|
||||||
|
cu = append(cu, f)
|
||||||
|
}
|
||||||
|
cutab, filetab, cuOffs := makeFilenametab([]compilationUnit{{cu}})
|
||||||
|
mod.cutab = cutab
|
||||||
|
mod.filetab = filetab
|
||||||
|
|
||||||
|
// make funcname table
|
||||||
|
funcnametab, nameOffs := makeFuncnameTab(funcs)
|
||||||
|
mod.funcnametab = funcnametab
|
||||||
|
|
||||||
|
// make pcdata table
|
||||||
|
// NOTICE: _func only use offset to index pcdata, thus no need mmap() pcdata
|
||||||
|
pctab, pcdataOffs, _funcs := makePctab(funcs, cuOffs, nameOffs)
|
||||||
|
mod.pctab = pctab
|
||||||
|
|
||||||
|
// write func data
|
||||||
|
// NOTICE: _func use mod.gofunc+offset to directly point funcdata, thus need cache funcdata
|
||||||
|
// TODO: estimate accurate capacity
|
||||||
|
cache := make([]byte, 0, len(funcs)*int(_PtrSize))
|
||||||
|
fstart, funcdataOffs := writeFuncdata(&cache, funcs)
|
||||||
|
|
||||||
|
// make pc->func (binary search) func table
|
||||||
|
lastFuncsize := funcs[len(funcs)-1].TextSize
|
||||||
|
ftab := makeFtab(_funcs, lastFuncsize)
|
||||||
|
mod.ftab = ftab
|
||||||
|
|
||||||
|
// write pc->func (modmap) findfunc table
|
||||||
|
ffstart := writeFindfunctab(&cache, ftab)
|
||||||
|
|
||||||
|
// make pclnt table
|
||||||
|
pclntab := makePclntable(_funcs, lastFuncsize, pcdataOffs, funcdataOffs)
|
||||||
|
mod.pclntable = pclntab
|
||||||
|
|
||||||
|
// mmap() text and funcdata segements
|
||||||
|
p := os.Getpagesize()
|
||||||
|
size := int(rnd(int64(len(text)), int64(p)))
|
||||||
|
addr := mmap(size)
|
||||||
|
// copy the machine code
|
||||||
|
s := rt.BytesFrom(unsafe.Pointer(addr), len(text), size)
|
||||||
|
copy(s, text)
|
||||||
|
// make it executable
|
||||||
|
mprotect(addr, size)
|
||||||
|
|
||||||
|
// assign addresses
|
||||||
|
mod.text = addr
|
||||||
|
mod.etext = addr + uintptr(size)
|
||||||
|
mod.minpc = addr
|
||||||
|
mod.maxpc = addr + uintptr(len(text))
|
||||||
|
|
||||||
|
// cache funcdata and findfuncbucket
|
||||||
|
moduleCache.Lock()
|
||||||
|
moduleCache.m[mod] = cache
|
||||||
|
moduleCache.Unlock()
|
||||||
|
mod.gofunc = uintptr(unsafe.Pointer(&cache[fstart]))
|
||||||
|
mod.findfunctab = uintptr(unsafe.Pointer(&cache[ffstart]))
|
||||||
|
|
||||||
|
// make pc header
|
||||||
|
mod.pcHeader = &pcHeader {
|
||||||
|
magic : _Magic,
|
||||||
|
minLC : _MinLC,
|
||||||
|
ptrSize : _PtrSize,
|
||||||
|
nfunc : len(funcs),
|
||||||
|
nfiles: uint(len(cu)),
|
||||||
|
textStart: mod.text,
|
||||||
|
funcnameOffset: getOffsetOf(moduledata{}, "funcnametab"),
|
||||||
|
cuOffset: getOffsetOf(moduledata{}, "cutab"),
|
||||||
|
filetabOffset: getOffsetOf(moduledata{}, "filetab"),
|
||||||
|
pctabOffset: getOffsetOf(moduledata{}, "pctab"),
|
||||||
|
pclnOffset: getOffsetOf(moduledata{}, "pclntable"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// sepecial case: gcdata and gcbss must by non-empty
|
||||||
|
mod.gcdata = uintptr(unsafe.Pointer(&emptyByte))
|
||||||
|
mod.gcbss = uintptr(unsafe.Pointer(&emptyByte))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// makePctab generates pcdelta->valuedelta tables for functions,
|
||||||
|
// and returns the table and the entry offset of every kind pcdata in the table.
|
||||||
|
func makePctab(funcs []Func, cuOffset []uint32, nameOffset []int32) (pctab []byte, pcdataOffs [][]uint32, _funcs []_func) {
|
||||||
|
_funcs = make([]_func, len(funcs))
|
||||||
|
|
||||||
|
// Pctab offsets of 0 are considered invalid in the runtime. We respect
|
||||||
|
// that by just padding a single byte at the beginning of runtime.pctab,
|
||||||
|
// that way no real offsets can be zero.
|
||||||
|
pctab = make([]byte, 1, 12*len(funcs)+1)
|
||||||
|
pcdataOffs = make([][]uint32, len(funcs))
|
||||||
|
|
||||||
|
for i, f := range funcs {
|
||||||
|
_f := &_funcs[i]
|
||||||
|
|
||||||
|
var writer = func(pc *Pcdata) {
|
||||||
|
var ab []byte
|
||||||
|
var err error
|
||||||
|
if pc != nil {
|
||||||
|
ab, err = pc.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
pcdataOffs[i] = append(pcdataOffs[i], uint32(len(pctab)))
|
||||||
|
} else {
|
||||||
|
ab = []byte{0}
|
||||||
|
pcdataOffs[i] = append(pcdataOffs[i], _PCDATA_INVALID_OFFSET)
|
||||||
|
}
|
||||||
|
pctab = append(pctab, ab...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Pcsp != nil {
|
||||||
|
_f.pcsp = uint32(len(pctab))
|
||||||
|
}
|
||||||
|
writer(f.Pcsp)
|
||||||
|
if f.Pcfile != nil {
|
||||||
|
_f.pcfile = uint32(len(pctab))
|
||||||
|
}
|
||||||
|
writer(f.Pcfile)
|
||||||
|
if f.Pcline != nil {
|
||||||
|
_f.pcln = uint32(len(pctab))
|
||||||
|
}
|
||||||
|
writer(f.Pcline)
|
||||||
|
writer(f.PcUnsafePoint)
|
||||||
|
writer(f.PcStackMapIndex)
|
||||||
|
writer(f.PcInlTreeIndex)
|
||||||
|
writer(f.PcArgLiveIndex)
|
||||||
|
|
||||||
|
_f.entryOff = f.EntryOff
|
||||||
|
_f.nameOff = nameOffset[i]
|
||||||
|
_f.args = f.ArgsSize
|
||||||
|
_f.deferreturn = f.DeferReturn
|
||||||
|
// NOTICE: _func.pcdata is always as [PCDATA_UnsafePoint(0) : PCDATA_ArgLiveIndex(3)]
|
||||||
|
_f.npcdata = uint32(_N_PCDATA)
|
||||||
|
_f.cuOffset = cuOffset[i]
|
||||||
|
_f.funcID = f.ID
|
||||||
|
_f.flag = f.Flag
|
||||||
|
_f.nfuncdata = uint8(_N_FUNCDATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFunction(name string, pc uintptr, textSize uintptr, fp int, args int, size uintptr, argptrs uintptr, localptrs uintptr) {}
|
||||||
4
vendor/github.com/bytedance/sonic/loader/mmap_unix.go
generated
vendored
4
vendor/github.com/bytedance/sonic/loader/mmap_unix.go
generated
vendored
@@ -1,5 +1,5 @@
|
|||||||
//go:build !windows
|
//go:build darwin || linux
|
||||||
// +build !windows
|
// +build darwin linux
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2023 ByteDance Inc.
|
* Copyright 2023 ByteDance Inc.
|
||||||
|
|||||||
61
vendor/github.com/bytedance/sonic/loader/pcdata.go
generated
vendored
61
vendor/github.com/bytedance/sonic/loader/pcdata.go
generated
vendored
@@ -16,10 +16,6 @@
|
|||||||
|
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
import (
|
|
||||||
`encoding/binary`
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_N_PCDATA = 4
|
_N_PCDATA = 4
|
||||||
|
|
||||||
@@ -53,16 +49,40 @@ const (
|
|||||||
|
|
||||||
var emptyByte byte
|
var emptyByte byte
|
||||||
|
|
||||||
// Pcvalue is the program count corresponding to the value Val
|
func encodeValue(v int) []byte {
|
||||||
// WARN: we use relative value here (to function entry)
|
return encodeVariant(toZigzag(v))
|
||||||
type Pcvalue struct {
|
}
|
||||||
PC uint32 // program count relative to function entry
|
|
||||||
Val int32 // value relative to the value in function entry
|
func toZigzag(v int) int {
|
||||||
|
return (v << 1) ^ (v >> 31)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeVariant(v int) []byte {
|
||||||
|
var u int
|
||||||
|
var r []byte
|
||||||
|
|
||||||
|
/* split every 7 bits */
|
||||||
|
for v > 127 {
|
||||||
|
u = v & 0x7f
|
||||||
|
v = v >> 7
|
||||||
|
r = append(r, byte(u) | 0x80)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check for last one */
|
||||||
|
if v == 0 {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add the last one */
|
||||||
|
r = append(r, byte(v))
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pcvalue struct {
|
||||||
|
PC uint32 // PC offset from func entry
|
||||||
|
Val int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pcdata represents pc->value mapping table.
|
|
||||||
// WARN: we use ** [Pcdata[i].PC, Pcdata[i+1].PC) **
|
|
||||||
// as the range where the Pcdata[i].Val is effective.
|
|
||||||
type Pcdata []Pcvalue
|
type Pcdata []Pcvalue
|
||||||
|
|
||||||
// see https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub
|
// see https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub
|
||||||
@@ -70,24 +90,11 @@ func (self Pcdata) MarshalBinary() (data []byte, err error) {
|
|||||||
// delta value always starts from -1
|
// delta value always starts from -1
|
||||||
sv := int32(_PCDATA_START_VAL)
|
sv := int32(_PCDATA_START_VAL)
|
||||||
sp := uint32(0)
|
sp := uint32(0)
|
||||||
buf := make([]byte, binary.MaxVarintLen32)
|
|
||||||
for _, v := range self {
|
for _, v := range self {
|
||||||
if v.PC < sp {
|
data = append(data, encodeVariant(toZigzag(int(v.Val - sv)))...)
|
||||||
panic("PC must be in ascending order!")
|
data = append(data, encodeVariant(int(v.PC - sp))...)
|
||||||
}
|
|
||||||
dp := uint64(v.PC - sp)
|
|
||||||
dv := int64(v.Val - sv)
|
|
||||||
if dv == 0 || dp == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n := binary.PutVarint(buf, dv)
|
|
||||||
data = append(data, buf[:n]...)
|
|
||||||
n2 := binary.PutUvarint(buf, dp)
|
|
||||||
data = append(data, buf[:n2]...)
|
|
||||||
sp = v.PC
|
sp = v.PC
|
||||||
sv = v.Val
|
sv = v.Val
|
||||||
}
|
}
|
||||||
// put 0 to indicate ends
|
|
||||||
data = append(data, 0)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
34
vendor/github.com/bytedance/sonic/loader/stubs.go
generated
vendored
34
vendor/github.com/bytedance/sonic/loader/stubs.go
generated
vendored
@@ -17,8 +17,7 @@
|
|||||||
package loader
|
package loader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync/atomic"
|
`sync`
|
||||||
"unsafe"
|
|
||||||
_ `unsafe`
|
_ `unsafe`
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,35 +25,16 @@ import (
|
|||||||
//goland:noinspection GoUnusedGlobalVariable
|
//goland:noinspection GoUnusedGlobalVariable
|
||||||
var lastmoduledatap *moduledata
|
var lastmoduledatap *moduledata
|
||||||
|
|
||||||
|
var moduledataMux sync.Mutex
|
||||||
|
|
||||||
func registerModule(mod *moduledata) {
|
func registerModule(mod *moduledata) {
|
||||||
registerModuleLockFree(&lastmoduledatap, mod)
|
moduledataMux.Lock()
|
||||||
|
lastmoduledatap.next = mod
|
||||||
|
lastmoduledatap = mod
|
||||||
|
moduledataMux.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:linkname moduledataverify1 runtime.moduledataverify1
|
//go:linkname moduledataverify1 runtime.moduledataverify1
|
||||||
func moduledataverify1(_ *moduledata)
|
func moduledataverify1(_ *moduledata)
|
||||||
|
|
||||||
func registerModuleLockFree(tail **moduledata, mod *moduledata) {
|
|
||||||
for {
|
|
||||||
oldTail := loadModule(tail)
|
|
||||||
if casModule(tail, oldTail, mod) {
|
|
||||||
storeModule(&oldTail.next, mod)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadModule(p **moduledata) *moduledata {
|
|
||||||
return (*moduledata)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p))))
|
|
||||||
}
|
|
||||||
|
|
||||||
func storeModule(p **moduledata, value *moduledata) {
|
|
||||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(p)), unsafe.Pointer(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
func casModule(p **moduledata, oldValue *moduledata, newValue *moduledata) bool {
|
|
||||||
return atomic.CompareAndSwapPointer(
|
|
||||||
(*unsafe.Pointer)(unsafe.Pointer(p)),
|
|
||||||
unsafe.Pointer(oldValue),
|
|
||||||
unsafe.Pointer(newValue),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
8
vendor/github.com/bytedance/sonic/sonic.go
generated
vendored
8
vendor/github.com/bytedance/sonic/sonic.go
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
// +build amd64,go1.16,!go1.23
|
// +build amd64,go1.15,!go1.21
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2021 ByteDance Inc.
|
* Copyright 2021 ByteDance Inc.
|
||||||
@@ -58,12 +58,6 @@ func (cfg Config) Froze() API {
|
|||||||
if cfg.ValidateString {
|
if cfg.ValidateString {
|
||||||
api.encoderOpts |= encoder.ValidateString
|
api.encoderOpts |= encoder.ValidateString
|
||||||
}
|
}
|
||||||
if cfg.NoValidateJSONMarshaler {
|
|
||||||
api.encoderOpts |= encoder.NoValidateJSONMarshaler
|
|
||||||
}
|
|
||||||
if cfg.NoEncoderNewline {
|
|
||||||
api.encoderOpts |= encoder.NoEncoderNewline
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure decoder options:
|
// configure decoder options:
|
||||||
if cfg.UseInt64 {
|
if cfg.UseInt64 {
|
||||||
|
|||||||
29
vendor/github.com/bytedance/sonic/unquote/unquote.go
generated
vendored
29
vendor/github.com/bytedance/sonic/unquote/unquote.go
generated
vendored
@@ -25,45 +25,27 @@ import (
|
|||||||
`github.com/bytedance/sonic/internal/rt`
|
`github.com/bytedance/sonic/internal/rt`
|
||||||
)
|
)
|
||||||
|
|
||||||
// String unescapes a escaped string (not including `"` at begining and end)
|
|
||||||
// It validates invalid UTF8 and replace with `\ufffd`
|
|
||||||
func String(s string) (ret string, err types.ParsingError) {
|
func String(s string) (ret string, err types.ParsingError) {
|
||||||
mm := make([]byte, 0, len(s))
|
mm := make([]byte, 0, len(s))
|
||||||
err = intoBytesUnsafe(s, &mm, true)
|
err = intoBytesUnsafe(s, &mm)
|
||||||
ret = rt.Mem2Str(mm)
|
ret = rt.Mem2Str(mm)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntoBytes is same with String besides it output result into a buffer m
|
|
||||||
func IntoBytes(s string, m *[]byte) types.ParsingError {
|
func IntoBytes(s string, m *[]byte) types.ParsingError {
|
||||||
if cap(*m) < len(s) {
|
if cap(*m) < len(s) {
|
||||||
return types.ERR_EOF
|
return types.ERR_EOF
|
||||||
} else {
|
} else {
|
||||||
return intoBytesUnsafe(s, m, true)
|
return intoBytesUnsafe(s, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// String unescapes a escaped string (not including `"` at begining and end)
|
func intoBytesUnsafe(s string, m *[]byte) types.ParsingError {
|
||||||
// - replace enables replacing invalid utf8 escaped char with `\uffd`
|
|
||||||
func _String(s string, replace bool) (ret string, err error) {
|
|
||||||
mm := make([]byte, 0, len(s))
|
|
||||||
err = intoBytesUnsafe(s, &mm, replace)
|
|
||||||
ret = rt.Mem2Str(mm)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func intoBytesUnsafe(s string, m *[]byte, replace bool) types.ParsingError {
|
|
||||||
pos := -1
|
pos := -1
|
||||||
slv := (*rt.GoSlice)(unsafe.Pointer(m))
|
slv := (*rt.GoSlice)(unsafe.Pointer(m))
|
||||||
str := (*rt.GoString)(unsafe.Pointer(&s))
|
str := (*rt.GoString)(unsafe.Pointer(&s))
|
||||||
|
|
||||||
flags := uint64(0)
|
|
||||||
if replace {
|
|
||||||
/* unquote as the default configuration, replace invalid unicode with \ufffd */
|
/* unquote as the default configuration, replace invalid unicode with \ufffd */
|
||||||
flags |= types.F_UNICODE_REPLACE
|
ret := native.Unquote(str.Ptr, str.Len, slv.Ptr, &pos, types.F_UNICODE_REPLACE)
|
||||||
}
|
|
||||||
|
|
||||||
ret := native.Unquote(str.Ptr, str.Len, slv.Ptr, &pos, flags)
|
|
||||||
|
|
||||||
/* check for errors */
|
/* check for errors */
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
@@ -75,6 +57,3 @@ func intoBytesUnsafe(s string, m *[]byte, replace bool) types.ParsingError {
|
|||||||
runtime.KeepAlive(s)
|
runtime.KeepAlive(s)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3
vendor/github.com/gabriel-vasile/mimetype/README.md
generated
vendored
3
vendor/github.com/gabriel-vasile/mimetype/README.md
generated
vendored
@@ -10,6 +10,9 @@
|
|||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
<a href="https://travis-ci.org/gabriel-vasile/mimetype">
|
||||||
|
<img alt="Build Status" src="https://travis-ci.org/gabriel-vasile/mimetype.svg?branch=master">
|
||||||
|
</a>
|
||||||
<a href="https://pkg.go.dev/github.com/gabriel-vasile/mimetype">
|
<a href="https://pkg.go.dev/github.com/gabriel-vasile/mimetype">
|
||||||
<img alt="Go Reference" src="https://pkg.go.dev/badge/github.com/gabriel-vasile/mimetype.svg">
|
<img alt="Go Reference" src="https://pkg.go.dev/badge/github.com/gabriel-vasile/mimetype.svg">
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
12
vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go
generated
vendored
12
vendor/github.com/gabriel-vasile/mimetype/internal/magic/binary.go
generated
vendored
@@ -150,20 +150,18 @@ func Marc(raw []byte, limit uint32) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Glb matches a glTF model format file.
|
// Glb matches a glTF model format file.
|
||||||
// GLB is the binary file format representation of 3D models saved in
|
// GLB is the binary file format representation of 3D models save in
|
||||||
// the GL transmission Format (glTF).
|
// the GL transmission Format (glTF).
|
||||||
// GLB uses little endian and its header structure is as follows:
|
// see more: https://docs.fileformat.com/3d/glb/
|
||||||
|
// https://www.iana.org/assignments/media-types/model/gltf-binary
|
||||||
|
// GLB file format is based on little endian and its header structure
|
||||||
|
// show below:
|
||||||
//
|
//
|
||||||
// <-- 12-byte header -->
|
// <-- 12-byte header -->
|
||||||
// | magic | version | length |
|
// | magic | version | length |
|
||||||
// | (uint32) | (uint32) | (uint32) |
|
// | (uint32) | (uint32) | (uint32) |
|
||||||
// | \x67\x6C\x54\x46 | \x01\x00\x00\x00 | ... |
|
// | \x67\x6C\x54\x46 | \x01\x00\x00\x00 | ... |
|
||||||
// | g l T F | 1 | ... |
|
// | g l T F | 1 | ... |
|
||||||
//
|
|
||||||
// Visit [glTF specification] and [IANA glTF entry] for more details.
|
|
||||||
//
|
|
||||||
// [glTF specification]: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html
|
|
||||||
// [IANA glTF entry]: https://www.iana.org/assignments/media-types/model/gltf-binary
|
|
||||||
var Glb = prefix([]byte("\x67\x6C\x54\x46\x02\x00\x00\x00"),
|
var Glb = prefix([]byte("\x67\x6C\x54\x46\x02\x00\x00\x00"),
|
||||||
[]byte("\x67\x6C\x54\x46\x01\x00\x00\x00"))
|
[]byte("\x67\x6C\x54\x46\x01\x00\x00\x00"))
|
||||||
|
|
||||||
|
|||||||
2
vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go
generated
vendored
2
vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go
generated
vendored
@@ -177,9 +177,7 @@ func newXMLSig(localName, xmlns string) xmlSig {
|
|||||||
// and, optionally, followed by the arguments for the interpreter.
|
// and, optionally, followed by the arguments for the interpreter.
|
||||||
//
|
//
|
||||||
// Ex:
|
// Ex:
|
||||||
//
|
|
||||||
// #! /usr/bin/env php
|
// #! /usr/bin/env php
|
||||||
//
|
|
||||||
// /usr/bin/env is the interpreter, php is the first and only argument.
|
// /usr/bin/env is the interpreter, php is the first and only argument.
|
||||||
func shebang(sigs ...[]byte) Detector {
|
func shebang(sigs ...[]byte) Detector {
|
||||||
return func(raw []byte, limit uint32) bool {
|
return func(raw []byte, limit uint32) bool {
|
||||||
|
|||||||
18
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text_csv.go
generated
vendored
18
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text_csv.go
generated
vendored
@@ -3,7 +3,6 @@ package magic
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,23 +19,12 @@ func Tsv(raw []byte, limit uint32) bool {
|
|||||||
func sv(in []byte, comma rune, limit uint32) bool {
|
func sv(in []byte, comma rune, limit uint32) bool {
|
||||||
r := csv.NewReader(dropLastLine(in, limit))
|
r := csv.NewReader(dropLastLine(in, limit))
|
||||||
r.Comma = comma
|
r.Comma = comma
|
||||||
r.ReuseRecord = true
|
r.TrimLeadingSpace = true
|
||||||
r.LazyQuotes = true
|
r.LazyQuotes = true
|
||||||
r.Comment = '#'
|
r.Comment = '#'
|
||||||
|
|
||||||
lines := 0
|
lines, err := r.ReadAll()
|
||||||
for {
|
return err == nil && r.FieldsPerRecord > 1 && len(lines) > 1
|
||||||
_, err := r.Read()
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
lines++
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.FieldsPerRecord > 1 && lines > 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dropLastLine drops the last incomplete line from b.
|
// dropLastLine drops the last incomplete line from b.
|
||||||
|
|||||||
1
vendor/github.com/gabriel-vasile/mimetype/mimetype.go
generated
vendored
1
vendor/github.com/gabriel-vasile/mimetype/mimetype.go
generated
vendored
@@ -39,7 +39,6 @@ func Detect(in []byte) *MIME {
|
|||||||
//
|
//
|
||||||
// DetectReader assumes the reader offset is at the start. If the input is an
|
// DetectReader assumes the reader offset is at the start. If the input is an
|
||||||
// io.ReadSeeker you previously read from, it should be rewinded before detection:
|
// io.ReadSeeker you previously read from, it should be rewinded before detection:
|
||||||
//
|
|
||||||
// reader.Seek(0, io.SeekStart)
|
// reader.Seek(0, io.SeekStart)
|
||||||
func DetectReader(r io.Reader) (*MIME, error) {
|
func DetectReader(r io.Reader) (*MIME, error) {
|
||||||
var in []byte
|
var in []byte
|
||||||
|
|||||||
2
vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md
generated
vendored
2
vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
## 173 Supported MIME types
|
## 172 Supported MIME types
|
||||||
This file is automatically generated when running tests. Do not edit manually.
|
This file is automatically generated when running tests. Do not edit manually.
|
||||||
|
|
||||||
Extension | MIME type | Aliases
|
Extension | MIME type | Aliases
|
||||||
|
|||||||
4
vendor/github.com/gin-gonic/gin/.gitignore
generated
vendored
4
vendor/github.com/gin-gonic/gin/.gitignore
generated
vendored
@@ -5,7 +5,3 @@ count.out
|
|||||||
test
|
test
|
||||||
profile.out
|
profile.out
|
||||||
tmp.out
|
tmp.out
|
||||||
|
|
||||||
# Develop tools
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
|
|||||||
1
vendor/github.com/gin-gonic/gin/.golangci.yml
generated
vendored
1
vendor/github.com/gin-gonic/gin/.golangci.yml
generated
vendored
@@ -3,6 +3,7 @@ run:
|
|||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- asciicheck
|
- asciicheck
|
||||||
|
- depguard
|
||||||
- dogsled
|
- dogsled
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- errcheck
|
- errcheck
|
||||||
|
|||||||
29
vendor/github.com/gin-gonic/gin/.goreleaser.yaml
generated
vendored
29
vendor/github.com/gin-gonic/gin/.goreleaser.yaml
generated
vendored
@@ -1,7 +1,8 @@
|
|||||||
project_name: gin
|
project_name: gin
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- # If true, skip the build.
|
-
|
||||||
|
# If true, skip the build.
|
||||||
# Useful for library projects.
|
# Useful for library projects.
|
||||||
# Default is false
|
# Default is false
|
||||||
skip: true
|
skip: true
|
||||||
@@ -9,7 +10,7 @@ builds:
|
|||||||
changelog:
|
changelog:
|
||||||
# Set it to true if you wish to skip the changelog generation.
|
# Set it to true if you wish to skip the changelog generation.
|
||||||
# This may result in an empty release notes on GitHub/GitLab/Gitea.
|
# This may result in an empty release notes on GitHub/GitLab/Gitea.
|
||||||
disable: false
|
skip: false
|
||||||
|
|
||||||
# Changelog generation implementation to use.
|
# Changelog generation implementation to use.
|
||||||
#
|
#
|
||||||
@@ -20,7 +21,7 @@ changelog:
|
|||||||
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
|
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
|
||||||
#
|
#
|
||||||
# Defaults to `git`.
|
# Defaults to `git`.
|
||||||
use: github
|
use: git
|
||||||
|
|
||||||
# Sorts the changelog by the commit's messages.
|
# Sorts the changelog by the commit's messages.
|
||||||
# Could either be asc, desc or empty
|
# Could either be asc, desc or empty
|
||||||
@@ -37,20 +38,20 @@ changelog:
|
|||||||
- title: Features
|
- title: Features
|
||||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||||
order: 0
|
order: 0
|
||||||
- title: "Bug fixes"
|
- title: 'Bug fixes'
|
||||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||||
order: 1
|
order: 1
|
||||||
- title: "Enhancements"
|
- title: 'Enhancements'
|
||||||
regexp: "^.*chore[(\\w)]*:+.*$"
|
regexp: "^.*chore[(\\w)]*:+.*$"
|
||||||
order: 2
|
order: 2
|
||||||
- title: "Refactor"
|
|
||||||
regexp: "^.*refactor[(\\w)]*:+.*$"
|
|
||||||
order: 3
|
|
||||||
- title: "Build process updates"
|
|
||||||
regexp: ^.*?(build|ci)(\(.+\))??!?:.+$
|
|
||||||
order: 4
|
|
||||||
- title: "Documentation updates"
|
|
||||||
regexp: ^.*?docs?(\(.+\))??!?:.+$
|
|
||||||
order: 4
|
|
||||||
- title: Others
|
- title: Others
|
||||||
order: 999
|
order: 999
|
||||||
|
|
||||||
|
filters:
|
||||||
|
# Commit messages matching the regexp listed here will be removed from
|
||||||
|
# the changelog
|
||||||
|
# Default is empty
|
||||||
|
exclude:
|
||||||
|
- '^docs'
|
||||||
|
- 'CICD'
|
||||||
|
- typo
|
||||||
|
|||||||
1
vendor/github.com/gin-gonic/gin/Makefile
generated
vendored
1
vendor/github.com/gin-gonic/gin/Makefile
generated
vendored
@@ -42,7 +42,6 @@ fmt-check:
|
|||||||
exit 1; \
|
exit 1; \
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
.PHONY: vet
|
|
||||||
vet:
|
vet:
|
||||||
$(GO) vet $(VETPACKAGES)
|
$(GO) vet $(VETPACKAGES)
|
||||||
|
|
||||||
|
|||||||
25
vendor/github.com/gin-gonic/gin/auth.go
generated
vendored
25
vendor/github.com/gin-gonic/gin/auth.go
generated
vendored
@@ -16,9 +16,6 @@ import (
|
|||||||
// AuthUserKey is the cookie name for user credential in basic auth.
|
// AuthUserKey is the cookie name for user credential in basic auth.
|
||||||
const AuthUserKey = "user"
|
const AuthUserKey = "user"
|
||||||
|
|
||||||
// AuthProxyUserKey is the cookie name for proxy_user credential in basic auth for proxy.
|
|
||||||
const AuthProxyUserKey = "proxy_user"
|
|
||||||
|
|
||||||
// Accounts defines a key/value for user/pass list of authorized logins.
|
// Accounts defines a key/value for user/pass list of authorized logins.
|
||||||
type Accounts map[string]string
|
type Accounts map[string]string
|
||||||
|
|
||||||
@@ -92,25 +89,3 @@ func authorizationHeader(user, password string) string {
|
|||||||
base := user + ":" + password
|
base := user + ":" + password
|
||||||
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
|
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware.
|
|
||||||
// If the realm is empty, "Proxy Authorization Required" will be used by default.
|
|
||||||
func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc {
|
|
||||||
if realm == "" {
|
|
||||||
realm = "Proxy Authorization Required"
|
|
||||||
}
|
|
||||||
realm = "Basic realm=" + strconv.Quote(realm)
|
|
||||||
pairs := processAccounts(accounts)
|
|
||||||
return func(c *Context) {
|
|
||||||
proxyUser, found := pairs.searchCredential(c.requestHeader("Proxy-Authorization"))
|
|
||||||
if !found {
|
|
||||||
// Credentials doesn't match, we return 407 and abort handlers chain.
|
|
||||||
c.Header("Proxy-Authenticate", realm)
|
|
||||||
c.AbortWithStatus(http.StatusProxyAuthRequired)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// The proxy_user credentials was found, set proxy_user's id to key AuthProxyUserKey in this context, the proxy_user's id can be read later using
|
|
||||||
// c.MustGet(gin.AuthProxyUserKey).
|
|
||||||
c.Set(AuthProxyUserKey, proxyUser)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
27
vendor/github.com/gin-gonic/gin/binding/binding.go
generated
vendored
27
vendor/github.com/gin-gonic/gin/binding/binding.go
generated
vendored
@@ -21,7 +21,6 @@ const (
|
|||||||
MIMEMSGPACK = "application/x-msgpack"
|
MIMEMSGPACK = "application/x-msgpack"
|
||||||
MIMEMSGPACK2 = "application/msgpack"
|
MIMEMSGPACK2 = "application/msgpack"
|
||||||
MIMEYAML = "application/x-yaml"
|
MIMEYAML = "application/x-yaml"
|
||||||
MIMEYAML2 = "application/yaml"
|
|
||||||
MIMETOML = "application/toml"
|
MIMETOML = "application/toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -73,18 +72,18 @@ var Validator StructValidator = &defaultValidator{}
|
|||||||
// These implement the Binding interface and can be used to bind the data
|
// These implement the Binding interface and can be used to bind the data
|
||||||
// present in the request to struct instances.
|
// present in the request to struct instances.
|
||||||
var (
|
var (
|
||||||
JSON BindingBody = jsonBinding{}
|
JSON = jsonBinding{}
|
||||||
XML BindingBody = xmlBinding{}
|
XML = xmlBinding{}
|
||||||
Form Binding = formBinding{}
|
Form = formBinding{}
|
||||||
Query Binding = queryBinding{}
|
Query = queryBinding{}
|
||||||
FormPost Binding = formPostBinding{}
|
FormPost = formPostBinding{}
|
||||||
FormMultipart Binding = formMultipartBinding{}
|
FormMultipart = formMultipartBinding{}
|
||||||
ProtoBuf BindingBody = protobufBinding{}
|
ProtoBuf = protobufBinding{}
|
||||||
MsgPack BindingBody = msgpackBinding{}
|
MsgPack = msgpackBinding{}
|
||||||
YAML BindingBody = yamlBinding{}
|
YAML = yamlBinding{}
|
||||||
Uri BindingUri = uriBinding{}
|
Uri = uriBinding{}
|
||||||
Header Binding = headerBinding{}
|
Header = headerBinding{}
|
||||||
TOML BindingBody = tomlBinding{}
|
TOML = tomlBinding{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default returns the appropriate Binding instance based on the HTTP method
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
@@ -103,7 +102,7 @@ func Default(method, contentType string) Binding {
|
|||||||
return ProtoBuf
|
return ProtoBuf
|
||||||
case MIMEMSGPACK, MIMEMSGPACK2:
|
case MIMEMSGPACK, MIMEMSGPACK2:
|
||||||
return MsgPack
|
return MsgPack
|
||||||
case MIMEYAML, MIMEYAML2:
|
case MIMEYAML:
|
||||||
return YAML
|
return YAML
|
||||||
case MIMETOML:
|
case MIMETOML:
|
||||||
return TOML
|
return TOML
|
||||||
|
|||||||
3
vendor/github.com/gin-gonic/gin/binding/binding_nomsgpack.go
generated
vendored
3
vendor/github.com/gin-gonic/gin/binding/binding_nomsgpack.go
generated
vendored
@@ -19,7 +19,6 @@ const (
|
|||||||
MIMEMultipartPOSTForm = "multipart/form-data"
|
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||||
MIMEPROTOBUF = "application/x-protobuf"
|
MIMEPROTOBUF = "application/x-protobuf"
|
||||||
MIMEYAML = "application/x-yaml"
|
MIMEYAML = "application/x-yaml"
|
||||||
MIMEYAML2 = "application/yaml"
|
|
||||||
MIMETOML = "application/toml"
|
MIMETOML = "application/toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,7 +96,7 @@ func Default(method, contentType string) Binding {
|
|||||||
return XML
|
return XML
|
||||||
case MIMEPROTOBUF:
|
case MIMEPROTOBUF:
|
||||||
return ProtoBuf
|
return ProtoBuf
|
||||||
case MIMEYAML, MIMEYAML2:
|
case MIMEYAML:
|
||||||
return YAML
|
return YAML
|
||||||
case MIMEMultipartPOSTForm:
|
case MIMEMultipartPOSTForm:
|
||||||
return FormMultipart
|
return FormMultipart
|
||||||
|
|||||||
3
vendor/github.com/gin-gonic/gin/binding/default_validator.go
generated
vendored
3
vendor/github.com/gin-gonic/gin/binding/default_validator.go
generated
vendored
@@ -54,10 +54,7 @@ func (v *defaultValidator) ValidateStruct(obj any) error {
|
|||||||
value := reflect.ValueOf(obj)
|
value := reflect.ValueOf(obj)
|
||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
if value.Elem().Kind() != reflect.Struct {
|
|
||||||
return v.ValidateStruct(value.Elem().Interface())
|
return v.ValidateStruct(value.Elem().Interface())
|
||||||
}
|
|
||||||
return v.validateStruct(obj)
|
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
return v.validateStruct(obj)
|
return v.validateStruct(obj)
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
|
|||||||
28
vendor/github.com/gin-gonic/gin/binding/form_mapping.go
generated
vendored
28
vendor/github.com/gin-gonic/gin/binding/form_mapping.go
generated
vendored
@@ -7,7 +7,6 @@ package binding
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime/multipart"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -165,23 +164,6 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
|
|||||||
return setter.TrySet(value, field, tagValue, setOpt)
|
return setter.TrySet(value, field, tagValue, setOpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
|
||||||
type BindUnmarshaler interface {
|
|
||||||
// UnmarshalParam decodes and assigns a value from an form or query param.
|
|
||||||
UnmarshalParam(param string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// trySetCustom tries to set a custom type value
|
|
||||||
// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true`
|
|
||||||
// to skip the default value setting.
|
|
||||||
func trySetCustom(val string, value reflect.Value) (isSet bool, err error) {
|
|
||||||
switch v := value.Addr().Interface().(type) {
|
|
||||||
case BindUnmarshaler:
|
|
||||||
return true, v.UnmarshalParam(val)
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
|
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
|
||||||
vs, ok := form[tagValue]
|
vs, ok := form[tagValue]
|
||||||
if !ok && !opt.isDefaultExists {
|
if !ok && !opt.isDefaultExists {
|
||||||
@@ -211,9 +193,6 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
if len(vs) > 0 {
|
if len(vs) > 0 {
|
||||||
val = vs[0]
|
val = vs[0]
|
||||||
}
|
}
|
||||||
if ok, err := trySetCustom(val, value); ok {
|
|
||||||
return ok, err
|
|
||||||
}
|
|
||||||
return true, setWithProperType(val, value, field)
|
return true, setWithProperType(val, value, field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,17 +235,10 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
|||||||
switch value.Interface().(type) {
|
switch value.Interface().(type) {
|
||||||
case time.Time:
|
case time.Time:
|
||||||
return setTimeField(val, field, value)
|
return setTimeField(val, field, value)
|
||||||
case multipart.FileHeader:
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
case reflect.Ptr:
|
|
||||||
if !value.Elem().IsValid() {
|
|
||||||
value.Set(reflect.New(value.Type().Elem()))
|
|
||||||
}
|
|
||||||
return setWithProperType(val, value.Elem(), field)
|
|
||||||
default:
|
default:
|
||||||
return errUnknownType
|
return errUnknownType
|
||||||
}
|
}
|
||||||
|
|||||||
52
vendor/github.com/gin-gonic/gin/context.go
generated
vendored
52
vendor/github.com/gin-gonic/gin/context.go
generated
vendored
@@ -43,10 +43,6 @@ const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
|
|||||||
// ContextKey is the key that a Context returns itself for.
|
// ContextKey is the key that a Context returns itself for.
|
||||||
const ContextKey = "_gin-gonic/gin/contextkey"
|
const ContextKey = "_gin-gonic/gin/contextkey"
|
||||||
|
|
||||||
type ContextKeyType int
|
|
||||||
|
|
||||||
const ContextRequestKey ContextKeyType = 0
|
|
||||||
|
|
||||||
// abortIndex represents a typical value used in abort functions.
|
// abortIndex represents a typical value used in abort functions.
|
||||||
const abortIndex int8 = math.MaxInt8 >> 1
|
const abortIndex int8 = math.MaxInt8 >> 1
|
||||||
|
|
||||||
@@ -117,27 +113,20 @@ func (c *Context) Copy() *Context {
|
|||||||
cp := Context{
|
cp := Context{
|
||||||
writermem: c.writermem,
|
writermem: c.writermem,
|
||||||
Request: c.Request,
|
Request: c.Request,
|
||||||
|
Params: c.Params,
|
||||||
engine: c.engine,
|
engine: c.engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
cp.writermem.ResponseWriter = nil
|
cp.writermem.ResponseWriter = nil
|
||||||
cp.Writer = &cp.writermem
|
cp.Writer = &cp.writermem
|
||||||
cp.index = abortIndex
|
cp.index = abortIndex
|
||||||
cp.handlers = nil
|
cp.handlers = nil
|
||||||
cp.fullPath = c.fullPath
|
cp.Keys = map[string]any{}
|
||||||
|
for k, v := range c.Keys {
|
||||||
cKeys := c.Keys
|
|
||||||
cp.Keys = make(map[string]any, len(cKeys))
|
|
||||||
c.mu.RLock()
|
|
||||||
for k, v := range cKeys {
|
|
||||||
cp.Keys[k] = v
|
cp.Keys[k] = v
|
||||||
}
|
}
|
||||||
c.mu.RUnlock()
|
paramCopy := make([]Param, len(cp.Params))
|
||||||
|
copy(paramCopy, cp.Params)
|
||||||
cParams := c.Params
|
cp.Params = paramCopy
|
||||||
cp.Params = make([]Param, len(cParams))
|
|
||||||
copy(cp.Params, cParams)
|
|
||||||
|
|
||||||
return &cp
|
return &cp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +386,7 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
|
|||||||
//
|
//
|
||||||
// router.GET("/user/:id", func(c *gin.Context) {
|
// router.GET("/user/:id", func(c *gin.Context) {
|
||||||
// // a GET request to /user/john
|
// // a GET request to /user/john
|
||||||
// id := c.Param("id") // id == "john"
|
// id := c.Param("id") // id == "/john"
|
||||||
// // a GET request to /user/john/
|
// // a GET request to /user/john/
|
||||||
// id := c.Param("id") // id == "/john/"
|
// id := c.Param("id") // id == "/john/"
|
||||||
// })
|
// })
|
||||||
@@ -739,7 +728,7 @@ func (c *Context) ShouldBindHeader(obj any) error {
|
|||||||
|
|
||||||
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
|
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
|
||||||
func (c *Context) ShouldBindUri(obj any) error {
|
func (c *Context) ShouldBindUri(obj any) error {
|
||||||
m := make(map[string][]string, len(c.Params))
|
m := make(map[string][]string)
|
||||||
for _, v := range c.Params {
|
for _, v := range c.Params {
|
||||||
m[v.Key] = []string{v.Value}
|
m[v.Key] = []string{v.Value}
|
||||||
}
|
}
|
||||||
@@ -774,26 +763,6 @@ func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error
|
|||||||
return bb.BindBody(body, obj)
|
return bb.BindBody(body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON).
|
|
||||||
func (c *Context) ShouldBindBodyWithJSON(obj any) error {
|
|
||||||
return c.ShouldBindBodyWith(obj, binding.JSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldBindBodyWithXML is a shortcut for c.ShouldBindBodyWith(obj, binding.XML).
|
|
||||||
func (c *Context) ShouldBindBodyWithXML(obj any) error {
|
|
||||||
return c.ShouldBindBodyWith(obj, binding.XML)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldBindBodyWithYAML is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML).
|
|
||||||
func (c *Context) ShouldBindBodyWithYAML(obj any) error {
|
|
||||||
return c.ShouldBindBodyWith(obj, binding.YAML)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldBindBodyWithTOML is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML).
|
|
||||||
func (c *Context) ShouldBindBodyWithTOML(obj any) error {
|
|
||||||
return c.ShouldBindBodyWith(obj, binding.TOML)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientIP implements one best effort algorithm to return the real client IP.
|
// ClientIP implements one best effort algorithm to return the real client IP.
|
||||||
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
|
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
|
||||||
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
|
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
|
||||||
@@ -904,9 +873,6 @@ func (c *Context) GetHeader(key string) string {
|
|||||||
|
|
||||||
// GetRawData returns stream data.
|
// GetRawData returns stream data.
|
||||||
func (c *Context) GetRawData() ([]byte, error) {
|
func (c *Context) GetRawData() ([]byte, error) {
|
||||||
if c.Request.Body == nil {
|
|
||||||
return nil, errors.New("cannot read nil body")
|
|
||||||
}
|
|
||||||
return io.ReadAll(c.Request.Body)
|
return io.ReadAll(c.Request.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1249,7 +1215,7 @@ func (c *Context) Err() error {
|
|||||||
// if no value is associated with key. Successive calls to Value with
|
// if no value is associated with key. Successive calls to Value with
|
||||||
// the same key returns the same result.
|
// the same key returns the same result.
|
||||||
func (c *Context) Value(key any) any {
|
func (c *Context) Value(key any) any {
|
||||||
if key == ContextRequestKey {
|
if key == 0 {
|
||||||
return c.Request
|
return c.Request
|
||||||
}
|
}
|
||||||
if key == ContextKey {
|
if key == ContextKey {
|
||||||
|
|||||||
14
vendor/github.com/gin-gonic/gin/debug.go
generated
vendored
14
vendor/github.com/gin-gonic/gin/debug.go
generated
vendored
@@ -23,9 +23,6 @@ func IsDebugging() bool {
|
|||||||
// DebugPrintRouteFunc indicates debug log output format.
|
// DebugPrintRouteFunc indicates debug log output format.
|
||||||
var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
|
var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
|
||||||
|
|
||||||
// DebugPrintFunc indicates debug log output format.
|
|
||||||
var DebugPrintFunc func(format string, values ...interface{})
|
|
||||||
|
|
||||||
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
nuHandlers := len(handlers)
|
nuHandlers := len(handlers)
|
||||||
@@ -51,19 +48,12 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func debugPrint(format string, values ...any) {
|
func debugPrint(format string, values ...any) {
|
||||||
if !IsDebugging() {
|
if IsDebugging() {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if DebugPrintFunc != nil {
|
|
||||||
DebugPrintFunc(format, values...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasSuffix(format, "\n") {
|
if !strings.HasSuffix(format, "\n") {
|
||||||
format += "\n"
|
format += "\n"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
|
fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMinVer(v string) (uint64, error) {
|
func getMinVer(v string) (uint64, error) {
|
||||||
|
|||||||
2
vendor/github.com/gin-gonic/gin/deprecated.go
generated
vendored
2
vendor/github.com/gin-gonic/gin/deprecated.go
generated
vendored
@@ -12,8 +12,6 @@ import (
|
|||||||
|
|
||||||
// BindWith binds the passed struct pointer using the specified binding engine.
|
// BindWith binds the passed struct pointer using the specified binding engine.
|
||||||
// See the binding package.
|
// See the binding package.
|
||||||
//
|
|
||||||
// Deprecated: Use MustBindWith or ShouldBindWith.
|
|
||||||
func (c *Context) BindWith(obj any, b binding.Binding) error {
|
func (c *Context) BindWith(obj any, b binding.Binding) error {
|
||||||
log.Println(`BindWith(\"any, binding.Binding\") error is going to
|
log.Println(`BindWith(\"any, binding.Binding\") error is going to
|
||||||
be deprecated, please check issue #662 and either use MustBindWith() if you
|
be deprecated, please check issue #662 and either use MustBindWith() if you
|
||||||
|
|||||||
33
vendor/github.com/gin-gonic/gin/gin.go
generated
vendored
33
vendor/github.com/gin-gonic/gin/gin.go
generated
vendored
@@ -47,9 +47,6 @@ var regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
|
|||||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||||
type HandlerFunc func(*Context)
|
type HandlerFunc func(*Context)
|
||||||
|
|
||||||
// OptionFunc defines the function to change the default configuration
|
|
||||||
type OptionFunc func(*Engine)
|
|
||||||
|
|
||||||
// HandlersChain defines a HandlerFunc slice.
|
// HandlersChain defines a HandlerFunc slice.
|
||||||
type HandlersChain []HandlerFunc
|
type HandlersChain []HandlerFunc
|
||||||
|
|
||||||
@@ -80,8 +77,6 @@ const (
|
|||||||
// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
|
// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
|
||||||
// the client's IP
|
// the client's IP
|
||||||
PlatformCloudflare = "CF-Connecting-IP"
|
PlatformCloudflare = "CF-Connecting-IP"
|
||||||
// PlatformFlyIO when running on Fly.io. Trust Fly-Client-IP for determining the client's IP
|
|
||||||
PlatformFlyIO = "Fly-Client-IP"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||||
@@ -185,7 +180,7 @@ var _ IRouter = (*Engine)(nil)
|
|||||||
// - ForwardedByClientIP: true
|
// - ForwardedByClientIP: true
|
||||||
// - UseRawPath: false
|
// - UseRawPath: false
|
||||||
// - UnescapePathValues: true
|
// - UnescapePathValues: true
|
||||||
func New(opts ...OptionFunc) *Engine {
|
func New() *Engine {
|
||||||
debugPrintWARNINGNew()
|
debugPrintWARNINGNew()
|
||||||
engine := &Engine{
|
engine := &Engine{
|
||||||
RouterGroup: RouterGroup{
|
RouterGroup: RouterGroup{
|
||||||
@@ -214,15 +209,15 @@ func New(opts ...OptionFunc) *Engine {
|
|||||||
engine.pool.New = func() any {
|
engine.pool.New = func() any {
|
||||||
return engine.allocateContext(engine.maxParams)
|
return engine.allocateContext(engine.maxParams)
|
||||||
}
|
}
|
||||||
return engine.With(opts...)
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
||||||
func Default(opts ...OptionFunc) *Engine {
|
func Default() *Engine {
|
||||||
debugPrintWARNINGDefault()
|
debugPrintWARNINGDefault()
|
||||||
engine := New()
|
engine := New()
|
||||||
engine.Use(Logger(), Recovery())
|
engine.Use(Logger(), Recovery())
|
||||||
return engine.With(opts...)
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) Handler() http.Handler {
|
func (engine *Engine) Handler() http.Handler {
|
||||||
@@ -316,15 +311,6 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
|||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
// With returns a new Engine instance with the provided options.
|
|
||||||
func (engine *Engine) With(opts ...OptionFunc) *Engine {
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(engine)
|
|
||||||
}
|
|
||||||
|
|
||||||
return engine
|
|
||||||
}
|
|
||||||
|
|
||||||
func (engine *Engine) rebuild404Handlers() {
|
func (engine *Engine) rebuild404Handlers() {
|
||||||
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
||||||
}
|
}
|
||||||
@@ -348,6 +334,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
|||||||
}
|
}
|
||||||
root.addRoute(path, handlers)
|
root.addRoute(path, handlers)
|
||||||
|
|
||||||
|
// Update maxParams
|
||||||
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
||||||
engine.maxParams = paramsCount
|
engine.maxParams = paramsCount
|
||||||
}
|
}
|
||||||
@@ -647,25 +634,17 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if engine.HandleMethodNotAllowed {
|
if engine.HandleMethodNotAllowed {
|
||||||
// According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response
|
|
||||||
// containing a list of the target resource's currently supported methods.
|
|
||||||
allowed := make([]string, 0, len(t)-1)
|
|
||||||
for _, tree := range engine.trees {
|
for _, tree := range engine.trees {
|
||||||
if tree.method == httpMethod {
|
if tree.method == httpMethod {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
|
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
|
||||||
allowed = append(allowed, tree.method)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(allowed) > 0 {
|
|
||||||
c.handlers = engine.allNoMethod
|
c.handlers = engine.allNoMethod
|
||||||
c.writermem.Header().Set("Allow", strings.Join(allowed, ", "))
|
|
||||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
c.handlers = engine.allNoRoute
|
c.handlers = engine.allNoRoute
|
||||||
serveError(c, http.StatusNotFound, default404Body)
|
serveError(c, http.StatusNotFound, default404Body)
|
||||||
}
|
}
|
||||||
|
|||||||
17
vendor/github.com/gin-gonic/gin/logger.go
generated
vendored
17
vendor/github.com/gin-gonic/gin/logger.go
generated
vendored
@@ -47,15 +47,8 @@ type LoggerConfig struct {
|
|||||||
// SkipPaths is an url path array which logs are not written.
|
// SkipPaths is an url path array which logs are not written.
|
||||||
// Optional.
|
// Optional.
|
||||||
SkipPaths []string
|
SkipPaths []string
|
||||||
|
|
||||||
// Skip is a Skipper that indicates which logs should not be written.
|
|
||||||
// Optional.
|
|
||||||
Skip Skipper
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skipper is a function to skip logs based on provided Context
|
|
||||||
type Skipper func(c *Context) bool
|
|
||||||
|
|
||||||
// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
|
// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
|
||||||
type LogFormatter func(params LogFormatterParams) string
|
type LogFormatter func(params LogFormatterParams) string
|
||||||
|
|
||||||
@@ -90,8 +83,6 @@ func (p *LogFormatterParams) StatusCodeColor() string {
|
|||||||
code := p.StatusCode
|
code := p.StatusCode
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case code >= http.StatusContinue && code < http.StatusOK:
|
|
||||||
return white
|
|
||||||
case code >= http.StatusOK && code < http.StatusMultipleChoices:
|
case code >= http.StatusOK && code < http.StatusMultipleChoices:
|
||||||
return green
|
return green
|
||||||
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
|
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
|
||||||
@@ -248,11 +239,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
|
|||||||
// Process request
|
// Process request
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|
||||||
// Log only when it is not being skipped
|
// Log only when path is not being skipped
|
||||||
if _, ok := skip[path]; ok || (conf.Skip != nil && conf.Skip(c)) {
|
if _, ok := skip[path]; !ok {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
param := LogFormatterParams{
|
param := LogFormatterParams{
|
||||||
Request: c.Request,
|
Request: c.Request,
|
||||||
isTerm: isTerm,
|
isTerm: isTerm,
|
||||||
@@ -278,4 +266,5 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
|
|||||||
|
|
||||||
fmt.Fprint(out, formatter(param))
|
fmt.Fprint(out, formatter(param))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
vendor/github.com/gin-gonic/gin/render/render.go
generated
vendored
32
vendor/github.com/gin-gonic/gin/render/render.go
generated
vendored
@@ -15,22 +15,22 @@ type Render interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ Render = (*JSON)(nil)
|
_ Render = JSON{}
|
||||||
_ Render = (*IndentedJSON)(nil)
|
_ Render = IndentedJSON{}
|
||||||
_ Render = (*SecureJSON)(nil)
|
_ Render = SecureJSON{}
|
||||||
_ Render = (*JsonpJSON)(nil)
|
_ Render = JsonpJSON{}
|
||||||
_ Render = (*XML)(nil)
|
_ Render = XML{}
|
||||||
_ Render = (*String)(nil)
|
_ Render = String{}
|
||||||
_ Render = (*Redirect)(nil)
|
_ Render = Redirect{}
|
||||||
_ Render = (*Data)(nil)
|
_ Render = Data{}
|
||||||
_ Render = (*HTML)(nil)
|
_ Render = HTML{}
|
||||||
_ HTMLRender = (*HTMLDebug)(nil)
|
_ HTMLRender = HTMLDebug{}
|
||||||
_ HTMLRender = (*HTMLProduction)(nil)
|
_ HTMLRender = HTMLProduction{}
|
||||||
_ Render = (*YAML)(nil)
|
_ Render = YAML{}
|
||||||
_ Render = (*Reader)(nil)
|
_ Render = Reader{}
|
||||||
_ Render = (*AsciiJSON)(nil)
|
_ Render = AsciiJSON{}
|
||||||
_ Render = (*ProtoBuf)(nil)
|
_ Render = ProtoBuf{}
|
||||||
_ Render = (*TOML)(nil)
|
_ Render = TOML{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeContentType(w http.ResponseWriter, value []string) {
|
func writeContentType(w http.ResponseWriter, value []string) {
|
||||||
|
|||||||
2
vendor/github.com/gin-gonic/gin/render/yaml.go
generated
vendored
2
vendor/github.com/gin-gonic/gin/render/yaml.go
generated
vendored
@@ -15,7 +15,7 @@ type YAML struct {
|
|||||||
Data any
|
Data any
|
||||||
}
|
}
|
||||||
|
|
||||||
var yamlContentType = []string{"application/yaml; charset=utf-8"}
|
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
|
||||||
|
|
||||||
// Render (YAML) marshals the given interface object and writes data with custom ContentType.
|
// Render (YAML) marshals the given interface object and writes data with custom ContentType.
|
||||||
func (r YAML) Render(w http.ResponseWriter) error {
|
func (r YAML) Render(w http.ResponseWriter) error {
|
||||||
|
|||||||
43
vendor/github.com/gin-gonic/gin/tree.go
generated
vendored
43
vendor/github.com/gin-gonic/gin/tree.go
generated
vendored
@@ -351,10 +351,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||||
pathSeg := ""
|
pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0]
|
||||||
if len(n.children) != 0 {
|
|
||||||
pathSeg = strings.SplitN(n.children[0].path, "/", 2)[0]
|
|
||||||
}
|
|
||||||
panic("catch-all wildcard '" + path +
|
panic("catch-all wildcard '" + path +
|
||||||
"' in new path '" + fullPath +
|
"' in new path '" + fullPath +
|
||||||
"' conflicts with existing path segment '" + pathSeg +
|
"' conflicts with existing path segment '" + pathSeg +
|
||||||
@@ -481,7 +478,7 @@ walk: // Outer loop for walking the tree
|
|||||||
// We can recommend to redirect to the same URL without a
|
// We can recommend to redirect to the same URL without a
|
||||||
// trailing slash if a leaf exists for that path.
|
// trailing slash if a leaf exists for that path.
|
||||||
value.tsr = path == "/" && n.handlers != nil
|
value.tsr = path == "/" && n.handlers != nil
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle wildcard child, which is always at the end of the array
|
// Handle wildcard child, which is always at the end of the array
|
||||||
@@ -500,14 +497,7 @@ walk: // Outer loop for walking the tree
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save param value
|
// Save param value
|
||||||
if params != nil {
|
if params != nil && cap(*params) > 0 {
|
||||||
// Preallocate capacity if necessary
|
|
||||||
if cap(*params) < int(globalParamsCount) {
|
|
||||||
newParams := make(Params, len(*params), globalParamsCount)
|
|
||||||
copy(newParams, *params)
|
|
||||||
*params = newParams
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.params == nil {
|
if value.params == nil {
|
||||||
value.params = params
|
value.params = params
|
||||||
}
|
}
|
||||||
@@ -536,12 +526,12 @@ walk: // Outer loop for walking the tree
|
|||||||
|
|
||||||
// ... but we can't
|
// ... but we can't
|
||||||
value.tsr = len(path) == end+1
|
value.tsr = len(path) == end+1
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if value.handlers = n.handlers; value.handlers != nil {
|
if value.handlers = n.handlers; value.handlers != nil {
|
||||||
value.fullPath = n.fullPath
|
value.fullPath = n.fullPath
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
if len(n.children) == 1 {
|
if len(n.children) == 1 {
|
||||||
// No handle found. Check if a handle for this path + a
|
// No handle found. Check if a handle for this path + a
|
||||||
@@ -549,18 +539,11 @@ walk: // Outer loop for walking the tree
|
|||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
|
value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
|
||||||
}
|
}
|
||||||
return value
|
return
|
||||||
|
|
||||||
case catchAll:
|
case catchAll:
|
||||||
// Save param value
|
// Save param value
|
||||||
if params != nil {
|
if params != nil {
|
||||||
// Preallocate capacity if necessary
|
|
||||||
if cap(*params) < int(globalParamsCount) {
|
|
||||||
newParams := make(Params, len(*params), globalParamsCount)
|
|
||||||
copy(newParams, *params)
|
|
||||||
*params = newParams
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.params == nil {
|
if value.params == nil {
|
||||||
value.params = params
|
value.params = params
|
||||||
}
|
}
|
||||||
@@ -581,7 +564,7 @@ walk: // Outer loop for walking the tree
|
|||||||
|
|
||||||
value.handlers = n.handlers
|
value.handlers = n.handlers
|
||||||
value.fullPath = n.fullPath
|
value.fullPath = n.fullPath
|
||||||
return value
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic("invalid node type")
|
panic("invalid node type")
|
||||||
@@ -612,7 +595,7 @@ walk: // Outer loop for walking the tree
|
|||||||
// Check if this node has a handle registered.
|
// Check if this node has a handle registered.
|
||||||
if value.handlers = n.handlers; value.handlers != nil {
|
if value.handlers = n.handlers; value.handlers != nil {
|
||||||
value.fullPath = n.fullPath
|
value.fullPath = n.fullPath
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no handle for this route, but this route has a
|
// If there is no handle for this route, but this route has a
|
||||||
@@ -620,12 +603,12 @@ walk: // Outer loop for walking the tree
|
|||||||
// additional trailing slash
|
// additional trailing slash
|
||||||
if path == "/" && n.wildChild && n.nType != root {
|
if path == "/" && n.wildChild && n.nType != root {
|
||||||
value.tsr = true
|
value.tsr = true
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "/" && n.nType == static {
|
if path == "/" && n.nType == static {
|
||||||
value.tsr = true
|
value.tsr = true
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// No handle found. Check if a handle for this path + a
|
// No handle found. Check if a handle for this path + a
|
||||||
@@ -635,11 +618,11 @@ walk: // Outer loop for walking the tree
|
|||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
|
value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
|
||||||
(n.nType == catchAll && n.children[0].handlers != nil)
|
(n.nType == catchAll && n.children[0].handlers != nil)
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing found. We can recommend to redirect to the same URL with an
|
// Nothing found. We can recommend to redirect to the same URL with an
|
||||||
@@ -665,7 +648,7 @@ walk: // Outer loop for walking the tree
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
vendor/github.com/gin-gonic/gin/version.go
generated
vendored
2
vendor/github.com/gin-gonic/gin/version.go
generated
vendored
@@ -5,4 +5,4 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
// Version is the current gin framework's version.
|
// Version is the current gin framework's version.
|
||||||
const Version = "v1.10.0"
|
const Version = "v1.9.1"
|
||||||
|
|||||||
4
vendor/github.com/go-playground/validator/v10/Makefile
generated
vendored
4
vendor/github.com/go-playground/validator/v10/Makefile
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
GOCMD=go
|
GOCMD=GO111MODULE=on go
|
||||||
|
|
||||||
linters-install:
|
linters-install:
|
||||||
@golangci-lint --version >/dev/null 2>&1 || { \
|
@golangci-lint --version >/dev/null 2>&1 || { \
|
||||||
@@ -13,6 +13,6 @@ test:
|
|||||||
$(GOCMD) test -cover -race ./...
|
$(GOCMD) test -cover -race ./...
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
$(GOCMD) test -run=NONE -bench=. -benchmem ./...
|
$(GOCMD) test -bench=. -benchmem ./...
|
||||||
|
|
||||||
.PHONY: test lint linters-install
|
.PHONY: test lint linters-install
|
||||||
141
vendor/github.com/go-playground/validator/v10/README.md
generated
vendored
141
vendor/github.com/go-playground/validator/v10/README.md
generated
vendored
@@ -1,7 +1,7 @@
|
|||||||
Package validator
|
Package validator
|
||||||
=================
|
=================
|
||||||
<img align="right" src="logo.png">[](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v10/logo.png">[](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||

|

|
||||||
[](https://travis-ci.org/go-playground/validator)
|
[](https://travis-ci.org/go-playground/validator)
|
||||||
[](https://coveralls.io/github/go-playground/validator?branch=master)
|
[](https://coveralls.io/github/go-playground/validator?branch=master)
|
||||||
[](https://goreportcard.com/report/github.com/go-playground/validator)
|
[](https://goreportcard.com/report/github.com/go-playground/validator)
|
||||||
@@ -67,12 +67,6 @@ Please see https://pkg.go.dev/github.com/go-playground/validator/v10 for detaile
|
|||||||
Baked-in Validations
|
Baked-in Validations
|
||||||
------
|
------
|
||||||
|
|
||||||
### Special Notes:
|
|
||||||
- If new to using validator it is highly recommended to initialize it using the `WithRequiredStructEnabled` option which is opt-in to new behaviour that will become the default behaviour in v11+. See documentation for more details.
|
|
||||||
```go
|
|
||||||
validate := validator.New(validator.WithRequiredStructEnabled())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields:
|
### Fields:
|
||||||
|
|
||||||
| Tag | Description |
|
| Tag | Description |
|
||||||
@@ -164,7 +158,6 @@ validate := validator.New(validator.WithRequiredStructEnabled())
|
|||||||
| credit_card | Credit Card Number |
|
| credit_card | Credit Card Number |
|
||||||
| mongodb | MongoDB ObjectID |
|
| mongodb | MongoDB ObjectID |
|
||||||
| cron | Cron |
|
| cron | Cron |
|
||||||
| spicedb | SpiceDb ObjectID/Permission/Type |
|
|
||||||
| datetime | Datetime |
|
| datetime | Datetime |
|
||||||
| e164 | e164 formatted phone number |
|
| e164 | e164 formatted phone number |
|
||||||
| email | E-mail String
|
| email | E-mail String
|
||||||
@@ -178,7 +171,6 @@ validate := validator.New(validator.WithRequiredStructEnabled())
|
|||||||
| isbn | International Standard Book Number |
|
| isbn | International Standard Book Number |
|
||||||
| isbn10 | International Standard Book Number 10 |
|
| isbn10 | International Standard Book Number 10 |
|
||||||
| isbn13 | International Standard Book Number 13 |
|
| isbn13 | International Standard Book Number 13 |
|
||||||
| issn | International Standard Serial Number |
|
|
||||||
| iso3166_1_alpha2 | Two-letter country code (ISO 3166-1 alpha-2) |
|
| iso3166_1_alpha2 | Two-letter country code (ISO 3166-1 alpha-2) |
|
||||||
| iso3166_1_alpha3 | Three-letter country code (ISO 3166-1 alpha-3) |
|
| iso3166_1_alpha3 | Three-letter country code (ISO 3166-1 alpha-3) |
|
||||||
| iso3166_1_alpha_numeric | Numeric country code (ISO 3166-1 numeric) |
|
| iso3166_1_alpha_numeric | Numeric country code (ISO 3166-1 numeric) |
|
||||||
@@ -267,72 +259,71 @@ Benchmarks
|
|||||||
------
|
------
|
||||||
###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64
|
###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64
|
||||||
```go
|
```go
|
||||||
go version go1.21.0 darwin/arm64
|
|
||||||
goos: darwin
|
goos: darwin
|
||||||
goarch: arm64
|
goarch: amd64
|
||||||
pkg: github.com/go-playground/validator/v10
|
pkg: github.com/go-playground/validator
|
||||||
BenchmarkFieldSuccess-8 33142266 35.94 ns/op 0 B/op 0 allocs/op
|
BenchmarkFieldSuccess-8 20000000 83.6 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkFieldSuccessParallel-8 200816191 6.568 ns/op 0 B/op 0 allocs/op
|
BenchmarkFieldSuccessParallel-8 50000000 26.8 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkFieldFailure-8 6779707 175.1 ns/op 200 B/op 4 allocs/op
|
BenchmarkFieldFailure-8 5000000 291 ns/op 208 B/op 4 allocs/op
|
||||||
BenchmarkFieldFailureParallel-8 11044147 108.4 ns/op 200 B/op 4 allocs/op
|
BenchmarkFieldFailureParallel-8 20000000 107 ns/op 208 B/op 4 allocs/op
|
||||||
BenchmarkFieldArrayDiveSuccess-8 6054232 194.4 ns/op 97 B/op 5 allocs/op
|
BenchmarkFieldArrayDiveSuccess-8 2000000 623 ns/op 201 B/op 11 allocs/op
|
||||||
BenchmarkFieldArrayDiveSuccessParallel-8 12523388 94.07 ns/op 97 B/op 5 allocs/op
|
BenchmarkFieldArrayDiveSuccessParallel-8 10000000 237 ns/op 201 B/op 11 allocs/op
|
||||||
BenchmarkFieldArrayDiveFailure-8 3587043 334.3 ns/op 300 B/op 10 allocs/op
|
BenchmarkFieldArrayDiveFailure-8 2000000 859 ns/op 412 B/op 16 allocs/op
|
||||||
BenchmarkFieldArrayDiveFailureParallel-8 5816665 200.8 ns/op 300 B/op 10 allocs/op
|
BenchmarkFieldArrayDiveFailureParallel-8 5000000 335 ns/op 413 B/op 16 allocs/op
|
||||||
BenchmarkFieldMapDiveSuccess-8 2217910 540.1 ns/op 288 B/op 14 allocs/op
|
BenchmarkFieldMapDiveSuccess-8 1000000 1292 ns/op 432 B/op 18 allocs/op
|
||||||
BenchmarkFieldMapDiveSuccessParallel-8 4446698 258.7 ns/op 288 B/op 14 allocs/op
|
BenchmarkFieldMapDiveSuccessParallel-8 3000000 467 ns/op 432 B/op 18 allocs/op
|
||||||
BenchmarkFieldMapDiveFailure-8 2392759 504.6 ns/op 376 B/op 13 allocs/op
|
BenchmarkFieldMapDiveFailure-8 1000000 1082 ns/op 512 B/op 16 allocs/op
|
||||||
BenchmarkFieldMapDiveFailureParallel-8 4244199 286.9 ns/op 376 B/op 13 allocs/op
|
BenchmarkFieldMapDiveFailureParallel-8 5000000 425 ns/op 512 B/op 16 allocs/op
|
||||||
BenchmarkFieldMapDiveWithKeysSuccess-8 2005857 592.1 ns/op 288 B/op 14 allocs/op
|
BenchmarkFieldMapDiveWithKeysSuccess-8 1000000 1539 ns/op 480 B/op 21 allocs/op
|
||||||
BenchmarkFieldMapDiveWithKeysSuccessParallel-8 4400850 296.9 ns/op 288 B/op 14 allocs/op
|
BenchmarkFieldMapDiveWithKeysSuccessParallel-8 3000000 613 ns/op 480 B/op 21 allocs/op
|
||||||
BenchmarkFieldMapDiveWithKeysFailure-8 1850227 643.8 ns/op 553 B/op 16 allocs/op
|
BenchmarkFieldMapDiveWithKeysFailure-8 1000000 1413 ns/op 721 B/op 21 allocs/op
|
||||||
BenchmarkFieldMapDiveWithKeysFailureParallel-8 3293233 375.1 ns/op 553 B/op 16 allocs/op
|
BenchmarkFieldMapDiveWithKeysFailureParallel-8 3000000 575 ns/op 721 B/op 21 allocs/op
|
||||||
BenchmarkFieldCustomTypeSuccess-8 12174412 98.25 ns/op 32 B/op 2 allocs/op
|
BenchmarkFieldCustomTypeSuccess-8 10000000 216 ns/op 32 B/op 2 allocs/op
|
||||||
BenchmarkFieldCustomTypeSuccessParallel-8 34389907 35.49 ns/op 32 B/op 2 allocs/op
|
BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.2 ns/op 32 B/op 2 allocs/op
|
||||||
BenchmarkFieldCustomTypeFailure-8 7582524 156.6 ns/op 184 B/op 3 allocs/op
|
BenchmarkFieldCustomTypeFailure-8 5000000 274 ns/op 208 B/op 4 allocs/op
|
||||||
BenchmarkFieldCustomTypeFailureParallel-8 13019902 92.79 ns/op 184 B/op 3 allocs/op
|
BenchmarkFieldCustomTypeFailureParallel-8 20000000 116 ns/op 208 B/op 4 allocs/op
|
||||||
BenchmarkFieldOrTagSuccess-8 3427260 349.4 ns/op 16 B/op 1 allocs/op
|
BenchmarkFieldOrTagSuccess-8 2000000 740 ns/op 16 B/op 1 allocs/op
|
||||||
BenchmarkFieldOrTagSuccessParallel-8 15144128 81.25 ns/op 16 B/op 1 allocs/op
|
BenchmarkFieldOrTagSuccessParallel-8 3000000 474 ns/op 16 B/op 1 allocs/op
|
||||||
BenchmarkFieldOrTagFailure-8 5913546 201.9 ns/op 216 B/op 5 allocs/op
|
BenchmarkFieldOrTagFailure-8 3000000 471 ns/op 224 B/op 5 allocs/op
|
||||||
BenchmarkFieldOrTagFailureParallel-8 9810212 113.7 ns/op 216 B/op 5 allocs/op
|
BenchmarkFieldOrTagFailureParallel-8 3000000 414 ns/op 224 B/op 5 allocs/op
|
||||||
BenchmarkStructLevelValidationSuccess-8 13456327 87.66 ns/op 16 B/op 1 allocs/op
|
BenchmarkStructLevelValidationSuccess-8 10000000 213 ns/op 32 B/op 2 allocs/op
|
||||||
BenchmarkStructLevelValidationSuccessParallel-8 41818888 27.77 ns/op 16 B/op 1 allocs/op
|
BenchmarkStructLevelValidationSuccessParallel-8 20000000 91.8 ns/op 32 B/op 2 allocs/op
|
||||||
BenchmarkStructLevelValidationFailure-8 4166284 272.6 ns/op 264 B/op 7 allocs/op
|
BenchmarkStructLevelValidationFailure-8 3000000 473 ns/op 304 B/op 8 allocs/op
|
||||||
BenchmarkStructLevelValidationFailureParallel-8 7594581 152.1 ns/op 264 B/op 7 allocs/op
|
BenchmarkStructLevelValidationFailureParallel-8 10000000 234 ns/op 304 B/op 8 allocs/op
|
||||||
BenchmarkStructSimpleCustomTypeSuccess-8 6508082 182.6 ns/op 32 B/op 2 allocs/op
|
BenchmarkStructSimpleCustomTypeSuccess-8 5000000 385 ns/op 32 B/op 2 allocs/op
|
||||||
BenchmarkStructSimpleCustomTypeSuccessParallel-8 23078605 54.78 ns/op 32 B/op 2 allocs/op
|
BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 161 ns/op 32 B/op 2 allocs/op
|
||||||
BenchmarkStructSimpleCustomTypeFailure-8 3118352 381.0 ns/op 416 B/op 9 allocs/op
|
BenchmarkStructSimpleCustomTypeFailure-8 2000000 640 ns/op 424 B/op 9 allocs/op
|
||||||
BenchmarkStructSimpleCustomTypeFailureParallel-8 5300738 224.1 ns/op 432 B/op 10 allocs/op
|
BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 318 ns/op 440 B/op 10 allocs/op
|
||||||
BenchmarkStructFilteredSuccess-8 4761807 251.1 ns/op 216 B/op 5 allocs/op
|
BenchmarkStructFilteredSuccess-8 2000000 597 ns/op 288 B/op 9 allocs/op
|
||||||
BenchmarkStructFilteredSuccessParallel-8 8792598 128.6 ns/op 216 B/op 5 allocs/op
|
BenchmarkStructFilteredSuccessParallel-8 10000000 266 ns/op 288 B/op 9 allocs/op
|
||||||
BenchmarkStructFilteredFailure-8 5202573 232.1 ns/op 216 B/op 5 allocs/op
|
BenchmarkStructFilteredFailure-8 3000000 454 ns/op 256 B/op 7 allocs/op
|
||||||
BenchmarkStructFilteredFailureParallel-8 9591267 121.4 ns/op 216 B/op 5 allocs/op
|
BenchmarkStructFilteredFailureParallel-8 10000000 214 ns/op 256 B/op 7 allocs/op
|
||||||
BenchmarkStructPartialSuccess-8 5188512 231.6 ns/op 224 B/op 4 allocs/op
|
BenchmarkStructPartialSuccess-8 3000000 502 ns/op 256 B/op 6 allocs/op
|
||||||
BenchmarkStructPartialSuccessParallel-8 9179776 123.1 ns/op 224 B/op 4 allocs/op
|
BenchmarkStructPartialSuccessParallel-8 10000000 225 ns/op 256 B/op 6 allocs/op
|
||||||
BenchmarkStructPartialFailure-8 3071212 392.5 ns/op 440 B/op 9 allocs/op
|
BenchmarkStructPartialFailure-8 2000000 702 ns/op 480 B/op 11 allocs/op
|
||||||
BenchmarkStructPartialFailureParallel-8 5344261 223.7 ns/op 440 B/op 9 allocs/op
|
BenchmarkStructPartialFailureParallel-8 5000000 329 ns/op 480 B/op 11 allocs/op
|
||||||
BenchmarkStructExceptSuccess-8 3184230 375.0 ns/op 424 B/op 8 allocs/op
|
BenchmarkStructExceptSuccess-8 2000000 793 ns/op 496 B/op 12 allocs/op
|
||||||
BenchmarkStructExceptSuccessParallel-8 10090130 108.9 ns/op 208 B/op 3 allocs/op
|
BenchmarkStructExceptSuccessParallel-8 10000000 193 ns/op 240 B/op 5 allocs/op
|
||||||
BenchmarkStructExceptFailure-8 3347226 357.7 ns/op 424 B/op 8 allocs/op
|
BenchmarkStructExceptFailure-8 2000000 639 ns/op 464 B/op 10 allocs/op
|
||||||
BenchmarkStructExceptFailureParallel-8 5654923 209.5 ns/op 424 B/op 8 allocs/op
|
BenchmarkStructExceptFailureParallel-8 5000000 300 ns/op 464 B/op 10 allocs/op
|
||||||
BenchmarkStructSimpleCrossFieldSuccess-8 5232265 229.1 ns/op 56 B/op 3 allocs/op
|
BenchmarkStructSimpleCrossFieldSuccess-8 3000000 417 ns/op 72 B/op 3 allocs/op
|
||||||
BenchmarkStructSimpleCrossFieldSuccessParallel-8 17436674 64.75 ns/op 56 B/op 3 allocs/op
|
BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 163 ns/op 72 B/op 3 allocs/op
|
||||||
BenchmarkStructSimpleCrossFieldFailure-8 3128613 383.6 ns/op 272 B/op 8 allocs/op
|
BenchmarkStructSimpleCrossFieldFailure-8 2000000 645 ns/op 304 B/op 8 allocs/op
|
||||||
BenchmarkStructSimpleCrossFieldFailureParallel-8 6994113 168.8 ns/op 272 B/op 8 allocs/op
|
BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 285 ns/op 304 B/op 8 allocs/op
|
||||||
BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 3506487 340.9 ns/op 64 B/op 4 allocs/op
|
BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 3000000 588 ns/op 80 B/op 4 allocs/op
|
||||||
BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 13431300 91.77 ns/op 64 B/op 4 allocs/op
|
BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 221 ns/op 80 B/op 4 allocs/op
|
||||||
BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2410566 500.9 ns/op 288 B/op 9 allocs/op
|
BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 868 ns/op 320 B/op 9 allocs/op
|
||||||
BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 6344510 188.2 ns/op 288 B/op 9 allocs/op
|
BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 337 ns/op 320 B/op 9 allocs/op
|
||||||
BenchmarkStructSimpleSuccess-8 8922726 133.8 ns/op 0 B/op 0 allocs/op
|
BenchmarkStructSimpleSuccess-8 5000000 260 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkStructSimpleSuccessParallel-8 55291153 23.63 ns/op 0 B/op 0 allocs/op
|
BenchmarkStructSimpleSuccessParallel-8 20000000 90.6 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkStructSimpleFailure-8 3171553 378.4 ns/op 416 B/op 9 allocs/op
|
BenchmarkStructSimpleFailure-8 2000000 619 ns/op 424 B/op 9 allocs/op
|
||||||
BenchmarkStructSimpleFailureParallel-8 5571692 212.0 ns/op 416 B/op 9 allocs/op
|
BenchmarkStructSimpleFailureParallel-8 5000000 296 ns/op 424 B/op 9 allocs/op
|
||||||
BenchmarkStructComplexSuccess-8 1683750 714.5 ns/op 224 B/op 5 allocs/op
|
BenchmarkStructComplexSuccess-8 1000000 1454 ns/op 128 B/op 8 allocs/op
|
||||||
BenchmarkStructComplexSuccessParallel-8 4578046 257.0 ns/op 224 B/op 5 allocs/op
|
BenchmarkStructComplexSuccessParallel-8 3000000 579 ns/op 128 B/op 8 allocs/op
|
||||||
BenchmarkStructComplexFailure-8 481585 2547 ns/op 3041 B/op 48 allocs/op
|
BenchmarkStructComplexFailure-8 300000 4140 ns/op 3041 B/op 53 allocs/op
|
||||||
BenchmarkStructComplexFailureParallel-8 965764 1577 ns/op 3040 B/op 48 allocs/op
|
BenchmarkStructComplexFailureParallel-8 1000000 2127 ns/op 3041 B/op 53 allocs/op
|
||||||
BenchmarkOneof-8 17380881 68.50 ns/op 0 B/op 0 allocs/op
|
BenchmarkOneof-8 10000000 140 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkOneofParallel-8 8084733 153.5 ns/op 0 B/op 0 allocs/op
|
BenchmarkOneofParallel-8 20000000 70.1 ns/op 0 B/op 0 allocs/op
|
||||||
```
|
```
|
||||||
|
|
||||||
Complementary Software
|
Complementary Software
|
||||||
|
|||||||
229
vendor/github.com/go-playground/validator/v10/baked_in.go
generated
vendored
229
vendor/github.com/go-playground/validator/v10/baked_in.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
|||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
urn "github.com/leodido/go-urn"
|
"github.com/leodido/go-urn"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Func accepts a FieldLevel interface for all validation needs. The return
|
// Func accepts a FieldLevel interface for all validation needs. The return
|
||||||
@@ -51,7 +51,6 @@ var (
|
|||||||
endKeysTag: {},
|
endKeysTag: {},
|
||||||
structOnlyTag: {},
|
structOnlyTag: {},
|
||||||
omitempty: {},
|
omitempty: {},
|
||||||
omitnil: {},
|
|
||||||
skipValidationTag: {},
|
skipValidationTag: {},
|
||||||
utf8HexComma: {},
|
utf8HexComma: {},
|
||||||
utf8Pipe: {},
|
utf8Pipe: {},
|
||||||
@@ -66,7 +65,6 @@ var (
|
|||||||
bakedInAliases = map[string]string{
|
bakedInAliases = map[string]string{
|
||||||
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
|
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
|
||||||
"country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric",
|
"country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric",
|
||||||
"eu_country_code": "iso3166_1_alpha2_eu|iso3166_1_alpha3_eu|iso3166_1_alpha_numeric_eu",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// bakedInValidators is the default map of ValidationFunc
|
// bakedInValidators is the default map of ValidationFunc
|
||||||
@@ -134,7 +132,6 @@ var (
|
|||||||
"urn_rfc2141": isUrnRFC2141, // RFC 2141
|
"urn_rfc2141": isUrnRFC2141, // RFC 2141
|
||||||
"file": isFile,
|
"file": isFile,
|
||||||
"filepath": isFilePath,
|
"filepath": isFilePath,
|
||||||
"base32": isBase32,
|
|
||||||
"base64": isBase64,
|
"base64": isBase64,
|
||||||
"base64url": isBase64URL,
|
"base64url": isBase64URL,
|
||||||
"base64rawurl": isBase64RawURL,
|
"base64rawurl": isBase64RawURL,
|
||||||
@@ -152,7 +149,6 @@ var (
|
|||||||
"isbn": isISBN,
|
"isbn": isISBN,
|
||||||
"isbn10": isISBN10,
|
"isbn10": isISBN10,
|
||||||
"isbn13": isISBN13,
|
"isbn13": isISBN13,
|
||||||
"issn": isISSN,
|
|
||||||
"eth_addr": isEthereumAddress,
|
"eth_addr": isEthereumAddress,
|
||||||
"eth_addr_checksum": isEthereumAddressChecksum,
|
"eth_addr_checksum": isEthereumAddressChecksum,
|
||||||
"btc_addr": isBitcoinAddress,
|
"btc_addr": isBitcoinAddress,
|
||||||
@@ -218,11 +214,8 @@ var (
|
|||||||
"datetime": isDatetime,
|
"datetime": isDatetime,
|
||||||
"timezone": isTimeZone,
|
"timezone": isTimeZone,
|
||||||
"iso3166_1_alpha2": isIso3166Alpha2,
|
"iso3166_1_alpha2": isIso3166Alpha2,
|
||||||
"iso3166_1_alpha2_eu": isIso3166Alpha2EU,
|
|
||||||
"iso3166_1_alpha3": isIso3166Alpha3,
|
"iso3166_1_alpha3": isIso3166Alpha3,
|
||||||
"iso3166_1_alpha3_eu": isIso3166Alpha3EU,
|
|
||||||
"iso3166_1_alpha_numeric": isIso3166AlphaNumeric,
|
"iso3166_1_alpha_numeric": isIso3166AlphaNumeric,
|
||||||
"iso3166_1_alpha_numeric_eu": isIso3166AlphaNumericEU,
|
|
||||||
"iso3166_2": isIso31662,
|
"iso3166_2": isIso31662,
|
||||||
"iso4217": isIso4217,
|
"iso4217": isIso4217,
|
||||||
"iso4217_numeric": isIso4217Numeric,
|
"iso4217_numeric": isIso4217Numeric,
|
||||||
@@ -237,7 +230,6 @@ var (
|
|||||||
"luhn_checksum": hasLuhnChecksum,
|
"luhn_checksum": hasLuhnChecksum,
|
||||||
"mongodb": isMongoDB,
|
"mongodb": isMongoDB,
|
||||||
"cron": isCron,
|
"cron": isCron,
|
||||||
"spicedb": isSpiceDB,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -380,9 +372,9 @@ func isMAC(fl FieldLevel) bool {
|
|||||||
|
|
||||||
// isCIDRv4 is the validation function for validating if the field's value is a valid v4 CIDR address.
|
// isCIDRv4 is the validation function for validating if the field's value is a valid v4 CIDR address.
|
||||||
func isCIDRv4(fl FieldLevel) bool {
|
func isCIDRv4(fl FieldLevel) bool {
|
||||||
ip, net, err := net.ParseCIDR(fl.Field().String())
|
ip, _, err := net.ParseCIDR(fl.Field().String())
|
||||||
|
|
||||||
return err == nil && ip.To4() != nil && net.IP.Equal(ip)
|
return err == nil && ip.To4() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isCIDRv6 is the validation function for validating if the field's value is a valid v6 CIDR address.
|
// isCIDRv6 is the validation function for validating if the field's value is a valid v6 CIDR address.
|
||||||
@@ -515,47 +507,47 @@ func isASCII(fl FieldLevel) bool {
|
|||||||
|
|
||||||
// isUUID5 is the validation function for validating if the field's value is a valid v5 UUID.
|
// isUUID5 is the validation function for validating if the field's value is a valid v5 UUID.
|
||||||
func isUUID5(fl FieldLevel) bool {
|
func isUUID5(fl FieldLevel) bool {
|
||||||
return fieldMatchesRegexByStringerValOrString(uUID5Regex, fl)
|
return uUID5Regex.MatchString(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUUID4 is the validation function for validating if the field's value is a valid v4 UUID.
|
// isUUID4 is the validation function for validating if the field's value is a valid v4 UUID.
|
||||||
func isUUID4(fl FieldLevel) bool {
|
func isUUID4(fl FieldLevel) bool {
|
||||||
return fieldMatchesRegexByStringerValOrString(uUID4Regex, fl)
|
return uUID4Regex.MatchString(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUUID3 is the validation function for validating if the field's value is a valid v3 UUID.
|
// isUUID3 is the validation function for validating if the field's value is a valid v3 UUID.
|
||||||
func isUUID3(fl FieldLevel) bool {
|
func isUUID3(fl FieldLevel) bool {
|
||||||
return fieldMatchesRegexByStringerValOrString(uUID3Regex, fl)
|
return uUID3Regex.MatchString(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUUID is the validation function for validating if the field's value is a valid UUID of any version.
|
// isUUID is the validation function for validating if the field's value is a valid UUID of any version.
|
||||||
func isUUID(fl FieldLevel) bool {
|
func isUUID(fl FieldLevel) bool {
|
||||||
return fieldMatchesRegexByStringerValOrString(uUIDRegex, fl)
|
return uUIDRegex.MatchString(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUUID5RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v5 UUID.
|
// isUUID5RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v5 UUID.
|
||||||
func isUUID5RFC4122(fl FieldLevel) bool {
|
func isUUID5RFC4122(fl FieldLevel) bool {
|
||||||
return fieldMatchesRegexByStringerValOrString(uUID5RFC4122Regex, fl)
|
return uUID5RFC4122Regex.MatchString(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUUID4RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v4 UUID.
|
// isUUID4RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v4 UUID.
|
||||||
func isUUID4RFC4122(fl FieldLevel) bool {
|
func isUUID4RFC4122(fl FieldLevel) bool {
|
||||||
return fieldMatchesRegexByStringerValOrString(uUID4RFC4122Regex, fl)
|
return uUID4RFC4122Regex.MatchString(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUUID3RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v3 UUID.
|
// isUUID3RFC4122 is the validation function for validating if the field's value is a valid RFC4122 v3 UUID.
|
||||||
func isUUID3RFC4122(fl FieldLevel) bool {
|
func isUUID3RFC4122(fl FieldLevel) bool {
|
||||||
return fieldMatchesRegexByStringerValOrString(uUID3RFC4122Regex, fl)
|
return uUID3RFC4122Regex.MatchString(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUUIDRFC4122 is the validation function for validating if the field's value is a valid RFC4122 UUID of any version.
|
// isUUIDRFC4122 is the validation function for validating if the field's value is a valid RFC4122 UUID of any version.
|
||||||
func isUUIDRFC4122(fl FieldLevel) bool {
|
func isUUIDRFC4122(fl FieldLevel) bool {
|
||||||
return fieldMatchesRegexByStringerValOrString(uUIDRFC4122Regex, fl)
|
return uUIDRFC4122Regex.MatchString(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isULID is the validation function for validating if the field's value is a valid ULID.
|
// isULID is the validation function for validating if the field's value is a valid ULID.
|
||||||
func isULID(fl FieldLevel) bool {
|
func isULID(fl FieldLevel) bool {
|
||||||
return fieldMatchesRegexByStringerValOrString(uLIDRegex, fl)
|
return uLIDRegex.MatchString(fl.Field().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isMD4 is the validation function for validating if the field's value is a valid MD4.
|
// isMD4 is the validation function for validating if the field's value is a valid MD4.
|
||||||
@@ -657,32 +649,6 @@ func isISBN10(fl FieldLevel) bool {
|
|||||||
return checksum%11 == 0
|
return checksum%11 == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// isISSN is the validation function for validating if the field's value is a valid ISSN.
|
|
||||||
func isISSN(fl FieldLevel) bool {
|
|
||||||
s := fl.Field().String()
|
|
||||||
|
|
||||||
if !iSSNRegex.MatchString(s) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
s = strings.ReplaceAll(s, "-", "")
|
|
||||||
|
|
||||||
pos := 8
|
|
||||||
checksum := 0
|
|
||||||
|
|
||||||
for i := 0; i < 7; i++ {
|
|
||||||
checksum += pos * int(s[i]-'0')
|
|
||||||
pos--
|
|
||||||
}
|
|
||||||
|
|
||||||
if s[7] == 'X' {
|
|
||||||
checksum += 10
|
|
||||||
} else {
|
|
||||||
checksum += int(s[7] - '0')
|
|
||||||
}
|
|
||||||
|
|
||||||
return checksum%11 == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// isEthereumAddress is the validation function for validating if the field's value is a valid Ethereum address.
|
// isEthereumAddress is the validation function for validating if the field's value is a valid Ethereum address.
|
||||||
func isEthereumAddress(fl FieldLevel) bool {
|
func isEthereumAddress(fl FieldLevel) bool {
|
||||||
address := fl.Field().String()
|
address := fl.Field().String()
|
||||||
@@ -1328,13 +1294,8 @@ func isEq(fl FieldLevel) bool {
|
|||||||
|
|
||||||
return field.Uint() == p
|
return field.Uint() == p
|
||||||
|
|
||||||
case reflect.Float32:
|
case reflect.Float32, reflect.Float64:
|
||||||
p := asFloat32(param)
|
p := asFloat(param)
|
||||||
|
|
||||||
return field.Float() == p
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
p := asFloat64(param)
|
|
||||||
|
|
||||||
return field.Float() == p
|
return field.Float() == p
|
||||||
|
|
||||||
@@ -1404,11 +1365,6 @@ func isPostcodeByIso3166Alpha2Field(fl FieldLevel) bool {
|
|||||||
return reg.MatchString(field.String())
|
return reg.MatchString(field.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// isBase32 is the validation function for validating if the current field's value is a valid base 32.
|
|
||||||
func isBase32(fl FieldLevel) bool {
|
|
||||||
return base32Regex.MatchString(fl.Field().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// isBase64 is the validation function for validating if the current field's value is a valid base 64.
|
// isBase64 is the validation function for validating if the current field's value is a valid base 64.
|
||||||
func isBase64(fl FieldLevel) bool {
|
func isBase64(fl FieldLevel) bool {
|
||||||
return base64Regex.MatchString(fl.Field().String())
|
return base64Regex.MatchString(fl.Field().String())
|
||||||
@@ -1451,15 +1407,6 @@ func isURI(fl FieldLevel) bool {
|
|||||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// isFileURL is the helper function for validating if the `path` valid file URL as per RFC8089
|
|
||||||
func isFileURL(path string) bool {
|
|
||||||
if !strings.HasPrefix(path, "file:/") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, err := url.ParseRequestURI(path)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isURL is the validation function for validating if the current field's value is a valid URL.
|
// isURL is the validation function for validating if the current field's value is a valid URL.
|
||||||
func isURL(fl FieldLevel) bool {
|
func isURL(fl FieldLevel) bool {
|
||||||
field := fl.Field()
|
field := fl.Field()
|
||||||
@@ -1467,25 +1414,25 @@ func isURL(fl FieldLevel) bool {
|
|||||||
switch field.Kind() {
|
switch field.Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
|
|
||||||
s := strings.ToLower(field.String())
|
var i int
|
||||||
|
s := field.String()
|
||||||
|
|
||||||
|
// checks needed as of Go 1.6 because of change https://github.com/golang/go/commit/617c93ce740c3c3cc28cdd1a0d712be183d0b328#diff-6c2d018290e298803c0c9419d8739885L195
|
||||||
|
// emulate browser and strip the '#' suffix prior to validation. see issue-#237
|
||||||
|
if i = strings.Index(s, "#"); i > -1 {
|
||||||
|
s = s[:i]
|
||||||
|
}
|
||||||
|
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if isFileURL(s) {
|
url, err := url.ParseRequestURI(s)
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := url.Parse(s)
|
|
||||||
if err != nil || url.Scheme == "" {
|
if err != nil || url.Scheme == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if url.Host == "" && url.Fragment == "" && url.Opaque == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1503,13 +1450,7 @@ func isHttpURL(fl FieldLevel) bool {
|
|||||||
case reflect.String:
|
case reflect.String:
|
||||||
|
|
||||||
s := strings.ToLower(field.String())
|
s := strings.ToLower(field.String())
|
||||||
|
return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
|
||||||
url, err := url.Parse(s)
|
|
||||||
if err != nil || url.Host == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.Scheme == "http" || url.Scheme == "https"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||||
@@ -1618,10 +1559,6 @@ func isFilePath(fl FieldLevel) bool {
|
|||||||
|
|
||||||
field := fl.Field()
|
field := fl.Field()
|
||||||
|
|
||||||
// Not valid if it is a directory.
|
|
||||||
if isDir(fl) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// If it exists, it obviously is valid.
|
// If it exists, it obviously is valid.
|
||||||
// This is done first to avoid code duplication and unnecessary additional logic.
|
// This is done first to avoid code duplication and unnecessary additional logic.
|
||||||
if exists = isFile(fl); exists {
|
if exists = isFile(fl); exists {
|
||||||
@@ -1771,7 +1708,7 @@ func hasValue(fl FieldLevel) bool {
|
|||||||
if fl.(*validate).fldIsPointer && field.Interface() != nil {
|
if fl.(*validate).fldIsPointer && field.Interface() != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return field.IsValid() && !field.IsZero()
|
return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1795,7 +1732,7 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo
|
|||||||
if nullable && field.Interface() != nil {
|
if nullable && field.Interface() != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return field.IsValid() && field.IsZero()
|
return field.IsValid() && field.Interface() == reflect.Zero(field.Type()).Interface()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1816,11 +1753,8 @@ func requireCheckFieldValue(
|
|||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
return field.Uint() == asUint(value)
|
return field.Uint() == asUint(value)
|
||||||
|
|
||||||
case reflect.Float32:
|
case reflect.Float32, reflect.Float64:
|
||||||
return field.Float() == asFloat32(value)
|
return field.Float() == asFloat(value)
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
return field.Float() == asFloat64(value)
|
|
||||||
|
|
||||||
case reflect.Slice, reflect.Map, reflect.Array:
|
case reflect.Slice, reflect.Map, reflect.Array:
|
||||||
return int64(field.Len()) == asInt(value)
|
return int64(field.Len()) == asInt(value)
|
||||||
@@ -2119,13 +2053,8 @@ func isGte(fl FieldLevel) bool {
|
|||||||
|
|
||||||
return field.Uint() >= p
|
return field.Uint() >= p
|
||||||
|
|
||||||
case reflect.Float32:
|
case reflect.Float32, reflect.Float64:
|
||||||
p := asFloat32(param)
|
p := asFloat(param)
|
||||||
|
|
||||||
return field.Float() >= p
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
p := asFloat64(param)
|
|
||||||
|
|
||||||
return field.Float() >= p
|
return field.Float() >= p
|
||||||
|
|
||||||
@@ -2170,16 +2099,10 @@ func isGt(fl FieldLevel) bool {
|
|||||||
|
|
||||||
return field.Uint() > p
|
return field.Uint() > p
|
||||||
|
|
||||||
case reflect.Float32:
|
case reflect.Float32, reflect.Float64:
|
||||||
p := asFloat32(param)
|
p := asFloat(param)
|
||||||
|
|
||||||
return field.Float() > p
|
return field.Float() > p
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
p := asFloat64(param)
|
|
||||||
|
|
||||||
return field.Float() > p
|
|
||||||
|
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
|
|
||||||
if field.Type().ConvertibleTo(timeType) {
|
if field.Type().ConvertibleTo(timeType) {
|
||||||
@@ -2218,13 +2141,8 @@ func hasLengthOf(fl FieldLevel) bool {
|
|||||||
|
|
||||||
return field.Uint() == p
|
return field.Uint() == p
|
||||||
|
|
||||||
case reflect.Float32:
|
case reflect.Float32, reflect.Float64:
|
||||||
p := asFloat32(param)
|
p := asFloat(param)
|
||||||
|
|
||||||
return field.Float() == p
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
p := asFloat64(param)
|
|
||||||
|
|
||||||
return field.Float() == p
|
return field.Float() == p
|
||||||
}
|
}
|
||||||
@@ -2356,13 +2274,8 @@ func isLte(fl FieldLevel) bool {
|
|||||||
|
|
||||||
return field.Uint() <= p
|
return field.Uint() <= p
|
||||||
|
|
||||||
case reflect.Float32:
|
case reflect.Float32, reflect.Float64:
|
||||||
p := asFloat32(param)
|
p := asFloat(param)
|
||||||
|
|
||||||
return field.Float() <= p
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
p := asFloat64(param)
|
|
||||||
|
|
||||||
return field.Float() <= p
|
return field.Float() <= p
|
||||||
|
|
||||||
@@ -2407,13 +2320,8 @@ func isLt(fl FieldLevel) bool {
|
|||||||
|
|
||||||
return field.Uint() < p
|
return field.Uint() < p
|
||||||
|
|
||||||
case reflect.Float32:
|
case reflect.Float32, reflect.Float64:
|
||||||
p := asFloat32(param)
|
p := asFloat(param)
|
||||||
|
|
||||||
return field.Float() < p
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
p := asFloat64(param)
|
|
||||||
|
|
||||||
return field.Float() < p
|
return field.Float() < p
|
||||||
|
|
||||||
@@ -2660,17 +2568,9 @@ func isDirPath(fl FieldLevel) bool {
|
|||||||
func isJSON(fl FieldLevel) bool {
|
func isJSON(fl FieldLevel) bool {
|
||||||
field := fl.Field()
|
field := fl.Field()
|
||||||
|
|
||||||
switch field.Kind() {
|
if field.Kind() == reflect.String {
|
||||||
case reflect.String:
|
|
||||||
val := field.String()
|
val := field.String()
|
||||||
return json.Valid([]byte(val))
|
return json.Valid([]byte(val))
|
||||||
case reflect.Slice:
|
|
||||||
fieldType := field.Type()
|
|
||||||
|
|
||||||
if fieldType.ConvertibleTo(byteSliceType) {
|
|
||||||
b := field.Convert(byteSliceType).Interface().([]byte)
|
|
||||||
return json.Valid(b)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||||
@@ -2772,24 +2672,12 @@ func isIso3166Alpha2(fl FieldLevel) bool {
|
|||||||
return iso3166_1_alpha2[val]
|
return iso3166_1_alpha2[val]
|
||||||
}
|
}
|
||||||
|
|
||||||
// isIso3166Alpha2EU is the validation function for validating if the current field's value is a valid iso3166-1 alpha-2 European Union country code.
|
|
||||||
func isIso3166Alpha2EU(fl FieldLevel) bool {
|
|
||||||
val := fl.Field().String()
|
|
||||||
return iso3166_1_alpha2_eu[val]
|
|
||||||
}
|
|
||||||
|
|
||||||
// isIso3166Alpha3 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 country code.
|
// isIso3166Alpha3 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 country code.
|
||||||
func isIso3166Alpha3(fl FieldLevel) bool {
|
func isIso3166Alpha3(fl FieldLevel) bool {
|
||||||
val := fl.Field().String()
|
val := fl.Field().String()
|
||||||
return iso3166_1_alpha3[val]
|
return iso3166_1_alpha3[val]
|
||||||
}
|
}
|
||||||
|
|
||||||
// isIso3166Alpha3EU is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 European Union country code.
|
|
||||||
func isIso3166Alpha3EU(fl FieldLevel) bool {
|
|
||||||
val := fl.Field().String()
|
|
||||||
return iso3166_1_alpha3_eu[val]
|
|
||||||
}
|
|
||||||
|
|
||||||
// isIso3166AlphaNumeric is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric country code.
|
// isIso3166AlphaNumeric is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric country code.
|
||||||
func isIso3166AlphaNumeric(fl FieldLevel) bool {
|
func isIso3166AlphaNumeric(fl FieldLevel) bool {
|
||||||
field := fl.Field()
|
field := fl.Field()
|
||||||
@@ -2812,28 +2700,6 @@ func isIso3166AlphaNumeric(fl FieldLevel) bool {
|
|||||||
return iso3166_1_alpha_numeric[code]
|
return iso3166_1_alpha_numeric[code]
|
||||||
}
|
}
|
||||||
|
|
||||||
// isIso3166AlphaNumericEU is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric European Union country code.
|
|
||||||
func isIso3166AlphaNumericEU(fl FieldLevel) bool {
|
|
||||||
field := fl.Field()
|
|
||||||
|
|
||||||
var code int
|
|
||||||
switch field.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
i, err := strconv.Atoi(field.String())
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
code = i % 1000
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
code = int(field.Int() % 1000)
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
code = int(field.Uint() % 1000)
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
|
||||||
}
|
|
||||||
return iso3166_1_alpha_numeric_eu[code]
|
|
||||||
}
|
|
||||||
|
|
||||||
// isIso31662 is the validation function for validating if the current field's value is a valid iso3166-2 code.
|
// isIso31662 is the validation function for validating if the current field's value is a valid iso3166-2 code.
|
||||||
func isIso31662(fl FieldLevel) bool {
|
func isIso31662(fl FieldLevel) bool {
|
||||||
val := fl.Field().String()
|
val := fl.Field().String()
|
||||||
@@ -2932,23 +2798,6 @@ func isMongoDB(fl FieldLevel) bool {
|
|||||||
return mongodbRegex.MatchString(val)
|
return mongodbRegex.MatchString(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isSpiceDB is the validation function for validating if the current field's value is valid for use with Authzed SpiceDB in the indicated way
|
|
||||||
func isSpiceDB(fl FieldLevel) bool {
|
|
||||||
val := fl.Field().String()
|
|
||||||
param := fl.Param()
|
|
||||||
|
|
||||||
switch param {
|
|
||||||
case "permission":
|
|
||||||
return spicedbPermissionRegex.MatchString(val)
|
|
||||||
case "type":
|
|
||||||
return spicedbTypeRegex.MatchString(val)
|
|
||||||
case "id", "":
|
|
||||||
return spicedbIDRegex.MatchString(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("Unrecognized parameter: " + param)
|
|
||||||
}
|
|
||||||
|
|
||||||
// isCreditCard is the validation function for validating if the current field's value is a valid credit card number
|
// isCreditCard is the validation function for validating if the current field's value is a valid credit card number
|
||||||
func isCreditCard(fl FieldLevel) bool {
|
func isCreditCard(fl FieldLevel) bool {
|
||||||
val := fl.Field().String()
|
val := fl.Field().String()
|
||||||
|
|||||||
7
vendor/github.com/go-playground/validator/v10/cache.go
generated
vendored
7
vendor/github.com/go-playground/validator/v10/cache.go
generated
vendored
@@ -20,7 +20,6 @@ const (
|
|||||||
typeOr
|
typeOr
|
||||||
typeKeys
|
typeKeys
|
||||||
typeEndKeys
|
typeEndKeys
|
||||||
typeOmitNil
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -126,7 +125,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
|
|||||||
|
|
||||||
fld = typ.Field(i)
|
fld = typ.Field(i)
|
||||||
|
|
||||||
if !v.privateFieldValidation && !fld.Anonymous && len(fld.PkgPath) > 0 {
|
if !fld.Anonymous && len(fld.PkgPath) > 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,10 +252,6 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
|
|||||||
current.typeof = typeOmitEmpty
|
current.typeof = typeOmitEmpty
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case omitnil:
|
|
||||||
current.typeof = typeOmitNil
|
|
||||||
continue
|
|
||||||
|
|
||||||
case structOnlyTag:
|
case structOnlyTag:
|
||||||
current.typeof = typeStructOnly
|
current.typeof = typeStructOnly
|
||||||
continue
|
continue
|
||||||
|
|||||||
27
vendor/github.com/go-playground/validator/v10/country_codes.go
generated
vendored
27
vendor/github.com/go-playground/validator/v10/country_codes.go
generated
vendored
@@ -54,15 +54,6 @@ var iso3166_1_alpha2 = map[string]bool{
|
|||||||
"EH": true, "YE": true, "ZM": true, "ZW": true, "XK": true,
|
"EH": true, "YE": true, "ZM": true, "ZW": true, "XK": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var iso3166_1_alpha2_eu = map[string]bool{
|
|
||||||
"AT": true, "BE": true, "BG": true, "HR": true, "CY": true,
|
|
||||||
"CZ": true, "DK": true, "EE": true, "FI": true, "FR": true,
|
|
||||||
"DE": true, "GR": true, "HU": true, "IE": true, "IT": true,
|
|
||||||
"LV": true, "LT": true, "LU": true, "MT": true, "NL": true,
|
|
||||||
"PL": true, "PT": true, "RO": true, "SK": true, "SI": true,
|
|
||||||
"ES": true, "SE": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var iso3166_1_alpha3 = map[string]bool{
|
var iso3166_1_alpha3 = map[string]bool{
|
||||||
// see: https://www.iso.org/iso-3166-country-codes.html
|
// see: https://www.iso.org/iso-3166-country-codes.html
|
||||||
"AFG": true, "ALB": true, "DZA": true, "ASM": true, "AND": true,
|
"AFG": true, "ALB": true, "DZA": true, "ASM": true, "AND": true,
|
||||||
@@ -116,15 +107,6 @@ var iso3166_1_alpha3 = map[string]bool{
|
|||||||
"VNM": true, "VGB": true, "VIR": true, "WLF": true, "ESH": true,
|
"VNM": true, "VGB": true, "VIR": true, "WLF": true, "ESH": true,
|
||||||
"YEM": true, "ZMB": true, "ZWE": true, "ALA": true, "UNK": true,
|
"YEM": true, "ZMB": true, "ZWE": true, "ALA": true, "UNK": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var iso3166_1_alpha3_eu = map[string]bool{
|
|
||||||
"AUT": true, "BEL": true, "BGR": true, "HRV": true, "CYP": true,
|
|
||||||
"CZE": true, "DNK": true, "EST": true, "FIN": true, "FRA": true,
|
|
||||||
"DEU": true, "GRC": true, "HUN": true, "IRL": true, "ITA": true,
|
|
||||||
"LVA": true, "LTU": true, "LUX": true, "MLT": true, "NLD": true,
|
|
||||||
"POL": true, "PRT": true, "ROU": true, "SVK": true, "SVN": true,
|
|
||||||
"ESP": true, "SWE": true,
|
|
||||||
}
|
|
||||||
var iso3166_1_alpha_numeric = map[int]bool{
|
var iso3166_1_alpha_numeric = map[int]bool{
|
||||||
// see: https://www.iso.org/iso-3166-country-codes.html
|
// see: https://www.iso.org/iso-3166-country-codes.html
|
||||||
4: true, 8: true, 12: true, 16: true, 20: true,
|
4: true, 8: true, 12: true, 16: true, 20: true,
|
||||||
@@ -179,15 +161,6 @@ var iso3166_1_alpha_numeric = map[int]bool{
|
|||||||
887: true, 894: true, 716: true, 248: true, 153: true,
|
887: true, 894: true, 716: true, 248: true, 153: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var iso3166_1_alpha_numeric_eu = map[int]bool{
|
|
||||||
40: true, 56: true, 100: true, 191: true, 196: true,
|
|
||||||
200: true, 208: true, 233: true, 246: true, 250: true,
|
|
||||||
276: true, 300: true, 348: true, 372: true, 380: true,
|
|
||||||
428: true, 440: true, 442: true, 470: true, 528: true,
|
|
||||||
616: true, 620: true, 642: true, 703: true, 705: true,
|
|
||||||
724: true, 752: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var iso3166_2 = map[string]bool{
|
var iso3166_2 = map[string]bool{
|
||||||
"AD-02": true, "AD-03": true, "AD-04": true, "AD-05": true, "AD-06": true,
|
"AD-02": true, "AD-03": true, "AD-04": true, "AD-05": true, "AD-06": true,
|
||||||
"AD-07": true, "AD-08": true, "AE-AJ": true, "AE-AZ": true, "AE-DU": true,
|
"AD-07": true, "AD-08": true, "AE-AJ": true, "AE-AZ": true, "AE-DU": true,
|
||||||
|
|||||||
42
vendor/github.com/go-playground/validator/v10/doc.go
generated
vendored
42
vendor/github.com/go-playground/validator/v10/doc.go
generated
vendored
@@ -194,13 +194,6 @@ such as min or max won't run, but if a value is set validation will run.
|
|||||||
|
|
||||||
Usage: omitempty
|
Usage: omitempty
|
||||||
|
|
||||||
# Omit Nil
|
|
||||||
|
|
||||||
Allows to skip the validation if the value is nil (same as omitempty, but
|
|
||||||
only for the nil-values).
|
|
||||||
|
|
||||||
Usage: omitnil
|
|
||||||
|
|
||||||
# Dive
|
# Dive
|
||||||
|
|
||||||
This tells the validator to dive into a slice, array or map and validate that
|
This tells the validator to dive into a slice, array or map and validate that
|
||||||
@@ -254,7 +247,7 @@ Example #2
|
|||||||
This validates that the value is not the data types default zero value.
|
This validates that the value is not the data types default zero value.
|
||||||
For numbers ensures value is not zero. For strings ensures value is
|
For numbers ensures value is not zero. For strings ensures value is
|
||||||
not "". For slices, maps, pointers, interfaces, channels and functions
|
not "". For slices, maps, pointers, interfaces, channels and functions
|
||||||
ensures the value is not nil. For structs ensures value is not the zero value when using WithRequiredStructEnabled.
|
ensures the value is not nil.
|
||||||
|
|
||||||
Usage: required
|
Usage: required
|
||||||
|
|
||||||
@@ -263,7 +256,7 @@ ensures the value is not nil. For structs ensures value is not the zero value wh
|
|||||||
The field under validation must be present and not empty only if all
|
The field under validation must be present and not empty only if all
|
||||||
the other specified fields are equal to the value following the specified
|
the other specified fields are equal to the value following the specified
|
||||||
field. For strings ensures value is not "". For slices, maps, pointers,
|
field. For strings ensures value is not "". For slices, maps, pointers,
|
||||||
interfaces, channels and functions ensures the value is not nil. For structs ensures value is not the zero value.
|
interfaces, channels and functions ensures the value is not nil.
|
||||||
|
|
||||||
Usage: required_if
|
Usage: required_if
|
||||||
|
|
||||||
@@ -280,7 +273,7 @@ Examples:
|
|||||||
The field under validation must be present and not empty unless all
|
The field under validation must be present and not empty unless all
|
||||||
the other specified fields are equal to the value following the specified
|
the other specified fields are equal to the value following the specified
|
||||||
field. For strings ensures value is not "". For slices, maps, pointers,
|
field. For strings ensures value is not "". For slices, maps, pointers,
|
||||||
interfaces, channels and functions ensures the value is not nil. For structs ensures value is not the zero value.
|
interfaces, channels and functions ensures the value is not nil.
|
||||||
|
|
||||||
Usage: required_unless
|
Usage: required_unless
|
||||||
|
|
||||||
@@ -297,7 +290,7 @@ Examples:
|
|||||||
The field under validation must be present and not empty only if any
|
The field under validation must be present and not empty only if any
|
||||||
of the other specified fields are present. For strings ensures value is
|
of the other specified fields are present. For strings ensures value is
|
||||||
not "". For slices, maps, pointers, interfaces, channels and functions
|
not "". For slices, maps, pointers, interfaces, channels and functions
|
||||||
ensures the value is not nil. For structs ensures value is not the zero value.
|
ensures the value is not nil.
|
||||||
|
|
||||||
Usage: required_with
|
Usage: required_with
|
||||||
|
|
||||||
@@ -314,7 +307,7 @@ Examples:
|
|||||||
The field under validation must be present and not empty only if all
|
The field under validation must be present and not empty only if all
|
||||||
of the other specified fields are present. For strings ensures value is
|
of the other specified fields are present. For strings ensures value is
|
||||||
not "". For slices, maps, pointers, interfaces, channels and functions
|
not "". For slices, maps, pointers, interfaces, channels and functions
|
||||||
ensures the value is not nil. For structs ensures value is not the zero value.
|
ensures the value is not nil.
|
||||||
|
|
||||||
Usage: required_with_all
|
Usage: required_with_all
|
||||||
|
|
||||||
@@ -328,7 +321,7 @@ Example:
|
|||||||
The field under validation must be present and not empty only when any
|
The field under validation must be present and not empty only when any
|
||||||
of the other specified fields are not present. For strings ensures value is
|
of the other specified fields are not present. For strings ensures value is
|
||||||
not "". For slices, maps, pointers, interfaces, channels and functions
|
not "". For slices, maps, pointers, interfaces, channels and functions
|
||||||
ensures the value is not nil. For structs ensures value is not the zero value.
|
ensures the value is not nil.
|
||||||
|
|
||||||
Usage: required_without
|
Usage: required_without
|
||||||
|
|
||||||
@@ -345,7 +338,7 @@ Examples:
|
|||||||
The field under validation must be present and not empty only when all
|
The field under validation must be present and not empty only when all
|
||||||
of the other specified fields are not present. For strings ensures value is
|
of the other specified fields are not present. For strings ensures value is
|
||||||
not "". For slices, maps, pointers, interfaces, channels and functions
|
not "". For slices, maps, pointers, interfaces, channels and functions
|
||||||
ensures the value is not nil. For structs ensures value is not the zero value.
|
ensures the value is not nil.
|
||||||
|
|
||||||
Usage: required_without_all
|
Usage: required_without_all
|
||||||
|
|
||||||
@@ -359,7 +352,7 @@ Example:
|
|||||||
The field under validation must not be present or not empty only if all
|
The field under validation must not be present or not empty only if all
|
||||||
the other specified fields are equal to the value following the specified
|
the other specified fields are equal to the value following the specified
|
||||||
field. For strings ensures value is not "". For slices, maps, pointers,
|
field. For strings ensures value is not "". For slices, maps, pointers,
|
||||||
interfaces, channels and functions ensures the value is not nil. For structs ensures value is not the zero value.
|
interfaces, channels and functions ensures the value is not nil.
|
||||||
|
|
||||||
Usage: excluded_if
|
Usage: excluded_if
|
||||||
|
|
||||||
@@ -376,7 +369,7 @@ Examples:
|
|||||||
The field under validation must not be present or empty unless all
|
The field under validation must not be present or empty unless all
|
||||||
the other specified fields are equal to the value following the specified
|
the other specified fields are equal to the value following the specified
|
||||||
field. For strings ensures value is not "". For slices, maps, pointers,
|
field. For strings ensures value is not "". For slices, maps, pointers,
|
||||||
interfaces, channels and functions ensures the value is not nil. For structs ensures value is not the zero value.
|
interfaces, channels and functions ensures the value is not nil.
|
||||||
|
|
||||||
Usage: excluded_unless
|
Usage: excluded_unless
|
||||||
|
|
||||||
@@ -886,6 +879,8 @@ This is done using os.Stat and github.com/gabriel-vasile/mimetype
|
|||||||
|
|
||||||
Usage: image
|
Usage: image
|
||||||
|
|
||||||
|
# URL String
|
||||||
|
|
||||||
# File Path
|
# File Path
|
||||||
|
|
||||||
This validates that a string value contains a valid file path but does not
|
This validates that a string value contains a valid file path but does not
|
||||||
@@ -916,15 +911,6 @@ according to the RFC 2141 spec.
|
|||||||
|
|
||||||
Usage: urn_rfc2141
|
Usage: urn_rfc2141
|
||||||
|
|
||||||
# Base32 String
|
|
||||||
|
|
||||||
This validates that a string value contains a valid bas324 value.
|
|
||||||
Although an empty string is valid base32 this will report an empty string
|
|
||||||
as an error, if you wish to accept an empty string as valid you can use
|
|
||||||
this with the omitempty tag.
|
|
||||||
|
|
||||||
Usage: base32
|
|
||||||
|
|
||||||
# Base64 String
|
# Base64 String
|
||||||
|
|
||||||
This validates that a string value contains a valid base64 value.
|
This validates that a string value contains a valid base64 value.
|
||||||
@@ -1398,12 +1384,6 @@ This validates that a string value contains a valid cron expression.
|
|||||||
|
|
||||||
Usage: cron
|
Usage: cron
|
||||||
|
|
||||||
# SpiceDb ObjectID/Permission/Object Type
|
|
||||||
|
|
||||||
This validates that a string is valid for use with SpiceDb for the indicated purpose. If no purpose is given, a purpose of 'id' is assumed.
|
|
||||||
|
|
||||||
Usage: spicedb=id|permission|type
|
|
||||||
|
|
||||||
# Alias Validators and Tags
|
# Alias Validators and Tags
|
||||||
|
|
||||||
Alias Validators and Tags
|
Alias Validators and Tags
|
||||||
|
|||||||
6
vendor/github.com/go-playground/validator/v10/errors.go
generated
vendored
6
vendor/github.com/go-playground/validator/v10/errors.go
generated
vendored
@@ -257,20 +257,16 @@ func (fe *fieldError) Error() string {
|
|||||||
// NOTE: if no registered translation can be found, it returns the original
|
// NOTE: if no registered translation can be found, it returns the original
|
||||||
// untranslated error message.
|
// untranslated error message.
|
||||||
func (fe *fieldError) Translate(ut ut.Translator) string {
|
func (fe *fieldError) Translate(ut ut.Translator) string {
|
||||||
var fn TranslationFunc
|
|
||||||
|
|
||||||
m, ok := fe.v.transTagFunc[ut]
|
m, ok := fe.v.transTagFunc[ut]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fe.Error()
|
return fe.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn, ok = m[fe.tag]
|
fn, ok := m[fe.tag]
|
||||||
if !ok {
|
|
||||||
fn, ok = m[fe.actualTag]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fe.Error()
|
return fe.Error()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return fn(ut, fe)
|
return fn(ut, fe)
|
||||||
}
|
}
|
||||||
|
|||||||
12
vendor/github.com/go-playground/validator/v10/regexes.go
generated
vendored
12
vendor/github.com/go-playground/validator/v10/regexes.go
generated
vendored
@@ -17,13 +17,11 @@ const (
|
|||||||
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
||||||
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
|
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
|
||||||
e164RegexString = "^\\+[1-9]?[0-9]{7,14}$"
|
e164RegexString = "^\\+[1-9]?[0-9]{7,14}$"
|
||||||
base32RegexString = "^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=|[A-Z2-7]{8})$"
|
|
||||||
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
|
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
|
||||||
base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$"
|
base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$"
|
||||||
base64RawURLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2,4})$"
|
base64RawURLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2,4})$"
|
||||||
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
|
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
|
||||||
iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
|
iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
|
||||||
iSSNRegexString = "^(?:[0-9]{4}-[0-9]{3}[0-9X])$"
|
|
||||||
uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
uUID3RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||||
uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
uUID4RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||||
uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
uUID5RegexString = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||||
@@ -32,7 +30,7 @@ const (
|
|||||||
uUID4RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
|
uUID4RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
|
||||||
uUID5RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
|
uUID5RFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-5[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
|
||||||
uUIDRFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
uUIDRFC4122RegexString = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
||||||
uLIDRegexString = "^(?i)[A-HJKMNP-TV-Z0-9]{26}$"
|
uLIDRegexString = "^[A-HJKMNP-TV-Z0-9]{26}$"
|
||||||
md4RegexString = "^[0-9a-f]{32}$"
|
md4RegexString = "^[0-9a-f]{32}$"
|
||||||
md5RegexString = "^[0-9a-f]{32}$"
|
md5RegexString = "^[0-9a-f]{32}$"
|
||||||
sha256RegexString = "^[0-9a-f]{64}$"
|
sha256RegexString = "^[0-9a-f]{64}$"
|
||||||
@@ -70,9 +68,6 @@ const (
|
|||||||
cveRegexString = `^CVE-(1999|2\d{3})-(0[^0]\d{2}|0\d[^0]\d{1}|0\d{2}[^0]|[1-9]{1}\d{3,})$` // CVE Format Id https://cve.mitre.org/cve/identifiers/syntaxchange.html
|
cveRegexString = `^CVE-(1999|2\d{3})-(0[^0]\d{2}|0\d[^0]\d{1}|0\d{2}[^0]|[1-9]{1}\d{3,})$` // CVE Format Id https://cve.mitre.org/cve/identifiers/syntaxchange.html
|
||||||
mongodbRegexString = "^[a-f\\d]{24}$"
|
mongodbRegexString = "^[a-f\\d]{24}$"
|
||||||
cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`
|
cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`
|
||||||
spicedbIDRegexString = `^(([a-zA-Z0-9/_|\-=+]{1,})|\*)$`
|
|
||||||
spicedbPermissionRegexString = "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$"
|
|
||||||
spicedbTypeRegexString = "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)?[a-z][a-z0-9_]{1,62}[a-z0-9]$"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -90,13 +85,11 @@ var (
|
|||||||
hslaRegex = regexp.MustCompile(hslaRegexString)
|
hslaRegex = regexp.MustCompile(hslaRegexString)
|
||||||
e164Regex = regexp.MustCompile(e164RegexString)
|
e164Regex = regexp.MustCompile(e164RegexString)
|
||||||
emailRegex = regexp.MustCompile(emailRegexString)
|
emailRegex = regexp.MustCompile(emailRegexString)
|
||||||
base32Regex = regexp.MustCompile(base32RegexString)
|
|
||||||
base64Regex = regexp.MustCompile(base64RegexString)
|
base64Regex = regexp.MustCompile(base64RegexString)
|
||||||
base64URLRegex = regexp.MustCompile(base64URLRegexString)
|
base64URLRegex = regexp.MustCompile(base64URLRegexString)
|
||||||
base64RawURLRegex = regexp.MustCompile(base64RawURLRegexString)
|
base64RawURLRegex = regexp.MustCompile(base64RawURLRegexString)
|
||||||
iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
|
iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
|
||||||
iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
|
iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
|
||||||
iSSNRegex = regexp.MustCompile(iSSNRegexString)
|
|
||||||
uUID3Regex = regexp.MustCompile(uUID3RegexString)
|
uUID3Regex = regexp.MustCompile(uUID3RegexString)
|
||||||
uUID4Regex = regexp.MustCompile(uUID4RegexString)
|
uUID4Regex = regexp.MustCompile(uUID4RegexString)
|
||||||
uUID5Regex = regexp.MustCompile(uUID5RegexString)
|
uUID5Regex = regexp.MustCompile(uUID5RegexString)
|
||||||
@@ -141,7 +134,4 @@ var (
|
|||||||
cveRegex = regexp.MustCompile(cveRegexString)
|
cveRegex = regexp.MustCompile(cveRegexString)
|
||||||
mongodbRegex = regexp.MustCompile(mongodbRegexString)
|
mongodbRegex = regexp.MustCompile(mongodbRegexString)
|
||||||
cronRegex = regexp.MustCompile(cronRegexString)
|
cronRegex = regexp.MustCompile(cronRegexString)
|
||||||
spicedbIDRegex = regexp.MustCompile(spicedbIDRegexString)
|
|
||||||
spicedbPermissionRegex = regexp.MustCompile(spicedbPermissionRegexString)
|
|
||||||
spicedbTypeRegex = regexp.MustCompile(spicedbTypeRegexString)
|
|
||||||
)
|
)
|
||||||
|
|||||||
29
vendor/github.com/go-playground/validator/v10/util.go
generated
vendored
29
vendor/github.com/go-playground/validator/v10/util.go
generated
vendored
@@ -1,9 +1,7 @@
|
|||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -263,19 +261,13 @@ func asUint(param string) uint64 {
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
// asFloat64 returns the parameter as a float64
|
// asFloat returns the parameter as a float64
|
||||||
// or panics if it can't convert
|
// or panics if it can't convert
|
||||||
func asFloat64(param string) float64 {
|
func asFloat(param string) float64 {
|
||||||
|
|
||||||
i, err := strconv.ParseFloat(param, 64)
|
i, err := strconv.ParseFloat(param, 64)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// asFloat64 returns the parameter as a float64
|
|
||||||
// or panics if it can't convert
|
|
||||||
func asFloat32(param string) float64 {
|
|
||||||
i, err := strconv.ParseFloat(param, 32)
|
|
||||||
panicIf(err)
|
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,18 +286,3 @@ func panicIf(err error) {
|
|||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if field value matches regex. If fl.Field can be cast to Stringer, it uses the Stringer interfaces
|
|
||||||
// String() return value. Otherwise, it uses fl.Field's String() value.
|
|
||||||
func fieldMatchesRegexByStringerValOrString(regex *regexp.Regexp, fl FieldLevel) bool {
|
|
||||||
switch fl.Field().Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
return regex.MatchString(fl.Field().String())
|
|
||||||
default:
|
|
||||||
if stringer, ok := fl.Field().Interface().(fmt.Stringer); ok {
|
|
||||||
return regex.MatchString(stringer.String())
|
|
||||||
} else {
|
|
||||||
return regex.MatchString(fl.Field().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
167
vendor/github.com/go-playground/validator/v10/validator.go
generated
vendored
167
vendor/github.com/go-playground/validator/v10/validator.go
generated
vendored
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// per validate construct
|
// per validate construct
|
||||||
@@ -100,8 +99,6 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
|
|||||||
|
|
||||||
current, kind, v.fldIsPointer = v.extractTypeInternal(current, false)
|
current, kind, v.fldIsPointer = v.extractTypeInternal(current, false)
|
||||||
|
|
||||||
var isNestedStruct bool
|
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Ptr, reflect.Interface, reflect.Invalid:
|
case reflect.Ptr, reflect.Interface, reflect.Invalid:
|
||||||
|
|
||||||
@@ -113,10 +110,6 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ct.typeof == typeOmitNil && (kind != reflect.Invalid && current.IsNil()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ct.hasTag {
|
if ct.hasTag {
|
||||||
if kind == reflect.Invalid {
|
if kind == reflect.Invalid {
|
||||||
v.str1 = string(append(ns, cf.altName...))
|
v.str1 = string(append(ns, cf.altName...))
|
||||||
@@ -157,7 +150,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
|
|||||||
structNs: v.str2,
|
structNs: v.str2,
|
||||||
fieldLen: uint8(len(cf.altName)),
|
fieldLen: uint8(len(cf.altName)),
|
||||||
structfieldLen: uint8(len(cf.name)),
|
structfieldLen: uint8(len(cf.name)),
|
||||||
value: getValue(current),
|
value: current.Interface(),
|
||||||
param: ct.param,
|
param: ct.param,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
typ: current.Type(),
|
typ: current.Type(),
|
||||||
@@ -167,61 +160,86 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == reflect.Invalid {
|
case reflect.Struct:
|
||||||
|
|
||||||
|
typ = current.Type()
|
||||||
|
|
||||||
|
if !typ.ConvertibleTo(timeType) {
|
||||||
|
|
||||||
|
if ct != nil {
|
||||||
|
|
||||||
|
if ct.typeof == typeStructOnly {
|
||||||
|
goto CONTINUE
|
||||||
|
} else if ct.typeof == typeIsDefault {
|
||||||
|
// set Field Level fields
|
||||||
|
v.slflParent = parent
|
||||||
|
v.flField = current
|
||||||
|
v.cf = cf
|
||||||
|
v.ct = ct
|
||||||
|
|
||||||
|
if !ct.fn(ctx, v) {
|
||||||
|
v.str1 = string(append(ns, cf.altName...))
|
||||||
|
|
||||||
|
if v.v.hasTagNameFunc {
|
||||||
|
v.str2 = string(append(structNs, cf.name...))
|
||||||
|
} else {
|
||||||
|
v.str2 = v.str1
|
||||||
|
}
|
||||||
|
|
||||||
|
v.errs = append(v.errs,
|
||||||
|
&fieldError{
|
||||||
|
v: v.v,
|
||||||
|
tag: ct.aliasTag,
|
||||||
|
actualTag: ct.tag,
|
||||||
|
ns: v.str1,
|
||||||
|
structNs: v.str2,
|
||||||
|
fieldLen: uint8(len(cf.altName)),
|
||||||
|
structfieldLen: uint8(len(cf.name)),
|
||||||
|
value: current.Interface(),
|
||||||
|
param: ct.param,
|
||||||
|
kind: kind,
|
||||||
|
typ: typ,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ct = ct.next
|
||||||
|
}
|
||||||
|
|
||||||
|
if ct != nil && ct.typeof == typeNoStructLevel {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case reflect.Struct:
|
CONTINUE:
|
||||||
isNestedStruct = !current.Type().ConvertibleTo(timeType)
|
// if len == 0 then validating using 'Var' or 'VarWithValue'
|
||||||
// For backward compatibility before struct level validation tags were supported
|
// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
|
||||||
// as there were a number of projects relying on `required` not failing on non-pointer
|
// VarWithField - this allows for validating against each field within the struct against a specific value
|
||||||
// structs. Since it's basically nonsensical to use `required` with a non-pointer struct
|
// pretty handy in certain situations
|
||||||
// are explicitly skipping the required validation for it. This WILL be removed in the
|
if len(cf.name) > 0 {
|
||||||
// next major version.
|
ns = append(append(ns, cf.altName...), '.')
|
||||||
if isNestedStruct && !v.v.requiredStructEnabled && ct != nil && ct.tag == requiredTag {
|
structNs = append(append(structNs, cf.name...), '.')
|
||||||
ct = ct.next
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ct == nil || !ct.hasTag {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
typ = current.Type()
|
typ = current.Type()
|
||||||
|
|
||||||
OUTER:
|
OUTER:
|
||||||
for {
|
for {
|
||||||
if ct == nil || !ct.hasTag || (isNestedStruct && len(cf.name) == 0) {
|
if ct == nil {
|
||||||
// isNestedStruct check here
|
|
||||||
if isNestedStruct {
|
|
||||||
// if len == 0 then validating using 'Var' or 'VarWithValue'
|
|
||||||
// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
|
|
||||||
// VarWithField - this allows for validating against each field within the struct against a specific value
|
|
||||||
// pretty handy in certain situations
|
|
||||||
if len(cf.name) > 0 {
|
|
||||||
ns = append(append(ns, cf.altName...), '.')
|
|
||||||
structNs = append(append(structNs, cf.name...), '.')
|
|
||||||
}
|
|
||||||
|
|
||||||
v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ct.typeof {
|
switch ct.typeof {
|
||||||
case typeNoStructLevel:
|
|
||||||
return
|
|
||||||
|
|
||||||
case typeStructOnly:
|
|
||||||
if isNestedStruct {
|
|
||||||
// if len == 0 then validating using 'Var' or 'VarWithValue'
|
|
||||||
// Var - doesn't make much sense to do it that way, should call 'Struct', but no harm...
|
|
||||||
// VarWithField - this allows for validating against each field within the struct against a specific value
|
|
||||||
// pretty handy in certain situations
|
|
||||||
if len(cf.name) > 0 {
|
|
||||||
ns = append(append(ns, cf.altName...), '.')
|
|
||||||
structNs = append(append(structNs, cf.name...), '.')
|
|
||||||
}
|
|
||||||
|
|
||||||
v.validateStruct(ctx, parent, current, typ, ns, structNs, ct)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
|
|
||||||
case typeOmitEmpty:
|
case typeOmitEmpty:
|
||||||
|
|
||||||
@@ -238,26 +256,6 @@ OUTER:
|
|||||||
ct = ct.next
|
ct = ct.next
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case typeOmitNil:
|
|
||||||
v.slflParent = parent
|
|
||||||
v.flField = current
|
|
||||||
v.cf = cf
|
|
||||||
v.ct = ct
|
|
||||||
|
|
||||||
switch field := v.Field(); field.Kind() {
|
|
||||||
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
|
|
||||||
if field.IsNil() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if v.fldIsPointer && field.Interface() == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ct = ct.next
|
|
||||||
continue
|
|
||||||
|
|
||||||
case typeEndKeys:
|
case typeEndKeys:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -368,7 +366,7 @@ OUTER:
|
|||||||
ct = ct.next
|
ct = ct.next
|
||||||
|
|
||||||
if ct == nil {
|
if ct == nil {
|
||||||
continue OUTER
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ct.typeof != typeOr {
|
if ct.typeof != typeOr {
|
||||||
@@ -411,7 +409,7 @@ OUTER:
|
|||||||
structNs: v.str2,
|
structNs: v.str2,
|
||||||
fieldLen: uint8(len(cf.altName)),
|
fieldLen: uint8(len(cf.altName)),
|
||||||
structfieldLen: uint8(len(cf.name)),
|
structfieldLen: uint8(len(cf.name)),
|
||||||
value: getValue(current),
|
value: current.Interface(),
|
||||||
param: ct.param,
|
param: ct.param,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
typ: typ,
|
typ: typ,
|
||||||
@@ -431,7 +429,7 @@ OUTER:
|
|||||||
structNs: v.str2,
|
structNs: v.str2,
|
||||||
fieldLen: uint8(len(cf.altName)),
|
fieldLen: uint8(len(cf.altName)),
|
||||||
structfieldLen: uint8(len(cf.name)),
|
structfieldLen: uint8(len(cf.name)),
|
||||||
value: getValue(current),
|
value: current.Interface(),
|
||||||
param: ct.param,
|
param: ct.param,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
typ: typ,
|
typ: typ,
|
||||||
@@ -471,7 +469,7 @@ OUTER:
|
|||||||
structNs: v.str2,
|
structNs: v.str2,
|
||||||
fieldLen: uint8(len(cf.altName)),
|
fieldLen: uint8(len(cf.altName)),
|
||||||
structfieldLen: uint8(len(cf.name)),
|
structfieldLen: uint8(len(cf.name)),
|
||||||
value: getValue(current),
|
value: current.Interface(),
|
||||||
param: ct.param,
|
param: ct.param,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
typ: typ,
|
typ: typ,
|
||||||
@@ -485,26 +483,3 @@ OUTER:
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getValue(val reflect.Value) interface{} {
|
|
||||||
if val.CanInterface() {
|
|
||||||
return val.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
if val.CanAddr() {
|
|
||||||
return reflect.NewAt(val.Type(), unsafe.Pointer(val.UnsafeAddr())).Elem().Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch val.Kind() {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return val.Int()
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return val.Uint()
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
return val.Complex()
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return val.Float()
|
|
||||||
default:
|
|
||||||
return val.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
14
vendor/github.com/go-playground/validator/v10/validator_instance.go
generated
vendored
14
vendor/github.com/go-playground/validator/v10/validator_instance.go
generated
vendored
@@ -22,7 +22,6 @@ const (
|
|||||||
structOnlyTag = "structonly"
|
structOnlyTag = "structonly"
|
||||||
noStructLevelTag = "nostructlevel"
|
noStructLevelTag = "nostructlevel"
|
||||||
omitempty = "omitempty"
|
omitempty = "omitempty"
|
||||||
omitnil = "omitnil"
|
|
||||||
isdefault = "isdefault"
|
isdefault = "isdefault"
|
||||||
requiredWithoutAllTag = "required_without_all"
|
requiredWithoutAllTag = "required_without_all"
|
||||||
requiredWithoutTag = "required_without"
|
requiredWithoutTag = "required_without"
|
||||||
@@ -54,8 +53,6 @@ var (
|
|||||||
timeDurationType = reflect.TypeOf(time.Duration(0))
|
timeDurationType = reflect.TypeOf(time.Duration(0))
|
||||||
timeType = reflect.TypeOf(time.Time{})
|
timeType = reflect.TypeOf(time.Time{})
|
||||||
|
|
||||||
byteSliceType = reflect.TypeOf([]byte{})
|
|
||||||
|
|
||||||
defaultCField = &cField{namesEqual: true}
|
defaultCField = &cField{namesEqual: true}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -82,6 +79,8 @@ type internalValidationFuncWrapper struct {
|
|||||||
type Validate struct {
|
type Validate struct {
|
||||||
tagName string
|
tagName string
|
||||||
pool *sync.Pool
|
pool *sync.Pool
|
||||||
|
hasCustomFuncs bool
|
||||||
|
hasTagNameFunc bool
|
||||||
tagNameFunc TagNameFunc
|
tagNameFunc TagNameFunc
|
||||||
structLevelFuncs map[reflect.Type]StructLevelFuncCtx
|
structLevelFuncs map[reflect.Type]StructLevelFuncCtx
|
||||||
customFuncs map[reflect.Type]CustomTypeFunc
|
customFuncs map[reflect.Type]CustomTypeFunc
|
||||||
@@ -91,10 +90,6 @@ type Validate struct {
|
|||||||
rules map[reflect.Type]map[string]string
|
rules map[reflect.Type]map[string]string
|
||||||
tagCache *tagCache
|
tagCache *tagCache
|
||||||
structCache *structCache
|
structCache *structCache
|
||||||
hasCustomFuncs bool
|
|
||||||
hasTagNameFunc bool
|
|
||||||
requiredStructEnabled bool
|
|
||||||
privateFieldValidation bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new instance of 'validate' with sane defaults.
|
// New returns a new instance of 'validate' with sane defaults.
|
||||||
@@ -102,7 +97,7 @@ type Validate struct {
|
|||||||
// It caches information about your struct and validations,
|
// It caches information about your struct and validations,
|
||||||
// in essence only parsing your validation tags once per struct type.
|
// in essence only parsing your validation tags once per struct type.
|
||||||
// Using multiple instances neglects the benefit of caching.
|
// Using multiple instances neglects the benefit of caching.
|
||||||
func New(options ...Option) *Validate {
|
func New() *Validate {
|
||||||
|
|
||||||
tc := new(tagCache)
|
tc := new(tagCache)
|
||||||
tc.m.Store(make(map[string]*cTag))
|
tc.m.Store(make(map[string]*cTag))
|
||||||
@@ -149,9 +144,6 @@ func New(options ...Option) *Validate {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, o := range options {
|
|
||||||
o(v)
|
|
||||||
}
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
vendor/github.com/jackc/pgservicefile/README.md
generated
vendored
5
vendor/github.com/jackc/pgservicefile/README.md
generated
vendored
@@ -1,6 +1,5 @@
|
|||||||
[](https://pkg.go.dev/github.com/jackc/pgservicefile)
|
[](https://godoc.org/github.com/jackc/pgservicefile)
|
||||||
[](https://github.com/jackc/pgservicefile/actions/workflows/ci.yml)
|
[](https://travis-ci.org/jackc/pgservicefile)
|
||||||
|
|
||||||
|
|
||||||
# pgservicefile
|
# pgservicefile
|
||||||
|
|
||||||
|
|||||||
4
vendor/github.com/jackc/pgservicefile/pgservicefile.go
generated
vendored
4
vendor/github.com/jackc/pgservicefile/pgservicefile.go
generated
vendored
@@ -57,7 +57,7 @@ func ParseServicefile(r io.Reader) (*Servicefile, error) {
|
|||||||
} else if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
} else if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||||
service = &Service{Name: line[1 : len(line)-1], Settings: make(map[string]string)}
|
service = &Service{Name: line[1 : len(line)-1], Settings: make(map[string]string)}
|
||||||
servicefile.Services = append(servicefile.Services, service)
|
servicefile.Services = append(servicefile.Services, service)
|
||||||
} else if service != nil {
|
} else {
|
||||||
parts := strings.SplitN(line, "=", 2)
|
parts := strings.SplitN(line, "=", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return nil, fmt.Errorf("unable to parse line %d", lineNum)
|
return nil, fmt.Errorf("unable to parse line %d", lineNum)
|
||||||
@@ -67,8 +67,6 @@ func ParseServicefile(r io.Reader) (*Servicefile, error) {
|
|||||||
value := strings.TrimSpace(parts[1])
|
value := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
service.Settings[key] = value
|
service.Settings[key] = value
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("line %d is not in a section", lineNum)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
111
vendor/github.com/jackc/pgx/v5/CHANGELOG.md
generated
vendored
111
vendor/github.com/jackc/pgx/v5/CHANGELOG.md
generated
vendored
@@ -1,114 +1,3 @@
|
|||||||
# 5.7.1 (September 10, 2024)
|
|
||||||
|
|
||||||
* Fix data race in tracelog.TraceLog
|
|
||||||
* Update puddle to v2.2.2. This removes the import of nanotime via linkname.
|
|
||||||
* Update golang.org/x/crypto and golang.org/x/text
|
|
||||||
|
|
||||||
# 5.7.0 (September 7, 2024)
|
|
||||||
|
|
||||||
* Add support for sslrootcert=system (Yann Soubeyrand)
|
|
||||||
* Add LoadTypes to load multiple types in a single SQL query (Nick Farrell)
|
|
||||||
* Add XMLCodec supports encoding + scanning XML column type like json (nickcruess-soda)
|
|
||||||
* Add MultiTrace (Stepan Rabotkin)
|
|
||||||
* Add TraceLogConfig with customizable TimeKey (stringintech)
|
|
||||||
* pgx.ErrNoRows wraps sql.ErrNoRows to aid in database/sql compatibility with native pgx functions (merlin)
|
|
||||||
* Support scanning binary formatted uint32 into string / TextScanner (jennifersp)
|
|
||||||
* Fix interval encoding to allow 0s and avoid extra spaces (Carlos Pérez-Aradros Herce)
|
|
||||||
* Update pgservicefile - fixes panic when parsing invalid file
|
|
||||||
* Better error message when reading past end of batch
|
|
||||||
* Don't print url when url.Parse returns an error (Kevin Biju)
|
|
||||||
* Fix snake case name normalization collision in RowToStructByName with db tag (nolandseigler)
|
|
||||||
* Fix: Scan and encode types with underlying types of arrays
|
|
||||||
|
|
||||||
# 5.6.0 (May 25, 2024)
|
|
||||||
|
|
||||||
* Add StrictNamedArgs (Tomas Zahradnicek)
|
|
||||||
* Add support for macaddr8 type (Carlos Pérez-Aradros Herce)
|
|
||||||
* Add SeverityUnlocalized field to PgError / Notice
|
|
||||||
* Performance optimization of RowToStructByPos/Name (Zach Olstein)
|
|
||||||
* Allow customizing context canceled behavior for pgconn
|
|
||||||
* Add ScanLocation to pgtype.Timestamp[tz]Codec
|
|
||||||
* Add custom data to pgconn.PgConn
|
|
||||||
* Fix ResultReader.Read() to handle nil values
|
|
||||||
* Do not encode interval microseconds when they are 0 (Carlos Pérez-Aradros Herce)
|
|
||||||
* pgconn.SafeToRetry checks for wrapped errors (tjasko)
|
|
||||||
* Failed connection attempts include all errors
|
|
||||||
* Optimize LargeObject.Read (Mitar)
|
|
||||||
* Add tracing for connection acquire and release from pool (ngavinsir)
|
|
||||||
* Fix encode driver.Valuer not called when nil
|
|
||||||
* Add support for custom JSON marshal and unmarshal (Mitar)
|
|
||||||
* Use Go default keepalive for TCP connections (Hans-Joachim Kliemeck)
|
|
||||||
|
|
||||||
# 5.5.5 (March 9, 2024)
|
|
||||||
|
|
||||||
Use spaces instead of parentheses for SQL sanitization.
|
|
||||||
|
|
||||||
This still solves the problem of negative numbers creating a line comment, but this avoids breaking edge cases such as
|
|
||||||
`set foo to $1` where the substitution is taking place in a location where an arbitrary expression is not allowed.
|
|
||||||
|
|
||||||
# 5.5.4 (March 4, 2024)
|
|
||||||
|
|
||||||
Fix CVE-2024-27304
|
|
||||||
|
|
||||||
SQL injection can occur if an attacker can cause a single query or bind message to exceed 4 GB in size. An integer
|
|
||||||
overflow in the calculated message size can cause the one large message to be sent as multiple messages under the
|
|
||||||
attacker's control.
|
|
||||||
|
|
||||||
Thanks to Paul Gerste for reporting this issue.
|
|
||||||
|
|
||||||
* Fix behavior of CollectRows to return empty slice if Rows are empty (Felix)
|
|
||||||
* Fix simple protocol encoding of json.RawMessage
|
|
||||||
* Fix *Pipeline.getResults should close pipeline on error
|
|
||||||
* Fix panic in TryFindUnderlyingTypeScanPlan (David Kurman)
|
|
||||||
* Fix deallocation of invalidated cached statements in a transaction
|
|
||||||
* Handle invalid sslkey file
|
|
||||||
* Fix scan float4 into sql.Scanner
|
|
||||||
* Fix pgtype.Bits not making copy of data from read buffer. This would cause the data to be corrupted by future reads.
|
|
||||||
|
|
||||||
# 5.5.3 (February 3, 2024)
|
|
||||||
|
|
||||||
* Fix: prepared statement already exists
|
|
||||||
* Improve CopyFrom auto-conversion of text-ish values
|
|
||||||
* Add ltree type support (Florent Viel)
|
|
||||||
* Make some properties of Batch and QueuedQuery public (Pavlo Golub)
|
|
||||||
* Add AppendRows function (Edoardo Spadolini)
|
|
||||||
* Optimize convert UUID [16]byte to string (Kirill Malikov)
|
|
||||||
* Fix: LargeObject Read and Write of more than ~1GB at a time (Mitar)
|
|
||||||
|
|
||||||
# 5.5.2 (January 13, 2024)
|
|
||||||
|
|
||||||
* Allow NamedArgs to start with underscore
|
|
||||||
* pgproto3: Maximum message body length support (jeremy.spriet)
|
|
||||||
* Upgrade golang.org/x/crypto to v0.17.0
|
|
||||||
* Add snake_case support to RowToStructByName (Tikhon Fedulov)
|
|
||||||
* Fix: update description cache after exec prepare (James Hartig)
|
|
||||||
* Fix: pipeline checks if it is closed (James Hartig and Ryan Fowler)
|
|
||||||
* Fix: normalize timeout / context errors during TLS startup (Samuel Stauffer)
|
|
||||||
* Add OnPgError for easier centralized error handling (James Hartig)
|
|
||||||
|
|
||||||
# 5.5.1 (December 9, 2023)
|
|
||||||
|
|
||||||
* Add CopyFromFunc helper function. (robford)
|
|
||||||
* Add PgConn.Deallocate method that uses PostgreSQL protocol Close message.
|
|
||||||
* pgx uses new PgConn.Deallocate method. This allows deallocating statements to work in a failed transaction. This fixes a case where the prepared statement map could become invalid.
|
|
||||||
* Fix: Prefer driver.Valuer over json.Marshaler for json fields. (Jacopo)
|
|
||||||
* Fix: simple protocol SQL sanitizer previously panicked if an invalid $0 placeholder was used. This now returns an error instead. (maksymnevajdev)
|
|
||||||
* Add pgtype.Numeric.ScanScientific (Eshton Robateau)
|
|
||||||
|
|
||||||
# 5.5.0 (November 4, 2023)
|
|
||||||
|
|
||||||
* Add CollectExactlyOneRow. (Julien GOTTELAND)
|
|
||||||
* Add OpenDBFromPool to create *database/sql.DB from *pgxpool.Pool. (Lev Zakharov)
|
|
||||||
* Prepare can automatically choose statement name based on sql. This makes it easier to explicitly manage prepared statements.
|
|
||||||
* Statement cache now uses deterministic, stable statement names.
|
|
||||||
* database/sql prepared statement names are deterministically generated.
|
|
||||||
* Fix: SendBatch wasn't respecting context cancellation.
|
|
||||||
* Fix: Timeout error from pipeline is now normalized.
|
|
||||||
* Fix: database/sql encoding json.RawMessage to []byte.
|
|
||||||
* CancelRequest: Wait for the cancel request to be acknowledged by the server. This should improve PgBouncer compatibility. (Anton Levakin)
|
|
||||||
* stdlib: Use Ping instead of CheckConn in ResetSession
|
|
||||||
* Add json.Marshaler and json.Unmarshaler for Float4, Float8 (Kirill Mironov)
|
|
||||||
|
|
||||||
# 5.4.3 (August 5, 2023)
|
# 5.4.3 (August 5, 2023)
|
||||||
|
|
||||||
* Fix: QCharArrayOID was defined with the wrong OID (Christoph Engelbert)
|
* Fix: QCharArrayOID was defined with the wrong OID (Christoph Engelbert)
|
||||||
|
|||||||
19
vendor/github.com/jackc/pgx/v5/CONTRIBUTING.md
generated
vendored
19
vendor/github.com/jackc/pgx/v5/CONTRIBUTING.md
generated
vendored
@@ -29,7 +29,6 @@ Create and setup a test database:
|
|||||||
export PGDATABASE=pgx_test
|
export PGDATABASE=pgx_test
|
||||||
createdb
|
createdb
|
||||||
psql -c 'create extension hstore;'
|
psql -c 'create extension hstore;'
|
||||||
psql -c 'create extension ltree;'
|
|
||||||
psql -c 'create domain uint64 as numeric(20,0);'
|
psql -c 'create domain uint64 as numeric(20,0);'
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -80,11 +79,20 @@ echo "listen_addresses = '127.0.0.1'" >> .testdb/$POSTGRESQL_DATA_DIR/postgresql
|
|||||||
echo "port = $PGPORT" >> .testdb/$POSTGRESQL_DATA_DIR/postgresql.conf
|
echo "port = $PGPORT" >> .testdb/$POSTGRESQL_DATA_DIR/postgresql.conf
|
||||||
cat testsetup/postgresql_ssl.conf >> .testdb/$POSTGRESQL_DATA_DIR/postgresql.conf
|
cat testsetup/postgresql_ssl.conf >> .testdb/$POSTGRESQL_DATA_DIR/postgresql.conf
|
||||||
cp testsetup/pg_hba.conf .testdb/$POSTGRESQL_DATA_DIR/pg_hba.conf
|
cp testsetup/pg_hba.conf .testdb/$POSTGRESQL_DATA_DIR/pg_hba.conf
|
||||||
|
cp testsetup/ca.cnf .testdb
|
||||||
|
cp testsetup/localhost.cnf .testdb
|
||||||
|
cp testsetup/pgx_sslcert.cnf .testdb
|
||||||
|
|
||||||
cd .testdb
|
cd .testdb
|
||||||
|
|
||||||
# Generate CA, server, and encrypted client certificates.
|
# Generate a CA public / private key pair.
|
||||||
go run ../testsetup/generate_certs.go
|
openssl genrsa -out ca.key 4096
|
||||||
|
openssl req -x509 -config ca.cnf -new -nodes -key ca.key -sha256 -days 365 -subj '/O=pgx-test-root' -out ca.pem
|
||||||
|
|
||||||
|
# Generate the certificate for localhost (the server).
|
||||||
|
openssl genrsa -out localhost.key 2048
|
||||||
|
openssl req -new -config localhost.cnf -key localhost.key -out localhost.csr
|
||||||
|
openssl x509 -req -in localhost.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out localhost.crt -days 364 -sha256 -extfile localhost.cnf -extensions v3_req
|
||||||
|
|
||||||
# Copy certificates to server directory and set permissions.
|
# Copy certificates to server directory and set permissions.
|
||||||
cp ca.pem $POSTGRESQL_DATA_DIR/root.crt
|
cp ca.pem $POSTGRESQL_DATA_DIR/root.crt
|
||||||
@@ -92,6 +100,11 @@ cp localhost.key $POSTGRESQL_DATA_DIR/server.key
|
|||||||
chmod 600 $POSTGRESQL_DATA_DIR/server.key
|
chmod 600 $POSTGRESQL_DATA_DIR/server.key
|
||||||
cp localhost.crt $POSTGRESQL_DATA_DIR/server.crt
|
cp localhost.crt $POSTGRESQL_DATA_DIR/server.crt
|
||||||
|
|
||||||
|
# Generate the certificate for client authentication.
|
||||||
|
openssl genrsa -des3 -out pgx_sslcert.key -passout pass:certpw 2048
|
||||||
|
openssl req -new -config pgx_sslcert.cnf -key pgx_sslcert.key -passin pass:certpw -out pgx_sslcert.csr
|
||||||
|
openssl x509 -req -in pgx_sslcert.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out pgx_sslcert.crt -days 363 -sha256 -extfile pgx_sslcert.cnf -extensions v3_req
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
7
vendor/github.com/jackc/pgx/v5/README.md
generated
vendored
7
vendor/github.com/jackc/pgx/v5/README.md
generated
vendored
@@ -86,13 +86,9 @@ It is also possible to use the `database/sql` interface and convert a connection
|
|||||||
|
|
||||||
See CONTRIBUTING.md for setup instructions.
|
See CONTRIBUTING.md for setup instructions.
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
See the presentation at Golang Estonia, [PGX Top to Bottom](https://www.youtube.com/watch?v=sXMSWhcHCf8) for a description of pgx architecture.
|
|
||||||
|
|
||||||
## Supported Go and PostgreSQL Versions
|
## Supported Go and PostgreSQL Versions
|
||||||
|
|
||||||
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.21 and higher and PostgreSQL 12 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
|
pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.19 and higher and PostgreSQL 11 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/).
|
||||||
|
|
||||||
## Version Policy
|
## Version Policy
|
||||||
|
|
||||||
@@ -120,7 +116,6 @@ pgerrcode contains constants for the PostgreSQL error codes.
|
|||||||
|
|
||||||
* [github.com/jackc/pgx-gofrs-uuid](https://github.com/jackc/pgx-gofrs-uuid)
|
* [github.com/jackc/pgx-gofrs-uuid](https://github.com/jackc/pgx-gofrs-uuid)
|
||||||
* [github.com/jackc/pgx-shopspring-decimal](https://github.com/jackc/pgx-shopspring-decimal)
|
* [github.com/jackc/pgx-shopspring-decimal](https://github.com/jackc/pgx-shopspring-decimal)
|
||||||
* [github.com/twpayne/pgx-geos](https://github.com/twpayne/pgx-geos) ([PostGIS](https://postgis.net/) and [GEOS](https://libgeos.org/) via [go-geos](https://github.com/twpayne/go-geos))
|
|
||||||
* [github.com/vgarvardt/pgx-google-uuid](https://github.com/vgarvardt/pgx-google-uuid)
|
* [github.com/vgarvardt/pgx-google-uuid](https://github.com/vgarvardt/pgx-google-uuid)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
84
vendor/github.com/jackc/pgx/v5/batch.go
generated
vendored
84
vendor/github.com/jackc/pgx/v5/batch.go
generated
vendored
@@ -10,9 +10,9 @@ import (
|
|||||||
|
|
||||||
// QueuedQuery is a query that has been queued for execution via a Batch.
|
// QueuedQuery is a query that has been queued for execution via a Batch.
|
||||||
type QueuedQuery struct {
|
type QueuedQuery struct {
|
||||||
SQL string
|
query string
|
||||||
Arguments []any
|
arguments []any
|
||||||
Fn batchItemFunc
|
fn batchItemFunc
|
||||||
sd *pgconn.StatementDescription
|
sd *pgconn.StatementDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ type batchItemFunc func(br BatchResults) error
|
|||||||
|
|
||||||
// Query sets fn to be called when the response to qq is received.
|
// Query sets fn to be called when the response to qq is received.
|
||||||
func (qq *QueuedQuery) Query(fn func(rows Rows) error) {
|
func (qq *QueuedQuery) Query(fn func(rows Rows) error) {
|
||||||
qq.Fn = func(br BatchResults) error {
|
qq.fn = func(br BatchResults) error {
|
||||||
rows, _ := br.Query()
|
rows, _ := br.Query()
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ func (qq *QueuedQuery) Query(fn func(rows Rows) error) {
|
|||||||
|
|
||||||
// Query sets fn to be called when the response to qq is received.
|
// Query sets fn to be called when the response to qq is received.
|
||||||
func (qq *QueuedQuery) QueryRow(fn func(row Row) error) {
|
func (qq *QueuedQuery) QueryRow(fn func(row Row) error) {
|
||||||
qq.Fn = func(br BatchResults) error {
|
qq.fn = func(br BatchResults) error {
|
||||||
row := br.QueryRow()
|
row := br.QueryRow()
|
||||||
return fn(row)
|
return fn(row)
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ func (qq *QueuedQuery) QueryRow(fn func(row Row) error) {
|
|||||||
|
|
||||||
// Exec sets fn to be called when the response to qq is received.
|
// Exec sets fn to be called when the response to qq is received.
|
||||||
func (qq *QueuedQuery) Exec(fn func(ct pgconn.CommandTag) error) {
|
func (qq *QueuedQuery) Exec(fn func(ct pgconn.CommandTag) error) {
|
||||||
qq.Fn = func(br BatchResults) error {
|
qq.fn = func(br BatchResults) error {
|
||||||
ct, err := br.Exec()
|
ct, err := br.Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -57,28 +57,22 @@ func (qq *QueuedQuery) Exec(fn func(ct pgconn.CommandTag) error) {
|
|||||||
// Batch queries are a way of bundling multiple queries together to avoid
|
// Batch queries are a way of bundling multiple queries together to avoid
|
||||||
// unnecessary network round trips. A Batch must only be sent once.
|
// unnecessary network round trips. A Batch must only be sent once.
|
||||||
type Batch struct {
|
type Batch struct {
|
||||||
QueuedQueries []*QueuedQuery
|
queuedQueries []*QueuedQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue queues a query to batch b. query can be an SQL query or the name of a prepared statement. The only pgx option
|
// Queue queues a query to batch b. query can be an SQL query or the name of a prepared statement.
|
||||||
// argument that is supported is QueryRewriter. Queries are executed using the connection's DefaultQueryExecMode.
|
|
||||||
//
|
|
||||||
// While query can contain multiple statements if the connection's DefaultQueryExecMode is QueryModeSimple, this should
|
|
||||||
// be avoided. QueuedQuery.Fn must not be set as it will only be called for the first query. That is, QueuedQuery.Query,
|
|
||||||
// QueuedQuery.QueryRow, and QueuedQuery.Exec must not be called. In addition, any error messages or tracing that
|
|
||||||
// include the current query may reference the wrong query.
|
|
||||||
func (b *Batch) Queue(query string, arguments ...any) *QueuedQuery {
|
func (b *Batch) Queue(query string, arguments ...any) *QueuedQuery {
|
||||||
qq := &QueuedQuery{
|
qq := &QueuedQuery{
|
||||||
SQL: query,
|
query: query,
|
||||||
Arguments: arguments,
|
arguments: arguments,
|
||||||
}
|
}
|
||||||
b.QueuedQueries = append(b.QueuedQueries, qq)
|
b.queuedQueries = append(b.queuedQueries, qq)
|
||||||
return qq
|
return qq
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns number of queries that have been queued so far.
|
// Len returns number of queries that have been queued so far.
|
||||||
func (b *Batch) Len() int {
|
func (b *Batch) Len() int {
|
||||||
return len(b.QueuedQueries)
|
return len(b.queuedQueries)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BatchResults interface {
|
type BatchResults interface {
|
||||||
@@ -132,7 +126,7 @@ func (br *batchResults) Exec() (pgconn.CommandTag, error) {
|
|||||||
if !br.mrr.NextResult() {
|
if !br.mrr.NextResult() {
|
||||||
err := br.mrr.Close()
|
err := br.mrr.Close()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = errors.New("no more results in batch")
|
err = errors.New("no result")
|
||||||
}
|
}
|
||||||
if br.conn.batchTracer != nil {
|
if br.conn.batchTracer != nil {
|
||||||
br.conn.batchTracer.TraceBatchQuery(br.ctx, br.conn, TraceBatchQueryData{
|
br.conn.batchTracer.TraceBatchQuery(br.ctx, br.conn, TraceBatchQueryData{
|
||||||
@@ -184,7 +178,7 @@ func (br *batchResults) Query() (Rows, error) {
|
|||||||
if !br.mrr.NextResult() {
|
if !br.mrr.NextResult() {
|
||||||
rows.err = br.mrr.Close()
|
rows.err = br.mrr.Close()
|
||||||
if rows.err == nil {
|
if rows.err == nil {
|
||||||
rows.err = errors.New("no more results in batch")
|
rows.err = errors.New("no result")
|
||||||
}
|
}
|
||||||
rows.closed = true
|
rows.closed = true
|
||||||
|
|
||||||
@@ -231,9 +225,9 @@ func (br *batchResults) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read and run fn for all remaining items
|
// Read and run fn for all remaining items
|
||||||
for br.err == nil && !br.closed && br.b != nil && br.qqIdx < len(br.b.QueuedQueries) {
|
for br.err == nil && !br.closed && br.b != nil && br.qqIdx < len(br.b.queuedQueries) {
|
||||||
if br.b.QueuedQueries[br.qqIdx].Fn != nil {
|
if br.b.queuedQueries[br.qqIdx].fn != nil {
|
||||||
err := br.b.QueuedQueries[br.qqIdx].Fn(br)
|
err := br.b.queuedQueries[br.qqIdx].fn(br)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
br.err = err
|
br.err = err
|
||||||
}
|
}
|
||||||
@@ -257,10 +251,10 @@ func (br *batchResults) earlyError() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (br *batchResults) nextQueryAndArgs() (query string, args []any, ok bool) {
|
func (br *batchResults) nextQueryAndArgs() (query string, args []any, ok bool) {
|
||||||
if br.b != nil && br.qqIdx < len(br.b.QueuedQueries) {
|
if br.b != nil && br.qqIdx < len(br.b.queuedQueries) {
|
||||||
bi := br.b.QueuedQueries[br.qqIdx]
|
bi := br.b.queuedQueries[br.qqIdx]
|
||||||
query = bi.SQL
|
query = bi.query
|
||||||
args = bi.Arguments
|
args = bi.arguments
|
||||||
ok = true
|
ok = true
|
||||||
br.qqIdx++
|
br.qqIdx++
|
||||||
}
|
}
|
||||||
@@ -291,10 +285,7 @@ func (br *pipelineBatchResults) Exec() (pgconn.CommandTag, error) {
|
|||||||
return pgconn.CommandTag{}, br.err
|
return pgconn.CommandTag{}, br.err
|
||||||
}
|
}
|
||||||
|
|
||||||
query, arguments, err := br.nextQueryAndArgs()
|
query, arguments, _ := br.nextQueryAndArgs()
|
||||||
if err != nil {
|
|
||||||
return pgconn.CommandTag{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
results, err := br.pipeline.GetResults()
|
results, err := br.pipeline.GetResults()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -337,9 +328,9 @@ func (br *pipelineBatchResults) Query() (Rows, error) {
|
|||||||
return &baseRows{err: br.err, closed: true}, br.err
|
return &baseRows{err: br.err, closed: true}, br.err
|
||||||
}
|
}
|
||||||
|
|
||||||
query, arguments, err := br.nextQueryAndArgs()
|
query, arguments, ok := br.nextQueryAndArgs()
|
||||||
if err != nil {
|
if !ok {
|
||||||
return &baseRows{err: err, closed: true}, err
|
query = "batch query"
|
||||||
}
|
}
|
||||||
|
|
||||||
rows := br.conn.getRows(br.ctx, query, arguments)
|
rows := br.conn.getRows(br.ctx, query, arguments)
|
||||||
@@ -403,9 +394,9 @@ func (br *pipelineBatchResults) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read and run fn for all remaining items
|
// Read and run fn for all remaining items
|
||||||
for br.err == nil && !br.closed && br.b != nil && br.qqIdx < len(br.b.QueuedQueries) {
|
for br.err == nil && !br.closed && br.b != nil && br.qqIdx < len(br.b.queuedQueries) {
|
||||||
if br.b.QueuedQueries[br.qqIdx].Fn != nil {
|
if br.b.queuedQueries[br.qqIdx].fn != nil {
|
||||||
err := br.b.QueuedQueries[br.qqIdx].Fn(br)
|
err := br.b.queuedQueries[br.qqIdx].fn(br)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
br.err = err
|
br.err = err
|
||||||
}
|
}
|
||||||
@@ -428,16 +419,13 @@ func (br *pipelineBatchResults) earlyError() error {
|
|||||||
return br.err
|
return br.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *pipelineBatchResults) nextQueryAndArgs() (query string, args []any, err error) {
|
func (br *pipelineBatchResults) nextQueryAndArgs() (query string, args []any, ok bool) {
|
||||||
if br.b == nil {
|
if br.b != nil && br.qqIdx < len(br.b.queuedQueries) {
|
||||||
return "", nil, errors.New("no reference to batch")
|
bi := br.b.queuedQueries[br.qqIdx]
|
||||||
}
|
query = bi.query
|
||||||
|
args = bi.arguments
|
||||||
if br.qqIdx >= len(br.b.QueuedQueries) {
|
ok = true
|
||||||
return "", nil, errors.New("no more results in batch")
|
|
||||||
}
|
|
||||||
|
|
||||||
bi := br.b.QueuedQueries[br.qqIdx]
|
|
||||||
br.qqIdx++
|
br.qqIdx++
|
||||||
return bi.SQL, bi.Arguments, nil
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
205
vendor/github.com/jackc/pgx/v5/conn.go
generated
vendored
205
vendor/github.com/jackc/pgx/v5/conn.go
generated
vendored
@@ -2,15 +2,13 @@ package pgx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/internal/anynil"
|
||||||
"github.com/jackc/pgx/v5/internal/sanitize"
|
"github.com/jackc/pgx/v5/internal/sanitize"
|
||||||
"github.com/jackc/pgx/v5/internal/stmtcache"
|
"github.com/jackc/pgx/v5/internal/stmtcache"
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
@@ -37,7 +35,7 @@ type ConnConfig struct {
|
|||||||
|
|
||||||
// DefaultQueryExecMode controls the default mode for executing queries. By default pgx uses the extended protocol
|
// DefaultQueryExecMode controls the default mode for executing queries. By default pgx uses the extended protocol
|
||||||
// and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as
|
// and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as
|
||||||
// PGBouncer. In this case it may be preferable to use QueryExecModeExec or QueryExecModeSimpleProtocol. The same
|
// PGBouncer. In this case it may be preferrable to use QueryExecModeExec or QueryExecModeSimpleProtocol. The same
|
||||||
// functionality can be controlled on a per query basis by passing a QueryExecMode as the first query argument.
|
// functionality can be controlled on a per query basis by passing a QueryExecMode as the first query argument.
|
||||||
DefaultQueryExecMode QueryExecMode
|
DefaultQueryExecMode QueryExecMode
|
||||||
|
|
||||||
@@ -101,33 +99,11 @@ func (ident Identifier) Sanitize() string {
|
|||||||
return strings.Join(parts, ".")
|
return strings.Join(parts, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// ErrNoRows occurs when rows are expected but none are returned.
|
||||||
// ErrNoRows occurs when rows are expected but none are returned.
|
var ErrNoRows = errors.New("no rows in result set")
|
||||||
ErrNoRows = newProxyErr(sql.ErrNoRows, "no rows in result set")
|
|
||||||
// ErrTooManyRows occurs when more rows than expected are returned.
|
|
||||||
ErrTooManyRows = errors.New("too many rows in result set")
|
|
||||||
)
|
|
||||||
|
|
||||||
func newProxyErr(background error, msg string) error {
|
var errDisabledStatementCache = fmt.Errorf("cannot use QueryExecModeCacheStatement with disabled statement cache")
|
||||||
return &proxyError{
|
var errDisabledDescriptionCache = fmt.Errorf("cannot use QueryExecModeCacheDescribe with disabled description cache")
|
||||||
msg: msg,
|
|
||||||
background: background,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type proxyError struct {
|
|
||||||
msg string
|
|
||||||
background error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err *proxyError) Error() string { return err.msg }
|
|
||||||
|
|
||||||
func (err *proxyError) Unwrap() error { return err.background }
|
|
||||||
|
|
||||||
var (
|
|
||||||
errDisabledStatementCache = fmt.Errorf("cannot use QueryExecModeCacheStatement with disabled statement cache")
|
|
||||||
errDisabledDescriptionCache = fmt.Errorf("cannot use QueryExecModeCacheDescribe with disabled description cache")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Connect establishes a connection with a PostgreSQL server with a connection string. See
|
// Connect establishes a connection with a PostgreSQL server with a connection string. See
|
||||||
// pgconn.Connect for details.
|
// pgconn.Connect for details.
|
||||||
@@ -293,7 +269,7 @@ func connect(ctx context.Context, config *ConnConfig) (c *Conn, err error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes a connection. It is safe to call Close on an already closed
|
// Close closes a connection. It is safe to call Close on a already closed
|
||||||
// connection.
|
// connection.
|
||||||
func (c *Conn) Close(ctx context.Context) error {
|
func (c *Conn) Close(ctx context.Context) error {
|
||||||
if c.IsClosed() {
|
if c.IsClosed() {
|
||||||
@@ -304,15 +280,12 @@ func (c *Conn) Close(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare creates a prepared statement with name and sql. sql can contain placeholders for bound parameters. These
|
// Prepare creates a prepared statement with name and sql. sql can contain placeholders
|
||||||
// placeholders are referenced positionally as $1, $2, etc. name can be used instead of sql with Query, QueryRow, and
|
// for bound parameters. These placeholders are referenced positional as $1, $2, etc.
|
||||||
// Exec to execute the statement. It can also be used with Batch.Queue.
|
|
||||||
//
|
//
|
||||||
// The underlying PostgreSQL identifier for the prepared statement will be name if name != sql or a digest of sql if
|
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same
|
||||||
// name == sql.
|
// name and sql arguments. This allows a code path to Prepare and Query/Exec without
|
||||||
//
|
// concern for if the statement has already been prepared.
|
||||||
// Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same name and sql arguments. This
|
|
||||||
// allows a code path to Prepare and Query/Exec without concern for if the statement has already been prepared.
|
|
||||||
func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error) {
|
func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error) {
|
||||||
if c.prepareTracer != nil {
|
if c.prepareTracer != nil {
|
||||||
ctx = c.prepareTracer.TracePrepareStart(ctx, c, TracePrepareStartData{Name: name, SQL: sql})
|
ctx = c.prepareTracer.TracePrepareStart(ctx, c, TracePrepareStartData{Name: name, SQL: sql})
|
||||||
@@ -334,48 +307,23 @@ func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.Statem
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
var psName, psKey string
|
sd, err = c.pgConn.Prepare(ctx, name, sql, nil)
|
||||||
if name == sql {
|
|
||||||
digest := sha256.Sum256([]byte(sql))
|
|
||||||
psName = "stmt_" + hex.EncodeToString(digest[0:24])
|
|
||||||
psKey = sql
|
|
||||||
} else {
|
|
||||||
psName = name
|
|
||||||
psKey = name
|
|
||||||
}
|
|
||||||
|
|
||||||
sd, err = c.pgConn.Prepare(ctx, psName, sql, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if psKey != "" {
|
if name != "" {
|
||||||
c.preparedStatements[psKey] = sd
|
c.preparedStatements[name] = sd
|
||||||
}
|
}
|
||||||
|
|
||||||
return sd, nil
|
return sd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deallocate releases a prepared statement. Calling Deallocate on a non-existent prepared statement will succeed.
|
// Deallocate released a prepared statement
|
||||||
func (c *Conn) Deallocate(ctx context.Context, name string) error {
|
func (c *Conn) Deallocate(ctx context.Context, name string) error {
|
||||||
var psName string
|
|
||||||
sd := c.preparedStatements[name]
|
|
||||||
if sd != nil {
|
|
||||||
psName = sd.Name
|
|
||||||
} else {
|
|
||||||
psName = name
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.pgConn.Deallocate(ctx, psName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sd != nil {
|
|
||||||
delete(c.preparedStatements, name)
|
delete(c.preparedStatements, name)
|
||||||
}
|
_, err := c.pgConn.Exec(ctx, "deallocate "+quoteIdentifier(name)).ReadAll()
|
||||||
|
return err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeallocateAll releases all previously prepared statements from the server and client, where it also resets the statement and description cache.
|
// DeallocateAll releases all previously prepared statements from the server and client, where it also resets the statement and description cache.
|
||||||
@@ -493,7 +441,7 @@ optionLoop:
|
|||||||
if queryRewriter != nil {
|
if queryRewriter != nil {
|
||||||
sql, arguments, err = queryRewriter.RewriteQuery(ctx, c, sql, arguments)
|
sql, arguments, err = queryRewriter.RewriteQuery(ctx, c, sql, arguments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pgconn.CommandTag{}, fmt.Errorf("rewrite query failed: %w", err)
|
return pgconn.CommandTag{}, fmt.Errorf("rewrite query failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,7 +461,7 @@ optionLoop:
|
|||||||
}
|
}
|
||||||
sd := c.statementCache.Get(sql)
|
sd := c.statementCache.Get(sql)
|
||||||
if sd == nil {
|
if sd == nil {
|
||||||
sd, err = c.Prepare(ctx, stmtcache.StatementName(sql), sql)
|
sd, err = c.Prepare(ctx, stmtcache.NextStatementName(), sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pgconn.CommandTag{}, err
|
return pgconn.CommandTag{}, err
|
||||||
}
|
}
|
||||||
@@ -531,7 +479,6 @@ optionLoop:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return pgconn.CommandTag{}, err
|
return pgconn.CommandTag{}, err
|
||||||
}
|
}
|
||||||
c.descriptionCache.Put(sd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.execParams(ctx, sd, arguments)
|
return c.execParams(ctx, sd, arguments)
|
||||||
@@ -626,35 +573,32 @@ type QueryExecMode int32
|
|||||||
const (
|
const (
|
||||||
_ QueryExecMode = iota
|
_ QueryExecMode = iota
|
||||||
|
|
||||||
// Automatically prepare and cache statements. This uses the extended protocol. Queries are executed in a single round
|
// Automatically prepare and cache statements. This uses the extended protocol. Queries are executed in a single
|
||||||
// trip after the statement is cached. This is the default. If the database schema is modified or the search_path is
|
// round trip after the statement is cached. This is the default.
|
||||||
// changed after a statement is cached then the first execution of a previously cached query may fail. e.g. If the
|
|
||||||
// number of columns returned by a "SELECT *" changes or the type of a column is changed.
|
|
||||||
QueryExecModeCacheStatement
|
QueryExecModeCacheStatement
|
||||||
|
|
||||||
// Cache statement descriptions (i.e. argument and result types) and assume they do not change. This uses the extended
|
// Cache statement descriptions (i.e. argument and result types) and assume they do not change. This uses the
|
||||||
// protocol. Queries are executed in a single round trip after the description is cached. If the database schema is
|
// extended protocol. Queries are executed in a single round trip after the description is cached. If the database
|
||||||
// modified or the search_path is changed after a statement is cached then the first execution of a previously cached
|
// schema is modified or the search_path is changed this may result in undetected result decoding errors.
|
||||||
// query may fail. e.g. If the number of columns returned by a "SELECT *" changes or the type of a column is changed.
|
|
||||||
QueryExecModeCacheDescribe
|
QueryExecModeCacheDescribe
|
||||||
|
|
||||||
// Get the statement description on every execution. This uses the extended protocol. Queries require two round trips
|
// Get the statement description on every execution. This uses the extended protocol. Queries require two round trips
|
||||||
// to execute. It does not use named prepared statements. But it does use the unnamed prepared statement to get the
|
// to execute. It does not use named prepared statements. But it does use the unnamed prepared statement to get the
|
||||||
// statement description on the first round trip and then uses it to execute the query on the second round trip. This
|
// statement description on the first round trip and then uses it to execute the query on the second round trip. This
|
||||||
// may cause problems with connection poolers that switch the underlying connection between round trips. It is safe
|
// may cause problems with connection poolers that switch the underlying connection between round trips. It is safe
|
||||||
// even when the database schema is modified concurrently.
|
// even when the the database schema is modified concurrently.
|
||||||
QueryExecModeDescribeExec
|
QueryExecModeDescribeExec
|
||||||
|
|
||||||
// Assume the PostgreSQL query parameter types based on the Go type of the arguments. This uses the extended protocol
|
// Assume the PostgreSQL query parameter types based on the Go type of the arguments. This uses the extended protocol
|
||||||
// with text formatted parameters and results. Queries are executed in a single round trip. Type mappings can be
|
// with text formatted parameters and results. Queries are executed in a single round trip. Type mappings can be
|
||||||
// registered with pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are
|
// registered with pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are
|
||||||
// unregistered or ambiguous. e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know
|
// unregistered or ambigious. e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know
|
||||||
// the PostgreSQL type can use a map[string]string directly as an argument. This mode cannot.
|
// the PostgreSQL type can use a map[string]string directly as an argument. This mode cannot.
|
||||||
QueryExecModeExec
|
QueryExecModeExec
|
||||||
|
|
||||||
// Use the simple protocol. Assume the PostgreSQL query parameter types based on the Go type of the arguments.
|
// Use the simple protocol. Assume the PostgreSQL query parameter types based on the Go type of the arguments.
|
||||||
// Queries are executed in a single round trip. Type mappings can be registered with
|
// Queries are executed in a single round trip. Type mappings can be registered with
|
||||||
// pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are unregistered or ambiguous.
|
// pgtype.Map.RegisterDefaultPgType. Queries will be rejected that have arguments that are unregistered or ambigious.
|
||||||
// e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know the PostgreSQL type can use
|
// e.g. A map[string]string may have the PostgreSQL type json or hstore. Modes that know the PostgreSQL type can use
|
||||||
// a map[string]string directly as an argument. This mode cannot.
|
// a map[string]string directly as an argument. This mode cannot.
|
||||||
//
|
//
|
||||||
@@ -761,7 +705,7 @@ optionLoop:
|
|||||||
sql, args, err = queryRewriter.RewriteQuery(ctx, c, sql, args)
|
sql, args, err = queryRewriter.RewriteQuery(ctx, c, sql, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rows := c.getRows(ctx, originalSQL, originalArgs)
|
rows := c.getRows(ctx, originalSQL, originalArgs)
|
||||||
err = fmt.Errorf("rewrite query failed: %w", err)
|
err = fmt.Errorf("rewrite query failed: %v", err)
|
||||||
rows.fatal(err)
|
rows.fatal(err)
|
||||||
return rows, err
|
return rows, err
|
||||||
}
|
}
|
||||||
@@ -773,6 +717,7 @@ optionLoop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.eqb.reset()
|
c.eqb.reset()
|
||||||
|
anynil.NormalizeSlice(args)
|
||||||
rows := c.getRows(ctx, sql, args)
|
rows := c.getRows(ctx, sql, args)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -862,6 +807,7 @@ func (c *Conn) getStatementDescription(
|
|||||||
mode QueryExecMode,
|
mode QueryExecMode,
|
||||||
sql string,
|
sql string,
|
||||||
) (sd *pgconn.StatementDescription, err error) {
|
) (sd *pgconn.StatementDescription, err error) {
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case QueryExecModeCacheStatement:
|
case QueryExecModeCacheStatement:
|
||||||
if c.statementCache == nil {
|
if c.statementCache == nil {
|
||||||
@@ -869,7 +815,7 @@ func (c *Conn) getStatementDescription(
|
|||||||
}
|
}
|
||||||
sd = c.statementCache.Get(sql)
|
sd = c.statementCache.Get(sql)
|
||||||
if sd == nil {
|
if sd == nil {
|
||||||
sd, err = c.Prepare(ctx, stmtcache.StatementName(sql), sql)
|
sd, err = c.Prepare(ctx, stmtcache.NextStatementName(), sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -919,14 +865,15 @@ func (c *Conn) SendBatch(ctx context.Context, b *Batch) (br BatchResults) {
|
|||||||
return &batchResults{ctx: ctx, conn: c, err: err}
|
return &batchResults{ctx: ctx, conn: c, err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bi := range b.QueuedQueries {
|
mode := c.config.DefaultQueryExecMode
|
||||||
|
|
||||||
|
for _, bi := range b.queuedQueries {
|
||||||
var queryRewriter QueryRewriter
|
var queryRewriter QueryRewriter
|
||||||
sql := bi.SQL
|
sql := bi.query
|
||||||
arguments := bi.Arguments
|
arguments := bi.arguments
|
||||||
|
|
||||||
optionLoop:
|
optionLoop:
|
||||||
for len(arguments) > 0 {
|
for len(arguments) > 0 {
|
||||||
// Update Batch.Queue function comment when additional options are implemented
|
|
||||||
switch arg := arguments[0].(type) {
|
switch arg := arguments[0].(type) {
|
||||||
case QueryRewriter:
|
case QueryRewriter:
|
||||||
queryRewriter = arg
|
queryRewriter = arg
|
||||||
@@ -940,23 +887,21 @@ func (c *Conn) SendBatch(ctx context.Context, b *Batch) (br BatchResults) {
|
|||||||
var err error
|
var err error
|
||||||
sql, arguments, err = queryRewriter.RewriteQuery(ctx, c, sql, arguments)
|
sql, arguments, err = queryRewriter.RewriteQuery(ctx, c, sql, arguments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &batchResults{ctx: ctx, conn: c, err: fmt.Errorf("rewrite query failed: %w", err)}
|
return &batchResults{ctx: ctx, conn: c, err: fmt.Errorf("rewrite query failed: %v", err)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bi.SQL = sql
|
bi.query = sql
|
||||||
bi.Arguments = arguments
|
bi.arguments = arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: changing mode per batch? Update Batch.Queue function comment when implemented
|
|
||||||
mode := c.config.DefaultQueryExecMode
|
|
||||||
if mode == QueryExecModeSimpleProtocol {
|
if mode == QueryExecModeSimpleProtocol {
|
||||||
return c.sendBatchQueryExecModeSimpleProtocol(ctx, b)
|
return c.sendBatchQueryExecModeSimpleProtocol(ctx, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// All other modes use extended protocol and thus can use prepared statements.
|
// All other modes use extended protocol and thus can use prepared statements.
|
||||||
for _, bi := range b.QueuedQueries {
|
for _, bi := range b.queuedQueries {
|
||||||
if sd, ok := c.preparedStatements[bi.SQL]; ok {
|
if sd, ok := c.preparedStatements[bi.query]; ok {
|
||||||
bi.sd = sd
|
bi.sd = sd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -977,11 +922,11 @@ func (c *Conn) SendBatch(ctx context.Context, b *Batch) (br BatchResults) {
|
|||||||
|
|
||||||
func (c *Conn) sendBatchQueryExecModeSimpleProtocol(ctx context.Context, b *Batch) *batchResults {
|
func (c *Conn) sendBatchQueryExecModeSimpleProtocol(ctx context.Context, b *Batch) *batchResults {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for i, bi := range b.QueuedQueries {
|
for i, bi := range b.queuedQueries {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
sb.WriteByte(';')
|
sb.WriteByte(';')
|
||||||
}
|
}
|
||||||
sql, err := c.sanitizeForSimpleQuery(bi.SQL, bi.Arguments...)
|
sql, err := c.sanitizeForSimpleQuery(bi.query, bi.arguments...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &batchResults{ctx: ctx, conn: c, err: err}
|
return &batchResults{ctx: ctx, conn: c, err: err}
|
||||||
}
|
}
|
||||||
@@ -1000,21 +945,21 @@ func (c *Conn) sendBatchQueryExecModeSimpleProtocol(ctx context.Context, b *Batc
|
|||||||
func (c *Conn) sendBatchQueryExecModeExec(ctx context.Context, b *Batch) *batchResults {
|
func (c *Conn) sendBatchQueryExecModeExec(ctx context.Context, b *Batch) *batchResults {
|
||||||
batch := &pgconn.Batch{}
|
batch := &pgconn.Batch{}
|
||||||
|
|
||||||
for _, bi := range b.QueuedQueries {
|
for _, bi := range b.queuedQueries {
|
||||||
sd := bi.sd
|
sd := bi.sd
|
||||||
if sd != nil {
|
if sd != nil {
|
||||||
err := c.eqb.Build(c.typeMap, sd, bi.Arguments)
|
err := c.eqb.Build(c.typeMap, sd, bi.arguments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &batchResults{ctx: ctx, conn: c, err: err}
|
return &batchResults{ctx: ctx, conn: c, err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.ExecPrepared(sd.Name, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
batch.ExecPrepared(sd.Name, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
||||||
} else {
|
} else {
|
||||||
err := c.eqb.Build(c.typeMap, nil, bi.Arguments)
|
err := c.eqb.Build(c.typeMap, nil, bi.arguments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &batchResults{ctx: ctx, conn: c, err: err}
|
return &batchResults{ctx: ctx, conn: c, err: err}
|
||||||
}
|
}
|
||||||
batch.ExecParams(bi.SQL, c.eqb.ParamValues, nil, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
batch.ExecParams(bi.query, c.eqb.ParamValues, nil, c.eqb.ParamFormats, c.eqb.ResultFormats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1039,18 +984,18 @@ func (c *Conn) sendBatchQueryExecModeCacheStatement(ctx context.Context, b *Batc
|
|||||||
distinctNewQueries := []*pgconn.StatementDescription{}
|
distinctNewQueries := []*pgconn.StatementDescription{}
|
||||||
distinctNewQueriesIdxMap := make(map[string]int)
|
distinctNewQueriesIdxMap := make(map[string]int)
|
||||||
|
|
||||||
for _, bi := range b.QueuedQueries {
|
for _, bi := range b.queuedQueries {
|
||||||
if bi.sd == nil {
|
if bi.sd == nil {
|
||||||
sd := c.statementCache.Get(bi.SQL)
|
sd := c.statementCache.Get(bi.query)
|
||||||
if sd != nil {
|
if sd != nil {
|
||||||
bi.sd = sd
|
bi.sd = sd
|
||||||
} else {
|
} else {
|
||||||
if idx, present := distinctNewQueriesIdxMap[bi.SQL]; present {
|
if idx, present := distinctNewQueriesIdxMap[bi.query]; present {
|
||||||
bi.sd = distinctNewQueries[idx]
|
bi.sd = distinctNewQueries[idx]
|
||||||
} else {
|
} else {
|
||||||
sd = &pgconn.StatementDescription{
|
sd = &pgconn.StatementDescription{
|
||||||
Name: stmtcache.StatementName(bi.SQL),
|
Name: stmtcache.NextStatementName(),
|
||||||
SQL: bi.SQL,
|
SQL: bi.query,
|
||||||
}
|
}
|
||||||
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
||||||
distinctNewQueries = append(distinctNewQueries, sd)
|
distinctNewQueries = append(distinctNewQueries, sd)
|
||||||
@@ -1071,17 +1016,17 @@ func (c *Conn) sendBatchQueryExecModeCacheDescribe(ctx context.Context, b *Batch
|
|||||||
distinctNewQueries := []*pgconn.StatementDescription{}
|
distinctNewQueries := []*pgconn.StatementDescription{}
|
||||||
distinctNewQueriesIdxMap := make(map[string]int)
|
distinctNewQueriesIdxMap := make(map[string]int)
|
||||||
|
|
||||||
for _, bi := range b.QueuedQueries {
|
for _, bi := range b.queuedQueries {
|
||||||
if bi.sd == nil {
|
if bi.sd == nil {
|
||||||
sd := c.descriptionCache.Get(bi.SQL)
|
sd := c.descriptionCache.Get(bi.query)
|
||||||
if sd != nil {
|
if sd != nil {
|
||||||
bi.sd = sd
|
bi.sd = sd
|
||||||
} else {
|
} else {
|
||||||
if idx, present := distinctNewQueriesIdxMap[bi.SQL]; present {
|
if idx, present := distinctNewQueriesIdxMap[bi.query]; present {
|
||||||
bi.sd = distinctNewQueries[idx]
|
bi.sd = distinctNewQueries[idx]
|
||||||
} else {
|
} else {
|
||||||
sd = &pgconn.StatementDescription{
|
sd = &pgconn.StatementDescription{
|
||||||
SQL: bi.SQL,
|
SQL: bi.query,
|
||||||
}
|
}
|
||||||
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
||||||
distinctNewQueries = append(distinctNewQueries, sd)
|
distinctNewQueries = append(distinctNewQueries, sd)
|
||||||
@@ -1098,13 +1043,13 @@ func (c *Conn) sendBatchQueryExecModeDescribeExec(ctx context.Context, b *Batch)
|
|||||||
distinctNewQueries := []*pgconn.StatementDescription{}
|
distinctNewQueries := []*pgconn.StatementDescription{}
|
||||||
distinctNewQueriesIdxMap := make(map[string]int)
|
distinctNewQueriesIdxMap := make(map[string]int)
|
||||||
|
|
||||||
for _, bi := range b.QueuedQueries {
|
for _, bi := range b.queuedQueries {
|
||||||
if bi.sd == nil {
|
if bi.sd == nil {
|
||||||
if idx, present := distinctNewQueriesIdxMap[bi.SQL]; present {
|
if idx, present := distinctNewQueriesIdxMap[bi.query]; present {
|
||||||
bi.sd = distinctNewQueries[idx]
|
bi.sd = distinctNewQueries[idx]
|
||||||
} else {
|
} else {
|
||||||
sd := &pgconn.StatementDescription{
|
sd := &pgconn.StatementDescription{
|
||||||
SQL: bi.SQL,
|
SQL: bi.query,
|
||||||
}
|
}
|
||||||
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
distinctNewQueriesIdxMap[sd.SQL] = len(distinctNewQueries)
|
||||||
distinctNewQueries = append(distinctNewQueries, sd)
|
distinctNewQueries = append(distinctNewQueries, sd)
|
||||||
@@ -1117,7 +1062,7 @@ func (c *Conn) sendBatchQueryExecModeDescribeExec(ctx context.Context, b *Batch)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, distinctNewQueries []*pgconn.StatementDescription, sdCache stmtcache.Cache) (pbr *pipelineBatchResults) {
|
func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, distinctNewQueries []*pgconn.StatementDescription, sdCache stmtcache.Cache) (pbr *pipelineBatchResults) {
|
||||||
pipeline := c.pgConn.StartPipeline(ctx)
|
pipeline := c.pgConn.StartPipeline(context.Background())
|
||||||
defer func() {
|
defer func() {
|
||||||
if pbr != nil && pbr.err != nil {
|
if pbr != nil && pbr.err != nil {
|
||||||
pipeline.Close()
|
pipeline.Close()
|
||||||
@@ -1170,11 +1115,11 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Queue the queries.
|
// Queue the queries.
|
||||||
for _, bi := range b.QueuedQueries {
|
for _, bi := range b.queuedQueries {
|
||||||
err := c.eqb.Build(c.typeMap, bi.sd, bi.Arguments)
|
err := c.eqb.Build(c.typeMap, bi.sd, bi.arguments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we wrap the error so we the user can understand which query failed inside the batch
|
// we wrap the error so we the user can understand which query failed inside the batch
|
||||||
err = fmt.Errorf("error building query %s: %w", bi.SQL, err)
|
err = fmt.Errorf("error building query %s: %w", bi.query, err)
|
||||||
return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true}
|
return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1219,15 +1164,7 @@ func (c *Conn) sanitizeForSimpleQuery(sql string, args ...any) (string, error) {
|
|||||||
return sanitize.SanitizeSQL(sql, valueArgs...)
|
return sanitize.SanitizeSQL(sql, valueArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadType inspects the database for typeName and produces a pgtype.Type suitable for registration. typeName must be
|
// LoadType inspects the database for typeName and produces a pgtype.Type suitable for registration.
|
||||||
// the name of a type where the underlying type(s) is already understood by pgx. It is for derived types. In particular,
|
|
||||||
// typeName must be one of the following:
|
|
||||||
// - An array type name of a type that is already registered. e.g. "_foo" when "foo" is registered.
|
|
||||||
// - A composite type name where all field types are already registered.
|
|
||||||
// - A domain type name where the base type is already registered.
|
|
||||||
// - An enum type name.
|
|
||||||
// - A range type name where the element type is already registered.
|
|
||||||
// - A multirange type name where the element type is already registered.
|
|
||||||
func (c *Conn) LoadType(ctx context.Context, typeName string) (*pgtype.Type, error) {
|
func (c *Conn) LoadType(ctx context.Context, typeName string) (*pgtype.Type, error) {
|
||||||
var oid uint32
|
var oid uint32
|
||||||
|
|
||||||
@@ -1370,17 +1307,17 @@ order by attnum`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) deallocateInvalidatedCachedStatements(ctx context.Context) error {
|
func (c *Conn) deallocateInvalidatedCachedStatements(ctx context.Context) error {
|
||||||
if txStatus := c.pgConn.TxStatus(); txStatus != 'I' && txStatus != 'T' {
|
if c.pgConn.TxStatus() != 'I' {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.descriptionCache != nil {
|
if c.descriptionCache != nil {
|
||||||
c.descriptionCache.RemoveInvalidated()
|
c.descriptionCache.HandleInvalidated()
|
||||||
}
|
}
|
||||||
|
|
||||||
var invalidatedStatements []*pgconn.StatementDescription
|
var invalidatedStatements []*pgconn.StatementDescription
|
||||||
if c.statementCache != nil {
|
if c.statementCache != nil {
|
||||||
invalidatedStatements = c.statementCache.GetInvalidated()
|
invalidatedStatements = c.statementCache.HandleInvalidated()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(invalidatedStatements) == 0 {
|
if len(invalidatedStatements) == 0 {
|
||||||
@@ -1392,6 +1329,7 @@ func (c *Conn) deallocateInvalidatedCachedStatements(ctx context.Context) error
|
|||||||
|
|
||||||
for _, sd := range invalidatedStatements {
|
for _, sd := range invalidatedStatements {
|
||||||
pipeline.SendDeallocate(sd.Name)
|
pipeline.SendDeallocate(sd.Name)
|
||||||
|
delete(c.preparedStatements, sd.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := pipeline.Sync()
|
err := pipeline.Sync()
|
||||||
@@ -1404,10 +1342,5 @@ func (c *Conn) deallocateInvalidatedCachedStatements(ctx context.Context) error
|
|||||||
return fmt.Errorf("failed to deallocate cached statement(s): %w", err)
|
return fmt.Errorf("failed to deallocate cached statement(s): %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.statementCache.RemoveInvalidated()
|
|
||||||
for _, sd := range invalidatedStatements {
|
|
||||||
delete(c.preparedStatements, sd.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
27
vendor/github.com/jackc/pgx/v5/copy_from.go
generated
vendored
27
vendor/github.com/jackc/pgx/v5/copy_from.go
generated
vendored
@@ -64,33 +64,6 @@ func (cts *copyFromSlice) Err() error {
|
|||||||
return cts.err
|
return cts.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyFromFunc returns a CopyFromSource interface that relies on nxtf for values.
|
|
||||||
// nxtf returns rows until it either signals an 'end of data' by returning row=nil and err=nil,
|
|
||||||
// or it returns an error. If nxtf returns an error, the copy is aborted.
|
|
||||||
func CopyFromFunc(nxtf func() (row []any, err error)) CopyFromSource {
|
|
||||||
return ©FromFunc{next: nxtf}
|
|
||||||
}
|
|
||||||
|
|
||||||
type copyFromFunc struct {
|
|
||||||
next func() ([]any, error)
|
|
||||||
valueRow []any
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *copyFromFunc) Next() bool {
|
|
||||||
g.valueRow, g.err = g.next()
|
|
||||||
// only return true if valueRow exists and no error
|
|
||||||
return g.valueRow != nil && g.err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *copyFromFunc) Values() ([]any, error) {
|
|
||||||
return g.valueRow, g.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *copyFromFunc) Err() error {
|
|
||||||
return g.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyFromSource is the interface used by *Conn.CopyFrom as the source for copy data.
|
// CopyFromSource is the interface used by *Conn.CopyFrom as the source for copy data.
|
||||||
type CopyFromSource interface {
|
type CopyFromSource interface {
|
||||||
// Next returns true if there is another row and makes the next row data
|
// Next returns true if there is another row and makes the next row data
|
||||||
|
|||||||
15
vendor/github.com/jackc/pgx/v5/doc.go
generated
vendored
15
vendor/github.com/jackc/pgx/v5/doc.go
generated
vendored
@@ -11,10 +11,9 @@ The primary way of establishing a connection is with [pgx.Connect]:
|
|||||||
|
|
||||||
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
|
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
|
||||||
|
|
||||||
The database connection string can be in URL or key/value format. Both PostgreSQL settings and pgx settings can be
|
The database connection string can be in URL or DSN format. Both PostgreSQL settings and pgx settings can be specified
|
||||||
specified here. In addition, a config struct can be created by [ParseConfig] and modified before establishing the
|
here. In addition, a config struct can be created by [ParseConfig] and modified before establishing the connection with
|
||||||
connection with [ConnectConfig] to configure settings such as tracing that cannot be configured with a connection
|
[ConnectConfig] to configure settings such as tracing that cannot be configured with a connection string.
|
||||||
string.
|
|
||||||
|
|
||||||
Connection Pool
|
Connection Pool
|
||||||
|
|
||||||
@@ -24,8 +23,8 @@ github.com/jackc/pgx/v5/pgxpool for a concurrency safe connection pool.
|
|||||||
Query Interface
|
Query Interface
|
||||||
|
|
||||||
pgx implements Query in the familiar database/sql style. However, pgx provides generic functions such as CollectRows and
|
pgx implements Query in the familiar database/sql style. However, pgx provides generic functions such as CollectRows and
|
||||||
ForEachRow that are a simpler and safer way of processing rows than manually calling defer rows.Close(), rows.Next(),
|
ForEachRow that are a simpler and safer way of processing rows than manually calling rows.Next(), rows.Scan, and
|
||||||
rows.Scan, and rows.Err().
|
rows.Err().
|
||||||
|
|
||||||
CollectRows can be used collect all returned rows into a slice.
|
CollectRows can be used collect all returned rows into a slice.
|
||||||
|
|
||||||
@@ -175,7 +174,7 @@ notification is received or the context is canceled.
|
|||||||
|
|
||||||
Tracing and Logging
|
Tracing and Logging
|
||||||
|
|
||||||
pgx supports tracing by setting ConnConfig.Tracer. To combine several tracers you can use the multitracer.Tracer.
|
pgx supports tracing by setting ConnConfig.Tracer.
|
||||||
|
|
||||||
In addition, the tracelog package provides the TraceLog type which lets a traditional logger act as a Tracer.
|
In addition, the tracelog package provides the TraceLog type which lets a traditional logger act as a Tracer.
|
||||||
|
|
||||||
@@ -188,7 +187,7 @@ implemented on top of pgconn. The Conn.PgConn() method can be used to access thi
|
|||||||
|
|
||||||
PgBouncer
|
PgBouncer
|
||||||
|
|
||||||
By default pgx automatically uses prepared statements. Prepared statements are incompatible with PgBouncer. This can be
|
By default pgx automatically uses prepared statements. Prepared statements are incompaptible with PgBouncer. This can be
|
||||||
disabled by setting a different QueryExecMode in ConnConfig.DefaultQueryExecMode.
|
disabled by setting a different QueryExecMode in ConnConfig.DefaultQueryExecMode.
|
||||||
*/
|
*/
|
||||||
package pgx
|
package pgx
|
||||||
|
|||||||
90
vendor/github.com/jackc/pgx/v5/extended_query_builder.go
generated
vendored
90
vendor/github.com/jackc/pgx/v5/extended_query_builder.go
generated
vendored
@@ -1,8 +1,10 @@
|
|||||||
package pgx
|
package pgx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/internal/anynil"
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
@@ -21,15 +23,10 @@ type ExtendedQueryBuilder struct {
|
|||||||
func (eqb *ExtendedQueryBuilder) Build(m *pgtype.Map, sd *pgconn.StatementDescription, args []any) error {
|
func (eqb *ExtendedQueryBuilder) Build(m *pgtype.Map, sd *pgconn.StatementDescription, args []any) error {
|
||||||
eqb.reset()
|
eqb.reset()
|
||||||
|
|
||||||
|
anynil.NormalizeSlice(args)
|
||||||
|
|
||||||
if sd == nil {
|
if sd == nil {
|
||||||
for i := range args {
|
return eqb.appendParamsForQueryExecModeExec(m, args)
|
||||||
err := eqb.appendParam(m, 0, pgtype.TextFormatCode, args[i])
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("failed to encode args[%d]: %w", i, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sd.ParamOIDs) != len(args) {
|
if len(sd.ParamOIDs) != len(args) {
|
||||||
@@ -39,7 +36,7 @@ func (eqb *ExtendedQueryBuilder) Build(m *pgtype.Map, sd *pgconn.StatementDescri
|
|||||||
for i := range args {
|
for i := range args {
|
||||||
err := eqb.appendParam(m, sd.ParamOIDs[i], -1, args[i])
|
err := eqb.appendParam(m, sd.ParamOIDs[i], -1, args[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to encode args[%d]: %w", i, err)
|
err = fmt.Errorf("failed to encode args[%d]: %v", i, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,6 +113,10 @@ func (eqb *ExtendedQueryBuilder) reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (eqb *ExtendedQueryBuilder) encodeExtendedParamValue(m *pgtype.Map, oid uint32, formatCode int16, arg any) ([]byte, error) {
|
func (eqb *ExtendedQueryBuilder) encodeExtendedParamValue(m *pgtype.Map, oid uint32, formatCode int16, arg any) ([]byte, error) {
|
||||||
|
if anynil.Is(arg) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
if eqb.paramValueBytes == nil {
|
if eqb.paramValueBytes == nil {
|
||||||
eqb.paramValueBytes = make([]byte, 0, 128)
|
eqb.paramValueBytes = make([]byte, 0, 128)
|
||||||
}
|
}
|
||||||
@@ -144,3 +145,74 @@ func (eqb *ExtendedQueryBuilder) chooseParameterFormatCode(m *pgtype.Map, oid ui
|
|||||||
|
|
||||||
return m.FormatCodeForOID(oid)
|
return m.FormatCodeForOID(oid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendParamsForQueryExecModeExec appends the args to eqb.
|
||||||
|
//
|
||||||
|
// Parameters must be encoded in the text format because of differences in type conversion between timestamps and
|
||||||
|
// dates. In QueryExecModeExec we don't know what the actual PostgreSQL type is. To determine the type we use the
|
||||||
|
// Go type to OID type mapping registered by RegisterDefaultPgType. However, the Go time.Time represents both
|
||||||
|
// PostgreSQL timestamp[tz] and date. To use the binary format we would need to also specify what the PostgreSQL
|
||||||
|
// type OID is. But that would mean telling PostgreSQL that we have sent a timestamp[tz] when what is needed is a date.
|
||||||
|
// This means that the value is converted from text to timestamp[tz] to date. This means it does a time zone conversion
|
||||||
|
// before converting it to date. This means that dates can be shifted by one day. In text format without that double
|
||||||
|
// type conversion it takes the date directly and ignores time zone (i.e. it works).
|
||||||
|
//
|
||||||
|
// Given that the whole point of QueryExecModeExec is to operate without having to know the PostgreSQL types there is
|
||||||
|
// no way to safely use binary or to specify the parameter OIDs.
|
||||||
|
func (eqb *ExtendedQueryBuilder) appendParamsForQueryExecModeExec(m *pgtype.Map, args []any) error {
|
||||||
|
for _, arg := range args {
|
||||||
|
if arg == nil {
|
||||||
|
err := eqb.appendParam(m, 0, TextFormatCode, arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dt, ok := m.TypeForValue(arg)
|
||||||
|
if !ok {
|
||||||
|
var tv pgtype.TextValuer
|
||||||
|
if tv, ok = arg.(pgtype.TextValuer); ok {
|
||||||
|
t, err := tv.TextValue()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, ok = m.TypeForOID(pgtype.TextOID)
|
||||||
|
if ok {
|
||||||
|
arg = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
var dv driver.Valuer
|
||||||
|
if dv, ok = arg.(driver.Valuer); ok {
|
||||||
|
v, err := dv.Value()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dt, ok = m.TypeForValue(v)
|
||||||
|
if ok {
|
||||||
|
arg = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
var str fmt.Stringer
|
||||||
|
if str, ok = arg.(fmt.Stringer); ok {
|
||||||
|
dt, ok = m.TypeForOID(pgtype.TextOID)
|
||||||
|
if ok {
|
||||||
|
arg = str.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return &unknownArgumentTypeQueryExecModeExecError{arg: arg}
|
||||||
|
}
|
||||||
|
err := eqb.appendParam(m, dt.OID, TextFormatCode, arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
9
vendor/github.com/jackc/pgx/v5/internal/sanitize/sanitize.go
generated
vendored
9
vendor/github.com/jackc/pgx/v5/internal/sanitize/sanitize.go
generated
vendored
@@ -35,11 +35,6 @@ func (q *Query) Sanitize(args ...any) (string, error) {
|
|||||||
str = part
|
str = part
|
||||||
case int:
|
case int:
|
||||||
argIdx := part - 1
|
argIdx := part - 1
|
||||||
|
|
||||||
if argIdx < 0 {
|
|
||||||
return "", fmt.Errorf("first sql argument must be > 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
if argIdx >= len(args) {
|
if argIdx >= len(args) {
|
||||||
return "", fmt.Errorf("insufficient arguments")
|
return "", fmt.Errorf("insufficient arguments")
|
||||||
}
|
}
|
||||||
@@ -63,10 +58,6 @@ func (q *Query) Sanitize(args ...any) (string, error) {
|
|||||||
return "", fmt.Errorf("invalid arg type: %T", arg)
|
return "", fmt.Errorf("invalid arg type: %T", arg)
|
||||||
}
|
}
|
||||||
argUse[argIdx] = true
|
argUse[argIdx] = true
|
||||||
|
|
||||||
// Prevent SQL injection via Line Comment Creation
|
|
||||||
// https://github.com/jackc/pgx/security/advisories/GHSA-m7wr-2xf7-cm9p
|
|
||||||
str = " " + str + " "
|
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("invalid Part type: %T", part)
|
return "", fmt.Errorf("invalid Part type: %T", part)
|
||||||
}
|
}
|
||||||
|
|||||||
22
vendor/github.com/jackc/pgx/v5/internal/stmtcache/lru_cache.go
generated
vendored
22
vendor/github.com/jackc/pgx/v5/internal/stmtcache/lru_cache.go
generated
vendored
@@ -34,8 +34,7 @@ func (c *LRUCache) Get(key string) *pgconn.StatementDescription {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache or
|
// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache.
|
||||||
// sd.SQL has been invalidated and HandleInvalidated has not been called yet.
|
|
||||||
func (c *LRUCache) Put(sd *pgconn.StatementDescription) {
|
func (c *LRUCache) Put(sd *pgconn.StatementDescription) {
|
||||||
if sd.SQL == "" {
|
if sd.SQL == "" {
|
||||||
panic("cannot store statement description with empty SQL")
|
panic("cannot store statement description with empty SQL")
|
||||||
@@ -45,13 +44,6 @@ func (c *LRUCache) Put(sd *pgconn.StatementDescription) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// The statement may have been invalidated but not yet handled. Do not readd it to the cache.
|
|
||||||
for _, invalidSD := range c.invalidStmts {
|
|
||||||
if invalidSD.SQL == sd.SQL {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.l.Len() == c.cap {
|
if c.l.Len() == c.cap {
|
||||||
c.invalidateOldest()
|
c.invalidateOldest()
|
||||||
}
|
}
|
||||||
@@ -81,16 +73,10 @@ func (c *LRUCache) InvalidateAll() {
|
|||||||
c.l = list.New()
|
c.l = list.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated.
|
func (c *LRUCache) HandleInvalidated() []*pgconn.StatementDescription {
|
||||||
func (c *LRUCache) GetInvalidated() []*pgconn.StatementDescription {
|
invalidStmts := c.invalidStmts
|
||||||
return c.invalidStmts
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveInvalidated removes all invalidated statement descriptions. No other calls to Cache must be made between a
|
|
||||||
// call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were
|
|
||||||
// never seen by the call to GetInvalidated.
|
|
||||||
func (c *LRUCache) RemoveInvalidated() {
|
|
||||||
c.invalidStmts = nil
|
c.invalidStmts = nil
|
||||||
|
return invalidStmts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the number of cached prepared statement descriptions.
|
// Len returns the number of cached prepared statement descriptions.
|
||||||
|
|||||||
40
vendor/github.com/jackc/pgx/v5/internal/stmtcache/stmtcache.go
generated
vendored
40
vendor/github.com/jackc/pgx/v5/internal/stmtcache/stmtcache.go
generated
vendored
@@ -2,17 +2,18 @@
|
|||||||
package stmtcache
|
package stmtcache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"strconv"
|
||||||
"encoding/hex"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatementName returns a statement name that will be stable for sql across multiple connections and program
|
var stmtCounter int64
|
||||||
// executions.
|
|
||||||
func StatementName(sql string) string {
|
// NextStatementName returns a statement name that will be unique for the lifetime of the program.
|
||||||
digest := sha256.Sum256([]byte(sql))
|
func NextStatementName() string {
|
||||||
return "stmtcache_" + hex.EncodeToString(digest[0:24])
|
n := atomic.AddInt64(&stmtCounter, 1)
|
||||||
|
return "stmtcache_" + strconv.FormatInt(n, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache caches statement descriptions.
|
// Cache caches statement descriptions.
|
||||||
@@ -29,13 +30,8 @@ type Cache interface {
|
|||||||
// InvalidateAll invalidates all statement descriptions.
|
// InvalidateAll invalidates all statement descriptions.
|
||||||
InvalidateAll()
|
InvalidateAll()
|
||||||
|
|
||||||
// GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated.
|
// HandleInvalidated returns a slice of all statement descriptions invalidated since the last call to HandleInvalidated.
|
||||||
GetInvalidated() []*pgconn.StatementDescription
|
HandleInvalidated() []*pgconn.StatementDescription
|
||||||
|
|
||||||
// RemoveInvalidated removes all invalidated statement descriptions. No other calls to Cache must be made between a
|
|
||||||
// call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were
|
|
||||||
// never seen by the call to GetInvalidated.
|
|
||||||
RemoveInvalidated()
|
|
||||||
|
|
||||||
// Len returns the number of cached prepared statement descriptions.
|
// Len returns the number of cached prepared statement descriptions.
|
||||||
Len() int
|
Len() int
|
||||||
@@ -43,3 +39,19 @@ type Cache interface {
|
|||||||
// Cap returns the maximum number of cached prepared statement descriptions.
|
// Cap returns the maximum number of cached prepared statement descriptions.
|
||||||
Cap() int
|
Cap() int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsStatementInvalid(err error) bool {
|
||||||
|
pgErr, ok := err.(*pgconn.PgError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/jackc/pgx/issues/1162
|
||||||
|
//
|
||||||
|
// We used to look for the message "cached plan must not change result type". However, that message can be localized.
|
||||||
|
// Unfortunately, error code "0A000" - "FEATURE NOT SUPPORTED" is used for many different errors and the only way to
|
||||||
|
// tell the difference is by the message. But all that happens is we clear a statement that we otherwise wouldn't
|
||||||
|
// have so it should be safe.
|
||||||
|
possibleInvalidCachedPlanError := pgErr.Code == "0A000"
|
||||||
|
return possibleInvalidCachedPlanError
|
||||||
|
}
|
||||||
|
|||||||
12
vendor/github.com/jackc/pgx/v5/internal/stmtcache/unlimited_cache.go
generated
vendored
12
vendor/github.com/jackc/pgx/v5/internal/stmtcache/unlimited_cache.go
generated
vendored
@@ -54,16 +54,10 @@ func (c *UnlimitedCache) InvalidateAll() {
|
|||||||
c.m = make(map[string]*pgconn.StatementDescription)
|
c.m = make(map[string]*pgconn.StatementDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated.
|
func (c *UnlimitedCache) HandleInvalidated() []*pgconn.StatementDescription {
|
||||||
func (c *UnlimitedCache) GetInvalidated() []*pgconn.StatementDescription {
|
invalidStmts := c.invalidStmts
|
||||||
return c.invalidStmts
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveInvalidated removes all invalidated statement descriptions. No other calls to Cache must be made between a
|
|
||||||
// call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were
|
|
||||||
// never seen by the call to GetInvalidated.
|
|
||||||
func (c *UnlimitedCache) RemoveInvalidated() {
|
|
||||||
c.invalidStmts = nil
|
c.invalidStmts = nil
|
||||||
|
return invalidStmts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the number of cached prepared statement descriptions.
|
// Len returns the number of cached prepared statement descriptions.
|
||||||
|
|||||||
62
vendor/github.com/jackc/pgx/v5/large_objects.go
generated
vendored
62
vendor/github.com/jackc/pgx/v5/large_objects.go
generated
vendored
@@ -4,15 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The PostgreSQL wire protocol has a limit of 1 GB - 1 per message. See definition of
|
|
||||||
// PQ_LARGE_MESSAGE_LIMIT in the PostgreSQL source code. To allow for the other data
|
|
||||||
// in the message,maxLargeObjectMessageLength should be no larger than 1 GB - 1 KB.
|
|
||||||
var maxLargeObjectMessageLength = 1024*1024*1024 - 1024
|
|
||||||
|
|
||||||
// LargeObjects is a structure used to access the large objects API. It is only valid within the transaction where it
|
// LargeObjects is a structure used to access the large objects API. It is only valid within the transaction where it
|
||||||
// was created.
|
// was created.
|
||||||
//
|
//
|
||||||
@@ -75,65 +68,32 @@ type LargeObject struct {
|
|||||||
|
|
||||||
// Write writes p to the large object and returns the number of bytes written and an error if not all of p was written.
|
// Write writes p to the large object and returns the number of bytes written and an error if not all of p was written.
|
||||||
func (o *LargeObject) Write(p []byte) (int, error) {
|
func (o *LargeObject) Write(p []byte) (int, error) {
|
||||||
nTotal := 0
|
|
||||||
for {
|
|
||||||
expected := len(p) - nTotal
|
|
||||||
if expected == 0 {
|
|
||||||
break
|
|
||||||
} else if expected > maxLargeObjectMessageLength {
|
|
||||||
expected = maxLargeObjectMessageLength
|
|
||||||
}
|
|
||||||
|
|
||||||
var n int
|
var n int
|
||||||
err := o.tx.QueryRow(o.ctx, "select lowrite($1, $2)", o.fd, p[nTotal:nTotal+expected]).Scan(&n)
|
err := o.tx.QueryRow(o.ctx, "select lowrite($1, $2)", o.fd, p).Scan(&n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nTotal, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if n < 0 {
|
if n < 0 {
|
||||||
return nTotal, errors.New("failed to write to large object")
|
return 0, errors.New("failed to write to large object")
|
||||||
}
|
}
|
||||||
|
|
||||||
nTotal += n
|
return n, nil
|
||||||
|
|
||||||
if n < expected {
|
|
||||||
return nTotal, errors.New("short write to large object")
|
|
||||||
} else if n > expected {
|
|
||||||
return nTotal, errors.New("invalid write to large object")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nTotal, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads up to len(p) bytes into p returning the number of bytes read.
|
// Read reads up to len(p) bytes into p returning the number of bytes read.
|
||||||
func (o *LargeObject) Read(p []byte) (int, error) {
|
func (o *LargeObject) Read(p []byte) (int, error) {
|
||||||
nTotal := 0
|
var res []byte
|
||||||
for {
|
err := o.tx.QueryRow(o.ctx, "select loread($1, $2)", o.fd, len(p)).Scan(&res)
|
||||||
expected := len(p) - nTotal
|
copy(p, res)
|
||||||
if expected == 0 {
|
|
||||||
break
|
|
||||||
} else if expected > maxLargeObjectMessageLength {
|
|
||||||
expected = maxLargeObjectMessageLength
|
|
||||||
}
|
|
||||||
|
|
||||||
res := pgtype.PreallocBytes(p[nTotal:])
|
|
||||||
err := o.tx.QueryRow(o.ctx, "select loread($1, $2)", o.fd, expected).Scan(&res)
|
|
||||||
// We compute expected so that it always fits into p, so it should never happen
|
|
||||||
// that PreallocBytes's ScanBytes had to allocate a new slice.
|
|
||||||
nTotal += len(res)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nTotal, err
|
return len(res), err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(res) < expected {
|
if len(res) < len(p) {
|
||||||
return nTotal, io.EOF
|
err = io.EOF
|
||||||
} else if len(res) > expected {
|
|
||||||
return nTotal, errors.New("invalid read of large object")
|
|
||||||
}
|
}
|
||||||
}
|
return len(res), err
|
||||||
|
|
||||||
return nTotal, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek moves the current location pointer to the new location specified by offset.
|
// Seek moves the current location pointer to the new location specified by offset.
|
||||||
|
|||||||
63
vendor/github.com/jackc/pgx/v5/named_args.go
generated
vendored
63
vendor/github.com/jackc/pgx/v5/named_args.go
generated
vendored
@@ -2,7 +2,6 @@ package pgx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -15,41 +14,10 @@ import (
|
|||||||
//
|
//
|
||||||
// conn.Query(ctx, "select * from widgets where foo = @foo and bar = @bar", pgx.NamedArgs{"foo": 1, "bar": 2})
|
// conn.Query(ctx, "select * from widgets where foo = @foo and bar = @bar", pgx.NamedArgs{"foo": 1, "bar": 2})
|
||||||
// conn.Query(ctx, "select * from widgets where foo = $1 and bar = $2", 1, 2)
|
// conn.Query(ctx, "select * from widgets where foo = $1 and bar = $2", 1, 2)
|
||||||
//
|
|
||||||
// Named placeholders are case sensitive and must start with a letter or underscore. Subsequent characters can be
|
|
||||||
// letters, numbers, or underscores.
|
|
||||||
type NamedArgs map[string]any
|
type NamedArgs map[string]any
|
||||||
|
|
||||||
// RewriteQuery implements the QueryRewriter interface.
|
// RewriteQuery implements the QueryRewriter interface.
|
||||||
func (na NamedArgs) RewriteQuery(ctx context.Context, conn *Conn, sql string, args []any) (newSQL string, newArgs []any, err error) {
|
func (na NamedArgs) RewriteQuery(ctx context.Context, conn *Conn, sql string, args []any) (newSQL string, newArgs []any, err error) {
|
||||||
return rewriteQuery(na, sql, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictNamedArgs can be used in the same way as NamedArgs, but provided arguments are also checked to include all
|
|
||||||
// named arguments that the sql query uses, and no extra arguments.
|
|
||||||
type StrictNamedArgs map[string]any
|
|
||||||
|
|
||||||
// RewriteQuery implements the QueryRewriter interface.
|
|
||||||
func (sna StrictNamedArgs) RewriteQuery(ctx context.Context, conn *Conn, sql string, args []any) (newSQL string, newArgs []any, err error) {
|
|
||||||
return rewriteQuery(sna, sql, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
type namedArg string
|
|
||||||
|
|
||||||
type sqlLexer struct {
|
|
||||||
src string
|
|
||||||
start int
|
|
||||||
pos int
|
|
||||||
nested int // multiline comment nesting level.
|
|
||||||
stateFn stateFn
|
|
||||||
parts []any
|
|
||||||
|
|
||||||
nameToOrdinal map[namedArg]int
|
|
||||||
}
|
|
||||||
|
|
||||||
type stateFn func(*sqlLexer) stateFn
|
|
||||||
|
|
||||||
func rewriteQuery(na map[string]any, sql string, isStrict bool) (newSQL string, newArgs []any, err error) {
|
|
||||||
l := &sqlLexer{
|
l := &sqlLexer{
|
||||||
src: sql,
|
src: sql,
|
||||||
stateFn: rawState,
|
stateFn: rawState,
|
||||||
@@ -73,24 +41,27 @@ func rewriteQuery(na map[string]any, sql string, isStrict bool) (newSQL string,
|
|||||||
|
|
||||||
newArgs = make([]any, len(l.nameToOrdinal))
|
newArgs = make([]any, len(l.nameToOrdinal))
|
||||||
for name, ordinal := range l.nameToOrdinal {
|
for name, ordinal := range l.nameToOrdinal {
|
||||||
var found bool
|
newArgs[ordinal-1] = na[string(name)]
|
||||||
newArgs[ordinal-1], found = na[string(name)]
|
|
||||||
if isStrict && !found {
|
|
||||||
return "", nil, fmt.Errorf("argument %s found in sql query but not present in StrictNamedArgs", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isStrict {
|
|
||||||
for name := range na {
|
|
||||||
if _, found := l.nameToOrdinal[namedArg(name)]; !found {
|
|
||||||
return "", nil, fmt.Errorf("argument %s of StrictNamedArgs not found in sql query", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String(), newArgs, nil
|
return sb.String(), newArgs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type namedArg string
|
||||||
|
|
||||||
|
type sqlLexer struct {
|
||||||
|
src string
|
||||||
|
start int
|
||||||
|
pos int
|
||||||
|
nested int // multiline comment nesting level.
|
||||||
|
stateFn stateFn
|
||||||
|
parts []any
|
||||||
|
|
||||||
|
nameToOrdinal map[namedArg]int
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateFn func(*sqlLexer) stateFn
|
||||||
|
|
||||||
func rawState(l *sqlLexer) stateFn {
|
func rawState(l *sqlLexer) stateFn {
|
||||||
for {
|
for {
|
||||||
r, width := utf8.DecodeRuneInString(l.src[l.pos:])
|
r, width := utf8.DecodeRuneInString(l.src[l.pos:])
|
||||||
@@ -109,7 +80,7 @@ func rawState(l *sqlLexer) stateFn {
|
|||||||
return doubleQuoteState
|
return doubleQuoteState
|
||||||
case '@':
|
case '@':
|
||||||
nextRune, _ := utf8.DecodeRuneInString(l.src[l.pos:])
|
nextRune, _ := utf8.DecodeRuneInString(l.src[l.pos:])
|
||||||
if isLetter(nextRune) || nextRune == '_' {
|
if isLetter(nextRune) {
|
||||||
if l.pos-l.start > 0 {
|
if l.pos-l.start > 0 {
|
||||||
l.parts = append(l.parts, l.src[l.start:l.pos-width])
|
l.parts = append(l.parts, l.src[l.start:l.pos-width])
|
||||||
}
|
}
|
||||||
|
|||||||
4
vendor/github.com/jackc/pgx/v5/pgconn/auth_scram.go
generated
vendored
4
vendor/github.com/jackc/pgx/v5/pgconn/auth_scram.go
generated
vendored
@@ -47,7 +47,7 @@ func (c *PgConn) scramAuth(serverAuthMechanisms []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive server-first-message payload in an AuthenticationSASLContinue.
|
// Receive server-first-message payload in a AuthenticationSASLContinue.
|
||||||
saslContinue, err := c.rxSASLContinue()
|
saslContinue, err := c.rxSASLContinue()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -67,7 +67,7 @@ func (c *PgConn) scramAuth(serverAuthMechanisms []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive server-final-message payload in an AuthenticationSASLFinal.
|
// Receive server-final-message payload in a AuthenticationSASLFinal.
|
||||||
saslFinal, err := c.rxSASLFinal()
|
saslFinal, err := c.rxSASLFinal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
145
vendor/github.com/jackc/pgx/v5/pgconn/config.go
generated
vendored
145
vendor/github.com/jackc/pgx/v5/pgconn/config.go
generated
vendored
@@ -19,7 +19,6 @@ import (
|
|||||||
|
|
||||||
"github.com/jackc/pgpassfile"
|
"github.com/jackc/pgpassfile"
|
||||||
"github.com/jackc/pgservicefile"
|
"github.com/jackc/pgservicefile"
|
||||||
"github.com/jackc/pgx/v5/pgconn/ctxwatch"
|
|
||||||
"github.com/jackc/pgx/v5/pgproto3"
|
"github.com/jackc/pgx/v5/pgproto3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,11 +39,6 @@ type Config struct {
|
|||||||
DialFunc DialFunc // e.g. net.Dialer.DialContext
|
DialFunc DialFunc // e.g. net.Dialer.DialContext
|
||||||
LookupFunc LookupFunc // e.g. net.Resolver.LookupHost
|
LookupFunc LookupFunc // e.g. net.Resolver.LookupHost
|
||||||
BuildFrontend BuildFrontendFunc
|
BuildFrontend BuildFrontendFunc
|
||||||
|
|
||||||
// BuildContextWatcherHandler is called to create a ContextWatcherHandler for a connection. The handler is called
|
|
||||||
// when a context passed to a PgConn method is canceled.
|
|
||||||
BuildContextWatcherHandler func(*PgConn) ctxwatch.Handler
|
|
||||||
|
|
||||||
RuntimeParams map[string]string // Run-time parameters to set on connection as session default values (e.g. search_path or application_name)
|
RuntimeParams map[string]string // Run-time parameters to set on connection as session default values (e.g. search_path or application_name)
|
||||||
|
|
||||||
KerberosSrvName string
|
KerberosSrvName string
|
||||||
@@ -66,17 +60,12 @@ type Config struct {
|
|||||||
// OnNotification is a callback function called when a notification from the LISTEN/NOTIFY system is received.
|
// OnNotification is a callback function called when a notification from the LISTEN/NOTIFY system is received.
|
||||||
OnNotification NotificationHandler
|
OnNotification NotificationHandler
|
||||||
|
|
||||||
// OnPgError is a callback function called when a Postgres error is received by the server. The default handler will close
|
|
||||||
// the connection on any FATAL errors. If you override this handler you should call the previously set handler or ensure
|
|
||||||
// that you close on FATAL errors by returning false.
|
|
||||||
OnPgError PgErrorHandler
|
|
||||||
|
|
||||||
createdByParseConfig bool // Used to enforce created by ParseConfig rule.
|
createdByParseConfig bool // Used to enforce created by ParseConfig rule.
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseConfigOptions contains options that control how a config is built such as GetSSLPassword.
|
// ParseConfigOptions contains options that control how a config is built such as GetSSLPassword.
|
||||||
type ParseConfigOptions struct {
|
type ParseConfigOptions struct {
|
||||||
// GetSSLPassword gets the password to decrypt a SSL client certificate. This is analogous to the libpq function
|
// GetSSLPassword gets the password to decrypt a SSL client certificate. This is analogous to the the libpq function
|
||||||
// PQsetSSLKeyPassHook_OpenSSL.
|
// PQsetSSLKeyPassHook_OpenSSL.
|
||||||
GetSSLPassword GetSSLPasswordFunc
|
GetSSLPassword GetSSLPasswordFunc
|
||||||
}
|
}
|
||||||
@@ -118,14 +107,6 @@ type FallbackConfig struct {
|
|||||||
TLSConfig *tls.Config // nil disables TLS
|
TLSConfig *tls.Config // nil disables TLS
|
||||||
}
|
}
|
||||||
|
|
||||||
// connectOneConfig is the configuration for a single attempt to connect to a single host.
|
|
||||||
type connectOneConfig struct {
|
|
||||||
network string
|
|
||||||
address string
|
|
||||||
originalHostname string // original hostname before resolving
|
|
||||||
tlsConfig *tls.Config // nil disables TLS
|
|
||||||
}
|
|
||||||
|
|
||||||
// isAbsolutePath checks if the provided value is an absolute path either
|
// isAbsolutePath checks if the provided value is an absolute path either
|
||||||
// beginning with a forward slash (as on Linux-based systems) or with a capital
|
// beginning with a forward slash (as on Linux-based systems) or with a capital
|
||||||
// letter A-Z followed by a colon and a backslash, e.g., "C:\", (as on Windows).
|
// letter A-Z followed by a colon and a backslash, e.g., "C:\", (as on Windows).
|
||||||
@@ -160,11 +141,11 @@ func NetworkAddress(host string, port uint16) (network, address string) {
|
|||||||
|
|
||||||
// ParseConfig builds a *Config from connString with similar behavior to the PostgreSQL standard C library libpq. It
|
// ParseConfig builds a *Config from connString with similar behavior to the PostgreSQL standard C library libpq. It
|
||||||
// uses the same defaults as libpq (e.g. port=5432) and understands most PG* environment variables. ParseConfig closely
|
// uses the same defaults as libpq (e.g. port=5432) and understands most PG* environment variables. ParseConfig closely
|
||||||
// matches the parsing behavior of libpq. connString may either be in URL format or keyword = value format. See
|
// matches the parsing behavior of libpq. connString may either be in URL format or keyword = value format (DSN style).
|
||||||
// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for details. connString also may be empty
|
// See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for details. connString also may be
|
||||||
// to only read from the environment. If a password is not supplied it will attempt to read the .pgpass file.
|
// empty to only read from the environment. If a password is not supplied it will attempt to read the .pgpass file.
|
||||||
//
|
//
|
||||||
// # Example Keyword/Value
|
// # Example DSN
|
||||||
// user=jack password=secret host=pg.example.com port=5432 dbname=mydb sslmode=verify-ca
|
// user=jack password=secret host=pg.example.com port=5432 dbname=mydb sslmode=verify-ca
|
||||||
//
|
//
|
||||||
// # Example URL
|
// # Example URL
|
||||||
@@ -183,7 +164,7 @@ func NetworkAddress(host string, port uint16) (network, address string) {
|
|||||||
// postgres://jack:secret@foo.example.com:5432,bar.example.com:5432/mydb
|
// postgres://jack:secret@foo.example.com:5432,bar.example.com:5432/mydb
|
||||||
//
|
//
|
||||||
// ParseConfig currently recognizes the following environment variable and their parameter key word equivalents passed
|
// ParseConfig currently recognizes the following environment variable and their parameter key word equivalents passed
|
||||||
// via database URL or keyword/value:
|
// via database URL or DSN:
|
||||||
//
|
//
|
||||||
// PGHOST
|
// PGHOST
|
||||||
// PGPORT
|
// PGPORT
|
||||||
@@ -247,16 +228,16 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
|||||||
connStringSettings := make(map[string]string)
|
connStringSettings := make(map[string]string)
|
||||||
if connString != "" {
|
if connString != "" {
|
||||||
var err error
|
var err error
|
||||||
// connString may be a database URL or in PostgreSQL keyword/value format
|
// connString may be a database URL or a DSN
|
||||||
if strings.HasPrefix(connString, "postgres://") || strings.HasPrefix(connString, "postgresql://") {
|
if strings.HasPrefix(connString, "postgres://") || strings.HasPrefix(connString, "postgresql://") {
|
||||||
connStringSettings, err = parseURLSettings(connString)
|
connStringSettings, err = parseURLSettings(connString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &ParseConfigError{ConnString: connString, msg: "failed to parse as URL", err: err}
|
return nil, &parseConfigError{connString: connString, msg: "failed to parse as URL", err: err}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
connStringSettings, err = parseKeywordValueSettings(connString)
|
connStringSettings, err = parseDSNSettings(connString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &ParseConfigError{ConnString: connString, msg: "failed to parse as keyword/value", err: err}
|
return nil, &parseConfigError{connString: connString, msg: "failed to parse as DSN", err: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,7 +246,7 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
|||||||
if service, present := settings["service"]; present {
|
if service, present := settings["service"]; present {
|
||||||
serviceSettings, err := parseServiceSettings(settings["servicefile"], service)
|
serviceSettings, err := parseServiceSettings(settings["servicefile"], service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &ParseConfigError{ConnString: connString, msg: "failed to read service", err: err}
|
return nil, &parseConfigError{connString: connString, msg: "failed to read service", err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
settings = mergeSettings(defaultSettings, envSettings, serviceSettings, connStringSettings)
|
settings = mergeSettings(defaultSettings, envSettings, serviceSettings, connStringSettings)
|
||||||
@@ -280,22 +261,12 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
|||||||
BuildFrontend: func(r io.Reader, w io.Writer) *pgproto3.Frontend {
|
BuildFrontend: func(r io.Reader, w io.Writer) *pgproto3.Frontend {
|
||||||
return pgproto3.NewFrontend(r, w)
|
return pgproto3.NewFrontend(r, w)
|
||||||
},
|
},
|
||||||
BuildContextWatcherHandler: func(pgConn *PgConn) ctxwatch.Handler {
|
|
||||||
return &DeadlineContextWatcherHandler{Conn: pgConn.conn}
|
|
||||||
},
|
|
||||||
OnPgError: func(_ *PgConn, pgErr *PgError) bool {
|
|
||||||
// we want to automatically close any fatal errors
|
|
||||||
if strings.EqualFold(pgErr.Severity, "FATAL") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if connectTimeoutSetting, present := settings["connect_timeout"]; present {
|
if connectTimeoutSetting, present := settings["connect_timeout"]; present {
|
||||||
connectTimeout, err := parseConnectTimeoutSetting(connectTimeoutSetting)
|
connectTimeout, err := parseConnectTimeoutSetting(connectTimeoutSetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &ParseConfigError{ConnString: connString, msg: "invalid connect_timeout", err: err}
|
return nil, &parseConfigError{connString: connString, msg: "invalid connect_timeout", err: err}
|
||||||
}
|
}
|
||||||
config.ConnectTimeout = connectTimeout
|
config.ConnectTimeout = connectTimeout
|
||||||
config.DialFunc = makeConnectTimeoutDialFunc(connectTimeout)
|
config.DialFunc = makeConnectTimeoutDialFunc(connectTimeout)
|
||||||
@@ -357,7 +328,7 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
|||||||
|
|
||||||
port, err := parsePort(portStr)
|
port, err := parsePort(portStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &ParseConfigError{ConnString: connString, msg: "invalid port", err: err}
|
return nil, &parseConfigError{connString: connString, msg: "invalid port", err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfigs []*tls.Config
|
var tlsConfigs []*tls.Config
|
||||||
@@ -369,7 +340,7 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
|||||||
var err error
|
var err error
|
||||||
tlsConfigs, err = configTLS(settings, host, options)
|
tlsConfigs, err = configTLS(settings, host, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &ParseConfigError{ConnString: connString, msg: "failed to configure TLS", err: err}
|
return nil, &parseConfigError{connString: connString, msg: "failed to configure TLS", err: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +384,7 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con
|
|||||||
case "any":
|
case "any":
|
||||||
// do nothing
|
// do nothing
|
||||||
default:
|
default:
|
||||||
return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("unknown target_session_attrs value: %v", tsa)}
|
return nil, &parseConfigError{connString: connString, msg: fmt.Sprintf("unknown target_session_attrs value: %v", tsa)}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
@@ -467,17 +438,14 @@ func parseEnvSettings() map[string]string {
|
|||||||
func parseURLSettings(connString string) (map[string]string, error) {
|
func parseURLSettings(connString string) (map[string]string, error) {
|
||||||
settings := make(map[string]string)
|
settings := make(map[string]string)
|
||||||
|
|
||||||
parsedURL, err := url.Parse(connString)
|
url, err := url.Parse(connString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if urlErr := new(url.Error); errors.As(err, &urlErr) {
|
|
||||||
return nil, urlErr.Err
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsedURL.User != nil {
|
if url.User != nil {
|
||||||
settings["user"] = parsedURL.User.Username()
|
settings["user"] = url.User.Username()
|
||||||
if password, present := parsedURL.User.Password(); present {
|
if password, present := url.User.Password(); present {
|
||||||
settings["password"] = password
|
settings["password"] = password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -485,7 +453,7 @@ func parseURLSettings(connString string) (map[string]string, error) {
|
|||||||
// Handle multiple host:port's in url.Host by splitting them into host,host,host and port,port,port.
|
// Handle multiple host:port's in url.Host by splitting them into host,host,host and port,port,port.
|
||||||
var hosts []string
|
var hosts []string
|
||||||
var ports []string
|
var ports []string
|
||||||
for _, host := range strings.Split(parsedURL.Host, ",") {
|
for _, host := range strings.Split(url.Host, ",") {
|
||||||
if host == "" {
|
if host == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -511,7 +479,7 @@ func parseURLSettings(connString string) (map[string]string, error) {
|
|||||||
settings["port"] = strings.Join(ports, ",")
|
settings["port"] = strings.Join(ports, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
database := strings.TrimLeft(parsedURL.Path, "/")
|
database := strings.TrimLeft(url.Path, "/")
|
||||||
if database != "" {
|
if database != "" {
|
||||||
settings["database"] = database
|
settings["database"] = database
|
||||||
}
|
}
|
||||||
@@ -520,7 +488,7 @@ func parseURLSettings(connString string) (map[string]string, error) {
|
|||||||
"dbname": "database",
|
"dbname": "database",
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range parsedURL.Query() {
|
for k, v := range url.Query() {
|
||||||
if k2, present := nameMap[k]; present {
|
if k2, present := nameMap[k]; present {
|
||||||
k = k2
|
k = k2
|
||||||
}
|
}
|
||||||
@@ -537,7 +505,7 @@ func isIPOnly(host string) bool {
|
|||||||
|
|
||||||
var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
|
var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
|
||||||
|
|
||||||
func parseKeywordValueSettings(s string) (map[string]string, error) {
|
func parseDSNSettings(s string) (map[string]string, error) {
|
||||||
settings := make(map[string]string)
|
settings := make(map[string]string)
|
||||||
|
|
||||||
nameMap := map[string]string{
|
nameMap := map[string]string{
|
||||||
@@ -548,7 +516,7 @@ func parseKeywordValueSettings(s string) (map[string]string, error) {
|
|||||||
var key, val string
|
var key, val string
|
||||||
eqIdx := strings.IndexRune(s, '=')
|
eqIdx := strings.IndexRune(s, '=')
|
||||||
if eqIdx < 0 {
|
if eqIdx < 0 {
|
||||||
return nil, errors.New("invalid keyword/value")
|
return nil, errors.New("invalid dsn")
|
||||||
}
|
}
|
||||||
|
|
||||||
key = strings.Trim(s[:eqIdx], " \t\n\r\v\f")
|
key = strings.Trim(s[:eqIdx], " \t\n\r\v\f")
|
||||||
@@ -600,7 +568,7 @@ func parseKeywordValueSettings(s string) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return nil, errors.New("invalid keyword/value")
|
return nil, errors.New("invalid dsn")
|
||||||
}
|
}
|
||||||
|
|
||||||
settings[key] = val
|
settings[key] = val
|
||||||
@@ -657,36 +625,6 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P
|
|||||||
|
|
||||||
tlsConfig := &tls.Config{}
|
tlsConfig := &tls.Config{}
|
||||||
|
|
||||||
if sslrootcert != "" {
|
|
||||||
var caCertPool *x509.CertPool
|
|
||||||
|
|
||||||
if sslrootcert == "system" {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
caCertPool, err = x509.SystemCertPool()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to load system certificate pool: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sslmode = "verify-full"
|
|
||||||
} else {
|
|
||||||
caCertPool = x509.NewCertPool()
|
|
||||||
|
|
||||||
caPath := sslrootcert
|
|
||||||
caCert, err := os.ReadFile(caPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to read CA file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !caCertPool.AppendCertsFromPEM(caCert) {
|
|
||||||
return nil, errors.New("unable to add CA to cert pool")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig.RootCAs = caCertPool
|
|
||||||
tlsConfig.ClientCAs = caCertPool
|
|
||||||
}
|
|
||||||
|
|
||||||
switch sslmode {
|
switch sslmode {
|
||||||
case "disable":
|
case "disable":
|
||||||
return []*tls.Config{nil}, nil
|
return []*tls.Config{nil}, nil
|
||||||
@@ -744,6 +682,23 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P
|
|||||||
return nil, errors.New("sslmode is invalid")
|
return nil, errors.New("sslmode is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sslrootcert != "" {
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
|
||||||
|
caPath := sslrootcert
|
||||||
|
caCert, err := os.ReadFile(caPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read CA file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !caCertPool.AppendCertsFromPEM(caCert) {
|
||||||
|
return nil, errors.New("unable to add CA to cert pool")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig.RootCAs = caCertPool
|
||||||
|
tlsConfig.ClientCAs = caCertPool
|
||||||
|
}
|
||||||
|
|
||||||
if (sslcert != "" && sslkey == "") || (sslcert == "" && sslkey != "") {
|
if (sslcert != "" && sslkey == "") || (sslcert == "" && sslkey != "") {
|
||||||
return nil, errors.New(`both "sslcert" and "sslkey" are required`)
|
return nil, errors.New(`both "sslcert" and "sslkey" are required`)
|
||||||
}
|
}
|
||||||
@@ -754,9 +709,6 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P
|
|||||||
return nil, fmt.Errorf("unable to read sslkey: %w", err)
|
return nil, fmt.Errorf("unable to read sslkey: %w", err)
|
||||||
}
|
}
|
||||||
block, _ := pem.Decode(buf)
|
block, _ := pem.Decode(buf)
|
||||||
if block == nil {
|
|
||||||
return nil, errors.New("failed to decode sslkey")
|
|
||||||
}
|
|
||||||
var pemKey []byte
|
var pemKey []byte
|
||||||
var decryptedKey []byte
|
var decryptedKey []byte
|
||||||
var decryptedError error
|
var decryptedError error
|
||||||
@@ -833,8 +785,7 @@ func parsePort(s string) (uint16, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeDefaultDialer() *net.Dialer {
|
func makeDefaultDialer() *net.Dialer {
|
||||||
// rely on GOLANG KeepAlive settings
|
return &net.Dialer{KeepAlive: 5 * time.Minute}
|
||||||
return &net.Dialer{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeDefaultResolver() *net.Resolver {
|
func makeDefaultResolver() *net.Resolver {
|
||||||
@@ -858,7 +809,7 @@ func makeConnectTimeoutDialFunc(timeout time.Duration) DialFunc {
|
|||||||
return d.DialContext
|
return d.DialContext
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsReadWrite is a ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsReadWrite is an ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=read-write.
|
// target_session_attrs=read-write.
|
||||||
func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read()
|
||||||
@@ -873,7 +824,7 @@ func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, pgConn *PgC
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsReadOnly is a ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsReadOnly is an ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=read-only.
|
// target_session_attrs=read-only.
|
||||||
func ValidateConnectTargetSessionAttrsReadOnly(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsReadOnly(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read()
|
||||||
@@ -888,7 +839,7 @@ func ValidateConnectTargetSessionAttrsReadOnly(ctx context.Context, pgConn *PgCo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsStandby is a ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsStandby is an ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=standby.
|
// target_session_attrs=standby.
|
||||||
func ValidateConnectTargetSessionAttrsStandby(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsStandby(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
||||||
@@ -903,7 +854,7 @@ func ValidateConnectTargetSessionAttrsStandby(ctx context.Context, pgConn *PgCon
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsPrimary is a ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsPrimary is an ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=primary.
|
// target_session_attrs=primary.
|
||||||
func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
||||||
@@ -918,7 +869,7 @@ func ValidateConnectTargetSessionAttrsPrimary(ctx context.Context, pgConn *PgCon
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateConnectTargetSessionAttrsPreferStandby is a ValidateConnectFunc that implements libpq compatible
|
// ValidateConnectTargetSessionAttrsPreferStandby is an ValidateConnectFunc that implements libpq compatible
|
||||||
// target_session_attrs=prefer-standby.
|
// target_session_attrs=prefer-standby.
|
||||||
func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn *PgConn) error {
|
func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn *PgConn) error {
|
||||||
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
result := pgConn.ExecParams(ctx, "select pg_is_in_recovery()", nil, nil, nil, nil).Read()
|
||||||
|
|||||||
16
vendor/github.com/jackc/pgx/v5/pgconn/doc.go
generated
vendored
16
vendor/github.com/jackc/pgx/v5/pgconn/doc.go
generated
vendored
@@ -5,8 +5,8 @@ nearly the same level is the C library libpq.
|
|||||||
|
|
||||||
Establishing a Connection
|
Establishing a Connection
|
||||||
|
|
||||||
Use Connect to establish a connection. It accepts a connection string in URL or keyword/value format and will read the
|
Use Connect to establish a connection. It accepts a connection string in URL or DSN and will read the environment for
|
||||||
environment for libpq style environment variables.
|
libpq style environment variables.
|
||||||
|
|
||||||
Executing a Query
|
Executing a Query
|
||||||
|
|
||||||
@@ -20,17 +20,13 @@ result. The ReadAll method reads all query results into memory.
|
|||||||
|
|
||||||
Pipeline Mode
|
Pipeline Mode
|
||||||
|
|
||||||
Pipeline mode allows sending queries without having read the results of previously sent queries. It allows control of
|
Pipeline mode allows sending queries without having read the results of previously sent queries. It allows
|
||||||
exactly how many and when network round trips occur.
|
control of exactly how many and when network round trips occur.
|
||||||
|
|
||||||
Context Support
|
Context Support
|
||||||
|
|
||||||
All potentially blocking operations take a context.Context. The default behavior when a context is canceled is for the
|
All potentially blocking operations take a context.Context. If a context is canceled while the method is in progress the
|
||||||
method to immediately return. In most circumstances, this will also close the underlying connection. This behavior can
|
method immediately returns. In most circumstances, this will close the underlying connection.
|
||||||
be customized by using BuildContextWatcherHandler on the Config to create a ctxwatch.Handler with different behavior.
|
|
||||||
This can be especially useful when queries that are frequently canceled and the overhead of creating new connections is
|
|
||||||
a problem. DeadlineContextWatcherHandler and CancelRequestContextWatcherHandler can be used to introduce a delay before
|
|
||||||
interrupting the query in such a way as to close the connection.
|
|
||||||
|
|
||||||
The CancelRequest method may be used to request the PostgreSQL server cancel an in-progress query without forcing the
|
The CancelRequest method may be used to request the PostgreSQL server cancel an in-progress query without forcing the
|
||||||
client to abort.
|
client to abort.
|
||||||
|
|||||||
70
vendor/github.com/jackc/pgx/v5/pgconn/errors.go
generated
vendored
70
vendor/github.com/jackc/pgx/v5/pgconn/errors.go
generated
vendored
@@ -12,14 +12,13 @@ import (
|
|||||||
|
|
||||||
// SafeToRetry checks if the err is guaranteed to have occurred before sending any data to the server.
|
// SafeToRetry checks if the err is guaranteed to have occurred before sending any data to the server.
|
||||||
func SafeToRetry(err error) bool {
|
func SafeToRetry(err error) bool {
|
||||||
var retryableErr interface{ SafeToRetry() bool }
|
if e, ok := err.(interface{ SafeToRetry() bool }); ok {
|
||||||
if errors.As(err, &retryableErr) {
|
return e.SafeToRetry()
|
||||||
return retryableErr.SafeToRetry()
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout checks if err was caused by a timeout. To be specific, it is true if err was caused within pgconn by a
|
// Timeout checks if err was was caused by a timeout. To be specific, it is true if err was caused within pgconn by a
|
||||||
// context.DeadlineExceeded or an implementer of net.Error where Timeout() is true.
|
// context.DeadlineExceeded or an implementer of net.Error where Timeout() is true.
|
||||||
func Timeout(err error) bool {
|
func Timeout(err error) bool {
|
||||||
var timeoutErr *errTimeout
|
var timeoutErr *errTimeout
|
||||||
@@ -31,7 +30,6 @@ func Timeout(err error) bool {
|
|||||||
// detailed field description.
|
// detailed field description.
|
||||||
type PgError struct {
|
type PgError struct {
|
||||||
Severity string
|
Severity string
|
||||||
SeverityUnlocalized string
|
|
||||||
Code string
|
Code string
|
||||||
Message string
|
Message string
|
||||||
Detail string
|
Detail string
|
||||||
@@ -59,37 +57,22 @@ func (pe *PgError) SQLState() string {
|
|||||||
return pe.Code
|
return pe.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectError is the error returned when a connection attempt fails.
|
type connectError struct {
|
||||||
type ConnectError struct {
|
config *Config
|
||||||
Config *Config // The configuration that was used in the connection attempt.
|
msg string
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ConnectError) Error() string {
|
func (e *connectError) Error() string {
|
||||||
prefix := fmt.Sprintf("failed to connect to `user=%s database=%s`:", e.Config.User, e.Config.Database)
|
sb := &strings.Builder{}
|
||||||
details := e.err.Error()
|
fmt.Fprintf(sb, "failed to connect to `host=%s user=%s database=%s`: %s", e.config.Host, e.config.User, e.config.Database, e.msg)
|
||||||
if strings.Contains(details, "\n") {
|
if e.err != nil {
|
||||||
return prefix + "\n\t" + strings.ReplaceAll(details, "\n", "\n\t")
|
fmt.Fprintf(sb, " (%s)", e.err.Error())
|
||||||
} else {
|
|
||||||
return prefix + " " + details
|
|
||||||
}
|
}
|
||||||
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ConnectError) Unwrap() error {
|
func (e *connectError) Unwrap() error {
|
||||||
return e.err
|
|
||||||
}
|
|
||||||
|
|
||||||
type perDialConnectError struct {
|
|
||||||
address string
|
|
||||||
originalHostname string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *perDialConnectError) Error() string {
|
|
||||||
return fmt.Sprintf("%s (%s): %s", e.address, e.originalHostname, e.err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *perDialConnectError) Unwrap() error {
|
|
||||||
return e.err
|
return e.err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,38 +88,33 @@ func (e *connLockError) Error() string {
|
|||||||
return e.status
|
return e.status
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseConfigError is the error returned when a connection string cannot be parsed.
|
type parseConfigError struct {
|
||||||
type ParseConfigError struct {
|
connString string
|
||||||
ConnString string // The connection string that could not be parsed.
|
|
||||||
msg string
|
msg string
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ParseConfigError) Error() string {
|
func (e *parseConfigError) Error() string {
|
||||||
// Now that ParseConfigError is public and ConnString is available to the developer, perhaps it would be better only
|
connString := redactPW(e.connString)
|
||||||
// return a static string. That would ensure that the error message cannot leak a password. The ConnString field would
|
|
||||||
// allow access to the original string if desired and Unwrap would allow access to the underlying error.
|
|
||||||
connString := redactPW(e.ConnString)
|
|
||||||
if e.err == nil {
|
if e.err == nil {
|
||||||
return fmt.Sprintf("cannot parse `%s`: %s", connString, e.msg)
|
return fmt.Sprintf("cannot parse `%s`: %s", connString, e.msg)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("cannot parse `%s`: %s (%s)", connString, e.msg, e.err.Error())
|
return fmt.Sprintf("cannot parse `%s`: %s (%s)", connString, e.msg, e.err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ParseConfigError) Unwrap() error {
|
func (e *parseConfigError) Unwrap() error {
|
||||||
return e.err
|
return e.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeTimeoutError(ctx context.Context, err error) error {
|
func normalizeTimeoutError(ctx context.Context, err error) error {
|
||||||
var netErr net.Error
|
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
|
||||||
if ctx.Err() == context.Canceled {
|
if ctx.Err() == context.Canceled {
|
||||||
// Since the timeout was caused by a context cancellation, the actual error is context.Canceled not the timeout error.
|
// Since the timeout was caused by a context cancellation, the actual error is context.Canceled not the timeout error.
|
||||||
return context.Canceled
|
return context.Canceled
|
||||||
} else if ctx.Err() == context.DeadlineExceeded {
|
} else if ctx.Err() == context.DeadlineExceeded {
|
||||||
return &errTimeout{err: ctx.Err()}
|
return &errTimeout{err: ctx.Err()}
|
||||||
} else {
|
} else {
|
||||||
return &errTimeout{err: netErr}
|
return &errTimeout{err: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -211,10 +189,10 @@ func redactPW(connString string) string {
|
|||||||
return redactURL(u)
|
return redactURL(u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
quotedKV := regexp.MustCompile(`password='[^']*'`)
|
quotedDSN := regexp.MustCompile(`password='[^']*'`)
|
||||||
connString = quotedKV.ReplaceAllLiteralString(connString, "password=xxxxx")
|
connString = quotedDSN.ReplaceAllLiteralString(connString, "password=xxxxx")
|
||||||
plainKV := regexp.MustCompile(`password=[^ ]*`)
|
plainDSN := regexp.MustCompile(`password=[^ ]*`)
|
||||||
connString = plainKV.ReplaceAllLiteralString(connString, "password=xxxxx")
|
connString = plainDSN.ReplaceAllLiteralString(connString, "password=xxxxx")
|
||||||
brokenURL := regexp.MustCompile(`:[^:@]+?@`)
|
brokenURL := regexp.MustCompile(`:[^:@]+?@`)
|
||||||
connString = brokenURL.ReplaceAllLiteralString(connString, ":xxxxxx@")
|
connString = brokenURL.ReplaceAllLiteralString(connString, ":xxxxxx@")
|
||||||
return connString
|
return connString
|
||||||
|
|||||||
568
vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go
generated
vendored
568
vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go
generated
vendored
@@ -18,8 +18,8 @@ import (
|
|||||||
|
|
||||||
"github.com/jackc/pgx/v5/internal/iobufpool"
|
"github.com/jackc/pgx/v5/internal/iobufpool"
|
||||||
"github.com/jackc/pgx/v5/internal/pgio"
|
"github.com/jackc/pgx/v5/internal/pgio"
|
||||||
"github.com/jackc/pgx/v5/pgconn/ctxwatch"
|
|
||||||
"github.com/jackc/pgx/v5/pgconn/internal/bgreader"
|
"github.com/jackc/pgx/v5/pgconn/internal/bgreader"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn/internal/ctxwatch"
|
||||||
"github.com/jackc/pgx/v5/pgproto3"
|
"github.com/jackc/pgx/v5/pgproto3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,12 +52,6 @@ type LookupFunc func(ctx context.Context, host string) (addrs []string, err erro
|
|||||||
// BuildFrontendFunc is a function that can be used to create Frontend implementation for connection.
|
// BuildFrontendFunc is a function that can be used to create Frontend implementation for connection.
|
||||||
type BuildFrontendFunc func(r io.Reader, w io.Writer) *pgproto3.Frontend
|
type BuildFrontendFunc func(r io.Reader, w io.Writer) *pgproto3.Frontend
|
||||||
|
|
||||||
// PgErrorHandler is a function that handles errors returned from Postgres. This function must return true to keep
|
|
||||||
// the connection open. Returning false will cause the connection to be closed immediately. You should return
|
|
||||||
// false on any FATAL-severity errors. This will not receive network errors. The *PgConn is provided so the handler is
|
|
||||||
// aware of the origin of the error, but it must not invoke any query method.
|
|
||||||
type PgErrorHandler func(*PgConn, *PgError) bool
|
|
||||||
|
|
||||||
// NoticeHandler is a function that can handle notices received from the PostgreSQL server. Notices can be received at
|
// NoticeHandler is a function that can handle notices received from the PostgreSQL server. Notices can be received at
|
||||||
// any time, usually during handling of a query response. The *PgConn is provided so the handler is aware of the origin
|
// any time, usually during handling of a query response. The *PgConn is provided so the handler is aware of the origin
|
||||||
// of the notice, but it must not invoke any query method. Be aware that this is distinct from LISTEN/NOTIFY
|
// of the notice, but it must not invoke any query method. Be aware that this is distinct from LISTEN/NOTIFY
|
||||||
@@ -80,9 +74,6 @@ type PgConn struct {
|
|||||||
frontend *pgproto3.Frontend
|
frontend *pgproto3.Frontend
|
||||||
bgReader *bgreader.BGReader
|
bgReader *bgreader.BGReader
|
||||||
slowWriteTimer *time.Timer
|
slowWriteTimer *time.Timer
|
||||||
bgReaderStarted chan struct{}
|
|
||||||
|
|
||||||
customData map[string]any
|
|
||||||
|
|
||||||
config *Config
|
config *Config
|
||||||
|
|
||||||
@@ -105,9 +96,8 @@ type PgConn struct {
|
|||||||
cleanupDone chan struct{}
|
cleanupDone chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or keyword/value
|
// Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or DSN format)
|
||||||
// format) to provide configuration. See documentation for [ParseConfig] for details. ctx can be used to cancel a
|
// to provide configuration. See documentation for [ParseConfig] for details. ctx can be used to cancel a connect attempt.
|
||||||
// connect attempt.
|
|
||||||
func Connect(ctx context.Context, connString string) (*PgConn, error) {
|
func Connect(ctx context.Context, connString string) (*PgConn, error) {
|
||||||
config, err := ParseConfig(connString)
|
config, err := ParseConfig(connString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -117,9 +107,9 @@ func Connect(ctx context.Context, connString string) (*PgConn, error) {
|
|||||||
return ConnectConfig(ctx, config)
|
return ConnectConfig(ctx, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or keyword/value
|
// Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or DSN format)
|
||||||
// format) and ParseConfigOptions to provide additional configuration. See documentation for [ParseConfig] for details.
|
// and ParseConfigOptions to provide additional configuration. See documentation for [ParseConfig] for details. ctx can be
|
||||||
// ctx can be used to cancel a connect attempt.
|
// used to cancel a connect attempt.
|
||||||
func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptions ParseConfigOptions) (*PgConn, error) {
|
func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptions ParseConfigOptions) (*PgConn, error) {
|
||||||
config, err := ParseConfigWithOptions(connString, parseConfigOptions)
|
config, err := ParseConfigWithOptions(connString, parseConfigOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -134,46 +124,15 @@ func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptio
|
|||||||
//
|
//
|
||||||
// If config.Fallbacks are present they will sequentially be tried in case of error establishing network connection. An
|
// If config.Fallbacks are present they will sequentially be tried in case of error establishing network connection. An
|
||||||
// authentication error will terminate the chain of attempts (like libpq:
|
// authentication error will terminate the chain of attempts (like libpq:
|
||||||
// https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS) and be returned as the error.
|
// https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS) and be returned as the error. Otherwise,
|
||||||
func ConnectConfig(ctx context.Context, config *Config) (*PgConn, error) {
|
// if all attempts fail the last error is returned.
|
||||||
|
func ConnectConfig(octx context.Context, config *Config) (pgConn *PgConn, err error) {
|
||||||
// Default values are set in ParseConfig. Enforce initial creation by ParseConfig rather than setting defaults from
|
// Default values are set in ParseConfig. Enforce initial creation by ParseConfig rather than setting defaults from
|
||||||
// zero values.
|
// zero values.
|
||||||
if !config.createdByParseConfig {
|
if !config.createdByParseConfig {
|
||||||
panic("config must be created by ParseConfig")
|
panic("config must be created by ParseConfig")
|
||||||
}
|
}
|
||||||
|
|
||||||
var allErrors []error
|
|
||||||
|
|
||||||
connectConfigs, errs := buildConnectOneConfigs(ctx, config)
|
|
||||||
if len(errs) > 0 {
|
|
||||||
allErrors = append(allErrors, errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(connectConfigs) == 0 {
|
|
||||||
return nil, &ConnectError{Config: config, err: fmt.Errorf("hostname resolving error: %w", errors.Join(allErrors...))}
|
|
||||||
}
|
|
||||||
|
|
||||||
pgConn, errs := connectPreferred(ctx, config, connectConfigs)
|
|
||||||
if len(errs) > 0 {
|
|
||||||
allErrors = append(allErrors, errs...)
|
|
||||||
return nil, &ConnectError{Config: config, err: errors.Join(allErrors...)}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.AfterConnect != nil {
|
|
||||||
err := config.AfterConnect(ctx, pgConn)
|
|
||||||
if err != nil {
|
|
||||||
pgConn.conn.Close()
|
|
||||||
return nil, &ConnectError{Config: config, err: fmt.Errorf("AfterConnect error: %w", err)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pgConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildConnectOneConfigs resolves hostnames and builds a list of connectOneConfigs to try connecting to. It returns a
|
|
||||||
// slice of successfully resolved connectOneConfigs and a slice of errors. It is possible for both slices to contain
|
|
||||||
// values if some hosts were successfully resolved and others were not.
|
|
||||||
func buildConnectOneConfigs(ctx context.Context, config *Config) ([]*connectOneConfig, []error) {
|
|
||||||
// Simplify usage by treating primary config and fallbacks the same.
|
// Simplify usage by treating primary config and fallbacks the same.
|
||||||
fallbackConfigs := []*FallbackConfig{
|
fallbackConfigs := []*FallbackConfig{
|
||||||
{
|
{
|
||||||
@@ -183,28 +142,95 @@ func buildConnectOneConfigs(ctx context.Context, config *Config) ([]*connectOneC
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
fallbackConfigs = append(fallbackConfigs, config.Fallbacks...)
|
fallbackConfigs = append(fallbackConfigs, config.Fallbacks...)
|
||||||
|
ctx := octx
|
||||||
|
fallbackConfigs, err = expandWithIPs(ctx, config.LookupFunc, fallbackConfigs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &connectError{config: config, msg: "hostname resolving error", err: err}
|
||||||
|
}
|
||||||
|
|
||||||
var configs []*connectOneConfig
|
if len(fallbackConfigs) == 0 {
|
||||||
|
return nil, &connectError{config: config, msg: "hostname resolving error", err: errors.New("ip addr wasn't found")}
|
||||||
|
}
|
||||||
|
|
||||||
var allErrors []error
|
foundBestServer := false
|
||||||
|
var fallbackConfig *FallbackConfig
|
||||||
|
for i, fc := range fallbackConfigs {
|
||||||
|
// ConnectTimeout restricts the whole connection process.
|
||||||
|
if config.ConnectTimeout != 0 {
|
||||||
|
// create new context first time or when previous host was different
|
||||||
|
if i == 0 || (fallbackConfigs[i].Host != fallbackConfigs[i-1].Host) {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithTimeout(octx, config.ConnectTimeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx = octx
|
||||||
|
}
|
||||||
|
pgConn, err = connect(ctx, config, fc, false)
|
||||||
|
if err == nil {
|
||||||
|
foundBestServer = true
|
||||||
|
break
|
||||||
|
} else if pgerr, ok := err.(*PgError); ok {
|
||||||
|
err = &connectError{config: config, msg: "server error", err: pgerr}
|
||||||
|
const ERRCODE_INVALID_PASSWORD = "28P01" // wrong password
|
||||||
|
const ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION = "28000" // wrong password or bad pg_hba.conf settings
|
||||||
|
const ERRCODE_INVALID_CATALOG_NAME = "3D000" // db does not exist
|
||||||
|
const ERRCODE_INSUFFICIENT_PRIVILEGE = "42501" // missing connect privilege
|
||||||
|
if pgerr.Code == ERRCODE_INVALID_PASSWORD ||
|
||||||
|
pgerr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION && fc.TLSConfig != nil ||
|
||||||
|
pgerr.Code == ERRCODE_INVALID_CATALOG_NAME ||
|
||||||
|
pgerr.Code == ERRCODE_INSUFFICIENT_PRIVILEGE {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if cerr, ok := err.(*connectError); ok {
|
||||||
|
if _, ok := cerr.err.(*NotPreferredError); ok {
|
||||||
|
fallbackConfig = fc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, fb := range fallbackConfigs {
|
if !foundBestServer && fallbackConfig != nil {
|
||||||
|
pgConn, err = connect(ctx, config, fallbackConfig, true)
|
||||||
|
if pgerr, ok := err.(*PgError); ok {
|
||||||
|
err = &connectError{config: config, msg: "server error", err: pgerr}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // no need to wrap in connectError because it will already be wrapped in all cases except PgError
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.AfterConnect != nil {
|
||||||
|
err := config.AfterConnect(ctx, pgConn)
|
||||||
|
if err != nil {
|
||||||
|
pgConn.conn.Close()
|
||||||
|
return nil, &connectError{config: config, msg: "AfterConnect error", err: err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pgConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandWithIPs(ctx context.Context, lookupFn LookupFunc, fallbacks []*FallbackConfig) ([]*FallbackConfig, error) {
|
||||||
|
var configs []*FallbackConfig
|
||||||
|
|
||||||
|
var lookupErrors []error
|
||||||
|
|
||||||
|
for _, fb := range fallbacks {
|
||||||
// skip resolve for unix sockets
|
// skip resolve for unix sockets
|
||||||
if isAbsolutePath(fb.Host) {
|
if isAbsolutePath(fb.Host) {
|
||||||
network, address := NetworkAddress(fb.Host, fb.Port)
|
configs = append(configs, &FallbackConfig{
|
||||||
configs = append(configs, &connectOneConfig{
|
Host: fb.Host,
|
||||||
network: network,
|
Port: fb.Port,
|
||||||
address: address,
|
TLSConfig: fb.TLSConfig,
|
||||||
originalHostname: fb.Host,
|
|
||||||
tlsConfig: fb.TLSConfig,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := config.LookupFunc(ctx, fb.Host)
|
ips, err := lookupFn(ctx, fb.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
allErrors = append(allErrors, err)
|
lookupErrors = append(lookupErrors, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,139 +239,70 @@ func buildConnectOneConfigs(ctx context.Context, config *Config) ([]*connectOneC
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
port, err := strconv.ParseUint(splitPort, 10, 16)
|
port, err := strconv.ParseUint(splitPort, 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, []error{fmt.Errorf("error parsing port (%s) from lookup: %w", splitPort, err)}
|
return nil, fmt.Errorf("error parsing port (%s) from lookup: %w", splitPort, err)
|
||||||
}
|
}
|
||||||
network, address := NetworkAddress(splitIP, uint16(port))
|
configs = append(configs, &FallbackConfig{
|
||||||
configs = append(configs, &connectOneConfig{
|
Host: splitIP,
|
||||||
network: network,
|
Port: uint16(port),
|
||||||
address: address,
|
TLSConfig: fb.TLSConfig,
|
||||||
originalHostname: fb.Host,
|
|
||||||
tlsConfig: fb.TLSConfig,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
network, address := NetworkAddress(ip, fb.Port)
|
configs = append(configs, &FallbackConfig{
|
||||||
configs = append(configs, &connectOneConfig{
|
Host: ip,
|
||||||
network: network,
|
Port: fb.Port,
|
||||||
address: address,
|
TLSConfig: fb.TLSConfig,
|
||||||
originalHostname: fb.Host,
|
|
||||||
tlsConfig: fb.TLSConfig,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return configs, allErrors
|
// See https://github.com/jackc/pgx/issues/1464. When Go 1.20 can be used in pgx consider using errors.Join so all
|
||||||
|
// errors are reported.
|
||||||
|
if len(configs) == 0 && len(lookupErrors) > 0 {
|
||||||
|
return nil, lookupErrors[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// connectPreferred attempts to connect to the preferred host from connectOneConfigs. The connections are attempted in
|
func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig,
|
||||||
// order. If a connection is successful it is returned. If no connection is successful then all errors are returned. If
|
|
||||||
// a connection attempt returns a [NotPreferredError], then that host will be used if no other hosts are successful.
|
|
||||||
func connectPreferred(ctx context.Context, config *Config, connectOneConfigs []*connectOneConfig) (*PgConn, []error) {
|
|
||||||
octx := ctx
|
|
||||||
var allErrors []error
|
|
||||||
|
|
||||||
var fallbackConnectOneConfig *connectOneConfig
|
|
||||||
for i, c := range connectOneConfigs {
|
|
||||||
// ConnectTimeout restricts the whole connection process.
|
|
||||||
if config.ConnectTimeout != 0 {
|
|
||||||
// create new context first time or when previous host was different
|
|
||||||
if i == 0 || (connectOneConfigs[i].address != connectOneConfigs[i-1].address) {
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
ctx, cancel = context.WithTimeout(octx, config.ConnectTimeout)
|
|
||||||
defer cancel()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx = octx
|
|
||||||
}
|
|
||||||
|
|
||||||
pgConn, err := connectOne(ctx, config, c, false)
|
|
||||||
if pgConn != nil {
|
|
||||||
return pgConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
allErrors = append(allErrors, err)
|
|
||||||
|
|
||||||
var pgErr *PgError
|
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
const ERRCODE_INVALID_PASSWORD = "28P01" // wrong password
|
|
||||||
const ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION = "28000" // wrong password or bad pg_hba.conf settings
|
|
||||||
const ERRCODE_INVALID_CATALOG_NAME = "3D000" // db does not exist
|
|
||||||
const ERRCODE_INSUFFICIENT_PRIVILEGE = "42501" // missing connect privilege
|
|
||||||
if pgErr.Code == ERRCODE_INVALID_PASSWORD ||
|
|
||||||
pgErr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION && c.tlsConfig != nil ||
|
|
||||||
pgErr.Code == ERRCODE_INVALID_CATALOG_NAME ||
|
|
||||||
pgErr.Code == ERRCODE_INSUFFICIENT_PRIVILEGE {
|
|
||||||
return nil, allErrors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var npErr *NotPreferredError
|
|
||||||
if errors.As(err, &npErr) {
|
|
||||||
fallbackConnectOneConfig = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fallbackConnectOneConfig != nil {
|
|
||||||
pgConn, err := connectOne(ctx, config, fallbackConnectOneConfig, true)
|
|
||||||
if err == nil {
|
|
||||||
return pgConn, nil
|
|
||||||
}
|
|
||||||
allErrors = append(allErrors, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, allErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
// connectOne makes one connection attempt to a single host.
|
|
||||||
func connectOne(ctx context.Context, config *Config, connectConfig *connectOneConfig,
|
|
||||||
ignoreNotPreferredErr bool,
|
ignoreNotPreferredErr bool,
|
||||||
) (*PgConn, error) {
|
) (*PgConn, error) {
|
||||||
pgConn := new(PgConn)
|
pgConn := new(PgConn)
|
||||||
pgConn.config = config
|
pgConn.config = config
|
||||||
pgConn.cleanupDone = make(chan struct{})
|
pgConn.cleanupDone = make(chan struct{})
|
||||||
pgConn.customData = make(map[string]any)
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
network, address := NetworkAddress(fallbackConfig.Host, fallbackConfig.Port)
|
||||||
newPerDialConnectError := func(msg string, err error) *perDialConnectError {
|
netConn, err := config.DialFunc(ctx, network, address)
|
||||||
err = normalizeTimeoutError(ctx, err)
|
|
||||||
e := &perDialConnectError{address: connectConfig.address, originalHostname: connectConfig.originalHostname, err: fmt.Errorf("%s: %w", msg, err)}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
pgConn.conn, err = config.DialFunc(ctx, connectConfig.network, connectConfig.address)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newPerDialConnectError("dial error", err)
|
return nil, &connectError{config: config, msg: "dial error", err: normalizeTimeoutError(ctx, err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if connectConfig.tlsConfig != nil {
|
pgConn.conn = netConn
|
||||||
pgConn.contextWatcher = ctxwatch.NewContextWatcher(&DeadlineContextWatcherHandler{Conn: pgConn.conn})
|
pgConn.contextWatcher = newContextWatcher(netConn)
|
||||||
pgConn.contextWatcher.Watch(ctx)
|
pgConn.contextWatcher.Watch(ctx)
|
||||||
tlsConn, err := startTLS(pgConn.conn, connectConfig.tlsConfig)
|
|
||||||
|
if fallbackConfig.TLSConfig != nil {
|
||||||
|
nbTLSConn, err := startTLS(netConn, fallbackConfig.TLSConfig)
|
||||||
pgConn.contextWatcher.Unwatch() // Always unwatch `netConn` after TLS.
|
pgConn.contextWatcher.Unwatch() // Always unwatch `netConn` after TLS.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pgConn.conn.Close()
|
netConn.Close()
|
||||||
return nil, newPerDialConnectError("tls error", err)
|
return nil, &connectError{config: config, msg: "tls error", err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
pgConn.conn = tlsConn
|
pgConn.conn = nbTLSConn
|
||||||
}
|
pgConn.contextWatcher = newContextWatcher(nbTLSConn)
|
||||||
|
|
||||||
pgConn.contextWatcher = ctxwatch.NewContextWatcher(config.BuildContextWatcherHandler(pgConn))
|
|
||||||
pgConn.contextWatcher.Watch(ctx)
|
pgConn.contextWatcher.Watch(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
defer pgConn.contextWatcher.Unwatch()
|
defer pgConn.contextWatcher.Unwatch()
|
||||||
|
|
||||||
pgConn.parameterStatuses = make(map[string]string)
|
pgConn.parameterStatuses = make(map[string]string)
|
||||||
pgConn.status = connStatusConnecting
|
pgConn.status = connStatusConnecting
|
||||||
pgConn.bgReader = bgreader.New(pgConn.conn)
|
pgConn.bgReader = bgreader.New(pgConn.conn)
|
||||||
pgConn.slowWriteTimer = time.AfterFunc(time.Duration(math.MaxInt64),
|
pgConn.slowWriteTimer = time.AfterFunc(time.Duration(math.MaxInt64), pgConn.bgReader.Start)
|
||||||
func() {
|
|
||||||
pgConn.bgReader.Start()
|
|
||||||
pgConn.bgReaderStarted <- struct{}{}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
pgConn.slowWriteTimer.Stop()
|
pgConn.slowWriteTimer.Stop()
|
||||||
pgConn.bgReaderStarted = make(chan struct{})
|
|
||||||
pgConn.frontend = config.BuildFrontend(pgConn.bgReader, pgConn.conn)
|
pgConn.frontend = config.BuildFrontend(pgConn.bgReader, pgConn.conn)
|
||||||
|
|
||||||
startupMsg := pgproto3.StartupMessage{
|
startupMsg := pgproto3.StartupMessage{
|
||||||
@@ -366,7 +323,7 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo
|
|||||||
pgConn.frontend.Send(&startupMsg)
|
pgConn.frontend.Send(&startupMsg)
|
||||||
if err := pgConn.flushWithPotentialWriteReadDeadlock(); err != nil {
|
if err := pgConn.flushWithPotentialWriteReadDeadlock(); err != nil {
|
||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
return nil, newPerDialConnectError("failed to write startup message", err)
|
return nil, &connectError{config: config, msg: "failed to write startup message", err: normalizeTimeoutError(ctx, err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -374,9 +331,9 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
if err, ok := err.(*PgError); ok {
|
if err, ok := err.(*PgError); ok {
|
||||||
return nil, newPerDialConnectError("server error", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, newPerDialConnectError("failed to receive message", err)
|
return nil, &connectError{config: config, msg: "failed to receive message", err: normalizeTimeoutError(ctx, err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
@@ -389,26 +346,26 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo
|
|||||||
err = pgConn.txPasswordMessage(pgConn.config.Password)
|
err = pgConn.txPasswordMessage(pgConn.config.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
return nil, newPerDialConnectError("failed to write password message", err)
|
return nil, &connectError{config: config, msg: "failed to write password message", err: err}
|
||||||
}
|
}
|
||||||
case *pgproto3.AuthenticationMD5Password:
|
case *pgproto3.AuthenticationMD5Password:
|
||||||
digestedPassword := "md5" + hexMD5(hexMD5(pgConn.config.Password+pgConn.config.User)+string(msg.Salt[:]))
|
digestedPassword := "md5" + hexMD5(hexMD5(pgConn.config.Password+pgConn.config.User)+string(msg.Salt[:]))
|
||||||
err = pgConn.txPasswordMessage(digestedPassword)
|
err = pgConn.txPasswordMessage(digestedPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
return nil, newPerDialConnectError("failed to write password message", err)
|
return nil, &connectError{config: config, msg: "failed to write password message", err: err}
|
||||||
}
|
}
|
||||||
case *pgproto3.AuthenticationSASL:
|
case *pgproto3.AuthenticationSASL:
|
||||||
err = pgConn.scramAuth(msg.AuthMechanisms)
|
err = pgConn.scramAuth(msg.AuthMechanisms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
return nil, newPerDialConnectError("failed SASL auth", err)
|
return nil, &connectError{config: config, msg: "failed SASL auth", err: err}
|
||||||
}
|
}
|
||||||
case *pgproto3.AuthenticationGSS:
|
case *pgproto3.AuthenticationGSS:
|
||||||
err = pgConn.gssAuth()
|
err = pgConn.gssAuth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
return nil, newPerDialConnectError("failed GSS auth", err)
|
return nil, &connectError{config: config, msg: "failed GSS auth", err: err}
|
||||||
}
|
}
|
||||||
case *pgproto3.ReadyForQuery:
|
case *pgproto3.ReadyForQuery:
|
||||||
pgConn.status = connStatusIdle
|
pgConn.status = connStatusIdle
|
||||||
@@ -426,7 +383,7 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo
|
|||||||
return pgConn, nil
|
return pgConn, nil
|
||||||
}
|
}
|
||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
return nil, newPerDialConnectError("ValidateConnect failed", err)
|
return nil, &connectError{config: config, msg: "ValidateConnect failed", err: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pgConn, nil
|
return pgConn, nil
|
||||||
@@ -434,14 +391,21 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo
|
|||||||
// handled by ReceiveMessage
|
// handled by ReceiveMessage
|
||||||
case *pgproto3.ErrorResponse:
|
case *pgproto3.ErrorResponse:
|
||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
return nil, newPerDialConnectError("server error", ErrorResponseToPgError(msg))
|
return nil, ErrorResponseToPgError(msg)
|
||||||
default:
|
default:
|
||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
return nil, newPerDialConnectError("received unexpected message", err)
|
return nil, &connectError{config: config, msg: "received unexpected message", err: err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newContextWatcher(conn net.Conn) *ctxwatch.ContextWatcher {
|
||||||
|
return ctxwatch.NewContextWatcher(
|
||||||
|
func() { conn.SetDeadline(time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC)) },
|
||||||
|
func() { conn.SetDeadline(time.Time{}) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func startTLS(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) {
|
func startTLS(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) {
|
||||||
err := binary.Write(conn, binary.BigEndian, []int32{8, 80877103})
|
err := binary.Write(conn, binary.BigEndian, []int32{8, 80877103})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -576,12 +540,11 @@ func (pgConn *PgConn) receiveMessage() (pgproto3.BackendMessage, error) {
|
|||||||
case *pgproto3.ParameterStatus:
|
case *pgproto3.ParameterStatus:
|
||||||
pgConn.parameterStatuses[msg.Name] = msg.Value
|
pgConn.parameterStatuses[msg.Name] = msg.Value
|
||||||
case *pgproto3.ErrorResponse:
|
case *pgproto3.ErrorResponse:
|
||||||
err := ErrorResponseToPgError(msg)
|
if msg.Severity == "FATAL" {
|
||||||
if pgConn.config.OnPgError != nil && !pgConn.config.OnPgError(pgConn, err) {
|
|
||||||
pgConn.status = connStatusClosed
|
pgConn.status = connStatusClosed
|
||||||
pgConn.conn.Close() // Ignore error as the connection is already broken and there is already an error to return.
|
pgConn.conn.Close() // Ignore error as the connection is already broken and there is already an error to return.
|
||||||
close(pgConn.cleanupDone)
|
close(pgConn.cleanupDone)
|
||||||
return nil, err
|
return nil, ErrorResponseToPgError(msg)
|
||||||
}
|
}
|
||||||
case *pgproto3.NoticeResponse:
|
case *pgproto3.NoticeResponse:
|
||||||
if pgConn.config.OnNotice != nil {
|
if pgConn.config.OnNotice != nil {
|
||||||
@@ -630,7 +593,7 @@ func (pgConn *PgConn) Frontend() *pgproto3.Frontend {
|
|||||||
return pgConn.frontend
|
return pgConn.frontend
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes a connection. It is safe to call Close on an already closed connection. Close attempts a clean close by
|
// Close closes a connection. It is safe to call Close on a already closed connection. Close attempts a clean close by
|
||||||
// sending the exit message to PostgreSQL. However, this could block so ctx is available to limit the time to wait. The
|
// sending the exit message to PostgreSQL. However, this could block so ctx is available to limit the time to wait. The
|
||||||
// underlying net.Conn.Close() will always be called regardless of any other errors.
|
// underlying net.Conn.Close() will always be called regardless of any other errors.
|
||||||
func (pgConn *PgConn) Close(ctx context.Context) error {
|
func (pgConn *PgConn) Close(ctx context.Context) error {
|
||||||
@@ -843,9 +806,6 @@ type StatementDescription struct {
|
|||||||
|
|
||||||
// Prepare creates a prepared statement. If the name is empty, the anonymous prepared statement will be used. This
|
// Prepare creates a prepared statement. If the name is empty, the anonymous prepared statement will be used. This
|
||||||
// allows Prepare to also to describe statements without creating a server-side prepared statement.
|
// allows Prepare to also to describe statements without creating a server-side prepared statement.
|
||||||
//
|
|
||||||
// Prepare does not send a PREPARE statement to the server. It uses the PostgreSQL Parse and Describe protocol messages
|
|
||||||
// directly.
|
|
||||||
func (pgConn *PgConn) Prepare(ctx context.Context, name, sql string, paramOIDs []uint32) (*StatementDescription, error) {
|
func (pgConn *PgConn) Prepare(ctx context.Context, name, sql string, paramOIDs []uint32) (*StatementDescription, error) {
|
||||||
if err := pgConn.lock(); err != nil {
|
if err := pgConn.lock(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -902,57 +862,10 @@ readloop:
|
|||||||
return psd, nil
|
return psd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deallocate deallocates a prepared statement.
|
|
||||||
//
|
|
||||||
// Deallocate does not send a DEALLOCATE statement to the server. It uses the PostgreSQL Close protocol message
|
|
||||||
// directly. This has slightly different behavior than executing DEALLOCATE statement.
|
|
||||||
// - Deallocate can succeed in an aborted transaction.
|
|
||||||
// - Deallocating a non-existent prepared statement is not an error.
|
|
||||||
func (pgConn *PgConn) Deallocate(ctx context.Context, name string) error {
|
|
||||||
if err := pgConn.lock(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer pgConn.unlock()
|
|
||||||
|
|
||||||
if ctx != context.Background() {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return newContextAlreadyDoneError(ctx)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
pgConn.contextWatcher.Watch(ctx)
|
|
||||||
defer pgConn.contextWatcher.Unwatch()
|
|
||||||
}
|
|
||||||
|
|
||||||
pgConn.frontend.SendClose(&pgproto3.Close{ObjectType: 'S', Name: name})
|
|
||||||
pgConn.frontend.SendSync(&pgproto3.Sync{})
|
|
||||||
err := pgConn.flushWithPotentialWriteReadDeadlock()
|
|
||||||
if err != nil {
|
|
||||||
pgConn.asyncClose()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
msg, err := pgConn.receiveMessage()
|
|
||||||
if err != nil {
|
|
||||||
pgConn.asyncClose()
|
|
||||||
return normalizeTimeoutError(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *pgproto3.ErrorResponse:
|
|
||||||
return ErrorResponseToPgError(msg)
|
|
||||||
case *pgproto3.ReadyForQuery:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorResponseToPgError converts a wire protocol error message to a *PgError.
|
// ErrorResponseToPgError converts a wire protocol error message to a *PgError.
|
||||||
func ErrorResponseToPgError(msg *pgproto3.ErrorResponse) *PgError {
|
func ErrorResponseToPgError(msg *pgproto3.ErrorResponse) *PgError {
|
||||||
return &PgError{
|
return &PgError{
|
||||||
Severity: msg.Severity,
|
Severity: msg.Severity,
|
||||||
SeverityUnlocalized: msg.SeverityUnlocalized,
|
|
||||||
Code: string(msg.Code),
|
Code: string(msg.Code),
|
||||||
Message: string(msg.Message),
|
Message: string(msg.Message),
|
||||||
Detail: string(msg.Detail),
|
Detail: string(msg.Detail),
|
||||||
@@ -1011,7 +924,10 @@ func (pgConn *PgConn) CancelRequest(ctx context.Context) error {
|
|||||||
defer cancelConn.Close()
|
defer cancelConn.Close()
|
||||||
|
|
||||||
if ctx != context.Background() {
|
if ctx != context.Background() {
|
||||||
contextWatcher := ctxwatch.NewContextWatcher(&DeadlineContextWatcherHandler{Conn: cancelConn})
|
contextWatcher := ctxwatch.NewContextWatcher(
|
||||||
|
func() { cancelConn.SetDeadline(time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC)) },
|
||||||
|
func() { cancelConn.SetDeadline(time.Time{}) },
|
||||||
|
)
|
||||||
contextWatcher.Watch(ctx)
|
contextWatcher.Watch(ctx)
|
||||||
defer contextWatcher.Unwatch()
|
defer contextWatcher.Unwatch()
|
||||||
}
|
}
|
||||||
@@ -1019,21 +935,16 @@ func (pgConn *PgConn) CancelRequest(ctx context.Context) error {
|
|||||||
buf := make([]byte, 16)
|
buf := make([]byte, 16)
|
||||||
binary.BigEndian.PutUint32(buf[0:4], 16)
|
binary.BigEndian.PutUint32(buf[0:4], 16)
|
||||||
binary.BigEndian.PutUint32(buf[4:8], 80877102)
|
binary.BigEndian.PutUint32(buf[4:8], 80877102)
|
||||||
binary.BigEndian.PutUint32(buf[8:12], pgConn.pid)
|
binary.BigEndian.PutUint32(buf[8:12], uint32(pgConn.pid))
|
||||||
binary.BigEndian.PutUint32(buf[12:16], pgConn.secretKey)
|
binary.BigEndian.PutUint32(buf[12:16], uint32(pgConn.secretKey))
|
||||||
|
// Postgres will process the request and close the connection
|
||||||
if _, err := cancelConn.Write(buf); err != nil {
|
// so when don't need to read the reply
|
||||||
return fmt.Errorf("write to connection for cancellation: %w", err)
|
// https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.6.7.10
|
||||||
}
|
_, err = cancelConn.Write(buf)
|
||||||
|
return err
|
||||||
// Wait for the cancel request to be acknowledged by the server.
|
|
||||||
// It copies the behavior of the libpq: https://github.com/postgres/postgres/blob/REL_16_0/src/interfaces/libpq/fe-connect.c#L4946-L4960
|
|
||||||
_, _ = cancelConn.Read(buf)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForNotification waits for a LISTEN/NOTIFY message to be received. It returns an error if a notification was not
|
// WaitForNotification waits for a LISTON/NOTIFY message to be received. It returns an error if a notification was not
|
||||||
// received.
|
// received.
|
||||||
func (pgConn *PgConn) WaitForNotification(ctx context.Context) error {
|
func (pgConn *PgConn) WaitForNotification(ctx context.Context) error {
|
||||||
if err := pgConn.lock(); err != nil {
|
if err := pgConn.lock(); err != nil {
|
||||||
@@ -1544,11 +1455,9 @@ func (rr *ResultReader) Read() *Result {
|
|||||||
values := rr.Values()
|
values := rr.Values()
|
||||||
row := make([][]byte, len(values))
|
row := make([][]byte, len(values))
|
||||||
for i := range row {
|
for i := range row {
|
||||||
if values[i] != nil {
|
|
||||||
row[i] = make([]byte, len(values[i]))
|
row[i] = make([]byte, len(values[i]))
|
||||||
copy(row[i], values[i])
|
copy(row[i], values[i])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
br.Rows = append(br.Rows, row)
|
br.Rows = append(br.Rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1697,55 +1606,25 @@ func (rr *ResultReader) concludeCommand(commandTag CommandTag, err error) {
|
|||||||
// Batch is a collection of queries that can be sent to the PostgreSQL server in a single round-trip.
|
// Batch is a collection of queries that can be sent to the PostgreSQL server in a single round-trip.
|
||||||
type Batch struct {
|
type Batch struct {
|
||||||
buf []byte
|
buf []byte
|
||||||
err error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecParams appends an ExecParams command to the batch. See PgConn.ExecParams for parameter descriptions.
|
// ExecParams appends an ExecParams command to the batch. See PgConn.ExecParams for parameter descriptions.
|
||||||
func (batch *Batch) ExecParams(sql string, paramValues [][]byte, paramOIDs []uint32, paramFormats []int16, resultFormats []int16) {
|
func (batch *Batch) ExecParams(sql string, paramValues [][]byte, paramOIDs []uint32, paramFormats []int16, resultFormats []int16) {
|
||||||
if batch.err != nil {
|
batch.buf = (&pgproto3.Parse{Query: sql, ParameterOIDs: paramOIDs}).Encode(batch.buf)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.buf, batch.err = (&pgproto3.Parse{Query: sql, ParameterOIDs: paramOIDs}).Encode(batch.buf)
|
|
||||||
if batch.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
batch.ExecPrepared("", paramValues, paramFormats, resultFormats)
|
batch.ExecPrepared("", paramValues, paramFormats, resultFormats)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecPrepared appends an ExecPrepared e command to the batch. See PgConn.ExecPrepared for parameter descriptions.
|
// ExecPrepared appends an ExecPrepared e command to the batch. See PgConn.ExecPrepared for parameter descriptions.
|
||||||
func (batch *Batch) ExecPrepared(stmtName string, paramValues [][]byte, paramFormats []int16, resultFormats []int16) {
|
func (batch *Batch) ExecPrepared(stmtName string, paramValues [][]byte, paramFormats []int16, resultFormats []int16) {
|
||||||
if batch.err != nil {
|
batch.buf = (&pgproto3.Bind{PreparedStatement: stmtName, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}).Encode(batch.buf)
|
||||||
return
|
batch.buf = (&pgproto3.Describe{ObjectType: 'P'}).Encode(batch.buf)
|
||||||
}
|
batch.buf = (&pgproto3.Execute{}).Encode(batch.buf)
|
||||||
|
|
||||||
batch.buf, batch.err = (&pgproto3.Bind{PreparedStatement: stmtName, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}).Encode(batch.buf)
|
|
||||||
if batch.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.buf, batch.err = (&pgproto3.Describe{ObjectType: 'P'}).Encode(batch.buf)
|
|
||||||
if batch.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.buf, batch.err = (&pgproto3.Execute{}).Encode(batch.buf)
|
|
||||||
if batch.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecBatch executes all the queries in batch in a single round-trip. Execution is implicitly transactional unless a
|
// ExecBatch executes all the queries in batch in a single round-trip. Execution is implicitly transactional unless a
|
||||||
// transaction is already in progress or SQL contains transaction control statements. This is a simpler way of executing
|
// transaction is already in progress or SQL contains transaction control statements. This is a simpler way of executing
|
||||||
// multiple queries in a single round trip than using pipeline mode.
|
// multiple queries in a single round trip than using pipeline mode.
|
||||||
func (pgConn *PgConn) ExecBatch(ctx context.Context, batch *Batch) *MultiResultReader {
|
func (pgConn *PgConn) ExecBatch(ctx context.Context, batch *Batch) *MultiResultReader {
|
||||||
if batch.err != nil {
|
|
||||||
return &MultiResultReader{
|
|
||||||
closed: true,
|
|
||||||
err: batch.err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pgConn.lock(); err != nil {
|
if err := pgConn.lock(); err != nil {
|
||||||
return &MultiResultReader{
|
return &MultiResultReader{
|
||||||
closed: true,
|
closed: true,
|
||||||
@@ -1771,13 +1650,7 @@ func (pgConn *PgConn) ExecBatch(ctx context.Context, batch *Batch) *MultiResultR
|
|||||||
pgConn.contextWatcher.Watch(ctx)
|
pgConn.contextWatcher.Watch(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.buf, batch.err = (&pgproto3.Sync{}).Encode(batch.buf)
|
batch.buf = (&pgproto3.Sync{}).Encode(batch.buf)
|
||||||
if batch.err != nil {
|
|
||||||
multiResult.closed = true
|
|
||||||
multiResult.err = batch.err
|
|
||||||
pgConn.unlock()
|
|
||||||
return multiResult
|
|
||||||
}
|
|
||||||
|
|
||||||
pgConn.enterPotentialWriteReadDeadlock()
|
pgConn.enterPotentialWriteReadDeadlock()
|
||||||
defer pgConn.exitPotentialWriteReadDeadlock()
|
defer pgConn.exitPotentialWriteReadDeadlock()
|
||||||
@@ -1859,16 +1732,10 @@ func (pgConn *PgConn) enterPotentialWriteReadDeadlock() {
|
|||||||
|
|
||||||
// exitPotentialWriteReadDeadlock must be called after a call to enterPotentialWriteReadDeadlock.
|
// exitPotentialWriteReadDeadlock must be called after a call to enterPotentialWriteReadDeadlock.
|
||||||
func (pgConn *PgConn) exitPotentialWriteReadDeadlock() {
|
func (pgConn *PgConn) exitPotentialWriteReadDeadlock() {
|
||||||
if !pgConn.slowWriteTimer.Stop() {
|
// The state of the timer is not relevant upon exiting the potential slow write. It may both
|
||||||
// The timer starts its function in a separate goroutine. It is necessary to ensure the background reader has
|
// fire (due to a slow write), or not fire (due to a fast write).
|
||||||
// started before calling Stop. Otherwise, the background reader may not be stopped. That on its own is not a
|
_ = pgConn.slowWriteTimer.Stop()
|
||||||
// serious problem. But what is a serious problem is that the background reader may start at an inopportune time in
|
|
||||||
// a subsequent query. For example, if a subsequent query was canceled then a deadline may be set on the net.Conn to
|
|
||||||
// interrupt an in-progress read. After the read is interrupted, but before the deadline is cleared, the background
|
|
||||||
// reader could start and read a deadline error. Then the next query would receive the an unexpected deadline error.
|
|
||||||
<-pgConn.bgReaderStarted
|
|
||||||
pgConn.bgReader.Stop()
|
pgConn.bgReader.Stop()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pgConn *PgConn) flushWithPotentialWriteReadDeadlock() error {
|
func (pgConn *PgConn) flushWithPotentialWriteReadDeadlock() error {
|
||||||
@@ -1897,16 +1764,11 @@ func (pgConn *PgConn) SyncConn(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should never happen. Only way I can imagine this occurring is if the server is constantly sending data such as
|
// This should never happen. Only way I can imagine this occuring is if the server is constantly sending data such as
|
||||||
// LISTEN/NOTIFY or log notifications such that we never can get an empty buffer.
|
// LISTEN/NOTIFY or log notifications such that we never can get an empty buffer.
|
||||||
return errors.New("SyncConn: conn never synchronized")
|
return errors.New("SyncConn: conn never synchronized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomData returns a map that can be used to associate custom data with the connection.
|
|
||||||
func (pgConn *PgConn) CustomData() map[string]any {
|
|
||||||
return pgConn.customData
|
|
||||||
}
|
|
||||||
|
|
||||||
// HijackedConn is the result of hijacking a connection.
|
// HijackedConn is the result of hijacking a connection.
|
||||||
//
|
//
|
||||||
// Due to the necessary exposure of internal implementation details, it is not covered by the semantic versioning
|
// Due to the necessary exposure of internal implementation details, it is not covered by the semantic versioning
|
||||||
@@ -1919,7 +1781,6 @@ type HijackedConn struct {
|
|||||||
TxStatus byte
|
TxStatus byte
|
||||||
Frontend *pgproto3.Frontend
|
Frontend *pgproto3.Frontend
|
||||||
Config *Config
|
Config *Config
|
||||||
CustomData map[string]any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hijack extracts the internal connection data. pgConn must be in an idle state. SyncConn should be called immediately
|
// Hijack extracts the internal connection data. pgConn must be in an idle state. SyncConn should be called immediately
|
||||||
@@ -1942,7 +1803,6 @@ func (pgConn *PgConn) Hijack() (*HijackedConn, error) {
|
|||||||
TxStatus: pgConn.txStatus,
|
TxStatus: pgConn.txStatus,
|
||||||
Frontend: pgConn.frontend,
|
Frontend: pgConn.frontend,
|
||||||
Config: pgConn.config,
|
Config: pgConn.config,
|
||||||
CustomData: pgConn.customData,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1962,23 +1822,16 @@ func Construct(hc *HijackedConn) (*PgConn, error) {
|
|||||||
txStatus: hc.TxStatus,
|
txStatus: hc.TxStatus,
|
||||||
frontend: hc.Frontend,
|
frontend: hc.Frontend,
|
||||||
config: hc.Config,
|
config: hc.Config,
|
||||||
customData: hc.CustomData,
|
|
||||||
|
|
||||||
status: connStatusIdle,
|
status: connStatusIdle,
|
||||||
|
|
||||||
cleanupDone: make(chan struct{}),
|
cleanupDone: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
pgConn.contextWatcher = ctxwatch.NewContextWatcher(hc.Config.BuildContextWatcherHandler(pgConn))
|
pgConn.contextWatcher = newContextWatcher(pgConn.conn)
|
||||||
pgConn.bgReader = bgreader.New(pgConn.conn)
|
pgConn.bgReader = bgreader.New(pgConn.conn)
|
||||||
pgConn.slowWriteTimer = time.AfterFunc(time.Duration(math.MaxInt64),
|
pgConn.slowWriteTimer = time.AfterFunc(time.Duration(math.MaxInt64), pgConn.bgReader.Start)
|
||||||
func() {
|
|
||||||
pgConn.bgReader.Start()
|
|
||||||
pgConn.bgReaderStarted <- struct{}{}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
pgConn.slowWriteTimer.Stop()
|
pgConn.slowWriteTimer.Stop()
|
||||||
pgConn.bgReaderStarted = make(chan struct{})
|
|
||||||
pgConn.frontend = hc.Config.BuildFrontend(pgConn.bgReader, pgConn.conn)
|
pgConn.frontend = hc.Config.BuildFrontend(pgConn.bgReader, pgConn.conn)
|
||||||
|
|
||||||
return pgConn, nil
|
return pgConn, nil
|
||||||
@@ -2120,13 +1973,6 @@ func (p *Pipeline) Flush() error {
|
|||||||
|
|
||||||
// Sync establishes a synchronization point and flushes the queued requests.
|
// Sync establishes a synchronization point and flushes the queued requests.
|
||||||
func (p *Pipeline) Sync() error {
|
func (p *Pipeline) Sync() error {
|
||||||
if p.closed {
|
|
||||||
if p.err != nil {
|
|
||||||
return p.err
|
|
||||||
}
|
|
||||||
return errors.New("pipeline closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
p.conn.frontend.SendSync(&pgproto3.Sync{})
|
p.conn.frontend.SendSync(&pgproto3.Sync{})
|
||||||
err := p.Flush()
|
err := p.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2143,28 +1989,14 @@ func (p *Pipeline) Sync() error {
|
|||||||
// *PipelineSync. If an ErrorResponse is received from the server, results will be nil and err will be a *PgError. If no
|
// *PipelineSync. If an ErrorResponse is received from the server, results will be nil and err will be a *PgError. If no
|
||||||
// results are available, results and err will both be nil.
|
// results are available, results and err will both be nil.
|
||||||
func (p *Pipeline) GetResults() (results any, err error) {
|
func (p *Pipeline) GetResults() (results any, err error) {
|
||||||
if p.closed {
|
|
||||||
if p.err != nil {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
return nil, errors.New("pipeline closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.expectedReadyForQueryCount == 0 {
|
if p.expectedReadyForQueryCount == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.getResults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pipeline) getResults() (results any, err error) {
|
|
||||||
for {
|
for {
|
||||||
msg, err := p.conn.receiveMessage()
|
msg, err := p.conn.receiveMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.closed = true
|
return nil, err
|
||||||
p.err = err
|
|
||||||
p.conn.asyncClose()
|
|
||||||
return nil, normalizeTimeoutError(p.ctx, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
@@ -2186,8 +2018,7 @@ func (p *Pipeline) getResults() (results any, err error) {
|
|||||||
case *pgproto3.ParseComplete:
|
case *pgproto3.ParseComplete:
|
||||||
peekedMsg, err := p.conn.peekMessage()
|
peekedMsg, err := p.conn.peekMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.conn.asyncClose()
|
return nil, err
|
||||||
return nil, normalizeTimeoutError(p.ctx, err)
|
|
||||||
}
|
}
|
||||||
if _, ok := peekedMsg.(*pgproto3.ParameterDescription); ok {
|
if _, ok := peekedMsg.(*pgproto3.ParameterDescription); ok {
|
||||||
return p.getResultsPrepare()
|
return p.getResultsPrepare()
|
||||||
@@ -2247,7 +2078,6 @@ func (p *Pipeline) Close() error {
|
|||||||
if p.closed {
|
if p.closed {
|
||||||
return p.err
|
return p.err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.closed = true
|
p.closed = true
|
||||||
|
|
||||||
if p.pendingSync {
|
if p.pendingSync {
|
||||||
@@ -2260,7 +2090,7 @@ func (p *Pipeline) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for p.expectedReadyForQueryCount > 0 {
|
for p.expectedReadyForQueryCount > 0 {
|
||||||
_, err := p.getResults()
|
_, err := p.GetResults()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.err = err
|
p.err = err
|
||||||
var pgErr *PgError
|
var pgErr *PgError
|
||||||
@@ -2276,71 +2106,3 @@ func (p *Pipeline) Close() error {
|
|||||||
|
|
||||||
return p.err
|
return p.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeadlineContextWatcherHandler handles canceled contexts by setting a deadline on a net.Conn.
|
|
||||||
type DeadlineContextWatcherHandler struct {
|
|
||||||
Conn net.Conn
|
|
||||||
|
|
||||||
// DeadlineDelay is the delay to set on the deadline set on net.Conn when the context is canceled.
|
|
||||||
DeadlineDelay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *DeadlineContextWatcherHandler) HandleCancel(ctx context.Context) {
|
|
||||||
h.Conn.SetDeadline(time.Now().Add(h.DeadlineDelay))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *DeadlineContextWatcherHandler) HandleUnwatchAfterCancel() {
|
|
||||||
h.Conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CancelRequestContextWatcherHandler handles canceled contexts by sending a cancel request to the server. It also sets
|
|
||||||
// a deadline on a net.Conn as a fallback.
|
|
||||||
type CancelRequestContextWatcherHandler struct {
|
|
||||||
Conn *PgConn
|
|
||||||
|
|
||||||
// CancelRequestDelay is the delay before sending the cancel request to the server.
|
|
||||||
CancelRequestDelay time.Duration
|
|
||||||
|
|
||||||
// DeadlineDelay is the delay to set on the deadline set on net.Conn when the context is canceled.
|
|
||||||
DeadlineDelay time.Duration
|
|
||||||
|
|
||||||
cancelFinishedChan chan struct{}
|
|
||||||
handleUnwatchAfterCancelCalled func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *CancelRequestContextWatcherHandler) HandleCancel(context.Context) {
|
|
||||||
h.cancelFinishedChan = make(chan struct{})
|
|
||||||
var handleUnwatchedAfterCancelCalledCtx context.Context
|
|
||||||
handleUnwatchedAfterCancelCalledCtx, h.handleUnwatchAfterCancelCalled = context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
deadline := time.Now().Add(h.DeadlineDelay)
|
|
||||||
h.Conn.conn.SetDeadline(deadline)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer close(h.cancelFinishedChan)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-handleUnwatchedAfterCancelCalledCtx.Done():
|
|
||||||
return
|
|
||||||
case <-time.After(h.CancelRequestDelay):
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelRequestCtx, cancel := context.WithDeadline(handleUnwatchedAfterCancelCalledCtx, deadline)
|
|
||||||
defer cancel()
|
|
||||||
h.Conn.CancelRequest(cancelRequestCtx)
|
|
||||||
|
|
||||||
// CancelRequest is inherently racy. Even though the cancel request has been received by the server at this point,
|
|
||||||
// it hasn't necessarily been delivered to the other connection. If we immediately return and the connection is
|
|
||||||
// immediately used then it is possible the CancelRequest will actually cancel our next query. The
|
|
||||||
// TestCancelRequestContextWatcherHandler Stress test can produce this error without the sleep below. The sleep time
|
|
||||||
// is arbitrary, but should be sufficient to prevent this error case.
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *CancelRequestContextWatcherHandler) HandleUnwatchAfterCancel() {
|
|
||||||
h.handleUnwatchAfterCancelCalled()
|
|
||||||
<-h.cancelFinishedChan
|
|
||||||
|
|
||||||
h.Conn.conn.SetDeadline(time.Time{})
|
|
||||||
}
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user