[chore] update bun + extras v1.1.16 -> v1.1.17 (#2534)

This commit is contained in:
tobi 2024-01-15 14:08:07 +01:00 committed by GitHub
parent a43ce99da9
commit 6433a50582
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 1426 additions and 294 deletions

16
go.mod
View file

@ -51,10 +51,10 @@ require (
github.com/technologize/otel-go-contrib v1.1.0 github.com/technologize/otel-go-contrib v1.1.0
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/ulule/limiter/v3 v3.11.2 github.com/ulule/limiter/v3 v3.11.2
github.com/uptrace/bun v1.1.16 github.com/uptrace/bun v1.1.17
github.com/uptrace/bun/dialect/pgdialect v1.1.16 github.com/uptrace/bun/dialect/pgdialect v1.1.17
github.com/uptrace/bun/dialect/sqlitedialect v1.1.16 github.com/uptrace/bun/dialect/sqlitedialect v1.1.17
github.com/uptrace/bun/extra/bunotel v1.1.16 github.com/uptrace/bun/extra/bunotel v1.1.17
github.com/wagslane/go-password-validator v0.3.0 github.com/wagslane/go-password-validator v0.3.0
github.com/yuin/goldmark v1.6.0 github.com/yuin/goldmark v1.6.0
go.opentelemetry.io/otel v1.21.0 go.opentelemetry.io/otel v1.21.0
@ -108,7 +108,7 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-errors/errors v1.4.1 // indirect github.com/go-errors/errors v1.4.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.1 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // 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
@ -136,7 +136,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect github.com/minio/sha256-simd v1.0.1 // indirect
@ -164,8 +164,8 @@ require (
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // 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.11 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2 // indirect github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect

32
go.sum
View file

@ -202,8 +202,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@ -391,8 +391,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
@ -550,24 +550,24 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA= github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA=
github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI= github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI=
github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A= github.com/uptrace/bun v1.1.17 h1:qxBaEIo0hC/8O3O6GrMDKxqyT+mw5/s0Pn/n6xjyGIk=
github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8= github.com/uptrace/bun v1.1.17/go.mod h1:hATAzivtTIRsSJR4B8AXR+uABqnQxr3myKDKEf5iQ9U=
github.com/uptrace/bun/dialect/pgdialect v1.1.16 h1:eUPZ+YCJ69BA+W1X1ZmpOJSkv1oYtinr0zCXf7zCo5g= github.com/uptrace/bun/dialect/pgdialect v1.1.17 h1:NsvFVHAx1Az6ytlAD/B6ty3cVE6j9Yp82bjqd9R9hOs=
github.com/uptrace/bun/dialect/pgdialect v1.1.16/go.mod h1:KQjfx/r6JM0OXfbv0rFrxAbdkPD7idK8VitnjIV9fZI= github.com/uptrace/bun/dialect/pgdialect v1.1.17/go.mod h1:fLBDclNc7nKsZLzNjFL6BqSdgJzbj2HdnyOnLoDvAME=
github.com/uptrace/bun/dialect/sqlitedialect v1.1.16 h1:gbc9BP/e4sNOB9VBj+Si46dpOz2oktmZPidkda92GYY= github.com/uptrace/bun/dialect/sqlitedialect v1.1.17 h1:i8NFU9r8YuavNFaYlNqi4ppn+MgoHtqLgpWQDrVTjm0=
github.com/uptrace/bun/dialect/sqlitedialect v1.1.16/go.mod h1:YNezpK7fIn5Wa2WGmTCZ/nEyiswcXmuT4iNWADeL1x4= github.com/uptrace/bun/dialect/sqlitedialect v1.1.17/go.mod h1:YF0FO4VVnY9GHNH6rM4r3STlVEBxkOc6L88Bm5X5mzA=
github.com/uptrace/bun/extra/bunotel v1.1.16 h1:qkLTaTZK3FZk3b2P/stO/krS7KX9Fq5wSOj7Hlb2HG8= github.com/uptrace/bun/extra/bunotel v1.1.17 h1:RLEJdHH06RI9BLg06Vu1JHJ3KNHQCfwa2Fa3x+56qkk=
github.com/uptrace/bun/extra/bunotel v1.1.16/go.mod h1:JwEH0kdXFnzYuK8D6eXUrf9HKsYy5wmB+lqQ/+dvH4E= github.com/uptrace/bun/extra/bunotel v1.1.17/go.mod h1:xV7AYrCFji4Sio6N9X+Cz+XJ+JuHq6TQQjuxaVbsypk=
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2 h1:USRngIQppxeyb39XzkVHXwQesKK0+JSwnHE/1c7fgic= github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3 h1:LNi0Qa7869/loPjz2kmMvp/jwZZnMZ9scMJKhDJ1DIo=
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2/go.mod h1:1frv9RN1rlTq0jzCq+mVuEQisubZCQ4OU6S/8CaHzGY= github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3/go.mod h1:jyigonKik3C5V895QNiAGpKYKEvFuqjw9qAEZks1mUg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE= github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=

View file

@ -91,11 +91,12 @@ logr design but also left out some parts and changed others:
| Adding a name to a logger | `WithName` | no API | | Adding a name to a logger | `WithName` | no API |
| Modify verbosity of log entries in a call chain | `V` | no API | | Modify verbosity of log entries in a call chain | `V` | no API |
| Grouping of key/value pairs | not supported | `WithGroup`, `GroupValue` | | Grouping of key/value pairs | not supported | `WithGroup`, `GroupValue` |
| Pass context for extracting additional values | no API | API variants like `InfoCtx` |
The high-level slog API is explicitly meant to be one of many different APIs The high-level slog API is explicitly meant to be one of many different APIs
that can be layered on top of a shared `slog.Handler`. logr is one such that can be layered on top of a shared `slog.Handler`. logr is one such
alternative API, with [interoperability](#slog-interoperability) provided by the [`slogr`](slogr) alternative API, with [interoperability](#slog-interoperability) provided by
package. some conversion functions.
### Inspiration ### Inspiration
@ -145,24 +146,24 @@ There are implementations for the following logging libraries:
## slog interoperability ## slog interoperability
Interoperability goes both ways, using the `logr.Logger` API with a `slog.Handler` Interoperability goes both ways, using the `logr.Logger` API with a `slog.Handler`
and using the `slog.Logger` API with a `logr.LogSink`. [slogr](./slogr) provides `NewLogr` and and using the `slog.Logger` API with a `logr.LogSink`. `FromSlogHandler` and
`NewSlogHandler` API calls to convert between a `logr.Logger` and a `slog.Handler`. `ToSlogHandler` convert between a `logr.Logger` and a `slog.Handler`.
As usual, `slog.New` can be used to wrap such a `slog.Handler` in the high-level As usual, `slog.New` can be used to wrap such a `slog.Handler` in the high-level
slog API. `slogr` itself leaves that to the caller. slog API.
## Using a `logr.Sink` as backend for slog ### Using a `logr.LogSink` as backend for slog
Ideally, a logr sink implementation should support both logr and slog by Ideally, a logr sink implementation should support both logr and slog by
implementing both the normal logr interface(s) and `slogr.SlogSink`. Because implementing both the normal logr interface(s) and `SlogSink`. Because
of a conflict in the parameters of the common `Enabled` method, it is [not of a conflict in the parameters of the common `Enabled` method, it is [not
possible to implement both slog.Handler and logr.Sink in the same possible to implement both slog.Handler and logr.Sink in the same
type](https://github.com/golang/go/issues/59110). type](https://github.com/golang/go/issues/59110).
If both are supported, log calls can go from the high-level APIs to the backend If both are supported, log calls can go from the high-level APIs to the backend
without the need to convert parameters. `NewLogr` and `NewSlogHandler` can without the need to convert parameters. `FromSlogHandler` and `ToSlogHandler` can
convert back and forth without adding additional wrappers, with one exception: convert back and forth without adding additional wrappers, with one exception:
when `Logger.V` was used to adjust the verbosity for a `slog.Handler`, then when `Logger.V` was used to adjust the verbosity for a `slog.Handler`, then
`NewSlogHandler` has to use a wrapper which adjusts the verbosity for future `ToSlogHandler` has to use a wrapper which adjusts the verbosity for future
log calls. log calls.
Such an implementation should also support values that implement specific Such an implementation should also support values that implement specific
@ -187,13 +188,13 @@ Not supporting slog has several drawbacks:
These drawbacks are severe enough that applications using a mixture of slog and These drawbacks are severe enough that applications using a mixture of slog and
logr should switch to a different backend. logr should switch to a different backend.
## Using a `slog.Handler` as backend for logr ### Using a `slog.Handler` as backend for logr
Using a plain `slog.Handler` without support for logr works better than the Using a plain `slog.Handler` without support for logr works better than the
other direction: other direction:
- All logr verbosity levels can be mapped 1:1 to their corresponding slog level - All logr verbosity levels can be mapped 1:1 to their corresponding slog level
by negating them. by negating them.
- Stack unwinding is done by the `slogr.SlogSink` and the resulting program - Stack unwinding is done by the `SlogSink` and the resulting program
counter is passed to the `slog.Handler`. counter is passed to the `slog.Handler`.
- Names added via `Logger.WithName` are gathered and recorded in an additional - Names added via `Logger.WithName` are gathered and recorded in an additional
attribute with `logger` as key and the names separated by slash as value. attribute with `logger` as key and the names separated by slash as value.
@ -205,27 +206,39 @@ ideally support both `logr.Marshaler` and `slog.Valuer`. If compatibility
with logr implementations without slog support is not important, then with logr implementations without slog support is not important, then
`slog.Valuer` is sufficient. `slog.Valuer` is sufficient.
## Context support for slog ### Context support for slog
Storing a logger in a `context.Context` is not supported by Storing a logger in a `context.Context` is not supported by
slog. `logr.NewContext` and `logr.FromContext` can be used with slog like this slog. `NewContextWithSlogLogger` and `FromContextAsSlogLogger` can be
to fill this gap: used to fill this gap. They store and retrieve a `slog.Logger` pointer
under the same context key that is also used by `NewContext` and
`FromContext` for `logr.Logger` value.
func HandlerFromContext(ctx context.Context) slog.Handler { When `NewContextWithSlogLogger` is followed by `FromContext`, the latter will
logger, err := logr.FromContext(ctx) automatically convert the `slog.Logger` to a
if err == nil { `logr.Logger`. `FromContextAsSlogLogger` does the same for the other direction.
return slogr.NewSlogHandler(logger)
}
return slog.Default().Handler()
}
func ContextWithHandler(ctx context.Context, handler slog.Handler) context.Context { With this approach, binaries which use either slog or logr are as efficient as
return logr.NewContext(ctx, slogr.NewLogr(handler)) possible with no unnecessary allocations. This is also why the API stores a
} `slog.Logger` pointer: when storing a `slog.Handler`, creating a `slog.Logger`
on retrieval would need to allocate one.
The downside is that storing and retrieving a `slog.Handler` needs more The downside is that switching back and forth needs more allocations. Because
allocations compared to using a `logr.Logger`. Therefore the recommendation is logr is the API that is already in use by different packages, in particular
to use the `logr.Logger` API in code which uses contextual logging. Kubernetes, the recommendation is to use the `logr.Logger` API in code which
uses contextual logging.
An alternative to adding values to a logger and storing that logger in the
context is to store the values in the context and to configure a logging
backend to extract those values when emitting log entries. This only works when
log calls are passed the context, which is not supported by the logr API.
With the slog API, it is possible, but not
required. https://github.com/veqryn/slog-context is a package for slog which
provides additional support code for this approach. It also contains wrappers
for the context functions in logr, so developers who prefer to not use the logr
APIs directly can use those instead and the resulting code will still be
interoperable with logr.
## FAQ ## FAQ

33
vendor/github.com/go-logr/logr/context.go generated vendored Normal file
View file

@ -0,0 +1,33 @@
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
// contextKey is how we find Loggers in a context.Context. With Go < 1.21,
// the value is always a Logger value. With Go >= 1.21, the value can be a
// Logger value or a slog.Logger pointer.
type contextKey struct{}
// notFoundError exists to carry an IsNotFound method.
type notFoundError struct{}
func (notFoundError) Error() string {
return "no logr.Logger was present"
}
func (notFoundError) IsNotFound() bool {
return true
}

49
vendor/github.com/go-logr/logr/context_noslog.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
//go:build !go1.21
// +build !go1.21
/*
Copyright 2019 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
)
// FromContext returns a Logger from ctx or an error if no Logger is found.
func FromContext(ctx context.Context) (Logger, error) {
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
return v, nil
}
return Logger{}, notFoundError{}
}
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
// returns a Logger that discards all log messages.
func FromContextOrDiscard(ctx context.Context) Logger {
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
return v
}
return Discard()
}
// NewContext returns a new Context, derived from ctx, which carries the
// provided Logger.
func NewContext(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, contextKey{}, logger)
}

83
vendor/github.com/go-logr/logr/context_slog.go generated vendored Normal file
View file

@ -0,0 +1,83 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2019 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
"fmt"
"log/slog"
)
// FromContext returns a Logger from ctx or an error if no Logger is found.
func FromContext(ctx context.Context) (Logger, error) {
v := ctx.Value(contextKey{})
if v == nil {
return Logger{}, notFoundError{}
}
switch v := v.(type) {
case Logger:
return v, nil
case *slog.Logger:
return FromSlogHandler(v.Handler()), nil
default:
// Not reached.
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
}
}
// FromContextAsSlogLogger returns a slog.Logger from ctx or nil if no such Logger is found.
func FromContextAsSlogLogger(ctx context.Context) *slog.Logger {
v := ctx.Value(contextKey{})
if v == nil {
return nil
}
switch v := v.(type) {
case Logger:
return slog.New(ToSlogHandler(v))
case *slog.Logger:
return v
default:
// Not reached.
panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
}
}
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
// returns a Logger that discards all log messages.
func FromContextOrDiscard(ctx context.Context) Logger {
if logger, err := FromContext(ctx); err == nil {
return logger
}
return Discard()
}
// NewContext returns a new Context, derived from ctx, which carries the
// provided Logger.
func NewContext(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, contextKey{}, logger)
}
// NewContextWithSlogLogger returns a new Context, derived from ctx, which carries the
// provided slog.Logger.
func NewContextWithSlogLogger(ctx context.Context, logger *slog.Logger) context.Context {
return context.WithValue(ctx, contextKey{}, logger)
}

View file

@ -100,6 +100,11 @@ type Options struct {
// details, see docs for Go's time.Layout. // details, see docs for Go's time.Layout.
TimestampFormat string TimestampFormat string
// LogInfoLevel tells funcr what key to use to log the info level.
// If not specified, the info level will be logged as "level".
// If this is set to "", the info level will not be logged at all.
LogInfoLevel *string
// Verbosity tells funcr which V logs to produce. Higher values enable // Verbosity tells funcr which V logs to produce. Higher values enable
// more logs. Info logs at or below this level will be written, while logs // more logs. Info logs at or below this level will be written, while logs
// above this level will be discarded. // above this level will be discarded.
@ -213,6 +218,10 @@ func newFormatter(opts Options, outfmt outputFormat) Formatter {
if opts.MaxLogDepth == 0 { if opts.MaxLogDepth == 0 {
opts.MaxLogDepth = defaultMaxLogDepth opts.MaxLogDepth = defaultMaxLogDepth
} }
if opts.LogInfoLevel == nil {
opts.LogInfoLevel = new(string)
*opts.LogInfoLevel = "level"
}
f := Formatter{ f := Formatter{
outputFormat: outfmt, outputFormat: outfmt,
prefix: "", prefix: "",
@ -231,8 +240,11 @@ type Formatter struct {
prefix string prefix string
values []any values []any
valuesStr string valuesStr string
parentValuesStr string
depth int depth int
opts *Options opts *Options
group string // for slog groups
groupDepth int
} }
// outputFormat indicates which outputFormat to use. // outputFormat indicates which outputFormat to use.
@ -253,33 +265,62 @@ func (f Formatter) render(builtins, args []any) string {
// Empirically bytes.Buffer is faster than strings.Builder for this. // Empirically bytes.Buffer is faster than strings.Builder for this.
buf := bytes.NewBuffer(make([]byte, 0, 1024)) buf := bytes.NewBuffer(make([]byte, 0, 1024))
if f.outputFormat == outputJSON { if f.outputFormat == outputJSON {
buf.WriteByte('{') buf.WriteByte('{') // for the whole line
} }
vals := builtins vals := builtins
if hook := f.opts.RenderBuiltinsHook; hook != nil { if hook := f.opts.RenderBuiltinsHook; hook != nil {
vals = hook(f.sanitize(vals)) vals = hook(f.sanitize(vals))
} }
f.flatten(buf, vals, false, false) // keys are ours, no need to escape f.flatten(buf, vals, false, false) // keys are ours, no need to escape
continuing := len(builtins) > 0 continuing := len(builtins) > 0
if len(f.valuesStr) > 0 {
if f.parentValuesStr != "" {
if continuing { if continuing {
if f.outputFormat == outputJSON { buf.WriteByte(f.comma())
buf.WriteByte(',')
} else {
buf.WriteByte(' ')
}
} }
buf.WriteString(f.parentValuesStr)
continuing = true continuing = true
buf.WriteString(f.valuesStr)
} }
groupDepth := f.groupDepth
if f.group != "" {
if f.valuesStr != "" || len(args) != 0 {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{') // for the group
continuing = false
} else {
// The group was empty
groupDepth--
}
}
if f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.valuesStr)
continuing = true
}
vals = args vals = args
if hook := f.opts.RenderArgsHook; hook != nil { if hook := f.opts.RenderArgsHook; hook != nil {
vals = hook(f.sanitize(vals)) vals = hook(f.sanitize(vals))
} }
f.flatten(buf, vals, continuing, true) // escape user-provided keys f.flatten(buf, vals, continuing, true) // escape user-provided keys
if f.outputFormat == outputJSON {
buf.WriteByte('}') for i := 0; i < groupDepth; i++ {
buf.WriteByte('}') // for the groups
} }
if f.outputFormat == outputJSON {
buf.WriteByte('}') // for the whole line
}
return buf.String() return buf.String()
} }
@ -298,9 +339,16 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
if len(kvList)%2 != 0 { if len(kvList)%2 != 0 {
kvList = append(kvList, noValue) kvList = append(kvList, noValue)
} }
copied := false
for i := 0; i < len(kvList); i += 2 { for i := 0; i < len(kvList); i += 2 {
k, ok := kvList[i].(string) k, ok := kvList[i].(string)
if !ok { if !ok {
if !copied {
newList := make([]any, len(kvList))
copy(newList, kvList)
kvList = newList
copied = true
}
k = f.nonStringKey(kvList[i]) k = f.nonStringKey(kvList[i])
kvList[i] = k kvList[i] = k
} }
@ -308,7 +356,7 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
if i > 0 || continuing { if i > 0 || continuing {
if f.outputFormat == outputJSON { if f.outputFormat == outputJSON {
buf.WriteByte(',') buf.WriteByte(f.comma())
} else { } else {
// In theory the format could be something we don't understand. In // In theory the format could be something we don't understand. In
// practice, we control it, so it won't be. // practice, we control it, so it won't be.
@ -316,24 +364,35 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
} }
} }
if escapeKeys { buf.WriteString(f.quoted(k, escapeKeys))
buf.WriteString(prettyString(k)) buf.WriteByte(f.colon())
} else {
// this is faster
buf.WriteByte('"')
buf.WriteString(k)
buf.WriteByte('"')
}
if f.outputFormat == outputJSON {
buf.WriteByte(':')
} else {
buf.WriteByte('=')
}
buf.WriteString(f.pretty(v)) buf.WriteString(f.pretty(v))
} }
return kvList return kvList
} }
func (f Formatter) quoted(str string, escape bool) string {
if escape {
return prettyString(str)
}
// this is faster
return `"` + str + `"`
}
func (f Formatter) comma() byte {
if f.outputFormat == outputJSON {
return ','
}
return ' '
}
func (f Formatter) colon() byte {
if f.outputFormat == outputJSON {
return ':'
}
return '='
}
func (f Formatter) pretty(value any) string { func (f Formatter) pretty(value any) string {
return f.prettyWithFlags(value, 0, 0) return f.prettyWithFlags(value, 0, 0)
} }
@ -407,12 +466,12 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
} }
for i := 0; i < len(v); i += 2 { for i := 0; i < len(v); i += 2 {
if i > 0 { if i > 0 {
buf.WriteByte(',') buf.WriteByte(f.comma())
} }
k, _ := v[i].(string) // sanitize() above means no need to check success k, _ := v[i].(string) // sanitize() above means no need to check success
// arbitrary keys might need escaping // arbitrary keys might need escaping
buf.WriteString(prettyString(k)) buf.WriteString(prettyString(k))
buf.WriteByte(':') buf.WriteByte(f.colon())
buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1)) buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
} }
if flags&flagRawStruct == 0 { if flags&flagRawStruct == 0 {
@ -481,7 +540,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
continue continue
} }
if printComma { if printComma {
buf.WriteByte(',') buf.WriteByte(f.comma())
} }
printComma = true // if we got here, we are rendering a field printComma = true // if we got here, we are rendering a field
if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" { if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
@ -492,10 +551,8 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
name = fld.Name name = fld.Name
} }
// field names can't contain characters which need escaping // field names can't contain characters which need escaping
buf.WriteByte('"') buf.WriteString(f.quoted(name, false))
buf.WriteString(name) buf.WriteByte(f.colon())
buf.WriteByte('"')
buf.WriteByte(':')
buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1)) buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
} }
if flags&flagRawStruct == 0 { if flags&flagRawStruct == 0 {
@ -520,7 +577,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
buf.WriteByte('[') buf.WriteByte('[')
for i := 0; i < v.Len(); i++ { for i := 0; i < v.Len(); i++ {
if i > 0 { if i > 0 {
buf.WriteByte(',') buf.WriteByte(f.comma())
} }
e := v.Index(i) e := v.Index(i)
buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1)) buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
@ -534,7 +591,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
i := 0 i := 0
for it.Next() { for it.Next() {
if i > 0 { if i > 0 {
buf.WriteByte(',') buf.WriteByte(f.comma())
} }
// If a map key supports TextMarshaler, use it. // If a map key supports TextMarshaler, use it.
keystr := "" keystr := ""
@ -556,7 +613,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
} }
} }
buf.WriteString(keystr) buf.WriteString(keystr)
buf.WriteByte(':') buf.WriteByte(f.colon())
buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1)) buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
i++ i++
} }
@ -706,6 +763,53 @@ func (f Formatter) sanitize(kvList []any) []any {
return kvList return kvList
} }
// startGroup opens a new group scope (basically a sub-struct), which locks all
// the current saved values and starts them anew. This is needed to satisfy
// slog.
func (f *Formatter) startGroup(group string) {
// Unnamed groups are just inlined.
if group == "" {
return
}
// Any saved values can no longer be changed.
buf := bytes.NewBuffer(make([]byte, 0, 1024))
continuing := false
if f.parentValuesStr != "" {
buf.WriteString(f.parentValuesStr)
continuing = true
}
if f.group != "" && f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{') // for the group
continuing = false
}
if f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.valuesStr)
}
// NOTE: We don't close the scope here - that's done later, when a log line
// is actually rendered (because we have N scopes to close).
f.parentValuesStr = buf.String()
// Start collecting new values.
f.group = group
f.groupDepth++
f.valuesStr = ""
f.values = nil
}
// Init configures this Formatter from runtime info, such as the call depth // Init configures this Formatter from runtime info, such as the call depth
// imposed by logr itself. // imposed by logr itself.
// Note that this receiver is a pointer, so depth can be saved. // Note that this receiver is a pointer, so depth can be saved.
@ -740,7 +844,10 @@ func (f Formatter) FormatInfo(level int, msg string, kvList []any) (prefix, args
if policy := f.opts.LogCaller; policy == All || policy == Info { if policy := f.opts.LogCaller; policy == All || policy == Info {
args = append(args, "caller", f.caller()) args = append(args, "caller", f.caller())
} }
args = append(args, "level", level, "msg", msg) if key := *f.opts.LogInfoLevel; key != "" {
args = append(args, key, level)
}
args = append(args, "msg", msg)
return prefix, f.render(args, kvList) return prefix, f.render(args, kvList)
} }

105
vendor/github.com/go-logr/logr/funcr/slogsink.go generated vendored Normal file
View file

@ -0,0 +1,105 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package funcr
import (
"context"
"log/slog"
"github.com/go-logr/logr"
)
var _ logr.SlogSink = &fnlogger{}
const extraSlogSinkDepth = 3 // 2 for slog, 1 for SlogSink
func (l fnlogger) Handle(_ context.Context, record slog.Record) error {
kvList := make([]any, 0, 2*record.NumAttrs())
record.Attrs(func(attr slog.Attr) bool {
kvList = attrToKVs(attr, kvList)
return true
})
if record.Level >= slog.LevelError {
l.WithCallDepth(extraSlogSinkDepth).Error(nil, record.Message, kvList...)
} else {
level := l.levelFromSlog(record.Level)
l.WithCallDepth(extraSlogSinkDepth).Info(level, record.Message, kvList...)
}
return nil
}
func (l fnlogger) WithAttrs(attrs []slog.Attr) logr.SlogSink {
kvList := make([]any, 0, 2*len(attrs))
for _, attr := range attrs {
kvList = attrToKVs(attr, kvList)
}
l.AddValues(kvList)
return &l
}
func (l fnlogger) WithGroup(name string) logr.SlogSink {
l.startGroup(name)
return &l
}
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
// and other details of slog.
func attrToKVs(attr slog.Attr, kvList []any) []any {
attrVal := attr.Value.Resolve()
if attrVal.Kind() == slog.KindGroup {
groupVal := attrVal.Group()
grpKVs := make([]any, 0, 2*len(groupVal))
for _, attr := range groupVal {
grpKVs = attrToKVs(attr, grpKVs)
}
if attr.Key == "" {
// slog says we have to inline these
kvList = append(kvList, grpKVs...)
} else {
kvList = append(kvList, attr.Key, PseudoStruct(grpKVs))
}
} else if attr.Key != "" {
kvList = append(kvList, attr.Key, attrVal.Any())
}
return kvList
}
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
// It ensures that the result is >= 0. This is necessary because the result is
// passed to a LogSink and that API did not historically document whether
// levels could be negative or what that meant.
//
// Some example usage:
//
// logrV0 := getMyLogger()
// logrV2 := logrV0.V(2)
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
func (l fnlogger) levelFromSlog(level slog.Level) int {
result := -level
if result < 0 {
result = 0 // because LogSink doesn't expect negative V levels
}
return int(result)
}

View file

@ -207,10 +207,6 @@
// those. // those.
package logr package logr
import (
"context"
)
// New returns a new Logger instance. This is primarily used by libraries // New returns a new Logger instance. This is primarily used by libraries
// implementing LogSink, rather than end users. Passing a nil sink will create // implementing LogSink, rather than end users. Passing a nil sink will create
// a Logger which discards all log lines. // a Logger which discards all log lines.
@ -410,45 +406,6 @@ func (l Logger) IsZero() bool {
return l.sink == nil return l.sink == nil
} }
// contextKey is how we find Loggers in a context.Context.
type contextKey struct{}
// FromContext returns a Logger from ctx or an error if no Logger is found.
func FromContext(ctx context.Context) (Logger, error) {
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
return v, nil
}
return Logger{}, notFoundError{}
}
// notFoundError exists to carry an IsNotFound method.
type notFoundError struct{}
func (notFoundError) Error() string {
return "no logr.Logger was present"
}
func (notFoundError) IsNotFound() bool {
return true
}
// FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this
// returns a Logger that discards all log messages.
func FromContextOrDiscard(ctx context.Context) Logger {
if v, ok := ctx.Value(contextKey{}).(Logger); ok {
return v
}
return Discard()
}
// NewContext returns a new Context, derived from ctx, which carries the
// provided Logger.
func NewContext(ctx context.Context, logger Logger) context.Context {
return context.WithValue(ctx, contextKey{}, logger)
}
// RuntimeInfo holds information that the logr "core" library knows which // RuntimeInfo holds information that the logr "core" library knows which
// LogSinks might want to know. // LogSinks might want to know.
type RuntimeInfo struct { type RuntimeInfo struct {

192
vendor/github.com/go-logr/logr/sloghandler.go generated vendored Normal file
View file

@ -0,0 +1,192 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
"log/slog"
)
type slogHandler struct {
// May be nil, in which case all logs get discarded.
sink LogSink
// Non-nil if sink is non-nil and implements SlogSink.
slogSink SlogSink
// groupPrefix collects values from WithGroup calls. It gets added as
// prefix to value keys when handling a log record.
groupPrefix string
// levelBias can be set when constructing the handler to influence the
// slog.Level of log records. A positive levelBias reduces the
// slog.Level value. slog has no API to influence this value after the
// handler got created, so it can only be set indirectly through
// Logger.V.
levelBias slog.Level
}
var _ slog.Handler = &slogHandler{}
// groupSeparator is used to concatenate WithGroup names and attribute keys.
const groupSeparator = "."
// GetLevel is used for black box unit testing.
func (l *slogHandler) GetLevel() slog.Level {
return l.levelBias
}
func (l *slogHandler) Enabled(_ context.Context, level slog.Level) bool {
return l.sink != nil && (level >= slog.LevelError || l.sink.Enabled(l.levelFromSlog(level)))
}
func (l *slogHandler) Handle(ctx context.Context, record slog.Record) error {
if l.slogSink != nil {
// Only adjust verbosity level of log entries < slog.LevelError.
if record.Level < slog.LevelError {
record.Level -= l.levelBias
}
return l.slogSink.Handle(ctx, record)
}
// No need to check for nil sink here because Handle will only be called
// when Enabled returned true.
kvList := make([]any, 0, 2*record.NumAttrs())
record.Attrs(func(attr slog.Attr) bool {
kvList = attrToKVs(attr, l.groupPrefix, kvList)
return true
})
if record.Level >= slog.LevelError {
l.sinkWithCallDepth().Error(nil, record.Message, kvList...)
} else {
level := l.levelFromSlog(record.Level)
l.sinkWithCallDepth().Info(level, record.Message, kvList...)
}
return nil
}
// sinkWithCallDepth adjusts the stack unwinding so that when Error or Info
// are called by Handle, code in slog gets skipped.
//
// This offset currently (Go 1.21.0) works for calls through
// slog.New(ToSlogHandler(...)). There's no guarantee that the call
// chain won't change. Wrapping the handler will also break unwinding. It's
// still better than not adjusting at all....
//
// This cannot be done when constructing the handler because FromSlogHandler needs
// access to the original sink without this adjustment. A second copy would
// work, but then WithAttrs would have to be called for both of them.
func (l *slogHandler) sinkWithCallDepth() LogSink {
if sink, ok := l.sink.(CallDepthLogSink); ok {
return sink.WithCallDepth(2)
}
return l.sink
}
func (l *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
if l.sink == nil || len(attrs) == 0 {
return l
}
clone := *l
if l.slogSink != nil {
clone.slogSink = l.slogSink.WithAttrs(attrs)
clone.sink = clone.slogSink
} else {
kvList := make([]any, 0, 2*len(attrs))
for _, attr := range attrs {
kvList = attrToKVs(attr, l.groupPrefix, kvList)
}
clone.sink = l.sink.WithValues(kvList...)
}
return &clone
}
func (l *slogHandler) WithGroup(name string) slog.Handler {
if l.sink == nil {
return l
}
if name == "" {
// slog says to inline empty groups
return l
}
clone := *l
if l.slogSink != nil {
clone.slogSink = l.slogSink.WithGroup(name)
clone.sink = clone.slogSink
} else {
clone.groupPrefix = addPrefix(clone.groupPrefix, name)
}
return &clone
}
// attrToKVs appends a slog.Attr to a logr-style kvList. It handle slog Groups
// and other details of slog.
func attrToKVs(attr slog.Attr, groupPrefix string, kvList []any) []any {
attrVal := attr.Value.Resolve()
if attrVal.Kind() == slog.KindGroup {
groupVal := attrVal.Group()
grpKVs := make([]any, 0, 2*len(groupVal))
prefix := groupPrefix
if attr.Key != "" {
prefix = addPrefix(groupPrefix, attr.Key)
}
for _, attr := range groupVal {
grpKVs = attrToKVs(attr, prefix, grpKVs)
}
kvList = append(kvList, grpKVs...)
} else if attr.Key != "" {
kvList = append(kvList, addPrefix(groupPrefix, attr.Key), attrVal.Any())
}
return kvList
}
func addPrefix(prefix, name string) string {
if prefix == "" {
return name
}
if name == "" {
return prefix
}
return prefix + groupSeparator + name
}
// levelFromSlog adjusts the level by the logger's verbosity and negates it.
// It ensures that the result is >= 0. This is necessary because the result is
// passed to a LogSink and that API did not historically document whether
// levels could be negative or what that meant.
//
// Some example usage:
//
// logrV0 := getMyLogger()
// logrV2 := logrV0.V(2)
// slogV2 := slog.New(logr.ToSlogHandler(logrV2))
// slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
// slogV2.Info("msg") // =~ logrV2.V(0) =~ logrV0.V(2)
// slogv2.Warn("msg") // =~ logrV2.V(-4) =~ logrV0.V(0)
func (l *slogHandler) levelFromSlog(level slog.Level) int {
result := -level
result += l.levelBias // in case the original Logger had a V level
if result < 0 {
result = 0 // because LogSink doesn't expect negative V levels
}
return int(result)
}

100
vendor/github.com/go-logr/logr/slogr.go generated vendored Normal file
View file

@ -0,0 +1,100 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
"log/slog"
)
// FromSlogHandler returns a Logger which writes to the slog.Handler.
//
// The logr verbosity level is mapped to slog levels such that V(0) becomes
// slog.LevelInfo and V(4) becomes slog.LevelDebug.
func FromSlogHandler(handler slog.Handler) Logger {
if handler, ok := handler.(*slogHandler); ok {
if handler.sink == nil {
return Discard()
}
return New(handler.sink).V(int(handler.levelBias))
}
return New(&slogSink{handler: handler})
}
// ToSlogHandler returns a slog.Handler which writes to the same sink as the Logger.
//
// The returned logger writes all records with level >= slog.LevelError as
// error log entries with LogSink.Error, regardless of the verbosity level of
// the Logger:
//
// logger := <some Logger with 0 as verbosity level>
// slog.New(ToSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...)
//
// The level of all other records gets reduced by the verbosity
// level of the Logger and the result is negated. If it happens
// to be negative, then it gets replaced by zero because a LogSink
// is not expected to handled negative levels:
//
// slog.New(ToSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...)
// slog.New(ToSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...)
// slog.New(ToSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...)
// slog.New(ToSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...)
func ToSlogHandler(logger Logger) slog.Handler {
if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 {
return sink.handler
}
handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())}
if slogSink, ok := handler.sink.(SlogSink); ok {
handler.slogSink = slogSink
}
return handler
}
// SlogSink is an optional interface that a LogSink can implement to support
// logging through the slog.Logger or slog.Handler APIs better. It then should
// also support special slog values like slog.Group. When used as a
// slog.Handler, the advantages are:
//
// - stack unwinding gets avoided in favor of logging the pre-recorded PC,
// as intended by slog
// - proper grouping of key/value pairs via WithGroup
// - verbosity levels > slog.LevelInfo can be recorded
// - less overhead
//
// Both APIs (Logger and slog.Logger/Handler) then are supported equally
// well. Developers can pick whatever API suits them better and/or mix
// packages which use either API in the same binary with a common logging
// implementation.
//
// This interface is necessary because the type implementing the LogSink
// interface cannot also implement the slog.Handler interface due to the
// different prototype of the common Enabled method.
//
// An implementation could support both interfaces in two different types, but then
// additional interfaces would be needed to convert between those types in FromSlogHandler
// and ToSlogHandler.
type SlogSink interface {
LogSink
Handle(ctx context.Context, record slog.Record) error
WithAttrs(attrs []slog.Attr) SlogSink
WithGroup(name string) SlogSink
}

120
vendor/github.com/go-logr/logr/slogsink.go generated vendored Normal file
View file

@ -0,0 +1,120 @@
//go:build go1.21
// +build go1.21
/*
Copyright 2023 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logr
import (
"context"
"log/slog"
"runtime"
"time"
)
var (
_ LogSink = &slogSink{}
_ CallDepthLogSink = &slogSink{}
_ Underlier = &slogSink{}
)
// Underlier is implemented by the LogSink returned by NewFromLogHandler.
type Underlier interface {
// GetUnderlying returns the Handler used by the LogSink.
GetUnderlying() slog.Handler
}
const (
// nameKey is used to log the `WithName` values as an additional attribute.
nameKey = "logger"
// errKey is used to log the error parameter of Error as an additional attribute.
errKey = "err"
)
type slogSink struct {
callDepth int
name string
handler slog.Handler
}
func (l *slogSink) Init(info RuntimeInfo) {
l.callDepth = info.CallDepth
}
func (l *slogSink) GetUnderlying() slog.Handler {
return l.handler
}
func (l *slogSink) WithCallDepth(depth int) LogSink {
newLogger := *l
newLogger.callDepth += depth
return &newLogger
}
func (l *slogSink) Enabled(level int) bool {
return l.handler.Enabled(context.Background(), slog.Level(-level))
}
func (l *slogSink) Info(level int, msg string, kvList ...interface{}) {
l.log(nil, msg, slog.Level(-level), kvList...)
}
func (l *slogSink) Error(err error, msg string, kvList ...interface{}) {
l.log(err, msg, slog.LevelError, kvList...)
}
func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) {
var pcs [1]uintptr
// skip runtime.Callers, this function, Info/Error, and all helper functions above that.
runtime.Callers(3+l.callDepth, pcs[:])
record := slog.NewRecord(time.Now(), level, msg, pcs[0])
if l.name != "" {
record.AddAttrs(slog.String(nameKey, l.name))
}
if err != nil {
record.AddAttrs(slog.Any(errKey, err))
}
record.Add(kvList...)
_ = l.handler.Handle(context.Background(), record)
}
func (l slogSink) WithName(name string) LogSink {
if l.name != "" {
l.name += "/"
}
l.name += name
return &l
}
func (l slogSink) WithValues(kvList ...interface{}) LogSink {
l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...))
return &l
}
func kvListToAttrs(kvList ...interface{}) []slog.Attr {
// We don't need the record itself, only its Add method.
record := slog.NewRecord(time.Time{}, 0, "", 0)
record.Add(kvList...)
attrs := make([]slog.Attr, 0, record.NumAttrs())
record.Attrs(func(attr slog.Attr) bool {
attrs = append(attrs, attr)
return true
})
return attrs
}

View file

@ -1,6 +1,7 @@
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine //go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine && !tinygo
// +build darwin freebsd openbsd netbsd dragonfly hurd // +build darwin freebsd openbsd netbsd dragonfly hurd
// +build !appengine // +build !appengine
// +build !tinygo
package isatty package isatty

View file

@ -1,5 +1,6 @@
//go:build appengine || js || nacl || wasm //go:build (appengine || js || nacl || tinygo || wasm) && !windows
// +build appengine js nacl wasm // +build appengine js nacl tinygo wasm
// +build !windows
package isatty package isatty

View file

@ -1,6 +1,7 @@
//go:build (linux || aix || zos) && !appengine //go:build (linux || aix || zos) && !appengine && !tinygo
// +build linux aix zos // +build linux aix zos
// +build !appengine // +build !appengine
// +build !tinygo
package isatty package isatty

View file

@ -1,3 +1,4 @@
# Patterns for files created by this project. # Patterns for files created by this project.
# For other files, use global gitignore. # For other files, use global gitignore.
*.s3db *.s3db
.idea

View file

@ -1,3 +1,15 @@
## [1.1.17](https://github.com/uptrace/bun/compare/v1.1.16...v1.1.17) (2024-01-11)
### Features
* add CreateTxSQLMigrations function ([#916](https://github.com/uptrace/bun/issues/916)) ([c68ec7c](https://github.com/uptrace/bun/commit/c68ec7cfc418959eb7c79028be7ac91f97d462ef))
* add Join to UpdateQuery ([#908](https://github.com/uptrace/bun/issues/908)) ([8c4d8be](https://github.com/uptrace/bun/commit/8c4d8be3aa4e64582698b37fd21434b8960dddc0))
* bunslog.QueryHook for Bun logging using `log/slog` ([#904](https://github.com/uptrace/bun/issues/904)) ([4953367](https://github.com/uptrace/bun/commit/495336731da0a995aa28c7bc84345c7825408e48))
* dbfixture.New to accept IDB interface ([#900](https://github.com/uptrace/bun/issues/900)) ([2dee174](https://github.com/uptrace/bun/commit/2dee174bc4d09a45caeeede2885306e5fd10002d))
## [1.1.16](https://github.com/uptrace/bun/compare/v1.1.15...v1.1.16) (2023-09-16) ## [1.1.16](https://github.com/uptrace/bun/compare/v1.1.15...v1.1.16) (2023-09-16)

View file

@ -108,3 +108,7 @@ func (d *Dialect) AppendUint32(b []byte, n uint32) []byte {
func (d *Dialect) AppendUint64(b []byte, n uint64) []byte { func (d *Dialect) AppendUint64(b []byte, n uint64) []byte {
return strconv.AppendInt(b, int64(n), 10) return strconv.AppendInt(b, int64(n), 10)
} }
func (d *Dialect) AppendSequence(b []byte, _ *schema.Table, _ *schema.Field) []byte {
return append(b, " GENERATED BY DEFAULT AS IDENTITY"...)
}

View file

@ -2,5 +2,5 @@
// Version is the current release version. // Version is the current release version.
func Version() string { func Version() string {
return "1.1.16" return "1.1.17"
} }

View file

@ -39,6 +39,7 @@ func New() *Dialect {
feature.InsertOnConflict | feature.InsertOnConflict |
feature.TableNotExists | feature.TableNotExists |
feature.SelectExists | feature.SelectExists |
feature.AutoIncrement |
feature.CompositeIn feature.CompositeIn
return d return d
} }
@ -91,6 +92,25 @@ func (d *Dialect) DefaultVarcharLen() int {
return 0 return 0
} }
// AppendSequence adds AUTOINCREMENT keyword to the column definition. As per [documentation],
// AUTOINCREMENT is only valid for INTEGER PRIMARY KEY, and this method will be a noop for other columns.
//
// Because this is a valid construct:
// CREATE TABLE ("id" INTEGER PRIMARY KEY AUTOINCREMENT);
// and this is not:
// CREATE TABLE ("id" INTEGER AUTOINCREMENT, PRIMARY KEY ("id"));
// AppendSequence adds a primary key constraint as a *side-effect*. Callers should expect it to avoid building invalid SQL.
// SQLite also [does not support] AUTOINCREMENT column in composite primary keys.
//
// [documentation]: https://www.sqlite.org/autoinc.html
// [does not support]: https://stackoverflow.com/a/6793274/14726116
func (d *Dialect) AppendSequence(b []byte, table *schema.Table, field *schema.Field) []byte {
if field.IsPK && len(table.PKs) == 1 && field.CreateTableSQLType == sqltype.Integer {
b = append(b, " PRIMARY KEY AUTOINCREMENT"...)
}
return b
}
func fieldSQLType(field *schema.Field) string { func fieldSQLType(field *schema.Field) string {
switch field.DiscoveredSQLType { switch field.DiscoveredSQLType {
case sqltype.SmallInt, sqltype.BigInt: case sqltype.SmallInt, sqltype.BigInt:

View file

@ -2,5 +2,5 @@
// Version is the current release version. // Version is the current release version.
func Version() string { func Version() string {
return "1.1.16" return "1.1.17"
} }

View file

@ -158,6 +158,11 @@ func init() {
SELECT 2 SELECT 2
` `
const transactionalSQLTemplate = `SET statement_timeout = 0;
SELECT 1;
`
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type MigrationSlice []Migration type MigrationSlice []Migration

View file

@ -267,19 +267,19 @@ func (m *Migrator) CreateGoMigration(
return mf, nil return mf, nil
} }
// CreateSQLMigrations creates an up and down SQL migration files. // CreateTxSQLMigration creates transactional up and down SQL migration files.
func (m *Migrator) CreateSQLMigrations(ctx context.Context, name string) ([]*MigrationFile, error) { func (m *Migrator) CreateTxSQLMigrations(ctx context.Context, name string) ([]*MigrationFile, error) {
name, err := m.genMigrationName(name) name, err := m.genMigrationName(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
up, err := m.createSQL(ctx, name+".up.sql") up, err := m.createSQL(ctx, name+".up.tx.sql", true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
down, err := m.createSQL(ctx, name+".down.sql") down, err := m.createSQL(ctx, name+".down.tx.sql", true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -287,10 +287,35 @@ func (m *Migrator) CreateSQLMigrations(ctx context.Context, name string) ([]*Mig
return []*MigrationFile{up, down}, nil return []*MigrationFile{up, down}, nil
} }
func (m *Migrator) createSQL(ctx context.Context, fname string) (*MigrationFile, error) { // CreateSQLMigrations creates up and down SQL migration files.
func (m *Migrator) CreateSQLMigrations(ctx context.Context, name string) ([]*MigrationFile, error) {
name, err := m.genMigrationName(name)
if err != nil {
return nil, err
}
up, err := m.createSQL(ctx, name+".up.sql", false)
if err != nil {
return nil, err
}
down, err := m.createSQL(ctx, name+".down.sql", false)
if err != nil {
return nil, err
}
return []*MigrationFile{up, down}, nil
}
func (m *Migrator) createSQL(ctx context.Context, fname string, transactional bool) (*MigrationFile, error) {
fpath := filepath.Join(m.migrations.getDirectory(), fname) fpath := filepath.Join(m.migrations.getDirectory(), fname)
if err := os.WriteFile(fpath, []byte(sqlTemplate), 0o644); err != nil { template := sqlTemplate
if transactional {
template = transactionalSQLTemplate
}
if err := os.WriteFile(fpath, []byte(template), 0o644); err != nil {
return nil, err return nil, err
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "gobun", "name": "gobun",
"version": "1.1.16", "version": "1.1.17",
"main": "index.js", "main": "index.js",
"repository": "git@github.com:uptrace/bun.git", "repository": "git@github.com:uptrace/bun.git",
"author": "Vladimir Mihailenco <vladimir.webdev@gmail.com>", "author": "Vladimir Mihailenco <vladimir.webdev@gmail.com>",

View file

@ -332,8 +332,8 @@ func (q *InsertQuery) appendStructValues(
switch { switch {
case isTemplate: case isTemplate:
b = append(b, '?') b = append(b, '?')
case (f.IsPtr && f.HasNilValue(strct)) || (f.NullZero && f.HasZeroValue(strct)): case q.marshalsToDefault(f, strct):
if q.db.features.Has(feature.DefaultPlaceholder) { if q.db.HasFeature(feature.DefaultPlaceholder) {
b = append(b, "DEFAULT"...) b = append(b, "DEFAULT"...)
} else if f.SQLDefault != "" { } else if f.SQLDefault != "" {
b = append(b, f.SQLDefault...) b = append(b, f.SQLDefault...)
@ -410,18 +410,23 @@ func (q *InsertQuery) getFields() ([]*schema.Field, error) {
q.addReturningField(f) q.addReturningField(f)
continue continue
} }
if f.NotNull && f.SQLDefault == "" { if f.NotNull && q.marshalsToDefault(f, strct) {
if (f.IsPtr && f.HasNilValue(strct)) || (f.NullZero && f.HasZeroValue(strct)) {
q.addReturningField(f) q.addReturningField(f)
continue continue
} }
}
fields = append(fields, f) fields = append(fields, f)
} }
return fields, nil return fields, nil
} }
// marshalsToDefault checks if the value will be marshaled as DEFAULT or NULL (if DEFAULT placeholder is not supported)
// when appending it to the VALUES clause in place of the given field.
func (q InsertQuery) marshalsToDefault(f *schema.Field, v reflect.Value) bool {
return (f.IsPtr && f.HasNilValue(v)) ||
(f.HasZeroValue(v) && (f.NullZero || f.SQLDefault != ""))
}
func (q *InsertQuery) appendFields( func (q *InsertQuery) appendFields(
fmter schema.Formatter, b []byte, fields []*schema.Field, fmter schema.Formatter, b []byte, fields []*schema.Field,
) []byte { ) []byte {

View file

@ -29,7 +29,7 @@ func NewMergeQuery(db *DB) *MergeQuery {
conn: db.DB, conn: db.DB,
}, },
} }
if !(q.db.dialect.Name() == dialect.MSSQL || q.db.dialect.Name() == dialect.PG) { if q.db.dialect.Name() != dialect.MSSQL && q.db.dialect.Name() != dialect.PG {
q.err = errors.New("bun: merge not supported for current dialect") q.err = errors.New("bun: merge not supported for current dialect")
} }
return q return q

View file

@ -1,6 +1,7 @@
package bun package bun
import ( import (
"bytes"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
@ -19,6 +20,7 @@ type CreateTableQuery struct {
temp bool temp bool
ifNotExists bool ifNotExists bool
fksFromRel bool // Create foreign keys captured in table's relations.
// varchar changes the default length for VARCHAR columns. // varchar changes the default length for VARCHAR columns.
// Because some dialects require that length is always specified for VARCHAR type, // Because some dialects require that length is always specified for VARCHAR type,
@ -120,21 +122,9 @@ func (q *CreateTableQuery) TableSpace(tablespace string) *CreateTableQuery {
return q return q
} }
// WithForeignKeys adds a FOREIGN KEY clause for each of the model's existing relations.
func (q *CreateTableQuery) WithForeignKeys() *CreateTableQuery { func (q *CreateTableQuery) WithForeignKeys() *CreateTableQuery {
for _, relation := range q.tableModel.Table().Relations { q.fksFromRel = true
if relation.Type == schema.ManyToManyRelation ||
relation.Type == schema.HasManyRelation {
continue
}
q = q.ForeignKey("(?) REFERENCES ? (?) ? ?",
Safe(appendColumns(nil, "", relation.BaseFields)),
relation.JoinTable.SQLName,
Safe(appendColumns(nil, "", relation.JoinFields)),
Safe(relation.OnUpdate),
Safe(relation.OnDelete),
)
}
return q return q
} }
@ -157,7 +147,7 @@ func (q *CreateTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []by
b = append(b, "TEMP "...) b = append(b, "TEMP "...)
} }
b = append(b, "TABLE "...) b = append(b, "TABLE "...)
if q.ifNotExists && fmter.Dialect().Features().Has(feature.TableNotExists) { if q.ifNotExists && fmter.HasFeature(feature.TableNotExists) {
b = append(b, "IF NOT EXISTS "...) b = append(b, "IF NOT EXISTS "...)
} }
b, err = q.appendFirstTable(fmter, b) b, err = q.appendFirstTable(fmter, b)
@ -178,19 +168,12 @@ func (q *CreateTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []by
if field.NotNull { if field.NotNull {
b = append(b, " NOT NULL"...) b = append(b, " NOT NULL"...)
} }
if field.AutoIncrement {
switch { if (field.Identity && fmter.HasFeature(feature.GeneratedIdentity)) ||
case fmter.Dialect().Features().Has(feature.AutoIncrement): (field.AutoIncrement && (fmter.HasFeature(feature.AutoIncrement) || fmter.HasFeature(feature.Identity))) {
b = append(b, " AUTO_INCREMENT"...) b = q.db.dialect.AppendSequence(b, q.table, field)
case fmter.Dialect().Features().Has(feature.Identity):
b = append(b, " IDENTITY"...)
}
}
if field.Identity {
if fmter.Dialect().Features().Has(feature.GeneratedIdentity) {
b = append(b, " GENERATED BY DEFAULT AS IDENTITY"...)
}
} }
if field.SQLDefault != "" { if field.SQLDefault != "" {
b = append(b, " DEFAULT "...) b = append(b, " DEFAULT "...)
b = append(b, field.SQLDefault...) b = append(b, field.SQLDefault...)
@ -210,8 +193,20 @@ func (q *CreateTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []by
} }
} }
// In SQLite AUTOINCREMENT is only valid for INTEGER PRIMARY KEY columns, so it might be that
// a primary key constraint has already been created in dialect.AppendSequence() call above.
// See sqldialect.Dialect.AppendSequence() for more details.
if len(q.table.PKs) > 0 && !bytes.Contains(b, []byte("PRIMARY KEY")) {
b = q.appendPKConstraint(b, q.table.PKs) b = q.appendPKConstraint(b, q.table.PKs)
}
b = q.appendUniqueConstraints(fmter, b) b = q.appendUniqueConstraints(fmter, b)
if q.fksFromRel {
b, err = q.appendFKConstraintsRel(fmter, b)
if err != nil {
return nil, err
}
}
b, err = q.appendFKConstraints(fmter, b) b, err = q.appendFKConstraints(fmter, b)
if err != nil { if err != nil {
return nil, err return nil, err
@ -295,13 +290,38 @@ func (q *CreateTableQuery) appendUniqueConstraint(
return b return b
} }
// appendFKConstraintsRel appends a FOREIGN KEY clause for each of the model's existing relations.
func (q *CreateTableQuery) appendFKConstraintsRel(fmter schema.Formatter, b []byte) (_ []byte, err error) {
for _, rel := range q.tableModel.Table().Relations {
if rel.References() {
b, err = q.appendFK(fmter, b, schema.QueryWithArgs{
Query: "(?) REFERENCES ? (?) ? ?",
Args: []interface{}{
Safe(appendColumns(nil, "", rel.BaseFields)),
rel.JoinTable.SQLName,
Safe(appendColumns(nil, "", rel.JoinFields)),
Safe(rel.OnUpdate),
Safe(rel.OnDelete),
},
})
if err != nil {
return nil, err
}
}
}
return b, nil
}
func (q *CreateTableQuery) appendFK(fmter schema.Formatter, b []byte, fk schema.QueryWithArgs) (_ []byte, err error) {
b = append(b, ", FOREIGN KEY "...)
return fk.AppendQuery(fmter, b)
}
func (q *CreateTableQuery) appendFKConstraints( func (q *CreateTableQuery) appendFKConstraints(
fmter schema.Formatter, b []byte, fmter schema.Formatter, b []byte,
) (_ []byte, err error) { ) (_ []byte, err error) {
for _, fk := range q.fks { for _, fk := range q.fks {
b = append(b, ", FOREIGN KEY "...) if b, err = q.appendFK(fmter, b, fk); err != nil {
b, err = fk.AppendQuery(fmter, b)
if err != nil {
return nil, err return nil, err
} }
} }
@ -309,10 +329,6 @@ func (q *CreateTableQuery) appendFKConstraints(
} }
func (q *CreateTableQuery) appendPKConstraint(b []byte, pks []*schema.Field) []byte { func (q *CreateTableQuery) appendPKConstraint(b []byte, pks []*schema.Field) []byte {
if len(pks) == 0 {
return b
}
b = append(b, ", PRIMARY KEY ("...) b = append(b, ", PRIMARY KEY ("...)
b = appendColumns(b, "", pks) b = appendColumns(b, "", pks)
b = append(b, ")"...) b = append(b, ")"...)
@ -364,3 +380,12 @@ func (q *CreateTableQuery) afterCreateTableHook(ctx context.Context) error {
} }
return nil return nil
} }
func (q *CreateTableQuery) String() string {
buf, err := q.AppendQuery(q.db.Formatter(), nil)
if err != nil {
panic(err)
}
return string(buf)
}

View file

@ -20,6 +20,7 @@ type UpdateQuery struct {
setQuery setQuery
idxHintsQuery idxHintsQuery
joins []joinQuery
omitZero bool omitZero bool
} }
@ -133,6 +134,33 @@ func (q *UpdateQuery) OmitZero() *UpdateQuery {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
func (q *UpdateQuery) Join(join string, args ...interface{}) *UpdateQuery {
q.joins = append(q.joins, joinQuery{
join: schema.SafeQuery(join, args),
})
return q
}
func (q *UpdateQuery) JoinOn(cond string, args ...interface{}) *UpdateQuery {
return q.joinOn(cond, args, " AND ")
}
func (q *UpdateQuery) JoinOnOr(cond string, args ...interface{}) *UpdateQuery {
return q.joinOn(cond, args, " OR ")
}
func (q *UpdateQuery) joinOn(cond string, args []interface{}, sep string) *UpdateQuery {
if len(q.joins) == 0 {
q.err = errors.New("bun: query has no joins")
return q
}
j := &q.joins[len(q.joins)-1]
j.on = append(j.on, schema.SafeQueryWithSep(cond, args, sep))
return q
}
//------------------------------------------------------------------------------
func (q *UpdateQuery) WherePK(cols ...string) *UpdateQuery { func (q *UpdateQuery) WherePK(cols ...string) *UpdateQuery {
q.addWhereCols(cols) q.addWhereCols(cols)
return q return q
@ -230,6 +258,13 @@ func (q *UpdateQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []byte, e
} }
} }
for _, j := range q.joins {
b, err = j.AppendQuery(fmter, b)
if err != nil {
return nil, err
}
}
if q.hasFeature(feature.Output) && q.hasReturning() { if q.hasFeature(feature.Output) && q.hasReturning() {
b = append(b, " OUTPUT "...) b = append(b, " OUTPUT "...)
b, err = q.appendOutput(fmter, b) b, err = q.appendOutput(fmter, b)

View file

@ -31,6 +31,10 @@ type Dialect interface {
AppendJSON(b, jsonb []byte) []byte AppendJSON(b, jsonb []byte) []byte
AppendBool(b []byte, v bool) []byte AppendBool(b []byte, v bool) []byte
// AppendSequence adds the appropriate instruction for the driver to create a sequence
// from which (autoincremented) values for the column will be generated.
AppendSequence(b []byte, t *Table, f *Field) []byte
// DefaultVarcharLen should be returned for dialects in which specifying VARCHAR length // DefaultVarcharLen should be returned for dialects in which specifying VARCHAR length
// is mandatory in queries that modify the schema (CREATE TABLE / ADD COLUMN, etc). // is mandatory in queries that modify the schema (CREATE TABLE / ADD COLUMN, etc).
// Dialects that do not have such requirement may return 0, which should be interpreted so by the caller. // Dialects that do not have such requirement may return 0, which should be interpreted so by the caller.
@ -177,3 +181,7 @@ func (d *nopDialect) IdentQuote() byte {
func (d *nopDialect) DefaultVarcharLen() int { func (d *nopDialect) DefaultVarcharLen() int {
return 0 return 0
} }
func (d *nopDialect) AppendSequence(b []byte, _ *Table, _ *Field) []byte {
return b
}

View file

@ -30,6 +30,12 @@ type Relation struct {
M2MJoinFields []*Field M2MJoinFields []*Field
} }
// References returns true if the table to which the Relation belongs needs to declare a foreign key constraint to create the relation.
// For other relations, the constraint is created in either the referencing table (1:N, 'has-many' relations) or a mapping table (N:N, 'm2m' relations).
func (r *Relation) References() bool {
return r.Type == HasOneRelation || r.Type == BelongsToRelation
}
func (r *Relation) String() string { func (r *Relation) String() string {
return fmt.Sprintf("relation=%s", r.Field.GoName) return fmt.Sprintf("relation=%s", r.Field.GoName)
} }

View file

@ -377,7 +377,6 @@ func (t *Table) newField(f reflect.StructField, prefix string, index []int) *Fie
} }
if s, ok := tag.Option("default"); ok { if s, ok := tag.Option("default"); ok {
field.SQLDefault = s field.SQLDefault = s
field.NullZero = true
} }
if s, ok := field.Tag.Option("type"); ok { if s, ok := field.Tag.Option("type"); ok {
field.UserSQLType = s field.UserSQLType = s
@ -477,7 +476,7 @@ func (t *Table) belongsToRelation(field *Field) *Relation {
} }
rel := &Relation{ rel := &Relation{
Type: HasOneRelation, Type: BelongsToRelation,
Field: field, Field: field,
JoinTable: joinTable, JoinTable: joinTable,
} }
@ -571,7 +570,7 @@ func (t *Table) hasOneRelation(field *Field) *Relation {
joinTable := t.dialect.Tables().Ref(field.IndirectType) joinTable := t.dialect.Tables().Ref(field.IndirectType)
rel := &Relation{ rel := &Relation{
Type: BelongsToRelation, Type: HasOneRelation,
Field: field, Field: field,
JoinTable: joinTable, JoinTable: joinTable,
} }

View file

@ -2,5 +2,5 @@
// Version is the current release version. // Version is the current release version.
func Version() string { func Version() string {
return "1.1.16" return "1.1.17"
} }

View file

@ -21,6 +21,12 @@ func Open(driverName, dsn string, opts ...Option) (*sql.DB, error) {
func patchDB(db *sql.DB, dsn string, opts ...Option) (*sql.DB, error) { func patchDB(db *sql.DB, dsn string, opts ...Option) (*sql.DB, error) {
dbDriver := db.Driver() dbDriver := db.Driver()
// Close the db since we are about to open a new one.
if err := db.Close(); err != nil {
return nil, err
}
d := newDriver(dbDriver, opts) d := newDriver(dbDriver, opts)
if _, ok := dbDriver.(driver.DriverContext); ok { if _, ok := dbDriver.(driver.DriverContext); ok {

View file

@ -2,5 +2,5 @@
// Version is the current release version. // Version is the current release version.
func Version() string { func Version() string {
return "0.2.2" return "0.2.3"
} }

View file

@ -1,6 +1,30 @@
## [5.4.1](https://github.com/vmihailenco/msgpack/compare/v5.4.0...v5.4.1) (2023-10-26)
### Bug Fixes
* **reflect:** not assignable to type ([edeaedd](https://github.com/vmihailenco/msgpack/commit/edeaeddb2d51868df8c6ff2d8a218b527aeaf5fd))
# [5.4.0](https://github.com/vmihailenco/msgpack/compare/v5.3.6...v5.4.0) (2023-10-01)
## [5.3.6](https://github.com/vmihailenco/msgpack/compare/v5.3.5...v5.3.6) (2023-10-01)
### Features
* allow overwriting time.Time parsing from extID 13 (for NodeJS Date) ([9a6b73b](https://github.com/vmihailenco/msgpack/commit/9a6b73b3588fd962d568715f4375e24b089f7066))
* apply omitEmptyFlag to empty structs ([e5f8d03](https://github.com/vmihailenco/msgpack/commit/e5f8d03c0a1dd9cc571d648cd610305139078de5))
* support sorted keys for map[string]bool ([690c1fa](https://github.com/vmihailenco/msgpack/commit/690c1fab9814fab4842295ea986111f49850d9a4))
## [5.3.5](https://github.com/vmihailenco/msgpack/compare/v5.3.4...v5.3.5) (2021-10-22) ## [5.3.5](https://github.com/vmihailenco/msgpack/compare/v5.3.4...v5.3.5) (2021-10-22)
- Allow decoding `nil` code as boolean false.
## v5 ## v5

View file

@ -5,19 +5,18 @@
[![Documentation](https://img.shields.io/badge/msgpack-documentation-informational)](https://msgpack.uptrace.dev/) [![Documentation](https://img.shields.io/badge/msgpack-documentation-informational)](https://msgpack.uptrace.dev/)
[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj) [![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj)
> :heart: > msgpack is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
> [**Uptrace.dev** - All-in-one tool to optimize performance and monitor errors & logs](https://uptrace.dev/?utm_source=gh-msgpack&utm_campaign=gh-msgpack-var2) > Uptrace is an [open source APM](https://uptrace.dev/get/open-source-apm.html) and blazingly fast
> [distributed tracing tool](https://get.uptrace.dev/compare/distributed-tracing-tools.html) powered
> by OpenTelemetry and ClickHouse. Give it a star as well!
## Resources
- Join [Discord](https://discord.gg/rWtp5Aj) to ask questions.
- [Documentation](https://msgpack.uptrace.dev) - [Documentation](https://msgpack.uptrace.dev)
- [Chat](https://discord.gg/rWtp5Aj)
- [Reference](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5) - [Reference](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5)
- [Examples](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#pkg-examples) - [Examples](https://pkg.go.dev/github.com/vmihailenco/msgpack/v5#pkg-examples)
Other projects you may like:
- [Bun](https://bun.uptrace.dev) - fast and simple SQL client for PostgreSQL, MySQL, and SQLite.
- [BunRouter](https://bunrouter.uptrace.dev/) - fast and flexible HTTP router for Go.
## Features ## Features
- Primitives, arrays, maps, structs, time.Time and interface{}. - Primitives, arrays, maps, structs, time.Time and interface{}.
@ -84,3 +83,18 @@ func ExampleMarshal() {
// Output: bar // Output: bar
} }
``` ```
## See also
- [Golang ORM](https://github.com/uptrace/bun) for PostgreSQL, MySQL, MSSQL, and SQLite
- [Golang PostgreSQL](https://bun.uptrace.dev/postgres/)
- [Golang HTTP router](https://github.com/uptrace/bunrouter)
- [Golang ClickHouse ORM](https://github.com/uptrace/go-clickhouse)
## Contributors
Thanks to all the people who already contributed!
<a href="https://github.com/vmihailenco/msgpack/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=vmihailenco/msgpack" />
</a>

View file

@ -14,14 +14,16 @@
) )
const ( const (
looseInterfaceDecodingFlag uint32 = 1 << iota bytesAllocLimit = 1 << 20 // 1mb
disallowUnknownFieldsFlag sliceAllocLimit = 1e6 // 1m elements
maxMapSize = 1e6 // 1m elements
) )
const ( const (
bytesAllocLimit = 1e6 // 1mb looseInterfaceDecodingFlag uint32 = 1 << iota
sliceAllocLimit = 1e4 disallowUnknownFieldsFlag
maxMapSize = 1e6 usePreallocateValues
disableAllocLimitFlag
) )
type bufReader interface { type bufReader interface {
@ -53,7 +55,7 @@ func PutDecoder(dec *Decoder) {
// in the value pointed to by v. // in the value pointed to by v.
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
dec := GetDecoder() dec := GetDecoder()
dec.UsePreallocateValues(true)
dec.Reset(bytes.NewReader(data)) dec.Reset(bytes.NewReader(data))
err := dec.Decode(v) err := dec.Decode(v)
@ -66,14 +68,12 @@ func Unmarshal(data []byte, v interface{}) error {
type Decoder struct { type Decoder struct {
r io.Reader r io.Reader
s io.ByteScanner s io.ByteScanner
mapDecoder func(*Decoder) (interface{}, error)
structTag string
buf []byte buf []byte
rec []byte
rec []byte // accumulates read data if not nil
dict []string dict []string
flags uint32 flags uint32
structTag string
mapDecoder func(*Decoder) (interface{}, error)
} }
// NewDecoder returns a new decoder that reads from r. // NewDecoder returns a new decoder that reads from r.
@ -95,10 +95,9 @@ func (d *Decoder) Reset(r io.Reader) {
// ResetDict is like Reset, but also resets the dict. // ResetDict is like Reset, but also resets the dict.
func (d *Decoder) ResetDict(r io.Reader, dict []string) { func (d *Decoder) ResetDict(r io.Reader, dict []string) {
d.resetReader(r) d.ResetReader(r)
d.flags = 0 d.flags = 0
d.structTag = "" d.structTag = ""
d.mapDecoder = nil
d.dict = dict d.dict = dict
} }
@ -110,10 +109,16 @@ func (d *Decoder) WithDict(dict []string, fn func(*Decoder) error) error {
return err return err
} }
func (d *Decoder) resetReader(r io.Reader) { func (d *Decoder) ResetReader(r io.Reader) {
d.mapDecoder = nil
d.dict = nil
if br, ok := r.(bufReader); ok { if br, ok := r.(bufReader); ok {
d.r = br d.r = br
d.s = br d.s = br
} else if r == nil {
d.r = nil
d.s = nil
} else { } else {
br := bufio.NewReader(r) br := bufio.NewReader(r)
d.r = br d.r = br
@ -161,6 +166,24 @@ func (d *Decoder) UseInternedStrings(on bool) {
} }
} }
// UsePreallocateValues enables preallocating values in chunks
func (d *Decoder) UsePreallocateValues(on bool) {
if on {
d.flags |= usePreallocateValues
} else {
d.flags &= ^usePreallocateValues
}
}
// DisableAllocLimit enables fully allocating slices/maps when the size is known
func (d *Decoder) DisableAllocLimit(on bool) {
if on {
d.flags |= disableAllocLimitFlag
} else {
d.flags &= ^disableAllocLimitFlag
}
}
// Buffered returns a reader of the data remaining in the Decoder's buffer. // Buffered returns a reader of the data remaining in the Decoder's buffer.
// The reader is valid until the next call to Decode. // The reader is valid until the next call to Decode.
func (d *Decoder) Buffered() io.Reader { func (d *Decoder) Buffered() io.Reader {
@ -603,7 +626,11 @@ func (d *Decoder) readFull(b []byte) error {
func (d *Decoder) readN(n int) ([]byte, error) { func (d *Decoder) readN(n int) ([]byte, error) {
var err error var err error
if d.flags&disableAllocLimitFlag != 0 {
d.buf, err = readN(d.r, d.buf, n) d.buf, err = readN(d.r, d.buf, n)
} else {
d.buf, err = readNGrow(d.r, d.buf, n)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -615,6 +642,24 @@ func (d *Decoder) readN(n int) ([]byte, error) {
} }
func readN(r io.Reader, b []byte, n int) ([]byte, error) { func readN(r io.Reader, b []byte, n int) ([]byte, error) {
if b == nil {
if n == 0 {
return make([]byte, 0), nil
}
b = make([]byte, 0, n)
}
if n > cap(b) {
b = append(b, make([]byte, n-len(b))...)
} else if n <= cap(b) {
b = b[:n]
}
_, err := io.ReadFull(r, b)
return b, err
}
func readNGrow(r io.Reader, b []byte, n int) ([]byte, error) {
if b == nil { if b == nil {
if n == 0 { if n == 0 {
return make([]byte, 0), nil return make([]byte, 0), nil

View file

@ -13,6 +13,8 @@
var ( var (
mapStringStringPtrType = reflect.TypeOf((*map[string]string)(nil)) mapStringStringPtrType = reflect.TypeOf((*map[string]string)(nil))
mapStringStringType = mapStringStringPtrType.Elem() mapStringStringType = mapStringStringPtrType.Elem()
mapStringBoolPtrType = reflect.TypeOf((*map[string]bool)(nil))
mapStringBoolType = mapStringBoolPtrType.Elem()
) )
var ( var (
@ -33,7 +35,11 @@ func decodeMapValue(d *Decoder, v reflect.Value) error {
} }
if v.IsNil() { if v.IsNil() {
v.Set(reflect.MakeMap(typ)) ln := n
if d.flags&disableAllocLimitFlag == 0 {
ln = min(ln, maxMapSize)
}
v.Set(reflect.MakeMapWithSize(typ, ln))
} }
if n == 0 { if n == 0 {
return nil return nil
@ -104,7 +110,11 @@ func (d *Decoder) decodeMapStringStringPtr(ptr *map[string]string) error {
m := *ptr m := *ptr
if m == nil { if m == nil {
*ptr = make(map[string]string, min(size, maxMapSize)) ln := size
if d.flags&disableAllocLimitFlag == 0 {
ln = min(size, maxMapSize)
}
*ptr = make(map[string]string, ln)
m = *ptr m = *ptr
} }
@ -147,7 +157,7 @@ func (d *Decoder) DecodeMap() (map[string]interface{}, error) {
return nil, nil return nil, nil
} }
m := make(map[string]interface{}, min(n, maxMapSize)) m := make(map[string]interface{}, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
mk, err := d.DecodeString() mk, err := d.DecodeString()
@ -174,7 +184,7 @@ func (d *Decoder) DecodeUntypedMap() (map[interface{}]interface{}, error) {
return nil, nil return nil, nil
} }
m := make(map[interface{}]interface{}, min(n, maxMapSize)) m := make(map[interface{}]interface{}, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
mk, err := d.decodeInterfaceCond() mk, err := d.decodeInterfaceCond()
@ -222,7 +232,13 @@ func (d *Decoder) DecodeTypedMap() (interface{}, error) {
} }
mapType := reflect.MapOf(keyType, valueType) mapType := reflect.MapOf(keyType, valueType)
mapValue := reflect.MakeMap(mapType)
ln := n
if d.flags&disableAllocLimitFlag == 0 {
ln = min(ln, maxMapSize)
}
mapValue := reflect.MakeMapWithSize(mapType, ln)
mapValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(value)) mapValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(value))
n-- n--
@ -234,17 +250,18 @@ func (d *Decoder) DecodeTypedMap() (interface{}, error) {
} }
func (d *Decoder) decodeTypedMapValue(v reflect.Value, n int) error { func (d *Decoder) decodeTypedMapValue(v reflect.Value, n int) error {
typ := v.Type() var (
keyType := typ.Key() typ = v.Type()
valueType := typ.Elem() keyType = typ.Key()
valueType = typ.Elem()
)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
mk := reflect.New(keyType).Elem() mk := d.newValue(keyType).Elem()
if err := d.DecodeValue(mk); err != nil { if err := d.DecodeValue(mk); err != nil {
return err return err
} }
mv := reflect.New(valueType).Elem() mv := d.newValue(valueType).Elem()
if err := d.DecodeValue(mv); err != nil { if err := d.DecodeValue(mv); err != nil {
return err return err
} }

View file

@ -11,9 +11,8 @@
type queryResult struct { type queryResult struct {
query string query string
key string key string
hasAsterisk bool
values []interface{} values []interface{}
hasAsterisk bool
} }
func (q *queryResult) nextKey() { func (q *queryResult) nextKey() {

View file

@ -49,7 +49,7 @@ func (d *Decoder) decodeStringSlicePtr(ptr *[]string) error {
return nil return nil
} }
ss := makeStrings(*ptr, n) ss := makeStrings(*ptr, n, d.flags&disableAllocLimitFlag != 0)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
s, err := d.DecodeString() s, err := d.DecodeString()
if err != nil { if err != nil {
@ -62,8 +62,8 @@ func (d *Decoder) decodeStringSlicePtr(ptr *[]string) error {
return nil return nil
} }
func makeStrings(s []string, n int) []string { func makeStrings(s []string, n int, noLimit bool) []string {
if n > sliceAllocLimit { if !noLimit && n > sliceAllocLimit {
n = sliceAllocLimit n = sliceAllocLimit
} }
@ -101,10 +101,17 @@ func decodeSliceValue(d *Decoder, v reflect.Value) error {
v.Set(v.Slice(0, v.Cap())) v.Set(v.Slice(0, v.Cap()))
} }
for i := 0; i < n; i++ { noLimit := d.flags&disableAllocLimitFlag != 1
if i >= v.Len() {
v.Set(growSliceValue(v, n)) if noLimit && n > v.Len() {
v.Set(growSliceValue(v, n, noLimit))
} }
for i := 0; i < n; i++ {
if !noLimit && i >= v.Len() {
v.Set(growSliceValue(v, n, noLimit))
}
elem := v.Index(i) elem := v.Index(i)
if err := d.DecodeValue(elem); err != nil { if err := d.DecodeValue(elem); err != nil {
return err return err
@ -114,9 +121,9 @@ func decodeSliceValue(d *Decoder, v reflect.Value) error {
return nil return nil
} }
func growSliceValue(v reflect.Value, n int) reflect.Value { func growSliceValue(v reflect.Value, n int, noLimit bool) reflect.Value {
diff := n - v.Len() diff := n - v.Len()
if diff > sliceAllocLimit { if !noLimit && diff > sliceAllocLimit {
diff = sliceAllocLimit diff = sliceAllocLimit
} }
v = reflect.AppendSlice(v, reflect.MakeSlice(v.Type(), diff, diff)) v = reflect.AppendSlice(v, reflect.MakeSlice(v.Type(), diff, diff))
@ -163,7 +170,7 @@ func (d *Decoder) decodeSlice(c byte) ([]interface{}, error) {
return nil, nil return nil, nil
} }
s := make([]interface{}, 0, min(n, sliceAllocLimit)) s := make([]interface{}, 0, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
v, err := d.decodeInterfaceCond() v, err := d.decodeInterfaceCond()
if err != nil { if err != nil {

View file

@ -0,0 +1,46 @@
package msgpack
import (
"reflect"
"sync"
)
var cachedValues struct {
m map[reflect.Type]chan reflect.Value
sync.RWMutex
}
func cachedValue(t reflect.Type) reflect.Value {
cachedValues.RLock()
ch := cachedValues.m[t]
cachedValues.RUnlock()
if ch != nil {
return <-ch
}
cachedValues.Lock()
defer cachedValues.Unlock()
if ch = cachedValues.m[t]; ch != nil {
return <-ch
}
ch = make(chan reflect.Value, 256)
go func() {
for {
ch <- reflect.New(t)
}
}()
if cachedValues.m == nil {
cachedValues.m = make(map[reflect.Type]chan reflect.Value, 8)
}
cachedValues.m[t] = ch
return <-ch
}
func (d *Decoder) newValue(t reflect.Type) reflect.Value {
if d.flags&usePreallocateValues == 0 {
return reflect.New(t)
}
return cachedValue(t)
}

View file

@ -10,6 +10,7 @@
var ( var (
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem() interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
stringType = reflect.TypeOf((*string)(nil)).Elem() stringType = reflect.TypeOf((*string)(nil)).Elem()
boolType = reflect.TypeOf((*bool)(nil)).Elem()
) )
var valueDecoders []decoderFunc var valueDecoders []decoderFunc
@ -127,12 +128,12 @@ func ptrValueDecoder(typ reflect.Type) decoderFunc {
return func(d *Decoder, v reflect.Value) error { return func(d *Decoder, v reflect.Value) error {
if d.hasNilCode() { if d.hasNilCode() {
if !v.IsNil() { if !v.IsNil() {
v.Set(reflect.Zero(v.Type())) v.Set(d.newValue(typ).Elem())
} }
return d.DecodeNil() return d.DecodeNil()
} }
if v.IsNil() { if v.IsNil() {
v.Set(reflect.New(v.Type().Elem())) v.Set(d.newValue(typ.Elem()))
} }
return decoder(d, v.Elem()) return decoder(d, v.Elem())
} }
@ -154,7 +155,7 @@ func nilAwareDecoder(typ reflect.Type, fn decoderFunc) decoderFunc {
return d.decodeNilValue(v) return d.decodeNilValue(v)
} }
if v.IsNil() { if v.IsNil() {
v.Set(reflect.New(v.Type().Elem())) v.Set(d.newValue(typ.Elem()))
} }
return fn(d, v) return fn(d, v)
} }

View file

@ -76,14 +76,11 @@ func Marshal(v interface{}) ([]byte, error) {
type Encoder struct { type Encoder struct {
w writer w writer
dict map[string]int
structTag string
buf []byte buf []byte
timeBuf []byte timeBuf []byte
dict map[string]int
flags uint32 flags uint32
structTag string
} }
// NewEncoder returns a new encoder that writes to w. // NewEncoder returns a new encoder that writes to w.
@ -107,7 +104,7 @@ func (e *Encoder) Reset(w io.Writer) {
// ResetDict is like Reset, but also resets the dict. // ResetDict is like Reset, but also resets the dict.
func (e *Encoder) ResetDict(w io.Writer, dict map[string]int) { func (e *Encoder) ResetDict(w io.Writer, dict map[string]int) {
e.resetWriter(w) e.ResetWriter(w)
e.flags = 0 e.flags = 0
e.structTag = "" e.structTag = ""
e.dict = dict e.dict = dict
@ -121,9 +118,12 @@ func (e *Encoder) WithDict(dict map[string]int, fn func(*Encoder) error) error {
return err return err
} }
func (e *Encoder) resetWriter(w io.Writer) { func (e *Encoder) ResetWriter(w io.Writer) {
e.dict = nil
if bw, ok := w.(writer); ok { if bw, ok := w.(writer); ok {
e.w = bw e.w = bw
} else if w == nil {
e.w = nil
} else { } else {
e.w = newByteWriter(w) e.w = newByteWriter(w)
} }
@ -132,6 +132,7 @@ func (e *Encoder) resetWriter(w io.Writer) {
// SetSortMapKeys causes the Encoder to encode map keys in increasing order. // SetSortMapKeys causes the Encoder to encode map keys in increasing order.
// Supported map types are: // Supported map types are:
// - map[string]string // - map[string]string
// - map[string]bool
// - map[string]interface{} // - map[string]interface{}
func (e *Encoder) SetSortMapKeys(on bool) *Encoder { func (e *Encoder) SetSortMapKeys(on bool) *Encoder {
if on { if on {

View file

@ -30,6 +30,32 @@ func encodeMapValue(e *Encoder, v reflect.Value) error {
return nil return nil
} }
func encodeMapStringBoolValue(e *Encoder, v reflect.Value) error {
if v.IsNil() {
return e.EncodeNil()
}
if err := e.EncodeMapLen(v.Len()); err != nil {
return err
}
m := v.Convert(mapStringBoolType).Interface().(map[string]bool)
if e.flags&sortMapKeysFlag != 0 {
return e.encodeSortedMapStringBool(m)
}
for mk, mv := range m {
if err := e.EncodeString(mk); err != nil {
return err
}
if err := e.EncodeBool(mv); err != nil {
return err
}
}
return nil
}
func encodeMapStringStringValue(e *Encoder, v reflect.Value) error { func encodeMapStringStringValue(e *Encoder, v reflect.Value) error {
if v.IsNil() { if v.IsNil() {
return e.EncodeNil() return e.EncodeNil()
@ -113,6 +139,26 @@ func (e *Encoder) EncodeMapSorted(m map[string]interface{}) error {
return nil return nil
} }
func (e *Encoder) encodeSortedMapStringBool(m map[string]bool) error {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
err := e.EncodeString(k)
if err != nil {
return err
}
if err = e.EncodeBool(m[k]); err != nil {
return err
}
}
return nil
}
func (e *Encoder) encodeSortedMapStringString(m map[string]string) error { func (e *Encoder) encodeSortedMapStringString(m map[string]string) error {
keys := make([]string, 0, len(m)) keys := make([]string, 0, len(m))
for k := range m { for k := range m {
@ -148,7 +194,7 @@ func encodeStructValue(e *Encoder, strct reflect.Value) error {
if e.flags&arrayEncodedStructsFlag != 0 || structFields.AsArray { if e.flags&arrayEncodedStructsFlag != 0 || structFields.AsArray {
return encodeStructValueAsArray(e, strct, structFields.List) return encodeStructValueAsArray(e, strct, structFields.List)
} }
fields := structFields.OmitEmpty(strct, e.flags&omitEmptyFlag != 0) fields := structFields.OmitEmpty(e, strct)
if err := e.EncodeMapLen(len(fields)); err != nil { if err := e.EncodeMapLen(len(fields)); err != nil {
return err return err

View file

@ -111,6 +111,8 @@ func _getEncoder(typ reflect.Type) encoderFunc {
switch typ.Elem() { switch typ.Elem() {
case stringType: case stringType:
return encodeMapStringStringValue return encodeMapStringStringValue
case boolType:
return encodeMapStringBoolValue
case interfaceType: case interfaceType:
return encodeMapStringInterfaceValue return encodeMapStringInterfaceValue
} }
@ -198,6 +200,13 @@ func nilable(kind reflect.Kind) bool {
return false return false
} }
func nilableType(t reflect.Type) bool {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return nilable(t.Kind())
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
func marshalBinaryValueAddr(e *Encoder, v reflect.Value) error { func marshalBinaryValueAddr(e *Encoder, v reflect.Value) error {

View file

@ -96,7 +96,7 @@ func makeExtEncoder(
func makeExtEncoderAddr(extEncoder encoderFunc) encoderFunc { func makeExtEncoderAddr(extEncoder encoderFunc) encoderFunc {
return func(e *Encoder, v reflect.Value) error { return func(e *Encoder, v reflect.Value) error {
if !v.CanAddr() { if !v.CanAddr() {
return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface()) return fmt.Errorf("msgpack: EncodeExt(nonaddressable %T)", v.Interface())
} }
return extEncoder(e, v.Addr()) return extEncoder(e, v.Addr())
} }
@ -157,7 +157,7 @@ func makeExtDecoder(
func makeExtDecoderAddr(extDecoder decoderFunc) decoderFunc { func makeExtDecoderAddr(extDecoder decoderFunc) decoderFunc {
return func(d *Decoder, v reflect.Value) error { return func(d *Decoder, v reflect.Value) error {
if !v.CanAddr() { if !v.CanAddr() {
return fmt.Errorf("msgpack: Decode(nonaddressable %T)", v.Interface()) return fmt.Errorf("msgpack: DecodeExt(nonaddressable %T)", v.Interface())
} }
return extDecoder(d, v.Addr()) return extDecoder(d, v.Addr())
} }
@ -254,9 +254,9 @@ func (d *Decoder) decodeInterfaceExt(c byte) (interface{}, error) {
return nil, fmt.Errorf("msgpack: unknown ext id=%d", extID) return nil, fmt.Errorf("msgpack: unknown ext id=%d", extID)
} }
v := reflect.New(info.Type).Elem() v := d.newValue(info.Type).Elem()
if nilable(v.Kind()) && v.IsNil() { if nilable(v.Kind()) && v.IsNil() {
v.Set(reflect.New(info.Type.Elem())) v.Set(d.newValue(info.Type.Elem()))
} }
if err := info.Decoder(d, v, extLen); err != nil { if err := info.Decoder(d, v, extLen); err != nil {

View file

@ -57,19 +57,17 @@ func encodeInternedStringValue(e *Encoder, v reflect.Value) error {
func (e *Encoder) encodeInternedString(s string, intern bool) error { func (e *Encoder) encodeInternedString(s string, intern bool) error {
// Interned string takes at least 3 bytes. Plain string 1 byte + string len. // Interned string takes at least 3 bytes. Plain string 1 byte + string len.
if len(s) >= minInternedStringLen {
if idx, ok := e.dict[s]; ok { if idx, ok := e.dict[s]; ok {
return e.encodeInternedStringIndex(idx) return e.encodeInternedStringIndex(idx)
} }
if intern && len(e.dict) < maxDictLen { if intern && len(s) >= minInternedStringLen && len(e.dict) < maxDictLen {
if e.dict == nil { if e.dict == nil {
e.dict = make(map[string]int) e.dict = make(map[string]int)
} }
idx := len(e.dict) idx := len(e.dict)
e.dict[s] = idx e.dict[s] = idx
} }
}
return e.encodeNormalString(s) return e.encodeNormalString(s)
} }

View file

@ -43,8 +43,8 @@ func (m *RawMessage) DecodeMsgpack(dec *Decoder) error {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type unexpectedCodeError struct { type unexpectedCodeError struct {
code byte
hint string hint string
code byte
} }
func (err unexpectedCodeError) Error() string { func (err unexpectedCodeError) Error() string {

View file

@ -1,4 +1,4 @@
{ {
"name": "msgpack", "name": "msgpack",
"version": "5.3.5" "version": "5.4.1"
} }

View file

@ -26,6 +26,11 @@ func timeDecoder(d *Decoder, v reflect.Value, extLen int) error {
return err return err
} }
if tm.IsZero() {
// Zero time does not have timezone information.
tm = tm.UTC()
}
ptr := v.Addr().Interface().(*time.Time) ptr := v.Addr().Interface().(*time.Time)
*ptr = tm *ptr = tm
@ -103,7 +108,8 @@ func (d *Decoder) DecodeTime() (time.Time, error) {
return time.Time{}, err return time.Time{}, err
} }
if extID != timeExtID { // NodeJS seems to use extID 13.
if extID != timeExtID && extID != 13 {
return time.Time{}, fmt.Errorf("msgpack: invalid time ext id=%d", extID) return time.Time{}, fmt.Errorf("msgpack: invalid time ext id=%d", extID)
} }

View file

@ -66,8 +66,8 @@ type structCache struct {
} }
type structCacheKey struct { type structCacheKey struct {
tag string
typ reflect.Type typ reflect.Type
tag string
} }
func newStructCache() *structCache { func newStructCache() *structCache {
@ -90,19 +90,20 @@ func (m *structCache) Fields(typ reflect.Type, tag string) *fields {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
type field struct { type field struct {
encoder encoderFunc
decoder decoderFunc
name string name string
index []int index []int
omitEmpty bool omitEmpty bool
encoder encoderFunc
decoder decoderFunc
} }
func (f *field) Omit(strct reflect.Value, forced bool) bool { func (f *field) Omit(e *Encoder, strct reflect.Value) bool {
v, ok := fieldByIndex(strct, f.index) v, ok := fieldByIndex(strct, f.index)
if !ok { if !ok {
return true return true
} }
return (f.omitEmpty || forced) && isEmptyValue(v) forced := e.flags&omitEmptyFlag != 0
return (f.omitEmpty || forced) && e.isEmptyValue(v)
} }
func (f *field) EncodeValue(e *Encoder, strct reflect.Value) error { func (f *field) EncodeValue(e *Encoder, strct reflect.Value) error {
@ -152,7 +153,8 @@ func (fs *fields) warnIfFieldExists(name string) {
} }
} }
func (fs *fields) OmitEmpty(strct reflect.Value, forced bool) []*field { func (fs *fields) OmitEmpty(e *Encoder, strct reflect.Value) []*field {
forced := e.flags&omitEmptyFlag != 0
if !fs.hasOmitEmpty && !forced { if !fs.hasOmitEmpty && !forced {
return fs.List return fs.List
} }
@ -160,7 +162,7 @@ func (fs *fields) OmitEmpty(strct reflect.Value, forced bool) []*field {
fields := make([]*field, 0, len(fs.List)) fields := make([]*field, 0, len(fs.List))
for _, f := range fs.List { for _, f := range fs.List {
if !f.Omit(strct, forced) { if !f.Omit(e, strct) {
fields = append(fields, f) fields = append(fields, f)
} }
} }
@ -317,7 +319,7 @@ type isZeroer interface {
IsZero() bool IsZero() bool
} }
func isEmptyValue(v reflect.Value) bool { func (e *Encoder) isEmptyValue(v reflect.Value) bool {
kind := v.Kind() kind := v.Kind()
for kind == reflect.Interface { for kind == reflect.Interface {
@ -335,6 +337,10 @@ func isEmptyValue(v reflect.Value) bool {
switch kind { switch kind {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String: case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0 return v.Len() == 0
case reflect.Struct:
structFields := structs.Fields(v.Type(), e.structTag)
fields := structFields.OmitEmpty(e, v)
return len(fields) == 0
case reflect.Bool: case reflect.Bool:
return !v.Bool() return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@ -399,7 +405,7 @@ func indirectNil(v reflect.Value) (reflect.Value, bool) {
if elemType.Kind() != reflect.Struct { if elemType.Kind() != reflect.Struct {
return v, false return v, false
} }
v.Set(reflect.New(elemType)) v.Set(cachedValue(elemType))
} }
v = v.Elem() v = v.Elem()
} }

View file

@ -2,5 +2,5 @@
// Version is the current release version. // Version is the current release version.
func Version() string { func Version() string {
return "5.3.5" return "5.4.1"
} }

18
vendor/modules.txt vendored
View file

@ -214,7 +214,7 @@ github.com/go-fed/httpsig
github.com/go-jose/go-jose/v3 github.com/go-jose/go-jose/v3
github.com/go-jose/go-jose/v3/cipher github.com/go-jose/go-jose/v3/cipher
github.com/go-jose/go-jose/v3/json github.com/go-jose/go-jose/v3/json
# github.com/go-logr/logr v1.3.0 # github.com/go-logr/logr v1.4.1
## explicit; go 1.18 ## explicit; go 1.18
github.com/go-logr/logr github.com/go-logr/logr
github.com/go-logr/logr/funcr github.com/go-logr/logr/funcr
@ -366,7 +366,7 @@ github.com/leodido/go-urn
# github.com/magiconair/properties v1.8.7 # github.com/magiconair/properties v1.8.7
## explicit; go 1.19 ## explicit; go 1.19
github.com/magiconair/properties github.com/magiconair/properties
# github.com/mattn/go-isatty v0.0.19 # github.com/mattn/go-isatty v0.0.20
## explicit; go 1.15 ## explicit; go 1.15
github.com/mattn/go-isatty github.com/mattn/go-isatty
# github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 # github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0
@ -719,7 +719,7 @@ github.com/ugorji/go/codec
github.com/ulule/limiter/v3 github.com/ulule/limiter/v3
github.com/ulule/limiter/v3/drivers/store/common github.com/ulule/limiter/v3/drivers/store/common
github.com/ulule/limiter/v3/drivers/store/memory github.com/ulule/limiter/v3/drivers/store/memory
# github.com/uptrace/bun v1.1.16 # github.com/uptrace/bun v1.1.17
## explicit; go 1.19 ## explicit; go 1.19
github.com/uptrace/bun github.com/uptrace/bun
github.com/uptrace/bun/dialect github.com/uptrace/bun/dialect
@ -731,20 +731,20 @@ github.com/uptrace/bun/internal/parser
github.com/uptrace/bun/internal/tagparser github.com/uptrace/bun/internal/tagparser
github.com/uptrace/bun/migrate github.com/uptrace/bun/migrate
github.com/uptrace/bun/schema github.com/uptrace/bun/schema
# github.com/uptrace/bun/dialect/pgdialect v1.1.16 # github.com/uptrace/bun/dialect/pgdialect v1.1.17
## explicit; go 1.19 ## explicit; go 1.19
github.com/uptrace/bun/dialect/pgdialect github.com/uptrace/bun/dialect/pgdialect
# github.com/uptrace/bun/dialect/sqlitedialect v1.1.16 # github.com/uptrace/bun/dialect/sqlitedialect v1.1.17
## explicit; go 1.19 ## explicit; go 1.19
github.com/uptrace/bun/dialect/sqlitedialect github.com/uptrace/bun/dialect/sqlitedialect
# github.com/uptrace/bun/extra/bunotel v1.1.16 # github.com/uptrace/bun/extra/bunotel v1.1.17
## explicit; go 1.19 ## explicit; go 1.19
github.com/uptrace/bun/extra/bunotel github.com/uptrace/bun/extra/bunotel
# github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2 # github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3
## explicit; go 1.18 ## explicit; go 1.18
github.com/uptrace/opentelemetry-go-extra/otelsql github.com/uptrace/opentelemetry-go-extra/otelsql
# github.com/vmihailenco/msgpack/v5 v5.3.5 # github.com/vmihailenco/msgpack/v5 v5.4.1
## explicit; go 1.11 ## explicit; go 1.19
github.com/vmihailenco/msgpack/v5 github.com/vmihailenco/msgpack/v5
github.com/vmihailenco/msgpack/v5/msgpcode github.com/vmihailenco/msgpack/v5/msgpcode
# github.com/vmihailenco/tagparser/v2 v2.0.0 # github.com/vmihailenco/tagparser/v2 v2.0.0