mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-25 13:16:40 +00:00
Compare commits
2 commits
693f542d15
...
a2b862a0b7
Author | SHA1 | Date | |
---|---|---|---|
a2b862a0b7 | |||
29007b1b88 |
23
go.mod
23
go.mod
|
@ -4,6 +4,12 @@ go 1.22.2
|
|||
|
||||
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.33.1-concurrency-workaround
|
||||
|
||||
replace go.opentelemetry.io/otel => go.opentelemetry.io/otel v1.29.0
|
||||
|
||||
replace go.opentelemetry.io/otel/metric => go.opentelemetry.io/otel/metric v1.29.0
|
||||
|
||||
replace go.opentelemetry.io/otel/trace => go.opentelemetry.io/otel/trace v1.29.0
|
||||
|
||||
require (
|
||||
codeberg.org/gruf/go-bytes v1.0.2
|
||||
codeberg.org/gruf/go-bytesize v1.0.3
|
||||
|
@ -57,20 +63,20 @@ require (
|
|||
github.com/tetratelabs/wazero v1.8.1
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
github.com/ulule/limiter/v3 v3.11.2
|
||||
github.com/uptrace/bun v1.2.1
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.1
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.1
|
||||
github.com/uptrace/bun v1.2.5
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.5
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.5
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.5
|
||||
github.com/wagslane/go-password-validator v0.3.0
|
||||
github.com/yuin/goldmark v1.7.8
|
||||
go.opentelemetry.io/otel v1.29.0
|
||||
go.opentelemetry.io/otel v1.31.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.51.0
|
||||
go.opentelemetry.io/otel/metric v1.29.0
|
||||
go.opentelemetry.io/otel/metric v1.31.0
|
||||
go.opentelemetry.io/otel/sdk v1.29.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.29.0
|
||||
go.opentelemetry.io/otel/trace v1.29.0
|
||||
go.opentelemetry.io/otel/trace v1.31.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/image v0.21.0
|
||||
|
@ -181,6 +187,7 @@ require (
|
|||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.59.1 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
|
@ -201,7 +208,7 @@ require (
|
|||
github.com/toqueteos/webbrowser v1.2.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4 // indirect
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
|
|
22
go.sum
generated
22
go.sum
generated
|
@ -469,6 +469,8 @@ github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJ
|
|||
github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
|
@ -578,16 +580,16 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
|||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
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/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk=
|
||||
github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.1 h1:ceP99r03u+s8ylaDE/RzgcajwGiC76Jz3nS2ZgyPQ4M=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.1/go.mod h1:mv6B12cisvSc6bwKm9q9wcrr26awkZK8QXM+nso9n2U=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1 h1:IprvkIKUjEjvt4VKpcmLpbMIucjrsmUPJOSlg19+a0Q=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1/go.mod h1:mMQf4NUpgY8bnOanxGmxNiHCdALOggS4cZ3v63a9D/o=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.1 h1:5oTy3Jh7Q1bhCd5vnPszBmJgYouw+PuuZ8iSCm+uNCQ=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.1/go.mod h1:SWW3HyjiXPYM36q0QSpdtTP8v21nWHnTCxu4lYkpO90=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4 h1:x3omFAG2XkvWFg1hvXRinY2ExAL1Aacl7W9ZlYjo6gc=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4/go.mod h1:qMKJr5fTnY0p7hqCQMNrAk62bCARWR5rAbTrGUFRuh4=
|
||||
github.com/uptrace/bun v1.2.5 h1:gSprL5xiBCp+tzcZHgENzJpXnmQwRM/A6s4HnBF85mc=
|
||||
github.com/uptrace/bun v1.2.5/go.mod h1:vkQMS4NNs4VNZv92y53uBSHXRqYyJp4bGhMHgaNCQpY=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.5 h1:dWLUxpjTdglzfBks2x+U2WIi+nRVjuh7Z3DLYVFswJk=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.5/go.mod h1:stwnlE8/6x8cuQ2aXcZqwDK/d+6jxgO3iQewflJT6C4=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.5 h1:liDvMaIWrN8DrHcxVbviOde/VDss9uhcqpcTSL3eJjc=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.5/go.mod h1:Mw6IDL/jNUL5ozcREAezOJSZ9Jm4LJlfoaXxBEfNBlM=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.5 h1:kkuuTbrG9d5leYZuSBKhq2gtq346lIrxf98Mig2y128=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.5/go.mod h1:rCHLszRZwppWE9cGDodO2FCI1qCrLwDjONp38KD3bA8=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ=
|
||||
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/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
|
||||
|
|
15
vendor/github.com/puzpuzpuz/xsync/v3/.gitignore
generated
vendored
Normal file
15
vendor/github.com/puzpuzpuz/xsync/v3/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
133
vendor/github.com/puzpuzpuz/xsync/v3/BENCHMARKS.md
generated
vendored
Normal file
133
vendor/github.com/puzpuzpuz/xsync/v3/BENCHMARKS.md
generated
vendored
Normal file
|
@ -0,0 +1,133 @@
|
|||
# xsync benchmarks
|
||||
|
||||
If you're interested in `MapOf` comparison with some of the popular concurrent hash maps written in Go, check [this](https://github.com/cornelk/hashmap/pull/70) and [this](https://github.com/alphadose/haxmap/pull/22) PRs.
|
||||
|
||||
The below results were obtained for xsync v2.3.1 on a c6g.metal EC2 instance (64 CPU, 128GB RAM) running Linux and Go 1.19.3. I'd like to thank [@felixge](https://github.com/felixge) who kindly ran the benchmarks.
|
||||
|
||||
The following commands were used to run the benchmarks:
|
||||
```bash
|
||||
$ go test -run='^$' -cpu=1,2,4,8,16,32,64 -bench . -count=30 -timeout=0 | tee bench.txt
|
||||
$ benchstat bench.txt | tee benchstat.txt
|
||||
```
|
||||
|
||||
The below sections contain some of the results. Refer to [this gist](https://gist.github.com/puzpuzpuz/e62e38e06feadecfdc823c0f941ece0b) for the complete output.
|
||||
|
||||
Please note that `MapOf` got a number of optimizations since v2.3.1, so the current result is likely to be different.
|
||||
|
||||
### Counter vs. atomic int64
|
||||
|
||||
```
|
||||
name time/op
|
||||
Counter 27.3ns ± 1%
|
||||
Counter-2 27.2ns ±11%
|
||||
Counter-4 15.3ns ± 8%
|
||||
Counter-8 7.43ns ± 7%
|
||||
Counter-16 3.70ns ±10%
|
||||
Counter-32 1.77ns ± 3%
|
||||
Counter-64 0.96ns ±10%
|
||||
AtomicInt64 7.60ns ± 0%
|
||||
AtomicInt64-2 12.6ns ±13%
|
||||
AtomicInt64-4 13.5ns ±14%
|
||||
AtomicInt64-8 12.7ns ± 9%
|
||||
AtomicInt64-16 12.8ns ± 8%
|
||||
AtomicInt64-32 13.0ns ± 6%
|
||||
AtomicInt64-64 12.9ns ± 7%
|
||||
```
|
||||
|
||||
Here `time/op` stands for average time spent on operation. If you divide `10^9` by the result in nanoseconds per operation, you'd get the throughput in operations per second. Thus, the ideal theoretical scalability of a concurrent data structure implies that the reported `time/op` decreases proportionally with the increased number of CPU cores. On the contrary, if the measured time per operation increases when run on more cores, it means performance degradation.
|
||||
|
||||
### MapOf vs. sync.Map
|
||||
|
||||
1,000 `[int, int]` entries with a warm-up, 100% Loads:
|
||||
```
|
||||
IntegerMapOf_WarmUp/reads=100% 24.0ns ± 0%
|
||||
IntegerMapOf_WarmUp/reads=100%-2 12.0ns ± 0%
|
||||
IntegerMapOf_WarmUp/reads=100%-4 6.02ns ± 0%
|
||||
IntegerMapOf_WarmUp/reads=100%-8 3.01ns ± 0%
|
||||
IntegerMapOf_WarmUp/reads=100%-16 1.50ns ± 0%
|
||||
IntegerMapOf_WarmUp/reads=100%-32 0.75ns ± 0%
|
||||
IntegerMapOf_WarmUp/reads=100%-64 0.38ns ± 0%
|
||||
IntegerMapStandard_WarmUp/reads=100% 55.3ns ± 0%
|
||||
IntegerMapStandard_WarmUp/reads=100%-2 27.6ns ± 0%
|
||||
IntegerMapStandard_WarmUp/reads=100%-4 16.1ns ± 3%
|
||||
IntegerMapStandard_WarmUp/reads=100%-8 8.35ns ± 7%
|
||||
IntegerMapStandard_WarmUp/reads=100%-16 4.24ns ± 7%
|
||||
IntegerMapStandard_WarmUp/reads=100%-32 2.18ns ± 6%
|
||||
IntegerMapStandard_WarmUp/reads=100%-64 1.11ns ± 3%
|
||||
```
|
||||
|
||||
1,000 `[int, int]` entries with a warm-up, 99% Loads, 0.5% Stores, 0.5% Deletes:
|
||||
```
|
||||
IntegerMapOf_WarmUp/reads=99% 31.0ns ± 0%
|
||||
IntegerMapOf_WarmUp/reads=99%-2 16.4ns ± 1%
|
||||
IntegerMapOf_WarmUp/reads=99%-4 8.42ns ± 0%
|
||||
IntegerMapOf_WarmUp/reads=99%-8 4.41ns ± 0%
|
||||
IntegerMapOf_WarmUp/reads=99%-16 2.38ns ± 2%
|
||||
IntegerMapOf_WarmUp/reads=99%-32 1.37ns ± 4%
|
||||
IntegerMapOf_WarmUp/reads=99%-64 0.85ns ± 2%
|
||||
IntegerMapStandard_WarmUp/reads=99% 121ns ± 1%
|
||||
IntegerMapStandard_WarmUp/reads=99%-2 109ns ± 3%
|
||||
IntegerMapStandard_WarmUp/reads=99%-4 115ns ± 4%
|
||||
IntegerMapStandard_WarmUp/reads=99%-8 114ns ± 2%
|
||||
IntegerMapStandard_WarmUp/reads=99%-16 105ns ± 2%
|
||||
IntegerMapStandard_WarmUp/reads=99%-32 97.0ns ± 3%
|
||||
IntegerMapStandard_WarmUp/reads=99%-64 98.0ns ± 2%
|
||||
```
|
||||
|
||||
1,000 `[int, int]` entries with a warm-up, 75% Loads, 12.5% Stores, 12.5% Deletes:
|
||||
```
|
||||
IntegerMapOf_WarmUp/reads=75%-reads 46.2ns ± 1%
|
||||
IntegerMapOf_WarmUp/reads=75%-reads-2 36.7ns ± 2%
|
||||
IntegerMapOf_WarmUp/reads=75%-reads-4 22.0ns ± 1%
|
||||
IntegerMapOf_WarmUp/reads=75%-reads-8 12.8ns ± 2%
|
||||
IntegerMapOf_WarmUp/reads=75%-reads-16 7.69ns ± 1%
|
||||
IntegerMapOf_WarmUp/reads=75%-reads-32 5.16ns ± 1%
|
||||
IntegerMapOf_WarmUp/reads=75%-reads-64 4.91ns ± 1%
|
||||
IntegerMapStandard_WarmUp/reads=75%-reads 156ns ± 0%
|
||||
IntegerMapStandard_WarmUp/reads=75%-reads-2 177ns ± 1%
|
||||
IntegerMapStandard_WarmUp/reads=75%-reads-4 197ns ± 1%
|
||||
IntegerMapStandard_WarmUp/reads=75%-reads-8 221ns ± 2%
|
||||
IntegerMapStandard_WarmUp/reads=75%-reads-16 242ns ± 1%
|
||||
IntegerMapStandard_WarmUp/reads=75%-reads-32 258ns ± 1%
|
||||
IntegerMapStandard_WarmUp/reads=75%-reads-64 264ns ± 1%
|
||||
```
|
||||
|
||||
### MPMCQueue vs. Go channels
|
||||
|
||||
Concurrent producers and consumers (1:1), queue/channel size 1,000, some work done by both producers and consumers:
|
||||
```
|
||||
QueueProdConsWork100 252ns ± 0%
|
||||
QueueProdConsWork100-2 206ns ± 5%
|
||||
QueueProdConsWork100-4 136ns ±12%
|
||||
QueueProdConsWork100-8 110ns ± 6%
|
||||
QueueProdConsWork100-16 108ns ± 2%
|
||||
QueueProdConsWork100-32 102ns ± 2%
|
||||
QueueProdConsWork100-64 101ns ± 0%
|
||||
ChanProdConsWork100 283ns ± 0%
|
||||
ChanProdConsWork100-2 406ns ±21%
|
||||
ChanProdConsWork100-4 549ns ± 7%
|
||||
ChanProdConsWork100-8 754ns ± 7%
|
||||
ChanProdConsWork100-16 828ns ± 7%
|
||||
ChanProdConsWork100-32 810ns ± 8%
|
||||
ChanProdConsWork100-64 832ns ± 4%
|
||||
```
|
||||
|
||||
### RBMutex vs. sync.RWMutex
|
||||
|
||||
The writer locks on each 100,000 iteration with some work in the critical section for both readers and the writer:
|
||||
```
|
||||
RBMutexWorkWrite100000 146ns ± 0%
|
||||
RBMutexWorkWrite100000-2 73.3ns ± 0%
|
||||
RBMutexWorkWrite100000-4 36.7ns ± 0%
|
||||
RBMutexWorkWrite100000-8 18.6ns ± 0%
|
||||
RBMutexWorkWrite100000-16 9.83ns ± 3%
|
||||
RBMutexWorkWrite100000-32 5.53ns ± 0%
|
||||
RBMutexWorkWrite100000-64 4.04ns ± 3%
|
||||
RWMutexWorkWrite100000 121ns ± 0%
|
||||
RWMutexWorkWrite100000-2 128ns ± 1%
|
||||
RWMutexWorkWrite100000-4 124ns ± 2%
|
||||
RWMutexWorkWrite100000-8 101ns ± 1%
|
||||
RWMutexWorkWrite100000-16 92.9ns ± 1%
|
||||
RWMutexWorkWrite100000-32 89.9ns ± 1%
|
||||
RWMutexWorkWrite100000-64 88.4ns ± 1%
|
||||
```
|
201
vendor/github.com/puzpuzpuz/xsync/v3/LICENSE
generated
vendored
Normal file
201
vendor/github.com/puzpuzpuz/xsync/v3/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
166
vendor/github.com/puzpuzpuz/xsync/v3/README.md
generated
vendored
Normal file
166
vendor/github.com/puzpuzpuz/xsync/v3/README.md
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
|||
[![GoDoc reference](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/puzpuzpuz/xsync/v3)
|
||||
[![GoReport](https://goreportcard.com/badge/github.com/puzpuzpuz/xsync/v3)](https://goreportcard.com/report/github.com/puzpuzpuz/xsync/v3)
|
||||
[![codecov](https://codecov.io/gh/puzpuzpuz/xsync/branch/main/graph/badge.svg)](https://codecov.io/gh/puzpuzpuz/xsync)
|
||||
|
||||
# xsync
|
||||
|
||||
Concurrent data structures for Go. Aims to provide more scalable alternatives for some of the data structures from the standard `sync` package, but not only.
|
||||
|
||||
Covered with tests following the approach described [here](https://puzpuzpuz.dev/testing-concurrent-code-for-fun-and-profit).
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Benchmark results may be found [here](BENCHMARKS.md). I'd like to thank [@felixge](https://github.com/felixge) who kindly ran the benchmarks on a beefy multicore machine.
|
||||
|
||||
Also, a non-scientific, unfair benchmark comparing Java's [j.u.c.ConcurrentHashMap](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html) and `xsync.MapOf` is available [here](https://puzpuzpuz.dev/concurrent-map-in-go-vs-java-yet-another-meaningless-benchmark).
|
||||
|
||||
## Usage
|
||||
|
||||
The latest xsync major version is v3, so `/v3` suffix should be used when importing the library:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
```
|
||||
|
||||
*Note for pre-v3 users*: v1 and v2 support is discontinued, so please upgrade to v3. While the API has some breaking changes, the migration should be trivial.
|
||||
|
||||
### Counter
|
||||
|
||||
A `Counter` is a striped `int64` counter inspired by the `j.u.c.a.LongAdder` class from the Java standard library.
|
||||
|
||||
```go
|
||||
c := xsync.NewCounter()
|
||||
// increment and decrement the counter
|
||||
c.Inc()
|
||||
c.Dec()
|
||||
// read the current value
|
||||
v := c.Value()
|
||||
```
|
||||
|
||||
Works better in comparison with a single atomically updated `int64` counter in high contention scenarios.
|
||||
|
||||
### Map
|
||||
|
||||
A `Map` is like a concurrent hash table-based map. It follows the interface of `sync.Map` with a number of valuable extensions like `Compute` or `Size`.
|
||||
|
||||
```go
|
||||
m := xsync.NewMap()
|
||||
m.Store("foo", "bar")
|
||||
v, ok := m.Load("foo")
|
||||
s := m.Size()
|
||||
```
|
||||
|
||||
`Map` uses a modified version of Cache-Line Hash Table (CLHT) data structure: https://github.com/LPD-EPFL/CLHT
|
||||
|
||||
CLHT is built around the idea of organizing the hash table in cache-line-sized buckets, so that on all modern CPUs update operations complete with minimal cache-line transfer. Also, `Get` operations are obstruction-free and involve no writes to shared memory, hence no mutexes or any other sort of locks. Due to this design, in all considered scenarios `Map` outperforms `sync.Map`.
|
||||
|
||||
One important difference with `sync.Map` is that only string keys are supported. That's because Golang standard library does not expose the built-in hash functions for `interface{}` values.
|
||||
|
||||
`MapOf[K, V]` is an implementation with parametrized key and value types. While it's still a CLHT-inspired hash map, `MapOf`'s design is quite different from `Map`. As a result, less GC pressure and fewer atomic operations on reads.
|
||||
|
||||
```go
|
||||
m := xsync.NewMapOf[string, string]()
|
||||
m.Store("foo", "bar")
|
||||
v, ok := m.Load("foo")
|
||||
```
|
||||
|
||||
Apart from CLHT, `MapOf` borrows ideas from Java's `j.u.c.ConcurrentHashMap` (immutable K/V pair structs instead of atomic snapshots) and C++'s `absl::flat_hash_map` (meta memory and SWAR-based lookups). It also has more dense memory layout when compared with `Map`. Long story short, `MapOf` should be preferred over `Map` when possible.
|
||||
|
||||
An important difference with `Map` is that `MapOf` supports arbitrary `comparable` key types:
|
||||
|
||||
```go
|
||||
type Point struct {
|
||||
x int32
|
||||
y int32
|
||||
}
|
||||
m := NewMapOf[Point, int]()
|
||||
m.Store(Point{42, 42}, 42)
|
||||
v, ok := m.Load(point{42, 42})
|
||||
```
|
||||
|
||||
Both maps use the built-in Golang's hash function which has DDOS protection. This means that each map instance gets its own seed number and the hash function uses that seed for hash code calculation. However, for smaller keys this hash function has some overhead. So, if you don't need DDOS protection, you may provide a custom hash function when creating a `MapOf`. For instance, Murmur3 finalizer does a decent job when it comes to integers:
|
||||
|
||||
```go
|
||||
m := NewMapOfWithHasher[int, int](func(i int, _ uint64) uint64 {
|
||||
h := uint64(i)
|
||||
h = (h ^ (h >> 33)) * 0xff51afd7ed558ccd
|
||||
h = (h ^ (h >> 33)) * 0xc4ceb9fe1a85ec53
|
||||
return h ^ (h >> 33)
|
||||
})
|
||||
```
|
||||
|
||||
When benchmarking concurrent maps, make sure to configure all of the competitors with the same hash function or, at least, take hash function performance into the consideration.
|
||||
|
||||
### MPMCQueue
|
||||
|
||||
A `MPMCQueue` is a bounded multi-producer multi-consumer concurrent queue.
|
||||
|
||||
```go
|
||||
q := xsync.NewMPMCQueue(1024)
|
||||
// producer inserts an item into the queue
|
||||
q.Enqueue("foo")
|
||||
// optimistic insertion attempt; doesn't block
|
||||
inserted := q.TryEnqueue("bar")
|
||||
// consumer obtains an item from the queue
|
||||
item := q.Dequeue() // interface{} pointing to a string
|
||||
// optimistic obtain attempt; doesn't block
|
||||
item, ok := q.TryDequeue()
|
||||
```
|
||||
|
||||
`MPMCQueueOf[I]` is an implementation with parametrized item type. It is available for Go 1.19 or later.
|
||||
|
||||
```go
|
||||
q := xsync.NewMPMCQueueOf[string](1024)
|
||||
q.Enqueue("foo")
|
||||
item := q.Dequeue() // string
|
||||
```
|
||||
|
||||
The queue is based on the algorithm from the [MPMCQueue](https://github.com/rigtorp/MPMCQueue) C++ library which in its turn references D.Vyukov's [MPMC queue](https://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue). According to the following [classification](https://www.1024cores.net/home/lock-free-algorithms/queues), the queue is array-based, fails on overflow, provides causal FIFO, has blocking producers and consumers.
|
||||
|
||||
The idea of the algorithm is to allow parallelism for concurrent producers and consumers by introducing the notion of tickets, i.e. values of two counters, one per producers/consumers. An atomic increment of one of those counters is the only noticeable contention point in queue operations. The rest of the operation avoids contention on writes thanks to the turn-based read/write access for each of the queue items.
|
||||
|
||||
In essence, `MPMCQueue` is a specialized queue for scenarios where there are multiple concurrent producers and consumers of a single queue running on a large multicore machine.
|
||||
|
||||
To get the optimal performance, you may want to set the queue size to be large enough, say, an order of magnitude greater than the number of producers/consumers, to allow producers and consumers to progress with their queue operations in parallel most of the time.
|
||||
|
||||
### RBMutex
|
||||
|
||||
A `RBMutex` is a reader-biased reader/writer mutual exclusion lock. The lock can be held by many readers or a single writer.
|
||||
|
||||
```go
|
||||
mu := xsync.NewRBMutex()
|
||||
// reader lock calls return a token
|
||||
t := mu.RLock()
|
||||
// the token must be later used to unlock the mutex
|
||||
mu.RUnlock(t)
|
||||
// writer locks are the same as in sync.RWMutex
|
||||
mu.Lock()
|
||||
mu.Unlock()
|
||||
```
|
||||
|
||||
`RBMutex` is based on a modified version of BRAVO (Biased Locking for Reader-Writer Locks) algorithm: https://arxiv.org/pdf/1810.01553.pdf
|
||||
|
||||
The idea of the algorithm is to build on top of an existing reader-writer mutex and introduce a fast path for readers. On the fast path, reader lock attempts are sharded over an internal array based on the reader identity (a token in the case of Golang). This means that readers do not contend over a single atomic counter like it's done in, say, `sync.RWMutex` allowing for better scalability in terms of cores.
|
||||
|
||||
Hence, by the design `RBMutex` is a specialized mutex for scenarios, such as caches, where the vast majority of locks are acquired by readers and write lock acquire attempts are infrequent. In such scenarios, `RBMutex` should perform better than the `sync.RWMutex` on large multicore machines.
|
||||
|
||||
`RBMutex` extends `sync.RWMutex` internally and uses it as the "reader bias disabled" fallback, so the same semantics apply. The only noticeable difference is in the reader tokens returned from the `RLock`/`RUnlock` methods.
|
||||
|
||||
Apart from blocking methods, `RBMutex` also has methods for optimistic locking:
|
||||
```go
|
||||
mu := xsync.NewRBMutex()
|
||||
if locked, t := mu.TryRLock(); locked {
|
||||
// critical reader section...
|
||||
mu.RUnlock(t)
|
||||
}
|
||||
if mu.TryLock() {
|
||||
// critical writer section...
|
||||
mu.Unlock()
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Licensed under MIT.
|
99
vendor/github.com/puzpuzpuz/xsync/v3/counter.go
generated
vendored
Normal file
99
vendor/github.com/puzpuzpuz/xsync/v3/counter.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
package xsync
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// pool for P tokens
|
||||
var ptokenPool sync.Pool
|
||||
|
||||
// a P token is used to point at the current OS thread (P)
|
||||
// on which the goroutine is run; exact identity of the thread,
|
||||
// as well as P migration tolerance, is not important since
|
||||
// it's used to as a best effort mechanism for assigning
|
||||
// concurrent operations (goroutines) to different stripes of
|
||||
// the counter
|
||||
type ptoken struct {
|
||||
idx uint32
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad [cacheLineSize - 4]byte
|
||||
}
|
||||
|
||||
// A Counter is a striped int64 counter.
|
||||
//
|
||||
// Should be preferred over a single atomically updated int64
|
||||
// counter in high contention scenarios.
|
||||
//
|
||||
// A Counter must not be copied after first use.
|
||||
type Counter struct {
|
||||
stripes []cstripe
|
||||
mask uint32
|
||||
}
|
||||
|
||||
type cstripe struct {
|
||||
c int64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad [cacheLineSize - 8]byte
|
||||
}
|
||||
|
||||
// NewCounter creates a new Counter instance.
|
||||
func NewCounter() *Counter {
|
||||
nstripes := nextPowOf2(parallelism())
|
||||
c := Counter{
|
||||
stripes: make([]cstripe, nstripes),
|
||||
mask: nstripes - 1,
|
||||
}
|
||||
return &c
|
||||
}
|
||||
|
||||
// Inc increments the counter by 1.
|
||||
func (c *Counter) Inc() {
|
||||
c.Add(1)
|
||||
}
|
||||
|
||||
// Dec decrements the counter by 1.
|
||||
func (c *Counter) Dec() {
|
||||
c.Add(-1)
|
||||
}
|
||||
|
||||
// Add adds the delta to the counter.
|
||||
func (c *Counter) Add(delta int64) {
|
||||
t, ok := ptokenPool.Get().(*ptoken)
|
||||
if !ok {
|
||||
t = new(ptoken)
|
||||
t.idx = runtime_fastrand()
|
||||
}
|
||||
for {
|
||||
stripe := &c.stripes[t.idx&c.mask]
|
||||
cnt := atomic.LoadInt64(&stripe.c)
|
||||
if atomic.CompareAndSwapInt64(&stripe.c, cnt, cnt+delta) {
|
||||
break
|
||||
}
|
||||
// Give a try with another randomly selected stripe.
|
||||
t.idx = runtime_fastrand()
|
||||
}
|
||||
ptokenPool.Put(t)
|
||||
}
|
||||
|
||||
// Value returns the current counter value.
|
||||
// The returned value may not include all of the latest operations in
|
||||
// presence of concurrent modifications of the counter.
|
||||
func (c *Counter) Value() int64 {
|
||||
v := int64(0)
|
||||
for i := 0; i < len(c.stripes); i++ {
|
||||
stripe := &c.stripes[i]
|
||||
v += atomic.LoadInt64(&stripe.c)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Reset resets the counter to zero.
|
||||
// This method should only be used when it is known that there are
|
||||
// no concurrent modifications of the counter.
|
||||
func (c *Counter) Reset() {
|
||||
for i := 0; i < len(c.stripes); i++ {
|
||||
stripe := &c.stripes[i]
|
||||
atomic.StoreInt64(&stripe.c, 0)
|
||||
}
|
||||
}
|
873
vendor/github.com/puzpuzpuz/xsync/v3/map.go
generated
vendored
Normal file
873
vendor/github.com/puzpuzpuz/xsync/v3/map.go
generated
vendored
Normal file
|
@ -0,0 +1,873 @@
|
|||
package xsync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type mapResizeHint int
|
||||
|
||||
const (
|
||||
mapGrowHint mapResizeHint = 0
|
||||
mapShrinkHint mapResizeHint = 1
|
||||
mapClearHint mapResizeHint = 2
|
||||
)
|
||||
|
||||
const (
|
||||
// number of Map entries per bucket; 3 entries lead to size of 64B
|
||||
// (one cache line) on 64-bit machines
|
||||
entriesPerMapBucket = 3
|
||||
// threshold fraction of table occupation to start a table shrinking
|
||||
// when deleting the last entry in a bucket chain
|
||||
mapShrinkFraction = 128
|
||||
// map load factor to trigger a table resize during insertion;
|
||||
// a map holds up to mapLoadFactor*entriesPerMapBucket*mapTableLen
|
||||
// key-value pairs (this is a soft limit)
|
||||
mapLoadFactor = 0.75
|
||||
// minimal table size, i.e. number of buckets; thus, minimal map
|
||||
// capacity can be calculated as entriesPerMapBucket*defaultMinMapTableLen
|
||||
defaultMinMapTableLen = 32
|
||||
// minimum counter stripes to use
|
||||
minMapCounterLen = 8
|
||||
// maximum counter stripes to use; stands for around 4KB of memory
|
||||
maxMapCounterLen = 32
|
||||
)
|
||||
|
||||
var (
|
||||
topHashMask = uint64((1<<20)-1) << 44
|
||||
topHashEntryMasks = [3]uint64{
|
||||
topHashMask,
|
||||
topHashMask >> 20,
|
||||
topHashMask >> 40,
|
||||
}
|
||||
)
|
||||
|
||||
// Map is like a Go map[string]interface{} but is safe for concurrent
|
||||
// use by multiple goroutines without additional locking or
|
||||
// coordination. It follows the interface of sync.Map with
|
||||
// a number of valuable extensions like Compute or Size.
|
||||
//
|
||||
// A Map must not be copied after first use.
|
||||
//
|
||||
// Map uses a modified version of Cache-Line Hash Table (CLHT)
|
||||
// data structure: https://github.com/LPD-EPFL/CLHT
|
||||
//
|
||||
// CLHT is built around idea to organize the hash table in
|
||||
// cache-line-sized buckets, so that on all modern CPUs update
|
||||
// operations complete with at most one cache-line transfer.
|
||||
// Also, Get operations involve no write to memory, as well as no
|
||||
// mutexes or any other sort of locks. Due to this design, in all
|
||||
// considered scenarios Map outperforms sync.Map.
|
||||
//
|
||||
// One important difference with sync.Map is that only string keys
|
||||
// are supported. That's because Golang standard library does not
|
||||
// expose the built-in hash functions for interface{} values.
|
||||
type Map struct {
|
||||
totalGrowths int64
|
||||
totalShrinks int64
|
||||
resizing int64 // resize in progress flag; updated atomically
|
||||
resizeMu sync.Mutex // only used along with resizeCond
|
||||
resizeCond sync.Cond // used to wake up resize waiters (concurrent modifications)
|
||||
table unsafe.Pointer // *mapTable
|
||||
minTableLen int
|
||||
growOnly bool
|
||||
}
|
||||
|
||||
type mapTable struct {
|
||||
buckets []bucketPadded
|
||||
// striped counter for number of table entries;
|
||||
// used to determine if a table shrinking is needed
|
||||
// occupies min(buckets_memory/1024, 64KB) of memory
|
||||
size []counterStripe
|
||||
seed uint64
|
||||
}
|
||||
|
||||
type counterStripe struct {
|
||||
c int64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad [cacheLineSize - 8]byte
|
||||
}
|
||||
|
||||
type bucketPadded struct {
|
||||
//lint:ignore U1000 ensure each bucket takes two cache lines on both 32 and 64-bit archs
|
||||
pad [cacheLineSize - unsafe.Sizeof(bucket{})]byte
|
||||
bucket
|
||||
}
|
||||
|
||||
type bucket struct {
|
||||
next unsafe.Pointer // *bucketPadded
|
||||
keys [entriesPerMapBucket]unsafe.Pointer
|
||||
values [entriesPerMapBucket]unsafe.Pointer
|
||||
// topHashMutex is a 2-in-1 value.
|
||||
//
|
||||
// It contains packed top 20 bits (20 MSBs) of hash codes for keys
|
||||
// stored in the bucket:
|
||||
// | key 0's top hash | key 1's top hash | key 2's top hash | bitmap for keys | mutex |
|
||||
// | 20 bits | 20 bits | 20 bits | 3 bits | 1 bit |
|
||||
//
|
||||
// The least significant bit is used for the mutex (TTAS spinlock).
|
||||
topHashMutex uint64
|
||||
}
|
||||
|
||||
type rangeEntry struct {
|
||||
key unsafe.Pointer
|
||||
value unsafe.Pointer
|
||||
}
|
||||
|
||||
// MapConfig defines configurable Map/MapOf options.
|
||||
type MapConfig struct {
|
||||
sizeHint int
|
||||
growOnly bool
|
||||
}
|
||||
|
||||
// WithPresize configures new Map/MapOf instance with capacity enough
|
||||
// to hold sizeHint entries. The capacity is treated as the minimal
|
||||
// capacity meaning that the underlying hash table will never shrink
|
||||
// to a smaller capacity. If sizeHint is zero or negative, the value
|
||||
// is ignored.
|
||||
func WithPresize(sizeHint int) func(*MapConfig) {
|
||||
return func(c *MapConfig) {
|
||||
c.sizeHint = sizeHint
|
||||
}
|
||||
}
|
||||
|
||||
// WithGrowOnly configures new Map/MapOf instance to be grow-only.
|
||||
// This means that the underlying hash table grows in capacity when
|
||||
// new keys are added, but does not shrink when keys are deleted.
|
||||
// The only exception to this rule is the Clear method which
|
||||
// shrinks the hash table back to the initial capacity.
|
||||
func WithGrowOnly() func(*MapConfig) {
|
||||
return func(c *MapConfig) {
|
||||
c.growOnly = true
|
||||
}
|
||||
}
|
||||
|
||||
// NewMap creates a new Map instance configured with the given
|
||||
// options.
|
||||
func NewMap(options ...func(*MapConfig)) *Map {
|
||||
c := &MapConfig{
|
||||
sizeHint: defaultMinMapTableLen * entriesPerMapBucket,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(c)
|
||||
}
|
||||
|
||||
m := &Map{}
|
||||
m.resizeCond = *sync.NewCond(&m.resizeMu)
|
||||
var table *mapTable
|
||||
if c.sizeHint <= defaultMinMapTableLen*entriesPerMapBucket {
|
||||
table = newMapTable(defaultMinMapTableLen)
|
||||
} else {
|
||||
tableLen := nextPowOf2(uint32((float64(c.sizeHint) / entriesPerMapBucket) / mapLoadFactor))
|
||||
table = newMapTable(int(tableLen))
|
||||
}
|
||||
m.minTableLen = len(table.buckets)
|
||||
m.growOnly = c.growOnly
|
||||
atomic.StorePointer(&m.table, unsafe.Pointer(table))
|
||||
return m
|
||||
}
|
||||
|
||||
// NewMapPresized creates a new Map instance with capacity enough to hold
|
||||
// sizeHint entries. The capacity is treated as the minimal capacity
|
||||
// meaning that the underlying hash table will never shrink to
|
||||
// a smaller capacity. If sizeHint is zero or negative, the value
|
||||
// is ignored.
|
||||
//
|
||||
// Deprecated: use NewMap in combination with WithPresize.
|
||||
func NewMapPresized(sizeHint int) *Map {
|
||||
return NewMap(WithPresize(sizeHint))
|
||||
}
|
||||
|
||||
func newMapTable(minTableLen int) *mapTable {
|
||||
buckets := make([]bucketPadded, minTableLen)
|
||||
counterLen := minTableLen >> 10
|
||||
if counterLen < minMapCounterLen {
|
||||
counterLen = minMapCounterLen
|
||||
} else if counterLen > maxMapCounterLen {
|
||||
counterLen = maxMapCounterLen
|
||||
}
|
||||
counter := make([]counterStripe, counterLen)
|
||||
t := &mapTable{
|
||||
buckets: buckets,
|
||||
size: counter,
|
||||
seed: makeSeed(),
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Load returns the value stored in the map for a key, or nil if no
|
||||
// value is present.
|
||||
// The ok result indicates whether value was found in the map.
|
||||
func (m *Map) Load(key string) (value interface{}, ok bool) {
|
||||
table := (*mapTable)(atomic.LoadPointer(&m.table))
|
||||
hash := hashString(key, table.seed)
|
||||
bidx := uint64(len(table.buckets)-1) & hash
|
||||
b := &table.buckets[bidx]
|
||||
for {
|
||||
topHashes := atomic.LoadUint64(&b.topHashMutex)
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if !topHashMatch(hash, topHashes, i) {
|
||||
continue
|
||||
}
|
||||
atomic_snapshot:
|
||||
// Start atomic snapshot.
|
||||
vp := atomic.LoadPointer(&b.values[i])
|
||||
kp := atomic.LoadPointer(&b.keys[i])
|
||||
if kp != nil && vp != nil {
|
||||
if key == derefKey(kp) {
|
||||
if uintptr(vp) == uintptr(atomic.LoadPointer(&b.values[i])) {
|
||||
// Atomic snapshot succeeded.
|
||||
return derefValue(vp), true
|
||||
}
|
||||
// Concurrent update/remove. Go for another spin.
|
||||
goto atomic_snapshot
|
||||
}
|
||||
}
|
||||
}
|
||||
bptr := atomic.LoadPointer(&b.next)
|
||||
if bptr == nil {
|
||||
return
|
||||
}
|
||||
b = (*bucketPadded)(bptr)
|
||||
}
|
||||
}
|
||||
|
||||
// Store sets the value for a key.
|
||||
func (m *Map) Store(key string, value interface{}) {
|
||||
m.doCompute(
|
||||
key,
|
||||
func(interface{}, bool) (interface{}, bool) {
|
||||
return value, false
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadOrStore returns the existing value for the key if present.
|
||||
// Otherwise, it stores and returns the given value.
|
||||
// The loaded result is true if the value was loaded, false if stored.
|
||||
func (m *Map) LoadOrStore(key string, value interface{}) (actual interface{}, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(interface{}, bool) (interface{}, bool) {
|
||||
return value, false
|
||||
},
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadAndStore returns the existing value for the key if present,
|
||||
// while setting the new value for the key.
|
||||
// It stores the new value and returns the existing one, if present.
|
||||
// The loaded result is true if the existing value was loaded,
|
||||
// false otherwise.
|
||||
func (m *Map) LoadAndStore(key string, value interface{}) (actual interface{}, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(interface{}, bool) (interface{}, bool) {
|
||||
return value, false
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadOrCompute returns the existing value for the key if present.
|
||||
// Otherwise, it computes the value using the provided function and
|
||||
// returns the computed value. The loaded result is true if the value
|
||||
// was loaded, false if stored.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *Map) LoadOrCompute(key string, valueFn func() interface{}) (actual interface{}, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(interface{}, bool) (interface{}, bool) {
|
||||
return valueFn(), false
|
||||
},
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// Compute either sets the computed new value for the key or deletes
|
||||
// the value for the key. When the delete result of the valueFn function
|
||||
// is set to true, the value will be deleted, if it exists. When delete
|
||||
// is set to false, the value is updated to the newValue.
|
||||
// The ok result indicates whether value was computed and stored, thus, is
|
||||
// present in the map. The actual result contains the new value in cases where
|
||||
// the value was computed and stored. See the example for a few use cases.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *Map) Compute(
|
||||
key string,
|
||||
valueFn func(oldValue interface{}, loaded bool) (newValue interface{}, delete bool),
|
||||
) (actual interface{}, ok bool) {
|
||||
return m.doCompute(key, valueFn, false, true)
|
||||
}
|
||||
|
||||
// LoadAndDelete deletes the value for a key, returning the previous
|
||||
// value if any. The loaded result reports whether the key was
|
||||
// present.
|
||||
func (m *Map) LoadAndDelete(key string) (value interface{}, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(value interface{}, loaded bool) (interface{}, bool) {
|
||||
return value, true
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// Delete deletes the value for a key.
|
||||
func (m *Map) Delete(key string) {
|
||||
m.doCompute(
|
||||
key,
|
||||
func(value interface{}, loaded bool) (interface{}, bool) {
|
||||
return value, true
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *Map) doCompute(
|
||||
key string,
|
||||
valueFn func(oldValue interface{}, loaded bool) (interface{}, bool),
|
||||
loadIfExists, computeOnly bool,
|
||||
) (interface{}, bool) {
|
||||
// Read-only path.
|
||||
if loadIfExists {
|
||||
if v, ok := m.Load(key); ok {
|
||||
return v, !computeOnly
|
||||
}
|
||||
}
|
||||
// Write path.
|
||||
for {
|
||||
compute_attempt:
|
||||
var (
|
||||
emptyb *bucketPadded
|
||||
emptyidx int
|
||||
hintNonEmpty int
|
||||
)
|
||||
table := (*mapTable)(atomic.LoadPointer(&m.table))
|
||||
tableLen := len(table.buckets)
|
||||
hash := hashString(key, table.seed)
|
||||
bidx := uint64(len(table.buckets)-1) & hash
|
||||
rootb := &table.buckets[bidx]
|
||||
lockBucket(&rootb.topHashMutex)
|
||||
// The following two checks must go in reverse to what's
|
||||
// in the resize method.
|
||||
if m.resizeInProgress() {
|
||||
// Resize is in progress. Wait, then go for another attempt.
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
m.waitForResize()
|
||||
goto compute_attempt
|
||||
}
|
||||
if m.newerTableExists(table) {
|
||||
// Someone resized the table. Go for another attempt.
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
goto compute_attempt
|
||||
}
|
||||
b := rootb
|
||||
for {
|
||||
topHashes := atomic.LoadUint64(&b.topHashMutex)
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if b.keys[i] == nil {
|
||||
if emptyb == nil {
|
||||
emptyb = b
|
||||
emptyidx = i
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !topHashMatch(hash, topHashes, i) {
|
||||
hintNonEmpty++
|
||||
continue
|
||||
}
|
||||
if key == derefKey(b.keys[i]) {
|
||||
vp := b.values[i]
|
||||
if loadIfExists {
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
return derefValue(vp), !computeOnly
|
||||
}
|
||||
// In-place update/delete.
|
||||
// We get a copy of the value via an interface{} on each call,
|
||||
// thus the live value pointers are unique. Otherwise atomic
|
||||
// snapshot won't be correct in case of multiple Store calls
|
||||
// using the same value.
|
||||
oldValue := derefValue(vp)
|
||||
newValue, del := valueFn(oldValue, true)
|
||||
if del {
|
||||
// Deletion.
|
||||
// First we update the value, then the key.
|
||||
// This is important for atomic snapshot states.
|
||||
atomic.StoreUint64(&b.topHashMutex, eraseTopHash(topHashes, i))
|
||||
atomic.StorePointer(&b.values[i], nil)
|
||||
atomic.StorePointer(&b.keys[i], nil)
|
||||
leftEmpty := false
|
||||
if hintNonEmpty == 0 {
|
||||
leftEmpty = isEmptyBucket(b)
|
||||
}
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
table.addSize(bidx, -1)
|
||||
// Might need to shrink the table.
|
||||
if leftEmpty {
|
||||
m.resize(table, mapShrinkHint)
|
||||
}
|
||||
return oldValue, !computeOnly
|
||||
}
|
||||
nvp := unsafe.Pointer(&newValue)
|
||||
if assertionsEnabled && vp == nvp {
|
||||
panic("non-unique value pointer")
|
||||
}
|
||||
atomic.StorePointer(&b.values[i], nvp)
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
if computeOnly {
|
||||
// Compute expects the new value to be returned.
|
||||
return newValue, true
|
||||
}
|
||||
// LoadAndStore expects the old value to be returned.
|
||||
return oldValue, true
|
||||
}
|
||||
hintNonEmpty++
|
||||
}
|
||||
if b.next == nil {
|
||||
if emptyb != nil {
|
||||
// Insertion into an existing bucket.
|
||||
var zeroedV interface{}
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
if del {
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
return zeroedV, false
|
||||
}
|
||||
// First we update the value, then the key.
|
||||
// This is important for atomic snapshot states.
|
||||
topHashes = atomic.LoadUint64(&emptyb.topHashMutex)
|
||||
atomic.StoreUint64(&emptyb.topHashMutex, storeTopHash(hash, topHashes, emptyidx))
|
||||
atomic.StorePointer(&emptyb.values[emptyidx], unsafe.Pointer(&newValue))
|
||||
atomic.StorePointer(&emptyb.keys[emptyidx], unsafe.Pointer(&key))
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
table.addSize(bidx, 1)
|
||||
return newValue, computeOnly
|
||||
}
|
||||
growThreshold := float64(tableLen) * entriesPerMapBucket * mapLoadFactor
|
||||
if table.sumSize() > int64(growThreshold) {
|
||||
// Need to grow the table. Then go for another attempt.
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
m.resize(table, mapGrowHint)
|
||||
goto compute_attempt
|
||||
}
|
||||
// Insertion into a new bucket.
|
||||
var zeroedV interface{}
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
if del {
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
return newValue, false
|
||||
}
|
||||
// Create and append a bucket.
|
||||
newb := new(bucketPadded)
|
||||
newb.keys[0] = unsafe.Pointer(&key)
|
||||
newb.values[0] = unsafe.Pointer(&newValue)
|
||||
newb.topHashMutex = storeTopHash(hash, newb.topHashMutex, 0)
|
||||
atomic.StorePointer(&b.next, unsafe.Pointer(newb))
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
table.addSize(bidx, 1)
|
||||
return newValue, computeOnly
|
||||
}
|
||||
b = (*bucketPadded)(b.next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Map) newerTableExists(table *mapTable) bool {
|
||||
curTablePtr := atomic.LoadPointer(&m.table)
|
||||
return uintptr(curTablePtr) != uintptr(unsafe.Pointer(table))
|
||||
}
|
||||
|
||||
func (m *Map) resizeInProgress() bool {
|
||||
return atomic.LoadInt64(&m.resizing) == 1
|
||||
}
|
||||
|
||||
func (m *Map) waitForResize() {
|
||||
m.resizeMu.Lock()
|
||||
for m.resizeInProgress() {
|
||||
m.resizeCond.Wait()
|
||||
}
|
||||
m.resizeMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *Map) resize(knownTable *mapTable, hint mapResizeHint) {
|
||||
knownTableLen := len(knownTable.buckets)
|
||||
// Fast path for shrink attempts.
|
||||
if hint == mapShrinkHint {
|
||||
if m.growOnly ||
|
||||
m.minTableLen == knownTableLen ||
|
||||
knownTable.sumSize() > int64((knownTableLen*entriesPerMapBucket)/mapShrinkFraction) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Slow path.
|
||||
if !atomic.CompareAndSwapInt64(&m.resizing, 0, 1) {
|
||||
// Someone else started resize. Wait for it to finish.
|
||||
m.waitForResize()
|
||||
return
|
||||
}
|
||||
var newTable *mapTable
|
||||
table := (*mapTable)(atomic.LoadPointer(&m.table))
|
||||
tableLen := len(table.buckets)
|
||||
switch hint {
|
||||
case mapGrowHint:
|
||||
// Grow the table with factor of 2.
|
||||
atomic.AddInt64(&m.totalGrowths, 1)
|
||||
newTable = newMapTable(tableLen << 1)
|
||||
case mapShrinkHint:
|
||||
shrinkThreshold := int64((tableLen * entriesPerMapBucket) / mapShrinkFraction)
|
||||
if tableLen > m.minTableLen && table.sumSize() <= shrinkThreshold {
|
||||
// Shrink the table with factor of 2.
|
||||
atomic.AddInt64(&m.totalShrinks, 1)
|
||||
newTable = newMapTable(tableLen >> 1)
|
||||
} else {
|
||||
// No need to shrink. Wake up all waiters and give up.
|
||||
m.resizeMu.Lock()
|
||||
atomic.StoreInt64(&m.resizing, 0)
|
||||
m.resizeCond.Broadcast()
|
||||
m.resizeMu.Unlock()
|
||||
return
|
||||
}
|
||||
case mapClearHint:
|
||||
newTable = newMapTable(m.minTableLen)
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected resize hint: %d", hint))
|
||||
}
|
||||
// Copy the data only if we're not clearing the map.
|
||||
if hint != mapClearHint {
|
||||
for i := 0; i < tableLen; i++ {
|
||||
copied := copyBucket(&table.buckets[i], newTable)
|
||||
newTable.addSizePlain(uint64(i), copied)
|
||||
}
|
||||
}
|
||||
// Publish the new table and wake up all waiters.
|
||||
atomic.StorePointer(&m.table, unsafe.Pointer(newTable))
|
||||
m.resizeMu.Lock()
|
||||
atomic.StoreInt64(&m.resizing, 0)
|
||||
m.resizeCond.Broadcast()
|
||||
m.resizeMu.Unlock()
|
||||
}
|
||||
|
||||
func copyBucket(b *bucketPadded, destTable *mapTable) (copied int) {
|
||||
rootb := b
|
||||
lockBucket(&rootb.topHashMutex)
|
||||
for {
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if b.keys[i] != nil {
|
||||
k := derefKey(b.keys[i])
|
||||
hash := hashString(k, destTable.seed)
|
||||
bidx := uint64(len(destTable.buckets)-1) & hash
|
||||
destb := &destTable.buckets[bidx]
|
||||
appendToBucket(hash, b.keys[i], b.values[i], destb)
|
||||
copied++
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
return
|
||||
}
|
||||
b = (*bucketPadded)(b.next)
|
||||
}
|
||||
}
|
||||
|
||||
func appendToBucket(hash uint64, keyPtr, valPtr unsafe.Pointer, b *bucketPadded) {
|
||||
for {
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if b.keys[i] == nil {
|
||||
b.keys[i] = keyPtr
|
||||
b.values[i] = valPtr
|
||||
b.topHashMutex = storeTopHash(hash, b.topHashMutex, i)
|
||||
return
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
newb := new(bucketPadded)
|
||||
newb.keys[0] = keyPtr
|
||||
newb.values[0] = valPtr
|
||||
newb.topHashMutex = storeTopHash(hash, newb.topHashMutex, 0)
|
||||
b.next = unsafe.Pointer(newb)
|
||||
return
|
||||
}
|
||||
b = (*bucketPadded)(b.next)
|
||||
}
|
||||
}
|
||||
|
||||
func isEmptyBucket(rootb *bucketPadded) bool {
|
||||
b := rootb
|
||||
for {
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if b.keys[i] != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
return true
|
||||
}
|
||||
b = (*bucketPadded)(b.next)
|
||||
}
|
||||
}
|
||||
|
||||
// Range calls f sequentially for each key and value present in the
|
||||
// map. If f returns false, range stops the iteration.
|
||||
//
|
||||
// Range does not necessarily correspond to any consistent snapshot
|
||||
// of the Map's contents: no key will be visited more than once, but
|
||||
// if the value for any key is stored or deleted concurrently, Range
|
||||
// may reflect any mapping for that key from any point during the
|
||||
// Range call.
|
||||
//
|
||||
// It is safe to modify the map while iterating it, including entry
|
||||
// creation, modification and deletion. However, the concurrent
|
||||
// modification rule apply, i.e. the changes may be not reflected
|
||||
// in the subsequently iterated entries.
|
||||
func (m *Map) Range(f func(key string, value interface{}) bool) {
|
||||
var zeroEntry rangeEntry
|
||||
// Pre-allocate array big enough to fit entries for most hash tables.
|
||||
bentries := make([]rangeEntry, 0, 16*entriesPerMapBucket)
|
||||
tablep := atomic.LoadPointer(&m.table)
|
||||
table := *(*mapTable)(tablep)
|
||||
for i := range table.buckets {
|
||||
rootb := &table.buckets[i]
|
||||
b := rootb
|
||||
// Prevent concurrent modifications and copy all entries into
|
||||
// the intermediate slice.
|
||||
lockBucket(&rootb.topHashMutex)
|
||||
for {
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if b.keys[i] != nil {
|
||||
bentries = append(bentries, rangeEntry{
|
||||
key: b.keys[i],
|
||||
value: b.values[i],
|
||||
})
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
break
|
||||
}
|
||||
b = (*bucketPadded)(b.next)
|
||||
}
|
||||
// Call the function for all copied entries.
|
||||
for j := range bentries {
|
||||
k := derefKey(bentries[j].key)
|
||||
v := derefValue(bentries[j].value)
|
||||
if !f(k, v) {
|
||||
return
|
||||
}
|
||||
// Remove the reference to avoid preventing the copied
|
||||
// entries from being GCed until this method finishes.
|
||||
bentries[j] = zeroEntry
|
||||
}
|
||||
bentries = bentries[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// Clear deletes all keys and values currently stored in the map.
|
||||
func (m *Map) Clear() {
|
||||
table := (*mapTable)(atomic.LoadPointer(&m.table))
|
||||
m.resize(table, mapClearHint)
|
||||
}
|
||||
|
||||
// Size returns current size of the map.
|
||||
func (m *Map) Size() int {
|
||||
table := (*mapTable)(atomic.LoadPointer(&m.table))
|
||||
return int(table.sumSize())
|
||||
}
|
||||
|
||||
func derefKey(keyPtr unsafe.Pointer) string {
|
||||
return *(*string)(keyPtr)
|
||||
}
|
||||
|
||||
func derefValue(valuePtr unsafe.Pointer) interface{} {
|
||||
return *(*interface{})(valuePtr)
|
||||
}
|
||||
|
||||
func lockBucket(mu *uint64) {
|
||||
for {
|
||||
var v uint64
|
||||
for {
|
||||
v = atomic.LoadUint64(mu)
|
||||
if v&1 != 1 {
|
||||
break
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
if atomic.CompareAndSwapUint64(mu, v, v|1) {
|
||||
return
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func unlockBucket(mu *uint64) {
|
||||
v := atomic.LoadUint64(mu)
|
||||
atomic.StoreUint64(mu, v&^1)
|
||||
}
|
||||
|
||||
func topHashMatch(hash, topHashes uint64, idx int) bool {
|
||||
if topHashes&(1<<(idx+1)) == 0 {
|
||||
// Entry is not present.
|
||||
return false
|
||||
}
|
||||
hash = hash & topHashMask
|
||||
topHashes = (topHashes & topHashEntryMasks[idx]) << (20 * idx)
|
||||
return hash == topHashes
|
||||
}
|
||||
|
||||
func storeTopHash(hash, topHashes uint64, idx int) uint64 {
|
||||
// Zero out top hash at idx.
|
||||
topHashes = topHashes &^ topHashEntryMasks[idx]
|
||||
// Chop top 20 MSBs of the given hash and position them at idx.
|
||||
hash = (hash & topHashMask) >> (20 * idx)
|
||||
// Store the MSBs.
|
||||
topHashes = topHashes | hash
|
||||
// Mark the entry as present.
|
||||
return topHashes | (1 << (idx + 1))
|
||||
}
|
||||
|
||||
func eraseTopHash(topHashes uint64, idx int) uint64 {
|
||||
return topHashes &^ (1 << (idx + 1))
|
||||
}
|
||||
|
||||
func (table *mapTable) addSize(bucketIdx uint64, delta int) {
|
||||
cidx := uint64(len(table.size)-1) & bucketIdx
|
||||
atomic.AddInt64(&table.size[cidx].c, int64(delta))
|
||||
}
|
||||
|
||||
func (table *mapTable) addSizePlain(bucketIdx uint64, delta int) {
|
||||
cidx := uint64(len(table.size)-1) & bucketIdx
|
||||
table.size[cidx].c += int64(delta)
|
||||
}
|
||||
|
||||
func (table *mapTable) sumSize() int64 {
|
||||
sum := int64(0)
|
||||
for i := range table.size {
|
||||
sum += atomic.LoadInt64(&table.size[i].c)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// MapStats is Map/MapOf statistics.
|
||||
//
|
||||
// Warning: map statistics are intented to be used for diagnostic
|
||||
// purposes, not for production code. This means that breaking changes
|
||||
// may be introduced into this struct even between minor releases.
|
||||
type MapStats struct {
|
||||
// RootBuckets is the number of root buckets in the hash table.
|
||||
// Each bucket holds a few entries.
|
||||
RootBuckets int
|
||||
// TotalBuckets is the total number of buckets in the hash table,
|
||||
// including root and their chained buckets. Each bucket holds
|
||||
// a few entries.
|
||||
TotalBuckets int
|
||||
// EmptyBuckets is the number of buckets that hold no entries.
|
||||
EmptyBuckets int
|
||||
// Capacity is the Map/MapOf capacity, i.e. the total number of
|
||||
// entries that all buckets can physically hold. This number
|
||||
// does not consider the load factor.
|
||||
Capacity int
|
||||
// Size is the exact number of entries stored in the map.
|
||||
Size int
|
||||
// Counter is the number of entries stored in the map according
|
||||
// to the internal atomic counter. In case of concurrent map
|
||||
// modifications this number may be different from Size.
|
||||
Counter int
|
||||
// CounterLen is the number of internal atomic counter stripes.
|
||||
// This number may grow with the map capacity to improve
|
||||
// multithreaded scalability.
|
||||
CounterLen int
|
||||
// MinEntries is the minimum number of entries per a chain of
|
||||
// buckets, i.e. a root bucket and its chained buckets.
|
||||
MinEntries int
|
||||
// MinEntries is the maximum number of entries per a chain of
|
||||
// buckets, i.e. a root bucket and its chained buckets.
|
||||
MaxEntries int
|
||||
// TotalGrowths is the number of times the hash table grew.
|
||||
TotalGrowths int64
|
||||
// TotalGrowths is the number of times the hash table shrinked.
|
||||
TotalShrinks int64
|
||||
}
|
||||
|
||||
// ToString returns string representation of map stats.
|
||||
func (s *MapStats) ToString() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("MapStats{\n")
|
||||
sb.WriteString(fmt.Sprintf("RootBuckets: %d\n", s.RootBuckets))
|
||||
sb.WriteString(fmt.Sprintf("TotalBuckets: %d\n", s.TotalBuckets))
|
||||
sb.WriteString(fmt.Sprintf("EmptyBuckets: %d\n", s.EmptyBuckets))
|
||||
sb.WriteString(fmt.Sprintf("Capacity: %d\n", s.Capacity))
|
||||
sb.WriteString(fmt.Sprintf("Size: %d\n", s.Size))
|
||||
sb.WriteString(fmt.Sprintf("Counter: %d\n", s.Counter))
|
||||
sb.WriteString(fmt.Sprintf("CounterLen: %d\n", s.CounterLen))
|
||||
sb.WriteString(fmt.Sprintf("MinEntries: %d\n", s.MinEntries))
|
||||
sb.WriteString(fmt.Sprintf("MaxEntries: %d\n", s.MaxEntries))
|
||||
sb.WriteString(fmt.Sprintf("TotalGrowths: %d\n", s.TotalGrowths))
|
||||
sb.WriteString(fmt.Sprintf("TotalShrinks: %d\n", s.TotalShrinks))
|
||||
sb.WriteString("}\n")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Stats returns statistics for the Map. Just like other map
|
||||
// methods, this one is thread-safe. Yet it's an O(N) operation,
|
||||
// so it should be used only for diagnostics or debugging purposes.
|
||||
func (m *Map) Stats() MapStats {
|
||||
stats := MapStats{
|
||||
TotalGrowths: atomic.LoadInt64(&m.totalGrowths),
|
||||
TotalShrinks: atomic.LoadInt64(&m.totalShrinks),
|
||||
MinEntries: math.MaxInt32,
|
||||
}
|
||||
table := (*mapTable)(atomic.LoadPointer(&m.table))
|
||||
stats.RootBuckets = len(table.buckets)
|
||||
stats.Counter = int(table.sumSize())
|
||||
stats.CounterLen = len(table.size)
|
||||
for i := range table.buckets {
|
||||
nentries := 0
|
||||
b := &table.buckets[i]
|
||||
stats.TotalBuckets++
|
||||
for {
|
||||
nentriesLocal := 0
|
||||
stats.Capacity += entriesPerMapBucket
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if atomic.LoadPointer(&b.keys[i]) != nil {
|
||||
stats.Size++
|
||||
nentriesLocal++
|
||||
}
|
||||
}
|
||||
nentries += nentriesLocal
|
||||
if nentriesLocal == 0 {
|
||||
stats.EmptyBuckets++
|
||||
}
|
||||
if b.next == nil {
|
||||
break
|
||||
}
|
||||
b = (*bucketPadded)(atomic.LoadPointer(&b.next))
|
||||
stats.TotalBuckets++
|
||||
}
|
||||
if nentries < stats.MinEntries {
|
||||
stats.MinEntries = nentries
|
||||
}
|
||||
if nentries > stats.MaxEntries {
|
||||
stats.MaxEntries = nentries
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
694
vendor/github.com/puzpuzpuz/xsync/v3/mapof.go
generated
vendored
Normal file
694
vendor/github.com/puzpuzpuz/xsync/v3/mapof.go
generated
vendored
Normal file
|
@ -0,0 +1,694 @@
|
|||
package xsync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// number of MapOf entries per bucket; 5 entries lead to size of 64B
|
||||
// (one cache line) on 64-bit machines
|
||||
entriesPerMapOfBucket = 5
|
||||
defaultMeta uint64 = 0x8080808080808080
|
||||
metaMask uint64 = 0xffffffffff
|
||||
defaultMetaMasked uint64 = defaultMeta & metaMask
|
||||
emptyMetaSlot uint8 = 0x80
|
||||
)
|
||||
|
||||
// MapOf is like a Go map[K]V but is safe for concurrent
|
||||
// use by multiple goroutines without additional locking or
|
||||
// coordination. It follows the interface of sync.Map with
|
||||
// a number of valuable extensions like Compute or Size.
|
||||
//
|
||||
// A MapOf must not be copied after first use.
|
||||
//
|
||||
// MapOf uses a modified version of Cache-Line Hash Table (CLHT)
|
||||
// data structure: https://github.com/LPD-EPFL/CLHT
|
||||
//
|
||||
// CLHT is built around idea to organize the hash table in
|
||||
// cache-line-sized buckets, so that on all modern CPUs update
|
||||
// operations complete with at most one cache-line transfer.
|
||||
// Also, Get operations involve no write to memory, as well as no
|
||||
// mutexes or any other sort of locks. Due to this design, in all
|
||||
// considered scenarios MapOf outperforms sync.Map.
|
||||
//
|
||||
// MapOf also borrows ideas from Java's j.u.c.ConcurrentHashMap
|
||||
// (immutable K/V pair structs instead of atomic snapshots)
|
||||
// and C++'s absl::flat_hash_map (meta memory and SWAR-based
|
||||
// lookups).
|
||||
type MapOf[K comparable, V any] struct {
|
||||
totalGrowths int64
|
||||
totalShrinks int64
|
||||
resizing int64 // resize in progress flag; updated atomically
|
||||
resizeMu sync.Mutex // only used along with resizeCond
|
||||
resizeCond sync.Cond // used to wake up resize waiters (concurrent modifications)
|
||||
table unsafe.Pointer // *mapOfTable
|
||||
hasher func(K, uint64) uint64
|
||||
minTableLen int
|
||||
growOnly bool
|
||||
}
|
||||
|
||||
type mapOfTable[K comparable, V any] struct {
|
||||
buckets []bucketOfPadded
|
||||
// striped counter for number of table entries;
|
||||
// used to determine if a table shrinking is needed
|
||||
// occupies min(buckets_memory/1024, 64KB) of memory
|
||||
size []counterStripe
|
||||
seed uint64
|
||||
}
|
||||
|
||||
// bucketOfPadded is a CL-sized map bucket holding up to
|
||||
// entriesPerMapOfBucket entries.
|
||||
type bucketOfPadded struct {
|
||||
//lint:ignore U1000 ensure each bucket takes two cache lines on both 32 and 64-bit archs
|
||||
pad [cacheLineSize - unsafe.Sizeof(bucketOf{})]byte
|
||||
bucketOf
|
||||
}
|
||||
|
||||
type bucketOf struct {
|
||||
meta uint64
|
||||
entries [entriesPerMapOfBucket]unsafe.Pointer // *entryOf
|
||||
next unsafe.Pointer // *bucketOfPadded
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// entryOf is an immutable map entry.
|
||||
type entryOf[K comparable, V any] struct {
|
||||
key K
|
||||
value V
|
||||
}
|
||||
|
||||
// NewMapOf creates a new MapOf instance configured with the given
|
||||
// options.
|
||||
func NewMapOf[K comparable, V any](options ...func(*MapConfig)) *MapOf[K, V] {
|
||||
return NewMapOfWithHasher[K, V](defaultHasher[K](), options...)
|
||||
}
|
||||
|
||||
// NewMapOfWithHasher creates a new MapOf instance configured with
|
||||
// the given hasher and options. The hash function is used instead
|
||||
// of the built-in hash function configured when a map is created
|
||||
// with the NewMapOf function.
|
||||
func NewMapOfWithHasher[K comparable, V any](
|
||||
hasher func(K, uint64) uint64,
|
||||
options ...func(*MapConfig),
|
||||
) *MapOf[K, V] {
|
||||
c := &MapConfig{
|
||||
sizeHint: defaultMinMapTableLen * entriesPerMapOfBucket,
|
||||
}
|
||||
for _, o := range options {
|
||||
o(c)
|
||||
}
|
||||
|
||||
m := &MapOf[K, V]{}
|
||||
m.resizeCond = *sync.NewCond(&m.resizeMu)
|
||||
m.hasher = hasher
|
||||
var table *mapOfTable[K, V]
|
||||
if c.sizeHint <= defaultMinMapTableLen*entriesPerMapOfBucket {
|
||||
table = newMapOfTable[K, V](defaultMinMapTableLen)
|
||||
} else {
|
||||
tableLen := nextPowOf2(uint32((float64(c.sizeHint) / entriesPerMapOfBucket) / mapLoadFactor))
|
||||
table = newMapOfTable[K, V](int(tableLen))
|
||||
}
|
||||
m.minTableLen = len(table.buckets)
|
||||
m.growOnly = c.growOnly
|
||||
atomic.StorePointer(&m.table, unsafe.Pointer(table))
|
||||
return m
|
||||
}
|
||||
|
||||
// NewMapOfPresized creates a new MapOf instance with capacity enough
|
||||
// to hold sizeHint entries. The capacity is treated as the minimal capacity
|
||||
// meaning that the underlying hash table will never shrink to
|
||||
// a smaller capacity. If sizeHint is zero or negative, the value
|
||||
// is ignored.
|
||||
//
|
||||
// Deprecated: use NewMapOf in combination with WithPresize.
|
||||
func NewMapOfPresized[K comparable, V any](sizeHint int) *MapOf[K, V] {
|
||||
return NewMapOf[K, V](WithPresize(sizeHint))
|
||||
}
|
||||
|
||||
func newMapOfTable[K comparable, V any](minTableLen int) *mapOfTable[K, V] {
|
||||
buckets := make([]bucketOfPadded, minTableLen)
|
||||
for i := range buckets {
|
||||
buckets[i].meta = defaultMeta
|
||||
}
|
||||
counterLen := minTableLen >> 10
|
||||
if counterLen < minMapCounterLen {
|
||||
counterLen = minMapCounterLen
|
||||
} else if counterLen > maxMapCounterLen {
|
||||
counterLen = maxMapCounterLen
|
||||
}
|
||||
counter := make([]counterStripe, counterLen)
|
||||
t := &mapOfTable[K, V]{
|
||||
buckets: buckets,
|
||||
size: counter,
|
||||
seed: makeSeed(),
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Load returns the value stored in the map for a key, or zero value
|
||||
// of type V if no value is present.
|
||||
// The ok result indicates whether value was found in the map.
|
||||
func (m *MapOf[K, V]) Load(key K) (value V, ok bool) {
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
hash := m.hasher(key, table.seed)
|
||||
h1 := h1(hash)
|
||||
h2w := broadcast(h2(hash))
|
||||
bidx := uint64(len(table.buckets)-1) & h1
|
||||
b := &table.buckets[bidx]
|
||||
for {
|
||||
metaw := atomic.LoadUint64(&b.meta)
|
||||
markedw := markZeroBytes(metaw^h2w) & metaMask
|
||||
for markedw != 0 {
|
||||
idx := firstMarkedByteIndex(markedw)
|
||||
eptr := atomic.LoadPointer(&b.entries[idx])
|
||||
if eptr != nil {
|
||||
e := (*entryOf[K, V])(eptr)
|
||||
if e.key == key {
|
||||
return e.value, true
|
||||
}
|
||||
}
|
||||
markedw &= markedw - 1
|
||||
}
|
||||
bptr := atomic.LoadPointer(&b.next)
|
||||
if bptr == nil {
|
||||
return
|
||||
}
|
||||
b = (*bucketOfPadded)(bptr)
|
||||
}
|
||||
}
|
||||
|
||||
// Store sets the value for a key.
|
||||
func (m *MapOf[K, V]) Store(key K, value V) {
|
||||
m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, bool) {
|
||||
return value, false
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadOrStore returns the existing value for the key if present.
|
||||
// Otherwise, it stores and returns the given value.
|
||||
// The loaded result is true if the value was loaded, false if stored.
|
||||
func (m *MapOf[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, bool) {
|
||||
return value, false
|
||||
},
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadAndStore returns the existing value for the key if present,
|
||||
// while setting the new value for the key.
|
||||
// It stores the new value and returns the existing one, if present.
|
||||
// The loaded result is true if the existing value was loaded,
|
||||
// false otherwise.
|
||||
func (m *MapOf[K, V]) LoadAndStore(key K, value V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, bool) {
|
||||
return value, false
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadOrCompute returns the existing value for the key if present.
|
||||
// Otherwise, it computes the value using the provided function and
|
||||
// returns the computed value. The loaded result is true if the value
|
||||
// was loaded, false if stored.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *MapOf[K, V]) LoadOrCompute(key K, valueFn func() V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, bool) {
|
||||
return valueFn(), false
|
||||
},
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// Compute either sets the computed new value for the key or deletes
|
||||
// the value for the key. When the delete result of the valueFn function
|
||||
// is set to true, the value will be deleted, if it exists. When delete
|
||||
// is set to false, the value is updated to the newValue.
|
||||
// The ok result indicates whether value was computed and stored, thus, is
|
||||
// present in the map. The actual result contains the new value in cases where
|
||||
// the value was computed and stored. See the example for a few use cases.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *MapOf[K, V]) Compute(
|
||||
key K,
|
||||
valueFn func(oldValue V, loaded bool) (newValue V, delete bool),
|
||||
) (actual V, ok bool) {
|
||||
return m.doCompute(key, valueFn, false, true)
|
||||
}
|
||||
|
||||
// LoadAndDelete deletes the value for a key, returning the previous
|
||||
// value if any. The loaded result reports whether the key was
|
||||
// present.
|
||||
func (m *MapOf[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(value V, loaded bool) (V, bool) {
|
||||
return value, true
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// Delete deletes the value for a key.
|
||||
func (m *MapOf[K, V]) Delete(key K) {
|
||||
m.doCompute(
|
||||
key,
|
||||
func(value V, loaded bool) (V, bool) {
|
||||
return value, true
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) doCompute(
|
||||
key K,
|
||||
valueFn func(oldValue V, loaded bool) (V, bool),
|
||||
loadIfExists, computeOnly bool,
|
||||
) (V, bool) {
|
||||
// Read-only path.
|
||||
if loadIfExists {
|
||||
if v, ok := m.Load(key); ok {
|
||||
return v, !computeOnly
|
||||
}
|
||||
}
|
||||
// Write path.
|
||||
for {
|
||||
compute_attempt:
|
||||
var (
|
||||
emptyb *bucketOfPadded
|
||||
emptyidx int
|
||||
)
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
tableLen := len(table.buckets)
|
||||
hash := m.hasher(key, table.seed)
|
||||
h1 := h1(hash)
|
||||
h2 := h2(hash)
|
||||
h2w := broadcast(h2)
|
||||
bidx := uint64(len(table.buckets)-1) & h1
|
||||
rootb := &table.buckets[bidx]
|
||||
rootb.mu.Lock()
|
||||
// The following two checks must go in reverse to what's
|
||||
// in the resize method.
|
||||
if m.resizeInProgress() {
|
||||
// Resize is in progress. Wait, then go for another attempt.
|
||||
rootb.mu.Unlock()
|
||||
m.waitForResize()
|
||||
goto compute_attempt
|
||||
}
|
||||
if m.newerTableExists(table) {
|
||||
// Someone resized the table. Go for another attempt.
|
||||
rootb.mu.Unlock()
|
||||
goto compute_attempt
|
||||
}
|
||||
b := rootb
|
||||
for {
|
||||
metaw := b.meta
|
||||
markedw := markZeroBytes(metaw^h2w) & metaMask
|
||||
for markedw != 0 {
|
||||
idx := firstMarkedByteIndex(markedw)
|
||||
eptr := b.entries[idx]
|
||||
if eptr != nil {
|
||||
e := (*entryOf[K, V])(eptr)
|
||||
if e.key == key {
|
||||
if loadIfExists {
|
||||
rootb.mu.Unlock()
|
||||
return e.value, !computeOnly
|
||||
}
|
||||
// In-place update/delete.
|
||||
// We get a copy of the value via an interface{} on each call,
|
||||
// thus the live value pointers are unique. Otherwise atomic
|
||||
// snapshot won't be correct in case of multiple Store calls
|
||||
// using the same value.
|
||||
oldv := e.value
|
||||
newv, del := valueFn(oldv, true)
|
||||
if del {
|
||||
// Deletion.
|
||||
// First we update the hash, then the entry.
|
||||
newmetaw := setByte(metaw, emptyMetaSlot, idx)
|
||||
atomic.StoreUint64(&b.meta, newmetaw)
|
||||
atomic.StorePointer(&b.entries[idx], nil)
|
||||
rootb.mu.Unlock()
|
||||
table.addSize(bidx, -1)
|
||||
// Might need to shrink the table if we left bucket empty.
|
||||
if newmetaw == defaultMeta {
|
||||
m.resize(table, mapShrinkHint)
|
||||
}
|
||||
return oldv, !computeOnly
|
||||
}
|
||||
newe := new(entryOf[K, V])
|
||||
newe.key = key
|
||||
newe.value = newv
|
||||
atomic.StorePointer(&b.entries[idx], unsafe.Pointer(newe))
|
||||
rootb.mu.Unlock()
|
||||
if computeOnly {
|
||||
// Compute expects the new value to be returned.
|
||||
return newv, true
|
||||
}
|
||||
// LoadAndStore expects the old value to be returned.
|
||||
return oldv, true
|
||||
}
|
||||
}
|
||||
markedw &= markedw - 1
|
||||
}
|
||||
if emptyb == nil {
|
||||
// Search for empty entries (up to 5 per bucket).
|
||||
emptyw := metaw & defaultMetaMasked
|
||||
if emptyw != 0 {
|
||||
idx := firstMarkedByteIndex(emptyw)
|
||||
emptyb = b
|
||||
emptyidx = idx
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
if emptyb != nil {
|
||||
// Insertion into an existing bucket.
|
||||
var zeroedV V
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
if del {
|
||||
rootb.mu.Unlock()
|
||||
return zeroedV, false
|
||||
}
|
||||
newe := new(entryOf[K, V])
|
||||
newe.key = key
|
||||
newe.value = newValue
|
||||
// First we update meta, then the entry.
|
||||
atomic.StoreUint64(&emptyb.meta, setByte(emptyb.meta, h2, emptyidx))
|
||||
atomic.StorePointer(&emptyb.entries[emptyidx], unsafe.Pointer(newe))
|
||||
rootb.mu.Unlock()
|
||||
table.addSize(bidx, 1)
|
||||
return newValue, computeOnly
|
||||
}
|
||||
growThreshold := float64(tableLen) * entriesPerMapOfBucket * mapLoadFactor
|
||||
if table.sumSize() > int64(growThreshold) {
|
||||
// Need to grow the table. Then go for another attempt.
|
||||
rootb.mu.Unlock()
|
||||
m.resize(table, mapGrowHint)
|
||||
goto compute_attempt
|
||||
}
|
||||
// Insertion into a new bucket.
|
||||
var zeroedV V
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
if del {
|
||||
rootb.mu.Unlock()
|
||||
return newValue, false
|
||||
}
|
||||
// Create and append a bucket.
|
||||
newb := new(bucketOfPadded)
|
||||
newb.meta = setByte(defaultMeta, h2, 0)
|
||||
newe := new(entryOf[K, V])
|
||||
newe.key = key
|
||||
newe.value = newValue
|
||||
newb.entries[0] = unsafe.Pointer(newe)
|
||||
atomic.StorePointer(&b.next, unsafe.Pointer(newb))
|
||||
rootb.mu.Unlock()
|
||||
table.addSize(bidx, 1)
|
||||
return newValue, computeOnly
|
||||
}
|
||||
b = (*bucketOfPadded)(b.next)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) newerTableExists(table *mapOfTable[K, V]) bool {
|
||||
curTablePtr := atomic.LoadPointer(&m.table)
|
||||
return uintptr(curTablePtr) != uintptr(unsafe.Pointer(table))
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) resizeInProgress() bool {
|
||||
return atomic.LoadInt64(&m.resizing) == 1
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) waitForResize() {
|
||||
m.resizeMu.Lock()
|
||||
for m.resizeInProgress() {
|
||||
m.resizeCond.Wait()
|
||||
}
|
||||
m.resizeMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) resize(knownTable *mapOfTable[K, V], hint mapResizeHint) {
|
||||
knownTableLen := len(knownTable.buckets)
|
||||
// Fast path for shrink attempts.
|
||||
if hint == mapShrinkHint {
|
||||
if m.growOnly ||
|
||||
m.minTableLen == knownTableLen ||
|
||||
knownTable.sumSize() > int64((knownTableLen*entriesPerMapOfBucket)/mapShrinkFraction) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Slow path.
|
||||
if !atomic.CompareAndSwapInt64(&m.resizing, 0, 1) {
|
||||
// Someone else started resize. Wait for it to finish.
|
||||
m.waitForResize()
|
||||
return
|
||||
}
|
||||
var newTable *mapOfTable[K, V]
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
tableLen := len(table.buckets)
|
||||
switch hint {
|
||||
case mapGrowHint:
|
||||
// Grow the table with factor of 2.
|
||||
atomic.AddInt64(&m.totalGrowths, 1)
|
||||
newTable = newMapOfTable[K, V](tableLen << 1)
|
||||
case mapShrinkHint:
|
||||
shrinkThreshold := int64((tableLen * entriesPerMapOfBucket) / mapShrinkFraction)
|
||||
if tableLen > m.minTableLen && table.sumSize() <= shrinkThreshold {
|
||||
// Shrink the table with factor of 2.
|
||||
atomic.AddInt64(&m.totalShrinks, 1)
|
||||
newTable = newMapOfTable[K, V](tableLen >> 1)
|
||||
} else {
|
||||
// No need to shrink. Wake up all waiters and give up.
|
||||
m.resizeMu.Lock()
|
||||
atomic.StoreInt64(&m.resizing, 0)
|
||||
m.resizeCond.Broadcast()
|
||||
m.resizeMu.Unlock()
|
||||
return
|
||||
}
|
||||
case mapClearHint:
|
||||
newTable = newMapOfTable[K, V](m.minTableLen)
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected resize hint: %d", hint))
|
||||
}
|
||||
// Copy the data only if we're not clearing the map.
|
||||
if hint != mapClearHint {
|
||||
for i := 0; i < tableLen; i++ {
|
||||
copied := copyBucketOf(&table.buckets[i], newTable, m.hasher)
|
||||
newTable.addSizePlain(uint64(i), copied)
|
||||
}
|
||||
}
|
||||
// Publish the new table and wake up all waiters.
|
||||
atomic.StorePointer(&m.table, unsafe.Pointer(newTable))
|
||||
m.resizeMu.Lock()
|
||||
atomic.StoreInt64(&m.resizing, 0)
|
||||
m.resizeCond.Broadcast()
|
||||
m.resizeMu.Unlock()
|
||||
}
|
||||
|
||||
func copyBucketOf[K comparable, V any](
|
||||
b *bucketOfPadded,
|
||||
destTable *mapOfTable[K, V],
|
||||
hasher func(K, uint64) uint64,
|
||||
) (copied int) {
|
||||
rootb := b
|
||||
rootb.mu.Lock()
|
||||
for {
|
||||
for i := 0; i < entriesPerMapOfBucket; i++ {
|
||||
if b.entries[i] != nil {
|
||||
e := (*entryOf[K, V])(b.entries[i])
|
||||
hash := hasher(e.key, destTable.seed)
|
||||
bidx := uint64(len(destTable.buckets)-1) & h1(hash)
|
||||
destb := &destTable.buckets[bidx]
|
||||
appendToBucketOf(h2(hash), b.entries[i], destb)
|
||||
copied++
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
rootb.mu.Unlock()
|
||||
return
|
||||
}
|
||||
b = (*bucketOfPadded)(b.next)
|
||||
}
|
||||
}
|
||||
|
||||
// Range calls f sequentially for each key and value present in the
|
||||
// map. If f returns false, range stops the iteration.
|
||||
//
|
||||
// Range does not necessarily correspond to any consistent snapshot
|
||||
// of the Map's contents: no key will be visited more than once, but
|
||||
// if the value for any key is stored or deleted concurrently, Range
|
||||
// may reflect any mapping for that key from any point during the
|
||||
// Range call.
|
||||
//
|
||||
// It is safe to modify the map while iterating it, including entry
|
||||
// creation, modification and deletion. However, the concurrent
|
||||
// modification rule apply, i.e. the changes may be not reflected
|
||||
// in the subsequently iterated entries.
|
||||
func (m *MapOf[K, V]) Range(f func(key K, value V) bool) {
|
||||
var zeroPtr unsafe.Pointer
|
||||
// Pre-allocate array big enough to fit entries for most hash tables.
|
||||
bentries := make([]unsafe.Pointer, 0, 16*entriesPerMapOfBucket)
|
||||
tablep := atomic.LoadPointer(&m.table)
|
||||
table := *(*mapOfTable[K, V])(tablep)
|
||||
for i := range table.buckets {
|
||||
rootb := &table.buckets[i]
|
||||
b := rootb
|
||||
// Prevent concurrent modifications and copy all entries into
|
||||
// the intermediate slice.
|
||||
rootb.mu.Lock()
|
||||
for {
|
||||
for i := 0; i < entriesPerMapOfBucket; i++ {
|
||||
if b.entries[i] != nil {
|
||||
bentries = append(bentries, b.entries[i])
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
rootb.mu.Unlock()
|
||||
break
|
||||
}
|
||||
b = (*bucketOfPadded)(b.next)
|
||||
}
|
||||
// Call the function for all copied entries.
|
||||
for j := range bentries {
|
||||
entry := (*entryOf[K, V])(bentries[j])
|
||||
if !f(entry.key, entry.value) {
|
||||
return
|
||||
}
|
||||
// Remove the reference to avoid preventing the copied
|
||||
// entries from being GCed until this method finishes.
|
||||
bentries[j] = zeroPtr
|
||||
}
|
||||
bentries = bentries[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// Clear deletes all keys and values currently stored in the map.
|
||||
func (m *MapOf[K, V]) Clear() {
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
m.resize(table, mapClearHint)
|
||||
}
|
||||
|
||||
// Size returns current size of the map.
|
||||
func (m *MapOf[K, V]) Size() int {
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
return int(table.sumSize())
|
||||
}
|
||||
|
||||
func appendToBucketOf(h2 uint8, entryPtr unsafe.Pointer, b *bucketOfPadded) {
|
||||
for {
|
||||
for i := 0; i < entriesPerMapOfBucket; i++ {
|
||||
if b.entries[i] == nil {
|
||||
b.meta = setByte(b.meta, h2, i)
|
||||
b.entries[i] = entryPtr
|
||||
return
|
||||
}
|
||||
}
|
||||
if b.next == nil {
|
||||
newb := new(bucketOfPadded)
|
||||
newb.meta = setByte(defaultMeta, h2, 0)
|
||||
newb.entries[0] = entryPtr
|
||||
b.next = unsafe.Pointer(newb)
|
||||
return
|
||||
}
|
||||
b = (*bucketOfPadded)(b.next)
|
||||
}
|
||||
}
|
||||
|
||||
func (table *mapOfTable[K, V]) addSize(bucketIdx uint64, delta int) {
|
||||
cidx := uint64(len(table.size)-1) & bucketIdx
|
||||
atomic.AddInt64(&table.size[cidx].c, int64(delta))
|
||||
}
|
||||
|
||||
func (table *mapOfTable[K, V]) addSizePlain(bucketIdx uint64, delta int) {
|
||||
cidx := uint64(len(table.size)-1) & bucketIdx
|
||||
table.size[cidx].c += int64(delta)
|
||||
}
|
||||
|
||||
func (table *mapOfTable[K, V]) sumSize() int64 {
|
||||
sum := int64(0)
|
||||
for i := range table.size {
|
||||
sum += atomic.LoadInt64(&table.size[i].c)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func h1(h uint64) uint64 {
|
||||
return h >> 7
|
||||
}
|
||||
|
||||
func h2(h uint64) uint8 {
|
||||
return uint8(h & 0x7f)
|
||||
}
|
||||
|
||||
// Stats returns statistics for the MapOf. Just like other map
|
||||
// methods, this one is thread-safe. Yet it's an O(N) operation,
|
||||
// so it should be used only for diagnostics or debugging purposes.
|
||||
func (m *MapOf[K, V]) Stats() MapStats {
|
||||
stats := MapStats{
|
||||
TotalGrowths: atomic.LoadInt64(&m.totalGrowths),
|
||||
TotalShrinks: atomic.LoadInt64(&m.totalShrinks),
|
||||
MinEntries: math.MaxInt32,
|
||||
}
|
||||
table := (*mapOfTable[K, V])(atomic.LoadPointer(&m.table))
|
||||
stats.RootBuckets = len(table.buckets)
|
||||
stats.Counter = int(table.sumSize())
|
||||
stats.CounterLen = len(table.size)
|
||||
for i := range table.buckets {
|
||||
nentries := 0
|
||||
b := &table.buckets[i]
|
||||
stats.TotalBuckets++
|
||||
for {
|
||||
nentriesLocal := 0
|
||||
stats.Capacity += entriesPerMapOfBucket
|
||||
for i := 0; i < entriesPerMapOfBucket; i++ {
|
||||
if atomic.LoadPointer(&b.entries[i]) != nil {
|
||||
stats.Size++
|
||||
nentriesLocal++
|
||||
}
|
||||
}
|
||||
nentries += nentriesLocal
|
||||
if nentriesLocal == 0 {
|
||||
stats.EmptyBuckets++
|
||||
}
|
||||
if b.next == nil {
|
||||
break
|
||||
}
|
||||
b = (*bucketOfPadded)(atomic.LoadPointer(&b.next))
|
||||
stats.TotalBuckets++
|
||||
}
|
||||
if nentries < stats.MinEntries {
|
||||
stats.MinEntries = nentries
|
||||
}
|
||||
if nentries > stats.MaxEntries {
|
||||
stats.MaxEntries = nentries
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
137
vendor/github.com/puzpuzpuz/xsync/v3/mpmcqueue.go
generated
vendored
Normal file
137
vendor/github.com/puzpuzpuz/xsync/v3/mpmcqueue.go
generated
vendored
Normal file
|
@ -0,0 +1,137 @@
|
|||
package xsync
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// A MPMCQueue is a bounded multi-producer multi-consumer concurrent
|
||||
// queue.
|
||||
//
|
||||
// MPMCQueue instances must be created with NewMPMCQueue function.
|
||||
// A MPMCQueue must not be copied after first use.
|
||||
//
|
||||
// Based on the data structure from the following C++ library:
|
||||
// https://github.com/rigtorp/MPMCQueue
|
||||
type MPMCQueue struct {
|
||||
cap uint64
|
||||
head uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
hpad [cacheLineSize - 8]byte
|
||||
tail uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
tpad [cacheLineSize - 8]byte
|
||||
slots []slotPadded
|
||||
}
|
||||
|
||||
type slotPadded struct {
|
||||
slot
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad [cacheLineSize - unsafe.Sizeof(slot{})]byte
|
||||
}
|
||||
|
||||
type slot struct {
|
||||
turn uint64
|
||||
item interface{}
|
||||
}
|
||||
|
||||
// NewMPMCQueue creates a new MPMCQueue instance with the given
|
||||
// capacity.
|
||||
func NewMPMCQueue(capacity int) *MPMCQueue {
|
||||
if capacity < 1 {
|
||||
panic("capacity must be positive number")
|
||||
}
|
||||
return &MPMCQueue{
|
||||
cap: uint64(capacity),
|
||||
slots: make([]slotPadded, capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue inserts the given item into the queue.
|
||||
// Blocks, if the queue is full.
|
||||
func (q *MPMCQueue) Enqueue(item interface{}) {
|
||||
head := atomic.AddUint64(&q.head, 1) - 1
|
||||
slot := &q.slots[q.idx(head)]
|
||||
turn := q.turn(head) * 2
|
||||
for atomic.LoadUint64(&slot.turn) != turn {
|
||||
runtime.Gosched()
|
||||
}
|
||||
slot.item = item
|
||||
atomic.StoreUint64(&slot.turn, turn+1)
|
||||
}
|
||||
|
||||
// Dequeue retrieves and removes the item from the head of the queue.
|
||||
// Blocks, if the queue is empty.
|
||||
func (q *MPMCQueue) Dequeue() interface{} {
|
||||
tail := atomic.AddUint64(&q.tail, 1) - 1
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
turn := q.turn(tail)*2 + 1
|
||||
for atomic.LoadUint64(&slot.turn) != turn {
|
||||
runtime.Gosched()
|
||||
}
|
||||
item := slot.item
|
||||
slot.item = nil
|
||||
atomic.StoreUint64(&slot.turn, turn+1)
|
||||
return item
|
||||
}
|
||||
|
||||
// TryEnqueue inserts the given item into the queue. Does not block
|
||||
// and returns immediately. The result indicates that the queue isn't
|
||||
// full and the item was inserted.
|
||||
func (q *MPMCQueue) TryEnqueue(item interface{}) bool {
|
||||
head := atomic.LoadUint64(&q.head)
|
||||
for {
|
||||
slot := &q.slots[q.idx(head)]
|
||||
turn := q.turn(head) * 2
|
||||
if atomic.LoadUint64(&slot.turn) == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.head, head, head+1) {
|
||||
slot.item = item
|
||||
atomic.StoreUint64(&slot.turn, turn+1)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
prevHead := head
|
||||
head = atomic.LoadUint64(&q.head)
|
||||
if head == prevHead {
|
||||
return false
|
||||
}
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
// TryDequeue retrieves and removes the item from the head of the
|
||||
// queue. Does not block and returns immediately. The ok result
|
||||
// indicates that the queue isn't empty and an item was retrieved.
|
||||
func (q *MPMCQueue) TryDequeue() (item interface{}, ok bool) {
|
||||
tail := atomic.LoadUint64(&q.tail)
|
||||
for {
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
turn := q.turn(tail)*2 + 1
|
||||
if atomic.LoadUint64(&slot.turn) == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.tail, tail, tail+1) {
|
||||
item = slot.item
|
||||
ok = true
|
||||
slot.item = nil
|
||||
atomic.StoreUint64(&slot.turn, turn+1)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
prevTail := tail
|
||||
tail = atomic.LoadUint64(&q.tail)
|
||||
if tail == prevTail {
|
||||
return
|
||||
}
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func (q *MPMCQueue) idx(i uint64) uint64 {
|
||||
return i % q.cap
|
||||
}
|
||||
|
||||
func (q *MPMCQueue) turn(i uint64) uint64 {
|
||||
return i / q.cap
|
||||
}
|
150
vendor/github.com/puzpuzpuz/xsync/v3/mpmcqueueof.go
generated
vendored
Normal file
150
vendor/github.com/puzpuzpuz/xsync/v3/mpmcqueueof.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package xsync
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// A MPMCQueueOf is a bounded multi-producer multi-consumer concurrent
|
||||
// queue. It's a generic version of MPMCQueue.
|
||||
//
|
||||
// MPMCQueue instances must be created with NewMPMCQueueOf function.
|
||||
// A MPMCQueueOf must not be copied after first use.
|
||||
//
|
||||
// Based on the data structure from the following C++ library:
|
||||
// https://github.com/rigtorp/MPMCQueue
|
||||
type MPMCQueueOf[I any] struct {
|
||||
cap uint64
|
||||
head uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
hpad [cacheLineSize - 8]byte
|
||||
tail uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
tpad [cacheLineSize - 8]byte
|
||||
slots []slotOfPadded[I]
|
||||
}
|
||||
|
||||
type slotOfPadded[I any] struct {
|
||||
slotOf[I]
|
||||
// Unfortunately, proper padding like the below one:
|
||||
//
|
||||
// pad [cacheLineSize - (unsafe.Sizeof(slotOf[I]{}) % cacheLineSize)]byte
|
||||
//
|
||||
// won't compile, so here we add a best-effort padding for items up to
|
||||
// 56 bytes size.
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad [cacheLineSize - unsafe.Sizeof(atomic.Uint64{})]byte
|
||||
}
|
||||
|
||||
type slotOf[I any] struct {
|
||||
// atomic.Uint64 is used here to get proper 8 byte alignment on
|
||||
// 32-bit archs.
|
||||
turn atomic.Uint64
|
||||
item I
|
||||
}
|
||||
|
||||
// NewMPMCQueueOf creates a new MPMCQueueOf instance with the given
|
||||
// capacity.
|
||||
func NewMPMCQueueOf[I any](capacity int) *MPMCQueueOf[I] {
|
||||
if capacity < 1 {
|
||||
panic("capacity must be positive number")
|
||||
}
|
||||
return &MPMCQueueOf[I]{
|
||||
cap: uint64(capacity),
|
||||
slots: make([]slotOfPadded[I], capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue inserts the given item into the queue.
|
||||
// Blocks, if the queue is full.
|
||||
func (q *MPMCQueueOf[I]) Enqueue(item I) {
|
||||
head := atomic.AddUint64(&q.head, 1) - 1
|
||||
slot := &q.slots[q.idx(head)]
|
||||
turn := q.turn(head) * 2
|
||||
for slot.turn.Load() != turn {
|
||||
runtime.Gosched()
|
||||
}
|
||||
slot.item = item
|
||||
slot.turn.Store(turn + 1)
|
||||
}
|
||||
|
||||
// Dequeue retrieves and removes the item from the head of the queue.
|
||||
// Blocks, if the queue is empty.
|
||||
func (q *MPMCQueueOf[I]) Dequeue() I {
|
||||
var zeroedI I
|
||||
tail := atomic.AddUint64(&q.tail, 1) - 1
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
turn := q.turn(tail)*2 + 1
|
||||
for slot.turn.Load() != turn {
|
||||
runtime.Gosched()
|
||||
}
|
||||
item := slot.item
|
||||
slot.item = zeroedI
|
||||
slot.turn.Store(turn + 1)
|
||||
return item
|
||||
}
|
||||
|
||||
// TryEnqueue inserts the given item into the queue. Does not block
|
||||
// and returns immediately. The result indicates that the queue isn't
|
||||
// full and the item was inserted.
|
||||
func (q *MPMCQueueOf[I]) TryEnqueue(item I) bool {
|
||||
head := atomic.LoadUint64(&q.head)
|
||||
for {
|
||||
slot := &q.slots[q.idx(head)]
|
||||
turn := q.turn(head) * 2
|
||||
if slot.turn.Load() == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.head, head, head+1) {
|
||||
slot.item = item
|
||||
slot.turn.Store(turn + 1)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
prevHead := head
|
||||
head = atomic.LoadUint64(&q.head)
|
||||
if head == prevHead {
|
||||
return false
|
||||
}
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
// TryDequeue retrieves and removes the item from the head of the
|
||||
// queue. Does not block and returns immediately. The ok result
|
||||
// indicates that the queue isn't empty and an item was retrieved.
|
||||
func (q *MPMCQueueOf[I]) TryDequeue() (item I, ok bool) {
|
||||
tail := atomic.LoadUint64(&q.tail)
|
||||
for {
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
turn := q.turn(tail)*2 + 1
|
||||
if slot.turn.Load() == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.tail, tail, tail+1) {
|
||||
var zeroedI I
|
||||
item = slot.item
|
||||
ok = true
|
||||
slot.item = zeroedI
|
||||
slot.turn.Store(turn + 1)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
prevTail := tail
|
||||
tail = atomic.LoadUint64(&q.tail)
|
||||
if tail == prevTail {
|
||||
return
|
||||
}
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func (q *MPMCQueueOf[I]) idx(i uint64) uint64 {
|
||||
return i % q.cap
|
||||
}
|
||||
|
||||
func (q *MPMCQueueOf[I]) turn(i uint64) uint64 {
|
||||
return i / q.cap
|
||||
}
|
188
vendor/github.com/puzpuzpuz/xsync/v3/rbmutex.go
generated
vendored
Normal file
188
vendor/github.com/puzpuzpuz/xsync/v3/rbmutex.go
generated
vendored
Normal file
|
@ -0,0 +1,188 @@
|
|||
package xsync
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// slow-down guard
|
||||
const nslowdown = 7
|
||||
|
||||
// pool for reader tokens
|
||||
var rtokenPool sync.Pool
|
||||
|
||||
// RToken is a reader lock token.
|
||||
type RToken struct {
|
||||
slot uint32
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad [cacheLineSize - 4]byte
|
||||
}
|
||||
|
||||
// A RBMutex is a reader biased reader/writer mutual exclusion lock.
|
||||
// The lock can be held by an many readers or a single writer.
|
||||
// The zero value for a RBMutex is an unlocked mutex.
|
||||
//
|
||||
// A RBMutex must not be copied after first use.
|
||||
//
|
||||
// RBMutex is based on a modified version of BRAVO
|
||||
// (Biased Locking for Reader-Writer Locks) algorithm:
|
||||
// https://arxiv.org/pdf/1810.01553.pdf
|
||||
//
|
||||
// RBMutex is a specialized mutex for scenarios, such as caches,
|
||||
// where the vast majority of locks are acquired by readers and write
|
||||
// lock acquire attempts are infrequent. In such scenarios, RBMutex
|
||||
// performs better than sync.RWMutex on large multicore machines.
|
||||
//
|
||||
// RBMutex extends sync.RWMutex internally and uses it as the "reader
|
||||
// bias disabled" fallback, so the same semantics apply. The only
|
||||
// noticeable difference is in reader tokens returned from the
|
||||
// RLock/RUnlock methods.
|
||||
type RBMutex struct {
|
||||
rslots []rslot
|
||||
rmask uint32
|
||||
rbias int32
|
||||
inhibitUntil time.Time
|
||||
rw sync.RWMutex
|
||||
}
|
||||
|
||||
type rslot struct {
|
||||
mu int32
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad [cacheLineSize - 4]byte
|
||||
}
|
||||
|
||||
// NewRBMutex creates a new RBMutex instance.
|
||||
func NewRBMutex() *RBMutex {
|
||||
nslots := nextPowOf2(parallelism())
|
||||
mu := RBMutex{
|
||||
rslots: make([]rslot, nslots),
|
||||
rmask: nslots - 1,
|
||||
rbias: 1,
|
||||
}
|
||||
return &mu
|
||||
}
|
||||
|
||||
// TryRLock tries to lock m for reading without blocking.
|
||||
// When TryRLock succeeds, it returns true and a reader token.
|
||||
// In case of a failure, a false is returned.
|
||||
func (mu *RBMutex) TryRLock() (bool, *RToken) {
|
||||
if t := mu.fastRlock(); t != nil {
|
||||
return true, t
|
||||
}
|
||||
// Optimistic slow path.
|
||||
if mu.rw.TryRLock() {
|
||||
if atomic.LoadInt32(&mu.rbias) == 0 && time.Now().After(mu.inhibitUntil) {
|
||||
atomic.StoreInt32(&mu.rbias, 1)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// RLock locks m for reading and returns a reader token. The
|
||||
// token must be used in the later RUnlock call.
|
||||
//
|
||||
// Should not be used for recursive read locking; a blocked Lock
|
||||
// call excludes new readers from acquiring the lock.
|
||||
func (mu *RBMutex) RLock() *RToken {
|
||||
if t := mu.fastRlock(); t != nil {
|
||||
return t
|
||||
}
|
||||
// Slow path.
|
||||
mu.rw.RLock()
|
||||
if atomic.LoadInt32(&mu.rbias) == 0 && time.Now().After(mu.inhibitUntil) {
|
||||
atomic.StoreInt32(&mu.rbias, 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mu *RBMutex) fastRlock() *RToken {
|
||||
if atomic.LoadInt32(&mu.rbias) == 1 {
|
||||
t, ok := rtokenPool.Get().(*RToken)
|
||||
if !ok {
|
||||
t = new(RToken)
|
||||
t.slot = runtime_fastrand()
|
||||
}
|
||||
// Try all available slots to distribute reader threads to slots.
|
||||
for i := 0; i < len(mu.rslots); i++ {
|
||||
slot := t.slot + uint32(i)
|
||||
rslot := &mu.rslots[slot&mu.rmask]
|
||||
rslotmu := atomic.LoadInt32(&rslot.mu)
|
||||
if atomic.CompareAndSwapInt32(&rslot.mu, rslotmu, rslotmu+1) {
|
||||
if atomic.LoadInt32(&mu.rbias) == 1 {
|
||||
// Hot path succeeded.
|
||||
t.slot = slot
|
||||
return t
|
||||
}
|
||||
// The mutex is no longer reader biased. Roll back.
|
||||
atomic.AddInt32(&rslot.mu, -1)
|
||||
rtokenPool.Put(t)
|
||||
return nil
|
||||
}
|
||||
// Contention detected. Give a try with the next slot.
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call. A reader token obtained from
|
||||
// the RLock call must be provided. RUnlock does not affect other
|
||||
// simultaneous readers. A panic is raised if m is not locked for
|
||||
// reading on entry to RUnlock.
|
||||
func (mu *RBMutex) RUnlock(t *RToken) {
|
||||
if t == nil {
|
||||
mu.rw.RUnlock()
|
||||
return
|
||||
}
|
||||
if atomic.AddInt32(&mu.rslots[t.slot&mu.rmask].mu, -1) < 0 {
|
||||
panic("invalid reader state detected")
|
||||
}
|
||||
rtokenPool.Put(t)
|
||||
}
|
||||
|
||||
// TryLock tries to lock m for writing without blocking.
|
||||
func (mu *RBMutex) TryLock() bool {
|
||||
if mu.rw.TryLock() {
|
||||
if atomic.LoadInt32(&mu.rbias) == 1 {
|
||||
atomic.StoreInt32(&mu.rbias, 0)
|
||||
for i := 0; i < len(mu.rslots); i++ {
|
||||
if atomic.LoadInt32(&mu.rslots[i].mu) > 0 {
|
||||
// There is a reader. Roll back.
|
||||
atomic.StoreInt32(&mu.rbias, 1)
|
||||
mu.rw.Unlock()
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Lock locks m for writing. If the lock is already locked for
|
||||
// reading or writing, Lock blocks until the lock is available.
|
||||
func (mu *RBMutex) Lock() {
|
||||
mu.rw.Lock()
|
||||
if atomic.LoadInt32(&mu.rbias) == 1 {
|
||||
atomic.StoreInt32(&mu.rbias, 0)
|
||||
start := time.Now()
|
||||
for i := 0; i < len(mu.rslots); i++ {
|
||||
for atomic.LoadInt32(&mu.rslots[i].mu) > 0 {
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
mu.inhibitUntil = time.Now().Add(time.Since(start) * nslowdown)
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock unlocks m for writing. A panic is raised if m is not locked
|
||||
// for writing on entry to Unlock.
|
||||
//
|
||||
// As with RWMutex, a locked RBMutex is not associated with a
|
||||
// particular goroutine. One goroutine may RLock (Lock) a RBMutex and
|
||||
// then arrange for another goroutine to RUnlock (Unlock) it.
|
||||
func (mu *RBMutex) Unlock() {
|
||||
mu.rw.Unlock()
|
||||
}
|
66
vendor/github.com/puzpuzpuz/xsync/v3/util.go
generated
vendored
Normal file
66
vendor/github.com/puzpuzpuz/xsync/v3/util.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
package xsync
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"runtime"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
// test-only assert()-like flag
|
||||
var assertionsEnabled = false
|
||||
|
||||
const (
|
||||
// cacheLineSize is used in paddings to prevent false sharing;
|
||||
// 64B are used instead of 128B as a compromise between
|
||||
// memory footprint and performance; 128B usage may give ~30%
|
||||
// improvement on NUMA machines.
|
||||
cacheLineSize = 64
|
||||
)
|
||||
|
||||
// nextPowOf2 computes the next highest power of 2 of 32-bit v.
|
||||
// Source: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
func nextPowOf2(v uint32) uint32 {
|
||||
if v == 0 {
|
||||
return 1
|
||||
}
|
||||
v--
|
||||
v |= v >> 1
|
||||
v |= v >> 2
|
||||
v |= v >> 4
|
||||
v |= v >> 8
|
||||
v |= v >> 16
|
||||
v++
|
||||
return v
|
||||
}
|
||||
|
||||
func parallelism() uint32 {
|
||||
maxProcs := uint32(runtime.GOMAXPROCS(0))
|
||||
numCores := uint32(runtime.NumCPU())
|
||||
if maxProcs < numCores {
|
||||
return maxProcs
|
||||
}
|
||||
return numCores
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
//go:linkname runtime_fastrand runtime.fastrand
|
||||
func runtime_fastrand() uint32
|
||||
|
||||
func broadcast(b uint8) uint64 {
|
||||
return 0x101010101010101 * uint64(b)
|
||||
}
|
||||
|
||||
func firstMarkedByteIndex(w uint64) int {
|
||||
return bits.TrailingZeros64(w) >> 3
|
||||
}
|
||||
|
||||
// SWAR byte search: may produce false positives, e.g. for 0x0100,
|
||||
// so make sure to double-check bytes found by this function.
|
||||
func markZeroBytes(w uint64) uint64 {
|
||||
return ((w - 0x0101010101010101) & (^w) & 0x8080808080808080)
|
||||
}
|
||||
|
||||
func setByte(w uint64, b uint8, idx int) uint64 {
|
||||
shift := idx << 3
|
||||
return (w &^ (0xff << shift)) | (uint64(b) << shift)
|
||||
}
|
77
vendor/github.com/puzpuzpuz/xsync/v3/util_hash.go
generated
vendored
Normal file
77
vendor/github.com/puzpuzpuz/xsync/v3/util_hash.go
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
package xsync
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// makeSeed creates a random seed.
|
||||
func makeSeed() uint64 {
|
||||
var s1 uint32
|
||||
for {
|
||||
s1 = runtime_fastrand()
|
||||
// We use seed 0 to indicate an uninitialized seed/hash,
|
||||
// so keep trying until we get a non-zero seed.
|
||||
if s1 != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
s2 := runtime_fastrand()
|
||||
return uint64(s1)<<32 | uint64(s2)
|
||||
}
|
||||
|
||||
// hashString calculates a hash of s with the given seed.
|
||||
func hashString(s string, seed uint64) uint64 {
|
||||
if s == "" {
|
||||
return seed
|
||||
}
|
||||
strh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
return uint64(runtime_memhash(unsafe.Pointer(strh.Data), uintptr(seed), uintptr(strh.Len)))
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
//go:linkname runtime_memhash runtime.memhash
|
||||
func runtime_memhash(p unsafe.Pointer, h, s uintptr) uintptr
|
||||
|
||||
// defaultHasher creates a fast hash function for the given comparable type.
|
||||
// The only limitation is that the type should not contain interfaces inside
|
||||
// based on runtime.typehash.
|
||||
func defaultHasher[T comparable]() func(T, uint64) uint64 {
|
||||
var zero T
|
||||
|
||||
if reflect.TypeOf(&zero).Elem().Kind() == reflect.Interface {
|
||||
return func(value T, seed uint64) uint64 {
|
||||
iValue := any(value)
|
||||
i := (*iface)(unsafe.Pointer(&iValue))
|
||||
return runtime_typehash64(i.typ, i.word, seed)
|
||||
}
|
||||
} else {
|
||||
var iZero any = zero
|
||||
i := (*iface)(unsafe.Pointer(&iZero))
|
||||
return func(value T, seed uint64) uint64 {
|
||||
return runtime_typehash64(i.typ, unsafe.Pointer(&value), seed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// how interface is represented in memory
|
||||
type iface struct {
|
||||
typ uintptr
|
||||
word unsafe.Pointer
|
||||
}
|
||||
|
||||
// same as runtime_typehash, but always returns a uint64
|
||||
// see: maphash.rthash function for details
|
||||
func runtime_typehash64(t uintptr, p unsafe.Pointer, seed uint64) uint64 {
|
||||
if unsafe.Sizeof(uintptr(0)) == 8 {
|
||||
return uint64(runtime_typehash(t, p, uintptr(seed)))
|
||||
}
|
||||
|
||||
lo := runtime_typehash(t, p, uintptr(seed))
|
||||
hi := runtime_typehash(t, p, uintptr(seed>>32))
|
||||
return uint64(hi)<<32 | uint64(lo)
|
||||
}
|
||||
|
||||
//go:noescape
|
||||
//go:linkname runtime_typehash runtime.typehash
|
||||
func runtime_typehash(t uintptr, p unsafe.Pointer, h uintptr) uintptr
|
79
vendor/github.com/uptrace/bun/CHANGELOG.md
generated
vendored
79
vendor/github.com/uptrace/bun/CHANGELOG.md
generated
vendored
|
@ -1,3 +1,70 @@
|
|||
## [1.2.5](https://github.com/uptrace/bun/compare/v1.2.3...v1.2.5) (2024-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow Limit() without Order() with MSSQL ([#1009](https://github.com/uptrace/bun/issues/1009)) ([1a46ddc](https://github.com/uptrace/bun/commit/1a46ddc0d3ca0bdc60ca8be5ad1886799d14c8b0))
|
||||
* copy bytes in mapModel.Scan ([#1030](https://github.com/uptrace/bun/issues/1030)) ([#1032](https://github.com/uptrace/bun/issues/1032)) ([39fda4e](https://github.com/uptrace/bun/commit/39fda4e3d341e59e4955f751cb354a939e57c1b1))
|
||||
* fix issue with has-many join and pointer fields ([#950](https://github.com/uptrace/bun/issues/950)) ([#983](https://github.com/uptrace/bun/issues/983)) ([cbc5177](https://github.com/uptrace/bun/commit/cbc517792ba6cdcef1828f3699d3d4dfe3c5e0eb))
|
||||
* restore explicit column: name override ([#984](https://github.com/uptrace/bun/issues/984)) ([169f258](https://github.com/uptrace/bun/commit/169f258a9460cad451f3025d2ef8df1bbd42a003))
|
||||
* return column option back ([#1036](https://github.com/uptrace/bun/issues/1036)) ([a3ccbea](https://github.com/uptrace/bun/commit/a3ccbeab39151d3eed6cb245fe15cfb5d71ba557))
|
||||
* sql.NullString mistaken as custom struct ([#1019](https://github.com/uptrace/bun/issues/1019)) ([87c77b8](https://github.com/uptrace/bun/commit/87c77b8911f2035b0ee8ea96356a2c7600b5b94d))
|
||||
* typos ([#1026](https://github.com/uptrace/bun/issues/1026)) ([760de7d](https://github.com/uptrace/bun/commit/760de7d0fad15dc761475670a4dde056aef9210d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add transaction isolation level support to pgdriver ([#1034](https://github.com/uptrace/bun/issues/1034)) ([3ef44ce](https://github.com/uptrace/bun/commit/3ef44ce1cdd969a21b76d6c803119cf12c375cb0))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* refactor SelectQuery.ScanAndCount to optimize performance when there is no limit and offset ([#1035](https://github.com/uptrace/bun/issues/1035)) ([8638613](https://github.com/uptrace/bun/commit/86386135897485bbada6c50ec9a2743626111433))
|
||||
|
||||
|
||||
|
||||
## [1.2.4](https://github.com/uptrace/bun/compare/v1.2.3...v1.2.4) (2024-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow Limit() without Order() with MSSQL ([#1009](https://github.com/uptrace/bun/issues/1009)) ([1a46ddc](https://github.com/uptrace/bun/commit/1a46ddc0d3ca0bdc60ca8be5ad1886799d14c8b0))
|
||||
* copy bytes in mapModel.Scan ([#1030](https://github.com/uptrace/bun/issues/1030)) ([#1032](https://github.com/uptrace/bun/issues/1032)) ([39fda4e](https://github.com/uptrace/bun/commit/39fda4e3d341e59e4955f751cb354a939e57c1b1))
|
||||
* return column option back ([#1036](https://github.com/uptrace/bun/issues/1036)) ([a3ccbea](https://github.com/uptrace/bun/commit/a3ccbeab39151d3eed6cb245fe15cfb5d71ba557))
|
||||
* sql.NullString mistaken as custom struct ([#1019](https://github.com/uptrace/bun/issues/1019)) ([87c77b8](https://github.com/uptrace/bun/commit/87c77b8911f2035b0ee8ea96356a2c7600b5b94d))
|
||||
* typos ([#1026](https://github.com/uptrace/bun/issues/1026)) ([760de7d](https://github.com/uptrace/bun/commit/760de7d0fad15dc761475670a4dde056aef9210d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add transaction isolation level support to pgdriver ([#1034](https://github.com/uptrace/bun/issues/1034)) ([3ef44ce](https://github.com/uptrace/bun/commit/3ef44ce1cdd969a21b76d6c803119cf12c375cb0))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* refactor SelectQuery.ScanAndCount to optimize performance when there is no limit and offset ([#1035](https://github.com/uptrace/bun/issues/1035)) ([8638613](https://github.com/uptrace/bun/commit/86386135897485bbada6c50ec9a2743626111433))
|
||||
|
||||
|
||||
|
||||
## [1.2.3](https://github.com/uptrace/bun/compare/v1.2.2...v1.2.3) (2024-08-31)
|
||||
|
||||
|
||||
|
||||
## [1.2.2](https://github.com/uptrace/bun/compare/v1.2.1...v1.2.2) (2024-08-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* gracefully handle empty hstore in pgdialect ([#1010](https://github.com/uptrace/bun/issues/1010)) ([2f73d8a](https://github.com/uptrace/bun/commit/2f73d8a8e16c8718ebfc956036d9c9a01a0888bc))
|
||||
* number each unit test ([#974](https://github.com/uptrace/bun/issues/974)) ([b005dc2](https://github.com/uptrace/bun/commit/b005dc2a9034715c6f59dcfc8e76aa3b85df38ab))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ModelTableExpr to TruncateTableQuery ([#969](https://github.com/uptrace/bun/issues/969)) ([7bc330f](https://github.com/uptrace/bun/commit/7bc330f152cf0d9dc30956478e2731ea5816f012))
|
||||
|
||||
|
||||
|
||||
## [1.2.1](https://github.com/uptrace/bun/compare/v1.2.0...v1.2.1) (2024-04-02)
|
||||
|
||||
|
||||
|
@ -14,7 +81,7 @@
|
|||
|
||||
### Features
|
||||
|
||||
* Allow overiding of Warn and Deprecated loggers ([#952](https://github.com/uptrace/bun/issues/952)) ([0e9d737](https://github.com/uptrace/bun/commit/0e9d737e4ca2deb86930237ee32a39cf3f7e8157))
|
||||
* Allow overriding of Warn and Deprecated loggers ([#952](https://github.com/uptrace/bun/issues/952)) ([0e9d737](https://github.com/uptrace/bun/commit/0e9d737e4ca2deb86930237ee32a39cf3f7e8157))
|
||||
* enable SNI ([#953](https://github.com/uptrace/bun/issues/953)) ([4071ffb](https://github.com/uptrace/bun/commit/4071ffb5bcb1b233cda239c92504d8139dcf1d2f))
|
||||
* **idb:** add NewMerge method to IDB ([#966](https://github.com/uptrace/bun/issues/966)) ([664e2f1](https://github.com/uptrace/bun/commit/664e2f154f1153d2a80cd062a5074f1692edaee7))
|
||||
|
||||
|
@ -100,7 +167,7 @@
|
|||
|
||||
### Bug Fixes
|
||||
|
||||
* add support for inserting values with unicode encoding for mssql dialect ([e98c6c0](https://github.com/uptrace/bun/commit/e98c6c0f033b553bea3bbc783aa56c2eaa17718f))
|
||||
* add support for inserting values with Unicode encoding for mssql dialect ([e98c6c0](https://github.com/uptrace/bun/commit/e98c6c0f033b553bea3bbc783aa56c2eaa17718f))
|
||||
* fix relation tag ([a3eedff](https://github.com/uptrace/bun/commit/a3eedff49700490d4998dcdcdc04f554d8f17166))
|
||||
|
||||
|
||||
|
@ -136,7 +203,7 @@
|
|||
|
||||
### Bug Fixes
|
||||
|
||||
* addng dialect override for append-bool ([#695](https://github.com/uptrace/bun/issues/695)) ([338f2f0](https://github.com/uptrace/bun/commit/338f2f04105ad89e64530db86aeb387e2ad4789e))
|
||||
* adding dialect override for append-bool ([#695](https://github.com/uptrace/bun/issues/695)) ([338f2f0](https://github.com/uptrace/bun/commit/338f2f04105ad89e64530db86aeb387e2ad4789e))
|
||||
* don't call hooks twice for whereExists ([9057857](https://github.com/uptrace/bun/commit/90578578e717f248e4b6eb114c5b495fd8d4ed41))
|
||||
* don't lock migrations when running Migrate and Rollback ([69a7354](https://github.com/uptrace/bun/commit/69a7354d987ff2ed5338c9ef5f4ce320724299ab))
|
||||
* **query:** make WhereDeleted compatible with ForceDelete ([299c3fd](https://github.com/uptrace/bun/commit/299c3fd57866aaecd127a8f219c95332898475db)), closes [#673](https://github.com/uptrace/bun/issues/673)
|
||||
|
@ -304,7 +371,7 @@ recommended to upgrade to v1.0.24 before upgrading to v1.1.x.
|
|||
|
||||
- append slice values
|
||||
([4a65129](https://github.com/uptrace/bun/commit/4a651294fb0f1e73079553024810c3ead9777311))
|
||||
- check for nils when appeding driver.Value
|
||||
- check for nils when appending driver.Value
|
||||
([7bb1640](https://github.com/uptrace/bun/commit/7bb1640a00fceca1e1075fe6544b9a4842ab2b26))
|
||||
- cleanup soft deletes for mssql
|
||||
([e72e2c5](https://github.com/uptrace/bun/commit/e72e2c5d0a85f3d26c3fa22c7284c2de1dcfda8e))
|
||||
|
@ -323,7 +390,7 @@ recommended to upgrade to v1.0.24 before upgrading to v1.1.x.
|
|||
|
||||
### Deprecated
|
||||
|
||||
In the comming v1.1.x release, Bun will stop automatically adding `,pk,autoincrement` options on
|
||||
In the coming v1.1.x release, Bun will stop automatically adding `,pk,autoincrement` options on
|
||||
`ID int64/int32` fields. This version (v1.0.23) only prints a warning when it encounters such
|
||||
fields, but the code will continue working as before.
|
||||
|
||||
|
@ -441,7 +508,7 @@ In v1.1.x, such options as `,nopk` and `,allowzero` will not be necessary and wi
|
|||
([693f1e1](https://github.com/uptrace/bun/commit/693f1e135999fc31cf83b99a2530a695b20f4e1b))
|
||||
- add model embedding via embed:prefix\_
|
||||
([9a2cedc](https://github.com/uptrace/bun/commit/9a2cedc8b08fa8585d4bfced338bd0a40d736b1d))
|
||||
- change the default logoutput to stderr
|
||||
- change the default log output to stderr
|
||||
([4bf5773](https://github.com/uptrace/bun/commit/4bf577382f19c64457cbf0d64490401450954654)),
|
||||
closes [#349](https://github.com/uptrace/bun/issues/349)
|
||||
|
||||
|
|
2
vendor/github.com/uptrace/bun/Makefile
generated
vendored
2
vendor/github.com/uptrace/bun/Makefile
generated
vendored
|
@ -15,7 +15,7 @@ go_mod_tidy:
|
|||
echo "go mod tidy in $${dir}"; \
|
||||
(cd "$${dir}" && \
|
||||
go get -u ./... && \
|
||||
go mod tidy -go=1.21); \
|
||||
go mod tidy); \
|
||||
done
|
||||
|
||||
fmt:
|
||||
|
|
3
vendor/github.com/uptrace/bun/README.md
generated
vendored
3
vendor/github.com/uptrace/bun/README.md
generated
vendored
|
@ -1,4 +1,4 @@
|
|||
# SQL-first Golang ORM for PostgreSQL, MySQL, MSSQL, and SQLite
|
||||
# SQL-first Golang ORM for PostgreSQL, MySQL, MSSQL, SQLite and Oracle
|
||||
|
||||
[![build workflow](https://github.com/uptrace/bun/actions/workflows/build.yml/badge.svg)](https://github.com/uptrace/bun/actions)
|
||||
[![PkgGoDev](https://pkg.go.dev/badge/github.com/uptrace/bun)](https://pkg.go.dev/github.com/uptrace/bun)
|
||||
|
@ -19,6 +19,7 @@
|
|||
[MySQL](https://bun.uptrace.dev/guide/drivers.html#mysql) (including MariaDB),
|
||||
[MSSQL](https://bun.uptrace.dev/guide/drivers.html#mssql),
|
||||
[SQLite](https://bun.uptrace.dev/guide/drivers.html#sqlite).
|
||||
[Oracle](https://bun.uptrace.dev/guide/drivers.html#oracle).
|
||||
- [ORM-like](/example/basic/) experience using good old SQL. Bun supports structs, map, scalars, and
|
||||
slices of map/structs/scalars.
|
||||
- [Bulk inserts](https://bun.uptrace.dev/guide/query-insert.html).
|
||||
|
|
6
vendor/github.com/uptrace/bun/bun.go
generated
vendored
6
vendor/github.com/uptrace/bun/bun.go
generated
vendored
|
@ -22,6 +22,10 @@
|
|||
AfterScanRowHook = schema.AfterScanRowHook
|
||||
)
|
||||
|
||||
func SafeQuery(query string, args ...interface{}) schema.QueryWithArgs {
|
||||
return schema.SafeQuery(query, args)
|
||||
}
|
||||
|
||||
type BeforeSelectHook interface {
|
||||
BeforeSelect(ctx context.Context, query *SelectQuery) error
|
||||
}
|
||||
|
@ -70,7 +74,7 @@ type AfterDropTableHook interface {
|
|||
AfterDropTable(ctx context.Context, query *DropTableQuery) error
|
||||
}
|
||||
|
||||
// SetLogger overwriters default Bun logger.
|
||||
// SetLogger overwrites default Bun logger.
|
||||
func SetLogger(logger internal.Logging) {
|
||||
internal.SetLogger(logger)
|
||||
}
|
||||
|
|
3
vendor/github.com/uptrace/bun/dialect/dialect.go
generated
vendored
3
vendor/github.com/uptrace/bun/dialect/dialect.go
generated
vendored
|
@ -12,6 +12,8 @@ func (n Name) String() string {
|
|||
return "mysql"
|
||||
case MSSQL:
|
||||
return "mssql"
|
||||
case Oracle:
|
||||
return "oracle"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
|
@ -23,4 +25,5 @@ func (n Name) String() string {
|
|||
SQLite
|
||||
MySQL
|
||||
MSSQL
|
||||
Oracle
|
||||
)
|
||||
|
|
313
vendor/github.com/uptrace/bun/dialect/pgdialect/append.go
generated
vendored
313
vendor/github.com/uptrace/bun/dialect/pgdialect/append.go
generated
vendored
|
@ -2,12 +2,9 @@
|
|||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/schema"
|
||||
|
@ -32,316 +29,10 @@
|
|||
sliceTimeType = reflect.TypeOf([]time.Time(nil))
|
||||
)
|
||||
|
||||
func arrayAppend(fmter schema.Formatter, b []byte, v interface{}) []byte {
|
||||
switch v := v.(type) {
|
||||
case int64:
|
||||
return strconv.AppendInt(b, v, 10)
|
||||
case float64:
|
||||
return dialect.AppendFloat64(b, v)
|
||||
case bool:
|
||||
return dialect.AppendBool(b, v)
|
||||
case []byte:
|
||||
return arrayAppendBytes(b, v)
|
||||
case string:
|
||||
return arrayAppendString(b, v)
|
||||
case time.Time:
|
||||
return fmter.Dialect().AppendTime(b, v)
|
||||
default:
|
||||
err := fmt.Errorf("pgdialect: can't append %T", v)
|
||||
return dialect.AppendError(b, err)
|
||||
}
|
||||
func appendTime(buf []byte, tm time.Time) []byte {
|
||||
return tm.UTC().AppendFormat(buf, "2006-01-02 15:04:05.999999-07:00")
|
||||
}
|
||||
|
||||
func arrayAppendStringValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
return arrayAppendString(b, v.String())
|
||||
}
|
||||
|
||||
func arrayAppendBytesValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
return arrayAppendBytes(b, v.Bytes())
|
||||
}
|
||||
|
||||
func arrayAppendDriverValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
iface, err := v.Interface().(driver.Valuer).Value()
|
||||
if err != nil {
|
||||
return dialect.AppendError(b, err)
|
||||
}
|
||||
return arrayAppend(fmter, b, iface)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (d *Dialect) arrayAppender(typ reflect.Type) schema.AppenderFunc {
|
||||
kind := typ.Kind()
|
||||
|
||||
switch kind {
|
||||
case reflect.Ptr:
|
||||
if fn := d.arrayAppender(typ.Elem()); fn != nil {
|
||||
return schema.PtrAppender(fn)
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
// ok:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
elemType := typ.Elem()
|
||||
|
||||
if kind == reflect.Slice {
|
||||
switch elemType {
|
||||
case stringType:
|
||||
return appendStringSliceValue
|
||||
case intType:
|
||||
return appendIntSliceValue
|
||||
case int64Type:
|
||||
return appendInt64SliceValue
|
||||
case float64Type:
|
||||
return appendFloat64SliceValue
|
||||
case timeType:
|
||||
return appendTimeSliceValue
|
||||
}
|
||||
}
|
||||
|
||||
appendElem := d.arrayElemAppender(elemType)
|
||||
if appendElem == nil {
|
||||
panic(fmt.Errorf("pgdialect: %s is not supported", typ))
|
||||
}
|
||||
|
||||
return func(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
kind := v.Kind()
|
||||
switch kind {
|
||||
case reflect.Ptr, reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
}
|
||||
|
||||
if kind == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
b = append(b, '{')
|
||||
ln := v.Len()
|
||||
for i := 0; i < ln; i++ {
|
||||
elem := v.Index(i)
|
||||
b = appendElem(fmter, b, elem)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if v.Len() > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dialect) arrayElemAppender(typ reflect.Type) schema.AppenderFunc {
|
||||
if typ.Implements(driverValuerType) {
|
||||
return arrayAppendDriverValue
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.String:
|
||||
return arrayAppendStringValue
|
||||
case reflect.Slice:
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
return arrayAppendBytesValue
|
||||
}
|
||||
}
|
||||
return schema.Appender(d, typ)
|
||||
}
|
||||
|
||||
func appendStringSliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
ss := v.Convert(sliceStringType).Interface().([]string)
|
||||
return appendStringSlice(b, ss)
|
||||
}
|
||||
|
||||
func appendStringSlice(b []byte, ss []string) []byte {
|
||||
if ss == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
b = append(b, '{')
|
||||
for _, s := range ss {
|
||||
b = arrayAppendString(b, s)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(ss) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func appendIntSliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
ints := v.Convert(sliceIntType).Interface().([]int)
|
||||
return appendIntSlice(b, ints)
|
||||
}
|
||||
|
||||
func appendIntSlice(b []byte, ints []int) []byte {
|
||||
if ints == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
b = append(b, '{')
|
||||
for _, n := range ints {
|
||||
b = strconv.AppendInt(b, int64(n), 10)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(ints) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func appendInt64SliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
ints := v.Convert(sliceInt64Type).Interface().([]int64)
|
||||
return appendInt64Slice(b, ints)
|
||||
}
|
||||
|
||||
func appendInt64Slice(b []byte, ints []int64) []byte {
|
||||
if ints == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
b = append(b, '{')
|
||||
for _, n := range ints {
|
||||
b = strconv.AppendInt(b, n, 10)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(ints) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func appendFloat64SliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
floats := v.Convert(sliceFloat64Type).Interface().([]float64)
|
||||
return appendFloat64Slice(b, floats)
|
||||
}
|
||||
|
||||
func appendFloat64Slice(b []byte, floats []float64) []byte {
|
||||
if floats == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
b = append(b, '{')
|
||||
for _, n := range floats {
|
||||
b = dialect.AppendFloat64(b, n)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(floats) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func arrayAppendBytes(b []byte, bs []byte) []byte {
|
||||
if bs == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, `"\\x`...)
|
||||
|
||||
s := len(b)
|
||||
b = append(b, make([]byte, hex.EncodedLen(len(bs)))...)
|
||||
hex.Encode(b[s:], bs)
|
||||
|
||||
b = append(b, '"')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func arrayAppendString(b []byte, s string) []byte {
|
||||
b = append(b, '"')
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case 0:
|
||||
// ignore
|
||||
case '\'':
|
||||
b = append(b, "''"...)
|
||||
case '"':
|
||||
b = append(b, '\\', '"')
|
||||
case '\\':
|
||||
b = append(b, '\\', '\\')
|
||||
default:
|
||||
if r < utf8.RuneSelf {
|
||||
b = append(b, byte(r))
|
||||
break
|
||||
}
|
||||
l := len(b)
|
||||
if cap(b)-l < utf8.UTFMax {
|
||||
b = append(b, make([]byte, utf8.UTFMax)...)
|
||||
}
|
||||
n := utf8.EncodeRune(b[l:l+utf8.UTFMax], r)
|
||||
b = b[:l+n]
|
||||
}
|
||||
}
|
||||
b = append(b, '"')
|
||||
return b
|
||||
}
|
||||
|
||||
func appendTimeSliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
ts := v.Convert(sliceTimeType).Interface().([]time.Time)
|
||||
return appendTimeSlice(fmter, b, ts)
|
||||
}
|
||||
|
||||
func appendTimeSlice(fmter schema.Formatter, b []byte, ts []time.Time) []byte {
|
||||
if ts == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
b = append(b, '\'')
|
||||
b = append(b, '{')
|
||||
for _, t := range ts {
|
||||
b = append(b, '"')
|
||||
b = t.UTC().AppendFormat(b, "2006-01-02 15:04:05.999999-07:00")
|
||||
b = append(b, '"')
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(ts) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
b = append(b, '\'')
|
||||
return b
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var mapStringStringType = reflect.TypeOf(map[string]string(nil))
|
||||
|
||||
func (d *Dialect) hstoreAppender(typ reflect.Type) schema.AppenderFunc {
|
||||
|
|
582
vendor/github.com/uptrace/bun/dialect/pgdialect/array.go
generated
vendored
582
vendor/github.com/uptrace/bun/dialect/pgdialect/array.go
generated
vendored
|
@ -2,9 +2,16 @@
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/internal"
|
||||
"github.com/uptrace/bun/schema"
|
||||
)
|
||||
|
||||
|
@ -20,7 +27,7 @@ type ArrayValue struct {
|
|||
//
|
||||
// For struct fields you can use array tag:
|
||||
//
|
||||
// Emails []string `bun:",array"`
|
||||
// Emails []string `bun:",array"`
|
||||
func Array(vi interface{}) *ArrayValue {
|
||||
v := reflect.ValueOf(vi)
|
||||
if !v.IsValid() {
|
||||
|
@ -63,3 +70,576 @@ func (a *ArrayValue) Value() interface{} {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (d *Dialect) arrayAppender(typ reflect.Type) schema.AppenderFunc {
|
||||
kind := typ.Kind()
|
||||
|
||||
switch kind {
|
||||
case reflect.Ptr:
|
||||
if fn := d.arrayAppender(typ.Elem()); fn != nil {
|
||||
return schema.PtrAppender(fn)
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
// continue below
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
elemType := typ.Elem()
|
||||
|
||||
if kind == reflect.Slice {
|
||||
switch elemType {
|
||||
case stringType:
|
||||
return appendStringSliceValue
|
||||
case intType:
|
||||
return appendIntSliceValue
|
||||
case int64Type:
|
||||
return appendInt64SliceValue
|
||||
case float64Type:
|
||||
return appendFloat64SliceValue
|
||||
case timeType:
|
||||
return appendTimeSliceValue
|
||||
}
|
||||
}
|
||||
|
||||
appendElem := d.arrayElemAppender(elemType)
|
||||
if appendElem == nil {
|
||||
panic(fmt.Errorf("pgdialect: %s is not supported", typ))
|
||||
}
|
||||
|
||||
return func(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
kind := v.Kind()
|
||||
switch kind {
|
||||
case reflect.Ptr, reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
}
|
||||
|
||||
if kind == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
b = append(b, "'{"...)
|
||||
|
||||
ln := v.Len()
|
||||
for i := 0; i < ln; i++ {
|
||||
elem := v.Index(i)
|
||||
if i > 0 {
|
||||
b = append(b, ',')
|
||||
}
|
||||
b = appendElem(fmter, b, elem)
|
||||
}
|
||||
|
||||
b = append(b, "}'"...)
|
||||
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dialect) arrayElemAppender(typ reflect.Type) schema.AppenderFunc {
|
||||
if typ.Implements(driverValuerType) {
|
||||
return arrayAppendDriverValue
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.String:
|
||||
return arrayAppendStringValue
|
||||
case reflect.Slice:
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
return arrayAppendBytesValue
|
||||
}
|
||||
}
|
||||
return schema.Appender(d, typ)
|
||||
}
|
||||
|
||||
func arrayAppend(fmter schema.Formatter, b []byte, v interface{}) []byte {
|
||||
switch v := v.(type) {
|
||||
case int64:
|
||||
return strconv.AppendInt(b, v, 10)
|
||||
case float64:
|
||||
return dialect.AppendFloat64(b, v)
|
||||
case bool:
|
||||
return dialect.AppendBool(b, v)
|
||||
case []byte:
|
||||
return arrayAppendBytes(b, v)
|
||||
case string:
|
||||
return arrayAppendString(b, v)
|
||||
case time.Time:
|
||||
return fmter.Dialect().AppendTime(b, v)
|
||||
default:
|
||||
err := fmt.Errorf("pgdialect: can't append %T", v)
|
||||
return dialect.AppendError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func arrayAppendStringValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
return arrayAppendString(b, v.String())
|
||||
}
|
||||
|
||||
func arrayAppendBytesValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
return arrayAppendBytes(b, v.Bytes())
|
||||
}
|
||||
|
||||
func arrayAppendDriverValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
iface, err := v.Interface().(driver.Valuer).Value()
|
||||
if err != nil {
|
||||
return dialect.AppendError(b, err)
|
||||
}
|
||||
return arrayAppend(fmter, b, iface)
|
||||
}
|
||||
|
||||
func appendStringSliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
ss := v.Convert(sliceStringType).Interface().([]string)
|
||||
return appendStringSlice(b, ss)
|
||||
}
|
||||
|
||||
func appendStringSlice(b []byte, ss []string) []byte {
|
||||
if ss == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
b = append(b, '{')
|
||||
for _, s := range ss {
|
||||
b = arrayAppendString(b, s)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(ss) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func appendIntSliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
ints := v.Convert(sliceIntType).Interface().([]int)
|
||||
return appendIntSlice(b, ints)
|
||||
}
|
||||
|
||||
func appendIntSlice(b []byte, ints []int) []byte {
|
||||
if ints == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
b = append(b, '{')
|
||||
for _, n := range ints {
|
||||
b = strconv.AppendInt(b, int64(n), 10)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(ints) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func appendInt64SliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
ints := v.Convert(sliceInt64Type).Interface().([]int64)
|
||||
return appendInt64Slice(b, ints)
|
||||
}
|
||||
|
||||
func appendInt64Slice(b []byte, ints []int64) []byte {
|
||||
if ints == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
b = append(b, '{')
|
||||
for _, n := range ints {
|
||||
b = strconv.AppendInt(b, n, 10)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(ints) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func appendFloat64SliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
floats := v.Convert(sliceFloat64Type).Interface().([]float64)
|
||||
return appendFloat64Slice(b, floats)
|
||||
}
|
||||
|
||||
func appendFloat64Slice(b []byte, floats []float64) []byte {
|
||||
if floats == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
b = append(b, '{')
|
||||
for _, n := range floats {
|
||||
b = dialect.AppendFloat64(b, n)
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(floats) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
|
||||
b = append(b, '\'')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func appendTimeSliceValue(fmter schema.Formatter, b []byte, v reflect.Value) []byte {
|
||||
ts := v.Convert(sliceTimeType).Interface().([]time.Time)
|
||||
return appendTimeSlice(fmter, b, ts)
|
||||
}
|
||||
|
||||
func appendTimeSlice(fmter schema.Formatter, b []byte, ts []time.Time) []byte {
|
||||
if ts == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
b = append(b, '\'')
|
||||
b = append(b, '{')
|
||||
for _, t := range ts {
|
||||
b = append(b, '"')
|
||||
b = appendTime(b, t)
|
||||
b = append(b, '"')
|
||||
b = append(b, ',')
|
||||
}
|
||||
if len(ts) > 0 {
|
||||
b[len(b)-1] = '}' // Replace trailing comma.
|
||||
} else {
|
||||
b = append(b, '}')
|
||||
}
|
||||
b = append(b, '\'')
|
||||
return b
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func arrayScanner(typ reflect.Type) schema.ScannerFunc {
|
||||
kind := typ.Kind()
|
||||
|
||||
switch kind {
|
||||
case reflect.Ptr:
|
||||
if fn := arrayScanner(typ.Elem()); fn != nil {
|
||||
return schema.PtrScanner(fn)
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
// ok:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
elemType := typ.Elem()
|
||||
|
||||
if kind == reflect.Slice {
|
||||
switch elemType {
|
||||
case stringType:
|
||||
return scanStringSliceValue
|
||||
case intType:
|
||||
return scanIntSliceValue
|
||||
case int64Type:
|
||||
return scanInt64SliceValue
|
||||
case float64Type:
|
||||
return scanFloat64SliceValue
|
||||
}
|
||||
}
|
||||
|
||||
scanElem := schema.Scanner(elemType)
|
||||
return func(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
kind := dest.Kind()
|
||||
|
||||
if src == nil {
|
||||
if kind != reflect.Slice || !dest.IsNil() {
|
||||
dest.Set(reflect.Zero(dest.Type()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if kind == reflect.Slice {
|
||||
if dest.IsNil() {
|
||||
dest.Set(reflect.MakeSlice(dest.Type(), 0, 0))
|
||||
} else if dest.Len() > 0 {
|
||||
dest.Set(dest.Slice(0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := newArrayParser(b)
|
||||
nextValue := internal.MakeSliceNextElemFunc(dest)
|
||||
for p.Next() {
|
||||
elem := p.Elem()
|
||||
elemValue := nextValue()
|
||||
if err := scanElem(elemValue, elem); err != nil {
|
||||
return fmt.Errorf("scanElem failed: %w", err)
|
||||
}
|
||||
}
|
||||
return p.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func scanStringSliceValue(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
slice, err := decodeStringSlice(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest.Set(reflect.ValueOf(slice))
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeStringSlice(src interface{}) ([]string, error) {
|
||||
if src == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice := make([]string, 0)
|
||||
|
||||
p := newArrayParser(b)
|
||||
for p.Next() {
|
||||
elem := p.Elem()
|
||||
slice = append(slice, string(elem))
|
||||
}
|
||||
if err := p.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func scanIntSliceValue(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
slice, err := decodeIntSlice(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest.Set(reflect.ValueOf(slice))
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeIntSlice(src interface{}) ([]int, error) {
|
||||
if src == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice := make([]int, 0)
|
||||
|
||||
p := newArrayParser(b)
|
||||
for p.Next() {
|
||||
elem := p.Elem()
|
||||
|
||||
if elem == nil {
|
||||
slice = append(slice, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
n, err := strconv.Atoi(bytesToString(elem))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice = append(slice, n)
|
||||
}
|
||||
if err := p.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func scanInt64SliceValue(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
slice, err := decodeInt64Slice(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest.Set(reflect.ValueOf(slice))
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeInt64Slice(src interface{}) ([]int64, error) {
|
||||
if src == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice := make([]int64, 0)
|
||||
|
||||
p := newArrayParser(b)
|
||||
for p.Next() {
|
||||
elem := p.Elem()
|
||||
|
||||
if elem == nil {
|
||||
slice = append(slice, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
n, err := strconv.ParseInt(bytesToString(elem), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice = append(slice, n)
|
||||
}
|
||||
if err := p.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func scanFloat64SliceValue(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
slice, err := scanFloat64Slice(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest.Set(reflect.ValueOf(slice))
|
||||
return nil
|
||||
}
|
||||
|
||||
func scanFloat64Slice(src interface{}) ([]float64, error) {
|
||||
if src == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice := make([]float64, 0)
|
||||
|
||||
p := newArrayParser(b)
|
||||
for p.Next() {
|
||||
elem := p.Elem()
|
||||
|
||||
if elem == nil {
|
||||
slice = append(slice, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
n, err := strconv.ParseFloat(bytesToString(elem), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice = append(slice, n)
|
||||
}
|
||||
if err := p.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func toBytes(src interface{}) ([]byte, error) {
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return stringToBytes(src), nil
|
||||
case []byte:
|
||||
return src, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("bun: got %T, wanted []byte or string", src)
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func arrayAppendBytes(b []byte, bs []byte) []byte {
|
||||
if bs == nil {
|
||||
return dialect.AppendNull(b)
|
||||
}
|
||||
|
||||
b = append(b, `"\\x`...)
|
||||
|
||||
s := len(b)
|
||||
b = append(b, make([]byte, hex.EncodedLen(len(bs)))...)
|
||||
hex.Encode(b[s:], bs)
|
||||
|
||||
b = append(b, '"')
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func arrayAppendString(b []byte, s string) []byte {
|
||||
b = append(b, '"')
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case 0:
|
||||
// ignore
|
||||
case '\'':
|
||||
b = append(b, "''"...)
|
||||
case '"':
|
||||
b = append(b, '\\', '"')
|
||||
case '\\':
|
||||
b = append(b, '\\', '\\')
|
||||
default:
|
||||
if r < utf8.RuneSelf {
|
||||
b = append(b, byte(r))
|
||||
break
|
||||
}
|
||||
l := len(b)
|
||||
if cap(b)-l < utf8.UTFMax {
|
||||
b = append(b, make([]byte, utf8.UTFMax)...)
|
||||
}
|
||||
n := utf8.EncodeRune(b[l:l+utf8.UTFMax], r)
|
||||
b = b[:l+n]
|
||||
}
|
||||
}
|
||||
b = append(b, '"')
|
||||
return b
|
||||
}
|
||||
|
|
158
vendor/github.com/uptrace/bun/dialect/pgdialect/array_parser.go
generated
vendored
158
vendor/github.com/uptrace/bun/dialect/pgdialect/array_parser.go
generated
vendored
|
@ -2,132 +2,92 @@
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type arrayParser struct {
|
||||
*streamParser
|
||||
err error
|
||||
p pgparser
|
||||
|
||||
elem []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func newArrayParser(b []byte) *arrayParser {
|
||||
p := &arrayParser{
|
||||
streamParser: newStreamParser(b, 1),
|
||||
}
|
||||
p := new(arrayParser)
|
||||
|
||||
if len(b) < 2 || b[0] != '{' || b[len(b)-1] != '}' {
|
||||
p.err = fmt.Errorf("bun: can't parse array: %q", b)
|
||||
p.err = fmt.Errorf("pgdialect: can't parse array: %q", b)
|
||||
return p
|
||||
}
|
||||
|
||||
p.p.Reset(b[1 : len(b)-1])
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *arrayParser) NextElem() ([]byte, error) {
|
||||
func (p *arrayParser) Next() bool {
|
||||
if p.err != nil {
|
||||
return nil, p.err
|
||||
return false
|
||||
}
|
||||
p.err = p.readNext()
|
||||
return p.err == nil
|
||||
}
|
||||
|
||||
func (p *arrayParser) Err() error {
|
||||
if p.err != io.EOF {
|
||||
return p.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *arrayParser) Elem() []byte {
|
||||
return p.elem
|
||||
}
|
||||
|
||||
func (p *arrayParser) readNext() error {
|
||||
ch := p.p.Read()
|
||||
if ch == 0 {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
c, err := p.readByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch c {
|
||||
switch ch {
|
||||
case '}':
|
||||
return nil, io.EOF
|
||||
return io.EOF
|
||||
case '"':
|
||||
b, err := p.readSubstring()
|
||||
b, err := p.p.ReadSubstring(ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
if p.peek() == ',' {
|
||||
p.skipNext()
|
||||
if p.p.Peek() == ',' {
|
||||
p.p.Advance()
|
||||
}
|
||||
|
||||
return b, nil
|
||||
p.elem = b
|
||||
return nil
|
||||
case '[', '(':
|
||||
rng, err := p.p.ReadRange(ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.p.Peek() == ',' {
|
||||
p.p.Advance()
|
||||
}
|
||||
|
||||
p.elem = rng
|
||||
return nil
|
||||
default:
|
||||
b := p.readSimple()
|
||||
if bytes.Equal(b, []byte("NULL")) {
|
||||
b = nil
|
||||
lit := p.p.ReadLiteral(ch)
|
||||
if bytes.Equal(lit, []byte("NULL")) {
|
||||
lit = nil
|
||||
}
|
||||
|
||||
if p.peek() == ',' {
|
||||
p.skipNext()
|
||||
if p.p.Peek() == ',' {
|
||||
p.p.Advance()
|
||||
}
|
||||
|
||||
return b, nil
|
||||
p.elem = lit
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *arrayParser) readSimple() []byte {
|
||||
p.unreadByte()
|
||||
|
||||
if i := bytes.IndexByte(p.b[p.i:], ','); i >= 0 {
|
||||
b := p.b[p.i : p.i+i]
|
||||
p.i += i
|
||||
return b
|
||||
}
|
||||
|
||||
b := p.b[p.i : len(p.b)-1]
|
||||
p.i = len(p.b) - 1
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *arrayParser) readSubstring() ([]byte, error) {
|
||||
c, err := p.readByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.buf = p.buf[:0]
|
||||
for {
|
||||
if c == '"' {
|
||||
break
|
||||
}
|
||||
|
||||
next, err := p.readByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c == '\\' {
|
||||
switch next {
|
||||
case '\\', '"':
|
||||
p.buf = append(p.buf, next)
|
||||
|
||||
c, err = p.readByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
p.buf = append(p.buf, '\\')
|
||||
c = next
|
||||
}
|
||||
continue
|
||||
}
|
||||
if c == '\'' && next == '\'' {
|
||||
p.buf = append(p.buf, next)
|
||||
c, err = p.readByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
p.buf = append(p.buf, c)
|
||||
c = next
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(p.buf, []byte("\\x")) && len(p.buf)%2 == 0 {
|
||||
data := p.buf[2:]
|
||||
buf := make([]byte, hex.DecodedLen(len(data)))
|
||||
n, err := hex.Decode(buf, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
return p.buf, nil
|
||||
}
|
||||
|
|
301
vendor/github.com/uptrace/bun/dialect/pgdialect/array_scan.go
generated
vendored
301
vendor/github.com/uptrace/bun/dialect/pgdialect/array_scan.go
generated
vendored
|
@ -1,302 +1 @@
|
|||
package pgdialect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/uptrace/bun/internal"
|
||||
"github.com/uptrace/bun/schema"
|
||||
)
|
||||
|
||||
func arrayScanner(typ reflect.Type) schema.ScannerFunc {
|
||||
kind := typ.Kind()
|
||||
|
||||
switch kind {
|
||||
case reflect.Ptr:
|
||||
if fn := arrayScanner(typ.Elem()); fn != nil {
|
||||
return schema.PtrScanner(fn)
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
// ok:
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
elemType := typ.Elem()
|
||||
|
||||
if kind == reflect.Slice {
|
||||
switch elemType {
|
||||
case stringType:
|
||||
return scanStringSliceValue
|
||||
case intType:
|
||||
return scanIntSliceValue
|
||||
case int64Type:
|
||||
return scanInt64SliceValue
|
||||
case float64Type:
|
||||
return scanFloat64SliceValue
|
||||
}
|
||||
}
|
||||
|
||||
scanElem := schema.Scanner(elemType)
|
||||
return func(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
kind := dest.Kind()
|
||||
|
||||
if src == nil {
|
||||
if kind != reflect.Slice || !dest.IsNil() {
|
||||
dest.Set(reflect.Zero(dest.Type()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if kind == reflect.Slice {
|
||||
if dest.IsNil() {
|
||||
dest.Set(reflect.MakeSlice(dest.Type(), 0, 0))
|
||||
} else if dest.Len() > 0 {
|
||||
dest.Set(dest.Slice(0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := newArrayParser(b)
|
||||
nextValue := internal.MakeSliceNextElemFunc(dest)
|
||||
for {
|
||||
elem, err := p.NextElem()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
elemValue := nextValue()
|
||||
if err := scanElem(elemValue, elem); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func scanStringSliceValue(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
slice, err := decodeStringSlice(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest.Set(reflect.ValueOf(slice))
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeStringSlice(src interface{}) ([]string, error) {
|
||||
if src == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice := make([]string, 0)
|
||||
|
||||
p := newArrayParser(b)
|
||||
for {
|
||||
elem, err := p.NextElem()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
slice = append(slice, string(elem))
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func scanIntSliceValue(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
slice, err := decodeIntSlice(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest.Set(reflect.ValueOf(slice))
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeIntSlice(src interface{}) ([]int, error) {
|
||||
if src == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice := make([]int, 0)
|
||||
|
||||
p := newArrayParser(b)
|
||||
for {
|
||||
elem, err := p.NextElem()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if elem == nil {
|
||||
slice = append(slice, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
n, err := strconv.Atoi(bytesToString(elem))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice = append(slice, n)
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func scanInt64SliceValue(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
slice, err := decodeInt64Slice(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest.Set(reflect.ValueOf(slice))
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeInt64Slice(src interface{}) ([]int64, error) {
|
||||
if src == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice := make([]int64, 0)
|
||||
|
||||
p := newArrayParser(b)
|
||||
for {
|
||||
elem, err := p.NextElem()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if elem == nil {
|
||||
slice = append(slice, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
n, err := strconv.ParseInt(bytesToString(elem), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice = append(slice, n)
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func scanFloat64SliceValue(dest reflect.Value, src interface{}) error {
|
||||
dest = reflect.Indirect(dest)
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("bun: Scan(non-settable %s)", dest.Type())
|
||||
}
|
||||
|
||||
slice, err := scanFloat64Slice(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest.Set(reflect.ValueOf(slice))
|
||||
return nil
|
||||
}
|
||||
|
||||
func scanFloat64Slice(src interface{}) ([]float64, error) {
|
||||
if src == -1 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b, err := toBytes(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice := make([]float64, 0)
|
||||
|
||||
p := newArrayParser(b)
|
||||
for {
|
||||
elem, err := p.NextElem()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if elem == nil {
|
||||
slice = append(slice, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
n, err := strconv.ParseFloat(bytesToString(elem), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slice = append(slice, n)
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func toBytes(src interface{}) ([]byte, error) {
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return stringToBytes(src), nil
|
||||
case []byte:
|
||||
return src, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("bun: got %T, wanted []byte or string", src)
|
||||
}
|
||||
}
|
||||
|
|
10
vendor/github.com/uptrace/bun/dialect/pgdialect/dialect.go
generated
vendored
10
vendor/github.com/uptrace/bun/dialect/pgdialect/dialect.go
generated
vendored
|
@ -89,9 +89,17 @@ func (d *Dialect) onField(field *schema.Field) {
|
|||
if field.Tag.HasOption("array") || strings.HasSuffix(field.UserSQLType, "[]") {
|
||||
field.Append = d.arrayAppender(field.StructField.Type)
|
||||
field.Scan = arrayScanner(field.StructField.Type)
|
||||
return
|
||||
}
|
||||
|
||||
if field.DiscoveredSQLType == sqltype.HSTORE {
|
||||
if field.Tag.HasOption("multirange") {
|
||||
field.Append = d.arrayAppender(field.StructField.Type)
|
||||
field.Scan = arrayScanner(field.StructField.Type)
|
||||
return
|
||||
}
|
||||
|
||||
switch field.DiscoveredSQLType {
|
||||
case sqltype.HSTORE:
|
||||
field.Append = d.hstoreAppender(field.StructField.Type)
|
||||
field.Scan = hstoreScanner(field.StructField.Type)
|
||||
}
|
||||
|
|
172
vendor/github.com/uptrace/bun/dialect/pgdialect/hstore_parser.go
generated
vendored
172
vendor/github.com/uptrace/bun/dialect/pgdialect/hstore_parser.go
generated
vendored
|
@ -3,140 +3,98 @@
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type hstoreParser struct {
|
||||
*streamParser
|
||||
err error
|
||||
p pgparser
|
||||
|
||||
key string
|
||||
value string
|
||||
err error
|
||||
}
|
||||
|
||||
func newHStoreParser(b []byte) *hstoreParser {
|
||||
p := &hstoreParser{
|
||||
streamParser: newStreamParser(b, 0),
|
||||
}
|
||||
if len(b) < 6 || b[0] != '"' {
|
||||
p.err = fmt.Errorf("bun: can't parse hstore: %q", b)
|
||||
p := new(hstoreParser)
|
||||
if len(b) != 0 && (len(b) < 6 || b[0] != '"') {
|
||||
p.err = fmt.Errorf("pgdialect: can't parse hstore: %q", b)
|
||||
return p
|
||||
}
|
||||
p.p.Reset(b)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *hstoreParser) NextKey() (string, error) {
|
||||
func (p *hstoreParser) Next() bool {
|
||||
if p.err != nil {
|
||||
return "", p.err
|
||||
return false
|
||||
}
|
||||
|
||||
err := p.skipByte('"')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key, err := p.readSubstring()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
const separator = "=>"
|
||||
|
||||
for i := range separator {
|
||||
err = p.skipByte(separator[i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return string(key), nil
|
||||
p.err = p.readNext()
|
||||
return p.err == nil
|
||||
}
|
||||
|
||||
func (p *hstoreParser) NextValue() (string, error) {
|
||||
if p.err != nil {
|
||||
return "", p.err
|
||||
func (p *hstoreParser) Err() error {
|
||||
if p.err != io.EOF {
|
||||
return p.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *hstoreParser) Key() string {
|
||||
return p.key
|
||||
}
|
||||
|
||||
func (p *hstoreParser) Value() string {
|
||||
return p.value
|
||||
}
|
||||
|
||||
func (p *hstoreParser) readNext() error {
|
||||
if !p.p.Valid() {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
c, err := p.readByte()
|
||||
if err := p.p.Skip('"'); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := p.p.ReadUnescapedSubstring('"')
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
p.key = string(key)
|
||||
|
||||
if err := p.p.SkipPrefix([]byte("=>")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch c {
|
||||
ch, err := p.p.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch ch {
|
||||
case '"':
|
||||
value, err := p.readSubstring()
|
||||
value, err := p.p.ReadUnescapedSubstring(ch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
if p.peek() == ',' {
|
||||
p.skipNext()
|
||||
}
|
||||
|
||||
if p.peek() == ' ' {
|
||||
p.skipNext()
|
||||
}
|
||||
|
||||
return string(value), nil
|
||||
p.skipComma()
|
||||
p.value = string(value)
|
||||
return nil
|
||||
default:
|
||||
value := p.readSimple()
|
||||
value := p.p.ReadLiteral(ch)
|
||||
if bytes.Equal(value, []byte("NULL")) {
|
||||
value = nil
|
||||
p.value = ""
|
||||
}
|
||||
|
||||
if p.peek() == ',' {
|
||||
p.skipNext()
|
||||
}
|
||||
|
||||
return string(value), nil
|
||||
p.skipComma()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *hstoreParser) readSimple() []byte {
|
||||
p.unreadByte()
|
||||
|
||||
if i := bytes.IndexByte(p.b[p.i:], ','); i >= 0 {
|
||||
b := p.b[p.i : p.i+i]
|
||||
p.i += i
|
||||
return b
|
||||
func (p *hstoreParser) skipComma() {
|
||||
if p.p.Peek() == ',' {
|
||||
p.p.Advance()
|
||||
}
|
||||
if p.p.Peek() == ' ' {
|
||||
p.p.Advance()
|
||||
}
|
||||
|
||||
b := p.b[p.i:len(p.b)]
|
||||
p.i = len(p.b)
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *hstoreParser) readSubstring() ([]byte, error) {
|
||||
c, err := p.readByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.buf = p.buf[:0]
|
||||
for {
|
||||
if c == '"' {
|
||||
break
|
||||
}
|
||||
|
||||
next, err := p.readByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c == '\\' {
|
||||
switch next {
|
||||
case '\\', '"':
|
||||
p.buf = append(p.buf, next)
|
||||
|
||||
c, err = p.readByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
p.buf = append(p.buf, '\\')
|
||||
c = next
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
p.buf = append(p.buf, c)
|
||||
c = next
|
||||
}
|
||||
|
||||
return p.buf, nil
|
||||
}
|
||||
|
|
25
vendor/github.com/uptrace/bun/dialect/pgdialect/hstore_scan.go
generated
vendored
25
vendor/github.com/uptrace/bun/dialect/pgdialect/hstore_scan.go
generated
vendored
|
@ -2,7 +2,6 @@
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/uptrace/bun/schema"
|
||||
|
@ -58,25 +57,11 @@ func decodeMapStringString(src interface{}) (map[string]string, error) {
|
|||
m := make(map[string]string)
|
||||
|
||||
p := newHStoreParser(b)
|
||||
for {
|
||||
key, err := p.NextKey()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value, err := p.NextValue()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m[key] = value
|
||||
for p.Next() {
|
||||
m[p.Key()] = p.Value()
|
||||
}
|
||||
if err := p.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
|
240
vendor/github.com/uptrace/bun/dialect/pgdialect/range.go
generated
vendored
Normal file
240
vendor/github.com/uptrace/bun/dialect/pgdialect/range.go
generated
vendored
Normal file
|
@ -0,0 +1,240 @@
|
|||
package pgdialect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun/internal"
|
||||
"github.com/uptrace/bun/internal/parser"
|
||||
"github.com/uptrace/bun/schema"
|
||||
)
|
||||
|
||||
type MultiRange[T any] []Range[T]
|
||||
|
||||
type Range[T any] struct {
|
||||
Lower, Upper T
|
||||
LowerBound, UpperBound RangeBound
|
||||
}
|
||||
|
||||
type RangeBound byte
|
||||
|
||||
const (
|
||||
RangeBoundInclusiveLeft RangeBound = '['
|
||||
RangeBoundInclusiveRight RangeBound = ']'
|
||||
RangeBoundExclusiveLeft RangeBound = '('
|
||||
RangeBoundExclusiveRight RangeBound = ')'
|
||||
)
|
||||
|
||||
func NewRange[T any](lower, upper T) Range[T] {
|
||||
return Range[T]{
|
||||
Lower: lower,
|
||||
Upper: upper,
|
||||
LowerBound: RangeBoundInclusiveLeft,
|
||||
UpperBound: RangeBoundExclusiveRight,
|
||||
}
|
||||
}
|
||||
|
||||
var _ sql.Scanner = (*Range[any])(nil)
|
||||
|
||||
func (r *Range[T]) Scan(anySrc any) (err error) {
|
||||
src := anySrc.([]byte)
|
||||
|
||||
if len(src) == 0 {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
r.LowerBound = RangeBound(src[0])
|
||||
src = src[1:]
|
||||
|
||||
src, err = scanElem(&r.Lower, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(src) == 0 {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if ch := src[0]; ch != ',' {
|
||||
return fmt.Errorf("got %q, wanted %q", ch, ',')
|
||||
}
|
||||
src = src[1:]
|
||||
|
||||
src, err = scanElem(&r.Upper, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(src) == 0 {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
r.UpperBound = RangeBound(src[0])
|
||||
src = src[1:]
|
||||
|
||||
if len(src) > 0 {
|
||||
return fmt.Errorf("unread data: %q", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ schema.QueryAppender = (*Range[any])(nil)
|
||||
|
||||
func (r *Range[T]) AppendQuery(fmt schema.Formatter, buf []byte) ([]byte, error) {
|
||||
buf = append(buf, byte(r.LowerBound))
|
||||
buf = appendElem(buf, r.Lower)
|
||||
buf = append(buf, ',')
|
||||
buf = appendElem(buf, r.Upper)
|
||||
buf = append(buf, byte(r.UpperBound))
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func appendElem(buf []byte, val any) []byte {
|
||||
switch val := val.(type) {
|
||||
case time.Time:
|
||||
buf = append(buf, '"')
|
||||
buf = appendTime(buf, val)
|
||||
buf = append(buf, '"')
|
||||
return buf
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported range type: %T", val))
|
||||
}
|
||||
}
|
||||
|
||||
func scanElem(ptr any, src []byte) ([]byte, error) {
|
||||
switch ptr := ptr.(type) {
|
||||
case *time.Time:
|
||||
src, str, err := readStringLiteral(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tm, err := internal.ParseTime(internal.String(str))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*ptr = tm
|
||||
|
||||
return src, nil
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported range type: %T", ptr))
|
||||
}
|
||||
}
|
||||
|
||||
func readStringLiteral(src []byte) ([]byte, []byte, error) {
|
||||
p := newParser(src)
|
||||
|
||||
if err := p.Skip('"'); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
str, err := p.ReadSubstring('"')
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
src = p.Remaining()
|
||||
return src, str, nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type pgparser struct {
|
||||
parser.Parser
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newParser(b []byte) *pgparser {
|
||||
p := new(pgparser)
|
||||
p.Reset(b)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *pgparser) ReadLiteral(ch byte) []byte {
|
||||
p.Unread()
|
||||
lit, _ := p.ReadSep(',')
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *pgparser) ReadUnescapedSubstring(ch byte) ([]byte, error) {
|
||||
return p.readSubstring(ch, false)
|
||||
}
|
||||
|
||||
func (p *pgparser) ReadSubstring(ch byte) ([]byte, error) {
|
||||
return p.readSubstring(ch, true)
|
||||
}
|
||||
|
||||
func (p *pgparser) readSubstring(ch byte, escaped bool) ([]byte, error) {
|
||||
ch, err := p.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.buf = p.buf[:0]
|
||||
for {
|
||||
if ch == '"' {
|
||||
break
|
||||
}
|
||||
|
||||
next, err := p.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
switch next {
|
||||
case '\\', '"':
|
||||
p.buf = append(p.buf, next)
|
||||
|
||||
ch, err = p.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
p.buf = append(p.buf, '\\')
|
||||
ch = next
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if escaped && ch == '\'' && next == '\'' {
|
||||
p.buf = append(p.buf, next)
|
||||
ch, err = p.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
p.buf = append(p.buf, ch)
|
||||
ch = next
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(p.buf, []byte("\\x")) && len(p.buf)%2 == 0 {
|
||||
data := p.buf[2:]
|
||||
buf := make([]byte, hex.DecodedLen(len(data)))
|
||||
n, err := hex.Decode(buf, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
return p.buf, nil
|
||||
}
|
||||
|
||||
func (p *pgparser) ReadRange(ch byte) ([]byte, error) {
|
||||
p.buf = p.buf[:0]
|
||||
p.buf = append(p.buf, ch)
|
||||
|
||||
for p.Valid() {
|
||||
ch = p.Read()
|
||||
p.buf = append(p.buf, ch)
|
||||
if ch == ']' || ch == ')' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return p.buf, nil
|
||||
}
|
20
vendor/github.com/uptrace/bun/dialect/pgdialect/sqltype.go
generated
vendored
20
vendor/github.com/uptrace/bun/dialect/pgdialect/sqltype.go
generated
vendored
|
@ -1,6 +1,7 @@
|
|||
package pgdialect
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"reflect"
|
||||
|
@ -27,14 +28,6 @@
|
|||
pgTypeSerial = "SERIAL" // 4 byte autoincrementing integer
|
||||
pgTypeBigSerial = "BIGSERIAL" // 8 byte autoincrementing integer
|
||||
|
||||
// Character Types
|
||||
pgTypeChar = "CHAR" // fixed length string (blank padded)
|
||||
pgTypeText = "TEXT" // variable length string without limit
|
||||
|
||||
// JSON Types
|
||||
pgTypeJSON = "JSON" // text representation of json data
|
||||
pgTypeJSONB = "JSONB" // binary representation of json data
|
||||
|
||||
// Binary Data Types
|
||||
pgTypeBytea = "BYTEA" // binary string
|
||||
)
|
||||
|
@ -43,6 +36,7 @@
|
|||
ipType = reflect.TypeOf((*net.IP)(nil)).Elem()
|
||||
ipNetType = reflect.TypeOf((*net.IPNet)(nil)).Elem()
|
||||
jsonRawMessageType = reflect.TypeOf((*json.RawMessage)(nil)).Elem()
|
||||
nullStringType = reflect.TypeOf((*sql.NullString)(nil)).Elem()
|
||||
)
|
||||
|
||||
func (d *Dialect) DefaultVarcharLen() int {
|
||||
|
@ -78,12 +72,14 @@ func fieldSQLType(field *schema.Field) string {
|
|||
|
||||
func sqlType(typ reflect.Type) string {
|
||||
switch typ {
|
||||
case nullStringType: // typ.Kind() == reflect.Struct, test for exact match
|
||||
return sqltype.VarChar
|
||||
case ipType:
|
||||
return pgTypeInet
|
||||
case ipNetType:
|
||||
return pgTypeCidr
|
||||
case jsonRawMessageType:
|
||||
return pgTypeJSONB
|
||||
return sqltype.JSONB
|
||||
}
|
||||
|
||||
sqlType := schema.DiscoverSQLType(typ)
|
||||
|
@ -93,16 +89,16 @@ func sqlType(typ reflect.Type) string {
|
|||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Map, reflect.Struct:
|
||||
case reflect.Map, reflect.Struct: // except typ == nullStringType, see above
|
||||
if sqlType == sqltype.VarChar {
|
||||
return pgTypeJSONB
|
||||
return sqltype.JSONB
|
||||
}
|
||||
return sqlType
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
return pgTypeBytea
|
||||
}
|
||||
return pgTypeJSONB
|
||||
return sqltype.JSONB
|
||||
}
|
||||
|
||||
return sqlType
|
||||
|
|
60
vendor/github.com/uptrace/bun/dialect/pgdialect/stream_parser.go
generated
vendored
60
vendor/github.com/uptrace/bun/dialect/pgdialect/stream_parser.go
generated
vendored
|
@ -1,60 +0,0 @@
|
|||
package pgdialect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type streamParser struct {
|
||||
b []byte
|
||||
i int
|
||||
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newStreamParser(b []byte, start int) *streamParser {
|
||||
return &streamParser{
|
||||
b: b,
|
||||
i: start,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamParser) valid() bool {
|
||||
return p.i < len(p.b)
|
||||
}
|
||||
|
||||
func (p *streamParser) skipByte(skip byte) error {
|
||||
c, err := p.readByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c == skip {
|
||||
return nil
|
||||
}
|
||||
p.unreadByte()
|
||||
return fmt.Errorf("got %q, wanted %q", c, skip)
|
||||
}
|
||||
|
||||
func (p *streamParser) readByte() (byte, error) {
|
||||
if p.valid() {
|
||||
c := p.b[p.i]
|
||||
p.i++
|
||||
return c, nil
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (p *streamParser) unreadByte() {
|
||||
p.i--
|
||||
}
|
||||
|
||||
func (p *streamParser) peek() byte {
|
||||
if p.valid() {
|
||||
return p.b[p.i]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *streamParser) skipNext() {
|
||||
p.i++
|
||||
}
|
2
vendor/github.com/uptrace/bun/dialect/pgdialect/version.go
generated
vendored
2
vendor/github.com/uptrace/bun/dialect/pgdialect/version.go
generated
vendored
|
@ -2,5 +2,5 @@
|
|||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "1.2.1"
|
||||
return "1.2.5"
|
||||
}
|
||||
|
|
2
vendor/github.com/uptrace/bun/dialect/sqlitedialect/version.go
generated
vendored
2
vendor/github.com/uptrace/bun/dialect/sqlitedialect/version.go
generated
vendored
|
@ -2,5 +2,5 @@
|
|||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "1.2.1"
|
||||
return "1.2.5"
|
||||
}
|
||||
|
|
2
vendor/github.com/uptrace/bun/extra/bunotel/README.md
generated
vendored
2
vendor/github.com/uptrace/bun/extra/bunotel/README.md
generated
vendored
|
@ -1,3 +1,3 @@
|
|||
# OpenTelemetry instrumentation for Bun
|
||||
|
||||
See [example](../example/opentelemetry) for details.
|
||||
See [example](../../example/opentelemetry) for details.
|
||||
|
|
16
vendor/github.com/uptrace/bun/extra/bunotel/unsafe.go
generated
vendored
16
vendor/github.com/uptrace/bun/extra/bunotel/unsafe.go
generated
vendored
|
@ -1,3 +1,4 @@
|
|||
//go:build !appengine
|
||||
// +build !appengine
|
||||
|
||||
package bunotel
|
||||
|
@ -5,14 +6,15 @@
|
|||
import "unsafe"
|
||||
|
||||
func bytesToString(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
return unsafe.String(&b[0], len(b))
|
||||
}
|
||||
|
||||
func stringToBytes(s string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(
|
||||
&struct {
|
||||
string
|
||||
Cap int
|
||||
}{s, len(s)},
|
||||
))
|
||||
if s == "" {
|
||||
return []byte{}
|
||||
}
|
||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||
}
|
||||
|
|
50
vendor/github.com/uptrace/bun/internal/parser/parser.go
generated
vendored
50
vendor/github.com/uptrace/bun/internal/parser/parser.go
generated
vendored
|
@ -2,6 +2,8 @@
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/uptrace/bun/internal"
|
||||
|
@ -22,23 +24,43 @@ func NewString(s string) *Parser {
|
|||
return New(internal.Bytes(s))
|
||||
}
|
||||
|
||||
func (p *Parser) Reset(b []byte) {
|
||||
p.b = b
|
||||
p.i = 0
|
||||
}
|
||||
|
||||
func (p *Parser) Valid() bool {
|
||||
return p.i < len(p.b)
|
||||
}
|
||||
|
||||
func (p *Parser) Bytes() []byte {
|
||||
func (p *Parser) Remaining() []byte {
|
||||
return p.b[p.i:]
|
||||
}
|
||||
|
||||
func (p *Parser) ReadByte() (byte, error) {
|
||||
if p.Valid() {
|
||||
ch := p.b[p.i]
|
||||
p.Advance()
|
||||
return ch, nil
|
||||
}
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
func (p *Parser) Read() byte {
|
||||
if p.Valid() {
|
||||
c := p.b[p.i]
|
||||
ch := p.b[p.i]
|
||||
p.Advance()
|
||||
return c
|
||||
return ch
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *Parser) Unread() {
|
||||
if p.i > 0 {
|
||||
p.i--
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) Peek() byte {
|
||||
if p.Valid() {
|
||||
return p.b[p.i]
|
||||
|
@ -50,19 +72,25 @@ func (p *Parser) Advance() {
|
|||
p.i++
|
||||
}
|
||||
|
||||
func (p *Parser) Skip(skip byte) bool {
|
||||
if p.Peek() == skip {
|
||||
func (p *Parser) Skip(skip byte) error {
|
||||
ch := p.Peek()
|
||||
if ch == skip {
|
||||
p.Advance()
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
return false
|
||||
return fmt.Errorf("got %q, wanted %q", ch, skip)
|
||||
}
|
||||
|
||||
func (p *Parser) SkipBytes(skip []byte) bool {
|
||||
if len(skip) > len(p.b[p.i:]) {
|
||||
return false
|
||||
func (p *Parser) SkipPrefix(skip []byte) error {
|
||||
if !bytes.HasPrefix(p.b[p.i:], skip) {
|
||||
return fmt.Errorf("got %q, wanted prefix %q", p.b, skip)
|
||||
}
|
||||
if !bytes.Equal(p.b[p.i:p.i+len(skip)], skip) {
|
||||
p.i += len(skip)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) CutPrefix(skip []byte) bool {
|
||||
if !bytes.HasPrefix(p.b[p.i:], skip) {
|
||||
return false
|
||||
}
|
||||
p.i += len(skip)
|
||||
|
|
16
vendor/github.com/uptrace/bun/internal/unsafe.go
generated
vendored
16
vendor/github.com/uptrace/bun/internal/unsafe.go
generated
vendored
|
@ -1,3 +1,4 @@
|
|||
//go:build !appengine
|
||||
// +build !appengine
|
||||
|
||||
package internal
|
||||
|
@ -6,15 +7,16 @@
|
|||
|
||||
// String converts byte slice to string.
|
||||
func String(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
return unsafe.String(&b[0], len(b))
|
||||
}
|
||||
|
||||
// Bytes converts string to byte slice.
|
||||
func Bytes(s string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(
|
||||
&struct {
|
||||
string
|
||||
Cap int
|
||||
}{s, len(s)},
|
||||
))
|
||||
if s == "" {
|
||||
return []byte{}
|
||||
}
|
||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||
}
|
||||
|
|
4
vendor/github.com/uptrace/bun/migrate/migrations.go
generated
vendored
4
vendor/github.com/uptrace/bun/migrate/migrations.go
generated
vendored
|
@ -96,10 +96,6 @@ func (m *Migrations) Discover(fsys fs.FS) error {
|
|||
}
|
||||
|
||||
migration := m.getOrCreateMigration(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migration.Comment = comment
|
||||
migrationFunc := NewSQLMigrationFunc(fsys, path)
|
||||
|
||||
|
|
5
vendor/github.com/uptrace/bun/migrate/migrator.go
generated
vendored
5
vendor/github.com/uptrace/bun/migrate/migrator.go
generated
vendored
|
@ -362,7 +362,10 @@ func (m *Migrator) MarkUnapplied(ctx context.Context, migration *Migration) erro
|
|||
}
|
||||
|
||||
func (m *Migrator) TruncateTable(ctx context.Context) error {
|
||||
_, err := m.db.NewTruncateTable().TableExpr(m.table).Exec(ctx)
|
||||
_, err := m.db.NewTruncateTable().
|
||||
Model((*Migration)(nil)).
|
||||
ModelTableExpr(m.table).
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
3
vendor/github.com/uptrace/bun/model_map.go
generated
vendored
3
vendor/github.com/uptrace/bun/model_map.go
generated
vendored
|
@ -1,6 +1,7 @@
|
|||
package bun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"reflect"
|
||||
|
@ -82,6 +83,8 @@ func (m *mapModel) Scan(src interface{}) error {
|
|||
return m.scanRaw(src)
|
||||
case reflect.Slice:
|
||||
if scanType.Elem().Kind() == reflect.Uint8 {
|
||||
// Reference types such as []byte are only valid until the next call to Scan.
|
||||
src := bytes.Clone(src.([]byte))
|
||||
return m.scanRaw(src)
|
||||
}
|
||||
}
|
||||
|
|
21
vendor/github.com/uptrace/bun/model_table_has_many.go
generated
vendored
21
vendor/github.com/uptrace/bun/model_table_has_many.go
generated
vendored
|
@ -24,7 +24,7 @@ type hasManyModel struct {
|
|||
func newHasManyModel(j *relationJoin) *hasManyModel {
|
||||
baseTable := j.BaseModel.Table()
|
||||
joinModel := j.JoinModel.(*sliceTableModel)
|
||||
baseValues := baseValues(joinModel, j.Relation.BaseFields)
|
||||
baseValues := baseValues(joinModel, j.Relation.BasePKs)
|
||||
if len(baseValues) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -92,9 +92,9 @@ func (m *hasManyModel) Scan(src interface{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
for _, f := range m.rel.JoinFields {
|
||||
for _, f := range m.rel.JoinPKs {
|
||||
if f.Name == field.Name {
|
||||
m.structKey = append(m.structKey, field.Value(m.strct).Interface())
|
||||
m.structKey = append(m.structKey, indirectFieldValue(field.Value(m.strct)))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ func (m *hasManyModel) Scan(src interface{}) error {
|
|||
}
|
||||
|
||||
func (m *hasManyModel) parkStruct() error {
|
||||
|
||||
baseValues, ok := m.baseValues[internal.NewMapKey(m.structKey)]
|
||||
if !ok {
|
||||
return fmt.Errorf(
|
||||
|
@ -143,7 +144,19 @@ func baseValues(model TableModel, fields []*schema.Field) map[internal.MapKey][]
|
|||
|
||||
func modelKey(key []interface{}, strct reflect.Value, fields []*schema.Field) []interface{} {
|
||||
for _, f := range fields {
|
||||
key = append(key, f.Value(strct).Interface())
|
||||
key = append(key, indirectFieldValue(f.Value(strct)))
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// indirectFieldValue return the field value dereferencing the pointer if necessary.
|
||||
// The value is then used as a map key.
|
||||
func indirectFieldValue(field reflect.Value) interface{} {
|
||||
if field.Kind() != reflect.Ptr {
|
||||
return field.Interface()
|
||||
}
|
||||
if field.IsNil() {
|
||||
return nil
|
||||
}
|
||||
return field.Elem().Interface()
|
||||
}
|
||||
|
|
22
vendor/github.com/uptrace/bun/model_table_m2m.go
generated
vendored
22
vendor/github.com/uptrace/bun/model_table_m2m.go
generated
vendored
|
@ -24,7 +24,7 @@ type m2mModel struct {
|
|||
func newM2MModel(j *relationJoin) *m2mModel {
|
||||
baseTable := j.BaseModel.Table()
|
||||
joinModel := j.JoinModel.(*sliceTableModel)
|
||||
baseValues := baseValues(joinModel, baseTable.PKs)
|
||||
baseValues := baseValues(joinModel, j.Relation.BasePKs)
|
||||
if len(baseValues) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -83,27 +83,21 @@ func (m *m2mModel) Scan(src interface{}) error {
|
|||
column := m.columns[m.scanIndex]
|
||||
m.scanIndex++
|
||||
|
||||
field, ok := m.table.FieldMap[column]
|
||||
if !ok {
|
||||
// Base pks must come first.
|
||||
if m.scanIndex <= len(m.rel.M2MBasePKs) {
|
||||
return m.scanM2MColumn(column, src)
|
||||
}
|
||||
|
||||
if err := field.ScanValue(m.strct, src); err != nil {
|
||||
return err
|
||||
if field, ok := m.table.FieldMap[column]; ok {
|
||||
return field.ScanValue(m.strct, src)
|
||||
}
|
||||
|
||||
for _, fk := range m.rel.M2MBaseFields {
|
||||
if fk.Name == field.Name {
|
||||
m.structKey = append(m.structKey, field.Value(m.strct).Interface())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
_, err := m.scanColumn(column, src)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *m2mModel) scanM2MColumn(column string, src interface{}) error {
|
||||
for _, field := range m.rel.M2MBaseFields {
|
||||
for _, field := range m.rel.M2MBasePKs {
|
||||
if field.Name == column {
|
||||
dest := reflect.New(field.IndirectType).Elem()
|
||||
if err := field.Scan(dest, src); err != nil {
|
||||
|
|
2
vendor/github.com/uptrace/bun/model_table_struct.go
generated
vendored
2
vendor/github.com/uptrace/bun/model_table_struct.go
generated
vendored
|
@ -242,7 +242,7 @@ func (m *structTableModel) ScanRows(ctx context.Context, rows *sql.Rows) (int, e
|
|||
n++
|
||||
|
||||
// And discard the rest. This is especially important for SQLite3, which can return
|
||||
// a row like it was inserted sucessfully and then return an actual error for the next row.
|
||||
// a row like it was inserted successfully and then return an actual error for the next row.
|
||||
// See issues/100.
|
||||
for rows.Next() {
|
||||
n++
|
||||
|
|
2
vendor/github.com/uptrace/bun/package.json
generated
vendored
2
vendor/github.com/uptrace/bun/package.json
generated
vendored
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "gobun",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.5",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:uptrace/bun.git",
|
||||
"author": "Vladimir Mihailenco <vladimir.webdev@gmail.com>",
|
||||
|
|
7
vendor/github.com/uptrace/bun/query_base.go
generated
vendored
7
vendor/github.com/uptrace/bun/query_base.go
generated
vendored
|
@ -8,6 +8,7 @@
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/dialect/feature"
|
||||
"github.com/uptrace/bun/internal"
|
||||
"github.com/uptrace/bun/schema"
|
||||
|
@ -418,7 +419,11 @@ func (q *baseQuery) _appendTables(
|
|||
} else {
|
||||
b = fmter.AppendQuery(b, string(q.table.SQLNameForSelects))
|
||||
if withAlias && q.table.SQLAlias != q.table.SQLNameForSelects {
|
||||
b = append(b, " AS "...)
|
||||
if q.db.dialect.Name() == dialect.Oracle {
|
||||
b = append(b, ' ')
|
||||
} else {
|
||||
b = append(b, " AS "...)
|
||||
}
|
||||
b = append(b, q.table.SQLAlias...)
|
||||
}
|
||||
}
|
||||
|
|
48
vendor/github.com/uptrace/bun/query_select.go
generated
vendored
48
vendor/github.com/uptrace/bun/query_select.go
generated
vendored
|
@ -538,6 +538,11 @@ func (q *SelectQuery) appendQuery(
|
|||
if count && !cteCount {
|
||||
b = append(b, "count(*)"...)
|
||||
} else {
|
||||
// MSSQL: allows Limit() without Order() as per https://stackoverflow.com/a/36156953
|
||||
if q.limit > 0 && len(q.order) == 0 && fmter.Dialect().Name() == dialect.MSSQL {
|
||||
b = append(b, "0 AS _temp_sort, "...)
|
||||
}
|
||||
|
||||
b, err = q.appendColumns(fmter, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -564,8 +569,8 @@ func (q *SelectQuery) appendQuery(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
for _, j := range q.joins {
|
||||
b, err = j.AppendQuery(fmter, b)
|
||||
for _, join := range q.joins {
|
||||
b, err = join.AppendQuery(fmter, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -793,6 +798,12 @@ func (q *SelectQuery) appendOrder(fmter schema.Formatter, b []byte) (_ []byte, e
|
|||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// MSSQL: allows Limit() without Order() as per https://stackoverflow.com/a/36156953
|
||||
if q.limit > 0 && fmter.Dialect().Name() == dialect.MSSQL {
|
||||
return append(b, " ORDER BY _temp_sort"...), nil
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
|
@ -856,52 +867,57 @@ func (q *SelectQuery) Exec(ctx context.Context, dest ...interface{}) (res sql.Re
|
|||
}
|
||||
|
||||
func (q *SelectQuery) Scan(ctx context.Context, dest ...interface{}) error {
|
||||
_, err := q.scanResult(ctx, dest...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *SelectQuery) scanResult(ctx context.Context, dest ...interface{}) (sql.Result, error) {
|
||||
if q.err != nil {
|
||||
return q.err
|
||||
return nil, q.err
|
||||
}
|
||||
|
||||
model, err := q.getModel(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if q.table != nil {
|
||||
if err := q.beforeSelectHook(ctx); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := q.beforeAppendModel(ctx, q); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queryBytes, err := q.AppendQuery(q.db.fmter, q.db.makeQueryBytes())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := internal.String(queryBytes)
|
||||
|
||||
res, err := q.scan(ctx, q, query, model, true)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n, _ := res.RowsAffected(); n > 0 {
|
||||
if tableModel, ok := model.(TableModel); ok {
|
||||
if err := q.selectJoins(ctx, tableModel.getJoins()); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if q.table != nil {
|
||||
if err := q.afterSelectHook(ctx); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (q *SelectQuery) beforeSelectHook(ctx context.Context) error {
|
||||
|
@ -946,6 +962,16 @@ func (q *SelectQuery) Count(ctx context.Context) (int, error) {
|
|||
}
|
||||
|
||||
func (q *SelectQuery) ScanAndCount(ctx context.Context, dest ...interface{}) (int, error) {
|
||||
if q.offset == 0 && q.limit == 0 {
|
||||
// If there is no limit and offset, we can use a single query to get the count and scan
|
||||
if res, err := q.scanResult(ctx, dest...); err != nil {
|
||||
return 0, err
|
||||
} else if n, err := res.RowsAffected(); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return int(n), nil
|
||||
}
|
||||
}
|
||||
if _, ok := q.conn.(*DB); ok {
|
||||
return q.scanAndCountConc(ctx, dest...)
|
||||
}
|
||||
|
|
13
vendor/github.com/uptrace/bun/query_table_create.go
generated
vendored
13
vendor/github.com/uptrace/bun/query_table_create.go
generated
vendored
|
@ -9,6 +9,7 @@
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/dialect/feature"
|
||||
"github.com/uptrace/bun/dialect/sqltype"
|
||||
"github.com/uptrace/bun/internal"
|
||||
|
@ -165,7 +166,7 @@ func (q *CreateTableQuery) AppendQuery(fmter schema.Formatter, b []byte) (_ []by
|
|||
b = append(b, field.SQLName...)
|
||||
b = append(b, " "...)
|
||||
b = q.appendSQLType(b, field)
|
||||
if field.NotNull {
|
||||
if field.NotNull && q.db.dialect.Name() != dialect.Oracle {
|
||||
b = append(b, " NOT NULL"...)
|
||||
}
|
||||
|
||||
|
@ -246,7 +247,11 @@ func (q *CreateTableQuery) appendSQLType(b []byte, field *schema.Field) []byte {
|
|||
return append(b, field.CreateTableSQLType...)
|
||||
}
|
||||
|
||||
b = append(b, sqltype.VarChar...)
|
||||
if q.db.dialect.Name() == dialect.Oracle {
|
||||
b = append(b, "VARCHAR2"...)
|
||||
} else {
|
||||
b = append(b, sqltype.VarChar...)
|
||||
}
|
||||
b = append(b, "("...)
|
||||
b = strconv.AppendInt(b, int64(q.varchar), 10)
|
||||
b = append(b, ")"...)
|
||||
|
@ -297,9 +302,9 @@ func (q *CreateTableQuery) appendFKConstraintsRel(fmter schema.Formatter, b []by
|
|||
b, err = q.appendFK(fmter, b, schema.QueryWithArgs{
|
||||
Query: "(?) REFERENCES ? (?) ? ?",
|
||||
Args: []interface{}{
|
||||
Safe(appendColumns(nil, "", rel.BaseFields)),
|
||||
Safe(appendColumns(nil, "", rel.BasePKs)),
|
||||
rel.JoinTable.SQLName,
|
||||
Safe(appendColumns(nil, "", rel.JoinFields)),
|
||||
Safe(appendColumns(nil, "", rel.JoinPKs)),
|
||||
Safe(rel.OnUpdate),
|
||||
Safe(rel.OnDelete),
|
||||
},
|
||||
|
|
5
vendor/github.com/uptrace/bun/query_table_truncate.go
generated
vendored
5
vendor/github.com/uptrace/bun/query_table_truncate.go
generated
vendored
|
@ -57,6 +57,11 @@ func (q *TruncateTableQuery) TableExpr(query string, args ...interface{}) *Trunc
|
|||
return q
|
||||
}
|
||||
|
||||
func (q *TruncateTableQuery) ModelTableExpr(query string, args ...interface{}) *TruncateTableQuery {
|
||||
q.modelTableName = schema.SafeQuery(query, args)
|
||||
return q
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (q *TruncateTableQuery) ContinueIdentity() *TruncateTableQuery {
|
||||
|
|
32
vendor/github.com/uptrace/bun/relation_join.go
generated
vendored
32
vendor/github.com/uptrace/bun/relation_join.go
generated
vendored
|
@ -70,11 +70,11 @@ func (j *relationJoin) manyQuery(q *SelectQuery) *SelectQuery {
|
|||
}
|
||||
|
||||
func (j *relationJoin) manyQueryCompositeIn(where []byte, q *SelectQuery) *SelectQuery {
|
||||
if len(j.Relation.JoinFields) > 1 {
|
||||
if len(j.Relation.JoinPKs) > 1 {
|
||||
where = append(where, '(')
|
||||
}
|
||||
where = appendColumns(where, j.JoinModel.Table().SQLAlias, j.Relation.JoinFields)
|
||||
if len(j.Relation.JoinFields) > 1 {
|
||||
where = appendColumns(where, j.JoinModel.Table().SQLAlias, j.Relation.JoinPKs)
|
||||
if len(j.Relation.JoinPKs) > 1 {
|
||||
where = append(where, ')')
|
||||
}
|
||||
where = append(where, " IN ("...)
|
||||
|
@ -83,7 +83,7 @@ func (j *relationJoin) manyQueryCompositeIn(where []byte, q *SelectQuery) *Selec
|
|||
where,
|
||||
j.JoinModel.rootValue(),
|
||||
j.JoinModel.parentIndex(),
|
||||
j.Relation.BaseFields,
|
||||
j.Relation.BasePKs,
|
||||
)
|
||||
where = append(where, ")"...)
|
||||
q = q.Where(internal.String(where))
|
||||
|
@ -104,8 +104,8 @@ func (j *relationJoin) manyQueryMulti(where []byte, q *SelectQuery) *SelectQuery
|
|||
where,
|
||||
j.JoinModel.rootValue(),
|
||||
j.JoinModel.parentIndex(),
|
||||
j.Relation.BaseFields,
|
||||
j.Relation.JoinFields,
|
||||
j.Relation.BasePKs,
|
||||
j.Relation.JoinPKs,
|
||||
j.JoinModel.Table().SQLAlias,
|
||||
)
|
||||
|
||||
|
@ -175,10 +175,10 @@ func (j *relationJoin) m2mQuery(q *SelectQuery) *SelectQuery {
|
|||
q = q.Model(m2mModel)
|
||||
|
||||
index := j.JoinModel.parentIndex()
|
||||
baseTable := j.BaseModel.Table()
|
||||
|
||||
if j.Relation.M2MTable != nil {
|
||||
fields := append(j.Relation.M2MBaseFields, j.Relation.M2MJoinFields...)
|
||||
// We only need base pks to park joined models to the base model.
|
||||
fields := j.Relation.M2MBasePKs
|
||||
|
||||
b := make([]byte, 0, len(fields))
|
||||
b = appendColumns(b, j.Relation.M2MTable.SQLAlias, fields)
|
||||
|
@ -193,7 +193,7 @@ func (j *relationJoin) m2mQuery(q *SelectQuery) *SelectQuery {
|
|||
join = append(join, " AS "...)
|
||||
join = append(join, j.Relation.M2MTable.SQLAlias...)
|
||||
join = append(join, " ON ("...)
|
||||
for i, col := range j.Relation.M2MBaseFields {
|
||||
for i, col := range j.Relation.M2MBasePKs {
|
||||
if i > 0 {
|
||||
join = append(join, ", "...)
|
||||
}
|
||||
|
@ -202,13 +202,13 @@ func (j *relationJoin) m2mQuery(q *SelectQuery) *SelectQuery {
|
|||
join = append(join, col.SQLName...)
|
||||
}
|
||||
join = append(join, ") IN ("...)
|
||||
join = appendChildValues(fmter, join, j.BaseModel.rootValue(), index, baseTable.PKs)
|
||||
join = appendChildValues(fmter, join, j.BaseModel.rootValue(), index, j.Relation.BasePKs)
|
||||
join = append(join, ")"...)
|
||||
q = q.Join(internal.String(join))
|
||||
|
||||
joinTable := j.JoinModel.Table()
|
||||
for i, m2mJoinField := range j.Relation.M2MJoinFields {
|
||||
joinField := j.Relation.JoinFields[i]
|
||||
for i, m2mJoinField := range j.Relation.M2MJoinPKs {
|
||||
joinField := j.Relation.JoinPKs[i]
|
||||
q = q.Where("?.? = ?.?",
|
||||
joinTable.SQLAlias, joinField.SQLName,
|
||||
j.Relation.M2MTable.SQLAlias, m2mJoinField.SQLName)
|
||||
|
@ -310,13 +310,13 @@ func (j *relationJoin) appendHasOneJoin(
|
|||
b = append(b, " ON "...)
|
||||
|
||||
b = append(b, '(')
|
||||
for i, baseField := range j.Relation.BaseFields {
|
||||
for i, baseField := range j.Relation.BasePKs {
|
||||
if i > 0 {
|
||||
b = append(b, " AND "...)
|
||||
}
|
||||
b = j.appendAlias(fmter, b)
|
||||
b = append(b, '.')
|
||||
b = append(b, j.Relation.JoinFields[i].SQLName...)
|
||||
b = append(b, j.Relation.JoinPKs[i].SQLName...)
|
||||
b = append(b, " = "...)
|
||||
b = j.appendBaseAlias(fmter, b)
|
||||
b = append(b, '.')
|
||||
|
@ -367,13 +367,13 @@ func appendChildValues(
|
|||
}
|
||||
|
||||
// appendMultiValues is an alternative to appendChildValues that doesn't use the sql keyword ID
|
||||
// but instead use a old style ((k1=v1) AND (k2=v2)) OR (...) of conditions.
|
||||
// but instead uses old style ((k1=v1) AND (k2=v2)) OR (...) conditions.
|
||||
func appendMultiValues(
|
||||
fmter schema.Formatter, b []byte, v reflect.Value, index []int, baseFields, joinFields []*schema.Field, joinTable schema.Safe,
|
||||
) []byte {
|
||||
// This is based on a mix of appendChildValues and query_base.appendColumns
|
||||
|
||||
// These should never missmatch in length but nice to know if it does
|
||||
// These should never mismatch in length but nice to know if it does
|
||||
if len(joinFields) != len(baseFields) {
|
||||
panic("not reached")
|
||||
}
|
||||
|
|
29
vendor/github.com/uptrace/bun/schema/append_value.go
generated
vendored
29
vendor/github.com/uptrace/bun/schema/append_value.go
generated
vendored
|
@ -7,9 +7,9 @@
|
|||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/dialect/sqltype"
|
||||
"github.com/uptrace/bun/extra/bunjson"
|
||||
|
@ -51,7 +51,7 @@
|
|||
reflect.UnsafePointer: nil,
|
||||
}
|
||||
|
||||
var appenderMap sync.Map
|
||||
var appenderCache = xsync.NewMapOf[reflect.Type, AppenderFunc]()
|
||||
|
||||
func FieldAppender(dialect Dialect, field *Field) AppenderFunc {
|
||||
if field.Tag.HasOption("msgpack") {
|
||||
|
@ -67,7 +67,7 @@ func FieldAppender(dialect Dialect, field *Field) AppenderFunc {
|
|||
}
|
||||
|
||||
if fieldType.Kind() != reflect.Ptr {
|
||||
if reflect.PtrTo(fieldType).Implements(driverValuerType) {
|
||||
if reflect.PointerTo(fieldType).Implements(driverValuerType) {
|
||||
return addrAppender(appendDriverValue)
|
||||
}
|
||||
}
|
||||
|
@ -79,14 +79,14 @@ func FieldAppender(dialect Dialect, field *Field) AppenderFunc {
|
|||
}
|
||||
|
||||
func Appender(dialect Dialect, typ reflect.Type) AppenderFunc {
|
||||
if v, ok := appenderMap.Load(typ); ok {
|
||||
return v.(AppenderFunc)
|
||||
if v, ok := appenderCache.Load(typ); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
fn := appender(dialect, typ)
|
||||
|
||||
if v, ok := appenderMap.LoadOrStore(typ, fn); ok {
|
||||
return v.(AppenderFunc)
|
||||
if v, ok := appenderCache.LoadOrStore(typ, fn); ok {
|
||||
return v
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
@ -99,10 +99,10 @@ func appender(dialect Dialect, typ reflect.Type) AppenderFunc {
|
|||
return appendTimeValue
|
||||
case timePtrType:
|
||||
return PtrAppender(appendTimeValue)
|
||||
case ipType:
|
||||
return appendIPValue
|
||||
case ipNetType:
|
||||
return appendIPNetValue
|
||||
case ipType, netipPrefixType, netipAddrType:
|
||||
return appendStringer
|
||||
case jsonRawMessageType:
|
||||
return appendJSONRawMessageValue
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ func appender(dialect Dialect, typ reflect.Type) AppenderFunc {
|
|||
}
|
||||
|
||||
if kind != reflect.Ptr {
|
||||
ptr := reflect.PtrTo(typ)
|
||||
ptr := reflect.PointerTo(typ)
|
||||
if ptr.Implements(queryAppenderType) {
|
||||
return addrAppender(appendQueryAppenderValue)
|
||||
}
|
||||
|
@ -247,16 +247,15 @@ func appendTimeValue(fmter Formatter, b []byte, v reflect.Value) []byte {
|
|||
return fmter.Dialect().AppendTime(b, tm)
|
||||
}
|
||||
|
||||
func appendIPValue(fmter Formatter, b []byte, v reflect.Value) []byte {
|
||||
ip := v.Interface().(net.IP)
|
||||
return fmter.Dialect().AppendString(b, ip.String())
|
||||
}
|
||||
|
||||
func appendIPNetValue(fmter Formatter, b []byte, v reflect.Value) []byte {
|
||||
ipnet := v.Interface().(net.IPNet)
|
||||
return fmter.Dialect().AppendString(b, ipnet.String())
|
||||
}
|
||||
|
||||
func appendStringer(fmter Formatter, b []byte, v reflect.Value) []byte {
|
||||
return fmter.Dialect().AppendString(b, v.Interface().(fmt.Stringer).String())
|
||||
}
|
||||
|
||||
func appendJSONRawMessageValue(fmter Formatter, b []byte, v reflect.Value) []byte {
|
||||
bytes := v.Bytes()
|
||||
if bytes == nil {
|
||||
|
|
2
vendor/github.com/uptrace/bun/schema/dialect.go
generated
vendored
2
vendor/github.com/uptrace/bun/schema/dialect.go
generated
vendored
|
@ -118,7 +118,7 @@ func (BaseDialect) AppendJSON(b, jsonb []byte) []byte {
|
|||
case '\000':
|
||||
continue
|
||||
case '\\':
|
||||
if p.SkipBytes([]byte("u0000")) {
|
||||
if p.CutPrefix([]byte("u0000")) {
|
||||
b = append(b, `\\u0000`...)
|
||||
} else {
|
||||
b = append(b, '\\')
|
||||
|
|
3
vendor/github.com/uptrace/bun/schema/reflect.go
generated
vendored
3
vendor/github.com/uptrace/bun/schema/reflect.go
generated
vendored
|
@ -4,6 +4,7 @@
|
|||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
@ -14,6 +15,8 @@
|
|||
timeType = timePtrType.Elem()
|
||||
ipType = reflect.TypeOf((*net.IP)(nil)).Elem()
|
||||
ipNetType = reflect.TypeOf((*net.IPNet)(nil)).Elem()
|
||||
netipPrefixType = reflect.TypeOf((*netip.Prefix)(nil)).Elem()
|
||||
netipAddrType = reflect.TypeOf((*netip.Addr)(nil)).Elem()
|
||||
jsonRawMessageType = reflect.TypeOf((*json.RawMessage)(nil)).Elem()
|
||||
|
||||
driverValuerType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||
|
|
26
vendor/github.com/uptrace/bun/schema/relation.go
generated
vendored
26
vendor/github.com/uptrace/bun/schema/relation.go
generated
vendored
|
@ -13,21 +13,25 @@
|
|||
)
|
||||
|
||||
type Relation struct {
|
||||
Type int
|
||||
Field *Field
|
||||
JoinTable *Table
|
||||
BaseFields []*Field
|
||||
JoinFields []*Field
|
||||
OnUpdate string
|
||||
OnDelete string
|
||||
Condition []string
|
||||
// Base and Join can be explained with this query:
|
||||
//
|
||||
// SELECT * FROM base_table JOIN join_table
|
||||
|
||||
Type int
|
||||
Field *Field
|
||||
JoinTable *Table
|
||||
BasePKs []*Field
|
||||
JoinPKs []*Field
|
||||
OnUpdate string
|
||||
OnDelete string
|
||||
Condition []string
|
||||
|
||||
PolymorphicField *Field
|
||||
PolymorphicValue string
|
||||
|
||||
M2MTable *Table
|
||||
M2MBaseFields []*Field
|
||||
M2MJoinFields []*Field
|
||||
M2MTable *Table
|
||||
M2MBasePKs []*Field
|
||||
M2MJoinPKs []*Field
|
||||
}
|
||||
|
||||
// References returns true if the table to which the Relation belongs needs to declare a foreign key constraint to create the relation.
|
||||
|
|
14
vendor/github.com/uptrace/bun/schema/scan.go
generated
vendored
14
vendor/github.com/uptrace/bun/schema/scan.go
generated
vendored
|
@ -8,9 +8,9 @@
|
|||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
|
||||
"github.com/uptrace/bun/dialect/sqltype"
|
||||
|
@ -53,7 +53,7 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
var scannerMap sync.Map
|
||||
var scannerCache = xsync.NewMapOf[reflect.Type, ScannerFunc]()
|
||||
|
||||
func FieldScanner(dialect Dialect, field *Field) ScannerFunc {
|
||||
if field.Tag.HasOption("msgpack") {
|
||||
|
@ -72,14 +72,14 @@ func FieldScanner(dialect Dialect, field *Field) ScannerFunc {
|
|||
}
|
||||
|
||||
func Scanner(typ reflect.Type) ScannerFunc {
|
||||
if v, ok := scannerMap.Load(typ); ok {
|
||||
return v.(ScannerFunc)
|
||||
if v, ok := scannerCache.Load(typ); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
fn := scanner(typ)
|
||||
|
||||
if v, ok := scannerMap.LoadOrStore(typ, fn); ok {
|
||||
return v.(ScannerFunc)
|
||||
if v, ok := scannerCache.LoadOrStore(typ, fn); ok {
|
||||
return v
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ func scanner(typ reflect.Type) ScannerFunc {
|
|||
}
|
||||
|
||||
if kind != reflect.Ptr {
|
||||
ptr := reflect.PtrTo(typ)
|
||||
ptr := reflect.PointerTo(typ)
|
||||
if ptr.Implements(scannerType) {
|
||||
return addrScanner(scanScanner)
|
||||
}
|
||||
|
|
86
vendor/github.com/uptrace/bun/schema/table.go
generated
vendored
86
vendor/github.com/uptrace/bun/schema/table.go
generated
vendored
|
@ -74,16 +74,7 @@ type structField struct {
|
|||
Table *Table
|
||||
}
|
||||
|
||||
func newTable(
|
||||
dialect Dialect, typ reflect.Type, seen map[reflect.Type]*Table, canAddr bool,
|
||||
) *Table {
|
||||
if table, ok := seen[typ]; ok {
|
||||
return table
|
||||
}
|
||||
|
||||
table := new(Table)
|
||||
seen[typ] = table
|
||||
|
||||
func (table *Table) init(dialect Dialect, typ reflect.Type, canAddr bool) {
|
||||
table.dialect = dialect
|
||||
table.Type = typ
|
||||
table.ZeroValue = reflect.New(table.Type).Elem()
|
||||
|
@ -97,7 +88,7 @@ func newTable(
|
|||
|
||||
table.Fields = make([]*Field, 0, typ.NumField())
|
||||
table.FieldMap = make(map[string]*Field, typ.NumField())
|
||||
table.processFields(typ, seen, canAddr)
|
||||
table.processFields(typ, canAddr)
|
||||
|
||||
hooks := []struct {
|
||||
typ reflect.Type
|
||||
|
@ -109,28 +100,15 @@ func newTable(
|
|||
{afterScanRowHookType, afterScanRowHookFlag},
|
||||
}
|
||||
|
||||
typ = reflect.PtrTo(table.Type)
|
||||
typ = reflect.PointerTo(table.Type)
|
||||
for _, hook := range hooks {
|
||||
if typ.Implements(hook.typ) {
|
||||
table.flags = table.flags.Set(hook.flag)
|
||||
}
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
func (t *Table) init() {
|
||||
for _, field := range t.relFields {
|
||||
t.processRelation(field)
|
||||
}
|
||||
t.relFields = nil
|
||||
}
|
||||
|
||||
func (t *Table) processFields(
|
||||
typ reflect.Type,
|
||||
seen map[reflect.Type]*Table,
|
||||
canAddr bool,
|
||||
) {
|
||||
func (t *Table) processFields(typ reflect.Type, canAddr bool) {
|
||||
type embeddedField struct {
|
||||
prefix string
|
||||
index []int
|
||||
|
@ -172,7 +150,7 @@ type embeddedField struct {
|
|||
continue
|
||||
}
|
||||
|
||||
subtable := newTable(t.dialect, sfType, seen, canAddr)
|
||||
subtable := t.dialect.Tables().InProgress(sfType)
|
||||
|
||||
for _, subfield := range subtable.allFields {
|
||||
embedded = append(embedded, embeddedField{
|
||||
|
@ -206,7 +184,7 @@ type embeddedField struct {
|
|||
t.TypeName, sf.Name, fieldType.Kind()))
|
||||
}
|
||||
|
||||
subtable := newTable(t.dialect, fieldType, seen, canAddr)
|
||||
subtable := t.dialect.Tables().InProgress(fieldType)
|
||||
for _, subfield := range subtable.allFields {
|
||||
embedded = append(embedded, embeddedField{
|
||||
prefix: prefix,
|
||||
|
@ -229,7 +207,7 @@ type embeddedField struct {
|
|||
}
|
||||
t.StructMap[field.Name] = &structField{
|
||||
Index: field.Index,
|
||||
Table: newTable(t.dialect, field.IndirectType, seen, canAddr),
|
||||
Table: t.dialect.Tables().InProgress(field.IndirectType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -423,6 +401,10 @@ func (t *Table) newField(sf reflect.StructField, tag tagparser.Tag) *Field {
|
|||
sqlName = tag.Name
|
||||
}
|
||||
|
||||
if s, ok := tag.Option("column"); ok {
|
||||
sqlName = s
|
||||
}
|
||||
|
||||
for name := range tag.Options {
|
||||
if !isKnownFieldOption(name) {
|
||||
internal.Warn.Printf("%s.%s has unknown tag option: %q", t.TypeName, sf.Name, name)
|
||||
|
@ -490,6 +472,13 @@ func (t *Table) newField(sf reflect.StructField, tag tagparser.Tag) *Field {
|
|||
|
||||
//---------------------------------------------------------------------------------------
|
||||
|
||||
func (t *Table) initRelations() {
|
||||
for _, field := range t.relFields {
|
||||
t.processRelation(field)
|
||||
}
|
||||
t.relFields = nil
|
||||
}
|
||||
|
||||
func (t *Table) processRelation(field *Field) {
|
||||
if rel, ok := field.Tag.Option("rel"); ok {
|
||||
t.initRelation(field, rel)
|
||||
|
@ -577,7 +566,7 @@ func (t *Table) belongsToRelation(field *Field) *Relation {
|
|||
joinColumn := joinColumns[i]
|
||||
|
||||
if f := t.FieldMap[baseColumn]; f != nil {
|
||||
rel.BaseFields = append(rel.BaseFields, f)
|
||||
rel.BasePKs = append(rel.BasePKs, f)
|
||||
} else {
|
||||
panic(fmt.Errorf(
|
||||
"bun: %s belongs-to %s: %s must have column %s",
|
||||
|
@ -586,7 +575,7 @@ func (t *Table) belongsToRelation(field *Field) *Relation {
|
|||
}
|
||||
|
||||
if f := joinTable.FieldMap[joinColumn]; f != nil {
|
||||
rel.JoinFields = append(rel.JoinFields, f)
|
||||
rel.JoinPKs = append(rel.JoinPKs, f)
|
||||
} else {
|
||||
panic(fmt.Errorf(
|
||||
"bun: %s belongs-to %s: %s must have column %s",
|
||||
|
@ -597,17 +586,17 @@ func (t *Table) belongsToRelation(field *Field) *Relation {
|
|||
return rel
|
||||
}
|
||||
|
||||
rel.JoinFields = joinTable.PKs
|
||||
rel.JoinPKs = joinTable.PKs
|
||||
fkPrefix := internal.Underscore(field.GoName) + "_"
|
||||
for _, joinPK := range joinTable.PKs {
|
||||
fkName := fkPrefix + joinPK.Name
|
||||
if fk := t.FieldMap[fkName]; fk != nil {
|
||||
rel.BaseFields = append(rel.BaseFields, fk)
|
||||
rel.BasePKs = append(rel.BasePKs, fk)
|
||||
continue
|
||||
}
|
||||
|
||||
if fk := t.FieldMap[joinPK.Name]; fk != nil {
|
||||
rel.BaseFields = append(rel.BaseFields, fk)
|
||||
rel.BasePKs = append(rel.BasePKs, fk)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -640,7 +629,7 @@ func (t *Table) hasOneRelation(field *Field) *Relation {
|
|||
baseColumns, joinColumns := parseRelationJoin(join)
|
||||
for i, baseColumn := range baseColumns {
|
||||
if f := t.FieldMap[baseColumn]; f != nil {
|
||||
rel.BaseFields = append(rel.BaseFields, f)
|
||||
rel.BasePKs = append(rel.BasePKs, f)
|
||||
} else {
|
||||
panic(fmt.Errorf(
|
||||
"bun: %s has-one %s: %s must have column %s",
|
||||
|
@ -650,7 +639,7 @@ func (t *Table) hasOneRelation(field *Field) *Relation {
|
|||
|
||||
joinColumn := joinColumns[i]
|
||||
if f := joinTable.FieldMap[joinColumn]; f != nil {
|
||||
rel.JoinFields = append(rel.JoinFields, f)
|
||||
rel.JoinPKs = append(rel.JoinPKs, f)
|
||||
} else {
|
||||
panic(fmt.Errorf(
|
||||
"bun: %s has-one %s: %s must have column %s",
|
||||
|
@ -661,17 +650,17 @@ func (t *Table) hasOneRelation(field *Field) *Relation {
|
|||
return rel
|
||||
}
|
||||
|
||||
rel.BaseFields = t.PKs
|
||||
rel.BasePKs = t.PKs
|
||||
fkPrefix := internal.Underscore(t.ModelName) + "_"
|
||||
for _, pk := range t.PKs {
|
||||
fkName := fkPrefix + pk.Name
|
||||
if f := joinTable.FieldMap[fkName]; f != nil {
|
||||
rel.JoinFields = append(rel.JoinFields, f)
|
||||
rel.JoinPKs = append(rel.JoinPKs, f)
|
||||
continue
|
||||
}
|
||||
|
||||
if f := joinTable.FieldMap[pk.Name]; f != nil {
|
||||
rel.JoinFields = append(rel.JoinFields, f)
|
||||
rel.JoinPKs = append(rel.JoinPKs, f)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -720,7 +709,7 @@ func (t *Table) hasManyRelation(field *Field) *Relation {
|
|||
}
|
||||
|
||||
if f := t.FieldMap[baseColumn]; f != nil {
|
||||
rel.BaseFields = append(rel.BaseFields, f)
|
||||
rel.BasePKs = append(rel.BasePKs, f)
|
||||
} else {
|
||||
panic(fmt.Errorf(
|
||||
"bun: %s has-many %s: %s must have column %s",
|
||||
|
@ -729,7 +718,7 @@ func (t *Table) hasManyRelation(field *Field) *Relation {
|
|||
}
|
||||
|
||||
if f := joinTable.FieldMap[joinColumn]; f != nil {
|
||||
rel.JoinFields = append(rel.JoinFields, f)
|
||||
rel.JoinPKs = append(rel.JoinPKs, f)
|
||||
} else {
|
||||
panic(fmt.Errorf(
|
||||
"bun: %s has-many %s: %s must have column %s",
|
||||
|
@ -738,7 +727,7 @@ func (t *Table) hasManyRelation(field *Field) *Relation {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
rel.BaseFields = t.PKs
|
||||
rel.BasePKs = t.PKs
|
||||
fkPrefix := internal.Underscore(t.ModelName) + "_"
|
||||
if isPolymorphic {
|
||||
polymorphicColumn = fkPrefix + "type"
|
||||
|
@ -747,12 +736,12 @@ func (t *Table) hasManyRelation(field *Field) *Relation {
|
|||
for _, pk := range t.PKs {
|
||||
joinColumn := fkPrefix + pk.Name
|
||||
if fk := joinTable.FieldMap[joinColumn]; fk != nil {
|
||||
rel.JoinFields = append(rel.JoinFields, fk)
|
||||
rel.JoinPKs = append(rel.JoinPKs, fk)
|
||||
continue
|
||||
}
|
||||
|
||||
if fk := joinTable.FieldMap[pk.Name]; fk != nil {
|
||||
rel.JoinFields = append(rel.JoinFields, fk)
|
||||
rel.JoinPKs = append(rel.JoinPKs, fk)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -852,12 +841,12 @@ func (t *Table) m2mRelation(field *Field) *Relation {
|
|||
}
|
||||
|
||||
leftRel := m2mTable.belongsToRelation(leftField)
|
||||
rel.BaseFields = leftRel.JoinFields
|
||||
rel.M2MBaseFields = leftRel.BaseFields
|
||||
rel.BasePKs = leftRel.JoinPKs
|
||||
rel.M2MBasePKs = leftRel.BasePKs
|
||||
|
||||
rightRel := m2mTable.belongsToRelation(rightField)
|
||||
rel.JoinFields = rightRel.JoinFields
|
||||
rel.M2MJoinFields = rightRel.BaseFields
|
||||
rel.JoinPKs = rightRel.JoinPKs
|
||||
rel.M2MJoinPKs = rightRel.BasePKs
|
||||
|
||||
return rel
|
||||
}
|
||||
|
@ -918,6 +907,7 @@ func isKnownFieldOption(name string) bool {
|
|||
"array",
|
||||
"hstore",
|
||||
"composite",
|
||||
"multirange",
|
||||
"json_use_number",
|
||||
"msgpack",
|
||||
"notnull",
|
||||
|
|
104
vendor/github.com/uptrace/bun/schema/tables.go
generated
vendored
104
vendor/github.com/uptrace/bun/schema/tables.go
generated
vendored
|
@ -4,22 +4,24 @@
|
|||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
type Tables struct {
|
||||
dialect Dialect
|
||||
tables sync.Map
|
||||
|
||||
mu sync.RWMutex
|
||||
seen map[reflect.Type]*Table
|
||||
inProgress map[reflect.Type]*tableInProgress
|
||||
mu sync.Mutex
|
||||
tables *xsync.MapOf[reflect.Type, *Table]
|
||||
|
||||
inProgress map[reflect.Type]*Table
|
||||
}
|
||||
|
||||
func NewTables(dialect Dialect) *Tables {
|
||||
return &Tables{
|
||||
dialect: dialect,
|
||||
seen: make(map[reflect.Type]*Table),
|
||||
inProgress: make(map[reflect.Type]*tableInProgress),
|
||||
tables: xsync.NewMapOf[reflect.Type, *Table](),
|
||||
inProgress: make(map[reflect.Type]*Table),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,58 +32,26 @@ func (t *Tables) Register(models ...interface{}) {
|
|||
}
|
||||
|
||||
func (t *Tables) Get(typ reflect.Type) *Table {
|
||||
return t.table(typ, false)
|
||||
}
|
||||
|
||||
func (t *Tables) InProgress(typ reflect.Type) *Table {
|
||||
return t.table(typ, true)
|
||||
}
|
||||
|
||||
func (t *Tables) table(typ reflect.Type, allowInProgress bool) *Table {
|
||||
typ = indirectType(typ)
|
||||
if typ.Kind() != reflect.Struct {
|
||||
panic(fmt.Errorf("got %s, wanted %s", typ.Kind(), reflect.Struct))
|
||||
}
|
||||
|
||||
if v, ok := t.tables.Load(typ); ok {
|
||||
return v.(*Table)
|
||||
return v
|
||||
}
|
||||
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if v, ok := t.tables.Load(typ); ok {
|
||||
t.mu.Unlock()
|
||||
return v.(*Table)
|
||||
return v
|
||||
}
|
||||
|
||||
var table *Table
|
||||
|
||||
inProgress := t.inProgress[typ]
|
||||
if inProgress == nil {
|
||||
table = newTable(t.dialect, typ, t.seen, false)
|
||||
inProgress = newTableInProgress(table)
|
||||
t.inProgress[typ] = inProgress
|
||||
} else {
|
||||
table = inProgress.table
|
||||
}
|
||||
|
||||
t.mu.Unlock()
|
||||
|
||||
if allowInProgress {
|
||||
return table
|
||||
}
|
||||
|
||||
if !inProgress.init() {
|
||||
return table
|
||||
}
|
||||
|
||||
t.mu.Lock()
|
||||
delete(t.inProgress, typ)
|
||||
t.tables.Store(typ, table)
|
||||
t.mu.Unlock()
|
||||
table := t.InProgress(typ)
|
||||
table.initRelations()
|
||||
|
||||
t.dialect.OnTable(table)
|
||||
|
||||
for _, field := range table.FieldMap {
|
||||
if field.UserSQLType == "" {
|
||||
field.UserSQLType = field.DiscoveredSQLType
|
||||
|
@ -91,15 +61,27 @@ func (t *Tables) table(typ reflect.Type, allowInProgress bool) *Table {
|
|||
}
|
||||
}
|
||||
|
||||
t.tables.Store(typ, table)
|
||||
return table
|
||||
}
|
||||
|
||||
func (t *Tables) InProgress(typ reflect.Type) *Table {
|
||||
if table, ok := t.inProgress[typ]; ok {
|
||||
return table
|
||||
}
|
||||
|
||||
table := new(Table)
|
||||
t.inProgress[typ] = table
|
||||
table.init(t.dialect, typ, false)
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
func (t *Tables) ByModel(name string) *Table {
|
||||
var found *Table
|
||||
t.tables.Range(func(key, value interface{}) bool {
|
||||
t := value.(*Table)
|
||||
if t.TypeName == name {
|
||||
found = t
|
||||
t.tables.Range(func(typ reflect.Type, table *Table) bool {
|
||||
if table.TypeName == name {
|
||||
found = table
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -109,34 +91,12 @@ func (t *Tables) ByModel(name string) *Table {
|
|||
|
||||
func (t *Tables) ByName(name string) *Table {
|
||||
var found *Table
|
||||
t.tables.Range(func(key, value interface{}) bool {
|
||||
t := value.(*Table)
|
||||
if t.Name == name {
|
||||
found = t
|
||||
t.tables.Range(func(typ reflect.Type, table *Table) bool {
|
||||
if table.Name == name {
|
||||
found = table
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
type tableInProgress struct {
|
||||
table *Table
|
||||
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
func newTableInProgress(table *Table) *tableInProgress {
|
||||
return &tableInProgress{
|
||||
table: table,
|
||||
}
|
||||
}
|
||||
|
||||
func (inp *tableInProgress) init() bool {
|
||||
var inited bool
|
||||
inp.initOnce.Do(func() {
|
||||
inp.table.init()
|
||||
inited = true
|
||||
})
|
||||
return inited
|
||||
}
|
||||
|
|
2
vendor/github.com/uptrace/bun/schema/zerochecker.go
generated
vendored
2
vendor/github.com/uptrace/bun/schema/zerochecker.go
generated
vendored
|
@ -60,7 +60,7 @@ func zeroChecker(typ reflect.Type) IsZeroerFunc {
|
|||
kind := typ.Kind()
|
||||
|
||||
if kind != reflect.Ptr {
|
||||
ptr := reflect.PtrTo(typ)
|
||||
ptr := reflect.PointerTo(typ)
|
||||
if ptr.Implements(isZeroerType) {
|
||||
return addrChecker(isZeroInterface)
|
||||
}
|
||||
|
|
2
vendor/github.com/uptrace/bun/version.go
generated
vendored
2
vendor/github.com/uptrace/bun/version.go
generated
vendored
|
@ -2,5 +2,5 @@
|
|||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "1.2.1"
|
||||
return "1.2.5"
|
||||
}
|
||||
|
|
5
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/README.md
generated
vendored
5
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/README.md
generated
vendored
|
@ -2,8 +2,9 @@
|
|||
|
||||
# database/sql instrumentation for OpenTelemetry Go
|
||||
|
||||
[database/sql OpenTelemetry instrumentation](https://uptrace.dev/getinstrument/opentelemetry-database-sql.html)
|
||||
records database queries (including `Tx` and `Stmt` queries) and reports `DBStats` metrics.
|
||||
[OpenTelemetry database/sql](https://uptrace.dev/get/instrument/opentelemetry-database-sql.html)
|
||||
instrumentation records database queries (including `Tx` and `Stmt` queries) and reports `DBStats`
|
||||
metrics.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
2
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/version.go
generated
vendored
2
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/version.go
generated
vendored
|
@ -2,5 +2,5 @@
|
|||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "0.2.4"
|
||||
return "0.3.2"
|
||||
}
|
||||
|
|
32
vendor/modules.txt
vendored
32
vendor/modules.txt
vendored
|
@ -572,6 +572,9 @@ github.com/prometheus/common/model
|
|||
github.com/prometheus/procfs
|
||||
github.com/prometheus/procfs/internal/fs
|
||||
github.com/prometheus/procfs/internal/util
|
||||
# github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||
## explicit; go 1.18
|
||||
github.com/puzpuzpuz/xsync/v3
|
||||
# github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b
|
||||
## explicit
|
||||
github.com/quasoft/memstore
|
||||
|
@ -915,8 +918,8 @@ github.com/ugorji/go/codec
|
|||
github.com/ulule/limiter/v3
|
||||
github.com/ulule/limiter/v3/drivers/store/common
|
||||
github.com/ulule/limiter/v3/drivers/store/memory
|
||||
# github.com/uptrace/bun v1.2.1
|
||||
## explicit; go 1.21
|
||||
# github.com/uptrace/bun v1.2.5
|
||||
## explicit; go 1.22
|
||||
github.com/uptrace/bun
|
||||
github.com/uptrace/bun/dialect
|
||||
github.com/uptrace/bun/dialect/feature
|
||||
|
@ -927,17 +930,17 @@ github.com/uptrace/bun/internal/parser
|
|||
github.com/uptrace/bun/internal/tagparser
|
||||
github.com/uptrace/bun/migrate
|
||||
github.com/uptrace/bun/schema
|
||||
# github.com/uptrace/bun/dialect/pgdialect v1.2.1
|
||||
## explicit; go 1.21
|
||||
# github.com/uptrace/bun/dialect/pgdialect v1.2.5
|
||||
## explicit; go 1.22
|
||||
github.com/uptrace/bun/dialect/pgdialect
|
||||
# github.com/uptrace/bun/dialect/sqlitedialect v1.2.1
|
||||
## explicit; go 1.21
|
||||
# github.com/uptrace/bun/dialect/sqlitedialect v1.2.5
|
||||
## explicit; go 1.22
|
||||
github.com/uptrace/bun/dialect/sqlitedialect
|
||||
# github.com/uptrace/bun/extra/bunotel v1.2.1
|
||||
## explicit; go 1.21
|
||||
# github.com/uptrace/bun/extra/bunotel v1.2.5
|
||||
## explicit; go 1.22
|
||||
github.com/uptrace/bun/extra/bunotel
|
||||
# github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4
|
||||
## explicit; go 1.21
|
||||
# github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2
|
||||
## explicit; go 1.22
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql
|
||||
# github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
## explicit; go 1.19
|
||||
|
@ -971,7 +974,7 @@ go.mongodb.org/mongo-driver/bson/bsonrw
|
|||
go.mongodb.org/mongo-driver/bson/bsontype
|
||||
go.mongodb.org/mongo-driver/bson/primitive
|
||||
go.mongodb.org/mongo-driver/x/bsonx/bsoncore
|
||||
# go.opentelemetry.io/otel v1.29.0
|
||||
# go.opentelemetry.io/otel v1.31.0 => go.opentelemetry.io/otel v1.29.0
|
||||
## explicit; go 1.21
|
||||
go.opentelemetry.io/otel
|
||||
go.opentelemetry.io/otel/attribute
|
||||
|
@ -1012,7 +1015,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/retry
|
|||
# go.opentelemetry.io/otel/exporters/prometheus v0.51.0
|
||||
## explicit; go 1.21
|
||||
go.opentelemetry.io/otel/exporters/prometheus
|
||||
# go.opentelemetry.io/otel/metric v1.29.0
|
||||
# go.opentelemetry.io/otel/metric v1.31.0 => go.opentelemetry.io/otel/metric v1.29.0
|
||||
## explicit; go 1.21
|
||||
go.opentelemetry.io/otel/metric
|
||||
go.opentelemetry.io/otel/metric/embedded
|
||||
|
@ -1033,7 +1036,7 @@ go.opentelemetry.io/otel/sdk/metric/internal/aggregate
|
|||
go.opentelemetry.io/otel/sdk/metric/internal/exemplar
|
||||
go.opentelemetry.io/otel/sdk/metric/internal/x
|
||||
go.opentelemetry.io/otel/sdk/metric/metricdata
|
||||
# go.opentelemetry.io/otel/trace v1.29.0
|
||||
# go.opentelemetry.io/otel/trace v1.31.0 => go.opentelemetry.io/otel/trace v1.29.0
|
||||
## explicit; go 1.21
|
||||
go.opentelemetry.io/otel/trace
|
||||
go.opentelemetry.io/otel/trace/embedded
|
||||
|
@ -1343,3 +1346,6 @@ modernc.org/token
|
|||
## explicit; go 1.19
|
||||
mvdan.cc/xurls/v2
|
||||
# modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.33.1-concurrency-workaround
|
||||
# go.opentelemetry.io/otel => go.opentelemetry.io/otel v1.29.0
|
||||
# go.opentelemetry.io/otel/metric => go.opentelemetry.io/otel/metric v1.29.0
|
||||
# go.opentelemetry.io/otel/trace => go.opentelemetry.io/otel/trace v1.29.0
|
||||
|
|
Loading…
Reference in a new issue