chore: ⬆️ updated deps
This commit is contained in:
+23
-15
@@ -4,7 +4,10 @@
|
||||
// an allocation is purposely not documented. https://github.com/golang/go/issues/16323
|
||||
package iobufpool
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"math/bits"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const minPoolExpOf2 = 8
|
||||
|
||||
@@ -37,15 +40,14 @@ func Get(size int) *[]byte {
|
||||
}
|
||||
|
||||
func getPoolIdx(size int) int {
|
||||
size--
|
||||
size >>= minPoolExpOf2
|
||||
i := 0
|
||||
for size > 0 {
|
||||
size >>= 1
|
||||
i++
|
||||
if size < 2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return i
|
||||
idx := bits.Len(uint(size-1)) - minPoolExpOf2
|
||||
if idx < 0 {
|
||||
return 0
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
// Put returns buf to the pool.
|
||||
@@ -59,12 +61,18 @@ func Put(buf *[]byte) {
|
||||
}
|
||||
|
||||
func putPoolIdx(size int) int {
|
||||
minPoolSize := 1 << minPoolExpOf2
|
||||
for i := range pools {
|
||||
if size == minPoolSize<<i {
|
||||
return i
|
||||
}
|
||||
// Only exact power-of-2 sizes match pool buckets
|
||||
if size&(size-1) != 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
return -1
|
||||
// Calculate log2(size) using trailing zeros count
|
||||
exp := bits.TrailingZeros(uint(size))
|
||||
idx := exp - minPoolExpOf2
|
||||
|
||||
if idx < 0 || idx >= len(pools) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return idx
|
||||
}
|
||||
|
||||
+7
-15
@@ -1,26 +1,18 @@
|
||||
package pgio
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
func AppendUint16(buf []byte, n uint16) []byte {
|
||||
wp := len(buf)
|
||||
buf = append(buf, 0, 0)
|
||||
binary.BigEndian.PutUint16(buf[wp:], n)
|
||||
return buf
|
||||
return append(buf, byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func AppendUint32(buf []byte, n uint32) []byte {
|
||||
wp := len(buf)
|
||||
buf = append(buf, 0, 0, 0, 0)
|
||||
binary.BigEndian.PutUint32(buf[wp:], n)
|
||||
return buf
|
||||
return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
||||
}
|
||||
|
||||
func AppendUint64(buf []byte, n uint64) []byte {
|
||||
wp := len(buf)
|
||||
buf = append(buf, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
binary.BigEndian.PutUint64(buf[wp:], n)
|
||||
return buf
|
||||
return append(buf,
|
||||
byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32),
|
||||
byte(n>>24), byte(n>>16), byte(n>>8), byte(n),
|
||||
)
|
||||
}
|
||||
|
||||
func AppendInt16(buf []byte, n int16) []byte {
|
||||
@@ -36,5 +28,5 @@ func AppendInt64(buf []byte, n int64) []byte {
|
||||
}
|
||||
|
||||
func SetInt32(buf []byte, n int32) {
|
||||
binary.BigEndian.PutUint32(buf, uint32(n))
|
||||
*(*[4]byte)(buf) = [4]byte{byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n)}
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -42,7 +42,7 @@ for i in "${!commits[@]}"; do
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Sanitized commmit message
|
||||
# Sanitized commit message
|
||||
commit_message=$(git log -1 --pretty=format:"%s" | tr -c '[:alnum:]-_' '_')
|
||||
|
||||
# Benchmark data will go there
|
||||
+89
-8
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -202,12 +203,13 @@ func QuoteBytes(dst, buf []byte) []byte {
|
||||
}
|
||||
|
||||
type sqlLexer struct {
|
||||
src string
|
||||
start int
|
||||
pos int
|
||||
nested int // multiline comment nesting level.
|
||||
stateFn stateFn
|
||||
parts []Part
|
||||
src string
|
||||
start int
|
||||
pos int
|
||||
nested int // multiline comment nesting level.
|
||||
dollarTag string // active tag while inside a dollar-quoted string (may be empty for $$).
|
||||
stateFn stateFn
|
||||
parts []Part
|
||||
}
|
||||
|
||||
type stateFn func(*sqlLexer) stateFn
|
||||
@@ -237,6 +239,15 @@ func rawState(l *sqlLexer) stateFn {
|
||||
l.start = l.pos
|
||||
return placeholderState
|
||||
}
|
||||
// PostgreSQL dollar-quoted string: $[tag]$...$[tag]$. The $ was
|
||||
// just consumed; try to match the rest of the opening tag.
|
||||
// Without this, placeholders embedded inside dollar-quoted
|
||||
// literals would be incorrectly substituted.
|
||||
if tagLen, ok := scanDollarQuoteTag(l.src[l.pos:]); ok {
|
||||
l.dollarTag = l.src[l.pos : l.pos+tagLen]
|
||||
l.pos += tagLen + 1 // advance past tag and closing '$'
|
||||
return dollarQuoteState
|
||||
}
|
||||
case '-':
|
||||
nextRune, width := utf8.DecodeRuneInString(l.src[l.pos:])
|
||||
if nextRune == '-' {
|
||||
@@ -319,8 +330,16 @@ func placeholderState(l *sqlLexer) stateFn {
|
||||
l.pos += width
|
||||
|
||||
if '0' <= r && r <= '9' {
|
||||
num *= 10
|
||||
num += int(r - '0')
|
||||
// Clamp rather than silently wrap on pathological input like
|
||||
// "$92233720368547758070" which would otherwise overflow int and
|
||||
// could land on a valid args index. Any value above MaxInt32 far
|
||||
// exceeds any plausible args length, so Sanitize will correctly
|
||||
// return "insufficient arguments".
|
||||
if num > (math.MaxInt32-9)/10 {
|
||||
num = math.MaxInt32
|
||||
} else {
|
||||
num = num*10 + int(r-'0')
|
||||
}
|
||||
} else {
|
||||
l.parts = append(l.parts, num)
|
||||
l.pos -= width
|
||||
@@ -330,6 +349,68 @@ func placeholderState(l *sqlLexer) stateFn {
|
||||
}
|
||||
}
|
||||
|
||||
// dollarQuoteState consumes the body of a PostgreSQL dollar-quoted string
|
||||
// ($[tag]$...$[tag]$). The opening tag (including its terminating '$') has
|
||||
// already been consumed.
|
||||
func dollarQuoteState(l *sqlLexer) stateFn {
|
||||
closer := "$" + l.dollarTag + "$"
|
||||
idx := strings.Index(l.src[l.pos:], closer)
|
||||
if idx < 0 {
|
||||
// Unterminated — mirror the behavior of other quoted-string states by
|
||||
// consuming the remaining input into the current part and stopping.
|
||||
if len(l.src)-l.start > 0 {
|
||||
l.parts = append(l.parts, l.src[l.start:])
|
||||
l.start = len(l.src)
|
||||
}
|
||||
l.pos = len(l.src)
|
||||
return nil
|
||||
}
|
||||
l.pos += idx + len(closer)
|
||||
l.dollarTag = ""
|
||||
return rawState
|
||||
}
|
||||
|
||||
// scanDollarQuoteTag checks whether src begins with an optional dollar-quoted
|
||||
// string tag followed by a closing '$'. src must point just past the opening
|
||||
// '$'. Returns the byte length of the tag (zero for an anonymous $$) and
|
||||
// whether a valid tag was found.
|
||||
//
|
||||
// Tag grammar matches the PostgreSQL lexer (scan.l):
|
||||
//
|
||||
// dolq_start: [A-Za-z_\x80-\xff]
|
||||
// dolq_cont: [A-Za-z0-9_\x80-\xff]
|
||||
func scanDollarQuoteTag(src string) (int, bool) {
|
||||
first := true
|
||||
for i := 0; i < len(src); {
|
||||
r, w := utf8.DecodeRuneInString(src[i:])
|
||||
if r == '$' {
|
||||
return i, true
|
||||
}
|
||||
if !isDollarTagRune(r, first) {
|
||||
return 0, false
|
||||
}
|
||||
first = false
|
||||
i += w
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func isDollarTagRune(r rune, first bool) bool {
|
||||
switch {
|
||||
case r == '_':
|
||||
return true
|
||||
case 'a' <= r && r <= 'z':
|
||||
return true
|
||||
case 'A' <= r && r <= 'Z':
|
||||
return true
|
||||
case !first && '0' <= r && r <= '9':
|
||||
return true
|
||||
case r >= 0x80 && r != utf8.RuneError:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func escapeStringState(l *sqlLexer) stateFn {
|
||||
for {
|
||||
r, width := utf8.DecodeRuneInString(l.src[l.pos:])
|
||||
|
||||
+113
-37
@@ -1,36 +1,54 @@
|
||||
package stmtcache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
// lruNode is a typed doubly-linked list node with freelist support.
|
||||
type lruNode struct {
|
||||
sd *pgconn.StatementDescription
|
||||
prev *lruNode
|
||||
next *lruNode
|
||||
}
|
||||
|
||||
// LRUCache implements Cache with a Least Recently Used (LRU) cache.
|
||||
type LRUCache struct {
|
||||
cap int
|
||||
m map[string]*list.Element
|
||||
l *list.List
|
||||
m map[string]*lruNode
|
||||
head *lruNode
|
||||
|
||||
tail *lruNode
|
||||
len int
|
||||
cap int
|
||||
freelist *lruNode
|
||||
|
||||
invalidStmts []*pgconn.StatementDescription
|
||||
invalidSet map[string]struct{}
|
||||
}
|
||||
|
||||
// NewLRUCache creates a new LRUCache. cap is the maximum size of the cache.
|
||||
func NewLRUCache(cap int) *LRUCache {
|
||||
head := &lruNode{}
|
||||
tail := &lruNode{}
|
||||
head.next = tail
|
||||
tail.prev = head
|
||||
|
||||
return &LRUCache{
|
||||
cap: cap,
|
||||
m: make(map[string]*list.Element),
|
||||
l: list.New(),
|
||||
cap: cap,
|
||||
m: make(map[string]*lruNode, cap),
|
||||
head: head,
|
||||
tail: tail,
|
||||
invalidSet: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the statement description for sql. Returns nil if not found.
|
||||
func (c *LRUCache) Get(key string) *pgconn.StatementDescription {
|
||||
if el, ok := c.m[key]; ok {
|
||||
c.l.MoveToFront(el)
|
||||
return el.Value.(*pgconn.StatementDescription)
|
||||
node, ok := c.m[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
c.moveToFront(node)
|
||||
return node.sd
|
||||
}
|
||||
|
||||
// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache or
|
||||
@@ -45,39 +63,49 @@ func (c *LRUCache) Put(sd *pgconn.StatementDescription) {
|
||||
}
|
||||
|
||||
// The statement may have been invalidated but not yet handled. Do not readd it to the cache.
|
||||
for _, invalidSD := range c.invalidStmts {
|
||||
if invalidSD.SQL == sd.SQL {
|
||||
return
|
||||
}
|
||||
if _, invalidated := c.invalidSet[sd.SQL]; invalidated {
|
||||
return
|
||||
}
|
||||
|
||||
if c.l.Len() == c.cap {
|
||||
if c.len == c.cap {
|
||||
c.invalidateOldest()
|
||||
}
|
||||
|
||||
el := c.l.PushFront(sd)
|
||||
c.m[sd.SQL] = el
|
||||
node := c.allocNode()
|
||||
node.sd = sd
|
||||
c.insertAfter(c.head, node)
|
||||
c.m[sd.SQL] = node
|
||||
c.len++
|
||||
}
|
||||
|
||||
// Invalidate invalidates statement description identified by sql. Does nothing if not found.
|
||||
func (c *LRUCache) Invalidate(sql string) {
|
||||
if el, ok := c.m[sql]; ok {
|
||||
delete(c.m, sql)
|
||||
c.invalidStmts = append(c.invalidStmts, el.Value.(*pgconn.StatementDescription))
|
||||
c.l.Remove(el)
|
||||
node, ok := c.m[sql]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(c.m, sql)
|
||||
c.invalidStmts = append(c.invalidStmts, node.sd)
|
||||
c.invalidSet[sql] = struct{}{}
|
||||
c.unlink(node)
|
||||
c.len--
|
||||
c.freeNode(node)
|
||||
}
|
||||
|
||||
// InvalidateAll invalidates all statement descriptions.
|
||||
func (c *LRUCache) InvalidateAll() {
|
||||
el := c.l.Front()
|
||||
for el != nil {
|
||||
c.invalidStmts = append(c.invalidStmts, el.Value.(*pgconn.StatementDescription))
|
||||
el = el.Next()
|
||||
for node := c.head.next; node != c.tail; {
|
||||
next := node.next
|
||||
c.invalidStmts = append(c.invalidStmts, node.sd)
|
||||
c.invalidSet[node.sd.SQL] = struct{}{}
|
||||
c.freeNode(node)
|
||||
node = next
|
||||
}
|
||||
|
||||
c.m = make(map[string]*list.Element)
|
||||
c.l = list.New()
|
||||
clear(c.m)
|
||||
c.head.next = c.tail
|
||||
c.tail.prev = c.head
|
||||
c.len = 0
|
||||
}
|
||||
|
||||
// GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated.
|
||||
@@ -89,12 +117,13 @@ func (c *LRUCache) GetInvalidated() []*pgconn.StatementDescription {
|
||||
// call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were
|
||||
// never seen by the call to GetInvalidated.
|
||||
func (c *LRUCache) RemoveInvalidated() {
|
||||
c.invalidStmts = nil
|
||||
c.invalidStmts = c.invalidStmts[:0]
|
||||
clear(c.invalidSet)
|
||||
}
|
||||
|
||||
// Len returns the number of cached prepared statement descriptions.
|
||||
func (c *LRUCache) Len() int {
|
||||
return c.l.Len()
|
||||
return c.len
|
||||
}
|
||||
|
||||
// Cap returns the maximum number of cached prepared statement descriptions.
|
||||
@@ -103,9 +132,56 @@ func (c *LRUCache) Cap() int {
|
||||
}
|
||||
|
||||
func (c *LRUCache) invalidateOldest() {
|
||||
oldest := c.l.Back()
|
||||
sd := oldest.Value.(*pgconn.StatementDescription)
|
||||
c.invalidStmts = append(c.invalidStmts, sd)
|
||||
delete(c.m, sd.SQL)
|
||||
c.l.Remove(oldest)
|
||||
node := c.tail.prev
|
||||
if node == c.head {
|
||||
return
|
||||
}
|
||||
c.invalidStmts = append(c.invalidStmts, node.sd)
|
||||
c.invalidSet[node.sd.SQL] = struct{}{}
|
||||
delete(c.m, node.sd.SQL)
|
||||
c.unlink(node)
|
||||
c.len--
|
||||
c.freeNode(node)
|
||||
}
|
||||
|
||||
// List operations - sentinel nodes eliminate nil checks
|
||||
|
||||
func (c *LRUCache) insertAfter(at, node *lruNode) {
|
||||
node.prev = at
|
||||
node.next = at.next
|
||||
at.next.prev = node
|
||||
at.next = node
|
||||
}
|
||||
|
||||
func (c *LRUCache) unlink(node *lruNode) {
|
||||
node.prev.next = node.next
|
||||
node.next.prev = node.prev
|
||||
}
|
||||
|
||||
func (c *LRUCache) moveToFront(node *lruNode) {
|
||||
if node.prev == c.head {
|
||||
return
|
||||
}
|
||||
c.unlink(node)
|
||||
c.insertAfter(c.head, node)
|
||||
}
|
||||
|
||||
// Node pool operations - reuse evicted nodes to avoid allocations
|
||||
|
||||
func (c *LRUCache) allocNode() *lruNode {
|
||||
if c.freelist != nil {
|
||||
node := c.freelist
|
||||
c.freelist = node.next
|
||||
node.next = nil
|
||||
node.prev = nil
|
||||
return node
|
||||
}
|
||||
return &lruNode{}
|
||||
}
|
||||
|
||||
func (c *LRUCache) freeNode(node *lruNode) {
|
||||
node.sd = nil
|
||||
node.prev = nil
|
||||
node.next = c.freelist
|
||||
c.freelist = node
|
||||
}
|
||||
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
package stmtcache
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
// UnlimitedCache implements Cache with no capacity limit.
|
||||
type UnlimitedCache struct {
|
||||
m map[string]*pgconn.StatementDescription
|
||||
invalidStmts []*pgconn.StatementDescription
|
||||
}
|
||||
|
||||
// NewUnlimitedCache creates a new UnlimitedCache.
|
||||
func NewUnlimitedCache() *UnlimitedCache {
|
||||
return &UnlimitedCache{
|
||||
m: make(map[string]*pgconn.StatementDescription),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the statement description for sql. Returns nil if not found.
|
||||
func (c *UnlimitedCache) Get(sql string) *pgconn.StatementDescription {
|
||||
return c.m[sql]
|
||||
}
|
||||
|
||||
// Put stores sd in the cache. Put panics if sd.SQL is "". Put does nothing if sd.SQL already exists in the cache.
|
||||
func (c *UnlimitedCache) Put(sd *pgconn.StatementDescription) {
|
||||
if sd.SQL == "" {
|
||||
panic("cannot store statement description with empty SQL")
|
||||
}
|
||||
|
||||
if _, present := c.m[sd.SQL]; present {
|
||||
return
|
||||
}
|
||||
|
||||
c.m[sd.SQL] = sd
|
||||
}
|
||||
|
||||
// Invalidate invalidates statement description identified by sql. Does nothing if not found.
|
||||
func (c *UnlimitedCache) Invalidate(sql string) {
|
||||
if sd, ok := c.m[sql]; ok {
|
||||
delete(c.m, sql)
|
||||
c.invalidStmts = append(c.invalidStmts, sd)
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidateAll invalidates all statement descriptions.
|
||||
func (c *UnlimitedCache) InvalidateAll() {
|
||||
for _, sd := range c.m {
|
||||
c.invalidStmts = append(c.invalidStmts, sd)
|
||||
}
|
||||
|
||||
c.m = make(map[string]*pgconn.StatementDescription)
|
||||
}
|
||||
|
||||
// GetInvalidated returns a slice of all statement descriptions invalidated since the last call to RemoveInvalidated.
|
||||
func (c *UnlimitedCache) GetInvalidated() []*pgconn.StatementDescription {
|
||||
return c.invalidStmts
|
||||
}
|
||||
|
||||
// RemoveInvalidated removes all invalidated statement descriptions. No other calls to Cache must be made between a
|
||||
// call to GetInvalidated and RemoveInvalidated or RemoveInvalidated may remove statement descriptions that were
|
||||
// never seen by the call to GetInvalidated.
|
||||
func (c *UnlimitedCache) RemoveInvalidated() {
|
||||
c.invalidStmts = nil
|
||||
}
|
||||
|
||||
// Len returns the number of cached prepared statement descriptions.
|
||||
func (c *UnlimitedCache) Len() int {
|
||||
return len(c.m)
|
||||
}
|
||||
|
||||
// Cap returns the maximum number of cached prepared statement descriptions.
|
||||
func (c *UnlimitedCache) Cap() int {
|
||||
return math.MaxInt
|
||||
}
|
||||
Reference in New Issue
Block a user