diff --git a/common-lib/securestore/rollback/go.mod b/common-lib/securestore/rollback/go.mod index 9cbe58435..b21d5b580 100644 --- a/common-lib/securestore/rollback/go.mod +++ b/common-lib/securestore/rollback/go.mod @@ -16,7 +16,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/docker/cli v28.1.1+incompatible // indirect + github.com/docker/cli v29.2.0+incompatible // indirect github.com/google/wire v0.6.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/common-lib/securestore/rollback/go.sum b/common-lib/securestore/rollback/go.sum index 1dfb52a32..2c6ee3307 100644 --- a/common-lib/securestore/rollback/go.sum +++ b/common-lib/securestore/rollback/go.sum @@ -10,8 +10,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/devtron-labs/devtron-services/common-lib v0.0.0-20251015063403-c79706370455 h1:K33kulX6ca48ChOpstenMbu8/ulrH2AztqrBpg2dFcU= github.com/devtron-labs/devtron-services/common-lib v0.0.0-20251015063403-c79706370455/go.mod h1:/Ciy9tD9OxZOWBDPIasM448H7uvSo4+ZJiExpfwBZpA= -github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= -github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= +github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= diff --git a/common-lib/securestore/rollback/vendor/github.com/docker/cli/AUTHORS b/common-lib/securestore/rollback/vendor/github.com/docker/cli/AUTHORS index ad1abd496..57af08b20 100644 --- a/common-lib/securestore/rollback/vendor/github.com/docker/cli/AUTHORS +++ b/common-lib/securestore/rollback/vendor/github.com/docker/cli/AUTHORS @@ -48,6 +48,7 @@ Alfred Landrum Ali Rostami Alicia Lauerman Allen Sun +Allie Sadler Alvin Deng Amen Belayneh Amey Shrivastava <72866602+AmeyShrivastava@users.noreply.github.com> @@ -62,6 +63,7 @@ Andreas Köhler Andres G. Aragoneses Andres Leon Rangel Andrew France +Andrew He Andrew Hsu Andrew Macpherson Andrew McDonnell @@ -81,13 +83,16 @@ Antonis Kalipetis Anusha Ragunathan Ao Li Arash Deshmeh +Archimedes Trajano Arko Dasgupta Arnaud Porterie Arnaud Rebillout +Arthur Flageul Arthur Peka Ashly Mathew Ashwini Oruganti Aslam Ahemad +Austin Vazquez Azat Khuyiyakhmetov Bardia Keyoumarsi Barnaby Gray @@ -132,9 +137,12 @@ Cao Weiwei Carlo Mion Carlos Alexandro Becker Carlos de Paula +carsontham +Carston Schilds Casey Korver Ce Gao Cedric Davies +Cesar Talledo Cezar Sa Espinola Chad Faragher Chao Wang @@ -189,6 +197,7 @@ Daisuke Ito dalanlan Damien Nadé Dan Cotora +Dan Wallis Danial Gharib Daniel Artine Daniel Cassidy @@ -215,7 +224,7 @@ David Alvarez David Beitey David Calavera David Cramer -David Dooling +David Dooling David Gageot David Karlsson David le Blanc @@ -237,6 +246,7 @@ Deshi Xiao Dharmit Shah Dhawal Yogesh Bhanushali Dieter Reuter +Dilep Dev <34891655+DilepDev@users.noreply.github.com> Dima Stopel Dimitry Andric Ding Fei @@ -259,6 +269,7 @@ Eli Uriegas Eli Uriegas Elias Faxö Elliot Luo <956941328@qq.com> +Eng Zer Jun Eric Bode Eric Curtin Eric Engestrom @@ -308,6 +319,8 @@ George MacRorie George Margaritis George Xie Gianluca Borello +Giau. Tran Minh +Giedrius Jonikas Gildas Cuisinier Gio d'Amelio Gleb Stsenov @@ -337,6 +350,7 @@ Henning Sprang Henry N Hernan Garcia Hongbin Lu +Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com> Hu Keping Huayi Zhang Hugo Chastel @@ -344,6 +358,7 @@ Hugo Gabriel Eyherabide huqun Huu Nguyen Hyzhou Zhy +Iain MacDonald Iain Samuel McLean Elder Ian Campbell Ian Philpot @@ -393,6 +408,7 @@ Jesse Adametz Jessica Frazelle Jezeniel Zapanta Jian Zhang +Jianyong Wu Jie Luo Jilles Oldenbeuving Jim Chen @@ -446,6 +462,7 @@ Julian Julien Barbier Julien Kassar Julien Maitrehenry +Julio Cesar Garcia Justas Brazauskas Justin Chadwell Justin Cormack @@ -490,19 +507,22 @@ Kunal Kushwaha Kyle Mitofsky Lachlan Cooper Lai Jiangshan +Lajos Papp Lars Kellogg-Stedman Laura Brehm Laura Frank Laurent Erignoux +Laurent Goderre Lee Gaines Lei Jitang Lennie +lentil32 Leo Gallucci Leonid Skorospelov Lewis Daly Li Fu Bang Li Yi -Li Yi +Li Zeghong Liang-Chi Hsieh Lihua Tang Lily Guo @@ -515,6 +535,7 @@ lixiaobing10051267 Lloyd Dewolf Lorenzo Fontana Louis Opter +Lovekesh Kumar Luca Favatella Luca Marturana Lucas Chan @@ -559,6 +580,7 @@ Matt Robenolt Matteo Orefice Matthew Heon Matthieu Hauglustaine +Matthieu MOREL Mauro Porras P Max Shytikov Max-Julian Pogner @@ -566,6 +588,7 @@ Maxime Petazzoni Maximillian Fan Xavier Mei ChunTao Melroy van den Berg +Mert Şişmanoğlu Metal <2466052+tedhexaflow@users.noreply.github.com> Micah Zoltu Michael A. Smith @@ -578,6 +601,7 @@ Michael Prokop Michael Scharf Michael Spetsiotis Michael Steinert +Michael Tews Michael West Michal Minář Michał Czeraszkiewicz @@ -598,7 +622,9 @@ Mindaugas Rukas Miroslav Gula Misty Stanley-Jones Mohammad Banikazemi +Mohammad Hossein Mohammed Aaqib Ansari +Mohammed Aminu Futa Mohini Anne Dsouza Moorthy RS Morgan Bauer @@ -633,9 +659,11 @@ Nicolas De Loof Nikhil Chawla Nikolas Garofil Nikolay Milovanov +NinaLua Nir Soffer Nishant Totla NIWA Hideyuki +Noah Silas Noah Treuhaft O.S. Tezer Oded Arbel @@ -653,10 +681,12 @@ Patrick Böänziger Patrick Daigle <114765035+pdaig@users.noreply.github.com> Patrick Hemmer Patrick Lang +Patrick St. laurent Paul Paul Kehrer Paul Lietar Paul Mulders +Paul Rogalski Paul Seyfert Paul Weaver Pavel Pospisil @@ -678,7 +708,6 @@ Philip Alexander Etling Philipp Gillé Philipp Schmied Phong Tran -pidster Pieter E Smit pixelistik Pratik Karki @@ -738,6 +767,7 @@ Samuel Cochran Samuel Karp Sandro Jäckel Santhosh Manohar +Sarah Sanders Sargun Dhillon Saswat Bhattacharya Saurabh Kumar @@ -770,6 +800,7 @@ Spencer Brown Spring Lee squeegels Srini Brahmaroutu +Stavros Panakakis Stefan S. Stefan Scherer Stefan Weil @@ -780,6 +811,7 @@ Steve Durrheimer Steve Richards Steven Burgess Stoica-Marcu Floris-Andrei +Stuart Williams Subhajit Ghosh Sun Jianbo Sune Keller @@ -867,9 +899,11 @@ Wang Yumu <37442693@qq.com> Wataru Ishida Wayne Song Wen Cheng Ma +Wenlong Zhang Wenzhi Liang Wes Morgan Wewang Xiaorenfine +Will Wang William Henry Xianglin Gao Xiaodong Liu @@ -908,3 +942,4 @@ Zhuo Zhi Átila Camurça Alves Александр Менщиков <__Singleton__@hackerdom.ru> 徐俊杰 +林博仁 Buo-ren Lin diff --git a/common-lib/securestore/rollback/vendor/github.com/docker/cli/cli/config/types/authconfig.go b/common-lib/securestore/rollback/vendor/github.com/docker/cli/cli/config/types/authconfig.go index 056af6b84..9fe90003b 100644 --- a/common-lib/securestore/rollback/vendor/github.com/docker/cli/cli/config/types/authconfig.go +++ b/common-lib/securestore/rollback/vendor/github.com/docker/cli/cli/config/types/authconfig.go @@ -6,11 +6,6 @@ type AuthConfig struct { Password string `json:"password,omitempty"` Auth string `json:"auth,omitempty"` - // Email is an optional value associated with the username. - // This field is deprecated and will be removed in a later - // version of docker. - Email string `json:"email,omitempty"` - ServerAddress string `json:"serveraddress,omitempty"` // IdentityToken is used to authenticate the user and get diff --git a/common-lib/securestore/rollback/vendor/modules.txt b/common-lib/securestore/rollback/vendor/modules.txt index b1c9c70e9..f6c4313d6 100644 --- a/common-lib/securestore/rollback/vendor/modules.txt +++ b/common-lib/securestore/rollback/vendor/modules.txt @@ -17,7 +17,7 @@ github.com/devtron-labs/common-lib/securestore github.com/devtron-labs/common-lib/utils github.com/devtron-labs/common-lib/utils/bean github.com/devtron-labs/common-lib/utils/sql -# github.com/docker/cli v28.1.1+incompatible +# github.com/docker/cli v29.2.0+incompatible ## explicit github.com/docker/cli/cli/config/types # github.com/go-pg/pg v6.15.1+incompatible diff --git a/kubelink/go.mod b/kubelink/go.mod index 37b511c9a..6d546fb8e 100644 --- a/kubelink/go.mod +++ b/kubelink/go.mod @@ -99,7 +99,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/spdystream v0.5.1 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/kubelink/go.sum b/kubelink/go.sum index 121879353..a60795ca0 100644 --- a/kubelink/go.sum +++ b/kubelink/go.sum @@ -249,8 +249,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/kubelink/vendor/github.com/moby/spdystream/NOTICE b/kubelink/vendor/github.com/moby/spdystream/NOTICE index b9b11c9ab..24e2e2aa3 100644 --- a/kubelink/vendor/github.com/moby/spdystream/NOTICE +++ b/kubelink/vendor/github.com/moby/spdystream/NOTICE @@ -3,3 +3,15 @@ Copyright 2014-2021 Docker Inc. This product includes software developed at Docker Inc. (https://www.docker.com/). + +SPDY implementation (spdy/) + +The spdy directory contains code derived from the Go project (golang.org/x/net). + +Copyright 2009-2013 The Go Authors. +Licensed under the BSD 3-Clause License. + +Modifications Copyright 2014-2021 Docker Inc. + +The BSD license text and Go patent grant are included in +spdy/LICENSE and spdy/PATENTS. diff --git a/kubelink/vendor/github.com/moby/spdystream/connection.go b/kubelink/vendor/github.com/moby/spdystream/connection.go index 1394d0ad4..69ce4777e 100644 --- a/kubelink/vendor/github.com/moby/spdystream/connection.go +++ b/kubelink/vendor/github.com/moby/spdystream/connection.go @@ -224,7 +224,13 @@ type Connection struct { // NewConnection creates a new spdy connection from an existing // network connection. func NewConnection(conn net.Conn, server bool) (*Connection, error) { - framer, framerErr := spdy.NewFramer(conn, conn) + return NewConnectionWithOptions(conn, server) +} + +// NewConnectionWithOptions creates a new spdy connection and applies frame +// parsing limits via options. +func NewConnectionWithOptions(conn net.Conn, server bool, opts ...spdy.FramerOption) (*Connection, error) { + framer, framerErr := spdy.NewFramerWithOptions(conn, conn, opts...) if framerErr != nil { return nil, framerErr } @@ -350,6 +356,9 @@ Loop: } else { debugMessage("(%p) EOF received", s) } + if spdyErr, ok := err.(*spdy.Error); ok && spdyErr.Err == spdy.InvalidControlFrame { + _ = s.conn.Close() + } break } var priority uint8 diff --git a/kubelink/vendor/github.com/moby/spdystream/spdy/LICENSE b/kubelink/vendor/github.com/moby/spdystream/spdy/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/kubelink/vendor/github.com/moby/spdystream/spdy/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/kubelink/vendor/github.com/moby/spdystream/spdy/PATENTS b/kubelink/vendor/github.com/moby/spdystream/spdy/PATENTS new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/kubelink/vendor/github.com/moby/spdystream/spdy/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google 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, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/kubelink/vendor/github.com/moby/spdystream/spdy/dictionary.go b/kubelink/vendor/github.com/moby/spdystream/spdy/dictionary.go index 392232f17..5a5ff0e14 100644 --- a/kubelink/vendor/github.com/moby/spdystream/spdy/dictionary.go +++ b/kubelink/vendor/github.com/moby/spdystream/spdy/dictionary.go @@ -1,19 +1,3 @@ -/* - Copyright 2014-2021 Docker Inc. - - 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. -*/ - // Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/kubelink/vendor/github.com/moby/spdystream/spdy/options.go b/kubelink/vendor/github.com/moby/spdystream/spdy/options.go new file mode 100644 index 000000000..ec03e0b9a --- /dev/null +++ b/kubelink/vendor/github.com/moby/spdystream/spdy/options.go @@ -0,0 +1,25 @@ +package spdy + +// FramerOption allows callers to customize frame parsing limits. +type FramerOption func(*Framer) + +// WithMaxControlFramePayloadSize sets the control-frame payload limit. +func WithMaxControlFramePayloadSize(size uint32) FramerOption { + return func(f *Framer) { + f.maxFrameLength = size + } +} + +// WithMaxHeaderFieldSize sets the per-header name/value size limit. +func WithMaxHeaderFieldSize(size uint32) FramerOption { + return func(f *Framer) { + f.maxHeaderFieldSize = size + } +} + +// WithMaxHeaderCount sets the maximum number of headers in a frame. +func WithMaxHeaderCount(count uint32) FramerOption { + return func(f *Framer) { + f.maxHeaderCount = count + } +} diff --git a/kubelink/vendor/github.com/moby/spdystream/spdy/read.go b/kubelink/vendor/github.com/moby/spdystream/spdy/read.go index 75ea045b8..2abb69433 100644 --- a/kubelink/vendor/github.com/moby/spdystream/spdy/read.go +++ b/kubelink/vendor/github.com/moby/spdystream/spdy/read.go @@ -1,19 +1,3 @@ -/* - Copyright 2014-2021 Docker Inc. - - 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. -*/ - // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -24,6 +8,7 @@ import ( "compress/zlib" "encoding/binary" "io" + "io/ioutil" "net/http" "strings" ) @@ -59,6 +44,11 @@ func (frame *SettingsFrame) read(h ControlFrameHeader, f *Framer) error { if err := binary.Read(f.r, binary.BigEndian, &numSettings); err != nil { return err } + // Each setting is 8 bytes (4-byte id + 4-byte value). + // Payload is 4 bytes for numSettings + numSettings*8. + if h.length < 4 || numSettings > (h.length-4)/8 { + return &Error{InvalidControlFrame, 0} + } frame.FlagIdValues = make([]SettingsFlagIdValue, numSettings) for i := uint32(0); i < numSettings; i++ { if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Id); err != nil { @@ -177,8 +167,19 @@ func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) ( if err := binary.Read(f.r, binary.BigEndian, &length); err != nil { return nil, err } + maxControlFramePayload := uint32(MaxDataLength) + if f.maxFrameLength > 0 { + maxControlFramePayload = f.maxFrameLength + } + flags := ControlFlags((length & 0xff000000) >> 24) length &= 0xffffff + if length > maxControlFramePayload { + if _, err := io.CopyN(ioutil.Discard, f.r, int64(length)); err != nil { + return nil, err + } + return nil, &Error{InvalidControlFrame, 0} + } header := ControlFrameHeader{version, frameType, flags, length} cframe, err := newControlFrame(frameType) if err != nil { @@ -190,11 +191,22 @@ func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) ( return cframe, nil } -func parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) { +func (f *Framer) parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) { var numHeaders uint32 if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil { return nil, err } + maxHeaders := defaultMaxHeaderCount + if f.maxHeaderCount > 0 { + maxHeaders = f.maxHeaderCount + } + if numHeaders > maxHeaders { + return nil, &Error{InvalidControlFrame, streamId} + } + maxFieldSize := defaultMaxHeaderFieldSize + if f.maxHeaderFieldSize > 0 { + maxFieldSize = f.maxHeaderFieldSize + } var e error h := make(http.Header, int(numHeaders)) for i := 0; i < int(numHeaders); i++ { @@ -202,6 +214,9 @@ func parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) if err := binary.Read(r, binary.BigEndian, &length); err != nil { return nil, err } + if length > maxFieldSize { + return nil, &Error{InvalidControlFrame, streamId} + } nameBytes := make([]byte, length) if _, err := io.ReadFull(r, nameBytes); err != nil { return nil, err @@ -217,6 +232,9 @@ func parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) if err := binary.Read(r, binary.BigEndian, &length); err != nil { return nil, err } + if length > maxFieldSize { + return nil, &Error{InvalidControlFrame, streamId} + } value := make([]byte, length) if _, err := io.ReadFull(r, value); err != nil { return nil, err @@ -256,7 +274,7 @@ func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame) } reader = f.headerDecompressor } - frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId) + frame.Headers, err = f.parseHeaderValueBlock(reader, frame.StreamId) if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) { err = &Error{WrongCompressedPayloadSize, 0} } @@ -288,7 +306,7 @@ func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) e } reader = f.headerDecompressor } - frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId) + frame.Headers, err = f.parseHeaderValueBlock(reader, frame.StreamId) if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) { err = &Error{WrongCompressedPayloadSize, 0} } @@ -320,7 +338,7 @@ func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) err } reader = f.headerDecompressor } - frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId) + frame.Headers, err = f.parseHeaderValueBlock(reader, frame.StreamId) if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) { err = &Error{WrongCompressedPayloadSize, 0} } diff --git a/kubelink/vendor/github.com/moby/spdystream/spdy/types.go b/kubelink/vendor/github.com/moby/spdystream/spdy/types.go index a254a43ab..a5528618c 100644 --- a/kubelink/vendor/github.com/moby/spdystream/spdy/types.go +++ b/kubelink/vendor/github.com/moby/spdystream/spdy/types.go @@ -1,23 +1,9 @@ -/* - Copyright 2014-2021 Docker Inc. - - 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. -*/ - // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Modifications Copyright 2014-2021 Docker Inc. + // Package spdy implements the SPDY protocol (currently SPDY/3), described in // http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3. package spdy @@ -63,8 +49,20 @@ const ( ) // MaxDataLength is the maximum number of bytes that can be stored in one frame. +// +// SPDY frame headers encode the payload length using a 24-bit field, +// so the maximum representable size for both data and control frames +// is 2^24-1 bytes. +// +// See the SPDY/3 specification, "Frame Format": +// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1/ const MaxDataLength = 1<<24 - 1 +const ( + defaultMaxHeaderFieldSize uint32 = 1 << 20 + defaultMaxHeaderCount uint32 = 1000 +) + // headerValueSepator separates multiple header values. const headerValueSeparator = "\x00" @@ -269,6 +267,10 @@ type Framer struct { r io.Reader headerReader io.LimitedReader headerDecompressor io.ReadCloser + + maxFrameLength uint32 // overrides the default frame payload length limit. + maxHeaderFieldSize uint32 // overrides the default per-header name/value length limit. + maxHeaderCount uint32 // overrides the default header count limit. } // NewFramer allocates a new Framer for a given SPDY connection, represented by @@ -276,6 +278,16 @@ type Framer struct { // from/to the Reader and Writer, so the caller should pass in an appropriately // buffered implementation to optimize performance. func NewFramer(w io.Writer, r io.Reader) (*Framer, error) { + return newFramer(w, r) +} + +// NewFramerWithOptions allocates a new Framer for a given SPDY connection and +// applies frame parsing limits via options. +func NewFramerWithOptions(w io.Writer, r io.Reader, opts ...FramerOption) (*Framer, error) { + return newFramer(w, r, opts...) +} + +func newFramer(w io.Writer, r io.Reader, opts ...FramerOption) (*Framer, error) { compressBuf := new(bytes.Buffer) compressor, err := zlib.NewWriterLevelDict(compressBuf, zlib.BestCompression, []byte(headerDictionary)) if err != nil { @@ -287,5 +299,10 @@ func NewFramer(w io.Writer, r io.Reader) (*Framer, error) { headerCompressor: compressor, r: r, } + for _, opt := range opts { + if opt != nil { + opt(framer) + } + } return framer, nil } diff --git a/kubelink/vendor/github.com/moby/spdystream/spdy/write.go b/kubelink/vendor/github.com/moby/spdystream/spdy/write.go index ab6d91f3b..75084d35d 100644 --- a/kubelink/vendor/github.com/moby/spdystream/spdy/write.go +++ b/kubelink/vendor/github.com/moby/spdystream/spdy/write.go @@ -1,19 +1,3 @@ -/* - Copyright 2014-2021 Docker Inc. - - 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. -*/ - // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -23,6 +7,7 @@ package spdy import ( "encoding/binary" "io" + "math" "net/http" "strings" ) @@ -63,13 +48,21 @@ func (frame *RstStreamFrame) write(f *Framer) (err error) { func (frame *SettingsFrame) write(f *Framer) (err error) { frame.CFHeader.version = Version frame.CFHeader.frameType = TypeSettings - frame.CFHeader.length = uint32(len(frame.FlagIdValues)*8 + 4) + payloadLen := len(frame.FlagIdValues)*8 + 4 + if payloadLen > MaxDataLength { + return &Error{InvalidControlFrame, 0} + } + frame.CFHeader.length = uint32(payloadLen) // Serialize frame to Writer. if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { return } - if err = binary.Write(f.w, binary.BigEndian, uint32(len(frame.FlagIdValues))); err != nil { + n := len(frame.FlagIdValues) + if uint64(n) > math.MaxUint32 { + return &Error{InvalidControlFrame, 0} + } + if err = binary.Write(f.w, binary.BigEndian, uint32(n)); err != nil { return } for _, flagIdValue := range frame.FlagIdValues { @@ -170,29 +163,41 @@ func writeControlFrameHeader(w io.Writer, h ControlFrameHeader) error { func writeHeaderValueBlock(w io.Writer, h http.Header) (n int, err error) { n = 0 - if err = binary.Write(w, binary.BigEndian, uint32(len(h))); err != nil { + numHeaders := len(h) + if numHeaders > math.MaxInt32 { + return n, &Error{InvalidControlFrame, 0} + } + if err = binary.Write(w, binary.BigEndian, uint32(numHeaders)); err != nil { return } - n += 2 + n += 4 for name, values := range h { - if err = binary.Write(w, binary.BigEndian, uint32(len(name))); err != nil { + nameLen := len(name) + if nameLen > math.MaxInt32 { + return n, &Error{InvalidControlFrame, 0} + } + if err = binary.Write(w, binary.BigEndian, uint32(nameLen)); err != nil { return } - n += 2 + n += 4 name = strings.ToLower(name) if _, err = io.WriteString(w, name); err != nil { return } - n += len(name) + n += nameLen v := strings.Join(values, headerValueSeparator) - if err = binary.Write(w, binary.BigEndian, uint32(len(v))); err != nil { + vLen := len(v) + if vLen > math.MaxInt32 { + return n, &Error{InvalidControlFrame, 0} + } + if err = binary.Write(w, binary.BigEndian, uint32(vLen)); err != nil { return } - n += 2 + n += 4 if _, err = io.WriteString(w, v); err != nil { return } - n += len(v) + n += vLen } return } @@ -216,7 +221,11 @@ func (f *Framer) writeSynStreamFrame(frame *SynStreamFrame) (err error) { // Set ControlFrameHeader. frame.CFHeader.version = Version frame.CFHeader.frameType = TypeSynStream - frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 10) + hLen := len(f.headerBuf.Bytes()) + 10 + if hLen > MaxDataLength { + return &Error{InvalidControlFrame, 0} + } + frame.CFHeader.length = uint32(hLen) // Serialize frame to Writer. if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { @@ -260,7 +269,11 @@ func (f *Framer) writeSynReplyFrame(frame *SynReplyFrame) (err error) { // Set ControlFrameHeader. frame.CFHeader.version = Version frame.CFHeader.frameType = TypeSynReply - frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4) + hLen := len(f.headerBuf.Bytes()) + 4 + if hLen > MaxDataLength { + return &Error{InvalidControlFrame, 0} + } + frame.CFHeader.length = uint32(hLen) // Serialize frame to Writer. if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { @@ -295,7 +308,11 @@ func (f *Framer) writeHeadersFrame(frame *HeadersFrame) (err error) { // Set ControlFrameHeader. frame.CFHeader.version = Version frame.CFHeader.frameType = TypeHeaders - frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4) + hLen := len(f.headerBuf.Bytes()) + 4 + if hLen > MaxDataLength { + return &Error{InvalidControlFrame, 0} + } + frame.CFHeader.length = uint32(hLen) // Serialize frame to Writer. if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { @@ -323,7 +340,11 @@ func (f *Framer) writeDataFrame(frame *DataFrame) (err error) { if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { return } - flagsAndLength := uint32(frame.Flags)<<24 | uint32(len(frame.Data)) + dLen := len(frame.Data) + if dLen > MaxDataLength { + return &Error{InvalidDataFrame, frame.StreamId} + } + flagsAndLength := uint32(frame.Flags)<<24 | uint32(dLen) if err = binary.Write(f.w, binary.BigEndian, flagsAndLength); err != nil { return } diff --git a/kubelink/vendor/modules.txt b/kubelink/vendor/modules.txt index 533bb2ef4..33b0a762c 100644 --- a/kubelink/vendor/modules.txt +++ b/kubelink/vendor/modules.txt @@ -361,7 +361,7 @@ github.com/mitchellh/go-wordwrap # github.com/mitchellh/reflectwalk v1.0.2 ## explicit github.com/mitchellh/reflectwalk -# github.com/moby/spdystream v0.5.0 +# github.com/moby/spdystream v0.5.1 ## explicit; go 1.13 github.com/moby/spdystream github.com/moby/spdystream/spdy diff --git a/kubewatch/go.mod b/kubewatch/go.mod index 53a0d88c5..1485c2b3d 100644 --- a/kubewatch/go.mod +++ b/kubewatch/go.mod @@ -125,7 +125,7 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgtype v1.14.4 // indirect - github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/pgx/v5 v5.9.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect @@ -147,7 +147,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/spdystream v0.5.1 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/kubewatch/go.sum b/kubewatch/go.sum index 75dd63ffb..53dd3f6a2 100644 --- a/kubewatch/go.sum +++ b/kubewatch/go.sum @@ -321,8 +321,8 @@ github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgS github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/pgx/v5 v5.9.0 h1:T/dI+2TvmI2H8s/KH1/lXIbz1CUFk3gn5oTjr0/mBsE= +github.com/jackc/pgx/v5 v5.9.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -402,8 +402,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/.golangci.yml b/kubewatch/vendor/github.com/jackc/pgx/v5/.golangci.yml new file mode 100644 index 000000000..d0903ab3f --- /dev/null +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/.golangci.yml @@ -0,0 +1,26 @@ +# See for configurations: https://golangci-lint.run/usage/configuration/ +version: "2" + +linters: + default: none + enable: + - govet + - ineffassign + +# See: https://golangci-lint.run/usage/formatters/ +formatters: + enable: + - gofmt # https://pkg.go.dev/cmd/gofmt + - gofumpt # https://github.com/mvdan/gofumpt + + settings: + gofmt: + simplify: true # Simplify code: gofmt with `-s` option. + + gofumpt: + # Module path which contains the source code being formatted. + # Default: "" + module-path: github.com/jackc/pgx/v5 # Should match with module in go.mod + # Choose whether to use the extra rules. + # Default: false + extra-rules: true diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/CHANGELOG.md b/kubewatch/vendor/github.com/jackc/pgx/v5/CHANGELOG.md index 1e56878e8..49efb6711 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/CHANGELOG.md +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/CHANGELOG.md @@ -1,3 +1,82 @@ +# 5.9.0 (March 21, 2026) + +This release includes a number of new features such as SCRAM-SHA-256-PLUS support, OAuth authentication support, and +PostgreSQL protocol 3.2 support. + +It significantly reduces the amount of network traffic when using prepared statements (which are used automatically by +default) by avoiding unnecessary Describe Portal messages. This also reduces local memory usage. + +It also includes multiple fixes for potential DoS due to panic or OOM if connected to a malicious server that sends +deliberately malformed messages. + +* Require Go 1.25+ +* Add SCRAM-SHA-256-PLUS support (Adam Brightwell) +* Add OAuth authentication support for PostgreSQL 18 (David Schneider) +* Add PostgreSQL protocol 3.2 support (Dirkjan Bussink) +* Add tsvector type support (Adam Brightwell) +* Skip Describe Portal for cached prepared statements reducing network round trips +* Make LoadTypes query easier to support on "postgres-like" servers (Jelte Fennema-Nio) +* Default empty user to current OS user matching libpq behavior (ShivangSrivastava) +* Optimize LRU statement cache with custom linked list and node pooling (Mathias Bogaert) +* Optimize date scanning by replacing regex with manual parsing (Mathias Bogaert) +* Optimize pgio append/set functions with direct byte shifts (Mathias Bogaert) +* Make RowsAffected faster (Abhishek Chanda) +* Fix: Pipeline.Close panic when server sends multiple FATAL errors (Varun Chawla) +* Fix: ContextWatcher goroutine leak (Hank Donnay) +* Fix: stdlib discard connections with open transactions in ResetSession (Jeremy Schneider) +* Fix: pipelineBatchResults.Exec silently swallowing lastRows error +* Fix: ColumnTypeLength using BPCharArrayOID instead of BPCharOID +* Fix: TSVector text encoding returning nil for valid empty tsvector +* Fix: wrong error messages for Int2 and Int4 underflow +* Fix: Numeric nil Int pointer dereference with Valid: true +* Fix: reversed strings.ContainsAny arguments in Numeric.ScanScientific +* Fix: message length parsing on 32-bit platforms +* Fix: FunctionCallResponse.Decode mishandling of signed result size +* Fix: returning wrong error in configTLS when DecryptPEMBlock fails (Maxim Motyshen) +* Fix: misleading ParseConfig error when default_query_exec_mode is invalid (Skarm) +* Fix: missed Unwatch in Pipeline error paths +* Clarify too many failed acquire attempts error message +* Better error wrapping with context and SQL statement (Aneesh Makala) +* Enable govet and ineffassign linters (Federico Guerinoni) +* Guard against various malformed binary messages (arrays, hstore, multirange, protocol messages) +* Fix various godoc comments (ferhat elmas) +* Fix typos in comments (Oleksandr Redko) + +# 5.8.0 (December 26, 2025) + +* Require Go 1.24+ +* Remove golang.org/x/crypto dependency +* Add OptionShouldPing to control ResetSession ping behavior (ilyam8) +* Fix: Avoid overflow when MaxConns is set to MaxInt32 +* Fix: Close batch pipeline after a query error (Anthonin Bonnefoy) +* Faster shutdown of pgxpool.Pool background goroutines (Blake Gentry) +* Add pgxpool ping timeout (Amirsalar Safaei) +* Fix: Rows.FieldDescriptions for empty query +* Scan unknown types into *any as string or []byte based on format code +* Optimize pgtype.Numeric (Philip Dubé) +* Add AfterNetConnect hook to pgconn.Config +* Fix: Handle for preparing statements that fail during the Describe phase +* Fix overflow in numeric scanning (Ilia Demianenko) +* Fix: json/jsonb sql.Scanner source type is []byte +* Migrate from math/rand to math/rand/v2 (Mathias Bogaert) +* Optimize internal iobufpool (Mathias Bogaert) +* Optimize stmtcache invalidation (Mathias Bogaert) +* Fix: missing error case in interval parsing (Maxime Soulé) +* Fix: invalidate statement/description cache in Exec (James Hartig) +* ColumnTypeLength method return the type length for varbit type (DengChan) +* Array and Composite codecs handle typed nils + +# 5.7.6 (September 8, 2025) + +* Use ParseConfigError in pgx.ParseConfig and pgxpool.ParseConfig (Yurasov Ilia) +* Add PrepareConn hook to pgxpool (Jonathan Hall) +* Reduce allocations in QueryContext (Dominique Lefevre) +* Add MarshalJSON and UnmarshalJSON for pgtype.Uint32 (Panos Koutsovasilis) +* Configure ping behavior on pgxpool with ShouldPing (Christian Kiely) +* zeronull int types implement Int64Valuer and Int64Scanner (Li Zeghong) +* Fix panic when receiving terminate connection message during CopyFrom (Michal Drausowski) +* Fix statement cache not being invalidated on error during batch (Muhammadali Nazarov) + # 5.7.5 (May 17, 2025) * Support sslnegotiation connection option (divyam234) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/CLAUDE.md b/kubewatch/vendor/github.com/jackc/pgx/v5/CLAUDE.md new file mode 100644 index 000000000..e3ed1a2ec --- /dev/null +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/CLAUDE.md @@ -0,0 +1,73 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +pgx is a PostgreSQL driver and toolkit for Go (`github.com/jackc/pgx/v5`). It provides both a native PostgreSQL interface and a `database/sql` compatible driver. Requires Go 1.25+ and supports PostgreSQL 14+ and CockroachDB. + +## Build & Test Commands + +```bash +# Run all tests (requires PGX_TEST_DATABASE to be set) +go test ./... + +# Run a specific test +go test -run TestFunctionName ./... + +# Run tests for a specific package +go test ./pgconn/... + +# Run tests with race detector +go test -race ./... + +# DevContainer: run tests against specific PostgreSQL versions +./test.sh pg18 # Default: PostgreSQL 18 +./test.sh pg16 -run TestConnect # Specific test against PG16 +./test.sh crdb # CockroachDB +./test.sh all # All targets (pg14-18 + crdb) + +# Format (always run after making changes) +goimports -w . + +# Lint +golangci-lint run ./... +``` + +## Test Database Setup + +Tests require `PGX_TEST_DATABASE` environment variable. In the devcontainer, `test.sh` handles this. For local development: + +```bash +export PGX_TEST_DATABASE="host=localhost user=postgres password=postgres dbname=pgx_test" +``` + +The test database needs extensions: `hstore`, `ltree`, and a `uint64` domain. See `testsetup/postgresql_setup.sql` for full setup. Many tests are skipped unless additional `PGX_TEST_*` env vars are set (for TLS, SCRAM, MD5, unix socket, PgBouncer testing). + +## Architecture + +The codebase is a layered architecture, bottom-up: + +- **pgproto3/** — PostgreSQL wire protocol v3 encoder/decoder. Defines `FrontendMessage` and `BackendMessage` types for every protocol message. +- **pgconn/** — Low-level connection layer (roughly libpq-equivalent). Handles authentication, TLS, query execution, COPY protocol, and notifications. `PgConn` is the core type. +- **pgx** (root package) — High-level query interface built on `pgconn`. Provides `Conn`, `Rows`, `Tx`, `Batch`, `CopyFrom`, and generic helpers like `CollectRows`/`ForEachRow`. Includes automatic statement caching (LRU). +- **pgtype/** — Type system mapping between Go and PostgreSQL types (70+ types). Key interfaces: `Codec`, `Type`, `TypeMap`. Custom types (enums, composites, domains) are registered through `TypeMap`. +- **pgxpool/** — Concurrency-safe connection pool built on `puddle/v2`. `Pool` is the main type; wraps `pgx.Conn`. +- **stdlib/** — `database/sql` compatibility adapter. + +Supporting packages: +- **internal/stmtcache/** — Prepared statement cache with LRU eviction +- **internal/sanitize/** — SQL query sanitization +- **tracelog/** — Logging adapter that implements tracer interfaces +- **multitracer/** — Composes multiple tracers into one +- **pgxtest/** — Test helpers for running tests across connection types + +## Key Design Conventions + +- **Semantic versioning** — strictly followed. Do not break the public API (no removing or renaming exported types, functions, methods, or fields; no changing function signatures). +- **Minimal dependencies** — adding new dependencies is strongly discouraged (see CONTRIBUTING.md). +- **Context-based** — all blocking operations take `context.Context`. +- **Tracer interfaces** — observability via `QueryTracer`, `BatchTracer`, `CopyFromTracer`, `PrepareTracer` on `ConnConfig.Tracer`. +- **Formatting** — always run `goimports -w .` after making changes to ensure code is properly formatted. CI checks formatting via `gofmt -l -s -w . && git diff --exit-code`. `gofumpt` with extra rules is also enforced via `golangci-lint`. +- **Linters** — `govet` and `ineffassign` only (configured in `.golangci.yml`). +- **CI matrix** — tests run against Go 1.25/1.26 × PostgreSQL 14-18 + CockroachDB, on Linux and Windows. Race detector enabled on Linux only. diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/CONTRIBUTING.md b/kubewatch/vendor/github.com/jackc/pgx/v5/CONTRIBUTING.md index c975a9372..2283ae670 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/CONTRIBUTING.md +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/CONTRIBUTING.md @@ -10,6 +10,18 @@ proposal. This will help to ensure your proposed change has a reasonable chance Adding a dependency is a big deal. While on occasion a new dependency may be accepted, the default answer to any change that adds a dependency is no. +## AI + +Using AI is acceptable (not that it can really be stopped) under one the following conditions. + +* AI was used, but you deeply understand the code and you can answer questions regarding your change. You are not going + to answer questions with "I don't know", AI did it. You are not going to "answer" questions by relaying them to your + agent. This is wasteful of the code reviewer's time. +* AI was used to solve a problem without your deep understanding. This can still be a good starting point for a fix or + feature. But you need to clearly state that this is an AI proposal. You should include additional information such as + the AI used and what prompts were used. You should also be aware that large, complicated, or subtle changes may be + rejected simply because the reviewer is not confident in a change that no human understands. + ## Development Environment Setup pgx tests naturally require a PostgreSQL database. It will connect to the database specified in the `PGX_TEST_DATABASE` @@ -17,7 +29,12 @@ environment variable. The `PGX_TEST_DATABASE` environment variable can either be the standard `PG*` environment variables will be respected. Consider using [direnv](https://github.com/direnv/direnv) to simplify environment variable handling. -### Using an Existing PostgreSQL Cluster +### Devcontainer + +The easiest way to start development is with the included devcontainer. It includes containers for each supported +PostgreSQL version as well as CockroachDB. `./test.sh all` will run the tests against all database types. + +### Using an Existing PostgreSQL Cluster Outside of a Devcontainer If you already have a PostgreSQL development server this is the quickest way to start and run the majority of the pgx test suite. Some tests will be skipped that require server configuration changes (e.g. those testing different @@ -49,7 +66,7 @@ go test ./... This will run the vast majority of the tests, but some tests will be skipped (e.g. those testing different connection methods). -### Creating a New PostgreSQL Cluster Exclusively for Testing +### Creating a New PostgreSQL Cluster Exclusively for Testing Outside of a Devcontainer The following environment variables need to be set both for initial setup and whenever the tests are run. (direnv is highly recommended). Depending on your platform, you may need to change the host for `PGX_TEST_UNIX_SOCKET_CONN_STRING`. @@ -63,10 +80,11 @@ export POSTGRESQL_DATA_DIR=postgresql export PGX_TEST_DATABASE="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret" export PGX_TEST_UNIX_SOCKET_CONN_STRING="host=/private/tmp database=pgx_test" export PGX_TEST_TCP_CONN_STRING="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret" -export PGX_TEST_SCRAM_PASSWORD_CONN_STRING="host=127.0.0.1 user=pgx_scram password=secret database=pgx_test" +export PGX_TEST_SCRAM_PASSWORD_CONN_STRING="host=127.0.0.1 user=pgx_scram password=secret database=pgx_test channel_binding=disable" +export PGX_TEST_SCRAM_PLUS_CONN_STRING="host=localhost user=pgx_ssl password=secret sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem database=pgx_test channel_binding=require" export PGX_TEST_MD5_PASSWORD_CONN_STRING="host=127.0.0.1 database=pgx_test user=pgx_md5 password=secret" export PGX_TEST_PLAIN_PASSWORD_CONN_STRING="host=127.0.0.1 user=pgx_pw password=secret" -export PGX_TEST_TLS_CONN_STRING="host=localhost user=pgx_ssl password=secret sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem" +export PGX_TEST_TLS_CONN_STRING="host=localhost user=pgx_ssl password=secret sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem channel_binding=disable" export PGX_SSL_PASSWORD=certpw export PGX_TEST_TLS_CLIENT_CONN_STRING="host=localhost user=pgx_sslcert sslmode=verify-full sslrootcert=`pwd`/.testdb/ca.pem database=pgx_test sslcert=`pwd`/.testdb/pgx_sslcert.crt sslkey=`pwd`/.testdb/pgx_sslcert.key" ``` diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/README.md b/kubewatch/vendor/github.com/jackc/pgx/v5/README.md index 0138c2c76..aa35e4a3d 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/README.md +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/README.md @@ -92,7 +92,7 @@ See the presentation at Golang Estonia, [PGX Top to Bottom](https://www.youtube. ## Supported Go and PostgreSQL Versions -pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.23 and higher and PostgreSQL 13 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/). +pgx supports the same versions of Go and PostgreSQL that are supported by their respective teams. For [Go](https://golang.org/doc/devel/release.html#policy) that is the two most recent major releases and for [PostgreSQL](https://www.postgresql.org/support/versioning/) the major releases in the last 5 years. This means pgx supports Go 1.25 and higher and PostgreSQL 14 and higher. pgx also is tested against the latest version of [CockroachDB](https://www.cockroachlabs.com/product/). ## Version Policy @@ -120,6 +120,7 @@ pgerrcode contains constants for the PostgreSQL error codes. * [github.com/jackc/pgx-gofrs-uuid](https://github.com/jackc/pgx-gofrs-uuid) * [github.com/jackc/pgx-shopspring-decimal](https://github.com/jackc/pgx-shopspring-decimal) +* [github.com/ColeBurch/pgx-govalues-decimal](https://github.com/ColeBurch/pgx-govalues-decimal) * [github.com/twpayne/pgx-geos](https://github.com/twpayne/pgx-geos) ([PostGIS](https://postgis.net/) and [GEOS](https://libgeos.org/) via [go-geos](https://github.com/twpayne/go-geos)) * [github.com/vgarvardt/pgx-google-uuid](https://github.com/vgarvardt/pgx-google-uuid) @@ -127,6 +128,7 @@ pgerrcode contains constants for the PostgreSQL error codes. ## Adapters for 3rd Party Tracers * [github.com/jackhopner/pgx-xray-tracer](https://github.com/jackhopner/pgx-xray-tracer) +* [github.com/exaring/otelpgx](https://github.com/exaring/otelpgx) ## Adapters for 3rd Party Loggers @@ -184,3 +186,7 @@ Simple Golang implementation for transactional outbox pattern for PostgreSQL usi ### [https://github.com/Arlandaren/pgxWrappy](https://github.com/Arlandaren/pgxWrappy) Simplifies working with the pgx library, providing convenient scanning of nested structures. + +### [https://github.com/KoNekoD/pgx-colon-query-rewriter](https://github.com/KoNekoD/pgx-colon-query-rewriter) + +Implementation of the pgx query rewriter to use ':' instead of '@' in named query parameters. diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/batch.go b/kubewatch/vendor/github.com/jackc/pgx/v5/batch.go index c3c2834f2..702fcff5b 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/batch.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/batch.go @@ -43,6 +43,10 @@ func (qq *QueuedQuery) QueryRow(fn func(row Row) error) { } // Exec sets fn to be called when the response to qq is received. +// +// Note: for simple batch insert uses where it is not required to handle +// each potential error individually, it's sufficient to not set any callbacks, +// and just handle the return value of BatchResults.Close. func (qq *QueuedQuery) Exec(fn func(ct pgconn.CommandTag) error) { qq.Fn = func(br BatchResults) error { ct, err := br.Exec() @@ -83,7 +87,7 @@ func (b *Batch) Len() int { type BatchResults interface { // Exec reads the results from the next query in the batch as if the query has been sent with Conn.Exec. Prefer - // calling Exec on the QueuedQuery. + // calling Exec on the QueuedQuery, or just calling Close. Exec() (pgconn.CommandTag, error) // Query reads the results from the next query in the batch as if the query has been sent with Conn.Query. Prefer @@ -98,6 +102,9 @@ type BatchResults interface { // QueuedQuery.Query, QueuedQuery.QueryRow, or QueuedQuery.Exec will be called. If a callback function returns an // error or the batch encounters an error subsequent callback functions will not be called. // + // For simple batch inserts inside a transaction or similar queries, it's sufficient to not set any callbacks, + // and just handle the return value of Close. + // // Close must be called before the underlying connection can be used again. Any error that occurred during a batch // operation may have made it impossible to resyncronize the connection with the server. In this case the underlying // connection will have been closed. @@ -207,7 +214,6 @@ func (br *batchResults) Query() (Rows, error) { func (br *batchResults) QueryRow() Row { rows, _ := br.Query() return (*connRow)(rows.(*baseRows)) - } // Close closes the batch operation. Any error that occurred during a batch operation may have made it impossible to @@ -220,6 +226,8 @@ func (br *batchResults) Close() error { } br.endTraced = true } + + invalidateCachesOnBatchResultsError(br.conn, br.b, br.err) }() if br.err != nil { @@ -264,7 +272,7 @@ func (br *batchResults) nextQueryAndArgs() (query string, args []any, ok bool) { ok = true br.qqIdx++ } - return + return query, args, ok } type pipelineBatchResults struct { @@ -288,6 +296,7 @@ func (br *pipelineBatchResults) Exec() (pgconn.CommandTag, error) { return pgconn.CommandTag{}, fmt.Errorf("batch already closed") } if br.lastRows != nil && br.lastRows.err != nil { + br.err = br.lastRows.err return pgconn.CommandTag{}, br.err } @@ -378,7 +387,6 @@ func (br *pipelineBatchResults) Query() (Rows, error) { func (br *pipelineBatchResults) QueryRow() Row { rows, _ := br.Query() return (*connRow)(rows.(*baseRows)) - } // Close closes the batch operation. Any error that occurred during a batch operation may have made it impossible to @@ -391,11 +399,12 @@ func (br *pipelineBatchResults) Close() error { } br.endTraced = true } + + invalidateCachesOnBatchResultsError(br.conn, br.b, br.err) }() if br.err == nil && br.lastRows != nil && br.lastRows.err != nil { br.err = br.lastRows.err - return br.err } if br.closed { @@ -441,3 +450,87 @@ func (br *pipelineBatchResults) nextQueryAndArgs() (query string, args []any, er br.qqIdx++ return bi.SQL, bi.Arguments, nil } + +type emptyBatchResults struct { + conn *Conn + closed bool +} + +// Exec reads the results from the next query in the batch as if the query has been sent with Exec. +func (br *emptyBatchResults) Exec() (pgconn.CommandTag, error) { + if br.closed { + return pgconn.CommandTag{}, fmt.Errorf("batch already closed") + } + return pgconn.CommandTag{}, errors.New("no more results in batch") +} + +// Query reads the results from the next query in the batch as if the query has been sent with Query. +func (br *emptyBatchResults) Query() (Rows, error) { + if br.closed { + alreadyClosedErr := fmt.Errorf("batch already closed") + return &baseRows{err: alreadyClosedErr, closed: true}, alreadyClosedErr + } + + rows := br.conn.getRows(context.Background(), "", nil) + rows.err = errors.New("no more results in batch") + rows.closed = true + return rows, rows.err +} + +// QueryRow reads the results from the next query in the batch as if the query has been sent with QueryRow. +func (br *emptyBatchResults) QueryRow() Row { + rows, _ := br.Query() + return (*connRow)(rows.(*baseRows)) +} + +// Close closes the batch operation. Any error that occurred during a batch operation may have made it impossible to +// resyncronize the connection with the server. In this case the underlying connection will have been closed. +func (br *emptyBatchResults) Close() error { + br.closed = true + return nil +} + +// invalidates statement and description caches on batch results error +func invalidateCachesOnBatchResultsError(conn *Conn, b *Batch, err error) { + if err != nil && conn != nil && b != nil { + if sc := conn.statementCache; sc != nil { + for _, bi := range b.QueuedQueries { + sc.Invalidate(bi.SQL) + } + } + + if sc := conn.descriptionCache; sc != nil { + for _, bi := range b.QueuedQueries { + sc.Invalidate(bi.SQL) + } + } + } +} + +// ErrPreprocessingBatch occurs when an error is encountered while preprocessing a batch. +// The two preprocessing steps are "prepare" (server-side SQL parse/plan) and +// "build" (client-side argument encoding). +type ErrPreprocessingBatch struct { + step string // "prepare" or "build" + sql string + err error +} + +func newErrPreprocessingBatch(step, sql string, err error) ErrPreprocessingBatch { + return ErrPreprocessingBatch{step: step, sql: sql, err: err} +} + +func (e ErrPreprocessingBatch) Error() string { + // intentionally not including the SQL query in the error message + // to avoid leaking potentially sensitive information into logs. + // If the user wants the SQL, they can call SQL(). + return fmt.Sprintf("error preprocessing batch (%s): %v", e.step, e.err) +} + +func (e ErrPreprocessingBatch) Unwrap() error { + return e.err +} + +func (e ErrPreprocessingBatch) SQL() string { + return e.sql +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/conn.go b/kubewatch/vendor/github.com/jackc/pgx/v5/conn.go index 93e2e7182..c52039b7a 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/conn.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/conn.go @@ -65,11 +65,12 @@ func (cc *ConnConfig) ConnString() string { return cc.connString } // Conn is a PostgreSQL connection handle. It is not safe for concurrent usage. Use a connection pool to manage access // to multiple database connections from multiple goroutines. type Conn struct { - pgConn *pgconn.PgConn - config *ConnConfig // config used when establishing this connection - preparedStatements map[string]*pgconn.StatementDescription - statementCache stmtcache.Cache - descriptionCache stmtcache.Cache + pgConn *pgconn.PgConn + config *ConnConfig // config used when establishing this connection + preparedStatements map[string]*pgconn.StatementDescription + failedDescribeStatement string + statementCache stmtcache.Cache + descriptionCache stmtcache.Cache queryTracer QueryTracer batchTracer BatchTracer @@ -172,7 +173,7 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con delete(config.RuntimeParams, "statement_cache_capacity") n, err := strconv.ParseInt(s, 10, 32) if err != nil { - return nil, fmt.Errorf("cannot parse statement_cache_capacity: %w", err) + return nil, pgconn.NewParseConfigError(connString, "cannot parse statement_cache_capacity", err) } statementCacheCapacity = int(n) } @@ -182,7 +183,7 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con delete(config.RuntimeParams, "description_cache_capacity") n, err := strconv.ParseInt(s, 10, 32) if err != nil { - return nil, fmt.Errorf("cannot parse description_cache_capacity: %w", err) + return nil, pgconn.NewParseConfigError(connString, "cannot parse description_cache_capacity", err) } descriptionCacheCapacity = int(n) } @@ -202,7 +203,9 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con case "simple_protocol": defaultQueryExecMode = QueryExecModeSimpleProtocol default: - return nil, fmt.Errorf("invalid default_query_exec_mode: %s", s) + return nil, pgconn.NewParseConfigError( + connString, "invalid default_query_exec_mode", fmt.Errorf("unknown value %q", s), + ) } } @@ -314,6 +317,14 @@ func (c *Conn) Close(ctx context.Context) error { // Prepare is idempotent; i.e. it is safe to call Prepare multiple times with the same name and sql arguments. This // allows a code path to Prepare and Query/Exec without concern for if the statement has already been prepared. func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error) { + if c.failedDescribeStatement != "" { + err = c.Deallocate(ctx, c.failedDescribeStatement) + if err != nil { + return nil, fmt.Errorf("failed to deallocate previously failed statement %q: %w", c.failedDescribeStatement, err) + } + c.failedDescribeStatement = "" + } + if c.prepareTracer != nil { ctx = c.prepareTracer.TracePrepareStart(ctx, c, TracePrepareStartData{Name: name, SQL: sql}) } @@ -346,6 +357,10 @@ func (c *Conn) Prepare(ctx context.Context, name, sql string) (sd *pgconn.Statem sd, err = c.pgConn.Prepare(ctx, psName, sql, nil) if err != nil { + var pErr *pgconn.PrepareError + if errors.As(err, &pErr) { + c.failedDescribeStatement = psKey + } return nil, err } @@ -502,6 +517,18 @@ optionLoop: mode = QueryExecModeSimpleProtocol } + defer func() { + if err != nil { + if sc := c.statementCache; sc != nil { + sc.Invalidate(sql) + } + + if sc := c.descriptionCache; sc != nil { + sc.Invalidate(sql) + } + } + }() + if sd, ok := c.preparedStatements[sql]; ok { return c.execPrepared(ctx, sd, arguments) } @@ -583,7 +610,7 @@ func (c *Conn) execPrepared(ctx context.Context, sd *pgconn.StatementDescription return pgconn.CommandTag{}, err } - result := c.pgConn.ExecPrepared(ctx, sd.Name, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats).Read() + result := c.pgConn.ExecStatement(ctx, sd, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats).Read() c.eqb.reset() // Allow c.eqb internal memory to be GC'ed as soon as possible. return result.CommandTag, result.Err } @@ -817,7 +844,7 @@ optionLoop: if !explicitPreparedStatement && mode == QueryExecModeCacheDescribe { rows.resultReader = c.pgConn.ExecParams(ctx, sql, c.eqb.ParamValues, sd.ParamOIDs, c.eqb.ParamFormats, resultFormats) } else { - rows.resultReader = c.pgConn.ExecPrepared(ctx, sd.Name, c.eqb.ParamValues, c.eqb.ParamFormats, resultFormats) + rows.resultReader = c.pgConn.ExecStatement(ctx, sd, c.eqb.ParamValues, c.eqb.ParamFormats, resultFormats) } } else if mode == QueryExecModeExec { err := c.eqb.Build(c.typeMap, nil, args) @@ -912,6 +939,10 @@ func (c *Conn) QueryRow(ctx context.Context, sql string, args ...any) Row { // Depending on the QueryExecMode, all queries may be prepared before any are executed. This means that creating a table // and using it in a subsequent query in the same batch can fail. func (c *Conn) SendBatch(ctx context.Context, b *Batch) (br BatchResults) { + if len(b.QueuedQueries) == 0 { + return &emptyBatchResults{conn: c} + } + if c.batchTracer != nil { ctx = c.batchTracer.TraceBatchStart(ctx, c, TraceBatchStartData{Batch: b}) defer func() { @@ -1163,7 +1194,7 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d for _, sd := range distinctNewQueries { results, err := pipeline.GetResults() if err != nil { - return err + return newErrPreprocessingBatch("prepare", sd.SQL, err) } resultSD, ok := results.(*pgconn.StatementDescription) @@ -1197,15 +1228,14 @@ func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, d for _, bi := range b.QueuedQueries { err := c.eqb.Build(c.typeMap, bi.sd, bi.Arguments) if err != nil { - // we wrap the error so we the user can understand which query failed inside the batch - err = fmt.Errorf("error building query %s: %w", bi.SQL, err) + err = newErrPreprocessingBatch("build", bi.SQL, err) return &pipelineBatchResults{ctx: ctx, conn: c, err: err, closed: true} } if bi.sd.Name == "" { pipeline.SendQueryParams(bi.sd.SQL, c.eqb.ParamValues, bi.sd.ParamOIDs, c.eqb.ParamFormats, c.eqb.ResultFormats) } else { - pipeline.SendQueryPrepared(bi.sd.Name, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats) + pipeline.SendQueryStatement(bi.sd, c.eqb.ParamValues, c.eqb.ParamFormats, c.eqb.ResultFormats) } } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/derived_types.go b/kubewatch/vendor/github.com/jackc/pgx/v5/derived_types.go index 72c0a2423..89b9a77c1 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/derived_types.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/derived_types.go @@ -24,7 +24,7 @@ func buildLoadDerivedTypesSQL(pgVersion int64, typeNames []string) string { // This should not occur; this will not return any types typeNamesClause = "= ''" } else { - typeNamesClause = "= ANY($1)" + typeNamesClause = "= ANY($1::text[])" } parts := make([]string, 0, 10) @@ -169,7 +169,7 @@ func (c *Conn) LoadTypes(ctx context.Context, typeNames []string) ([]*pgtype.Typ // the SQL not support recent structures such as multirange serverVersion, _ := serverVersion(c) sql := buildLoadDerivedTypesSQL(serverVersion, typeNames) - rows, err := c.Query(ctx, sql, QueryExecModeSimpleProtocol, typeNames) + rows, err := c.Query(ctx, sql, QueryResultFormats{TextFormatCode}, typeNames) if err != nil { return nil, fmt.Errorf("While generating load types query: %w", err) } @@ -227,7 +227,7 @@ func (c *Conn) LoadTypes(ctx context.Context, typeNames []string) ([]*pgtype.Typ return nil, fmt.Errorf("Unknown typtype %q was found while registering %q", ti.Typtype, ti.TypeName) } - // the type_ is imposible to be null + // the type_ is impossible to be null m.RegisterType(type_) if ti.NspName != "" { nspType := &pgtype.Type{Name: ti.NspName + "." + type_.Name, OID: type_.OID, Codec: type_.Codec} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/iobufpool/iobufpool.go b/kubewatch/vendor/github.com/jackc/pgx/v5/internal/iobufpool/iobufpool.go index 89e0c2273..abc41f657 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/iobufpool/iobufpool.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/internal/iobufpool/iobufpool.go @@ -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<= len(pools) { + return -1 } - return -1 + return idx } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/pgio/write.go b/kubewatch/vendor/github.com/jackc/pgx/v5/internal/pgio/write.go index 96aedf9dd..3a6700dc4 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/pgio/write.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/internal/pgio/write.go @@ -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)} } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/sanitize/benchmmark.sh b/kubewatch/vendor/github.com/jackc/pgx/v5/internal/sanitize/benchmark.sh similarity index 97% rename from kubewatch/vendor/github.com/jackc/pgx/v5/internal/sanitize/benchmmark.sh rename to kubewatch/vendor/github.com/jackc/pgx/v5/internal/sanitize/benchmark.sh index ec0f7b03a..b4ee3fe74 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/sanitize/benchmmark.sh +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/internal/sanitize/benchmark.sh @@ -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 diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/stmtcache/lru_cache.go b/kubewatch/vendor/github.com/jackc/pgx/v5/internal/stmtcache/lru_cache.go index dec83f47b..b677d29cb 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/stmtcache/lru_cache.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/internal/stmtcache/lru_cache.go @@ -1,37 +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 @@ -46,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. @@ -90,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. @@ -104,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 } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/stmtcache/unlimited_cache.go b/kubewatch/vendor/github.com/jackc/pgx/v5/internal/stmtcache/unlimited_cache.go deleted file mode 100644 index 696413291..000000000 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/internal/stmtcache/unlimited_cache.go +++ /dev/null @@ -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 -} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/auth_oauth.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/auth_oauth.go new file mode 100644 index 000000000..991f6585d --- /dev/null +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/auth_oauth.go @@ -0,0 +1,67 @@ +package pgconn + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/jackc/pgx/v5/pgproto3" +) + +func (c *PgConn) oauthAuth(ctx context.Context) error { + if c.config.OAuthTokenProvider == nil { + return errors.New("OAuth authentication required but no token provider configured") + } + + token, err := c.config.OAuthTokenProvider(ctx) + if err != nil { + return fmt.Errorf("failed to obtain OAuth token: %w", err) + } + + // https://www.rfc-editor.org/rfc/rfc7628.html#section-3.1 + initialResponse := []byte("n,,\x01auth=Bearer " + token + "\x01\x01") + + saslInitialResponse := &pgproto3.SASLInitialResponse{ + AuthMechanism: "OAUTHBEARER", + Data: initialResponse, + } + c.frontend.Send(saslInitialResponse) + err = c.flushWithPotentialWriteReadDeadlock() + if err != nil { + return err + } + + msg, err := c.receiveMessage() + if err != nil { + return err + } + + switch m := msg.(type) { + case *pgproto3.AuthenticationOk: + return nil + case *pgproto3.AuthenticationSASLContinue: + // Server sent error response in SASL continue + // https://www.rfc-editor.org/rfc/rfc7628.html#section-3.2.2 + // https://www.rfc-editor.org/rfc/rfc7628.html#section-3.2.3 + errResponse := struct { + Status string `json:"status"` + Scope string `json:"scope"` + OpenIDConfiguration string `json:"openid-configuration"` + }{} + err := json.Unmarshal(m.Data, &errResponse) + if err != nil { + return fmt.Errorf("invalid OAuth error response from server: %w", err) + } + + // Per RFC 7628 section 3.2.3, we should send a SASLResponse which only contains \x01. + // However, since the connection will be closed anyway, we can skip this + return fmt.Errorf("OAuth authentication failed: %s", errResponse.Status) + + case *pgproto3.ErrorResponse: + return ErrorResponseToPgError(m) + + default: + return fmt.Errorf("unexpected message type during OAuth auth: %T", msg) + } +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/auth_scram.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/auth_scram.go index 064983615..f59d39c4e 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/auth_scram.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/auth_scram.go @@ -1,7 +1,8 @@ -// SCRAM-SHA-256 authentication +// SCRAM-SHA-256 and SCRAM-SHA-256-PLUS authentication // // Resources: // https://tools.ietf.org/html/rfc5802 +// https://tools.ietf.org/html/rfc5929 // https://tools.ietf.org/html/rfc8265 // https://www.postgresql.org/docs/current/sasl-authentication.html // @@ -15,19 +16,28 @@ package pgconn import ( "bytes" "crypto/hmac" + "crypto/pbkdf2" "crypto/rand" "crypto/sha256" + "crypto/sha512" + "crypto/tls" + "crypto/x509" "encoding/base64" "errors" "fmt" + "hash" + "slices" "strconv" "github.com/jackc/pgx/v5/pgproto3" - "golang.org/x/crypto/pbkdf2" "golang.org/x/text/secure/precis" ) -const clientNonceLen = 18 +const ( + clientNonceLen = 18 + scramSHA256Name = "SCRAM-SHA-256" + scramSHA256PlusName = "SCRAM-SHA-256-PLUS" +) // Perform SCRAM authentication. func (c *PgConn) scramAuth(serverAuthMechanisms []string) error { @@ -36,9 +46,35 @@ func (c *PgConn) scramAuth(serverAuthMechanisms []string) error { return err } + serverHasPlus := slices.Contains(sc.serverAuthMechanisms, scramSHA256PlusName) + if c.config.ChannelBinding == "require" && !serverHasPlus { + return errors.New("channel binding required but server does not support SCRAM-SHA-256-PLUS") + } + + // If we have a TLS connection and channel binding is not disabled, attempt to + // extract the server certificate hash for tls-server-end-point channel binding. + if tlsConn, ok := c.conn.(*tls.Conn); ok && c.config.ChannelBinding != "disable" { + certHash, err := getTLSCertificateHash(tlsConn) + if err != nil && c.config.ChannelBinding == "require" { + return fmt.Errorf("channel binding required but failed to get server certificate hash: %w", err) + } + + // Upgrade to SCRAM-SHA-256-PLUS if we have binding data and the server supports it. + if certHash != nil && serverHasPlus { + sc.authMechanism = scramSHA256PlusName + } + + sc.channelBindingData = certHash + sc.hasTLS = true + } + + if c.config.ChannelBinding == "require" && sc.channelBindingData == nil { + return errors.New("channel binding required but channel binding data is not available") + } + // Send client-first-message in a SASLInitialResponse saslInitialResponse := &pgproto3.SASLInitialResponse{ - AuthMechanism: "SCRAM-SHA-256", + AuthMechanism: sc.authMechanism, Data: sc.clientFirstMessage(), } c.frontend.Send(saslInitialResponse) @@ -107,10 +143,31 @@ func (c *PgConn) rxSASLFinal() (*pgproto3.AuthenticationSASLFinal, error) { type scramClient struct { serverAuthMechanisms []string - password []byte + password string clientNonce []byte + // authMechanism is the selected SASL mechanism for the client. Must be + // either SCRAM-SHA-256 (default) or SCRAM-SHA-256-PLUS. + // + // Upgraded to SCRAM-SHA-256-PLUS during authentication when channel binding + // is not disabled, channel binding data is available (TLS connection with + // an obtainable server certificate hash) and the server advertises + // SCRAM-SHA-256-PLUS. + authMechanism string + + // hasTLS indicates whether the connection is using TLS. This is + // needed because the GS2 header must distinguish between a client that + // supports channel binding but the server does not ("y,,") versus one + // that does not support it at all ("n,,"). + hasTLS bool + + // channelBindingData is the hash of the server's TLS certificate, computed + // per the tls-server-end-point channel binding type (RFC 5929). Used as + // the binding input in SCRAM-SHA-256-PLUS. nil when not in use. + channelBindingData []byte + clientFirstMessageBare []byte + clientGS2Header []byte serverFirstMessage []byte clientAndServerNonce []byte @@ -124,26 +181,23 @@ type scramClient struct { func newScramClient(serverAuthMechanisms []string, password string) (*scramClient, error) { sc := &scramClient{ serverAuthMechanisms: serverAuthMechanisms, + authMechanism: scramSHA256Name, } - // Ensure server supports SCRAM-SHA-256 - hasScramSHA256 := false - for _, mech := range sc.serverAuthMechanisms { - if mech == "SCRAM-SHA-256" { - hasScramSHA256 = true - break - } - } - if !hasScramSHA256 { + // Ensure the server supports SCRAM-SHA-256. SCRAM-SHA-256-PLUS is the + // channel binding variant and is only advertised when the server supports + // SSL. PostgreSQL always advertises the base SCRAM-SHA-256 mechanism + // regardless of SSL. + if !slices.Contains(sc.serverAuthMechanisms, scramSHA256Name) { return nil, errors.New("server does not support SCRAM-SHA-256") } // precis.OpaqueString is equivalent to SASLprep for password. var err error - sc.password, err = precis.OpaqueString.Bytes([]byte(password)) + sc.password, err = precis.OpaqueString.String(password) if err != nil { // PostgreSQL allows passwords invalid according to SCRAM / SASLprep. - sc.password = []byte(password) + sc.password = password } buf := make([]byte, clientNonceLen) @@ -158,8 +212,32 @@ func newScramClient(serverAuthMechanisms []string, password string) (*scramClien } func (sc *scramClient) clientFirstMessage() []byte { - sc.clientFirstMessageBare = []byte(fmt.Sprintf("n=,r=%s", sc.clientNonce)) - return []byte(fmt.Sprintf("n,,%s", sc.clientFirstMessageBare)) + // The client-first-message is the GS2 header concatenated with the bare + // message (username + client nonce). The GS2 header communicates the + // client's channel binding capability to the server: + // + // "n,," - client is not using TLS (channel binding not possible) + // "y,," - client is using TLS but channel binding is not + // in use (e.g., server did not advertise SCRAM-SHA-256-PLUS + // or the server certificate hash was not obtainable) + // "p=tls-server-end-point,," - channel binding is active via SCRAM-SHA-256-PLUS + // + // See: + // https://www.rfc-editor.org/rfc/rfc5802#section-6 + // https://www.rfc-editor.org/rfc/rfc5929#section-4 + // https://www.postgresql.org/docs/current/sasl-authentication.html#SASL-SCRAM-SHA-256 + + sc.clientFirstMessageBare = fmt.Appendf(nil, "n=,r=%s", sc.clientNonce) + + if sc.authMechanism == scramSHA256PlusName { + sc.clientGS2Header = []byte("p=tls-server-end-point,,") + } else if sc.hasTLS { + sc.clientGS2Header = []byte("y,,") + } else { + sc.clientGS2Header = []byte("n,,") + } + + return append(sc.clientGS2Header, sc.clientFirstMessageBare...) } func (sc *scramClient) recvServerFirstMessage(serverFirstMessage []byte) error { @@ -218,9 +296,25 @@ func (sc *scramClient) recvServerFirstMessage(serverFirstMessage []byte) error { } func (sc *scramClient) clientFinalMessage() string { - clientFinalMessageWithoutProof := []byte(fmt.Sprintf("c=biws,r=%s", sc.clientAndServerNonce)) + // The c= attribute carries the base64-encoded channel binding input. + // + // Without channel binding this is just the GS2 header alone ("biws" for + // "n,," or "eSws" for "y,,"). + // + // With channel binding, this is the GS2 header with the channel binding data + // (certificate hash) appended. + channelBindInput := sc.clientGS2Header + if sc.authMechanism == scramSHA256PlusName { + channelBindInput = slices.Concat(sc.clientGS2Header, sc.channelBindingData) + } + channelBindingEncoded := base64.StdEncoding.EncodeToString(channelBindInput) + clientFinalMessageWithoutProof := fmt.Appendf(nil, "c=%s,r=%s", channelBindingEncoded, sc.clientAndServerNonce) - sc.saltedPassword = pbkdf2.Key([]byte(sc.password), sc.salt, sc.iterations, 32, sha256.New) + var err error + sc.saltedPassword, err = pbkdf2.Key(sha256.New, sc.password, sc.salt, sc.iterations, 32) + if err != nil { + panic(err) // This should never happen. + } sc.authMessage = bytes.Join([][]byte{sc.clientFirstMessageBare, sc.serverFirstMessage, clientFinalMessageWithoutProof}, []byte(",")) clientProof := computeClientProof(sc.saltedPassword, sc.authMessage) @@ -254,7 +348,7 @@ func computeClientProof(saltedPassword, authMessage []byte) []byte { clientSignature := computeHMAC(storedKey[:], authMessage) clientProof := make([]byte, len(clientSignature)) - for i := 0; i < len(clientSignature); i++ { + for i := range clientSignature { clientProof[i] = clientKey[i] ^ clientSignature[i] } @@ -263,10 +357,43 @@ func computeClientProof(saltedPassword, authMessage []byte) []byte { return buf } -func computeServerSignature(saltedPassword []byte, authMessage []byte) []byte { +func computeServerSignature(saltedPassword, authMessage []byte) []byte { serverKey := computeHMAC(saltedPassword, []byte("Server Key")) serverSignature := computeHMAC(serverKey, authMessage) buf := make([]byte, base64.StdEncoding.EncodedLen(len(serverSignature))) base64.StdEncoding.Encode(buf, serverSignature) return buf } + +// Get the server certificate hash for SCRAM channel binding type +// tls-server-end-point. +func getTLSCertificateHash(conn *tls.Conn) ([]byte, error) { + state := conn.ConnectionState() + if len(state.PeerCertificates) == 0 { + return nil, errors.New("no peer certificates for channel binding") + } + + cert := state.PeerCertificates[0] + + // Per RFC 5929 section 4.1: If the certificate's signatureAlgorithm uses + // MD5 or SHA-1, use SHA-256. Otherwise use the hash from the signature + // algorithm. + // + // See: https://www.rfc-editor.org/rfc/rfc5929.html#section-4.1 + var h hash.Hash + switch cert.SignatureAlgorithm { + case x509.MD5WithRSA, x509.SHA1WithRSA, x509.ECDSAWithSHA1: + h = sha256.New() + case x509.SHA256WithRSA, x509.SHA256WithRSAPSS, x509.ECDSAWithSHA256: + h = sha256.New() + case x509.SHA384WithRSA, x509.SHA384WithRSAPSS, x509.ECDSAWithSHA384: + h = sha512.New384() + case x509.SHA512WithRSA, x509.SHA512WithRSAPSS, x509.ECDSAWithSHA512: + h = sha512.New() + default: + return nil, fmt.Errorf("tls-server-end-point channel binding is undefined for certificate signature algorithm %v", cert.SignatureAlgorithm) + } + + h.Write(cert.Raw) + return h.Sum(nil), nil +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/config.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/config.go index 1c28c4079..dff550953 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/config.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/config.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "maps" "math" "net" "net/url" @@ -23,9 +24,11 @@ import ( "github.com/jackc/pgx/v5/pgproto3" ) -type AfterConnectFunc func(ctx context.Context, pgconn *PgConn) error -type ValidateConnectFunc func(ctx context.Context, pgconn *PgConn) error -type GetSSLPasswordFunc func(ctx context.Context) string +type ( + AfterConnectFunc func(ctx context.Context, pgconn *PgConn) error + ValidateConnectFunc func(ctx context.Context, pgconn *PgConn) error + GetSSLPasswordFunc func(ctx context.Context) string +) // Config is the settings used to establish a connection to a PostgreSQL server. It must be created by [ParseConfig]. A // manually initialized Config will cause ConnectConfig to panic. @@ -53,6 +56,13 @@ type Config struct { SSLNegotiation string // sslnegotiation=postgres or sslnegotiation=direct + // AfterNetConnect is called after the network connection, including TLS if applicable, is established but before any + // PostgreSQL protocol communication. It takes the established net.Conn and returns a net.Conn that will be used in + // its place. It can be used to wrap the net.Conn (e.g. for logging, diagnostics, or testing). Its functionality has + // some overlap with DialFunc. However, DialFunc takes place before TLS is established and cannot be used to control + // the final net.Conn used for PostgreSQL protocol communication while AfterNetConnect can. + AfterNetConnect func(ctx context.Context, config *Config, conn net.Conn) (net.Conn, error) + // ValidateConnect is called during a connection attempt after a successful authentication with the PostgreSQL server. // It can be used to validate that the server is acceptable. If this returns an error the connection is closed and the next // fallback config is tried. This allows implementing high availability behavior such as libpq does with target_session_attrs. @@ -73,6 +83,23 @@ type Config struct { // that you close on FATAL errors by returning false. OnPgError PgErrorHandler + // OAuthTokenProvider is a function that returns an OAuth token for authentication. If set, it will be used for + // OAUTHBEARER SASL authentication when the server requests it. + OAuthTokenProvider func(context.Context) (string, error) + + // MinProtocolVersion is the minimum acceptable PostgreSQL protocol version. + // If the server does not support at least this version, the connection will fail. + // Valid values: "3.0", "3.2", "latest". Defaults to "3.0". + MinProtocolVersion string + + // MaxProtocolVersion is the maximum PostgreSQL protocol version to request from the server. + // Valid values: "3.0", "3.2", "latest". Defaults to "3.0" for compatibility. + MaxProtocolVersion string + + // ChannelBinding is the channel_binding parameter for SCRAM-SHA-256-PLUS authentication. + // Valid values: "disable", "prefer", "require". Defaults to "prefer". + ChannelBinding string + createdByParseConfig bool // Used to enforce created by ParseConfig rule. } @@ -94,9 +121,7 @@ func (c *Config) Copy() *Config { } if newConf.RuntimeParams != nil { newConf.RuntimeParams = make(map[string]string, len(c.RuntimeParams)) - for k, v := range c.RuntimeParams { - newConf.RuntimeParams[k] = v - } + maps.Copy(newConf.RuntimeParams, c.RuntimeParams) } if newConf.Fallbacks != nil { newConf.Fallbacks = make([]*FallbackConfig, len(c.Fallbacks)) @@ -179,7 +204,7 @@ func NetworkAddress(host string, port uint16) (network, address string) { // // ParseConfig supports specifying multiple hosts in similar manner to libpq. Host and port may include comma separated // values that will be tried in order. This can be used as part of a high availability system. See -// https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS for more information. +// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS for more information. // // # Example URL // postgres://jack:secret@foo.example.com:5432,bar.example.com:5432/mydb @@ -205,10 +230,12 @@ func NetworkAddress(host string, port uint16) (network, address string) { // PGCONNECT_TIMEOUT // PGTARGETSESSIONATTRS // PGTZ +// PGMINPROTOCOLVERSION +// PGMAXPROTOCOLVERSION // -// See http://www.postgresql.org/docs/11/static/libpq-envars.html for details on the meaning of environment variables. +// See http://www.postgresql.org/docs/current/static/libpq-envars.html for details on the meaning of environment variables. // -// See https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-PARAMKEYWORDS for parameter key word names. They are +// See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS for parameter key word names. They are // usually but not always the environment variable name downcased and without the "PG" prefix. // // Important Security Notes: @@ -216,7 +243,7 @@ func NetworkAddress(host string, port uint16) (network, address string) { // ParseConfig tries to match libpq behavior with regard to PGSSLMODE. This includes defaulting to "prefer" behavior if // not set. // -// See http://www.postgresql.org/docs/11/static/libpq-ssl.html#LIBPQ-SSL-PROTECTION for details on what level of +// See http://www.postgresql.org/docs/current/static/libpq-ssl.html#LIBPQ-SSL-PROTECTION for details on what level of // security each sslmode provides. // // The sslmode "prefer" (the default), sslmode "allow", and multiple hosts are implemented via the Fallbacks field of @@ -330,6 +357,9 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con "target_session_attrs": {}, "service": {}, "servicefile": {}, + "min_protocol_version": {}, + "max_protocol_version": {}, + "channel_binding": {}, } // Adding kerberos configuration @@ -422,6 +452,38 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("unknown target_session_attrs value: %v", tsa)} } + minProto, err := parseProtocolVersion(settings["min_protocol_version"]) + if err != nil { + return nil, &ParseConfigError{ConnString: connString, msg: "invalid min_protocol_version", err: err} + } + maxProto, err := parseProtocolVersion(settings["max_protocol_version"]) + if err != nil { + return nil, &ParseConfigError{ConnString: connString, msg: "invalid max_protocol_version", err: err} + } + if minProto > maxProto { + return nil, &ParseConfigError{ConnString: connString, msg: "min_protocol_version cannot be greater than max_protocol_version"} + } + + config.MinProtocolVersion = settings["min_protocol_version"] + config.MaxProtocolVersion = settings["max_protocol_version"] + if config.MinProtocolVersion == "" { + config.MinProtocolVersion = "3.0" + } + if config.MaxProtocolVersion == "" { + config.MaxProtocolVersion = "3.0" + } + + switch channelBinding := settings["channel_binding"]; channelBinding { + case "", "prefer": + config.ChannelBinding = "prefer" + case "disable": + config.ChannelBinding = "disable" + case "require": + config.ChannelBinding = "require" + default: + return nil, &ParseConfigError{ConnString: connString, msg: fmt.Sprintf("unknown channel_binding value: %v", channelBinding)} + } + return config, nil } @@ -429,9 +491,7 @@ func mergeSettings(settingSets ...map[string]string) map[string]string { settings := make(map[string]string) for _, s2 := range settingSets { - for k, v := range s2 { - settings[k] = v - } + maps.Copy(settings, s2) } return settings @@ -461,6 +521,8 @@ func parseEnvSettings() map[string]string { "PGSERVICEFILE": "servicefile", "PGTZ": "timezone", "PGOPTIONS": "options", + "PGMINPROTOCOLVERSION": "min_protocol_version", + "PGMAXPROTOCOLVERSION": "max_protocol_version", } for envname, realname := range nameMap { @@ -485,7 +547,9 @@ func parseURLSettings(connString string) (map[string]string, error) { } if parsedURL.User != nil { - settings["user"] = parsedURL.User.Username() + if u := parsedURL.User.Username(); u != "" { + settings["user"] = u + } if password, present := parsedURL.User.Password(); present { settings["password"] = password } @@ -494,7 +558,7 @@ func parseURLSettings(connString string) (map[string]string, error) { // Handle multiple host:port's in url.Host by splitting them into host,host,host and port,port,port. var hosts []string var ports []string - for _, host := range strings.Split(parsedURL.Host, ",") { + for host := range strings.SplitSeq(parsedURL.Host, ",") { if host == "" { continue } @@ -612,6 +676,9 @@ func parseKeywordValueSettings(s string) (map[string]string, error) { return nil, errors.New("invalid keyword/value") } + if key == "user" && val == "" { + continue + } settings[key] = val } @@ -713,7 +780,7 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P // According to PostgreSQL documentation, if a root CA file exists, // the behavior of sslmode=require should be the same as that of verify-ca // - // See https://www.postgresql.org/docs/12/libpq-ssl.html + // See https://www.postgresql.org/docs/current/libpq-ssl.html if sslrootcert != "" { goto nextCase } @@ -782,10 +849,10 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P // Attempt decryption with pass phrase // NOTE: only supports RSA (PKCS#1) if sslpassword != "" { - decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) + decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) //nolint:ineffassign } - //if sslpassword not provided or has decryption error when use it - //try to find sslpassword with callback function + // if sslpassword not provided or has decryption error when use it + // try to find sslpassword with callback function if sslpassword == "" || decryptedError != nil { if parseConfigOptions.GetSSLPassword != nil { sslpassword = parseConfigOptions.GetSSLPassword(context.Background()) @@ -797,7 +864,7 @@ func configTLS(settings map[string]string, thisHost string, parseConfigOptions P decryptedKey, decryptedError = x509.DecryptPEMBlock(block, []byte(sslpassword)) // Should we also provide warning for PKCS#1 needed? if decryptedError != nil { - return nil, fmt.Errorf("unable to decrypt key: %w", err) + return nil, fmt.Errorf("unable to decrypt key: %w", decryptedError) } pemBytes := pem.Block{ @@ -949,3 +1016,14 @@ func ValidateConnectTargetSessionAttrsPreferStandby(ctx context.Context, pgConn return nil } + +func parseProtocolVersion(s string) (uint32, error) { + switch s { + case "", "3.0": + return pgproto3.ProtocolVersion30, nil + case "3.2", "latest": + return pgproto3.ProtocolVersion32, nil + default: + return 0, fmt.Errorf("invalid protocol version: %q", s) + } +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/ctxwatch/context_watcher.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/ctxwatch/context_watcher.go index db8884eb8..b8892e68b 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/ctxwatch/context_watcher.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/ctxwatch/context_watcher.go @@ -8,12 +8,13 @@ import ( // ContextWatcher watches a context and performs an action when the context is canceled. It can watch one context at a // time. type ContextWatcher struct { - handler Handler - unwatchChan chan struct{} + handler Handler - lock sync.Mutex - watchInProgress bool - onCancelWasCalled bool + // Lock protects the members below. + lock sync.Mutex + // Stop is the handle for an "after func". See [context.AfterFunc]. + stop func() bool + done chan struct{} } // NewContextWatcher returns a ContextWatcher. onCancel will be called when a watched context is canceled. @@ -21,8 +22,7 @@ type ContextWatcher struct { // onCancel called. func NewContextWatcher(handler Handler) *ContextWatcher { cw := &ContextWatcher{ - handler: handler, - unwatchChan: make(chan struct{}), + handler: handler, } return cw @@ -33,25 +33,16 @@ func (cw *ContextWatcher) Watch(ctx context.Context) { cw.lock.Lock() defer cw.lock.Unlock() - if cw.watchInProgress { - panic("Watch already in progress") + if cw.stop != nil { + panic("watch already in progress") } - cw.onCancelWasCalled = false - if ctx.Done() != nil { - cw.watchInProgress = true - go func() { - select { - case <-ctx.Done(): - cw.handler.HandleCancel(ctx) - cw.onCancelWasCalled = true - <-cw.unwatchChan - case <-cw.unwatchChan: - } - }() - } else { - cw.watchInProgress = false + cw.done = make(chan struct{}) + cw.stop = context.AfterFunc(ctx, func() { + cw.handler.HandleCancel(ctx) + close(cw.done) + }) } } @@ -61,12 +52,13 @@ func (cw *ContextWatcher) Unwatch() { cw.lock.Lock() defer cw.lock.Unlock() - if cw.watchInProgress { - cw.unwatchChan <- struct{}{} - if cw.onCancelWasCalled { + if cw.stop != nil { + if !cw.stop() { + <-cw.done cw.handler.HandleUnwatchAfterCancel() } - cw.watchInProgress = false + cw.stop = nil + cw.done = nil } } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/errors.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/errors.go index ec4a6d47c..bc1e31e31 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/errors.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/errors.go @@ -27,7 +27,7 @@ func Timeout(err error) bool { } // PgError represents an error reported by the PostgreSQL server. See -// http://www.postgresql.org/docs/11/static/protocol-error-fields.html for +// http://www.postgresql.org/docs/current/static/protocol-error-fields.html for // detailed field description. type PgError struct { Severity string @@ -112,6 +112,14 @@ type ParseConfigError struct { err error } +func NewParseConfigError(conn, msg string, err error) error { + return &ParseConfigError{ + ConnString: conn, + msg: msg, + err: err, + } +} + func (e *ParseConfigError) Error() string { // Now that ParseConfigError is public and ConnString is available to the developer, perhaps it would be better only // return a static string. That would ensure that the error message cannot leak a password. The ConnString field would @@ -246,3 +254,20 @@ func (e *NotPreferredError) SafeToRetry() bool { func (e *NotPreferredError) Unwrap() error { return e.err } + +type PrepareError struct { + err error + + ParseComplete bool // Indicates whether the error occurred after a ParseComplete message was received. +} + +func (e *PrepareError) Error() string { + if e.ParseComplete { + return fmt.Sprintf("prepare failed after ParseComplete: %s", e.err.Error()) + } + return e.err.Error() +} + +func (e *PrepareError) Unwrap() error { + return e.err +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/krb5.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/krb5.go index 3c1af3477..efb0d61b8 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/krb5.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/krb5.go @@ -28,7 +28,7 @@ func RegisterGSSProvider(newGSSArg NewGSSFunc) { // GSS provides GSSAPI authentication (e.g., Kerberos). type GSS interface { - GetInitToken(host string, service string) ([]byte, error) + GetInitToken(host, service string) ([]byte, error) GetInitTokenFromSPN(spn string) ([]byte, error) Continue(inToken []byte) (done bool, outToken []byte, err error) } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go index bf3eaec60..ca9a48cad 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "maps" "math" "net" "strconv" @@ -22,6 +23,7 @@ import ( "github.com/jackc/pgx/v5/pgconn/ctxwatch" "github.com/jackc/pgx/v5/pgconn/internal/bgreader" "github.com/jackc/pgx/v5/pgproto3" + "github.com/jackc/pgx/v5/pgtype" ) const ( @@ -75,7 +77,7 @@ type NotificationHandler func(*PgConn, *Notification) type PgConn struct { conn net.Conn pid uint32 // backend pid - secretKey uint32 // key to use to send a cancel query message to the server + secretKey []byte // key to use to send a cancel query message to the server parameterStatuses map[string]string // parameters that have been reported by the server txStatus byte frontend *pgproto3.Frontend @@ -135,7 +137,7 @@ func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptio // // If config.Fallbacks are present they will sequentially be tried in case of error establishing network connection. An // authentication error will terminate the chain of attempts (like libpq: -// https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS) and be returned as the error. +// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS) and be returned as the error. func ConnectConfig(ctx context.Context, config *Config) (*PgConn, error) { // Default values are set in ParseConfig. Enforce initial creation by ParseConfig rather than setting defaults from // zero values. @@ -317,6 +319,15 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo return e } + maxProtocolVersion, err := parseProtocolVersion(config.MaxProtocolVersion) + if err != nil { + return nil, newPerDialConnectError("invalid max_protocol_version", err) + } + minProtocolVersion, err := parseProtocolVersion(config.MinProtocolVersion) + if err != nil { + return nil, newPerDialConnectError("invalid min_protocol_version", err) + } + pgConn.conn, err = config.DialFunc(ctx, connectConfig.network, connectConfig.address) if err != nil { return nil, newPerDialConnectError("dial error", err) @@ -343,6 +354,14 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo pgConn.conn = tlsConn } + if config.AfterNetConnect != nil { + pgConn.conn, err = config.AfterNetConnect(ctx, config, pgConn.conn) + if err != nil { + pgConn.conn.Close() + return nil, newPerDialConnectError("AfterNetConnect failed", err) + } + } + pgConn.contextWatcher = ctxwatch.NewContextWatcher(config.BuildContextWatcherHandler(pgConn)) pgConn.contextWatcher.Watch(ctx) defer pgConn.contextWatcher.Unwatch() @@ -361,14 +380,12 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo pgConn.frontend = config.BuildFrontend(pgConn.bgReader, pgConn.conn) startupMsg := pgproto3.StartupMessage{ - ProtocolVersion: pgproto3.ProtocolVersionNumber, + ProtocolVersion: maxProtocolVersion, Parameters: make(map[string]string), } // Copy default run-time params - for k, v := range config.RuntimeParams { - startupMsg.Parameters[k] = v - } + maps.Copy(startupMsg.Parameters, config.RuntimeParams) startupMsg.Parameters["user"] = config.User if config.Database != "" { @@ -411,7 +428,20 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo return nil, newPerDialConnectError("failed to write password message", err) } case *pgproto3.AuthenticationSASL: - err = pgConn.scramAuth(msg.AuthMechanisms) + // Check if OAUTHBEARER is supported + serverSupportsOAuthBearer := false + for _, mech := range msg.AuthMechanisms { + if mech == "OAUTHBEARER" { + serverSupportsOAuthBearer = true + break + } + } + + if serverSupportsOAuthBearer && pgConn.config.OAuthTokenProvider != nil { + err = pgConn.oauthAuth(ctx) + } else { + err = pgConn.scramAuth(msg.AuthMechanisms) + } if err != nil { pgConn.conn.Close() return nil, newPerDialConnectError("failed SASL auth", err) @@ -444,6 +474,12 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo return pgConn, nil case *pgproto3.ParameterStatus, *pgproto3.NoticeResponse: // handled by ReceiveMessage + case *pgproto3.NegotiateProtocolVersion: + serverVersion := pgproto3.ProtocolVersion30&0xFFFF0000 | uint32(msg.NewestMinorProtocol) + if serverVersion < minProtocolVersion { + pgConn.conn.Close() + return nil, newPerDialConnectError("server protocol version too low", nil) + } case *pgproto3.ErrorResponse: pgConn.conn.Close() return nil, newPerDialConnectError("server error", ErrorResponseToPgError(msg)) @@ -576,6 +612,10 @@ func (pgConn *PgConn) peekMessage() (pgproto3.BackendMessage, error) { // receiveMessage receives a message without setting up context cancellation func (pgConn *PgConn) receiveMessage() (pgproto3.BackendMessage, error) { + if pgConn.status == connStatusClosed { + return nil, &connLockError{status: "conn closed"} + } + msg, err := pgConn.peekMessage() if err != nil { return nil, err @@ -633,7 +673,7 @@ func (pgConn *PgConn) TxStatus() byte { } // SecretKey returns the backend secret key used to send a cancel query message to the server. -func (pgConn *PgConn) SecretKey() uint32 { +func (pgConn *PgConn) SecretKey() []byte { return pgConn.secretKey } @@ -770,25 +810,20 @@ func NewCommandTag(s string) CommandTag { // RowsAffected returns the number of rows affected. If the CommandTag was not // for a row affecting command (e.g. "CREATE TABLE") then it returns 0. func (ct CommandTag) RowsAffected() int64 { - // Find last non-digit - idx := -1 + // Parse the number from the end in a single pass. + var n int64 + var mult int64 = 1 + for i := len(ct.s) - 1; i >= 0; i-- { - if ct.s[i] >= '0' && ct.s[i] <= '9' { - idx = i + c := ct.s[i] + if c >= '0' && c <= '9' { + n += int64(c-'0') * mult + mult *= 10 } else { break } } - if idx == -1 { - return 0 - } - - var n int64 - for _, b := range ct.s[idx:] { - n = n*10 + int64(b-'0') - } - return n } @@ -826,13 +861,15 @@ type FieldDescription struct { Format int16 } -func (pgConn *PgConn) convertRowDescription(dst []FieldDescription, rd *pgproto3.RowDescription) []FieldDescription { - if cap(dst) >= len(rd.Fields) { - dst = dst[:len(rd.Fields):len(rd.Fields)] +func (pgConn *PgConn) getFieldDescriptionSlice(n int) []FieldDescription { + if cap(pgConn.fieldDescriptions) >= n { + return pgConn.fieldDescriptions[:n:n] } else { - dst = make([]FieldDescription, len(rd.Fields)) + return make([]FieldDescription, n) } +} +func convertRowDescription(dst []FieldDescription, rd *pgproto3.RowDescription) { for i := range rd.Fields { dst[i].Name = string(rd.Fields[i].Name) dst[i].TableOID = rd.Fields[i].TableOID @@ -842,8 +879,6 @@ func (pgConn *PgConn) convertRowDescription(dst []FieldDescription, rd *pgproto3 dst[i].TypeModifier = rd.Fields[i].TypeModifier dst[i].Format = rd.Fields[i].Format } - - return dst } type StatementDescription struct { @@ -858,6 +893,10 @@ type StatementDescription struct { // // Prepare does not send a PREPARE statement to the server. It uses the PostgreSQL Parse and Describe protocol messages // directly. +// +// In extremely rare cases, Prepare may fail after the Parse is successful, but before the Describe is complete. In this +// case, the returned error will be an error where errors.As with a *PrepareError succeeds and the *PrepareError has +// ParseComplete set to true. func (pgConn *PgConn) Prepare(ctx context.Context, name, sql string, paramOIDs []uint32) (*StatementDescription, error) { if err := pgConn.lock(); err != nil { return nil, err @@ -885,7 +924,8 @@ func (pgConn *PgConn) Prepare(ctx context.Context, name, sql string, paramOIDs [ psd := &StatementDescription{Name: name, SQL: sql} - var parseErr error + var ParseComplete bool + var pgErr *PgError readloop: for { @@ -896,20 +936,23 @@ readloop: } switch msg := msg.(type) { + case *pgproto3.ParseComplete: + ParseComplete = true case *pgproto3.ParameterDescription: psd.ParamOIDs = make([]uint32, len(msg.ParameterOIDs)) copy(psd.ParamOIDs, msg.ParameterOIDs) case *pgproto3.RowDescription: - psd.Fields = pgConn.convertRowDescription(nil, msg) + psd.Fields = make([]FieldDescription, len(msg.Fields)) + convertRowDescription(psd.Fields, msg) case *pgproto3.ErrorResponse: - parseErr = ErrorResponseToPgError(msg) + pgErr = ErrorResponseToPgError(msg) case *pgproto3.ReadyForQuery: break readloop } } - if parseErr != nil { - return nil, parseErr + if pgErr != nil { + return nil, &PrepareError{err: pgErr, ParseComplete: ParseComplete} } return psd, nil } @@ -991,7 +1034,8 @@ func noticeResponseToNotice(msg *pgproto3.NoticeResponse) *Notice { // CancelRequest sends a cancel request to the PostgreSQL server. It returns an error if unable to deliver the cancel // request, but lack of an error does not ensure that the query was canceled. As specified in the documentation, there -// is no way to be sure a query was canceled. See https://www.postgresql.org/docs/11/protocol-flow.html#id-1.10.5.7.9 +// is no way to be sure a query was canceled. +// See https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-CANCELING-REQUESTS func (pgConn *PgConn) CancelRequest(ctx context.Context) error { // Open a cancellation request to the same server. The address is taken from the net.Conn directly instead of reusing // the connection config. This is important in high availability configurations where fallback connections may be @@ -1028,11 +1072,11 @@ func (pgConn *PgConn) CancelRequest(ctx context.Context) error { defer contextWatcher.Unwatch() } - buf := make([]byte, 16) - binary.BigEndian.PutUint32(buf[0:4], 16) + buf := make([]byte, 12+len(pgConn.secretKey)) + binary.BigEndian.PutUint32(buf[0:4], uint32(len(buf))) binary.BigEndian.PutUint32(buf[4:8], 80877102) binary.BigEndian.PutUint32(buf[8:12], pgConn.pid) - binary.BigEndian.PutUint32(buf[12:16], pgConn.secretKey) + copy(buf[12:], pgConn.secretKey) if _, err := cancelConn.Write(buf); err != nil { return fmt.Errorf("write to connection for cancellation: %w", err) @@ -1140,7 +1184,7 @@ func (pgConn *PgConn) Exec(ctx context.Context, sql string) *MultiResultReader { // binary format. If resultFormats is nil all results will be in text format. // // ResultReader must be closed before PgConn can be used again. -func (pgConn *PgConn) ExecParams(ctx context.Context, sql string, paramValues [][]byte, paramOIDs []uint32, paramFormats []int16, resultFormats []int16) *ResultReader { +func (pgConn *PgConn) ExecParams(ctx context.Context, sql string, paramValues [][]byte, paramOIDs []uint32, paramFormats, resultFormats []int16) *ResultReader { result := pgConn.execExtendedPrefix(ctx, paramValues) if result.closed { return result @@ -1149,7 +1193,7 @@ func (pgConn *PgConn) ExecParams(ctx context.Context, sql string, paramValues [] pgConn.frontend.SendParse(&pgproto3.Parse{Query: sql, ParameterOIDs: paramOIDs}) pgConn.frontend.SendBind(&pgproto3.Bind{ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}) - pgConn.execExtendedSuffix(result) + pgConn.execExtendedSuffix(result, nil, nil) return result } @@ -1166,7 +1210,7 @@ func (pgConn *PgConn) ExecParams(ctx context.Context, sql string, paramValues [] // binary format. If resultFormats is nil all results will be in text format. // // ResultReader must be closed before PgConn can be used again. -func (pgConn *PgConn) ExecPrepared(ctx context.Context, stmtName string, paramValues [][]byte, paramFormats []int16, resultFormats []int16) *ResultReader { +func (pgConn *PgConn) ExecPrepared(ctx context.Context, stmtName string, paramValues [][]byte, paramFormats, resultFormats []int16) *ResultReader { result := pgConn.execExtendedPrefix(ctx, paramValues) if result.closed { return result @@ -1174,7 +1218,36 @@ func (pgConn *PgConn) ExecPrepared(ctx context.Context, stmtName string, paramVa pgConn.frontend.SendBind(&pgproto3.Bind{PreparedStatement: stmtName, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}) - pgConn.execExtendedSuffix(result) + pgConn.execExtendedSuffix(result, nil, nil) + + return result +} + +// ExecStatement enqueues the execution of a prepared statement via the PostgreSQL extended query protocol. +// +// This differs from ExecPrepared in that it takes a *StatementDescription instead of the prepared statement name. +// Because it has the *StatementDescription it can avoid the Describe Portal message that ExecPrepared must send to get +// the result column descriptions. +// +// paramValues are the parameter values. It must be encoded in the format given by paramFormats. +// +// paramFormats is a slice of format codes determining for each paramValue column whether it is encoded in text or +// binary format. If paramFormats is nil all params are text format. ExecPrepared will panic if len(paramFormats) is not +// 0, 1, or len(paramValues). +// +// resultFormats is a slice of format codes determining for each result column whether it is encoded in text or binary +// format. If resultFormats is nil all results will be in text format. +// +// ResultReader must be closed before PgConn can be used again. +func (pgConn *PgConn) ExecStatement(ctx context.Context, statementDescription *StatementDescription, paramValues [][]byte, paramFormats, resultFormats []int16) *ResultReader { + result := pgConn.execExtendedPrefix(ctx, paramValues) + if result.closed { + return result + } + + pgConn.frontend.SendBind(&pgproto3.Bind{PreparedStatement: statementDescription.Name, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}) + + pgConn.execExtendedSuffix(result, statementDescription, resultFormats) return result } @@ -1214,8 +1287,10 @@ func (pgConn *PgConn) execExtendedPrefix(ctx context.Context, paramValues [][]by return result } -func (pgConn *PgConn) execExtendedSuffix(result *ResultReader) { - pgConn.frontend.SendDescribe(&pgproto3.Describe{ObjectType: 'P'}) +func (pgConn *PgConn) execExtendedSuffix(result *ResultReader, statementDescription *StatementDescription, resultFormats []int16) { + if statementDescription == nil { + pgConn.frontend.SendDescribe(&pgproto3.Describe{ObjectType: 'P'}) + } pgConn.frontend.SendExecute(&pgproto3.Execute{}) pgConn.frontend.SendSync(&pgproto3.Sync{}) @@ -1229,7 +1304,7 @@ func (pgConn *PgConn) execExtendedSuffix(result *ResultReader) { return } - result.readUntilRowDescription() + result.readUntilRowDescription(statementDescription, resultFormats) } // CopyTo executes the copy command sql and copies the results to w. @@ -1321,10 +1396,7 @@ func (pgConn *PgConn) CopyFrom(ctx context.Context, r io.Reader, sql string) (Co copyErrChan := make(chan error, 1) signalMessageChan := pgConn.signalMessage() var wg sync.WaitGroup - wg.Add(1) - - go func() { - defer wg.Done() + wg.Go(func() { buf := iobufpool.Get(65536) defer iobufpool.Put(buf) (*buf)[0] = 'd' @@ -1356,7 +1428,7 @@ func (pgConn *PgConn) CopyFrom(ctx context.Context, r io.Reader, sql string) (Co default: } } - }() + }) var pgErr error var copyErr error @@ -1373,7 +1445,14 @@ func (pgConn *PgConn) CopyFrom(ctx context.Context, r io.Reader, sql string) (Co close(pgConn.cleanupDone) return CommandTag{}, normalizeTimeoutError(ctx, err) } - msg, _ := pgConn.receiveMessage() + // peekMessage never returns err in the bufferingReceive mode - it only forwards the bufferingReceive variables. + // Therefore, the only case for receiveMessage to return err is during handling of the ErrorResponse message type + // and using pgOnError handler to determine the connection is no longer valid (and thus closing the conn). + msg, serverError := pgConn.receiveMessage() + if serverError != nil { + close(abortCopyChan) + return CommandTag{}, serverError + } switch msg := msg.(type) { case *pgproto3.ErrorResponse: @@ -1425,6 +1504,10 @@ type MultiResultReader struct { rr *ResultReader + // Data from when the batch was queued. + statementDescriptions []*StatementDescription + resultFormats [][]int16 + closed bool err error } @@ -1466,6 +1549,39 @@ func (mrr *MultiResultReader) receiveMessage() (pgproto3.BackendMessage, error) // NextResult returns advances the MultiResultReader to the next result and returns true if a result is available. func (mrr *MultiResultReader) NextResult() bool { for !mrr.closed && mrr.err == nil { + msg, _ := mrr.pgConn.peekMessage() + if _, ok := msg.(*pgproto3.DataRow); ok { + if len(mrr.statementDescriptions) > 0 { + rr := ResultReader{ + pgConn: mrr.pgConn, + multiResultReader: mrr, + ctx: mrr.ctx, + } + + // This result corresponds to a prepared statement description that was provided when queuing the batch. + sd := mrr.statementDescriptions[0] + mrr.statementDescriptions = mrr.statementDescriptions[1:] + + resultFormats := mrr.resultFormats[0] + mrr.resultFormats = mrr.resultFormats[1:] + + sdFields := sd.Fields + rr.fieldDescriptions = rr.pgConn.getFieldDescriptionSlice(len(sdFields)) + + err := combineFieldDescriptionsAndResultFormats(rr.fieldDescriptions, sdFields, resultFormats) + if err != nil { + rr.concludeCommand(CommandTag{}, err) + } + + mrr.pgConn.resultReader = rr + mrr.rr = &mrr.pgConn.resultReader + return true + } + + mrr.err = fmt.Errorf("unexpected DataRow message without preceding RowDescription") + return false + } + msg, err := mrr.receiveMessage() if err != nil { return false @@ -1477,8 +1593,9 @@ func (mrr *MultiResultReader) NextResult() bool { pgConn: mrr.pgConn, multiResultReader: mrr, ctx: mrr.ctx, - fieldDescriptions: mrr.pgConn.convertRowDescription(mrr.pgConn.fieldDescriptions[:], msg), + fieldDescriptions: mrr.pgConn.getFieldDescriptionSlice(len(msg.Fields)), } + convertRowDescription(mrr.pgConn.resultReader.fieldDescriptions, msg) mrr.rr = &mrr.pgConn.resultReader return true @@ -1491,7 +1608,12 @@ func (mrr *MultiResultReader) NextResult() bool { mrr.rr = &mrr.pgConn.resultReader return true case *pgproto3.EmptyQueryResponse: - return false + mrr.pgConn.resultReader = ResultReader{ + commandConcluded: true, + closed: true, + } + mrr.rr = &mrr.pgConn.resultReader + return true } } @@ -1525,6 +1647,7 @@ type ResultReader struct { fieldDescriptions []FieldDescription rowValues [][]byte commandTag CommandTag + preloaded bool commandConcluded bool closed bool err error @@ -1566,6 +1689,11 @@ func (rr *ResultReader) Read() *Result { // NextRow advances the ResultReader to the next row and returns true if a row is available. func (rr *ResultReader) NextRow() bool { + if rr.preloaded { + rr.preloaded = false + return true + } + for !rr.commandConcluded { msg, err := rr.receiveMessage() if err != nil { @@ -1582,6 +1710,11 @@ func (rr *ResultReader) NextRow() bool { return false } +func (rr *ResultReader) preloadRowValues(values [][]byte) { + rr.rowValues = values + rr.preloaded = true +} + // FieldDescriptions returns the field descriptions for the current result set. The returned slice is only valid until // the ResultReader is closed. It may return nil (for example, if the query did not return a result set or an error was // encountered.) @@ -1634,19 +1767,34 @@ func (rr *ResultReader) Close() (CommandTag, error) { // readUntilRowDescription ensures the ResultReader's fieldDescriptions are loaded. It does not return an error as any // error will be stored in the ResultReader. -func (rr *ResultReader) readUntilRowDescription() { +func (rr *ResultReader) readUntilRowDescription(statementDescription *StatementDescription, resultFormats []int16) { for !rr.commandConcluded { - // Peek before receive to avoid consuming a DataRow if the result set does not include a RowDescription method. - // This should never happen under normal pgconn usage, but it is possible if SendBytes and ReceiveResults are - // manually used to construct a query that does not issue a describe statement. - msg, _ := rr.pgConn.peekMessage() - if _, ok := msg.(*pgproto3.DataRow); ok { + msg, _ := rr.receiveMessage() + switch msg := msg.(type) { + case *pgproto3.RowDescription: return - } + case *pgproto3.DataRow: + rr.preloadRowValues(msg.Values) + if statementDescription != nil { + sdFields := statementDescription.Fields + rr.fieldDescriptions = rr.pgConn.getFieldDescriptionSlice(len(sdFields)) - // Consume the message - msg, _ = rr.receiveMessage() - if _, ok := msg.(*pgproto3.RowDescription); ok { + err := combineFieldDescriptionsAndResultFormats(rr.fieldDescriptions, sdFields, resultFormats) + if err != nil { + rr.concludeCommand(CommandTag{}, err) + } + } + return + case *pgproto3.CommandComplete: + if statementDescription != nil { + sdFields := statementDescription.Fields + rr.fieldDescriptions = rr.pgConn.getFieldDescriptionSlice(len(sdFields)) + + err := combineFieldDescriptionsAndResultFormats(rr.fieldDescriptions, sdFields, resultFormats) + if err != nil { + rr.concludeCommand(CommandTag{}, err) + } + } return } } @@ -1673,7 +1821,8 @@ func (rr *ResultReader) receiveMessage() (msg pgproto3.BackendMessage, err error switch msg := msg.(type) { case *pgproto3.RowDescription: - rr.fieldDescriptions = rr.pgConn.convertRowDescription(rr.pgConn.fieldDescriptions[:], msg) + rr.fieldDescriptions = rr.pgConn.getFieldDescriptionSlice(len(msg.Fields)) + convertRowDescription(rr.fieldDescriptions, msg) case *pgproto3.CommandComplete: rr.concludeCommand(rr.pgConn.makeCommandTag(msg.CommandTag), nil) case *pgproto3.EmptyQueryResponse: @@ -1707,12 +1856,14 @@ func (rr *ResultReader) concludeCommand(commandTag CommandTag, err error) { // Batch is a collection of queries that can be sent to the PostgreSQL server in a single round-trip. type Batch struct { - buf []byte - err error + buf []byte + statementDescriptions []*StatementDescription + resultFormats [][]int16 + err error } // ExecParams appends an ExecParams command to the batch. See PgConn.ExecParams for parameter descriptions. -func (batch *Batch) ExecParams(sql string, paramValues [][]byte, paramOIDs []uint32, paramFormats []int16, resultFormats []int16) { +func (batch *Batch) ExecParams(sql string, paramValues [][]byte, paramOIDs []uint32, paramFormats, resultFormats []int16) { if batch.err != nil { return } @@ -1725,7 +1876,7 @@ func (batch *Batch) ExecParams(sql string, paramValues [][]byte, paramOIDs []uin } // ExecPrepared appends an ExecPrepared e command to the batch. See PgConn.ExecPrepared for parameter descriptions. -func (batch *Batch) ExecPrepared(stmtName string, paramValues [][]byte, paramFormats []int16, resultFormats []int16) { +func (batch *Batch) ExecPrepared(stmtName string, paramValues [][]byte, paramFormats, resultFormats []int16) { if batch.err != nil { return } @@ -1746,6 +1897,30 @@ func (batch *Batch) ExecPrepared(stmtName string, paramValues [][]byte, paramFor } } +// ExecStatement appends an ExecStatement command to the batch. See PgConn.ExecPrepared for parameter descriptions. +// +// This differs from ExecPrepared in that it takes a *StatementDescription instead of just the prepared statement name. +// Because it has the *StatementDescription it can avoid the Describe Portal message that ExecPrepared must send to get +// the result column descriptions. +func (batch *Batch) ExecStatement(statementDescription *StatementDescription, paramValues [][]byte, paramFormats, resultFormats []int16) { + if batch.err != nil { + return + } + + batch.buf, batch.err = (&pgproto3.Bind{PreparedStatement: statementDescription.Name, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}).Encode(batch.buf) + if batch.err != nil { + return + } + + batch.statementDescriptions = append(batch.statementDescriptions, statementDescription) + batch.resultFormats = append(batch.resultFormats, resultFormats) + + batch.buf, batch.err = (&pgproto3.Execute{}).Encode(batch.buf) + if batch.err != nil { + return + } +} + // ExecBatch executes all the queries in batch in a single round-trip. Execution is implicitly transactional unless a // transaction is already in progress or SQL contains transaction control statements. This is a simpler way of executing // multiple queries in a single round trip than using pipeline mode. @@ -1765,8 +1940,10 @@ func (pgConn *PgConn) ExecBatch(ctx context.Context, batch *Batch) *MultiResultR } pgConn.multiResultReader = MultiResultReader{ - pgConn: pgConn, - ctx: ctx, + pgConn: pgConn, + ctx: ctx, + statementDescriptions: batch.statementDescriptions, + resultFormats: batch.resultFormats, } multiResult := &pgConn.multiResultReader @@ -1791,9 +1968,11 @@ func (pgConn *PgConn) ExecBatch(ctx context.Context, batch *Batch) *MultiResultR return multiResult } - pgConn.enterPotentialWriteReadDeadlock() - defer pgConn.exitPotentialWriteReadDeadlock() - _, err := pgConn.conn.Write(batch.buf) + _, err := func(buf []byte) (int, error) { + pgConn.enterPotentialWriteReadDeadlock() + defer pgConn.exitPotentialWriteReadDeadlock() + return pgConn.conn.Write(buf) + }(batch.buf) if err != nil { pgConn.contextWatcher.Unwatch() multiResult.err = normalizeTimeoutError(multiResult.ctx, err) @@ -1899,7 +2078,7 @@ func (pgConn *PgConn) flushWithPotentialWriteReadDeadlock() error { // // This should not be confused with the PostgreSQL protocol Sync message. func (pgConn *PgConn) SyncConn(ctx context.Context) error { - for i := 0; i < 10; i++ { + for range 10 { if pgConn.bgReader.Status() == bgreader.StatusStopped && pgConn.frontend.ReadBufferLen() == 0 { return nil } @@ -1927,7 +2106,7 @@ func (pgConn *PgConn) CustomData() map[string]any { type HijackedConn struct { Conn net.Conn PID uint32 // backend pid - SecretKey uint32 // key to use to send a cancel query message to the server + SecretKey []byte // key to use to send a cancel query message to the server ParameterStatuses map[string]string // parameters that have been reported by the server TxStatus byte Frontend *pgproto3.Frontend @@ -1999,9 +2178,10 @@ func Construct(hc *HijackedConn) (*PgConn, error) { // Pipeline represents a connection in pipeline mode. // -// SendPrepare, SendQueryParams, and SendQueryPrepared queue requests to the server. These requests are not written until -// pipeline is flushed by Flush or Sync. Sync must be called after the last request is queued. Requests between -// synchronization points are implicitly transactional unless explicit transaction control statements have been issued. +// SendPrepare, SendQueryParams, SendQueryPrepared, and SendQueryStatement queue requests to the server. These requests +// are not written until pipeline is flushed by Flush or Sync. Sync must be called after the last request is queued. +// Requests between synchronization points are implicitly transactional unless explicit transaction control statements +// have been issued. // // The context the pipeline was started with is in effect for the entire life of the Pipeline. // @@ -2030,6 +2210,7 @@ const ( pipelinePrepare pipelineQueryParams pipelineQueryPrepared + pipelineQueryStatement pipelineDeallocate pipelineSyncRequest pipelineFlushRequest @@ -2043,6 +2224,8 @@ type pipelineRequestEvent struct { type pipelineState struct { requestEventQueue list.List + statementDescriptionsQueue list.List + resultFormatsQueue list.List lastRequestType pipelineRequestType pgErr *PgError expectedReadyForQueryCount int @@ -2050,6 +2233,8 @@ type pipelineState struct { func (s *pipelineState) Init() { s.requestEventQueue.Init() + s.statementDescriptionsQueue.Init() + s.resultFormatsQueue.Init() s.lastRequestType = pipelineNil } @@ -2114,6 +2299,29 @@ func (s *pipelineState) ExtractFrontRequestType() pipelineRequestType { } } +func (s *pipelineState) PushBackStatementData(sd *StatementDescription, resultFormats []int16) { + s.statementDescriptionsQueue.PushBack(sd) + s.resultFormatsQueue.PushBack(resultFormats) +} + +func (s *pipelineState) ExtractFrontStatementData() (*StatementDescription, []int16) { + sdElem := s.statementDescriptionsQueue.Front() + var sd *StatementDescription + if sdElem != nil { + s.statementDescriptionsQueue.Remove(sdElem) + sd = sdElem.Value.(*StatementDescription) + } + + rfElem := s.resultFormatsQueue.Front() + var resultFormats []int16 + if rfElem != nil { + s.resultFormatsQueue.Remove(rfElem) + resultFormats = rfElem.Value.([]int16) + } + + return sd, resultFormats +} + func (s *pipelineState) HandleError(err *PgError) { s.pgErr = err } @@ -2156,6 +2364,8 @@ func (pgConn *PgConn) StartPipeline(ctx context.Context) *Pipeline { return pipeline } + pgConn.resultReader = ResultReader{closed: true} + pgConn.pipeline = Pipeline{ conn: pgConn, ctx: ctx, @@ -2200,8 +2410,8 @@ func (p *Pipeline) SendDeallocate(name string) { p.state.PushBackRequestType(pipelineDeallocate) } -// SendQueryParams is the pipeline version of *PgConn.QueryParams. -func (p *Pipeline) SendQueryParams(sql string, paramValues [][]byte, paramOIDs []uint32, paramFormats []int16, resultFormats []int16) { +// SendQueryParams is the pipeline version of *PgConn.ExecParams. +func (p *Pipeline) SendQueryParams(sql string, paramValues [][]byte, paramOIDs []uint32, paramFormats, resultFormats []int16) { if p.closed { return } @@ -2213,8 +2423,8 @@ func (p *Pipeline) SendQueryParams(sql string, paramValues [][]byte, paramOIDs [ p.state.PushBackRequestType(pipelineQueryParams) } -// SendQueryPrepared is the pipeline version of *PgConn.QueryPrepared. -func (p *Pipeline) SendQueryPrepared(stmtName string, paramValues [][]byte, paramFormats []int16, resultFormats []int16) { +// SendQueryPrepared is the pipeline version of *PgConn.ExecPrepared. +func (p *Pipeline) SendQueryPrepared(stmtName string, paramValues [][]byte, paramFormats, resultFormats []int16) { if p.closed { return } @@ -2225,6 +2435,18 @@ func (p *Pipeline) SendQueryPrepared(stmtName string, paramValues [][]byte, para p.state.PushBackRequestType(pipelineQueryPrepared) } +// SendQueryStatement is the pipeline version of *PgConn.ExecStatement. +func (p *Pipeline) SendQueryStatement(statementDescription *StatementDescription, paramValues [][]byte, paramFormats, resultFormats []int16) { + if p.closed { + return + } + + p.conn.frontend.SendBind(&pgproto3.Bind{PreparedStatement: statementDescription.Name, ParameterFormatCodes: paramFormats, Parameters: paramValues, ResultFormatCodes: resultFormats}) + p.conn.frontend.SendExecute(&pgproto3.Execute{}) + p.state.PushBackRequestType(pipelineQueryStatement) + p.state.PushBackStatementData(statementDescription, resultFormats) +} + // SendFlushRequest sends a request for the server to flush its output buffer. // // The server flushes its output buffer automatically as a result of Sync being called, @@ -2299,99 +2521,315 @@ func (p *Pipeline) GetResults() (results any, err error) { return nil, errors.New("pipeline closed") } - if p.state.ExtractFrontRequestType() == pipelineNil { - return nil, nil - } - return p.getResults() } func (p *Pipeline) getResults() (results any, err error) { - for { - msg, err := p.conn.receiveMessage() + if !p.conn.resultReader.closed { + _, err := p.conn.resultReader.Close() if err != nil { - p.closed = true - p.err = err - p.conn.asyncClose() - return nil, normalizeTimeoutError(p.ctx, err) + return nil, err } + } - switch msg := msg.(type) { - case *pgproto3.RowDescription: - p.conn.resultReader = ResultReader{ - pgConn: p.conn, - pipeline: p, - ctx: p.ctx, - fieldDescriptions: p.conn.convertRowDescription(p.conn.fieldDescriptions[:], msg), - } - return &p.conn.resultReader, nil - case *pgproto3.CommandComplete: - p.conn.resultReader = ResultReader{ - commandTag: p.conn.makeCommandTag(msg.CommandTag), - commandConcluded: true, - closed: true, - } - return &p.conn.resultReader, nil - case *pgproto3.ParseComplete: - peekedMsg, err := p.conn.peekMessage() - if err != nil { - p.conn.asyncClose() - return nil, normalizeTimeoutError(p.ctx, err) - } - if _, ok := peekedMsg.(*pgproto3.ParameterDescription); ok { - return p.getResultsPrepare() - } - case *pgproto3.CloseComplete: - return &CloseComplete{}, nil - case *pgproto3.ReadyForQuery: - p.state.HandleReadyForQuery() - return &PipelineSync{}, nil - case *pgproto3.ErrorResponse: - pgErr := ErrorResponseToPgError(msg) - p.state.HandleError(pgErr) - return nil, pgErr - } + currentRequestType := p.state.ExtractFrontRequestType() + switch currentRequestType { + case pipelineNil: + return nil, nil + case pipelinePrepare: + return p.getResultsPrepare() + case pipelineQueryParams: + return p.getResultsQueryParams() + case pipelineQueryPrepared: + return p.getResultsQueryPrepared() + case pipelineQueryStatement: + return p.getResultsQueryStatement() + case pipelineDeallocate: + return p.getResultsDeallocate() + case pipelineSyncRequest: + return p.getResultsSync() + case pipelineFlushRequest: + return nil, errors.New("BUG: pipelineFlushRequest should not be in request queue") + default: + return nil, errors.New("BUG: unknown pipeline request type") } } func (p *Pipeline) getResultsPrepare() (*StatementDescription, error) { + err := p.receiveParseComplete("Prepare") + if err != nil { + return nil, err + } + psd := &StatementDescription{} + msg, err := p.receiveMessage() + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *pgproto3.ParameterDescription: + psd.ParamOIDs = make([]uint32, len(msg.ParameterOIDs)) + copy(psd.ParamOIDs, msg.ParameterOIDs) + case *pgproto3.ErrorResponse: + pgErr := ErrorResponseToPgError(msg) + p.state.HandleError(pgErr) + return nil, pgErr + default: + return nil, p.handleUnexpectedMessage("Prepare ParameterDescription", msg) + } + + msg, err = p.receiveMessage() + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *pgproto3.RowDescription: + psd.Fields = make([]FieldDescription, len(msg.Fields)) + convertRowDescription(psd.Fields, msg) + return psd, nil + + // NoData is returned instead of RowDescription when there is no expected result. e.g. An INSERT without a RETURNING + // clause. + case *pgproto3.NoData: + return psd, nil + + case *pgproto3.ErrorResponse: + pgErr := ErrorResponseToPgError(msg) + p.state.HandleError(pgErr) + return nil, pgErr + default: + return nil, p.handleUnexpectedMessage("Prepare RowDescription", msg) + } +} + +func (p *Pipeline) getResultsQueryParams() (*ResultReader, error) { + err := p.receiveParseComplete("QueryParams") + if err != nil { + return nil, err + } + + err = p.receiveBindComplete("QueryParams") + if err != nil { + return nil, err + } + + return p.receiveDescribedResultReader("QueryParams") +} + +func (p *Pipeline) getResultsQueryPrepared() (*ResultReader, error) { + err := p.receiveBindComplete("QueryPrepared") + if err != nil { + return nil, err + } + + return p.receiveDescribedResultReader("QueryPrepared") +} + +func (p *Pipeline) getResultsQueryStatement() (*ResultReader, error) { + err := p.receiveBindComplete("QueryStatement") + if err != nil { + return nil, err + } + + msg, err := p.receiveMessage() + if err != nil { + return nil, err + } + + sd, resultFormats := p.state.ExtractFrontStatementData() + if sd == nil { + return nil, errors.New("BUG: missing statement description or result formats for QueryStatement") + } + sdFields := sd.Fields + fieldDescriptions := p.conn.getFieldDescriptionSlice(len(sdFields)) + err = combineFieldDescriptionsAndResultFormats(fieldDescriptions, sdFields, resultFormats) + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *pgproto3.DataRow: + rr := ResultReader{ + pgConn: p.conn, + pipeline: p, + ctx: p.ctx, + fieldDescriptions: fieldDescriptions, + } + rr.preloadRowValues(msg.Values) + p.conn.resultReader = rr + return &p.conn.resultReader, nil + case *pgproto3.CommandComplete: + p.conn.resultReader = ResultReader{ + commandTag: p.conn.makeCommandTag(msg.CommandTag), + commandConcluded: true, + closed: true, + fieldDescriptions: fieldDescriptions, + } + return &p.conn.resultReader, nil + case *pgproto3.ErrorResponse: + pgErr := ErrorResponseToPgError(msg) + p.state.HandleError(pgErr) + p.conn.resultReader.closed = true + return nil, pgErr + default: + return nil, p.handleUnexpectedMessage("QueryStatement", msg) + } +} + +func (p *Pipeline) getResultsDeallocate() (*CloseComplete, error) { + msg, err := p.receiveMessage() + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *pgproto3.CloseComplete: + return &CloseComplete{}, nil + case *pgproto3.ErrorResponse: + pgErr := ErrorResponseToPgError(msg) + p.state.HandleError(pgErr) + p.conn.resultReader.closed = true + return nil, pgErr + default: + return nil, p.handleUnexpectedMessage("Deallocate", msg) + } +} + +func (p *Pipeline) getResultsSync() (*PipelineSync, error) { + msg, err := p.receiveMessage() + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *pgproto3.ReadyForQuery: + p.state.HandleReadyForQuery() + return &PipelineSync{}, nil + case *pgproto3.ErrorResponse: + // Error message that is received while expecting a Sync message still consumes the expected Sync. Put it back. + p.state.requestEventQueue.PushFront(pipelineRequestEvent{RequestType: pipelineSyncRequest, WasSentToServer: true, BeforeFlushOrSync: true}) + + pgErr := ErrorResponseToPgError(msg) + p.state.HandleError(pgErr) + p.conn.resultReader.closed = true + return nil, pgErr + default: + return nil, p.handleUnexpectedMessage("Sync", msg) + } +} + +func (p *Pipeline) receiveParseComplete(errStr string) error { + msg, err := p.receiveMessage() + if err != nil { + return err + } + + switch msg := msg.(type) { + case *pgproto3.ParseComplete: + return nil + case *pgproto3.ErrorResponse: + pgErr := ErrorResponseToPgError(msg) + p.state.HandleError(pgErr) + return pgErr + default: + return p.handleUnexpectedMessage(fmt.Sprintf("%s Parse", errStr), msg) + } +} + +func (p *Pipeline) receiveBindComplete(errStr string) error { + msg, err := p.receiveMessage() + if err != nil { + return err + } + + switch msg := msg.(type) { + case *pgproto3.BindComplete: + return nil + case *pgproto3.ErrorResponse: + pgErr := ErrorResponseToPgError(msg) + p.state.HandleError(pgErr) + return pgErr + default: + return p.handleUnexpectedMessage(fmt.Sprintf("%s Bind", errStr), msg) + } +} + +func (p *Pipeline) receiveDescribedResultReader(errStr string) (*ResultReader, error) { + msg, err := p.receiveMessage() + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *pgproto3.RowDescription: + p.conn.resultReader = ResultReader{ + pgConn: p.conn, + pipeline: p, + ctx: p.ctx, + fieldDescriptions: p.conn.getFieldDescriptionSlice(len(msg.Fields)), + } + convertRowDescription(p.conn.resultReader.fieldDescriptions, msg) + return &p.conn.resultReader, nil + case *pgproto3.NoData: + case *pgproto3.ErrorResponse: + pgErr := ErrorResponseToPgError(msg) + p.state.HandleError(pgErr) + p.conn.resultReader.closed = true + return nil, pgErr + default: + return nil, p.handleUnexpectedMessage(fmt.Sprintf("%s RowDescription or NoData", errStr), msg) + } + + msg, err = p.receiveMessage() + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *pgproto3.CommandComplete: + p.conn.resultReader = ResultReader{ + commandTag: p.conn.makeCommandTag(msg.CommandTag), + commandConcluded: true, + closed: true, + } + return &p.conn.resultReader, nil + case *pgproto3.ErrorResponse: + pgErr := ErrorResponseToPgError(msg) + p.state.HandleError(pgErr) + p.conn.resultReader.closed = true + return nil, pgErr + default: + return nil, p.handleUnexpectedMessage(fmt.Sprintf("%s CommandComplete", errStr), msg) + } +} + +func (p *Pipeline) receiveMessage() (pgproto3.BackendMessage, error) { for { msg, err := p.conn.receiveMessage() if err != nil { + p.err = err p.conn.asyncClose() return nil, normalizeTimeoutError(p.ctx, err) } switch msg := msg.(type) { - case *pgproto3.ParameterDescription: - psd.ParamOIDs = make([]uint32, len(msg.ParameterOIDs)) - copy(psd.ParamOIDs, msg.ParameterOIDs) - case *pgproto3.RowDescription: - psd.Fields = p.conn.convertRowDescription(nil, msg) - return psd, nil - - // NoData is returned instead of RowDescription when there is no expected result. e.g. An INSERT without a RETURNING - // clause. - case *pgproto3.NoData: - return psd, nil - - // These should never happen here. But don't take chances that could lead to a deadlock. - case *pgproto3.ErrorResponse: - pgErr := ErrorResponseToPgError(msg) - p.state.HandleError(pgErr) - return nil, pgErr - case *pgproto3.CommandComplete: - p.conn.asyncClose() - return nil, errors.New("BUG: received CommandComplete while handling Describe") - case *pgproto3.ReadyForQuery: - p.conn.asyncClose() - return nil, errors.New("BUG: received ReadyForQuery while handling Describe") + case *pgproto3.ParameterStatus, *pgproto3.NoticeResponse, *pgproto3.NotificationResponse: + // Filter these message types out in pipeline mode. The normal processing is handled by PgConn.receiveMessage. + default: + return msg, nil } } } +func (p *Pipeline) handleUnexpectedMessage(errStr string, msg pgproto3.BackendMessage) error { + p.err = fmt.Errorf("pipeline: %s: received unexpected message type %T", errStr, msg) + p.conn.asyncClose() + return p.err +} + // Close closes the pipeline and returns the connection to normal mode. func (p *Pipeline) Close() error { if p.closed { @@ -2410,7 +2848,7 @@ func (p *Pipeline) Close() error { } for p.state.ExpectedReadyForQuery() > 0 { - _, err := p.getResults() + results, err := p.getResults() if err != nil { p.err = err var pgErr *PgError @@ -2418,6 +2856,15 @@ func (p *Pipeline) Close() error { p.conn.asyncClose() break } + } else if results == nil { + // getResults returns (nil, nil) when the request queue is exhausted but + // ExpectedReadyForQuery is still > 0. This can happen when FATAL errors consume + // queued request slots without the server ever sending ReadyForQuery. + p.conn.asyncClose() + if p.err == nil { + p.err = errors.New("pipeline: no more results but expected ReadyForQuery") + } + break } } @@ -2494,3 +2941,32 @@ func (h *CancelRequestContextWatcherHandler) HandleUnwatchAfterCancel() { h.Conn.conn.SetDeadline(time.Time{}) } + +func combineFieldDescriptionsAndResultFormats(outputFields, inputFields []FieldDescription, resultFormats []int16) error { + switch { + case len(resultFormats) == 0: + // No format codes provided means text format for all columns. + for i := range inputFields { + outputFields[i] = inputFields[i] + outputFields[i].Format = pgtype.TextFormatCode + } + case len(resultFormats) == 1: + // Single format code applies to all columns. + format := resultFormats[0] + for i := range inputFields { + outputFields[i] = inputFields[i] + outputFields[i].Format = format + } + case len(resultFormats) == len(inputFields): + // One format code per column. + for i := range inputFields { + outputFields[i] = inputFields[i] + outputFields[i].Format = resultFormats[i] + } + default: + // This should not occur if Bind validation is correct, but handle gracefully + return fmt.Errorf("result format codes length %d does not match field count %d", len(resultFormats), len(inputFields)) + } + + return nil +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_cleartext_password.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_cleartext_password.go index ac2962e9e..415e1a24a 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_cleartext_password.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_cleartext_password.go @@ -9,8 +9,7 @@ import ( ) // AuthenticationCleartextPassword is a message sent from the backend indicating that a clear-text password is required. -type AuthenticationCleartextPassword struct { -} +type AuthenticationCleartextPassword struct{} // Backend identifies this message as sendable by the PostgreSQL backend. func (*AuthenticationCleartextPassword) Backend() {} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_ok.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_ok.go index ec11d39f1..98c0b2d66 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_ok.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_ok.go @@ -9,8 +9,7 @@ import ( ) // AuthenticationOk is a message sent from the backend indicating that authentication was successful. -type AuthenticationOk struct { -} +type AuthenticationOk struct{} // Backend identifies this message as sendable by the PostgreSQL backend. func (*AuthenticationOk) Backend() {} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_sasl.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_sasl.go index e66580f44..69e228215 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_sasl.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/authentication_sasl.go @@ -33,6 +33,7 @@ func (dst *AuthenticationSASL) Decode(src []byte) error { return errors.New("bad auth type") } + dst.AuthMechanisms = dst.AuthMechanisms[:0] authMechanisms := src[4:] for len(authMechanisms) > 1 { idx := bytes.IndexByte(authMechanisms, 0) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/backend.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/backend.go index 28cff049a..65388ad49 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/backend.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/backend.go @@ -46,8 +46,8 @@ type Backend struct { } const ( - minStartupPacketLen = 4 // minStartupPacketLen is a single 32-bit int version or code. - maxStartupPacketLen = 10000 // maxStartupPacketLen is MAX_STARTUP_PACKET_LENGTH from PG source. + minStartupPacketLen = 4 // minStartupPacketLen is a single 32-bit int version or code. + maxStartupPacketLen = 10_000 // maxStartupPacketLen is MAX_STARTUP_PACKET_LENGTH from PG source. ) // NewBackend creates a new Backend. @@ -123,7 +123,7 @@ func (b *Backend) ReceiveStartupMessage() (FrontendMessage, error) { if err != nil { return nil, err } - msgSize := int(binary.BigEndian.Uint32(buf) - 4) + msgSize := int(int32(binary.BigEndian.Uint32(buf)) - 4) if msgSize < minStartupPacketLen || msgSize > maxStartupPacketLen { return nil, fmt.Errorf("invalid length of startup packet: %d", msgSize) @@ -137,7 +137,7 @@ func (b *Backend) ReceiveStartupMessage() (FrontendMessage, error) { code := binary.BigEndian.Uint32(buf) switch code { - case ProtocolVersionNumber: + case ProtocolVersion30, ProtocolVersion32: err = b.startupMessage.Decode(buf) if err != nil { return nil, err @@ -176,7 +176,7 @@ func (b *Backend) Receive() (FrontendMessage, error) { b.msgType = header[0] - msgLength := int(binary.BigEndian.Uint32(header[1:])) + msgLength := int(int32(binary.BigEndian.Uint32(header[1:]))) if msgLength < 4 { return nil, fmt.Errorf("invalid message length: %d", msgLength) } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/backend_key_data.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/backend_key_data.go index 23f5da677..c73b2da0c 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/backend_key_data.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/backend_key_data.go @@ -2,6 +2,7 @@ package pgproto3 import ( "encoding/binary" + "encoding/hex" "encoding/json" "github.com/jackc/pgx/v5/internal/pgio" @@ -9,7 +10,7 @@ import ( type BackendKeyData struct { ProcessID uint32 - SecretKey uint32 + SecretKey []byte } // Backend identifies this message as sendable by the PostgreSQL backend. @@ -18,12 +19,13 @@ func (*BackendKeyData) Backend() {} // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message // type identifier and 4 byte message length. func (dst *BackendKeyData) Decode(src []byte) error { - if len(src) != 8 { + if len(src) < 8 { return &invalidMessageLenErr{messageType: "BackendKeyData", expectedLen: 8, actualLen: len(src)} } dst.ProcessID = binary.BigEndian.Uint32(src[:4]) - dst.SecretKey = binary.BigEndian.Uint32(src[4:]) + dst.SecretKey = make([]byte, len(src)-4) + copy(dst.SecretKey, src[4:]) return nil } @@ -32,7 +34,7 @@ func (dst *BackendKeyData) Decode(src []byte) error { func (src *BackendKeyData) Encode(dst []byte) ([]byte, error) { dst, sp := beginMessage(dst, 'K') dst = pgio.AppendUint32(dst, src.ProcessID) - dst = pgio.AppendUint32(dst, src.SecretKey) + dst = append(dst, src.SecretKey...) return finishMessage(dst, sp) } @@ -41,10 +43,29 @@ func (src BackendKeyData) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Type string ProcessID uint32 - SecretKey uint32 + SecretKey string }{ Type: "BackendKeyData", ProcessID: src.ProcessID, - SecretKey: src.SecretKey, + SecretKey: hex.EncodeToString(src.SecretKey), }) } + +// UnmarshalJSON implements encoding/json.Unmarshaler. +func (dst *BackendKeyData) UnmarshalJSON(data []byte) error { + var msg struct { + ProcessID uint32 + SecretKey string + } + if err := json.Unmarshal(data, &msg); err != nil { + return err + } + + dst.ProcessID = msg.ProcessID + secretKey, err := hex.DecodeString(msg.SecretKey) + if err != nil { + return err + } + dst.SecretKey = secretKey + return nil +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/bind.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/bind.go index ad6ac48bf..fb56e4dca 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/bind.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/bind.go @@ -54,7 +54,7 @@ func (dst *Bind) Decode(src []byte) error { if len(src[rp:]) < len(dst.ParameterFormatCodes)*2 { return &invalidMessageFormatErr{messageType: "Bind"} } - for i := 0; i < parameterFormatCodeCount; i++ { + for i := range parameterFormatCodeCount { dst.ParameterFormatCodes[i] = int16(binary.BigEndian.Uint16(src[rp:])) rp += 2 } @@ -69,7 +69,7 @@ func (dst *Bind) Decode(src []byte) error { if parameterCount > 0 { dst.Parameters = make([][]byte, parameterCount) - for i := 0; i < parameterCount; i++ { + for i := range parameterCount { if len(src[rp:]) < 4 { return &invalidMessageFormatErr{messageType: "Bind"} } @@ -82,7 +82,7 @@ func (dst *Bind) Decode(src []byte) error { continue } - if len(src[rp:]) < msgSize { + if msgSize < 0 || len(src[rp:]) < msgSize { return &invalidMessageFormatErr{messageType: "Bind"} } @@ -101,7 +101,7 @@ func (dst *Bind) Decode(src []byte) error { if len(src[rp:]) < len(dst.ResultFormatCodes)*2 { return &invalidMessageFormatErr{messageType: "Bind"} } - for i := 0; i < resultFormatCodeCount; i++ { + for i := range resultFormatCodeCount { dst.ResultFormatCodes[i] = int16(binary.BigEndian.Uint16(src[rp:])) rp += 2 } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/cancel_request.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/cancel_request.go index 6b52dd977..63ebe5c47 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/cancel_request.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/cancel_request.go @@ -2,6 +2,7 @@ package pgproto3 import ( "encoding/binary" + "encoding/hex" "encoding/json" "errors" @@ -12,35 +13,42 @@ const cancelRequestCode = 80877102 type CancelRequest struct { ProcessID uint32 - SecretKey uint32 + SecretKey []byte } // Frontend identifies this message as sendable by a PostgreSQL frontend. func (*CancelRequest) Frontend() {} func (dst *CancelRequest) Decode(src []byte) error { - if len(src) != 12 { - return errors.New("bad cancel request size") + if len(src) < 12 { + return errors.New("cancel request too short") + } + if len(src) > 264 { + return errors.New("cancel request too long") } requestCode := binary.BigEndian.Uint32(src) - if requestCode != cancelRequestCode { return errors.New("bad cancel request code") } dst.ProcessID = binary.BigEndian.Uint32(src[4:]) - dst.SecretKey = binary.BigEndian.Uint32(src[8:]) + dst.SecretKey = make([]byte, len(src)-8) + copy(dst.SecretKey, src[8:]) return nil } // Encode encodes src into dst. dst will include the 4 byte message length. func (src *CancelRequest) Encode(dst []byte) ([]byte, error) { - dst = pgio.AppendInt32(dst, 16) + if len(src.SecretKey) > 256 { + return nil, errors.New("secret key too long") + } + msgLen := int32(12 + len(src.SecretKey)) + dst = pgio.AppendInt32(dst, msgLen) dst = pgio.AppendInt32(dst, cancelRequestCode) dst = pgio.AppendUint32(dst, src.ProcessID) - dst = pgio.AppendUint32(dst, src.SecretKey) + dst = append(dst, src.SecretKey...) return dst, nil } @@ -49,10 +57,29 @@ func (src CancelRequest) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Type string ProcessID uint32 - SecretKey uint32 + SecretKey string }{ Type: "CancelRequest", ProcessID: src.ProcessID, - SecretKey: src.SecretKey, + SecretKey: hex.EncodeToString(src.SecretKey), }) } + +// UnmarshalJSON implements encoding/json.Unmarshaler. +func (dst *CancelRequest) UnmarshalJSON(data []byte) error { + var msg struct { + ProcessID uint32 + SecretKey string + } + if err := json.Unmarshal(data, &msg); err != nil { + return err + } + + dst.ProcessID = msg.ProcessID + secretKey, err := hex.DecodeString(msg.SecretKey) + if err != nil { + return err + } + dst.SecretKey = secretKey + return nil +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_both_response.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_both_response.go index 99e1afea4..e2a402f9a 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_both_response.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_both_response.go @@ -35,7 +35,7 @@ func (dst *CopyBothResponse) Decode(src []byte) error { } columnFormatCodes := make([]uint16, columnCount) - for i := 0; i < columnCount; i++ { + for i := range columnCount { columnFormatCodes[i] = binary.BigEndian.Uint16(buf.Next(2)) } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_done.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_done.go index 040814dbd..c3421a9b5 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_done.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_done.go @@ -4,8 +4,7 @@ import ( "encoding/json" ) -type CopyDone struct { -} +type CopyDone struct{} // Backend identifies this message as sendable by the PostgreSQL backend. func (*CopyDone) Backend() {} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_fail.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_fail.go index 72a85fd09..f8a00b8b7 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_fail.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_fail.go @@ -15,6 +15,10 @@ func (*CopyFail) Frontend() {} // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message // type identifier and 4 byte message length. func (dst *CopyFail) Decode(src []byte) error { + if len(src) == 0 { + return &invalidMessageFormatErr{messageType: "CopyFail"} + } + idx := bytes.IndexByte(src, 0) if idx != len(src)-1 { return &invalidMessageFormatErr{messageType: "CopyFail"} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_in_response.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_in_response.go index 06cf99ced..0633935b9 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_in_response.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_in_response.go @@ -35,7 +35,7 @@ func (dst *CopyInResponse) Decode(src []byte) error { } columnFormatCodes := make([]uint16, columnCount) - for i := 0; i < columnCount; i++ { + for i := range columnCount { columnFormatCodes[i] = binary.BigEndian.Uint16(buf.Next(2)) } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_out_response.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_out_response.go index 549e916c1..006864ac8 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_out_response.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/copy_out_response.go @@ -34,7 +34,7 @@ func (dst *CopyOutResponse) Decode(src []byte) error { } columnFormatCodes := make([]uint16, columnCount) - for i := 0; i < columnCount; i++ { + for i := range columnCount { columnFormatCodes[i] = binary.BigEndian.Uint16(buf.Next(2)) } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/data_row.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/data_row.go index fdfb0f7f6..54418d58c 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/data_row.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/data_row.go @@ -31,16 +31,13 @@ func (dst *DataRow) Decode(src []byte) error { // large reallocate. This is too avoid one row with many columns from // permanently allocating memory. if cap(dst.Values) < fieldCount || cap(dst.Values)-fieldCount > 32 { - newCap := 32 - if newCap < fieldCount { - newCap = fieldCount - } + newCap := max(32, fieldCount) dst.Values = make([][]byte, fieldCount, newCap) } else { dst.Values = dst.Values[:fieldCount] } - for i := 0; i < fieldCount; i++ { + for i := range fieldCount { if len(src[rp:]) < 4 { return &invalidMessageFormatErr{messageType: "DataRow"} } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/frontend.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/frontend.go index 056e547cd..3d66518bd 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/frontend.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/frontend.go @@ -52,6 +52,7 @@ type Frontend struct { readyForQuery ReadyForQuery rowDescription RowDescription portalSuspended PortalSuspended + negotiateProtocolVersion NegotiateProtocolVersion bodyLen int maxBodyLen int // maxBodyLen is the maximum length of a message body in octets. If a message body exceeds this length, Receive will return an error. @@ -230,7 +231,7 @@ func (f *Frontend) SendExecute(msg *Execute) { f.wbuf = newBuf if f.tracer != nil { - f.tracer.TraceQueryute('F', int32(len(f.wbuf)-prevLen), msg) + f.tracer.traceExecute('F', int32(len(f.wbuf)-prevLen), msg) } } @@ -312,7 +313,7 @@ func (f *Frontend) Receive() (BackendMessage, error) { f.msgType = header[0] - msgLength := int(binary.BigEndian.Uint32(header[1:])) + msgLength := int(int32(binary.BigEndian.Uint32(header[1:]))) if msgLength < 4 { return nil, fmt.Errorf("invalid message length: %d", msgLength) } @@ -383,6 +384,8 @@ func (f *Frontend) Receive() (BackendMessage, error) { msg = &f.copyBothResponse case 'Z': msg = &f.readyForQuery + case 'v': + msg = &f.negotiateProtocolVersion default: return nil, fmt.Errorf("unknown message type: %c", f.msgType) } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/function_call.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/function_call.go index 7d83579ff..23bbd8b8e 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/function_call.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/function_call.go @@ -23,6 +23,11 @@ func (*FunctionCall) Frontend() {} func (dst *FunctionCall) Decode(src []byte) error { *dst = FunctionCall{} rp := 0 + + if len(src) < 8 { + return &invalidMessageFormatErr{messageType: "FunctionCall"} + } + // Specifies the object ID of the function to call. dst.Function = binary.BigEndian.Uint32(src[rp:]) rp += 4 @@ -32,8 +37,13 @@ func (dst *FunctionCall) Decode(src []byte) error { // or it can equal the actual number of arguments. nArgumentCodes := int(binary.BigEndian.Uint16(src[rp:])) rp += 2 + + if len(src[rp:]) < nArgumentCodes*2+2 { + return &invalidMessageFormatErr{messageType: "FunctionCall"} + } + argumentCodes := make([]uint16, nArgumentCodes) - for i := 0; i < nArgumentCodes; i++ { + for i := range nArgumentCodes { // The argument format codes. Each must presently be zero (text) or one (binary). ac := binary.BigEndian.Uint16(src[rp:]) if ac != 0 && ac != 1 { @@ -48,14 +58,22 @@ func (dst *FunctionCall) Decode(src []byte) error { nArguments := int(binary.BigEndian.Uint16(src[rp:])) rp += 2 arguments := make([][]byte, nArguments) - for i := 0; i < nArguments; i++ { + for i := range nArguments { + if len(src[rp:]) < 4 { + return &invalidMessageFormatErr{messageType: "FunctionCall"} + } // The length of the argument value, in bytes (this count does not include itself). Can be zero. // As a special case, -1 indicates a NULL argument value. No value bytes follow in the NULL case. - argumentLength := int(binary.BigEndian.Uint32(src[rp:])) + argumentLength := int(int32(binary.BigEndian.Uint32(src[rp:]))) rp += 4 if argumentLength == -1 { arguments[i] = nil + } else if argumentLength < 0 { + return &invalidMessageFormatErr{messageType: "FunctionCall"} } else { + if len(src[rp:]) < argumentLength { + return &invalidMessageFormatErr{messageType: "FunctionCall"} + } // The value of the argument, in the format indicated by the associated format code. n is the above length. argumentValue := src[rp : rp+argumentLength] rp += argumentLength @@ -64,6 +82,9 @@ func (dst *FunctionCall) Decode(src []byte) error { } dst.Arguments = arguments // The format code for the function result. Must presently be zero (text) or one (binary). + if len(src[rp:]) < 2 { + return &invalidMessageFormatErr{messageType: "FunctionCall"} + } resultFormatCode := binary.BigEndian.Uint16(src[rp:]) if resultFormatCode != 0 && resultFormatCode != 1 { return &invalidMessageFormatErr{messageType: "FunctionCall"} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/function_call_response.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/function_call_response.go index 1f2734952..6b6ed8b92 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/function_call_response.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/function_call_response.go @@ -22,7 +22,7 @@ func (dst *FunctionCallResponse) Decode(src []byte) error { return &invalidMessageFormatErr{messageType: "FunctionCallResponse"} } rp := 0 - resultSize := int(binary.BigEndian.Uint32(src[rp:])) + resultSize := int(int32(binary.BigEndian.Uint32(src[rp:]))) rp += 4 if resultSize == -1 { @@ -30,7 +30,7 @@ func (dst *FunctionCallResponse) Decode(src []byte) error { return nil } - if len(src[rp:]) != resultSize { + if resultSize < 0 || len(src[rp:]) != resultSize { return &invalidMessageFormatErr{messageType: "FunctionCallResponse"} } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/gss_enc_request.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/gss_enc_request.go index 70cb20cd5..122d1341c 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/gss_enc_request.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/gss_enc_request.go @@ -10,8 +10,7 @@ import ( const gssEncReqNumber = 80877104 -type GSSEncRequest struct { -} +type GSSEncRequest struct{} // Frontend identifies this message as sendable by a PostgreSQL frontend. func (*GSSEncRequest) Frontend() {} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/negotiate_protocol_version.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/negotiate_protocol_version.go new file mode 100644 index 000000000..43bd7ec63 --- /dev/null +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/negotiate_protocol_version.go @@ -0,0 +1,93 @@ +package pgproto3 + +import ( + "encoding/binary" + "encoding/json" + + "github.com/jackc/pgx/v5/internal/pgio" +) + +type NegotiateProtocolVersion struct { + NewestMinorProtocol uint32 + UnrecognizedOptions []string +} + +// Backend identifies this message as sendable by the PostgreSQL backend. +func (*NegotiateProtocolVersion) Backend() {} + +// Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message +// type identifier and 4 byte message length. +func (dst *NegotiateProtocolVersion) Decode(src []byte) error { + if len(src) < 8 { + return &invalidMessageLenErr{messageType: "NegotiateProtocolVersion", expectedLen: 8, actualLen: len(src)} + } + + dst.NewestMinorProtocol = binary.BigEndian.Uint32(src[:4]) + optionCount := int(binary.BigEndian.Uint32(src[4:8])) + + rp := 8 + + // Use the remaining message size as an upper bound for capacity to prevent + // malicious optionCount values from causing excessive memory allocation. + capHint := optionCount + if remaining := len(src) - rp; capHint > remaining { + capHint = remaining + } + dst.UnrecognizedOptions = make([]string, 0, capHint) + for i := 0; i < optionCount; i++ { + if rp >= len(src) { + return &invalidMessageFormatErr{messageType: "NegotiateProtocolVersion"} + } + end := rp + for end < len(src) && src[end] != 0 { + end++ + } + if end >= len(src) { + return &invalidMessageFormatErr{messageType: "NegotiateProtocolVersion"} + } + dst.UnrecognizedOptions = append(dst.UnrecognizedOptions, string(src[rp:end])) + rp = end + 1 + } + + return nil +} + +// Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length. +func (src *NegotiateProtocolVersion) Encode(dst []byte) ([]byte, error) { + dst, sp := beginMessage(dst, 'v') + dst = pgio.AppendUint32(dst, src.NewestMinorProtocol) + dst = pgio.AppendUint32(dst, uint32(len(src.UnrecognizedOptions))) + for _, option := range src.UnrecognizedOptions { + dst = append(dst, option...) + dst = append(dst, 0) + } + return finishMessage(dst, sp) +} + +// MarshalJSON implements encoding/json.Marshaler. +func (src NegotiateProtocolVersion) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type string + NewestMinorProtocol uint32 + UnrecognizedOptions []string + }{ + Type: "NegotiateProtocolVersion", + NewestMinorProtocol: src.NewestMinorProtocol, + UnrecognizedOptions: src.UnrecognizedOptions, + }) +} + +// UnmarshalJSON implements encoding/json.Unmarshaler. +func (dst *NegotiateProtocolVersion) UnmarshalJSON(data []byte) error { + var msg struct { + NewestMinorProtocol uint32 + UnrecognizedOptions []string + } + if err := json.Unmarshal(data, &msg); err != nil { + return err + } + + dst.NewestMinorProtocol = msg.NewestMinorProtocol + dst.UnrecognizedOptions = msg.UnrecognizedOptions + return nil +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/parameter_description.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/parameter_description.go index 1ef27b75f..58eb26ef0 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/parameter_description.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/parameter_description.go @@ -33,7 +33,7 @@ func (dst *ParameterDescription) Decode(src []byte) error { *dst = ParameterDescription{ParameterOIDs: make([]uint32, parameterCount)} - for i := 0; i < parameterCount; i++ { + for i := range parameterCount { dst.ParameterOIDs[i] = binary.BigEndian.Uint32(buf.Next(4)) } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/parse.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/parse.go index 6ba3486cf..8fb8de5d4 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/parse.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/parse.go @@ -43,7 +43,7 @@ func (dst *Parse) Decode(src []byte) error { } parameterOIDCount := int(binary.BigEndian.Uint16(buf.Next(2))) - for i := 0; i < parameterOIDCount; i++ { + for range parameterOIDCount { if buf.Len() < 4 { return &invalidMessageFormatErr{messageType: "Parse"} } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/query.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/query.go index aebdfde89..9e16465c2 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/query.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/query.go @@ -15,6 +15,10 @@ func (*Query) Frontend() {} // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message // type identifier and 4 byte message length. func (dst *Query) Decode(src []byte) error { + if len(src) == 0 { + return &invalidMessageFormatErr{messageType: "Query"} + } + i := bytes.IndexByte(src, 0) if i != len(src)-1 { return &invalidMessageFormatErr{messageType: "Query"} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/row_description.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/row_description.go index dc2a4ddf2..b46f510dc 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/row_description.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/row_description.go @@ -56,7 +56,6 @@ func (*RowDescription) Backend() {} // Decode decodes src into dst. src must contain the complete message with the exception of the initial 1 byte message // type identifier and 4 byte message length. func (dst *RowDescription) Decode(src []byte) error { - if len(src) < 2 { return &invalidMessageFormatErr{messageType: "RowDescription"} } @@ -65,7 +64,7 @@ func (dst *RowDescription) Decode(src []byte) error { dst.Fields = dst.Fields[0:0] - for i := 0; i < fieldCount; i++ { + for range fieldCount { var fd FieldDescription idx := bytes.IndexByte(src[rp:], 0) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/sasl_initial_response.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/sasl_initial_response.go index 9eb1b6a4b..123f3cd66 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/sasl_initial_response.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/sasl_initial_response.go @@ -32,6 +32,9 @@ func (dst *SASLInitialResponse) Decode(src []byte) error { dst.AuthMechanism = string(src[rp:idx]) rp = idx + 1 + if len(src[rp:]) < 4 { + return errors.New("invalid SASLInitialResponse") + } rp += 4 // The rest of the message is data so we can just skip the size dst.Data = src[rp:] diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/ssl_request.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/ssl_request.go index b0fc28476..bdfc7c427 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/ssl_request.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/ssl_request.go @@ -10,8 +10,7 @@ import ( const sslRequestNumber = 80877103 -type SSLRequest struct { -} +type SSLRequest struct{} // Frontend identifies this message as sendable by a PostgreSQL frontend. func (*SSLRequest) Frontend() {} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/startup_message.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/startup_message.go index 3af4587d8..6caab3ee4 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/startup_message.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/startup_message.go @@ -10,7 +10,11 @@ import ( "github.com/jackc/pgx/v5/internal/pgio" ) -const ProtocolVersionNumber = 196608 // 3.0 +const ( + ProtocolVersion30 = 196608 // 3.0 + ProtocolVersion32 = 196610 // 3.2 + ProtocolVersionNumber = ProtocolVersion30 // Default is still 3.0 +) type StartupMessage struct { ProtocolVersion uint32 @@ -30,8 +34,8 @@ func (dst *StartupMessage) Decode(src []byte) error { dst.ProtocolVersion = binary.BigEndian.Uint32(src) rp := 4 - if dst.ProtocolVersion != ProtocolVersionNumber { - return fmt.Errorf("Bad startup message version number. Expected %d, got %d", ProtocolVersionNumber, dst.ProtocolVersion) + if dst.ProtocolVersion != ProtocolVersion30 && dst.ProtocolVersion != ProtocolVersion32 { + return fmt.Errorf("Bad startup message version number. Expected %d or %d, got %d", ProtocolVersion30, ProtocolVersion32, dst.ProtocolVersion) } dst.Parameters = make(map[string]string) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/trace.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/trace.go index 6cc7d3e36..2f9da6289 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/trace.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgproto3/trace.go @@ -82,7 +82,7 @@ func (t *tracer) traceMessage(sender byte, encodedLen int32, msg Message) { case *ErrorResponse: t.traceErrorResponse(sender, encodedLen, msg) case *Execute: - t.TraceQueryute(sender, encodedLen, msg) + t.traceExecute(sender, encodedLen, msg) case *Flush: t.traceFlush(sender, encodedLen, msg) case *FunctionCall: @@ -260,7 +260,7 @@ func (t *tracer) traceErrorResponse(sender byte, encodedLen int32, msg *ErrorRes t.writeTrace(sender, encodedLen, "ErrorResponse", nil) } -func (t *tracer) TraceQueryute(sender byte, encodedLen int32, msg *Execute) { +func (t *tracer) traceExecute(sender byte, encodedLen int32, msg *Execute) { t.writeTrace(sender, encodedLen, "Execute", func() { fmt.Fprintf(t.buf, "\t %s %d", traceDoubleQuotedString([]byte(msg.Portal)), msg.MaxRows) }) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/array.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/array.go index 06b824ad0..10b96e7b6 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/array.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/array.go @@ -38,6 +38,10 @@ func cardinality(dimensions []ArrayDimension) int { elementCount *= int(d.Length) } + if elementCount < 0 { + return 0 + } + return elementCount } @@ -51,16 +55,20 @@ func (dst *arrayHeader) DecodeBinary(m *Map, src []byte) (int, error) { numDims := int(binary.BigEndian.Uint32(src[rp:])) rp += 4 + if numDims > 6 { + return 0, fmt.Errorf("array has too many dimensions: %d", numDims) + } + dst.ContainsNull = binary.BigEndian.Uint32(src[rp:]) == 1 rp += 4 dst.ElementOID = binary.BigEndian.Uint32(src[rp:]) rp += 4 - dst.Dimensions = make([]ArrayDimension, numDims) if len(src) < 12+numDims*8 { return 0, fmt.Errorf("array header too short for %d dimensions: %d", numDims, len(src)) } + dst.Dimensions = make([]ArrayDimension, numDims) for i := range dst.Dimensions { dst.Dimensions[i].Length = int32(binary.BigEndian.Uint32(src[rp:])) rp += 4 @@ -299,7 +307,7 @@ func arrayParseQuotedValue(buf *bytes.Buffer) (string, bool, error) { return "", false, err } case '"': - r, _, err = buf.ReadRune() + _, _, err = buf.ReadRune() if err != nil { return "", false, err } @@ -374,8 +382,8 @@ func quoteArrayElementIfNeeded(src string) string { return src } -// Array represents a PostgreSQL array for T. It implements the ArrayGetter and ArraySetter interfaces. It preserves -// PostgreSQL dimensions and custom lower bounds. Use FlatArray if these are not needed. +// Array represents a PostgreSQL array for T. It implements the [ArrayGetter] and [ArraySetter] interfaces. It preserves +// PostgreSQL dimensions and custom lower bounds. Use [FlatArray] if these are not needed. type Array[T any] struct { Elements []T Dims []ArrayDimension @@ -419,8 +427,8 @@ func (a Array[T]) ScanIndexType() any { return new(T) } -// FlatArray implements the ArrayGetter and ArraySetter interfaces for any slice of T. It ignores PostgreSQL dimensions -// and custom lower bounds. Use Array to preserve these. +// FlatArray implements the [ArrayGetter] and [ArraySetter] interfaces for any slice of T. It ignores PostgreSQL dimensions +// and custom lower bounds. Use [Array] to preserve these. type FlatArray[T any] []T func (a FlatArray[T]) Dimensions() []ArrayDimension { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/array_codec.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/array_codec.go index bf5f6989a..f6b36f439 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/array_codec.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/array_codec.go @@ -118,7 +118,7 @@ func (p *encodePlanArrayCodecText) Encode(value any, buf []byte) (newBuf []byte, var encodePlan EncodePlan var lastElemType reflect.Type inElemBuf := make([]byte, 0, 32) - for i := 0; i < elementCount; i++ { + for i := range elementCount { if i > 0 { buf = append(buf, ',') } @@ -131,7 +131,7 @@ func (p *encodePlanArrayCodecText) Encode(value any, buf []byte) (newBuf []byte, elem := array.Index(i) var elemBuf []byte - if elem != nil { + if isNil, _ := isNilDriverValuer(elem); !isNil { elemType := reflect.TypeOf(elem) if lastElemType != elemType { lastElemType = elemType @@ -189,13 +189,13 @@ func (p *encodePlanArrayCodecBinary) Encode(value any, buf []byte) (newBuf []byt var encodePlan EncodePlan var lastElemType reflect.Type - for i := 0; i < elementCount; i++ { + for i := range elementCount { sp := len(buf) buf = pgio.AppendInt32(buf, -1) elem := array.Index(i) var elemBuf []byte - if elem != nil { + if isNil, _ := isNilDriverValuer(elem); !isNil { elemType := reflect.TypeOf(elem) if lastElemType != elemType { lastElemType = elemType @@ -270,7 +270,7 @@ func (c *ArrayCodec) decodeBinary(m *Map, arrayOID uint32, src []byte, array Arr elementScanPlan = m.PlanScan(c.ElementType.OID, BinaryFormatCode, array.ScanIndex(0)) } - for i := 0; i < elementCount; i++ { + for i := range elementCount { elem := array.ScanIndex(i) elemLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) rp += 4 @@ -388,7 +388,7 @@ func isRagged(slice reflect.Value) bool { sliceLen := slice.Len() innerLen := 0 - for i := 0; i < sliceLen; i++ { + for i := range sliceLen { if i == 0 { innerLen = slice.Index(i).Len() } else { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bits.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bits.go index e7a1d016d..2a48e3549 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bits.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bits.go @@ -23,16 +23,18 @@ type Bits struct { Valid bool } +// ScanBits implements the [BitsScanner] interface. func (b *Bits) ScanBits(v Bits) error { *b = v return nil } +// BitsValue implements the [BitsValuer] interface. func (b Bits) BitsValue() (Bits, error) { return b, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Bits) Scan(src any) error { if src == nil { *dst = Bits{} @@ -47,7 +49,7 @@ func (dst *Bits) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Bits) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -127,7 +129,6 @@ func (encodePlanBitsCodecText) Encode(value any, buf []byte) (newBuf []byte, err } func (BitsCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bool.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bool.go index 71caffa74..955f01fe8 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bool.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bool.go @@ -22,16 +22,18 @@ type Bool struct { Valid bool } +// ScanBool implements the [BoolScanner] interface. func (b *Bool) ScanBool(v Bool) error { *b = v return nil } +// BoolValue implements the [BoolValuer] interface. func (b Bool) BoolValue() (Bool, error) { return b, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Bool) Scan(src any) error { if src == nil { *dst = Bool{} @@ -61,7 +63,7 @@ func (dst *Bool) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Bool) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -70,6 +72,7 @@ func (src Bool) Value() (driver.Value, error) { return src.Bool, nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (src Bool) MarshalJSON() ([]byte, error) { if !src.Valid { return []byte("null"), nil @@ -82,6 +85,7 @@ func (src Bool) MarshalJSON() ([]byte, error) { } } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (dst *Bool) UnmarshalJSON(b []byte) error { var v *bool err := json.Unmarshal(b, &v) @@ -200,7 +204,6 @@ func (encodePlanBoolCodecTextBool) Encode(value any, buf []byte) (newBuf []byte, } func (BoolCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { @@ -328,7 +331,7 @@ func (scanPlanTextAnyToBoolScanner) Scan(src []byte, dst any) error { return s.ScanBool(Bool{Bool: v, Valid: true}) } -// https://www.postgresql.org/docs/11/datatype-boolean.html +// https://www.postgresql.org/docs/current/datatype-boolean.html func planTextToBool(src []byte) (bool, error) { s := string(bytes.ToLower(bytes.TrimSpace(src))) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/box.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/box.go index 887d268bc..d243f58e3 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/box.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/box.go @@ -24,16 +24,18 @@ type Box struct { Valid bool } +// ScanBox implements the [BoxScanner] interface. func (b *Box) ScanBox(v Box) error { *b = v return nil } +// BoxValue implements the [BoxValuer] interface. func (b Box) BoxValue() (Box, error) { return b, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Box) Scan(src any) error { if src == nil { *dst = Box{} @@ -48,7 +50,7 @@ func (dst *Box) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Box) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -127,7 +129,6 @@ func (encodePlanBoxCodecText) Encode(value any, buf []byte) (newBuf []byte, err } func (BoxCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/builtin_wrappers.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/builtin_wrappers.go index b39d3fa10..126e0be2a 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/builtin_wrappers.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/builtin_wrappers.go @@ -527,6 +527,7 @@ func (w *netIPNetWrapper) ScanNetipPrefix(v netip.Prefix) error { return nil } + func (w netIPNetWrapper) NetipPrefixValue() (netip.Prefix, error) { ip, ok := netip.AddrFromSlice(w.IP) if !ok { @@ -881,7 +882,6 @@ func (a *anyMultiDimSliceArray) SetDimensions(dimensions []ArrayDimension) error return nil } - } func (a *anyMultiDimSliceArray) makeMultidimensionalSlice(sliceType reflect.Type, dimensions []ArrayDimension, flatSlice reflect.Value, flatSliceIdx int) reflect.Value { @@ -892,7 +892,7 @@ func (a *anyMultiDimSliceArray) makeMultidimensionalSlice(sliceType reflect.Type sliceLen := int(dimensions[0].Length) slice := reflect.MakeSlice(sliceType, sliceLen, sliceLen) - for i := 0; i < sliceLen; i++ { + for i := range sliceLen { subSlice := a.makeMultidimensionalSlice(sliceType.Elem(), dimensions[1:], flatSlice, flatSliceIdx+(i*int(dimensions[1].Length))) slice.Index(i).Set(subSlice) } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bytea.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bytea.go index a247705e9..6c4f0c5ea 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bytea.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/bytea.go @@ -148,7 +148,6 @@ func (encodePlanBytesCodecTextBytesValuer) Encode(value any, buf []byte) (newBuf } func (ByteaCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/circle.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/circle.go index e8f118cc9..fb9b4c11d 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/circle.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/circle.go @@ -25,16 +25,18 @@ type Circle struct { Valid bool } +// ScanCircle implements the [CircleScanner] interface. func (c *Circle) ScanCircle(v Circle) error { *c = v return nil } +// CircleValue implements the [CircleValuer] interface. func (c Circle) CircleValue() (Circle, error) { return c, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Circle) Scan(src any) error { if src == nil { *dst = Circle{} @@ -49,7 +51,7 @@ func (dst *Circle) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Circle) Value() (driver.Value, error) { if !src.Valid { return nil, nil diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/composite.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/composite.go index fb372325b..4667036b8 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/composite.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/composite.go @@ -276,7 +276,6 @@ func (c *CompositeCodec) DecodeValue(m *Map, oid uint32, format int16, src []byt default: return nil, fmt.Errorf("unknown format code %d", format) } - } type CompositeBinaryScanner struct { @@ -290,7 +289,7 @@ type CompositeBinaryScanner struct { err error } -// NewCompositeBinaryScanner a scanner over a binary encoded composite balue. +// NewCompositeBinaryScanner a scanner over a binary encoded composite value. func NewCompositeBinaryScanner(m *Map, src []byte) *CompositeBinaryScanner { rp := 0 if len(src[rp:]) < 4 { @@ -477,7 +476,7 @@ func (b *CompositeBinaryBuilder) AppendValue(oid uint32, field any) { return } - if field == nil { + if isNil, _ := isNilDriverValuer(field); isNil { b.buf = pgio.AppendUint32(b.buf, oid) b.buf = pgio.AppendInt32(b.buf, -1) b.fieldCount++ @@ -534,7 +533,7 @@ func (b *CompositeTextBuilder) AppendValue(oid uint32, field any) { return } - if field == nil { + if isNil, _ := isNilDriverValuer(field); isNil { b.buf = append(b.buf, ',') return } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/convert.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/convert.go index 8a9cee9c3..5cfc0ea30 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/convert.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/convert.go @@ -90,19 +90,19 @@ func GetAssignToDstType(dst any) (any, bool) { func init() { kindTypes = map[reflect.Kind]reflect.Type{ - reflect.Bool: reflect.TypeOf(false), - reflect.Float32: reflect.TypeOf(float32(0)), - reflect.Float64: reflect.TypeOf(float64(0)), - reflect.Int: reflect.TypeOf(int(0)), - reflect.Int8: reflect.TypeOf(int8(0)), - reflect.Int16: reflect.TypeOf(int16(0)), - reflect.Int32: reflect.TypeOf(int32(0)), - reflect.Int64: reflect.TypeOf(int64(0)), - reflect.Uint: reflect.TypeOf(uint(0)), - reflect.Uint8: reflect.TypeOf(uint8(0)), - reflect.Uint16: reflect.TypeOf(uint16(0)), - reflect.Uint32: reflect.TypeOf(uint32(0)), - reflect.Uint64: reflect.TypeOf(uint64(0)), - reflect.String: reflect.TypeOf(""), + reflect.Bool: reflect.TypeFor[bool](), + reflect.Float32: reflect.TypeFor[float32](), + reflect.Float64: reflect.TypeFor[float64](), + reflect.Int: reflect.TypeFor[int](), + reflect.Int8: reflect.TypeFor[int8](), + reflect.Int16: reflect.TypeFor[int16](), + reflect.Int32: reflect.TypeFor[int32](), + reflect.Int64: reflect.TypeFor[int64](), + reflect.Uint: reflect.TypeFor[uint](), + reflect.Uint8: reflect.TypeFor[uint8](), + reflect.Uint16: reflect.TypeFor[uint16](), + reflect.Uint32: reflect.TypeFor[uint32](), + reflect.Uint64: reflect.TypeFor[uint64](), + reflect.String: reflect.TypeFor[string](), } } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/date.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/date.go index 784b16deb..68c9585e8 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/date.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/date.go @@ -5,7 +5,6 @@ import ( "encoding/binary" "encoding/json" "fmt" - "regexp" "strconv" "time" @@ -26,11 +25,13 @@ type Date struct { Valid bool } +// ScanDate implements the [DateScanner] interface. func (d *Date) ScanDate(v Date) error { *d = v return nil } +// DateValue implements the [DateValuer] interface. func (d Date) DateValue() (Date, error) { return d, nil } @@ -40,7 +41,7 @@ const ( infinityDayOffset = 2147483647 ) -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Date) Scan(src any) error { if src == nil { *dst = Date{} @@ -58,7 +59,7 @@ func (dst *Date) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Date) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -70,6 +71,7 @@ func (src Date) Value() (driver.Value, error) { return src.Time, nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (src Date) MarshalJSON() ([]byte, error) { if !src.Valid { return []byte("null"), nil @@ -89,6 +91,7 @@ func (src Date) MarshalJSON() ([]byte, error) { return json.Marshal(s) } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (dst *Date) UnmarshalJSON(b []byte) error { var s *string err := json.Unmarshal(b, &s) @@ -223,7 +226,6 @@ func (encodePlanDateCodecText) Encode(value any, buf []byte) (newBuf []byte, err } func (DateCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { @@ -268,8 +270,6 @@ func (scanPlanBinaryDateToDateScanner) Scan(src []byte, dst any) error { type scanPlanTextAnyToDateScanner struct{} -var dateRegexp = regexp.MustCompile(`^(\d{4,})-(\d\d)-(\d\d)( BC)?$`) - func (scanPlanTextAnyToDateScanner) Scan(src []byte, dst any) error { scanner := (dst).(DateScanner) @@ -277,41 +277,104 @@ func (scanPlanTextAnyToDateScanner) Scan(src []byte, dst any) error { return scanner.ScanDate(Date{}) } - sbuf := string(src) - match := dateRegexp.FindStringSubmatch(sbuf) - if match != nil { - year, err := strconv.ParseInt(match[1], 10, 32) - if err != nil { - return fmt.Errorf("BUG: cannot parse date that regexp matched (year): %w", err) - } + // Check infinity cases first + if len(src) == 8 && string(src) == "infinity" { + return scanner.ScanDate(Date{InfinityModifier: Infinity, Valid: true}) + } + if len(src) == 9 && string(src) == "-infinity" { + return scanner.ScanDate(Date{InfinityModifier: -Infinity, Valid: true}) + } - month, err := strconv.ParseInt(match[2], 10, 32) - if err != nil { - return fmt.Errorf("BUG: cannot parse date that regexp matched (month): %w", err) - } + // Format: YYYY-MM-DD or YYYY...-MM-DD BC + // Minimum: 10 chars (2000-01-01), with BC: 13 chars + if len(src) < 10 { + return fmt.Errorf("invalid date format") + } - day, err := strconv.ParseInt(match[3], 10, 32) - if err != nil { - return fmt.Errorf("BUG: cannot parse date that regexp matched (month): %w", err) - } + // Check for BC suffix + bc := false + datePart := src + if len(src) >= 13 && string(src[len(src)-3:]) == " BC" { + bc = true + datePart = src[:len(src)-3] + } - // BC matched - if len(match[4]) > 0 { - year = -year + 1 + // Find year-month separator (first dash after at least 4 digits) + yearEnd := -1 + for i := 4; i < len(datePart); i++ { + if datePart[i] == '-' { + yearEnd = i + break + } + if datePart[i] < '0' || datePart[i] > '9' { + return fmt.Errorf("invalid date format") } + } + if yearEnd == -1 || yearEnd+6 > len(datePart) { + return fmt.Errorf("invalid date format") + } - t := time.Date(int(year), time.Month(month), int(day), 0, 0, 0, 0, time.UTC) - return scanner.ScanDate(Date{Time: t, Valid: true}) + // Validate: -MM-DD structure after year + if datePart[yearEnd+3] != '-' { + return fmt.Errorf("invalid date format") } - switch sbuf { - case "infinity": - return scanner.ScanDate(Date{InfinityModifier: Infinity, Valid: true}) - case "-infinity": - return scanner.ScanDate(Date{InfinityModifier: -Infinity, Valid: true}) - default: + // Parse year + year, err := parseDigits(datePart[:yearEnd]) + if err != nil { + return fmt.Errorf("invalid date format") + } + + // Parse month (2 digits) + month, err := parse2Digits(datePart[yearEnd+1 : yearEnd+3]) + if err != nil { + return fmt.Errorf("invalid date format") + } + + // Parse day (2 digits) + day, err := parse2Digits(datePart[yearEnd+4 : yearEnd+6]) + if err != nil { + return fmt.Errorf("invalid date format") + } + + // Ensure nothing extra after day + if yearEnd+6 != len(datePart) { return fmt.Errorf("invalid date format") } + + if bc { + year = -year + 1 + } + + t := time.Date(int(year), time.Month(month), int(day), 0, 0, 0, 0, time.UTC) + return scanner.ScanDate(Date{Time: t, Valid: true}) +} + +// parse2Digits parses exactly 2 ASCII digits. +func parse2Digits(b []byte) (int64, error) { + if len(b) != 2 { + return 0, fmt.Errorf("expected 2 digits") + } + d1, d2 := b[0], b[1] + if d1 < '0' || d1 > '9' || d2 < '0' || d2 > '9' { + return 0, fmt.Errorf("expected digits") + } + return int64(d1-'0')*10 + int64(d2-'0'), nil +} + +// parseDigits parses a sequence of ASCII digits. +func parseDigits(b []byte) (int64, error) { + if len(b) == 0 { + return 0, fmt.Errorf("empty") + } + var n int64 + for _, c := range b { + if c < '0' || c > '9' { + return 0, fmt.Errorf("non-digit") + } + n = n*10 + int64(c-'0') + } + return n, nil } func (c DateCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/float4.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/float4.go index 8646d9d22..241a25add 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/float4.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/float4.go @@ -16,26 +16,29 @@ type Float4 struct { Valid bool } -// ScanFloat64 implements the Float64Scanner interface. +// ScanFloat64 implements the [Float64Scanner] interface. func (f *Float4) ScanFloat64(n Float8) error { *f = Float4{Float32: float32(n.Float64), Valid: n.Valid} return nil } +// Float64Value implements the [Float64Valuer] interface. func (f Float4) Float64Value() (Float8, error) { return Float8{Float64: float64(f.Float32), Valid: f.Valid}, nil } +// ScanInt64 implements the [Int64Scanner] interface. func (f *Float4) ScanInt64(n Int8) error { *f = Float4{Float32: float32(n.Int64), Valid: n.Valid} return nil } +// Int64Value implements the [Int64Valuer] interface. func (f Float4) Int64Value() (Int8, error) { return Int8{Int64: int64(f.Float32), Valid: f.Valid}, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (f *Float4) Scan(src any) error { if src == nil { *f = Float4{} @@ -58,7 +61,7 @@ func (f *Float4) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (f Float4) Value() (driver.Value, error) { if !f.Valid { return nil, nil @@ -66,6 +69,7 @@ func (f Float4) Value() (driver.Value, error) { return float64(f.Float32), nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (f Float4) MarshalJSON() ([]byte, error) { if !f.Valid { return []byte("null"), nil @@ -73,6 +77,7 @@ func (f Float4) MarshalJSON() ([]byte, error) { return json.Marshal(f.Float32) } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (f *Float4) UnmarshalJSON(b []byte) error { var n *float32 err := json.Unmarshal(b, &n) @@ -170,7 +175,6 @@ func (encodePlanFloat4CodecBinaryInt64Valuer) Encode(value any, buf []byte) (new } func (Float4Codec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/float8.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/float8.go index 9c923c9a3..54d6781ec 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/float8.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/float8.go @@ -24,26 +24,29 @@ type Float8 struct { Valid bool } -// ScanFloat64 implements the Float64Scanner interface. +// ScanFloat64 implements the [Float64Scanner] interface. func (f *Float8) ScanFloat64(n Float8) error { *f = n return nil } +// Float64Value implements the [Float64Valuer] interface. func (f Float8) Float64Value() (Float8, error) { return f, nil } +// ScanInt64 implements the [Int64Scanner] interface. func (f *Float8) ScanInt64(n Int8) error { *f = Float8{Float64: float64(n.Int64), Valid: n.Valid} return nil } +// Int64Value implements the [Int64Valuer] interface. func (f Float8) Int64Value() (Int8, error) { return Int8{Int64: int64(f.Float64), Valid: f.Valid}, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (f *Float8) Scan(src any) error { if src == nil { *f = Float8{} @@ -66,7 +69,7 @@ func (f *Float8) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (f Float8) Value() (driver.Value, error) { if !f.Valid { return nil, nil @@ -74,6 +77,7 @@ func (f Float8) Value() (driver.Value, error) { return f.Float64, nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (f Float8) MarshalJSON() ([]byte, error) { if !f.Valid { return []byte("null"), nil @@ -81,6 +85,7 @@ func (f Float8) MarshalJSON() ([]byte, error) { return json.Marshal(f.Float64) } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (f *Float8) UnmarshalJSON(b []byte) error { var n *float64 err := json.Unmarshal(b, &n) @@ -208,7 +213,6 @@ func (encodePlanTextInt64Valuer) Encode(value any, buf []byte) (newBuf []byte, e } func (Float8Codec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/hstore.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/hstore.go index 2f34f4c9e..c5fa22c63 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/hstore.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/hstore.go @@ -22,16 +22,18 @@ type HstoreValuer interface { // associated with its keys. type Hstore map[string]*string +// ScanHstore implements the [HstoreScanner] interface. func (h *Hstore) ScanHstore(v Hstore) error { *h = v return nil } +// HstoreValue implements the [HstoreValuer] interface. func (h Hstore) HstoreValue() (Hstore, error) { return h, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (h *Hstore) Scan(src any) error { if src == nil { *h = nil @@ -46,7 +48,7 @@ func (h *Hstore) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (h Hstore) Value() (driver.Value, error) { if h == nil { return nil, nil @@ -162,7 +164,6 @@ func (encodePlanHstoreCodecText) Encode(value any, buf []byte) (newBuf []byte, e } func (HstoreCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { @@ -197,17 +198,24 @@ func (scanPlanBinaryHstoreToHstoreScanner) Scan(src []byte, dst any) error { pairCount := int(int32(binary.BigEndian.Uint32(src[rp:]))) rp += uint32Len + if pairCount < 0 { + return fmt.Errorf("hstore invalid pair count: %d", pairCount) + } + hstore := make(Hstore, pairCount) // one allocation for all *string, rather than one per string, just like text parsing valueStrings := make([]string, pairCount) - for i := 0; i < pairCount; i++ { + for i := range pairCount { if len(src[rp:]) < uint32Len { return fmt.Errorf("hstore incomplete %v", src) } keyLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) rp += uint32Len + if keyLen < 0 { + return fmt.Errorf("hstore invalid key length: %d", keyLen) + } if len(src[rp:]) < keyLen { return fmt.Errorf("hstore incomplete %v", src) } @@ -298,7 +306,7 @@ func (p *hstoreParser) consume() (b byte, end bool) { return b, false } -func unexpectedByteErr(actualB byte, expectedB byte) error { +func unexpectedByteErr(actualB, expectedB byte) error { return fmt.Errorf("expected '%c' ('%#v'); found '%c' ('%#v')", expectedB, expectedB, actualB, actualB) } @@ -316,7 +324,7 @@ func (p *hstoreParser) consumeExpectedByte(expectedB byte) error { // consumeExpected2 consumes two expected bytes or returns an error. // This was a bit faster than using a string argument (better inlining? Not sure). -func (p *hstoreParser) consumeExpected2(one byte, two byte) error { +func (p *hstoreParser) consumeExpected2(one, two byte) error { if p.pos+2 > len(p.str) { return errors.New("unexpected end of string") } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/inet.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/inet.go index 6ca10ea07..b92edb239 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/inet.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/inet.go @@ -24,7 +24,7 @@ type NetipPrefixValuer interface { NetipPrefixValue() (netip.Prefix, error) } -// InetCodec handles both inet and cidr PostgreSQL types. The preferred Go types are netip.Prefix and netip.Addr. If +// InetCodec handles both inet and cidr PostgreSQL types. The preferred Go types are [netip.Prefix] and [netip.Addr]. If // IsValid() is false then they are treated as SQL NULL. type InetCodec struct{} @@ -107,7 +107,6 @@ func (encodePlanInetCodecText) Encode(value any, buf []byte) (newBuf []byte, err } func (InetCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/int.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/int.go index 7a2f8cb24..95032e5a2 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/int.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/int.go @@ -26,7 +26,7 @@ type Int2 struct { Valid bool } -// ScanInt64 implements the Int64Scanner interface. +// ScanInt64 implements the [Int64Scanner] interface. func (dst *Int2) ScanInt64(n Int8) error { if !n.Valid { *dst = Int2{} @@ -44,11 +44,12 @@ func (dst *Int2) ScanInt64(n Int8) error { return nil } +// Int64Value implements the [Int64Valuer] interface. func (n Int2) Int64Value() (Int8, error) { return Int8{Int64: int64(n.Int16), Valid: n.Valid}, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Int2) Scan(src any) error { if src == nil { *dst = Int2{} @@ -77,7 +78,7 @@ func (dst *Int2) Scan(src any) error { } if n < math.MinInt16 { - return fmt.Errorf("%d is greater than maximum value for Int2", n) + return fmt.Errorf("%d is less than minimum value for Int2", n) } if n > math.MaxInt16 { return fmt.Errorf("%d is greater than maximum value for Int2", n) @@ -87,7 +88,7 @@ func (dst *Int2) Scan(src any) error { return nil } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Int2) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -95,6 +96,7 @@ func (src Int2) Value() (driver.Value, error) { return int64(src.Int16), nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (src Int2) MarshalJSON() ([]byte, error) { if !src.Valid { return []byte("null"), nil @@ -102,6 +104,7 @@ func (src Int2) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatInt(int64(src.Int16), 10)), nil } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (dst *Int2) UnmarshalJSON(b []byte) error { var n *int16 err := json.Unmarshal(b, &n) @@ -586,7 +589,7 @@ type Int4 struct { Valid bool } -// ScanInt64 implements the Int64Scanner interface. +// ScanInt64 implements the [Int64Scanner] interface. func (dst *Int4) ScanInt64(n Int8) error { if !n.Valid { *dst = Int4{} @@ -604,11 +607,12 @@ func (dst *Int4) ScanInt64(n Int8) error { return nil } +// Int64Value implements the [Int64Valuer] interface. func (n Int4) Int64Value() (Int8, error) { return Int8{Int64: int64(n.Int32), Valid: n.Valid}, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Int4) Scan(src any) error { if src == nil { *dst = Int4{} @@ -637,7 +641,7 @@ func (dst *Int4) Scan(src any) error { } if n < math.MinInt32 { - return fmt.Errorf("%d is greater than maximum value for Int4", n) + return fmt.Errorf("%d is less than minimum value for Int4", n) } if n > math.MaxInt32 { return fmt.Errorf("%d is greater than maximum value for Int4", n) @@ -647,7 +651,7 @@ func (dst *Int4) Scan(src any) error { return nil } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Int4) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -655,6 +659,7 @@ func (src Int4) Value() (driver.Value, error) { return int64(src.Int32), nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (src Int4) MarshalJSON() ([]byte, error) { if !src.Valid { return []byte("null"), nil @@ -662,6 +667,7 @@ func (src Int4) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatInt(int64(src.Int32), 10)), nil } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (dst *Int4) UnmarshalJSON(b []byte) error { var n *int32 err := json.Unmarshal(b, &n) @@ -1157,7 +1163,7 @@ type Int8 struct { Valid bool } -// ScanInt64 implements the Int64Scanner interface. +// ScanInt64 implements the [Int64Scanner] interface. func (dst *Int8) ScanInt64(n Int8) error { if !n.Valid { *dst = Int8{} @@ -1175,11 +1181,12 @@ func (dst *Int8) ScanInt64(n Int8) error { return nil } +// Int64Value implements the [Int64Valuer] interface. func (n Int8) Int64Value() (Int8, error) { return Int8{Int64: int64(n.Int64), Valid: n.Valid}, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Int8) Scan(src any) error { if src == nil { *dst = Int8{} @@ -1218,7 +1225,7 @@ func (dst *Int8) Scan(src any) error { return nil } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Int8) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -1226,6 +1233,7 @@ func (src Int8) Value() (driver.Value, error) { return int64(src.Int64), nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (src Int8) MarshalJSON() ([]byte, error) { if !src.Valid { return []byte("null"), nil @@ -1233,6 +1241,7 @@ func (src Int8) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatInt(int64(src.Int64), 10)), nil } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (dst *Int8) UnmarshalJSON(b []byte) error { var n *int64 err := json.Unmarshal(b, &n) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/int.go.erb b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/int.go.erb index e0c8b7a3f..c2d40f60b 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/int.go.erb +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/int.go.erb @@ -27,7 +27,7 @@ type Int<%= pg_byte_size %> struct { Valid bool } -// ScanInt64 implements the Int64Scanner interface. +// ScanInt64 implements the [Int64Scanner] interface. func (dst *Int<%= pg_byte_size %>) ScanInt64(n Int8) error { if !n.Valid { *dst = Int<%= pg_byte_size %>{} @@ -45,11 +45,12 @@ func (dst *Int<%= pg_byte_size %>) ScanInt64(n Int8) error { return nil } +// Int64Value implements the [Int64Valuer] interface. func (n Int<%= pg_byte_size %>) Int64Value() (Int8, error) { return Int8{Int64: int64(n.Int<%= pg_bit_size %>), Valid: n.Valid}, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Int<%= pg_byte_size %>) Scan(src any) error { if src == nil { *dst = Int<%= pg_byte_size %>{} @@ -88,7 +89,7 @@ func (dst *Int<%= pg_byte_size %>) Scan(src any) error { return nil } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Int<%= pg_byte_size %>) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -96,6 +97,7 @@ func (src Int<%= pg_byte_size %>) Value() (driver.Value, error) { return int64(src.Int<%= pg_bit_size %>), nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (src Int<%= pg_byte_size %>) MarshalJSON() ([]byte, error) { if !src.Valid { return []byte("null"), nil @@ -103,6 +105,7 @@ func (src Int<%= pg_byte_size %>) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatInt(int64(src.Int<%= pg_bit_size %>), 10)), nil } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (dst *Int<%= pg_byte_size %>) UnmarshalJSON(b []byte) error { var n *int<%= pg_bit_size %> err := json.Unmarshal(b, &n) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/interval.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/interval.go index 4b5116295..b1bc78527 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/interval.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/interval.go @@ -11,7 +11,7 @@ import ( ) const ( - microsecondsPerSecond = 1000000 + microsecondsPerSecond = 1_000_000 microsecondsPerMinute = 60 * microsecondsPerSecond microsecondsPerHour = 60 * microsecondsPerMinute microsecondsPerDay = 24 * microsecondsPerHour @@ -33,16 +33,18 @@ type Interval struct { Valid bool } +// ScanInterval implements the [IntervalScanner] interface. func (interval *Interval) ScanInterval(v Interval) error { *interval = v return nil } +// IntervalValue implements the [IntervalValuer] interface. func (interval Interval) IntervalValue() (Interval, error) { return interval, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (interval *Interval) Scan(src any) error { if src == nil { *interval = Interval{} @@ -57,7 +59,7 @@ func (interval *Interval) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (interval Interval) Value() (driver.Value, error) { if !interval.Valid { return nil, nil @@ -157,7 +159,6 @@ func (encodePlanIntervalCodecText) Encode(value any, buf []byte) (newBuf []byte, } func (IntervalCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { @@ -222,6 +223,8 @@ func (scanPlanTextAnyToIntervalScanner) Scan(src []byte, dst any) error { months += int32(scalar) case "day", "days": days = int32(scalar) + default: + return fmt.Errorf("bad interval format: %q", parts[i+1]) } } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/json.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/json.go index 60aa2b71d..bf70735ee 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/json.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/json.go @@ -157,7 +157,7 @@ func (c *JSONCodec) planScan(m *Map, oid uint32, formatCode int16, target any, d case BytesScanner: return &scanPlanBinaryBytesToBytesScanner{} case sql.Scanner: - return &scanPlanSQLScanner{formatCode: formatCode} + return &scanPlanCodecSQLScanner{c: c, m: m, oid: oid, formatCode: formatCode} } rv := reflect.ValueOf(target) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/line.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/line.go index 4ae8003e8..10efc8ce7 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/line.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/line.go @@ -24,11 +24,13 @@ type Line struct { Valid bool } +// ScanLine implements the [LineScanner] interface. func (line *Line) ScanLine(v Line) error { *line = v return nil } +// LineValue implements the [LineValuer] interface. func (line Line) LineValue() (Line, error) { return line, nil } @@ -37,7 +39,7 @@ func (line *Line) Set(src any) error { return fmt.Errorf("cannot convert %v to Line", src) } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (line *Line) Scan(src any) error { if src == nil { *line = Line{} @@ -52,7 +54,7 @@ func (line *Line) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (line Line) Value() (driver.Value, error) { if !line.Valid { return nil, nil @@ -129,7 +131,6 @@ func (encodePlanLineCodecText) Encode(value any, buf []byte) (newBuf []byte, err } func (LineCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/lseg.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/lseg.go index 05a86e1c8..ed0d40d2a 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/lseg.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/lseg.go @@ -24,16 +24,18 @@ type Lseg struct { Valid bool } +// ScanLseg implements the [LsegScanner] interface. func (lseg *Lseg) ScanLseg(v Lseg) error { *lseg = v return nil } +// LsegValue implements the [LsegValuer] interface. func (lseg Lseg) LsegValue() (Lseg, error) { return lseg, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (lseg *Lseg) Scan(src any) error { if src == nil { *lseg = Lseg{} @@ -48,7 +50,7 @@ func (lseg *Lseg) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (lseg Lseg) Value() (driver.Value, error) { if !lseg.Valid { return nil, nil @@ -127,7 +129,6 @@ func (encodePlanLsegCodecText) Encode(value any, buf []byte) (newBuf []byte, err } func (LsegCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/multirange.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/multirange.go index e57637880..0c02575cd 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/multirange.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/multirange.go @@ -98,7 +98,7 @@ func (p *encodePlanMultirangeCodecText) Encode(value any, buf []byte) (newBuf [] var encodePlan EncodePlan var lastElemType reflect.Type inElemBuf := make([]byte, 0, 32) - for i := 0; i < elementCount; i++ { + for i := range elementCount { if i > 0 { buf = append(buf, ',') } @@ -151,7 +151,7 @@ func (p *encodePlanMultirangeCodecBinary) Encode(value any, buf []byte) (newBuf var encodePlan EncodePlan var lastElemType reflect.Type - for i := 0; i < elementCount; i++ { + for i := range elementCount { sp := len(buf) buf = pgio.AppendInt32(buf, -1) @@ -210,6 +210,11 @@ func (c *MultirangeCodec) decodeBinary(m *Map, multirangeOID uint32, src []byte, elementCount := int(binary.BigEndian.Uint32(src[rp:])) rp += 4 + // Each element requires at least 4 bytes for its length prefix. + if elementCount > len(src)/4 { + return fmt.Errorf("multirange element count %d exceeds available data", elementCount) + } + err := multirange.SetLen(elementCount) if err != nil { return err @@ -224,7 +229,7 @@ func (c *MultirangeCodec) decodeBinary(m *Map, multirangeOID uint32, src []byte, elementScanPlan = m.PlanScan(c.ElementType.OID, BinaryFormatCode, multirange.ScanIndex(0)) } - for i := 0; i < elementCount; i++ { + for i := range elementCount { elem := multirange.ScanIndex(i) elemLen := int(int32(binary.BigEndian.Uint32(src[rp:]))) rp += 4 @@ -374,7 +379,6 @@ parseValueLoop: } return elements, nil - } func parseRange(buf *bytes.Buffer) (string, error) { @@ -403,8 +407,8 @@ func parseRange(buf *bytes.Buffer) (string, error) { // Multirange is a generic multirange type. // -// T should implement RangeValuer and *T should implement RangeScanner. However, there does not appear to be a way to -// enforce the RangeScanner constraint. +// T should implement [RangeValuer] and *T should implement [RangeScanner]. However, there does not appear to be a way to +// enforce the [RangeScanner] constraint. type Multirange[T RangeValuer] []T func (r Multirange[T]) IsNull() bool { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/numeric.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/numeric.go index 4dbec7861..c9022abce 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/numeric.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/numeric.go @@ -14,7 +14,7 @@ import ( ) // PostgreSQL internal numeric storage uses 16-bit "digits" with base of 10,000 -const nbase = 10000 +const nbase = 10_000 const ( pgNumericNaN = 0x00000000c0000000 @@ -27,16 +27,19 @@ const ( pgNumericNegInfSign = 0xf000 ) -var big0 *big.Int = big.NewInt(0) -var big1 *big.Int = big.NewInt(1) -var big10 *big.Int = big.NewInt(10) -var big100 *big.Int = big.NewInt(100) -var big1000 *big.Int = big.NewInt(1000) +var ( + big1 *big.Int = big.NewInt(1) + big10 *big.Int = big.NewInt(10) + big100 *big.Int = big.NewInt(100) + big1000 *big.Int = big.NewInt(1000) +) -var bigNBase *big.Int = big.NewInt(nbase) -var bigNBaseX2 *big.Int = big.NewInt(nbase * nbase) -var bigNBaseX3 *big.Int = big.NewInt(nbase * nbase * nbase) -var bigNBaseX4 *big.Int = big.NewInt(nbase * nbase * nbase * nbase) +var ( + bigNBase *big.Int = big.NewInt(nbase) + bigNBaseX2 *big.Int = big.NewInt(nbase * nbase) + bigNBaseX3 *big.Int = big.NewInt(nbase * nbase * nbase) + bigNBaseX4 *big.Int = big.NewInt(nbase * nbase * nbase * nbase) +) type NumericScanner interface { ScanNumeric(v Numeric) error @@ -54,15 +57,18 @@ type Numeric struct { Valid bool } +// ScanNumeric implements the [NumericScanner] interface. func (n *Numeric) ScanNumeric(v Numeric) error { *n = v return nil } +// NumericValue implements the [NumericValuer] interface. func (n Numeric) NumericValue() (Numeric, error) { return n, nil } +// Float64Value implements the [Float64Valuer] interface. func (n Numeric) Float64Value() (Float8, error) { if !n.Valid { return Float8{}, nil @@ -92,6 +98,7 @@ func (n Numeric) Float64Value() (Float8, error) { return Float8{Float64: f, Valid: true}, nil } +// ScanInt64 implements the [Int64Scanner] interface. func (n *Numeric) ScanInt64(v Int8) error { if !v.Valid { *n = Numeric{} @@ -102,6 +109,7 @@ func (n *Numeric) ScanInt64(v Int8) error { return nil } +// Int64Value implements the [Int64Valuer] interface. func (n Numeric) Int64Value() (Int8, error) { if !n.Valid { return Int8{}, nil @@ -120,7 +128,7 @@ func (n Numeric) Int64Value() (Int8, error) { } func (n *Numeric) ScanScientific(src string) error { - if !strings.ContainsAny("eE", src) { + if !strings.ContainsAny(src, "eE") { return scanPlanTextAnyToNumericScanner{}.Scan([]byte(src), n) } @@ -157,7 +165,7 @@ func (n *Numeric) toBigInt() (*big.Int, error) { div.Exp(big10, big.NewInt(int64(-n.Exp)), nil) remainder := &big.Int{} num.DivMod(num, div, remainder) - if remainder.Cmp(big0) != 0 { + if remainder.Sign() != 0 { return nil, fmt.Errorf("cannot convert %v to integer", n) } return num, nil @@ -185,14 +193,11 @@ func parseNumericString(str string) (n *big.Int, exp int32, err error) { } func nbaseDigitsToInt64(src []byte) (accum int64, bytesRead, digitsRead int) { - digits := len(src) / 2 - if digits > 4 { - digits = 4 - } + digits := min(len(src)/2, 4) rp := 0 - for i := 0; i < digits; i++ { + for i := range digits { if i > 0 { accum *= nbase } @@ -203,7 +208,7 @@ func nbaseDigitsToInt64(src []byte) (accum int64, bytesRead, digitsRead int) { return accum, rp, digits } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (n *Numeric) Scan(src any) error { if src == nil { *n = Numeric{} @@ -218,7 +223,7 @@ func (n *Numeric) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (n Numeric) Value() (driver.Value, error) { if !n.Valid { return nil, nil @@ -231,6 +236,7 @@ func (n Numeric) Value() (driver.Value, error) { return string(buf), err } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (n Numeric) MarshalJSON() ([]byte, error) { if !n.Valid { return []byte("null"), nil @@ -243,6 +249,7 @@ func (n Numeric) MarshalJSON() ([]byte, error) { return n.numberTextBytes(), nil } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (n *Numeric) UnmarshalJSON(src []byte) error { if bytes.Equal(src, []byte(`null`)) { *n = Numeric{} @@ -257,6 +264,10 @@ func (n *Numeric) UnmarshalJSON(src []byte) error { // numberString returns a string of the number. undefined if NaN, infinite, or NULL func (n Numeric) numberTextBytes() []byte { + if n.Int == nil { + return []byte("0") + } + intStr := n.Int.String() buf := &bytes.Buffer{} @@ -269,14 +280,14 @@ func (n Numeric) numberTextBytes() []byte { exp := int(n.Exp) if exp > 0 { buf.WriteString(intStr) - for i := 0; i < exp; i++ { + for range exp { buf.WriteByte('0') } } else if exp < 0 { if len(intStr) <= -exp { buf.WriteString("0.") leadingZeros := -exp - len(intStr) - for i := 0; i < leadingZeros; i++ { + for range leadingZeros { buf.WriteByte('0') } buf.WriteString(intStr) @@ -398,7 +409,7 @@ func encodeNumericBinary(n Numeric, buf []byte) (newBuf []byte, err error) { } var sign int16 - if n.Int.Cmp(big0) < 0 { + if n.Int != nil && n.Int.Sign() < 0 { sign = 16384 } @@ -406,7 +417,9 @@ func encodeNumericBinary(n Numeric, buf []byte) (newBuf []byte, err error) { wholePart := &big.Int{} fracPart := &big.Int{} remainder := &big.Int{} - absInt.Abs(n.Int) + if n.Int != nil { + absInt.Abs(n.Int) + } // Normalize absInt and exp to where exp is always a multiple of 4. This makes // converting to 16-bit base 10,000 digits easier. @@ -436,12 +449,12 @@ func encodeNumericBinary(n Numeric, buf []byte) (newBuf []byte, err error) { var wholeDigits, fracDigits []int16 - for wholePart.Cmp(big0) != 0 { + for wholePart.Sign() != 0 { wholePart.DivMod(wholePart, bigNBase, remainder) wholeDigits = append(wholeDigits, int16(remainder.Int64())) } - if fracPart.Cmp(big0) != 0 { + if fracPart.Sign() != 0 { for fracPart.Cmp(big1) != 0 { fracPart.DivMod(fracPart, bigNBase, remainder) fracDigits = append(fracDigits, int16(remainder.Int64())) @@ -553,7 +566,6 @@ func encodeNumericText(n Numeric, buf []byte) (newBuf []byte, err error) { } func (NumericCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { @@ -648,18 +660,19 @@ func (scanPlanBinaryNumericToNumericScanner) Scan(src []byte, dst any) error { exp := (int32(weight) - int32(ndigits) + 1) * 4 if dscale > 0 { - fracNBaseDigits := int16(int32(ndigits) - int32(weight) - 1) + fracNBaseDigits := int(ndigits) - int(weight) - 1 fracDecimalDigits := fracNBaseDigits * 4 + dscaleInt := int(dscale) - if dscale > fracDecimalDigits { - multCount := int(dscale - fracDecimalDigits) - for i := 0; i < multCount; i++ { + if dscaleInt > fracDecimalDigits { + multCount := dscaleInt - fracDecimalDigits + for range multCount { accum.Mul(accum, big10) exp-- } - } else if dscale < fracDecimalDigits { - divCount := int(fracDecimalDigits - dscale) - for i := 0; i < divCount; i++ { + } else if dscaleInt < fracDecimalDigits { + divCount := fracDecimalDigits - dscaleInt + for range divCount { accum.Div(accum, big10) exp++ } @@ -671,7 +684,7 @@ func (scanPlanBinaryNumericToNumericScanner) Scan(src []byte, dst any) error { if exp >= 0 { for { reduced.DivMod(accum, big10, remainder) - if remainder.Cmp(big0) != 0 { + if remainder.Sign() != 0 { break } accum.Set(reduced) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/path.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/path.go index 73e0ec52f..685996a89 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/path.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/path.go @@ -25,16 +25,18 @@ type Path struct { Valid bool } +// ScanPath implements the [PathScanner] interface. func (path *Path) ScanPath(v Path) error { *path = v return nil } +// PathValue implements the [PathValuer] interface. func (path Path) PathValue() (Path, error) { return path, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (path *Path) Scan(src any) error { if src == nil { *path = Path{} @@ -49,7 +51,7 @@ func (path *Path) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (path Path) Value() (driver.Value, error) { if !path.Valid { return nil, nil @@ -154,7 +156,6 @@ func (encodePlanPathCodecText) Encode(value any, buf []byte) (newBuf []byte, err } func (PathCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { @@ -194,7 +195,7 @@ func (scanPlanBinaryPathToPathScanner) Scan(src []byte, dst any) error { } points := make([]Vec2, pointCount) - for i := 0; i < len(points); i++ { + for i := range points { x := binary.BigEndian.Uint64(src[rp:]) rp += 8 y := binary.BigEndian.Uint64(src[rp:]) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go index 22cf66d84..29721a482 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go @@ -96,6 +96,8 @@ const ( RecordArrayOID = 2287 UUIDOID = 2950 UUIDArrayOID = 2951 + TSVectorOID = 3614 + TSVectorArrayOID = 3643 JSONBOID = 3802 JSONBArrayOID = 3807 DaterangeOID = 3912 @@ -523,20 +525,20 @@ type SkipUnderlyingTypePlanner interface { } var elemKindToPointerTypes map[reflect.Kind]reflect.Type = map[reflect.Kind]reflect.Type{ - reflect.Int: reflect.TypeOf(new(int)), - reflect.Int8: reflect.TypeOf(new(int8)), - reflect.Int16: reflect.TypeOf(new(int16)), - reflect.Int32: reflect.TypeOf(new(int32)), - reflect.Int64: reflect.TypeOf(new(int64)), - reflect.Uint: reflect.TypeOf(new(uint)), - reflect.Uint8: reflect.TypeOf(new(uint8)), - reflect.Uint16: reflect.TypeOf(new(uint16)), - reflect.Uint32: reflect.TypeOf(new(uint32)), - reflect.Uint64: reflect.TypeOf(new(uint64)), - reflect.Float32: reflect.TypeOf(new(float32)), - reflect.Float64: reflect.TypeOf(new(float64)), - reflect.String: reflect.TypeOf(new(string)), - reflect.Bool: reflect.TypeOf(new(bool)), + reflect.Int: reflect.TypeFor[*int](), + reflect.Int8: reflect.TypeFor[*int8](), + reflect.Int16: reflect.TypeFor[*int16](), + reflect.Int32: reflect.TypeFor[*int32](), + reflect.Int64: reflect.TypeFor[*int64](), + reflect.Uint: reflect.TypeFor[*uint](), + reflect.Uint8: reflect.TypeFor[*uint8](), + reflect.Uint16: reflect.TypeFor[*uint16](), + reflect.Uint32: reflect.TypeFor[*uint32](), + reflect.Uint64: reflect.TypeFor[*uint64](), + reflect.Float32: reflect.TypeFor[*float32](), + reflect.Float64: reflect.TypeFor[*float64](), + reflect.String: reflect.TypeFor[*string](), + reflect.Bool: reflect.TypeFor[*bool](), } type underlyingTypeScanPlan struct { @@ -901,7 +903,7 @@ func (plan *pointerEmptyInterfaceScanPlan) Scan(src []byte, dst any) error { return nil } -// TryWrapStructPlan tries to wrap a struct with a wrapper that implements CompositeIndexGetter. +// TryWrapStructScanPlan tries to wrap a struct with a wrapper that implements CompositeIndexGetter. func TryWrapStructScanPlan(target any) (plan WrappedScanPlanNextSetter, nextValue any, ok bool) { targetValue := reflect.ValueOf(target) if targetValue.Kind() != reflect.Ptr { @@ -1135,10 +1137,18 @@ func (m *Map) planScan(oid uint32, formatCode int16, target any, depth int) Scan } } - if dt != nil { - if _, ok := target.(*any); ok { - return &pointerEmptyInterfaceScanPlan{codec: dt.Codec, m: m, oid: oid, formatCode: formatCode} + if _, ok := target.(*any); ok { + var codec Codec + if dt != nil { + codec = dt.Codec + } else { + if formatCode == TextFormatCode { + codec = TextCodec{} + } else { + codec = ByteaCodec{} + } } + return &pointerEmptyInterfaceScanPlan{codec: codec, m: m, oid: oid, formatCode: formatCode} } return &scanPlanFail{m: m, oid: oid, formatCode: formatCode} @@ -1364,23 +1374,23 @@ func TryWrapDerefPointerEncodePlan(value any) (plan WrappedEncodePlanNextSetter, } var kindToTypes map[reflect.Kind]reflect.Type = map[reflect.Kind]reflect.Type{ - reflect.Int: reflect.TypeOf(int(0)), - reflect.Int8: reflect.TypeOf(int8(0)), - reflect.Int16: reflect.TypeOf(int16(0)), - reflect.Int32: reflect.TypeOf(int32(0)), - reflect.Int64: reflect.TypeOf(int64(0)), - reflect.Uint: reflect.TypeOf(uint(0)), - reflect.Uint8: reflect.TypeOf(uint8(0)), - reflect.Uint16: reflect.TypeOf(uint16(0)), - reflect.Uint32: reflect.TypeOf(uint32(0)), - reflect.Uint64: reflect.TypeOf(uint64(0)), - reflect.Float32: reflect.TypeOf(float32(0)), - reflect.Float64: reflect.TypeOf(float64(0)), - reflect.String: reflect.TypeOf(""), - reflect.Bool: reflect.TypeOf(false), -} - -var byteSliceType = reflect.TypeOf([]byte{}) + reflect.Int: reflect.TypeFor[int](), + reflect.Int8: reflect.TypeFor[int8](), + reflect.Int16: reflect.TypeFor[int16](), + reflect.Int32: reflect.TypeFor[int32](), + reflect.Int64: reflect.TypeFor[int64](), + reflect.Uint: reflect.TypeFor[uint](), + reflect.Uint8: reflect.TypeFor[uint8](), + reflect.Uint16: reflect.TypeFor[uint16](), + reflect.Uint32: reflect.TypeFor[uint32](), + reflect.Uint64: reflect.TypeFor[uint64](), + reflect.Float32: reflect.TypeFor[float32](), + reflect.Float64: reflect.TypeFor[float64](), + reflect.String: reflect.TypeFor[string](), + reflect.Bool: reflect.TypeFor[bool](), +} + +var byteSliceType = reflect.TypeFor[[]byte]() type underlyingTypeEncodePlan struct { nextValueType reflect.Type @@ -1743,7 +1753,7 @@ func (plan *wrapFmtStringerEncodePlan) Encode(value any, buf []byte) (newBuf []b return plan.next.Encode(fmtStringerWrapper{value.(fmt.Stringer)}, buf) } -// TryWrapStructPlan tries to wrap a struct with a wrapper that implements CompositeIndexGetter. +// TryWrapStructEncodePlan tries to wrap a struct with a wrapper that implements CompositeIndexGetter. func TryWrapStructEncodePlan(value any) (plan WrappedEncodePlanNextSetter, nextValue any, ok bool) { if _, ok := value.(driver.Valuer); ok { return nil, nil, false @@ -1999,7 +2009,7 @@ func (w *sqlScannerWrapper) Scan(src any) error { case []byte: bufSrc = src default: - bufSrc = []byte(fmt.Sprint(bufSrc)) + bufSrc = fmt.Append(nil, bufSrc) } } @@ -2010,7 +2020,7 @@ var valuerReflectType = reflect.TypeFor[driver.Valuer]() // isNilDriverValuer returns true if value is any type of nil unless it implements driver.Valuer. *T is not considered to implement // driver.Valuer if it is only implemented by T. -func isNilDriverValuer(value any) (isNil bool, callNilDriverValuer bool) { +func isNilDriverValuer(value any) (isNil, callNilDriverValuer bool) { if value == nil { return true, false } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/pgtype_default.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/pgtype_default.go index 5648d89bf..42b39d827 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/pgtype_default.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/pgtype_default.go @@ -81,6 +81,7 @@ func initDefaultMap() { defaultMap.RegisterType(&Type{Name: "record", OID: RecordOID, Codec: RecordCodec{}}) defaultMap.RegisterType(&Type{Name: "text", OID: TextOID, Codec: TextCodec{}}) defaultMap.RegisterType(&Type{Name: "tid", OID: TIDOID, Codec: TIDCodec{}}) + defaultMap.RegisterType(&Type{Name: "tsvector", OID: TSVectorOID, Codec: TSVectorCodec{}}) defaultMap.RegisterType(&Type{Name: "time", OID: TimeOID, Codec: TimeCodec{}}) defaultMap.RegisterType(&Type{Name: "timestamp", OID: TimestampOID, Codec: &TimestampCodec{}}) defaultMap.RegisterType(&Type{Name: "timestamptz", OID: TimestamptzOID, Codec: &TimestamptzCodec{}}) @@ -164,6 +165,7 @@ func initDefaultMap() { defaultMap.RegisterType(&Type{Name: "_record", OID: RecordArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[RecordOID]}}) defaultMap.RegisterType(&Type{Name: "_text", OID: TextArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[TextOID]}}) defaultMap.RegisterType(&Type{Name: "_tid", OID: TIDArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[TIDOID]}}) + defaultMap.RegisterType(&Type{Name: "_tsvector", OID: TSVectorArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[TSVectorOID]}}) defaultMap.RegisterType(&Type{Name: "_time", OID: TimeArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[TimeOID]}}) defaultMap.RegisterType(&Type{Name: "_timestamp", OID: TimestampArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[TimestampOID]}}) defaultMap.RegisterType(&Type{Name: "_timestamptz", OID: TimestamptzArrayOID, Codec: &ArrayCodec{ElementType: defaultMap.oidToType[TimestamptzOID]}}) @@ -242,6 +244,7 @@ func initDefaultMap() { registerDefaultPgTypeVariants[Multirange[Range[Timestamp]]](defaultMap, "tsmultirange") registerDefaultPgTypeVariants[Range[Timestamptz]](defaultMap, "tstzrange") registerDefaultPgTypeVariants[Multirange[Range[Timestamptz]]](defaultMap, "tstzmultirange") + registerDefaultPgTypeVariants[TSVector](defaultMap, "tsvector") registerDefaultPgTypeVariants[UUID](defaultMap, "uuid") defaultMap.buildReflectTypeToType() diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/point.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/point.go index 09b19bb53..b701513dc 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/point.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/point.go @@ -30,11 +30,13 @@ type Point struct { Valid bool } +// ScanPoint implements the [PointScanner] interface. func (p *Point) ScanPoint(v Point) error { *p = v return nil } +// PointValue implements the [PointValuer] interface. func (p Point) PointValue() (Point, error) { return p, nil } @@ -68,7 +70,7 @@ func parsePoint(src []byte) (*Point, error) { return &Point{P: Vec2{x, y}, Valid: true}, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Point) Scan(src any) error { if src == nil { *dst = Point{} @@ -83,7 +85,7 @@ func (dst *Point) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Point) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -96,6 +98,7 @@ func (src Point) Value() (driver.Value, error) { return string(buf), err } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (src Point) MarshalJSON() ([]byte, error) { if !src.Valid { return []byte("null"), nil @@ -108,6 +111,7 @@ func (src Point) MarshalJSON() ([]byte, error) { return buff.Bytes(), nil } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (dst *Point) UnmarshalJSON(point []byte) error { p, err := parsePoint(point) if err != nil { @@ -178,7 +182,6 @@ func (encodePlanPointCodecText) Encode(value any, buf []byte) (newBuf []byte, er } func (PointCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/polygon.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/polygon.go index 04b0ba6b0..e18c9da63 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/polygon.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/polygon.go @@ -24,16 +24,18 @@ type Polygon struct { Valid bool } +// ScanPolygon implements the [PolygonScanner] interface. func (p *Polygon) ScanPolygon(v Polygon) error { *p = v return nil } +// PolygonValue implements the [PolygonValuer] interface. func (p Polygon) PolygonValue() (Polygon, error) { return p, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (p *Polygon) Scan(src any) error { if src == nil { *p = Polygon{} @@ -48,7 +50,7 @@ func (p *Polygon) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (p Polygon) Value() (driver.Value, error) { if !p.Valid { return nil, nil @@ -139,7 +141,6 @@ func (encodePlanPolygonCodecText) Encode(value any, buf []byte) (newBuf []byte, } func (PolygonCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { @@ -177,7 +178,7 @@ func (scanPlanBinaryPolygonToPolygonScanner) Scan(src []byte, dst any) error { } points := make([]Vec2, pointCount) - for i := 0; i < len(points); i++ { + for i := range points { x := binary.BigEndian.Uint64(src[rp:]) rp += 8 y := binary.BigEndian.Uint64(src[rp:]) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/range.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/range.go index 16427cccd..62d699905 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/range.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/range.go @@ -191,11 +191,13 @@ type untypedBinaryRange struct { // 18 = [ = 10010 // 24 = = 11000 -const emptyMask = 1 -const lowerInclusiveMask = 2 -const upperInclusiveMask = 4 -const lowerUnboundedMask = 8 -const upperUnboundedMask = 16 +const ( + emptyMask = 1 + lowerInclusiveMask = 2 + upperInclusiveMask = 4 + lowerUnboundedMask = 8 + upperUnboundedMask = 16 +) func parseUntypedBinaryRange(src []byte) (*untypedBinaryRange, error) { ubr := &untypedBinaryRange{} @@ -273,7 +275,6 @@ func parseUntypedBinaryRange(src []byte) (*untypedBinaryRange, error) { } return ubr, nil - } // Range is a generic range type. diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/record_codec.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/record_codec.go index b3b166045..90b9bd4bb 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/record_codec.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/record_codec.go @@ -121,5 +121,4 @@ func (RecordCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (an default: return nil, fmt.Errorf("unknown format code %d", format) } - } diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/text.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/text.go index 021ee331b..e08b12549 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/text.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/text.go @@ -19,16 +19,18 @@ type Text struct { Valid bool } +// ScanText implements the [TextScanner] interface. func (t *Text) ScanText(v Text) error { *t = v return nil } +// TextValue implements the [TextValuer] interface. func (t Text) TextValue() (Text, error) { return t, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Text) Scan(src any) error { if src == nil { *dst = Text{} @@ -47,7 +49,7 @@ func (dst *Text) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Text) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -55,6 +57,7 @@ func (src Text) Value() (driver.Value, error) { return src.String, nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (src Text) MarshalJSON() ([]byte, error) { if !src.Valid { return []byte("null"), nil @@ -63,6 +66,7 @@ func (src Text) MarshalJSON() ([]byte, error) { return json.Marshal(src.String) } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (dst *Text) UnmarshalJSON(b []byte) error { var s *string err := json.Unmarshal(b, &s) @@ -146,7 +150,6 @@ func (encodePlanTextCodecTextValuer) Encode(value any, buf []byte) (newBuf []byt } func (TextCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case TextFormatCode, BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/tid.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/tid.go index 9bc2c2a14..05c9e6d98 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/tid.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/tid.go @@ -35,16 +35,18 @@ type TID struct { Valid bool } +// ScanTID implements the [TIDScanner] interface. func (b *TID) ScanTID(v TID) error { *b = v return nil } +// TIDValue implements the [TIDValuer] interface. func (b TID) TIDValue() (TID, error) { return b, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *TID) Scan(src any) error { if src == nil { *dst = TID{} @@ -59,7 +61,7 @@ func (dst *TID) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src TID) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -131,7 +133,6 @@ func (encodePlanTIDCodecText) Encode(value any, buf []byte) (newBuf []byte, err } func (TIDCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/time.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/time.go index f8fd94891..4b8f69083 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/time.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/time.go @@ -29,16 +29,18 @@ type Time struct { Valid bool } +// ScanTime implements the [TimeScanner] interface. func (t *Time) ScanTime(v Time) error { *t = v return nil } +// TimeValue implements the [TimeValuer] interface. func (t Time) TimeValue() (Time, error) { return t, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (t *Time) Scan(src any) error { if src == nil { *t = Time{} @@ -58,7 +60,7 @@ func (t *Time) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (t Time) Value() (driver.Value, error) { if !t.Valid { return nil, nil @@ -137,7 +139,6 @@ func (encodePlanTimeCodecText) Encode(value any, buf []byte) (newBuf []byte, err } func (TimeCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/timestamp.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/timestamp.go index c31f2ac53..de500a19e 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/timestamp.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/timestamp.go @@ -11,8 +11,10 @@ import ( "github.com/jackc/pgx/v5/internal/pgio" ) -const pgTimestampFormat = "2006-01-02 15:04:05.999999999" -const jsonISO8601 = "2006-01-02T15:04:05.999999999" +const ( + pgTimestampFormat = "2006-01-02 15:04:05.999999999" + jsonISO8601 = "2006-01-02T15:04:05.999999999" +) type TimestampScanner interface { ScanTimestamp(v Timestamp) error @@ -29,16 +31,18 @@ type Timestamp struct { Valid bool } +// ScanTimestamp implements the [TimestampScanner] interface. func (ts *Timestamp) ScanTimestamp(v Timestamp) error { *ts = v return nil } +// TimestampValue implements the [TimestampValuer] interface. func (ts Timestamp) TimestampValue() (Timestamp, error) { return ts, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (ts *Timestamp) Scan(src any) error { if src == nil { *ts = Timestamp{} @@ -56,7 +60,7 @@ func (ts *Timestamp) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (ts Timestamp) Value() (driver.Value, error) { if !ts.Valid { return nil, nil @@ -68,6 +72,7 @@ func (ts Timestamp) Value() (driver.Value, error) { return ts.Time, nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (ts Timestamp) MarshalJSON() ([]byte, error) { if !ts.Valid { return []byte("null"), nil @@ -87,6 +92,7 @@ func (ts Timestamp) MarshalJSON() ([]byte, error) { return json.Marshal(s) } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (ts *Timestamp) UnmarshalJSON(b []byte) error { var s *string err := json.Unmarshal(b, &s) @@ -105,9 +111,9 @@ func (ts *Timestamp) UnmarshalJSON(b []byte) error { case "-infinity": *ts = Timestamp{Valid: true, InfinityModifier: -Infinity} default: - // Parse time with or without timezonr + // Parse time with or without timezone tss := *s - // PostgreSQL uses ISO 8601 without timezone for to_json function and casting from a string to timestampt + // PostgreSQL uses ISO 8601 without timezone for to_json function and casting from a string to timestamp tim, err := time.Parse(time.RFC3339Nano, tss) if err == nil { *ts = Timestamp{Time: tim, Valid: true} @@ -170,7 +176,7 @@ func (encodePlanTimestampCodecBinary) Encode(value any, buf []byte) (newBuf []by switch ts.InfinityModifier { case Finite: t := discardTimeZone(ts.Time) - microsecSinceUnixEpoch := t.Unix()*1000000 + int64(t.Nanosecond())/1000 + microsecSinceUnixEpoch := t.Unix()*1_000_000 + int64(t.Nanosecond())/1000 microsecSinceY2K = microsecSinceUnixEpoch - microsecFromUnixEpochToY2K case Infinity: microsecSinceY2K = infinityMicrosecondOffset @@ -273,8 +279,8 @@ func (plan *scanPlanBinaryTimestampToTimestampScanner) Scan(src []byte, dst any) ts = Timestamp{Valid: true, InfinityModifier: -Infinity} default: tim := time.Unix( - microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000, - (microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000), + microsecFromUnixEpochToY2K/1_000_000+microsecSinceY2K/1_000_000, + (microsecFromUnixEpochToY2K%1_000_000*1_000)+(microsecSinceY2K%1_000_000*1000), ).UTC() if plan.location != nil { tim = time.Date(tim.Year(), tim.Month(), tim.Day(), tim.Hour(), tim.Minute(), tim.Second(), tim.Nanosecond(), plan.location) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/timestamptz.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/timestamptz.go index 7efbcffd2..4d055bfab 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/timestamptz.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/timestamptz.go @@ -11,10 +11,12 @@ import ( "github.com/jackc/pgx/v5/internal/pgio" ) -const pgTimestamptzHourFormat = "2006-01-02 15:04:05.999999999Z07" -const pgTimestamptzMinuteFormat = "2006-01-02 15:04:05.999999999Z07:00" -const pgTimestamptzSecondFormat = "2006-01-02 15:04:05.999999999Z07:00:00" -const microsecFromUnixEpochToY2K = 946684800 * 1000000 +const ( + pgTimestamptzHourFormat = "2006-01-02 15:04:05.999999999Z07" + pgTimestamptzMinuteFormat = "2006-01-02 15:04:05.999999999Z07:00" + pgTimestamptzSecondFormat = "2006-01-02 15:04:05.999999999Z07:00:00" + microsecFromUnixEpochToY2K = 946_684_800 * 1_000_000 +) const ( negativeInfinityMicrosecondOffset = -9223372036854775808 @@ -36,16 +38,18 @@ type Timestamptz struct { Valid bool } +// ScanTimestamptz implements the [TimestamptzScanner] interface. func (tstz *Timestamptz) ScanTimestamptz(v Timestamptz) error { *tstz = v return nil } +// TimestamptzValue implements the [TimestamptzValuer] interface. func (tstz Timestamptz) TimestamptzValue() (Timestamptz, error) { return tstz, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (tstz *Timestamptz) Scan(src any) error { if src == nil { *tstz = Timestamptz{} @@ -63,7 +67,7 @@ func (tstz *Timestamptz) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (tstz Timestamptz) Value() (driver.Value, error) { if !tstz.Valid { return nil, nil @@ -75,6 +79,7 @@ func (tstz Timestamptz) Value() (driver.Value, error) { return tstz.Time, nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (tstz Timestamptz) MarshalJSON() ([]byte, error) { if !tstz.Valid { return []byte("null"), nil @@ -94,6 +99,7 @@ func (tstz Timestamptz) MarshalJSON() ([]byte, error) { return json.Marshal(s) } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (tstz *Timestamptz) UnmarshalJSON(b []byte) error { var s *string err := json.Unmarshal(b, &s) @@ -225,7 +231,6 @@ func (encodePlanTimestamptzCodecText) Encode(value any, buf []byte) (newBuf []by } func (c *TimestamptzCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { @@ -265,8 +270,8 @@ func (plan *scanPlanBinaryTimestamptzToTimestamptzScanner) Scan(src []byte, dst tstz = Timestamptz{Valid: true, InfinityModifier: -Infinity} default: tim := time.Unix( - microsecFromUnixEpochToY2K/1000000+microsecSinceY2K/1000000, - (microsecFromUnixEpochToY2K%1000000*1000)+(microsecSinceY2K%1000000*1000), + microsecFromUnixEpochToY2K/1_000_000+microsecSinceY2K/1_000_000, + (microsecFromUnixEpochToY2K%1_000_000*1_000)+(microsecSinceY2K%1_000_000*1_000), ) if plan.location != nil { tim = tim.In(plan.location) diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/tsvector.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/tsvector.go new file mode 100644 index 000000000..b357948a0 --- /dev/null +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/tsvector.go @@ -0,0 +1,507 @@ +package pgtype + +import ( + "bytes" + "database/sql/driver" + "encoding/binary" + "fmt" + "strconv" + "strings" + + "github.com/jackc/pgx/v5/internal/pgio" +) + +type TSVectorScanner interface { + ScanTSVector(TSVector) error +} + +type TSVectorValuer interface { + TSVectorValue() (TSVector, error) +} + +// TSVector represents a PostgreSQL tsvector value. +type TSVector struct { + Lexemes []TSVectorLexeme + Valid bool +} + +// TSVectorLexeme represents a lexeme within a tsvector, consisting of a word and its positions. +type TSVectorLexeme struct { + Word string + Positions []TSVectorPosition +} + +// ScanTSVector implements the [TSVectorScanner] interface. +func (t *TSVector) ScanTSVector(v TSVector) error { + *t = v + return nil +} + +// TSVectorValue implements the [TSVectorValuer] interface. +func (t TSVector) TSVectorValue() (TSVector, error) { + return t, nil +} + +func (t TSVector) String() string { + buf, _ := encodePlanTSVectorCodecText{}.Encode(t, nil) + return string(buf) +} + +// Scan implements the [database/sql.Scanner] interface. +func (t *TSVector) Scan(src any) error { + if src == nil { + *t = TSVector{} + return nil + } + + switch src := src.(type) { + case string: + return scanPlanTextAnyToTSVectorScanner{}.scanString(src, t) + } + + return fmt.Errorf("cannot scan %T", src) +} + +// Value implements the [database/sql/driver.Valuer] interface. +func (t TSVector) Value() (driver.Value, error) { + if !t.Valid { + return nil, nil + } + + buf, err := TSVectorCodec{}.PlanEncode(nil, 0, TextFormatCode, t).Encode(t, nil) + if err != nil { + return nil, err + } + + return string(buf), nil +} + +// TSVectorWeight represents the weight label of a lexeme position in a tsvector. +type TSVectorWeight byte + +const ( + TSVectorWeightA = TSVectorWeight('A') + TSVectorWeightB = TSVectorWeight('B') + TSVectorWeightC = TSVectorWeight('C') + TSVectorWeightD = TSVectorWeight('D') +) + +// tsvectorWeightToBinary converts a TSVectorWeight to the 2-bit binary encoding used by PostgreSQL. +func tsvectorWeightToBinary(w TSVectorWeight) uint16 { + switch w { + case TSVectorWeightA: + return 3 + case TSVectorWeightB: + return 2 + case TSVectorWeightC: + return 1 + default: + return 0 // D or unset + } +} + +// tsvectorWeightFromBinary converts a 2-bit binary weight value to a TSVectorWeight. +func tsvectorWeightFromBinary(b uint16) TSVectorWeight { + switch b { + case 3: + return TSVectorWeightA + case 2: + return TSVectorWeightB + case 1: + return TSVectorWeightC + default: + return TSVectorWeightD + } +} + +// TSVectorPosition represents a lexeme position and its optional weight within a tsvector. +type TSVectorPosition struct { + Position uint16 + Weight TSVectorWeight +} + +func (p TSVectorPosition) String() string { + s := strconv.FormatUint(uint64(p.Position), 10) + if p.Weight != 0 && p.Weight != TSVectorWeightD { + s += string(p.Weight) + } + return s +} + +type TSVectorCodec struct{} + +func (TSVectorCodec) FormatSupported(format int16) bool { + return format == TextFormatCode || format == BinaryFormatCode +} + +func (TSVectorCodec) PreferredFormat() int16 { + return BinaryFormatCode +} + +func (TSVectorCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan { + if _, ok := value.(TSVectorValuer); !ok { + return nil + } + + switch format { + case BinaryFormatCode: + return encodePlanTSVectorCodecBinary{} + case TextFormatCode: + return encodePlanTSVectorCodecText{} + } + + return nil +} + +type encodePlanTSVectorCodecBinary struct{} + +func (encodePlanTSVectorCodecBinary) Encode(value any, buf []byte) ([]byte, error) { + tsv, err := value.(TSVectorValuer).TSVectorValue() + if err != nil { + return nil, err + } + + if !tsv.Valid { + return nil, nil + } + + buf = pgio.AppendInt32(buf, int32(len(tsv.Lexemes))) + + for _, entry := range tsv.Lexemes { + buf = append(buf, entry.Word...) + buf = append(buf, 0x00) + buf = pgio.AppendUint16(buf, uint16(len(entry.Positions))) + + // Each position is a uint16: weight (2 bits) | position (14 bits) + for _, pos := range entry.Positions { + packed := tsvectorWeightToBinary(pos.Weight)<<14 | uint16(pos.Position)&0x3FFF + buf = pgio.AppendUint16(buf, packed) + } + } + + return buf, nil +} + +type scanPlanBinaryTSVectorToTSVectorScanner struct{} + +func (scanPlanBinaryTSVectorToTSVectorScanner) Scan(src []byte, dst any) error { + scanner := (dst).(TSVectorScanner) + + if src == nil { + return scanner.ScanTSVector(TSVector{}) + } + + rp := 0 + + const ( + uint16Len = 2 + uint32Len = 4 + ) + + if len(src[rp:]) < uint32Len { + return fmt.Errorf("tsvector incomplete %v", src) + } + entryCount := int(int32(binary.BigEndian.Uint32(src[rp:]))) + rp += uint32Len + + var tsv TSVector + if entryCount > 0 { + tsv.Lexemes = make([]TSVectorLexeme, entryCount) + } + + for i := range entryCount { + nullIndex := bytes.IndexByte(src[rp:], 0x00) + if nullIndex == -1 { + return fmt.Errorf("invalid tsvector binary format: missing null terminator") + } + + lexeme := TSVectorLexeme{Word: string(src[rp : rp+nullIndex])} + rp += nullIndex + 1 // skip past null terminator + + // Read position count. + if len(src[rp:]) < uint16Len { + return fmt.Errorf("invalid tsvector binary format: incomplete position count") + } + + numPositions := int(binary.BigEndian.Uint16(src[rp:])) + rp += uint16Len + + // Read each packed position: weight (2 bits) | position (14 bits) + if len(src[rp:]) < numPositions*uint16Len { + return fmt.Errorf("invalid tsvector binary format: incomplete positions") + } + + if numPositions > 0 { + lexeme.Positions = make([]TSVectorPosition, numPositions) + for pos := range numPositions { + packed := binary.BigEndian.Uint16(src[rp:]) + rp += uint16Len + lexeme.Positions[pos] = TSVectorPosition{ + Position: packed & 0x3FFF, + Weight: tsvectorWeightFromBinary(packed >> 14), + } + } + } + + tsv.Lexemes[i] = lexeme + } + tsv.Valid = true + + return scanner.ScanTSVector(tsv) +} + +var tsvectorLexemeReplacer = strings.NewReplacer( + `\`, `\\`, + `'`, `\'`, +) + +type encodePlanTSVectorCodecText struct{} + +func (encodePlanTSVectorCodecText) Encode(value any, buf []byte) ([]byte, error) { + tsv, err := value.(TSVectorValuer).TSVectorValue() + if err != nil { + return nil, err + } + + if !tsv.Valid { + return nil, nil + } + + if buf == nil { + buf = []byte{} + } + + for i, lex := range tsv.Lexemes { + if i > 0 { + buf = append(buf, ' ') + } + + buf = append(buf, '\'') + buf = append(buf, tsvectorLexemeReplacer.Replace(lex.Word)...) + buf = append(buf, '\'') + + sep := byte(':') + for _, p := range lex.Positions { + buf = append(buf, sep) + buf = append(buf, p.String()...) + sep = ',' + } + } + + return buf, nil +} + +func (TSVectorCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { + switch format { + case BinaryFormatCode: + switch target.(type) { + case TSVectorScanner: + return scanPlanBinaryTSVectorToTSVectorScanner{} + } + case TextFormatCode: + switch target.(type) { + case TSVectorScanner: + return scanPlanTextAnyToTSVectorScanner{} + } + } + + return nil +} + +type scanPlanTextAnyToTSVectorScanner struct{} + +func (s scanPlanTextAnyToTSVectorScanner) Scan(src []byte, dst any) error { + scanner := (dst).(TSVectorScanner) + + if src == nil { + return scanner.ScanTSVector(TSVector{}) + } + + return s.scanString(string(src), scanner) +} + +func (scanPlanTextAnyToTSVectorScanner) scanString(src string, scanner TSVectorScanner) error { + tsv, err := parseTSVector(src) + if err != nil { + return err + } + return scanner.ScanTSVector(tsv) +} + +func (c TSVectorCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) { + return codecDecodeToTextFormat(c, m, oid, format, src) +} + +func (c TSVectorCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) { + if src == nil { + return nil, nil + } + + var tsv TSVector + err := codecScan(c, m, oid, format, src, &tsv) + if err != nil { + return nil, err + } + return tsv, nil +} + +type tsvectorParser struct { + str string + pos int +} + +func (p *tsvectorParser) atEnd() bool { + return p.pos >= len(p.str) +} + +func (p *tsvectorParser) peek() byte { + return p.str[p.pos] +} + +func (p *tsvectorParser) consume() (byte, bool) { + if p.pos >= len(p.str) { + return 0, true + } + b := p.str[p.pos] + p.pos++ + return b, false +} + +func (p *tsvectorParser) consumeSpaces() { + for !p.atEnd() && p.peek() == ' ' { + p.consume() + } +} + +// consumeLexeme consumes a single-quoted lexeme, handling single quotes and backslash escapes. +func (p *tsvectorParser) consumeLexeme() (string, error) { + ch, end := p.consume() + if end || ch != '\'' { + return "", fmt.Errorf("invalid tsvector format: lexeme must start with a single quote") + } + + var buf strings.Builder + for { + ch, end := p.consume() + if end { + return "", fmt.Errorf("invalid tsvector format: unterminated quoted lexeme") + } + + switch ch { + case '\'': + // Escaped quote ('') — write a literal single quote + if !p.atEnd() && p.peek() == '\'' { + p.consume() + buf.WriteByte('\'') + } else { + // Closing quote — lexeme is complete + return buf.String(), nil + } + case '\\': + next, end := p.consume() + if end { + return "", fmt.Errorf("invalid tsvector format: unexpected end after backslash") + } + buf.WriteByte(next) + default: + buf.WriteByte(ch) + } + } +} + +// consumePositions consumes a comma-separated list of position[weight] values. +func (p *tsvectorParser) consumePositions() ([]TSVectorPosition, error) { + var positions []TSVectorPosition + + for { + pos, err := p.consumePosition() + if err != nil { + return nil, err + } + positions = append(positions, pos) + + if p.atEnd() || p.peek() != ',' { + break + } + + p.consume() // skip ',' + } + + return positions, nil +} + +// consumePosition consumes a single position number with optional weight letter. +func (p *tsvectorParser) consumePosition() (TSVectorPosition, error) { + start := p.pos + + for !p.atEnd() && p.peek() >= '0' && p.peek() <= '9' { + p.consume() + } + + if p.pos == start { + return TSVectorPosition{}, fmt.Errorf("invalid tsvector format: expected position number") + } + + num, err := strconv.ParseUint(p.str[start:p.pos], 10, 16) + if err != nil { + return TSVectorPosition{}, fmt.Errorf("invalid tsvector format: invalid position number %q", p.str[start:p.pos]) + } + + pos := TSVectorPosition{Position: uint16(num), Weight: TSVectorWeightD} + + // Check for optional weight letter + if !p.atEnd() { + switch p.peek() { + case 'A', 'a': + pos.Weight = TSVectorWeightA + case 'B', 'b': + pos.Weight = TSVectorWeightB + case 'C', 'c': + pos.Weight = TSVectorWeightC + case 'D', 'd': + pos.Weight = TSVectorWeightD + default: + return pos, nil + } + p.consume() + } + + return pos, nil +} + +// parseTSVector parses a PostgreSQL tsvector text representation. +func parseTSVector(s string) (TSVector, error) { + result := TSVector{} + p := &tsvectorParser{str: strings.TrimSpace(s), pos: 0} + + for !p.atEnd() { + p.consumeSpaces() + if p.atEnd() { + break + } + + word, err := p.consumeLexeme() + if err != nil { + return TSVector{}, err + } + + entry := TSVectorLexeme{Word: word} + + // Check for optional positions after ':' + if !p.atEnd() && p.peek() == ':' { + p.consume() // skip ':' + + positions, err := p.consumePositions() + if err != nil { + return TSVector{}, err + } + entry.Positions = positions + } + + result.Lexemes = append(result.Lexemes, entry) + } + + result.Valid = true + + return result, nil +} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uint32.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uint32.go index f2b2fa6d4..e6d4b1cf6 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uint32.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uint32.go @@ -3,6 +3,7 @@ package pgtype import ( "database/sql/driver" "encoding/binary" + "encoding/json" "fmt" "math" "strconv" @@ -24,16 +25,18 @@ type Uint32 struct { Valid bool } +// ScanUint32 implements the [Uint32Scanner] interface. func (n *Uint32) ScanUint32(v Uint32) error { *n = v return nil } +// Uint32Value implements the [Uint32Valuer] interface. func (n Uint32) Uint32Value() (Uint32, error) { return n, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Uint32) Scan(src any) error { if src == nil { *dst = Uint32{} @@ -67,7 +70,7 @@ func (dst *Uint32) Scan(src any) error { return nil } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Uint32) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -75,6 +78,31 @@ func (src Uint32) Value() (driver.Value, error) { return int64(src.Uint32), nil } +// MarshalJSON implements the [encoding/json.Marshaler] interface. +func (src Uint32) MarshalJSON() ([]byte, error) { + if !src.Valid { + return []byte("null"), nil + } + return json.Marshal(src.Uint32) +} + +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. +func (dst *Uint32) UnmarshalJSON(b []byte) error { + var n *uint32 + err := json.Unmarshal(b, &n) + if err != nil { + return err + } + + if n == nil { + *dst = Uint32{} + } else { + *dst = Uint32{Uint32: *n, Valid: true} + } + + return nil +} + type Uint32Codec struct{} func (Uint32Codec) FormatSupported(format int16) bool { @@ -197,7 +225,6 @@ func (encodePlanUint32CodecTextInt64Valuer) Encode(value any, buf []byte) (newBu } func (Uint32Codec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uint64.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uint64.go index dd2130ebc..68fd16613 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uint64.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uint64.go @@ -24,16 +24,18 @@ type Uint64 struct { Valid bool } +// ScanUint64 implements the [Uint64Scanner] interface. func (n *Uint64) ScanUint64(v Uint64) error { *n = v return nil } +// Uint64Value implements the [Uint64Valuer] interface. func (n Uint64) Uint64Value() (Uint64, error) { return n, nil } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *Uint64) Scan(src any) error { if src == nil { *dst = Uint64{} @@ -63,7 +65,7 @@ func (dst *Uint64) Scan(src any) error { return nil } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src Uint64) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -194,7 +196,6 @@ func (encodePlanUint64CodecTextInt64Valuer) Encode(value any, buf []byte) (newBu } func (Uint64Codec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { - switch format { case BinaryFormatCode: switch target.(type) { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uuid.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uuid.go index 0628f193f..83d0c4127 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uuid.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgtype/uuid.go @@ -20,11 +20,13 @@ type UUID struct { Valid bool } +// ScanUUID implements the [UUIDScanner] interface. func (b *UUID) ScanUUID(v UUID) error { *b = v return nil } +// UUIDValue implements the [UUIDValuer] interface. func (b UUID) UUIDValue() (UUID, error) { return b, nil } @@ -67,7 +69,7 @@ func encodeUUID(src [16]byte) string { return string(buf[:]) } -// Scan implements the database/sql Scanner interface. +// Scan implements the [database/sql.Scanner] interface. func (dst *UUID) Scan(src any) error { if src == nil { *dst = UUID{} @@ -87,7 +89,7 @@ func (dst *UUID) Scan(src any) error { return fmt.Errorf("cannot scan %T", src) } -// Value implements the database/sql/driver Valuer interface. +// Value implements the [database/sql/driver.Valuer] interface. func (src UUID) Value() (driver.Value, error) { if !src.Valid { return nil, nil @@ -104,6 +106,7 @@ func (src UUID) String() string { return encodeUUID(src.Bytes) } +// MarshalJSON implements the [encoding/json.Marshaler] interface. func (src UUID) MarshalJSON() ([]byte, error) { if !src.Valid { return []byte("null"), nil @@ -116,6 +119,7 @@ func (src UUID) MarshalJSON() ([]byte, error) { return buff.Bytes(), nil } +// UnmarshalJSON implements the [encoding/json.Unmarshaler] interface. func (dst *UUID) UnmarshalJSON(src []byte) error { if bytes.Equal(src, []byte("null")) { *dst = UUID{} diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/pgxpool/pool.go b/kubewatch/vendor/github.com/jackc/pgx/v5/pgxpool/pool.go index e22ed289a..8291ed820 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/pgxpool/pool.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/pgxpool/pool.go @@ -2,8 +2,8 @@ package pgxpool import ( "context" - "fmt" - "math/rand" + "errors" + "math/rand/v2" "runtime" "strconv" "sync" @@ -15,12 +15,14 @@ import ( "github.com/jackc/puddle/v2" ) -var defaultMaxConns = int32(4) -var defaultMinConns = int32(0) -var defaultMinIdleConns = int32(0) -var defaultMaxConnLifetime = time.Hour -var defaultMaxConnIdleTime = time.Minute * 30 -var defaultHealthCheckPeriod = time.Minute +var ( + defaultMaxConns = int32(4) + defaultMinConns = int32(0) + defaultMinIdleConns = int32(0) + defaultMaxConnLifetime = time.Hour + defaultMaxConnIdleTime = time.Minute * 30 + defaultHealthCheckPeriod = time.Minute +) type connResource struct { conn *pgx.Conn @@ -84,9 +86,10 @@ type Pool struct { config *Config beforeConnect func(context.Context, *pgx.ConnConfig) error afterConnect func(context.Context, *pgx.Conn) error - beforeAcquire func(context.Context, *pgx.Conn) bool + prepareConn func(context.Context, *pgx.Conn) (bool, error) afterRelease func(*pgx.Conn) bool beforeClose func(*pgx.Conn) + shouldPing func(context.Context, ShouldPingParams) bool minConns int32 minIdleConns int32 maxConns int32 @@ -94,6 +97,10 @@ type Pool struct { maxConnLifetimeJitter time.Duration maxConnIdleTime time.Duration healthCheckPeriod time.Duration + pingTimeout time.Duration + + healthCheckMu sync.Mutex + healthCheckTimer *time.Timer healthCheckChan chan struct{} @@ -104,6 +111,12 @@ type Pool struct { closeChan chan struct{} } +// ShouldPingParams are the parameters passed to ShouldPing. +type ShouldPingParams struct { + Conn *pgx.Conn + IdleDuration time.Duration +} + // Config is the configuration struct for creating a pool. It must be created by [ParseConfig] and then it can be // modified. type Config struct { @@ -119,8 +132,23 @@ type Config struct { // BeforeAcquire is called before a connection is acquired from the pool. It must return true to allow the // acquisition or false to indicate that the connection should be destroyed and a different connection should be // acquired. + // + // Deprecated: Use PrepareConn instead. If both PrepareConn and BeforeAcquire are set, PrepareConn will take + // precedence, ignoring BeforeAcquire. BeforeAcquire func(context.Context, *pgx.Conn) bool + // PrepareConn is called before a connection is acquired from the pool. If this function returns true, the connection + // is considered valid, otherwise the connection is destroyed. If the function returns a non-nil error, the instigating + // query will fail with the returned error. + // + // Specifically, this means that: + // + // - If it returns true and a nil error, the query proceeds as normal. + // - If it returns true and an error, the connection will be returned to the pool, and the instigating query will fail with the returned error. + // - If it returns false, and an error, the connection will be destroyed, and the query will fail with the returned error. + // - If it returns false and a nil error, the connection will be destroyed, and the instigating query will be retried on a new connection. + PrepareConn func(context.Context, *pgx.Conn) (bool, error) + // AfterRelease is called after a connection is released, but before it is returned to the pool. It must return true to // return the connection to the pool or false to destroy the connection. AfterRelease func(*pgx.Conn) bool @@ -128,6 +156,10 @@ type Config struct { // BeforeClose is called right before a connection is closed and removed from the pool. BeforeClose func(*pgx.Conn) + // ShouldPing is called after a connection is acquired from the pool. If it returns true, the connection is pinged to check for liveness. + // If this func is not set, the default behavior is to ping connections that have been idle for at least 1 second. + ShouldPing func(context.Context, ShouldPingParams) bool + // MaxConnLifetime is the duration since creation after which a connection will be automatically closed. MaxConnLifetime time.Duration @@ -138,6 +170,10 @@ type Config struct { // MaxConnIdleTime is the duration after which an idle connection will be automatically closed by the health check. MaxConnIdleTime time.Duration + // PingTimeout is the maximum amount of time to wait for a connection to pong before considering it as unhealthy and + // destroying it. If zero, the default is no timeout. + PingTimeout time.Duration + // MaxConns is the maximum size of the pool. The default is the greater of 4 or runtime.NumCPU(). MaxConns int32 @@ -190,11 +226,18 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) { panic("config must be created by ParseConfig") } + prepareConn := config.PrepareConn + if prepareConn == nil && config.BeforeAcquire != nil { + prepareConn = func(ctx context.Context, conn *pgx.Conn) (bool, error) { + return config.BeforeAcquire(ctx, conn), nil + } + } + p := &Pool{ config: config, beforeConnect: config.BeforeConnect, afterConnect: config.AfterConnect, - beforeAcquire: config.BeforeAcquire, + prepareConn: prepareConn, afterRelease: config.AfterRelease, beforeClose: config.BeforeClose, minConns: config.MinConns, @@ -203,6 +246,7 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) { maxConnLifetime: config.MaxConnLifetime, maxConnLifetimeJitter: config.MaxConnLifetimeJitter, maxConnIdleTime: config.MaxConnIdleTime, + pingTimeout: config.PingTimeout, healthCheckPeriod: config.HealthCheckPeriod, healthCheckChan: make(chan struct{}, 1), closeChan: make(chan struct{}), @@ -216,6 +260,14 @@ func NewWithConfig(ctx context.Context, config *Config) (*Pool, error) { p.releaseTracer = t } + if config.ShouldPing != nil { + p.shouldPing = config.ShouldPing + } else { + p.shouldPing = func(ctx context.Context, params ShouldPingParams) bool { + return params.IdleDuration > time.Second + } + } + var err error p.p, err = puddle.NewPool( &puddle.Config[*connResource]{ @@ -321,10 +373,10 @@ func ParseConfig(connString string) (*Config, error) { delete(connConfig.Config.RuntimeParams, "pool_max_conns") n, err := strconv.ParseInt(s, 10, 32) if err != nil { - return nil, fmt.Errorf("cannot parse pool_max_conns: %w", err) + return nil, pgconn.NewParseConfigError(connString, "cannot parse pool_max_conns", err) } if n < 1 { - return nil, fmt.Errorf("pool_max_conns too small: %d", n) + return nil, pgconn.NewParseConfigError(connString, "pool_max_conns too small", err) } config.MaxConns = int32(n) } else { @@ -338,7 +390,7 @@ func ParseConfig(connString string) (*Config, error) { delete(connConfig.Config.RuntimeParams, "pool_min_conns") n, err := strconv.ParseInt(s, 10, 32) if err != nil { - return nil, fmt.Errorf("cannot parse pool_min_conns: %w", err) + return nil, pgconn.NewParseConfigError(connString, "cannot parse pool_min_conns", err) } config.MinConns = int32(n) } else { @@ -349,7 +401,7 @@ func ParseConfig(connString string) (*Config, error) { delete(connConfig.Config.RuntimeParams, "pool_min_idle_conns") n, err := strconv.ParseInt(s, 10, 32) if err != nil { - return nil, fmt.Errorf("cannot parse pool_min_idle_conns: %w", err) + return nil, pgconn.NewParseConfigError(connString, "cannot parse pool_min_idle_conns", err) } config.MinIdleConns = int32(n) } else { @@ -360,7 +412,7 @@ func ParseConfig(connString string) (*Config, error) { delete(connConfig.Config.RuntimeParams, "pool_max_conn_lifetime") d, err := time.ParseDuration(s) if err != nil { - return nil, fmt.Errorf("invalid pool_max_conn_lifetime: %w", err) + return nil, pgconn.NewParseConfigError(connString, "cannot parse pool_max_conn_lifetime", err) } config.MaxConnLifetime = d } else { @@ -371,7 +423,7 @@ func ParseConfig(connString string) (*Config, error) { delete(connConfig.Config.RuntimeParams, "pool_max_conn_idle_time") d, err := time.ParseDuration(s) if err != nil { - return nil, fmt.Errorf("invalid pool_max_conn_idle_time: %w", err) + return nil, pgconn.NewParseConfigError(connString, "cannot parse pool_max_conn_idle_time", err) } config.MaxConnIdleTime = d } else { @@ -382,7 +434,7 @@ func ParseConfig(connString string) (*Config, error) { delete(connConfig.Config.RuntimeParams, "pool_health_check_period") d, err := time.ParseDuration(s) if err != nil { - return nil, fmt.Errorf("invalid pool_health_check_period: %w", err) + return nil, pgconn.NewParseConfigError(connString, "cannot parse pool_health_check_period", err) } config.HealthCheckPeriod = d } else { @@ -393,7 +445,7 @@ func ParseConfig(connString string) (*Config, error) { delete(connConfig.Config.RuntimeParams, "pool_max_conn_lifetime_jitter") d, err := time.ParseDuration(s) if err != nil { - return nil, fmt.Errorf("invalid pool_max_conn_lifetime_jitter: %w", err) + return nil, pgconn.NewParseConfigError(connString, "cannot parse pool_max_conn_lifetime_jitter", err) } config.MaxConnLifetimeJitter = d } @@ -415,15 +467,25 @@ func (p *Pool) isExpired(res *puddle.Resource[*connResource]) bool { } func (p *Pool) triggerHealthCheck() { - go func() { + const healthCheckDelay = 500 * time.Millisecond + + p.healthCheckMu.Lock() + defer p.healthCheckMu.Unlock() + + if p.healthCheckTimer == nil { // Destroy is asynchronous so we give it time to actually remove itself from // the pool otherwise we might try to check the pool size too soon - time.Sleep(500 * time.Millisecond) - select { - case p.healthCheckChan <- struct{}{}: - default: - } - }() + p.healthCheckTimer = time.AfterFunc(healthCheckDelay, func() { + select { + case <-p.closeChan: + case p.healthCheckChan <- struct{}{}: + default: + } + }) + return + } + + p.healthCheckTimer.Reset(healthCheckDelay) } func (p *Pool) backgroundHealthCheck() { @@ -496,7 +558,8 @@ func (p *Pool) checkMinConns() error { // off this check // Create the number of connections needed to get to both minConns and minIdleConns - toCreate := max(p.minConns-p.Stat().TotalConns(), p.minIdleConns-p.Stat().IdleConns()) + stat := p.Stat() + toCreate := max(p.minConns-stat.TotalConns(), p.minIdleConns-stat.IdleConns()) if toCreate > 0 { return p.createIdleResources(context.Background(), int(toCreate)) } @@ -509,7 +572,7 @@ func (p *Pool) createIdleResources(parentCtx context.Context, targetResources in errs := make(chan error, targetResources) - for i := 0; i < targetResources; i++ { + for range targetResources { go func() { err := p.p.CreateResource(ctx) // Ignore ErrNotAvailable since it means that the pool has become full since we started creating resource. @@ -521,7 +584,7 @@ func (p *Pool) createIdleResources(parentCtx context.Context, targetResources in } var firstError error - for i := 0; i < targetResources; i++ { + for range targetResources { err := <-errs if err != nil && firstError == nil { cancel() @@ -545,7 +608,10 @@ func (p *Pool) Acquire(ctx context.Context) (c *Conn, err error) { }() } - for { + // Try to acquire from the connection pool up to maxConns + 1 times, so that + // any that fatal errors would empty the pool and still at least try 1 fresh + // connection. + for range int(p.maxConns) + 1 { res, err := p.p.Acquire(ctx) if err != nil { return nil, err @@ -553,20 +619,42 @@ func (p *Pool) Acquire(ctx context.Context) (c *Conn, err error) { cr := res.Value() - if res.IdleDuration() > time.Second { - err := cr.conn.Ping(ctx) + shouldPingParams := ShouldPingParams{Conn: cr.conn, IdleDuration: res.IdleDuration()} + if p.shouldPing(ctx, shouldPingParams) { + err := func() error { + pingCtx := ctx + if p.pingTimeout > 0 { + var cancel context.CancelFunc + pingCtx, cancel = context.WithTimeout(ctx, p.pingTimeout) + defer cancel() + } + return cr.conn.Ping(pingCtx) + }() if err != nil { res.Destroy() continue } } - if p.beforeAcquire == nil || p.beforeAcquire(ctx, cr.conn) { - return cr.getConn(p, res), nil + if p.prepareConn != nil { + ok, err := p.prepareConn(ctx, cr.conn) + if !ok { + res.Destroy() + } + if err != nil { + if ok { + res.Release() + } + return nil, err + } + if !ok { + continue + } } - res.Destroy() + return cr.getConn(p, res), nil } + return nil, errors.New("pgxpool: too many failed attempts acquiring connection; likely bug in PrepareConn, BeforeAcquire, or ShouldPing hook") } // AcquireFunc acquires a *Conn and calls f with that *Conn. ctx will only affect the Acquire. It has no effect on the @@ -589,11 +677,14 @@ func (p *Pool) AcquireAllIdle(ctx context.Context) []*Conn { conns := make([]*Conn, 0, len(resources)) for _, res := range resources { cr := res.Value() - if p.beforeAcquire == nil || p.beforeAcquire(ctx, cr.conn) { - conns = append(conns, cr.getConn(p, res)) - } else { - res.Destroy() + if p.prepareConn != nil { + ok, err := p.prepareConn(ctx, cr.conn) + if !ok || err != nil { + res.Destroy() + continue + } } + conns = append(conns, cr.getConn(p, res)) } return conns diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/rows.go b/kubewatch/vendor/github.com/jackc/pgx/v5/rows.go index 3e64a3adb..d74518de4 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/rows.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/rows.go @@ -29,9 +29,9 @@ type Rows interface { // to call Close after rows is already closed. Close() - // Err returns any error that occurred while reading. Err must only be called after the Rows is closed (either by - // calling Close or by Next returning false). If it is called early it may return nil even if there was an error - // executing the query. + // Err returns any error that occurred while executing a query or reading its results. Err must be called after the + // Rows is closed (either by calling Close or by Next returning false) to check if the query was successful. If it is + // called before the Rows is closed it may return nil even if the query failed on the server. Err() error // CommandTag returns the command tag from this query. It is only available after Rows is closed. @@ -41,22 +41,19 @@ type Rows interface { // when there was an error executing the query. FieldDescriptions() []pgconn.FieldDescription - // Next prepares the next row for reading. It returns true if there is another - // row and false if no more rows are available or a fatal error has occurred. - // It automatically closes rows when all rows are read. + // Next prepares the next row for reading. It returns true if there is another row and false if no more rows are + // available or a fatal error has occurred. It automatically closes rows upon returning false (whether due to all rows + // having been read or due to an error). // - // Callers should check rows.Err() after rows.Next() returns false to detect - // whether result-set reading ended prematurely due to an error. See - // Conn.Query for details. + // Callers should check rows.Err() after rows.Next() returns false to detect whether result-set reading ended + // prematurely due to an error. See Conn.Query for details. // - // For simpler error handling, consider using the higher-level pgx v5 - // CollectRows() and ForEachRow() helpers instead. + // For simpler error handling, consider using the higher-level pgx v5 CollectRows() and ForEachRow() helpers instead. Next() bool - // Scan reads the values from the current row into dest values positionally. - // dest can include pointers to core types, values implementing the Scanner - // interface, and nil. nil will skip the value entirely. It is an error to - // call Scan without first calling Next() and checking that it returned true. + // Scan reads the values from the current row into dest values positionally. dest can include pointers to core types, + // values implementing the Scanner interface, and nil. nil will skip the value entirely. It is an error to call Scan + // without first calling Next() and checking that it returned true. Rows is automatically closed upon error. Scan(dest ...any) error // Values returns the decoded row values. As with Scan(), it is an error to @@ -532,7 +529,7 @@ func RowTo[T any](row CollectableRow) (T, error) { return value, err } -// RowTo returns a the address of a T scanned from row. +// RowToAddrOf returns the address of a T scanned from row. func RowToAddrOf[T any](row CollectableRow) (*T, error) { var value T err := row.Scan(&value) @@ -563,7 +560,7 @@ func (rs *mapRowScanner) ScanRow(rows Rows) error { return nil } -// RowToStructByPos returns a T scanned from row. T must be a struct. T must have the same number a public fields as row +// RowToStructByPos returns a T scanned from row. T must be a struct. T must have the same number of public fields as row // has fields. The row and T fields will be matched by position. If the "db" struct tag is "-" then the field will be // ignored. func RowToStructByPos[T any](row CollectableRow) (T, error) { @@ -851,7 +848,7 @@ func fieldPosByName(fldDescs []pgconn.FieldDescription, field string, normalize } } } - return + return i } // structRowField describes a field of a struct. diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/stdlib/sql.go b/kubewatch/vendor/github.com/jackc/pgx/v5/stdlib/sql.go index 4924fe41a..a37d58c41 100644 --- a/kubewatch/vendor/github.com/jackc/pgx/v5/stdlib/sql.go +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/stdlib/sql.go @@ -73,7 +73,7 @@ import ( "fmt" "io" "math" - "math/rand" + "math/rand/v2" "reflect" "slices" "strconv" @@ -124,6 +124,22 @@ func init() { // OptionOpenDB options for configuring the driver when opening a new db pool. type OptionOpenDB func(*connector) +// ShouldPingParams are passed to OptionShouldPing to decide whether to ping before reusing a connection. +type ShouldPingParams struct { + // Conn is the underlying pgx connection. + Conn *pgx.Conn + // IdleDuration is how long it has been since ResetSession last ran. + IdleDuration time.Duration +} + +// OptionShouldPing controls whether stdlib should issue a liveness ping before reusing a connection. +// If the function returns true, stdlib will ping. +// If it returns false, stdlib will skip the ping. +// If not provided, default is ping only when IdleDuration > 1s. +func OptionShouldPing(f func(context.Context, ShouldPingParams) bool) OptionOpenDB { + return func(dc *connector) { dc.ShouldPing = f } +} + // OptionBeforeConnect provides a callback for before connect. It is passed a shallow copy of the ConnConfig that will // be used to connect, so only its immediate members should be modified. Used only if db is opened with *pgx.ConnConfig. func OptionBeforeConnect(bc func(context.Context, *pgx.ConnConfig) error) OptionOpenDB { @@ -231,6 +247,7 @@ type connector struct { BeforeConnect func(context.Context, *pgx.ConnConfig) error // function to call before creation of every new connection AfterConnect func(context.Context, *pgx.Conn) error // function to call after creation of every new connection ResetSession func(context.Context, *pgx.Conn) error // function is called before a connection is reused + ShouldPing func(context.Context, ShouldPingParams) bool // function to decide if stdlib should ping before reusing a connection driver *Driver } @@ -282,6 +299,7 @@ func (c connector) Connect(ctx context.Context) (driver.Conn, error) { driver: c.driver, connConfig: connConfig, resetSessionFunc: c.ResetSession, + shouldPing: c.ShouldPing, psRefCounts: make(map[*pgconn.StatementDescription]int), }, nil } @@ -389,7 +407,8 @@ type Conn struct { close func(context.Context) error driver *Driver connConfig pgx.ConnConfig - resetSessionFunc func(context.Context, *pgx.Conn) error // Function is called before a connection is reused + resetSessionFunc func(context.Context, *pgx.Conn) error // Function is called before a connection is reused + shouldPing func(context.Context, ShouldPingParams) bool // Function to decide if stdlib should ping before reusing a connection lastResetSessionTime time.Time // psRefCounts contains reference counts for prepared statements. Prepare uses the underlying pgx logic to generate @@ -471,7 +490,8 @@ func (c *Conn) ExecContext(ctx context.Context, query string, argsV []driver.Nam return nil, driver.ErrBadConn } - args := namedValueToInterface(argsV) + args := make([]any, len(argsV)) + convertNamedArguments(args, argsV) commandTag, err := c.conn.Exec(ctx, query, args...) // if we got a network error before we had a chance to send the query, retry @@ -488,8 +508,9 @@ func (c *Conn) QueryContext(ctx context.Context, query string, argsV []driver.Na return nil, driver.ErrBadConn } - args := []any{databaseSQLResultFormats} - args = append(args, namedValueToInterface(argsV)...) + args := make([]any, 1+len(argsV)) + args[0] = databaseSQLResultFormats + convertNamedArguments(args[1:], argsV) rows, err := c.conn.Query(ctx, query, args...) if err != nil { @@ -534,12 +555,30 @@ func (c *Conn) ResetSession(ctx context.Context) error { return driver.ErrBadConn } + // Discard connection if it has an open transaction. This can happen if the + // application did not properly commit or rollback a transaction. + if c.conn.PgConn().TxStatus() != 'I' { + return driver.ErrBadConn + } + now := time.Now() - if now.Sub(c.lastResetSessionTime) > time.Second { + idle := now.Sub(c.lastResetSessionTime) + + doPing := idle > time.Second // default behavior: ping only if idle > 1s + + if c.shouldPing != nil { + doPing = c.shouldPing(ctx, ShouldPingParams{ + Conn: c.conn, + IdleDuration: idle, + }) + } + + if doPing { if err := c.conn.PgConn().Ping(ctx); err != nil { return driver.ErrBadConn } } + c.lastResetSessionTime = now return c.resetSessionFunc(ctx, c.conn) @@ -629,8 +668,10 @@ func (r *Rows) ColumnTypeLength(index int) (int64, bool) { switch fd.DataTypeOID { case pgtype.TextOID, pgtype.ByteaOID: return math.MaxInt64, true - case pgtype.VarcharOID, pgtype.BPCharArrayOID: + case pgtype.VarcharOID, pgtype.BPCharOID: return int64(fd.TypeModifier - varHeaderSize), true + case pgtype.VarbitOID: + return int64(fd.TypeModifier), true default: return 0, false } @@ -658,25 +699,25 @@ func (r *Rows) ColumnTypeScanType(index int) reflect.Type { switch fd.DataTypeOID { case pgtype.Float8OID: - return reflect.TypeOf(float64(0)) + return reflect.TypeFor[float64]() case pgtype.Float4OID: - return reflect.TypeOf(float32(0)) + return reflect.TypeFor[float32]() case pgtype.Int8OID: - return reflect.TypeOf(int64(0)) + return reflect.TypeFor[int64]() case pgtype.Int4OID: - return reflect.TypeOf(int32(0)) + return reflect.TypeFor[int32]() case pgtype.Int2OID: - return reflect.TypeOf(int16(0)) + return reflect.TypeFor[int16]() case pgtype.BoolOID: - return reflect.TypeOf(false) + return reflect.TypeFor[bool]() case pgtype.NumericOID: - return reflect.TypeOf(float64(0)) + return reflect.TypeFor[float64]() case pgtype.DateOID, pgtype.TimestampOID, pgtype.TimestamptzOID: - return reflect.TypeOf(time.Time{}) + return reflect.TypeFor[time.Time]() case pgtype.ByteaOID: - return reflect.TypeOf([]byte(nil)) + return reflect.TypeFor[[]byte]() default: - return reflect.TypeOf("") + return reflect.TypeFor[string]() } } @@ -848,28 +889,14 @@ func (r *Rows) Next(dest []driver.Value) error { return nil } -func valueToInterface(argsV []driver.Value) []any { - args := make([]any, 0, len(argsV)) - for _, v := range argsV { - if v != nil { - args = append(args, v.(any)) - } else { - args = append(args, nil) - } - } - return args -} - -func namedValueToInterface(argsV []driver.NamedValue) []any { - args := make([]any, 0, len(argsV)) - for _, v := range argsV { +func convertNamedArguments(args []any, argsV []driver.NamedValue) { + for i, v := range argsV { if v.Value != nil { - args = append(args, v.Value.(any)) + args[i] = v.Value.(any) } else { - args = append(args, nil) + args[i] = nil } } - return args } type wrapTx struct { diff --git a/kubewatch/vendor/github.com/jackc/pgx/v5/test.sh b/kubewatch/vendor/github.com/jackc/pgx/v5/test.sh new file mode 100644 index 000000000..8bab2d280 --- /dev/null +++ b/kubewatch/vendor/github.com/jackc/pgx/v5/test.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env bash +set -euo pipefail + +# test.sh - Run pgx tests against specific database targets +# +# Usage: +# ./test.sh [target] [go test flags...] +# +# Targets: +# pg14 - PostgreSQL 14 (port 5414) +# pg15 - PostgreSQL 15 (port 5415) +# pg16 - PostgreSQL 16 (port 5416) +# pg17 - PostgreSQL 17 (port 5417) +# pg18 - PostgreSQL 18 (port 5432) [default] +# crdb - CockroachDB (port 26257) +# all - Run against all targets sequentially +# +# Examples: +# ./test.sh # Test against PG18 +# ./test.sh pg14 # Test against PG14 +# ./test.sh crdb # Test against CockroachDB +# ./test.sh all # Test against all targets +# ./test.sh pg16 -run TestConnect # Test specific test against PG16 +# ./test.sh pg18 -count=1 -v # Verbose, no cache, PG18 + +# Color output (disabled if not a terminal) +if [ -t 1 ]; then + GREEN='\033[0;32m' + RED='\033[0;31m' + BLUE='\033[0;34m' + NC='\033[0m' +else + GREEN='' + RED='' + BLUE='' + NC='' +fi + +log_info() { echo -e "${BLUE}==> $*${NC}"; } +log_ok() { echo -e "${GREEN}==> $*${NC}"; } +log_err() { echo -e "${RED}==> $*${NC}" >&2; } + +# Wait for a database to accept connections +wait_for_ready() { + local connstr="$1" + local label="$2" + local max_attempts=30 + local attempt=0 + + log_info "Waiting for $label to be ready..." + while ! psql "$connstr" -c "SELECT 1" > /dev/null 2>&1; do + attempt=$((attempt + 1)) + if [ "$attempt" -ge "$max_attempts" ]; then + log_err "$label did not become ready after $max_attempts attempts" + return 1 + fi + sleep 1 + done + log_ok "$label is ready" +} + +# Directory containing this script (used to locate testsetup/) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CERTS_DIR="$SCRIPT_DIR/testsetup/certs" + +# Copy client certificates to /tmp for TLS tests +setup_client_certs() { + if [ -d "$CERTS_DIR" ]; then + base64 -d "$CERTS_DIR/ca.pem.b64" > /tmp/ca.pem + base64 -d "$CERTS_DIR/pgx_sslcert.crt.b64" > /tmp/pgx_sslcert.crt + base64 -d "$CERTS_DIR/pgx_sslcert.key.b64" > /tmp/pgx_sslcert.key + fi +} + +# Initialize CockroachDB (create database if not exists) +init_crdb() { + local connstr="postgresql://root@localhost:26257/?sslmode=disable" + wait_for_ready "$connstr" "CockroachDB" + log_info "Ensuring pgx_test database exists on CockroachDB..." + psql "$connstr" -c "CREATE DATABASE IF NOT EXISTS pgx_test" 2>/dev/null || true +} + +# Run tests against a single target +run_tests() { + local target="$1" + shift + local extra_args=("$@") + + local label="" + local port="" + + case "$target" in + pg14) label="PostgreSQL 14"; port=5414 ;; + pg15) label="PostgreSQL 15"; port=5415 ;; + pg16) label="PostgreSQL 16"; port=5416 ;; + pg17) label="PostgreSQL 17"; port=5417 ;; + pg18) label="PostgreSQL 18"; port=5432 ;; + crdb) + label="CockroachDB (port 26257)" + init_crdb + log_info "Testing against $label" + if ! PGX_TEST_DATABASE="postgresql://root@localhost:26257/pgx_test?sslmode=disable&experimental_enable_temp_tables=on" \ + go test -count=1 "${extra_args[@]}" ./...; then + log_err "Tests FAILED against $label" + return 1 + fi + log_ok "Tests passed against $label" + return 0 + ;; + *) + log_err "Unknown target: $target" + log_err "Valid targets: pg14, pg15, pg16, pg17, pg18, crdb, all" + return 1 + ;; + esac + + setup_client_certs + + log_info "Testing against $label (port $port)" + if ! PGX_TEST_DATABASE="host=localhost port=$port user=postgres password=postgres dbname=pgx_test" \ + PGX_TEST_UNIX_SOCKET_CONN_STRING="host=/var/run/postgresql port=$port user=postgres dbname=pgx_test" \ + PGX_TEST_TCP_CONN_STRING="host=127.0.0.1 port=$port user=pgx_md5 password=secret dbname=pgx_test" \ + PGX_TEST_MD5_PASSWORD_CONN_STRING="host=127.0.0.1 port=$port user=pgx_md5 password=secret dbname=pgx_test" \ + PGX_TEST_SCRAM_PASSWORD_CONN_STRING="host=127.0.0.1 port=$port user=pgx_scram password=secret dbname=pgx_test channel_binding=disable" \ + PGX_TEST_SCRAM_PLUS_CONN_STRING="host=localhost port=$port user=pgx_ssl password=secret sslmode=verify-full sslrootcert=/tmp/ca.pem dbname=pgx_test channel_binding=require" \ + PGX_TEST_PLAIN_PASSWORD_CONN_STRING="host=127.0.0.1 port=$port user=pgx_pw password=secret dbname=pgx_test" \ + PGX_TEST_TLS_CONN_STRING="host=localhost port=$port user=pgx_ssl password=secret sslmode=verify-full sslrootcert=/tmp/ca.pem dbname=pgx_test channel_binding=disable" \ + PGX_TEST_TLS_CLIENT_CONN_STRING="host=localhost port=$port user=pgx_sslcert sslmode=verify-full sslrootcert=/tmp/ca.pem sslcert=/tmp/pgx_sslcert.crt sslkey=/tmp/pgx_sslcert.key dbname=pgx_test" \ + PGX_SSL_PASSWORD=certpw \ + go test -count=1 "${extra_args[@]}" ./...; then + log_err "Tests FAILED against $label" + return 1 + fi + log_ok "Tests passed against $label" +} + +# Main +main() { + local target="${1:-pg18}" + + if [ "$target" = "all" ]; then + shift || true + local targets=(pg14 pg15 pg16 pg17 pg18 crdb) + local failed=() + + for t in "${targets[@]}"; do + echo "" + log_info "==========================================" + log_info "Target: $t" + log_info "==========================================" + if ! run_tests "$t" "$@"; then + failed+=("$t") + log_err "FAILED: $t" + fi + done + + echo "" + if [ ${#failed[@]} -gt 0 ]; then + log_err "Failed targets: ${failed[*]}" + return 1 + else + log_ok "All targets passed" + fi + else + shift || true + run_tests "$target" "$@" + fi +} + +main "$@" diff --git a/kubewatch/vendor/github.com/moby/spdystream/NOTICE b/kubewatch/vendor/github.com/moby/spdystream/NOTICE index b9b11c9ab..24e2e2aa3 100644 --- a/kubewatch/vendor/github.com/moby/spdystream/NOTICE +++ b/kubewatch/vendor/github.com/moby/spdystream/NOTICE @@ -3,3 +3,15 @@ Copyright 2014-2021 Docker Inc. This product includes software developed at Docker Inc. (https://www.docker.com/). + +SPDY implementation (spdy/) + +The spdy directory contains code derived from the Go project (golang.org/x/net). + +Copyright 2009-2013 The Go Authors. +Licensed under the BSD 3-Clause License. + +Modifications Copyright 2014-2021 Docker Inc. + +The BSD license text and Go patent grant are included in +spdy/LICENSE and spdy/PATENTS. diff --git a/kubewatch/vendor/github.com/moby/spdystream/connection.go b/kubewatch/vendor/github.com/moby/spdystream/connection.go index 1394d0ad4..69ce4777e 100644 --- a/kubewatch/vendor/github.com/moby/spdystream/connection.go +++ b/kubewatch/vendor/github.com/moby/spdystream/connection.go @@ -224,7 +224,13 @@ type Connection struct { // NewConnection creates a new spdy connection from an existing // network connection. func NewConnection(conn net.Conn, server bool) (*Connection, error) { - framer, framerErr := spdy.NewFramer(conn, conn) + return NewConnectionWithOptions(conn, server) +} + +// NewConnectionWithOptions creates a new spdy connection and applies frame +// parsing limits via options. +func NewConnectionWithOptions(conn net.Conn, server bool, opts ...spdy.FramerOption) (*Connection, error) { + framer, framerErr := spdy.NewFramerWithOptions(conn, conn, opts...) if framerErr != nil { return nil, framerErr } @@ -350,6 +356,9 @@ Loop: } else { debugMessage("(%p) EOF received", s) } + if spdyErr, ok := err.(*spdy.Error); ok && spdyErr.Err == spdy.InvalidControlFrame { + _ = s.conn.Close() + } break } var priority uint8 diff --git a/kubewatch/vendor/github.com/moby/spdystream/spdy/LICENSE b/kubewatch/vendor/github.com/moby/spdystream/spdy/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/kubewatch/vendor/github.com/moby/spdystream/spdy/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/kubewatch/vendor/github.com/moby/spdystream/spdy/PATENTS b/kubewatch/vendor/github.com/moby/spdystream/spdy/PATENTS new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/kubewatch/vendor/github.com/moby/spdystream/spdy/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google 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, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/kubewatch/vendor/github.com/moby/spdystream/spdy/dictionary.go b/kubewatch/vendor/github.com/moby/spdystream/spdy/dictionary.go index 392232f17..5a5ff0e14 100644 --- a/kubewatch/vendor/github.com/moby/spdystream/spdy/dictionary.go +++ b/kubewatch/vendor/github.com/moby/spdystream/spdy/dictionary.go @@ -1,19 +1,3 @@ -/* - Copyright 2014-2021 Docker Inc. - - 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. -*/ - // Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/kubewatch/vendor/github.com/moby/spdystream/spdy/options.go b/kubewatch/vendor/github.com/moby/spdystream/spdy/options.go new file mode 100644 index 000000000..ec03e0b9a --- /dev/null +++ b/kubewatch/vendor/github.com/moby/spdystream/spdy/options.go @@ -0,0 +1,25 @@ +package spdy + +// FramerOption allows callers to customize frame parsing limits. +type FramerOption func(*Framer) + +// WithMaxControlFramePayloadSize sets the control-frame payload limit. +func WithMaxControlFramePayloadSize(size uint32) FramerOption { + return func(f *Framer) { + f.maxFrameLength = size + } +} + +// WithMaxHeaderFieldSize sets the per-header name/value size limit. +func WithMaxHeaderFieldSize(size uint32) FramerOption { + return func(f *Framer) { + f.maxHeaderFieldSize = size + } +} + +// WithMaxHeaderCount sets the maximum number of headers in a frame. +func WithMaxHeaderCount(count uint32) FramerOption { + return func(f *Framer) { + f.maxHeaderCount = count + } +} diff --git a/kubewatch/vendor/github.com/moby/spdystream/spdy/read.go b/kubewatch/vendor/github.com/moby/spdystream/spdy/read.go index 75ea045b8..2abb69433 100644 --- a/kubewatch/vendor/github.com/moby/spdystream/spdy/read.go +++ b/kubewatch/vendor/github.com/moby/spdystream/spdy/read.go @@ -1,19 +1,3 @@ -/* - Copyright 2014-2021 Docker Inc. - - 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. -*/ - // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -24,6 +8,7 @@ import ( "compress/zlib" "encoding/binary" "io" + "io/ioutil" "net/http" "strings" ) @@ -59,6 +44,11 @@ func (frame *SettingsFrame) read(h ControlFrameHeader, f *Framer) error { if err := binary.Read(f.r, binary.BigEndian, &numSettings); err != nil { return err } + // Each setting is 8 bytes (4-byte id + 4-byte value). + // Payload is 4 bytes for numSettings + numSettings*8. + if h.length < 4 || numSettings > (h.length-4)/8 { + return &Error{InvalidControlFrame, 0} + } frame.FlagIdValues = make([]SettingsFlagIdValue, numSettings) for i := uint32(0); i < numSettings; i++ { if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Id); err != nil { @@ -177,8 +167,19 @@ func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) ( if err := binary.Read(f.r, binary.BigEndian, &length); err != nil { return nil, err } + maxControlFramePayload := uint32(MaxDataLength) + if f.maxFrameLength > 0 { + maxControlFramePayload = f.maxFrameLength + } + flags := ControlFlags((length & 0xff000000) >> 24) length &= 0xffffff + if length > maxControlFramePayload { + if _, err := io.CopyN(ioutil.Discard, f.r, int64(length)); err != nil { + return nil, err + } + return nil, &Error{InvalidControlFrame, 0} + } header := ControlFrameHeader{version, frameType, flags, length} cframe, err := newControlFrame(frameType) if err != nil { @@ -190,11 +191,22 @@ func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) ( return cframe, nil } -func parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) { +func (f *Framer) parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) { var numHeaders uint32 if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil { return nil, err } + maxHeaders := defaultMaxHeaderCount + if f.maxHeaderCount > 0 { + maxHeaders = f.maxHeaderCount + } + if numHeaders > maxHeaders { + return nil, &Error{InvalidControlFrame, streamId} + } + maxFieldSize := defaultMaxHeaderFieldSize + if f.maxHeaderFieldSize > 0 { + maxFieldSize = f.maxHeaderFieldSize + } var e error h := make(http.Header, int(numHeaders)) for i := 0; i < int(numHeaders); i++ { @@ -202,6 +214,9 @@ func parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) if err := binary.Read(r, binary.BigEndian, &length); err != nil { return nil, err } + if length > maxFieldSize { + return nil, &Error{InvalidControlFrame, streamId} + } nameBytes := make([]byte, length) if _, err := io.ReadFull(r, nameBytes); err != nil { return nil, err @@ -217,6 +232,9 @@ func parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) if err := binary.Read(r, binary.BigEndian, &length); err != nil { return nil, err } + if length > maxFieldSize { + return nil, &Error{InvalidControlFrame, streamId} + } value := make([]byte, length) if _, err := io.ReadFull(r, value); err != nil { return nil, err @@ -256,7 +274,7 @@ func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame) } reader = f.headerDecompressor } - frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId) + frame.Headers, err = f.parseHeaderValueBlock(reader, frame.StreamId) if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) { err = &Error{WrongCompressedPayloadSize, 0} } @@ -288,7 +306,7 @@ func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) e } reader = f.headerDecompressor } - frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId) + frame.Headers, err = f.parseHeaderValueBlock(reader, frame.StreamId) if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) { err = &Error{WrongCompressedPayloadSize, 0} } @@ -320,7 +338,7 @@ func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) err } reader = f.headerDecompressor } - frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId) + frame.Headers, err = f.parseHeaderValueBlock(reader, frame.StreamId) if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) { err = &Error{WrongCompressedPayloadSize, 0} } diff --git a/kubewatch/vendor/github.com/moby/spdystream/spdy/types.go b/kubewatch/vendor/github.com/moby/spdystream/spdy/types.go index a254a43ab..a5528618c 100644 --- a/kubewatch/vendor/github.com/moby/spdystream/spdy/types.go +++ b/kubewatch/vendor/github.com/moby/spdystream/spdy/types.go @@ -1,23 +1,9 @@ -/* - Copyright 2014-2021 Docker Inc. - - 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. -*/ - // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Modifications Copyright 2014-2021 Docker Inc. + // Package spdy implements the SPDY protocol (currently SPDY/3), described in // http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3. package spdy @@ -63,8 +49,20 @@ const ( ) // MaxDataLength is the maximum number of bytes that can be stored in one frame. +// +// SPDY frame headers encode the payload length using a 24-bit field, +// so the maximum representable size for both data and control frames +// is 2^24-1 bytes. +// +// See the SPDY/3 specification, "Frame Format": +// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1/ const MaxDataLength = 1<<24 - 1 +const ( + defaultMaxHeaderFieldSize uint32 = 1 << 20 + defaultMaxHeaderCount uint32 = 1000 +) + // headerValueSepator separates multiple header values. const headerValueSeparator = "\x00" @@ -269,6 +267,10 @@ type Framer struct { r io.Reader headerReader io.LimitedReader headerDecompressor io.ReadCloser + + maxFrameLength uint32 // overrides the default frame payload length limit. + maxHeaderFieldSize uint32 // overrides the default per-header name/value length limit. + maxHeaderCount uint32 // overrides the default header count limit. } // NewFramer allocates a new Framer for a given SPDY connection, represented by @@ -276,6 +278,16 @@ type Framer struct { // from/to the Reader and Writer, so the caller should pass in an appropriately // buffered implementation to optimize performance. func NewFramer(w io.Writer, r io.Reader) (*Framer, error) { + return newFramer(w, r) +} + +// NewFramerWithOptions allocates a new Framer for a given SPDY connection and +// applies frame parsing limits via options. +func NewFramerWithOptions(w io.Writer, r io.Reader, opts ...FramerOption) (*Framer, error) { + return newFramer(w, r, opts...) +} + +func newFramer(w io.Writer, r io.Reader, opts ...FramerOption) (*Framer, error) { compressBuf := new(bytes.Buffer) compressor, err := zlib.NewWriterLevelDict(compressBuf, zlib.BestCompression, []byte(headerDictionary)) if err != nil { @@ -287,5 +299,10 @@ func NewFramer(w io.Writer, r io.Reader) (*Framer, error) { headerCompressor: compressor, r: r, } + for _, opt := range opts { + if opt != nil { + opt(framer) + } + } return framer, nil } diff --git a/kubewatch/vendor/github.com/moby/spdystream/spdy/write.go b/kubewatch/vendor/github.com/moby/spdystream/spdy/write.go index ab6d91f3b..75084d35d 100644 --- a/kubewatch/vendor/github.com/moby/spdystream/spdy/write.go +++ b/kubewatch/vendor/github.com/moby/spdystream/spdy/write.go @@ -1,19 +1,3 @@ -/* - Copyright 2014-2021 Docker Inc. - - 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. -*/ - // Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -23,6 +7,7 @@ package spdy import ( "encoding/binary" "io" + "math" "net/http" "strings" ) @@ -63,13 +48,21 @@ func (frame *RstStreamFrame) write(f *Framer) (err error) { func (frame *SettingsFrame) write(f *Framer) (err error) { frame.CFHeader.version = Version frame.CFHeader.frameType = TypeSettings - frame.CFHeader.length = uint32(len(frame.FlagIdValues)*8 + 4) + payloadLen := len(frame.FlagIdValues)*8 + 4 + if payloadLen > MaxDataLength { + return &Error{InvalidControlFrame, 0} + } + frame.CFHeader.length = uint32(payloadLen) // Serialize frame to Writer. if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { return } - if err = binary.Write(f.w, binary.BigEndian, uint32(len(frame.FlagIdValues))); err != nil { + n := len(frame.FlagIdValues) + if uint64(n) > math.MaxUint32 { + return &Error{InvalidControlFrame, 0} + } + if err = binary.Write(f.w, binary.BigEndian, uint32(n)); err != nil { return } for _, flagIdValue := range frame.FlagIdValues { @@ -170,29 +163,41 @@ func writeControlFrameHeader(w io.Writer, h ControlFrameHeader) error { func writeHeaderValueBlock(w io.Writer, h http.Header) (n int, err error) { n = 0 - if err = binary.Write(w, binary.BigEndian, uint32(len(h))); err != nil { + numHeaders := len(h) + if numHeaders > math.MaxInt32 { + return n, &Error{InvalidControlFrame, 0} + } + if err = binary.Write(w, binary.BigEndian, uint32(numHeaders)); err != nil { return } - n += 2 + n += 4 for name, values := range h { - if err = binary.Write(w, binary.BigEndian, uint32(len(name))); err != nil { + nameLen := len(name) + if nameLen > math.MaxInt32 { + return n, &Error{InvalidControlFrame, 0} + } + if err = binary.Write(w, binary.BigEndian, uint32(nameLen)); err != nil { return } - n += 2 + n += 4 name = strings.ToLower(name) if _, err = io.WriteString(w, name); err != nil { return } - n += len(name) + n += nameLen v := strings.Join(values, headerValueSeparator) - if err = binary.Write(w, binary.BigEndian, uint32(len(v))); err != nil { + vLen := len(v) + if vLen > math.MaxInt32 { + return n, &Error{InvalidControlFrame, 0} + } + if err = binary.Write(w, binary.BigEndian, uint32(vLen)); err != nil { return } - n += 2 + n += 4 if _, err = io.WriteString(w, v); err != nil { return } - n += len(v) + n += vLen } return } @@ -216,7 +221,11 @@ func (f *Framer) writeSynStreamFrame(frame *SynStreamFrame) (err error) { // Set ControlFrameHeader. frame.CFHeader.version = Version frame.CFHeader.frameType = TypeSynStream - frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 10) + hLen := len(f.headerBuf.Bytes()) + 10 + if hLen > MaxDataLength { + return &Error{InvalidControlFrame, 0} + } + frame.CFHeader.length = uint32(hLen) // Serialize frame to Writer. if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { @@ -260,7 +269,11 @@ func (f *Framer) writeSynReplyFrame(frame *SynReplyFrame) (err error) { // Set ControlFrameHeader. frame.CFHeader.version = Version frame.CFHeader.frameType = TypeSynReply - frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4) + hLen := len(f.headerBuf.Bytes()) + 4 + if hLen > MaxDataLength { + return &Error{InvalidControlFrame, 0} + } + frame.CFHeader.length = uint32(hLen) // Serialize frame to Writer. if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { @@ -295,7 +308,11 @@ func (f *Framer) writeHeadersFrame(frame *HeadersFrame) (err error) { // Set ControlFrameHeader. frame.CFHeader.version = Version frame.CFHeader.frameType = TypeHeaders - frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4) + hLen := len(f.headerBuf.Bytes()) + 4 + if hLen > MaxDataLength { + return &Error{InvalidControlFrame, 0} + } + frame.CFHeader.length = uint32(hLen) // Serialize frame to Writer. if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil { @@ -323,7 +340,11 @@ func (f *Framer) writeDataFrame(frame *DataFrame) (err error) { if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil { return } - flagsAndLength := uint32(frame.Flags)<<24 | uint32(len(frame.Data)) + dLen := len(frame.Data) + if dLen > MaxDataLength { + return &Error{InvalidDataFrame, frame.StreamId} + } + flagsAndLength := uint32(frame.Flags)<<24 | uint32(dLen) if err = binary.Write(f.w, binary.BigEndian, flagsAndLength); err != nil { return } diff --git a/kubewatch/vendor/modules.txt b/kubewatch/vendor/modules.txt index 80440b01f..c9a759fbf 100644 --- a/kubewatch/vendor/modules.txt +++ b/kubewatch/vendor/modules.txt @@ -580,8 +580,8 @@ github.com/jackc/pgservicefile # github.com/jackc/pgtype v1.14.4 ## explicit; go 1.13 github.com/jackc/pgtype -# github.com/jackc/pgx/v5 v5.7.5 -## explicit; go 1.23.0 +# github.com/jackc/pgx/v5 v5.9.0 +## explicit; go 1.25.0 github.com/jackc/pgx/v5 github.com/jackc/pgx/v5/internal/iobufpool github.com/jackc/pgx/v5/internal/pgio @@ -704,7 +704,7 @@ github.com/mitchellh/go-wordwrap # github.com/mitchellh/reflectwalk v1.0.2 ## explicit github.com/mitchellh/reflectwalk -# github.com/moby/spdystream v0.5.0 +# github.com/moby/spdystream v0.5.1 ## explicit; go 1.13 github.com/moby/spdystream github.com/moby/spdystream/spdy