From dd908aa58324a4b019aeb9c977aa716e0b933358 Mon Sep 17 00:00:00 2001 From: Simon Esposito Date: Wed, 9 Aug 2023 18:16:40 +0100 Subject: [PATCH] Upgrade to pgx v5.4.3 Bug fixes. Improve performance of notification persist. --- data/modules/index.js | 542 ++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- main.go | 1 + server/console_channel.go | 3 +- server/core_account.go | 22 +- vendor/github.com/jackc/pgx/v5/CHANGELOG.md | 12 + vendor/github.com/jackc/pgx/v5/README.md | 6 +- vendor/github.com/jackc/pgx/v5/conn.go | 6 +- vendor/github.com/jackc/pgx/v5/doc.go | 8 +- .../github.com/jackc/pgx/v5/pgconn/config.go | 2 +- .../github.com/jackc/pgx/v5/pgconn/pgconn.go | 17 +- .../github.com/jackc/pgx/v5/pgproto3/trace.go | 260 ++++----- vendor/github.com/jackc/pgx/v5/pgtype/json.go | 30 +- .../github.com/jackc/pgx/v5/pgtype/numeric.go | 16 +- .../github.com/jackc/pgx/v5/pgtype/pgtype.go | 2 +- vendor/github.com/jackc/pgx/v5/rows.go | 13 +- vendor/github.com/jackc/pgx/v5/tx.go | 1 - vendor/modules.txt | 2 +- 19 files changed, 748 insertions(+), 201 deletions(-) create mode 100644 data/modules/index.js diff --git a/data/modules/index.js b/data/modules/index.js new file mode 100644 index 0000000000..369e0251db --- /dev/null +++ b/data/modules/index.js @@ -0,0 +1,542 @@ +"use strict"; +// Copyright 2020 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +var rpcIdRewards = 'rewards_js'; +var rpcIdFindMatch = 'find_match_js'; +function InitModule(ctx, logger, nk, initializer) { + initializer.registerRpc(rpcIdRewards, rpcReward); + initializer.registerRpc(rpcIdFindMatch, rpcFindMatch); + initializer.registerMatch(moduleName, { + matchInit: matchInit, + matchJoinAttempt: matchJoinAttempt, + matchJoin: matchJoin, + matchLeave: matchLeave, + matchLoop: matchLoop, + matchTerminate: matchTerminate, + matchSignal: matchSignal, + }); + var notifs = [ + { userId: '767da49c-9792-4fed-b6ca-589b0bc2aaca', code: 10, content: { foo: 'bar' }, senderId: '00000000-0000-0000-0000-000000000000', subject: 'test', persistent: true }, + ]; + // nk.notificationsSend(notifs); + logger.info('JavaScript logic loaded.'); +} +// Copyright 2020 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +function rpcReward(context, logger, nk, payload) { + if (!context.userId) { + throw Error('No user ID in context'); + } + if (payload) { + throw Error('no input allowed'); + } + var objectId = { + collection: 'reward', + key: 'daily', + userId: context.userId, + }; + var objects; + try { + objects = nk.storageRead([objectId]); + } + catch (error) { + logger.error('storageRead error: %s', error); + throw error; + } + var dailyReward = { + lastClaimUnix: 0, + }; + objects.forEach(function (object) { + if (object.key == 'daily') { + dailyReward = object.value; + } + }); + var resp = { + coinsReceived: 0, + }; + var d = new Date(); + d.setHours(0, 0, 0, 0); + // If last claimed is before the new day grant a new reward! + if (dailyReward.lastClaimUnix < msecToSec(d.getTime())) { + resp.coinsReceived = 500; + // Update player wallet. + var changeset = { + coins: resp.coinsReceived, + }; + try { + nk.walletUpdate(context.userId, changeset, {}, false); + } + catch (error) { + logger.error('walletUpdate error: %q', error); + throw error; + } + var notification = { + code: 1001, + content: changeset, + persistent: true, + subject: "You've received your daily reward!", + userId: context.userId, + }; + try { + nk.notificationsSend([notification]); + } + catch (error) { + logger.error('notificationsSend error: %q', error); + throw error; + } + dailyReward.lastClaimUnix = msecToSec(Date.now()); + var write = { + collection: 'reward', + key: 'daily', + permissionRead: 1, + permissionWrite: 0, + value: dailyReward, + userId: context.userId, + }; + if (objects.length > 0) { + write.version = objects[0].version; + } + try { + nk.storageWrite([write]); + } + catch (error) { + logger.error('storageWrite error: %q', error); + throw error; + } + } + var result = JSON.stringify(resp); + logger.debug('rpcReward resp: %q', result); + return result; +} +function msecToSec(n) { + return Math.floor(n / 1000); +} +var Mark; +(function (Mark) { + Mark[Mark["X"] = 0] = "X"; + Mark[Mark["O"] = 1] = "O"; + Mark[Mark["UNDEFINED"] = 2] = "UNDEFINED"; +})(Mark || (Mark = {})); +// The complete set of opcodes used for communication between clients and server. +var OpCode; +(function (OpCode) { + // New game round starting. + OpCode[OpCode["START"] = 1] = "START"; + // Update to the state of an ongoing round. + OpCode[OpCode["UPDATE"] = 2] = "UPDATE"; + // A game round has just completed. + OpCode[OpCode["DONE"] = 3] = "DONE"; + // A move the player wishes to make and sends to the server. + OpCode[OpCode["MOVE"] = 4] = "MOVE"; + // Move was rejected. + OpCode[OpCode["REJECTED"] = 5] = "REJECTED"; +})(OpCode || (OpCode = {})); +// Copyright 2020 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +var moduleName = "tic-tac-toe_js"; +var tickRate = 5; +var maxEmptySec = 30; +var delaybetweenGamesSec = 5; +var turnTimeFastSec = 10; +var turnTimeNormalSec = 20; +var winningPositions = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [0, 3, 6], + [1, 4, 7], + [2, 5, 8], + [0, 4, 8], + [2, 4, 6], +]; +var matchInit = function (ctx, logger, nk, params) { + var fast = !!params['fast']; + var label = { + open: 1, + fast: 0, + }; + if (fast) { + label.fast = 1; + } + var state = { + label: label, + emptyTicks: 0, + presences: {}, + joinsInProgress: 0, + playing: false, + board: [], + marks: {}, + mark: Mark.UNDEFINED, + deadlineRemainingTicks: 0, + winner: null, + winnerPositions: null, + nextGameRemainingTicks: 0, + }; + return { + state: state, + tickRate: tickRate, + label: JSON.stringify(label), + }; +}; +var matchJoinAttempt = function (ctx, logger, nk, dispatcher, tick, state, presence, metadata) { + // Check if it's a user attempting to rejoin after a disconnect. + if (presence.userId in state.presences) { + if (state.presences[presence.userId] === null) { + // User rejoining after a disconnect. + state.joinsInProgress++; + return { + state: state, + accept: false, + }; + } + else { + // User attempting to join from 2 different devices at the same time. + return { + state: state, + accept: false, + rejectMessage: 'already joined', + }; + } + } + // Check if match is full. + if (connectedPlayers(state) + state.joinsInProgress >= 2) { + return { + state: state, + accept: false, + rejectMessage: 'match full', + }; + } + // New player attempting to connect. + state.joinsInProgress++; + return { + state: state, + accept: true, + }; +}; +var matchJoin = function (ctx, logger, nk, dispatcher, tick, state, presences) { + var t = msecToSec(Date.now()); + for (var _i = 0, presences_1 = presences; _i < presences_1.length; _i++) { + var presence = presences_1[_i]; + state.emptyTicks = 0; + state.presences[presence.userId] = presence; + state.joinsInProgress--; + // Check if we must send a message to this user to update them on the current game state. + if (state.playing) { + // There's a game still currently in progress, the player is re-joining after a disconnect. Give them a state update. + var update = { + board: state.board, + mark: state.mark, + deadline: t + Math.floor(state.deadlineRemainingTicks / tickRate), + }; + // Send a message to the user that just joined. + dispatcher.broadcastMessage(OpCode.UPDATE, JSON.stringify(update)); + } + else if (state.board.length !== 0 && Object.keys(state.marks).length !== 0 && state.marks[presence.userId]) { + logger.debug('player %s rejoined game', presence.userId); + // There's no game in progress but we still have a completed game that the user was part of. + // They likely disconnected before the game ended, and have since forfeited because they took too long to return. + var done = { + board: state.board, + winner: state.winner, + winnerPositions: state.winnerPositions, + nextGameStart: t + Math.floor(state.nextGameRemainingTicks / tickRate) + }; + // Send a message to the user that just joined. + dispatcher.broadcastMessage(OpCode.DONE, JSON.stringify(done)); + } + } + // Check if match was open to new players, but should now be closed. + if (Object.keys(state.presences).length >= 2 && state.label.open != 0) { + state.label.open = 0; + var labelJSON = JSON.stringify(state.label); + dispatcher.matchLabelUpdate(labelJSON); + } + return { state: state }; +}; +var matchLeave = function (ctx, logger, nk, dispatcher, tick, state, presences) { + for (var _i = 0, presences_2 = presences; _i < presences_2.length; _i++) { + var presence = presences_2[_i]; + logger.info("Player: %s left match: %s.", presence.userId, ctx.matchId); + state.presences[presence.userId] = null; + } + return { state: state }; +}; +var matchLoop = function (ctx, logger, nk, dispatcher, tick, state, messages) { + var _a; + logger.debug('Running match loop. Tick: %d', tick); + if (connectedPlayers(state) + state.joinsInProgress === 0) { + state.emptyTicks++; + if (state.emptyTicks >= maxEmptySec * tickRate) { + // Match has been empty for too long, close it. + logger.info('closing idle match'); + return null; + } + } + var t = msecToSec(Date.now()); + // If there's no game in progress check if we can (and should) start one! + if (!state.playing) { + // Between games any disconnected users are purged, there's no in-progress game for them to return to anyway. + for (var userID in state.presences) { + if (state.presences[userID] === null) { + delete state.presences[userID]; + } + } + // Check if we need to update the label so the match now advertises itself as open to join. + if (Object.keys(state.presences).length < 2 && state.label.open != 1) { + state.label.open = 1; + var labelJSON = JSON.stringify(state.label); + dispatcher.matchLabelUpdate(labelJSON); + } + // Check if we have enough players to start a game. + if (Object.keys(state.presences).length < 2) { + return { state: state }; + } + // Check if enough time has passed since the last game. + if (state.nextGameRemainingTicks > 0) { + state.nextGameRemainingTicks--; + return { state: state }; + } + // We can start a game! Set up the game state and assign the marks to each player. + state.playing = true; + state.board = new Array(9); + state.marks = {}; + var marks_1 = [Mark.X, Mark.O]; + Object.keys(state.presences).forEach(function (userId) { + var _a; + state.marks[userId] = (_a = marks_1.shift(), (_a !== null && _a !== void 0 ? _a : null)); + }); + state.mark = Mark.X; + state.winner = null; + state.winnerPositions = null; + state.deadlineRemainingTicks = calculateDeadlineTicks(state.label); + state.nextGameRemainingTicks = 0; + // Notify the players a new game has started. + var msg = { + board: state.board, + marks: state.marks, + mark: state.mark, + deadline: t + Math.floor(state.deadlineRemainingTicks / tickRate), + }; + dispatcher.broadcastMessage(OpCode.START, JSON.stringify(msg)); + return { state: state }; + } + // There's a game in progresstate. Check for input, update match state, and send messages to clientstate. + for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { + var message = messages_1[_i]; + switch (message.opCode) { + case OpCode.MOVE: + logger.debug('Received move message from user: %v', state.marks); + var mark = (_a = state.marks[message.sender.userId], (_a !== null && _a !== void 0 ? _a : null)); + if (mark === null || state.mark != mark) { + // It is not this player's turn. + dispatcher.broadcastMessage(OpCode.REJECTED, null, [message.sender]); + continue; + } + var msg = {}; + try { + msg = JSON.parse(nk.binaryToString(message.data)); + } + catch (error) { + // Client sent bad data. + dispatcher.broadcastMessage(OpCode.REJECTED, null, [message.sender]); + logger.debug('Bad data received: %v', error); + continue; + } + if (state.board[msg.position]) { + // Client sent a position outside the board, or one that has already been played. + dispatcher.broadcastMessage(OpCode.REJECTED, null, [message.sender]); + continue; + } + // Update the game state. + state.board[msg.position] = mark; + state.mark = mark === Mark.O ? Mark.X : Mark.O; + state.deadlineRemainingTicks = calculateDeadlineTicks(state.label); + // Check if game is over through a winning move. + var _b = winCheck(state.board, mark), winner = _b[0], winningPos = _b[1]; + if (winner) { + state.winner = mark; + state.winnerPositions = winningPos; + state.playing = false; + state.deadlineRemainingTicks = 0; + state.nextGameRemainingTicks = delaybetweenGamesSec * tickRate; + } + // Check if game is over because no more moves are possible. + var tie = state.board.every(function (v) { return v !== null; }); + if (tie) { + // Update state to reflect the tie, and schedule the next game. + state.playing = false; + state.deadlineRemainingTicks = 0; + state.nextGameRemainingTicks = delaybetweenGamesSec * tickRate; + } + var opCode = void 0; + var outgoingMsg = void 0; + if (state.playing) { + opCode = OpCode.UPDATE; + var msg_1 = { + board: state.board, + mark: state.mark, + deadline: t + Math.floor(state.deadlineRemainingTicks / tickRate), + }; + outgoingMsg = msg_1; + } + else { + opCode = OpCode.DONE; + var msg_2 = { + board: state.board, + winner: state.winner, + winnerPositions: state.winnerPositions, + nextGameStart: t + Math.floor(state.nextGameRemainingTicks / tickRate), + }; + outgoingMsg = msg_2; + } + dispatcher.broadcastMessage(opCode, JSON.stringify(outgoingMsg)); + break; + default: + // No other opcodes are expected from the client, so automatically treat it as an error. + dispatcher.broadcastMessage(OpCode.REJECTED, null, [message.sender]); + logger.error('Unexpected opcode received: %d', message.opCode); + } + } + // Keep track of the time remaining for the player to submit their move. Idle players forfeit. + if (state.playing) { + state.deadlineRemainingTicks--; + if (state.deadlineRemainingTicks <= 0) { + // The player has run out of time to submit their move. + state.playing = false; + state.winner = state.mark === Mark.O ? Mark.X : Mark.O; + state.deadlineRemainingTicks = 0; + state.nextGameRemainingTicks = delaybetweenGamesSec * tickRate; + var msg = { + board: state.board, + winner: state.winner, + nextGameStart: t + Math.floor(state.nextGameRemainingTicks / tickRate), + winnerPositions: null, + }; + dispatcher.broadcastMessage(OpCode.DONE, JSON.stringify(msg)); + } + } + return { state: state }; +}; +var matchTerminate = function (ctx, logger, nk, dispatcher, tick, state, graceSeconds) { + return { state: state }; +}; +var matchSignal = function (ctx, logger, nk, dispatcher, tick, state) { + return { state: state }; +}; +function calculateDeadlineTicks(l) { + if (l.fast === 1) { + return turnTimeFastSec * tickRate; + } + else { + return turnTimeNormalSec * tickRate; + } +} +function winCheck(board, mark) { + for (var _i = 0, winningPositions_1 = winningPositions; _i < winningPositions_1.length; _i++) { + var wp = winningPositions_1[_i]; + if (board[wp[0]] === mark && + board[wp[1]] === mark && + board[wp[2]] === mark) { + return [true, wp]; + } + } + return [false, null]; +} +function connectedPlayers(s) { + var count = 0; + for (var _i = 0, _a = Object.keys(s.presences); _i < _a.length; _i++) { + var p = _a[_i]; + if (s.presences[p] !== null) { + count++; + } + } + return count; +} +// Copyright 2020 The Nakama Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +var rpcFindMatch = function (ctx, logger, nk, payload) { + if (!ctx.userId) { + throw Error('No user ID in context'); + } + if (!payload) { + throw Error('Expects payload.'); + } + var request = {}; + try { + request = JSON.parse(payload); + } + catch (error) { + logger.error('Error parsing json message: %q', error); + throw error; + } + var matches; + try { + var query = "+label.open:1 +label.fast:" + (request.fast ? 1 : 0); + matches = nk.matchList(10, true, null, null, 1, query); + } + catch (error) { + logger.error('Error listing matches: %v', error); + throw error; + } + var matchIds = []; + if (matches.length > 0) { + // There are one or more ongoing matches the user could join. + matchIds = matches.map(function (m) { return m.matchId; }); + } + else { + // No available matches found, create a new one. + try { + matchIds.push(nk.matchCreate(moduleName, { fast: request.fast })); + } + catch (error) { + logger.error('Error creating match: %v', error); + throw error; + } + } + var res = { matchIds: matchIds }; + return JSON.stringify(res); +}; diff --git a/go.mod b/go.mod index 3914c56768..340f19310c 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/heroiclabs/sql-migrate v0.0.0-20230615133120-fb3ad977aaaf github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59 - github.com/jackc/pgx/v5 v5.4.2 + github.com/jackc/pgx/v5 v5.4.3 github.com/prometheus/client_golang v1.16.0 github.com/stretchr/testify v1.8.4 github.com/twmb/murmur3 v1.1.8 diff --git a/go.sum b/go.sum index dfe0b16b20..efff5d63c9 100644 --- a/go.sum +++ b/go.sum @@ -181,8 +181,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186 h1:ZQM8qLT/E/CGD6XX0E6q9FAwxJYmWpJufzmLMaFuzgQ= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v5 v5.4.2 h1:u1gmGDwbdRUZiwisBm/Ky2M14uQyUP65bG8+20nnyrg= -github.com/jackc/pgx/v5 v5.4.2/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= 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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= diff --git a/main.go b/main.go index fd1a5a7fc4..4386de2327 100644 --- a/main.go +++ b/main.go @@ -152,6 +152,7 @@ func main() { }); err != nil { logger.Fatal("Failed to acquire pgx conn for migration check", zap.Error(err)) } + conn.Close() // Access to social provider integrations. socialClient := social.NewClient(logger, 5*time.Second, config.GetGoogleAuth().OAuthConfig) diff --git a/server/console_channel.go b/server/console_channel.go index b3d949bf81..3c5868714d 100644 --- a/server/console_channel.go +++ b/server/console_channel.go @@ -13,7 +13,6 @@ import ( "github.com/heroiclabs/nakama-common/api" "github.com/heroiclabs/nakama-common/runtime" "github.com/heroiclabs/nakama/v3/console" - "github.com/jackc/pgx/v5/pgtype" "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -55,7 +54,7 @@ func (s *ConsoleServer) DeleteChannelMessages(ctx context.Context, in *console.D var res sql.Result var err error - if res, err = s.db.ExecContext(ctx, query, &pgtype.Timestamptz{Time: deleteBefore, Valid: true}); err != nil { + if res, err = s.db.ExecContext(ctx, query, deleteBefore); err != nil { s.logger.Error("Could not delete messages.", zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while trying to delete messages.") } diff --git a/server/core_account.go b/server/core_account.go index 1622dfe42c..f9e4bf67bd 100644 --- a/server/core_account.go +++ b/server/core_account.go @@ -52,8 +52,8 @@ type accountUpdate struct { } func GetAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, statusRegistry *StatusRegistry, userID uuid.UUID) (*api.Account, error) { - var displayName sql.NullString var username sql.NullString + var displayName sql.NullString var avatarURL sql.NullString var langTag sql.NullString var location sql.NullString @@ -73,7 +73,9 @@ func GetAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, statusRegis var updateTime pgtype.Timestamptz var verifyTime pgtype.Timestamptz var disableTime pgtype.Timestamptz - var deviceIDs pgtype.Array[string] + var deviceIDs pgtype.FlatArray[string] + + m := pgtype.NewMap() query := ` SELECT u.username, u.display_name, u.avatar_url, u.lang_tag, u.location, u.timezone, u.metadata, u.wallet, @@ -82,7 +84,7 @@ SELECT u.username, u.display_name, u.avatar_url, u.lang_tag, u.location, u.timez FROM users u WHERE u.id = $1` - if err := db.QueryRowContext(ctx, query, userID).Scan(&username, &displayName, &avatarURL, &langTag, &location, &timezone, &metadata, &wallet, &email, &apple, &facebook, &facebookInstantGame, &google, &gamecenter, &steam, &customID, &edgeCount, &createTime, &updateTime, &verifyTime, &disableTime, &deviceIDs); err != nil { + if err := db.QueryRowContext(ctx, query, userID).Scan(&username, &displayName, &avatarURL, &langTag, &location, &timezone, &metadata, &wallet, &email, &apple, &facebook, &facebookInstantGame, &google, &gamecenter, &steam, &customID, &edgeCount, &createTime, &updateTime, &verifyTime, &disableTime, m.SQLScanner(&deviceIDs)); err != nil { if err == sql.ErrNoRows { return nil, ErrAccountNotFound } @@ -90,8 +92,8 @@ WHERE u.id = $1` return nil, err } - devices := make([]*api.AccountDevice, 0, len(deviceIDs.Elements)) - for _, deviceID := range deviceIDs.Elements { + devices := make([]*api.AccountDevice, 0, len(deviceIDs)) + for _, deviceID := range deviceIDs { devices = append(devices, &api.AccountDevice{Id: deviceID}) } @@ -183,17 +185,19 @@ WHERE u.id IN (` + strings.Join(statements, ",") + `)` var updateTime pgtype.Timestamptz var verifyTime pgtype.Timestamptz var disableTime pgtype.Timestamptz - var deviceIDs pgtype.Array[string] + var deviceIDs pgtype.FlatArray[string] + + m := pgtype.NewMap() - err = rows.Scan(&userID, &username, &displayName, &avatarURL, &langTag, &location, &timezone, &metadata, &wallet, &email, &apple, &facebook, &facebookInstantGame, &google, &gamecenter, &steam, &customID, &edgeCount, &createTime, &updateTime, &verifyTime, &disableTime, &deviceIDs) + err = rows.Scan(&userID, &username, &displayName, &avatarURL, &langTag, &location, &timezone, &metadata, &wallet, &email, &apple, &facebook, &facebookInstantGame, &google, &gamecenter, &steam, &customID, &edgeCount, &createTime, &updateTime, &verifyTime, &disableTime, m.SQLScanner(&deviceIDs)) if err != nil { _ = rows.Close() logger.Error("Error retrieving user accounts.", zap.Error(err)) return nil, err } - devices := make([]*api.AccountDevice, 0, len(deviceIDs.Elements)) - for _, deviceID := range deviceIDs.Elements { + devices := make([]*api.AccountDevice, 0, len(deviceIDs)) + for _, deviceID := range deviceIDs { devices = append(devices, &api.AccountDevice{Id: deviceID}) } diff --git a/vendor/github.com/jackc/pgx/v5/CHANGELOG.md b/vendor/github.com/jackc/pgx/v5/CHANGELOG.md index 916666b4aa..fb2304a2fb 100644 --- a/vendor/github.com/jackc/pgx/v5/CHANGELOG.md +++ b/vendor/github.com/jackc/pgx/v5/CHANGELOG.md @@ -1,3 +1,15 @@ +# 5.4.3 (August 5, 2023) + +* Fix: QCharArrayOID was defined with the wrong OID (Christoph Engelbert) +* Fix: connect_timeout for sslmode=allow|prefer (smaher-edb) +* Fix: pgxpool: background health check cannot overflow pool +* Fix: Check for nil in defer when sending batch (recover properly from panic) +* Fix: json scan of non-string pointer to pointer +* Fix: zeronull.Timestamptz should use pgtype.Timestamptz +* Fix: NewConnsCount was not correctly counting connections created by Acquire directly. (James Hartig) +* RowTo(AddrOf)StructByPos ignores fields with "-" db tag +* Optimization: improve text format numeric parsing (horpto) + # 5.4.2 (July 11, 2023) * Fix: RowScanner errors are fatal to Rows diff --git a/vendor/github.com/jackc/pgx/v5/README.md b/vendor/github.com/jackc/pgx/v5/README.md index ad48697c18..522206f95a 100644 --- a/vendor/github.com/jackc/pgx/v5/README.md +++ b/vendor/github.com/jackc/pgx/v5/README.md @@ -1,5 +1,5 @@ [![Go Reference](https://pkg.go.dev/badge/github.com/jackc/pgx/v5.svg)](https://pkg.go.dev/github.com/jackc/pgx/v5) -![Build Status](https://github.com/jackc/pgx/actions/workflows/ci.yml/badge.svg) +[![Build Status](https://github.com/jackc/pgx/actions/workflows/ci.yml/badge.svg)](https://github.com/jackc/pgx/actions/workflows/ci.yml) # pgx - PostgreSQL Driver and Toolkit @@ -139,8 +139,8 @@ These adapters can be used with the tracelog package. ### [github.com/pashagolub/pgxmock](https://github.com/pashagolub/pgxmock) -pgxmock is a mock library implementing pgx interfaces. -pgxmock has one and only purpose - to simulate pgx behavior in tests, without needing a real database connection. +pgxmock is a mock library implementing pgx interfaces. +pgxmock has one and only purpose - to simulate pgx behavior in tests, without needing a real database connection. ### [github.com/georgysavva/scany](https://github.com/georgysavva/scany) diff --git a/vendor/github.com/jackc/pgx/v5/conn.go b/vendor/github.com/jackc/pgx/v5/conn.go index a609d10020..7c7081b487 100644 --- a/vendor/github.com/jackc/pgx/v5/conn.go +++ b/vendor/github.com/jackc/pgx/v5/conn.go @@ -194,7 +194,7 @@ func ParseConfigWithOptions(connString string, options ParseConfigOptions) (*Con return connConfig, nil } -// ParseConfig creates a ConnConfig from a connection string. ParseConfig handles all options that pgconn.ParseConfig +// ParseConfig creates a ConnConfig from a connection string. ParseConfig handles all options that [pgconn.ParseConfig] // does. In addition, it accepts the following options: // // - default_query_exec_mode. @@ -507,7 +507,7 @@ func (c *Conn) execSimpleProtocol(ctx context.Context, sql string, arguments []a mrr := c.pgConn.Exec(ctx, sql) for mrr.NextResult() { - commandTag, err = mrr.ResultReader().Close() + commandTag, _ = mrr.ResultReader().Close() } err = mrr.Close() return commandTag, err @@ -1064,7 +1064,7 @@ func (c *Conn) sendBatchQueryExecModeDescribeExec(ctx context.Context, b *Batch) func (c *Conn) sendBatchExtendedWithDescription(ctx context.Context, b *Batch, distinctNewQueries []*pgconn.StatementDescription, sdCache stmtcache.Cache) (pbr *pipelineBatchResults) { pipeline := c.pgConn.StartPipeline(context.Background()) defer func() { - if pbr.err != nil { + if pbr != nil && pbr.err != nil { pipeline.Close() } }() diff --git a/vendor/github.com/jackc/pgx/v5/doc.go b/vendor/github.com/jackc/pgx/v5/doc.go index 0db8cbb140..7486f42c5d 100644 --- a/vendor/github.com/jackc/pgx/v5/doc.go +++ b/vendor/github.com/jackc/pgx/v5/doc.go @@ -7,17 +7,17 @@ details. Establishing a Connection -The primary way of establishing a connection is with `pgx.Connect`. +The primary way of establishing a connection is with [pgx.Connect]: conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL")) The database connection string can be in URL or DSN format. Both PostgreSQL settings and pgx settings can be specified -here. In addition, a config struct can be created by `ParseConfig` and modified before establishing the connection with -`ConnectConfig` to configure settings such as tracing that cannot be configured with a connection string. +here. In addition, a config struct can be created by [ParseConfig] and modified before establishing the connection with +[ConnectConfig] to configure settings such as tracing that cannot be configured with a connection string. Connection Pool -`*pgx.Conn` represents a single connection to the database and is not concurrency safe. Use package +[*pgx.Conn] represents a single connection to the database and is not concurrency safe. Use package github.com/jackc/pgx/v5/pgxpool for a concurrency safe connection pool. Query Interface diff --git a/vendor/github.com/jackc/pgx/v5/pgconn/config.go b/vendor/github.com/jackc/pgx/v5/pgconn/config.go index 24bf837ce1..1c2c647d9f 100644 --- a/vendor/github.com/jackc/pgx/v5/pgconn/config.go +++ b/vendor/github.com/jackc/pgx/v5/pgconn/config.go @@ -26,7 +26,7 @@ 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 -// Config is the settings used to establish a connection to a PostgreSQL server. It must be created by ParseConfig. A +// 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. type Config struct { Host string // host (e.g. localhost) or absolute path to unix domain socket directory (e.g. /private/tmp) diff --git a/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go b/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go index 12357751a3..8f602e4090 100644 --- a/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go +++ b/vendor/github.com/jackc/pgx/v5/pgconn/pgconn.go @@ -97,7 +97,7 @@ type PgConn struct { } // Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or DSN format) -// to provide configuration. See documentation for ParseConfig for details. ctx can be used to cancel a connect attempt. +// to provide configuration. See documentation for [ParseConfig] for details. ctx can be used to cancel a connect attempt. func Connect(ctx context.Context, connString string) (*PgConn, error) { config, err := ParseConfig(connString) if err != nil { @@ -108,7 +108,7 @@ func Connect(ctx context.Context, connString string) (*PgConn, error) { } // Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or DSN format) -// and ParseConfigOptions to provide additional configuration. See documentation for ParseConfig for details. ctx can be +// and ParseConfigOptions to provide additional configuration. See documentation for [ParseConfig] for details. ctx can be // used to cancel a connect attempt. func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptions ParseConfigOptions) (*PgConn, error) { config, err := ParseConfigWithOptions(connString, parseConfigOptions) @@ -120,7 +120,7 @@ func ConnectWithOptions(ctx context.Context, connString string, parseConfigOptio } // Connect establishes a connection to a PostgreSQL server using config. config must have been constructed with -// ParseConfig. ctx can be used to cancel a connect attempt. +// [ParseConfig]. ctx can be used to cancel a connect attempt. // // 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: @@ -154,12 +154,15 @@ func ConnectConfig(octx context.Context, config *Config) (pgConn *PgConn, err er foundBestServer := false var fallbackConfig *FallbackConfig - for _, fc := range fallbackConfigs { + for i, fc := range fallbackConfigs { // ConnectTimeout restricts the whole connection process. if config.ConnectTimeout != 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(octx, config.ConnectTimeout) - defer cancel() + // create new context first time or when previous host was different + if i == 0 || (fallbackConfigs[i].Host != fallbackConfigs[i-1].Host) { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(octx, config.ConnectTimeout) + defer cancel() + } } else { ctx = octx } diff --git a/vendor/github.com/jackc/pgx/v5/pgproto3/trace.go b/vendor/github.com/jackc/pgx/v5/pgproto3/trace.go index c09f68d1a6..6cc7d3e36c 100644 --- a/vendor/github.com/jackc/pgx/v5/pgproto3/trace.go +++ b/vendor/github.com/jackc/pgx/v5/pgproto3/trace.go @@ -6,15 +6,18 @@ import ( "io" "strconv" "strings" + "sync" "time" ) // tracer traces the messages send to and from a Backend or Frontend. The format it produces roughly mimics the // format produced by the libpq C function PQtrace. type tracer struct { + TracerOptions + + mux sync.Mutex w io.Writer buf *bytes.Buffer - TracerOptions } // TracerOptions controls tracing behavior. It is roughly equivalent to the libpq function PQsetTraceFlags. @@ -119,278 +122,255 @@ func (t *tracer) traceMessage(sender byte, encodedLen int32, msg Message) { case *Terminate: t.traceTerminate(sender, encodedLen, msg) default: - t.beginTrace(sender, encodedLen, "Unknown") - t.finishTrace() + t.writeTrace(sender, encodedLen, "Unknown", nil) } } func (t *tracer) traceAuthenticationCleartextPassword(sender byte, encodedLen int32, msg *AuthenticationCleartextPassword) { - t.beginTrace(sender, encodedLen, "AuthenticationCleartextPassword") - t.finishTrace() + t.writeTrace(sender, encodedLen, "AuthenticationCleartextPassword", nil) } func (t *tracer) traceAuthenticationGSS(sender byte, encodedLen int32, msg *AuthenticationGSS) { - t.beginTrace(sender, encodedLen, "AuthenticationGSS") - t.finishTrace() + t.writeTrace(sender, encodedLen, "AuthenticationGSS", nil) } func (t *tracer) traceAuthenticationGSSContinue(sender byte, encodedLen int32, msg *AuthenticationGSSContinue) { - t.beginTrace(sender, encodedLen, "AuthenticationGSSContinue") - t.finishTrace() + t.writeTrace(sender, encodedLen, "AuthenticationGSSContinue", nil) } func (t *tracer) traceAuthenticationMD5Password(sender byte, encodedLen int32, msg *AuthenticationMD5Password) { - t.beginTrace(sender, encodedLen, "AuthenticationMD5Password") - t.finishTrace() + t.writeTrace(sender, encodedLen, "AuthenticationMD5Password", nil) } func (t *tracer) traceAuthenticationOk(sender byte, encodedLen int32, msg *AuthenticationOk) { - t.beginTrace(sender, encodedLen, "AuthenticationOk") - t.finishTrace() + t.writeTrace(sender, encodedLen, "AuthenticationOk", nil) } func (t *tracer) traceAuthenticationSASL(sender byte, encodedLen int32, msg *AuthenticationSASL) { - t.beginTrace(sender, encodedLen, "AuthenticationSASL") - t.finishTrace() + t.writeTrace(sender, encodedLen, "AuthenticationSASL", nil) } func (t *tracer) traceAuthenticationSASLContinue(sender byte, encodedLen int32, msg *AuthenticationSASLContinue) { - t.beginTrace(sender, encodedLen, "AuthenticationSASLContinue") - t.finishTrace() + t.writeTrace(sender, encodedLen, "AuthenticationSASLContinue", nil) } func (t *tracer) traceAuthenticationSASLFinal(sender byte, encodedLen int32, msg *AuthenticationSASLFinal) { - t.beginTrace(sender, encodedLen, "AuthenticationSASLFinal") - t.finishTrace() + t.writeTrace(sender, encodedLen, "AuthenticationSASLFinal", nil) } func (t *tracer) traceBackendKeyData(sender byte, encodedLen int32, msg *BackendKeyData) { - t.beginTrace(sender, encodedLen, "BackendKeyData") - if t.RegressMode { - t.buf.WriteString("\t NNNN NNNN") - } else { - fmt.Fprintf(t.buf, "\t %d %d", msg.ProcessID, msg.SecretKey) - } - t.finishTrace() + t.writeTrace(sender, encodedLen, "BackendKeyData", func() { + if t.RegressMode { + t.buf.WriteString("\t NNNN NNNN") + } else { + fmt.Fprintf(t.buf, "\t %d %d", msg.ProcessID, msg.SecretKey) + } + }) } func (t *tracer) traceBind(sender byte, encodedLen int32, msg *Bind) { - t.beginTrace(sender, encodedLen, "Bind") - fmt.Fprintf(t.buf, "\t %s %s %d", traceDoubleQuotedString([]byte(msg.DestinationPortal)), traceDoubleQuotedString([]byte(msg.PreparedStatement)), len(msg.ParameterFormatCodes)) - for _, fc := range msg.ParameterFormatCodes { - fmt.Fprintf(t.buf, " %d", fc) - } - fmt.Fprintf(t.buf, " %d", len(msg.Parameters)) - for _, p := range msg.Parameters { - fmt.Fprintf(t.buf, " %s", traceSingleQuotedString(p)) - } - fmt.Fprintf(t.buf, " %d", len(msg.ResultFormatCodes)) - for _, fc := range msg.ResultFormatCodes { - fmt.Fprintf(t.buf, " %d", fc) - } - t.finishTrace() + t.writeTrace(sender, encodedLen, "Bind", func() { + fmt.Fprintf(t.buf, "\t %s %s %d", traceDoubleQuotedString([]byte(msg.DestinationPortal)), traceDoubleQuotedString([]byte(msg.PreparedStatement)), len(msg.ParameterFormatCodes)) + for _, fc := range msg.ParameterFormatCodes { + fmt.Fprintf(t.buf, " %d", fc) + } + fmt.Fprintf(t.buf, " %d", len(msg.Parameters)) + for _, p := range msg.Parameters { + fmt.Fprintf(t.buf, " %s", traceSingleQuotedString(p)) + } + fmt.Fprintf(t.buf, " %d", len(msg.ResultFormatCodes)) + for _, fc := range msg.ResultFormatCodes { + fmt.Fprintf(t.buf, " %d", fc) + } + }) } func (t *tracer) traceBindComplete(sender byte, encodedLen int32, msg *BindComplete) { - t.beginTrace(sender, encodedLen, "BindComplete") - t.finishTrace() + t.writeTrace(sender, encodedLen, "BindComplete", nil) } func (t *tracer) traceCancelRequest(sender byte, encodedLen int32, msg *CancelRequest) { - t.beginTrace(sender, encodedLen, "CancelRequest") - t.finishTrace() + t.writeTrace(sender, encodedLen, "CancelRequest", nil) } func (t *tracer) traceClose(sender byte, encodedLen int32, msg *Close) { - t.beginTrace(sender, encodedLen, "Close") - t.finishTrace() + t.writeTrace(sender, encodedLen, "Close", nil) } func (t *tracer) traceCloseComplete(sender byte, encodedLen int32, msg *CloseComplete) { - t.beginTrace(sender, encodedLen, "CloseComplete") - t.finishTrace() + t.writeTrace(sender, encodedLen, "CloseComplete", nil) } func (t *tracer) traceCommandComplete(sender byte, encodedLen int32, msg *CommandComplete) { - t.beginTrace(sender, encodedLen, "CommandComplete") - fmt.Fprintf(t.buf, "\t %s", traceDoubleQuotedString(msg.CommandTag)) - t.finishTrace() + t.writeTrace(sender, encodedLen, "CommandComplete", func() { + fmt.Fprintf(t.buf, "\t %s", traceDoubleQuotedString(msg.CommandTag)) + }) } func (t *tracer) traceCopyBothResponse(sender byte, encodedLen int32, msg *CopyBothResponse) { - t.beginTrace(sender, encodedLen, "CopyBothResponse") - t.finishTrace() + t.writeTrace(sender, encodedLen, "CopyBothResponse", nil) } func (t *tracer) traceCopyData(sender byte, encodedLen int32, msg *CopyData) { - t.beginTrace(sender, encodedLen, "CopyData") - t.finishTrace() + t.writeTrace(sender, encodedLen, "CopyData", nil) } func (t *tracer) traceCopyDone(sender byte, encodedLen int32, msg *CopyDone) { - t.beginTrace(sender, encodedLen, "CopyDone") - t.finishTrace() + t.writeTrace(sender, encodedLen, "CopyDone", nil) } func (t *tracer) traceCopyFail(sender byte, encodedLen int32, msg *CopyFail) { - t.beginTrace(sender, encodedLen, "CopyFail") - fmt.Fprintf(t.buf, "\t %s", traceDoubleQuotedString([]byte(msg.Message))) - t.finishTrace() + t.writeTrace(sender, encodedLen, "CopyFail", func() { + fmt.Fprintf(t.buf, "\t %s", traceDoubleQuotedString([]byte(msg.Message))) + }) } func (t *tracer) traceCopyInResponse(sender byte, encodedLen int32, msg *CopyInResponse) { - t.beginTrace(sender, encodedLen, "CopyInResponse") - t.finishTrace() + t.writeTrace(sender, encodedLen, "CopyInResponse", nil) } func (t *tracer) traceCopyOutResponse(sender byte, encodedLen int32, msg *CopyOutResponse) { - t.beginTrace(sender, encodedLen, "CopyOutResponse") - t.finishTrace() + t.writeTrace(sender, encodedLen, "CopyOutResponse", nil) } func (t *tracer) traceDataRow(sender byte, encodedLen int32, msg *DataRow) { - t.beginTrace(sender, encodedLen, "DataRow") - fmt.Fprintf(t.buf, "\t %d", len(msg.Values)) - for _, v := range msg.Values { - if v == nil { - t.buf.WriteString(" -1") - } else { - fmt.Fprintf(t.buf, " %d %s", len(v), traceSingleQuotedString(v)) + t.writeTrace(sender, encodedLen, "DataRow", func() { + fmt.Fprintf(t.buf, "\t %d", len(msg.Values)) + for _, v := range msg.Values { + if v == nil { + t.buf.WriteString(" -1") + } else { + fmt.Fprintf(t.buf, " %d %s", len(v), traceSingleQuotedString(v)) + } } - } - t.finishTrace() + }) } func (t *tracer) traceDescribe(sender byte, encodedLen int32, msg *Describe) { - t.beginTrace(sender, encodedLen, "Describe") - fmt.Fprintf(t.buf, "\t %c %s", msg.ObjectType, traceDoubleQuotedString([]byte(msg.Name))) - t.finishTrace() + t.writeTrace(sender, encodedLen, "Describe", func() { + fmt.Fprintf(t.buf, "\t %c %s", msg.ObjectType, traceDoubleQuotedString([]byte(msg.Name))) + }) } func (t *tracer) traceEmptyQueryResponse(sender byte, encodedLen int32, msg *EmptyQueryResponse) { - t.beginTrace(sender, encodedLen, "EmptyQueryResponse") - t.finishTrace() + t.writeTrace(sender, encodedLen, "EmptyQueryResponse", nil) } func (t *tracer) traceErrorResponse(sender byte, encodedLen int32, msg *ErrorResponse) { - t.beginTrace(sender, encodedLen, "ErrorResponse") - t.finishTrace() + t.writeTrace(sender, encodedLen, "ErrorResponse", nil) } func (t *tracer) TraceQueryute(sender byte, encodedLen int32, msg *Execute) { - t.beginTrace(sender, encodedLen, "Execute") - fmt.Fprintf(t.buf, "\t %s %d", traceDoubleQuotedString([]byte(msg.Portal)), msg.MaxRows) - t.finishTrace() + t.writeTrace(sender, encodedLen, "Execute", func() { + fmt.Fprintf(t.buf, "\t %s %d", traceDoubleQuotedString([]byte(msg.Portal)), msg.MaxRows) + }) } func (t *tracer) traceFlush(sender byte, encodedLen int32, msg *Flush) { - t.beginTrace(sender, encodedLen, "Flush") - t.finishTrace() + t.writeTrace(sender, encodedLen, "Flush", nil) } func (t *tracer) traceFunctionCall(sender byte, encodedLen int32, msg *FunctionCall) { - t.beginTrace(sender, encodedLen, "FunctionCall") - t.finishTrace() + t.writeTrace(sender, encodedLen, "FunctionCall", nil) } func (t *tracer) traceFunctionCallResponse(sender byte, encodedLen int32, msg *FunctionCallResponse) { - t.beginTrace(sender, encodedLen, "FunctionCallResponse") - t.finishTrace() + t.writeTrace(sender, encodedLen, "FunctionCallResponse", nil) } func (t *tracer) traceGSSEncRequest(sender byte, encodedLen int32, msg *GSSEncRequest) { - t.beginTrace(sender, encodedLen, "GSSEncRequest") - t.finishTrace() + t.writeTrace(sender, encodedLen, "GSSEncRequest", nil) } func (t *tracer) traceNoData(sender byte, encodedLen int32, msg *NoData) { - t.beginTrace(sender, encodedLen, "NoData") - t.finishTrace() + t.writeTrace(sender, encodedLen, "NoData", nil) } func (t *tracer) traceNoticeResponse(sender byte, encodedLen int32, msg *NoticeResponse) { - t.beginTrace(sender, encodedLen, "NoticeResponse") - t.finishTrace() + t.writeTrace(sender, encodedLen, "NoticeResponse", nil) } func (t *tracer) traceNotificationResponse(sender byte, encodedLen int32, msg *NotificationResponse) { - t.beginTrace(sender, encodedLen, "NotificationResponse") - fmt.Fprintf(t.buf, "\t %d %s %s", msg.PID, traceDoubleQuotedString([]byte(msg.Channel)), traceDoubleQuotedString([]byte(msg.Payload))) - t.finishTrace() + t.writeTrace(sender, encodedLen, "NotificationResponse", func() { + fmt.Fprintf(t.buf, "\t %d %s %s", msg.PID, traceDoubleQuotedString([]byte(msg.Channel)), traceDoubleQuotedString([]byte(msg.Payload))) + }) } func (t *tracer) traceParameterDescription(sender byte, encodedLen int32, msg *ParameterDescription) { - t.beginTrace(sender, encodedLen, "ParameterDescription") - t.finishTrace() + t.writeTrace(sender, encodedLen, "ParameterDescription", nil) } func (t *tracer) traceParameterStatus(sender byte, encodedLen int32, msg *ParameterStatus) { - t.beginTrace(sender, encodedLen, "ParameterStatus") - fmt.Fprintf(t.buf, "\t %s %s", traceDoubleQuotedString([]byte(msg.Name)), traceDoubleQuotedString([]byte(msg.Value))) - t.finishTrace() + t.writeTrace(sender, encodedLen, "ParameterStatus", func() { + fmt.Fprintf(t.buf, "\t %s %s", traceDoubleQuotedString([]byte(msg.Name)), traceDoubleQuotedString([]byte(msg.Value))) + }) } func (t *tracer) traceParse(sender byte, encodedLen int32, msg *Parse) { - t.beginTrace(sender, encodedLen, "Parse") - fmt.Fprintf(t.buf, "\t %s %s %d", traceDoubleQuotedString([]byte(msg.Name)), traceDoubleQuotedString([]byte(msg.Query)), len(msg.ParameterOIDs)) - for _, oid := range msg.ParameterOIDs { - fmt.Fprintf(t.buf, " %d", oid) - } - t.finishTrace() + t.writeTrace(sender, encodedLen, "Parse", func() { + fmt.Fprintf(t.buf, "\t %s %s %d", traceDoubleQuotedString([]byte(msg.Name)), traceDoubleQuotedString([]byte(msg.Query)), len(msg.ParameterOIDs)) + for _, oid := range msg.ParameterOIDs { + fmt.Fprintf(t.buf, " %d", oid) + } + }) } func (t *tracer) traceParseComplete(sender byte, encodedLen int32, msg *ParseComplete) { - t.beginTrace(sender, encodedLen, "ParseComplete") - t.finishTrace() + t.writeTrace(sender, encodedLen, "ParseComplete", nil) } func (t *tracer) tracePortalSuspended(sender byte, encodedLen int32, msg *PortalSuspended) { - t.beginTrace(sender, encodedLen, "PortalSuspended") - t.finishTrace() + t.writeTrace(sender, encodedLen, "PortalSuspended", nil) } func (t *tracer) traceQuery(sender byte, encodedLen int32, msg *Query) { - t.beginTrace(sender, encodedLen, "Query") - fmt.Fprintf(t.buf, "\t %s", traceDoubleQuotedString([]byte(msg.String))) - t.finishTrace() + t.writeTrace(sender, encodedLen, "Query", func() { + fmt.Fprintf(t.buf, "\t %s", traceDoubleQuotedString([]byte(msg.String))) + }) } func (t *tracer) traceReadyForQuery(sender byte, encodedLen int32, msg *ReadyForQuery) { - t.beginTrace(sender, encodedLen, "ReadyForQuery") - fmt.Fprintf(t.buf, "\t %c", msg.TxStatus) - t.finishTrace() + t.writeTrace(sender, encodedLen, "ReadyForQuery", func() { + fmt.Fprintf(t.buf, "\t %c", msg.TxStatus) + }) } func (t *tracer) traceRowDescription(sender byte, encodedLen int32, msg *RowDescription) { - t.beginTrace(sender, encodedLen, "RowDescription") - fmt.Fprintf(t.buf, "\t %d", len(msg.Fields)) - for _, fd := range msg.Fields { - fmt.Fprintf(t.buf, ` %s %d %d %d %d %d %d`, traceDoubleQuotedString(fd.Name), fd.TableOID, fd.TableAttributeNumber, fd.DataTypeOID, fd.DataTypeSize, fd.TypeModifier, fd.Format) - } - t.finishTrace() + t.writeTrace(sender, encodedLen, "RowDescription", func() { + fmt.Fprintf(t.buf, "\t %d", len(msg.Fields)) + for _, fd := range msg.Fields { + fmt.Fprintf(t.buf, ` %s %d %d %d %d %d %d`, traceDoubleQuotedString(fd.Name), fd.TableOID, fd.TableAttributeNumber, fd.DataTypeOID, fd.DataTypeSize, fd.TypeModifier, fd.Format) + } + }) } func (t *tracer) traceSSLRequest(sender byte, encodedLen int32, msg *SSLRequest) { - t.beginTrace(sender, encodedLen, "SSLRequest") - t.finishTrace() + t.writeTrace(sender, encodedLen, "SSLRequest", nil) } func (t *tracer) traceStartupMessage(sender byte, encodedLen int32, msg *StartupMessage) { - t.beginTrace(sender, encodedLen, "StartupMessage") - t.finishTrace() + t.writeTrace(sender, encodedLen, "StartupMessage", nil) } func (t *tracer) traceSync(sender byte, encodedLen int32, msg *Sync) { - t.beginTrace(sender, encodedLen, "Sync") - t.finishTrace() + t.writeTrace(sender, encodedLen, "Sync", nil) } func (t *tracer) traceTerminate(sender byte, encodedLen int32, msg *Terminate) { - t.beginTrace(sender, encodedLen, "Terminate") - t.finishTrace() + t.writeTrace(sender, encodedLen, "Terminate", nil) } -func (t *tracer) beginTrace(sender byte, encodedLen int32, msgType string) { +func (t *tracer) writeTrace(sender byte, encodedLen int32, msgType string, writeDetails func()) { + t.mux.Lock() + defer t.mux.Unlock() + defer func() { + if t.buf.Cap() > 1024 { + t.buf = &bytes.Buffer{} + } else { + t.buf.Reset() + } + }() + if !t.SuppressTimestamps { now := time.Now() t.buf.WriteString(now.Format("2006-01-02 15:04:05.000000")) @@ -402,17 +382,13 @@ func (t *tracer) beginTrace(sender byte, encodedLen int32, msgType string) { t.buf.WriteString(msgType) t.buf.WriteByte('\t') t.buf.WriteString(strconv.FormatInt(int64(encodedLen), 10)) -} -func (t *tracer) finishTrace() { + if writeDetails != nil { + writeDetails() + } + t.buf.WriteByte('\n') t.buf.WriteTo(t.w) - - if t.buf.Cap() > 1024 { - t.buf = &bytes.Buffer{} - } else { - t.buf.Reset() - } } // traceDoubleQuotedString returns t.buf as a double-quoted string without any escaping. It is roughly equivalent to diff --git a/vendor/github.com/jackc/pgx/v5/pgtype/json.go b/vendor/github.com/jackc/pgx/v5/pgtype/json.go index b7a7101eca..d332dd0db1 100644 --- a/vendor/github.com/jackc/pgx/v5/pgtype/json.go +++ b/vendor/github.com/jackc/pgx/v5/pgtype/json.go @@ -92,6 +92,23 @@ func (JSONCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan switch target.(type) { case *string: return scanPlanAnyToString{} + + case **string: + // This is to fix **string scanning. It seems wrong to special case **string, but it's not clear what a better + // solution would be. + // + // https://github.com/jackc/pgx/issues/1470 -- **string + // https://github.com/jackc/pgx/issues/1691 -- ** anything else + + if wrapperPlan, nextDst, ok := TryPointerPointerScanPlan(target); ok { + if nextPlan := m.planScan(oid, format, nextDst); nextPlan != nil { + if _, failed := nextPlan.(*scanPlanFail); !failed { + wrapperPlan.SetNext(nextPlan) + return wrapperPlan + } + } + } + case *[]byte: return scanPlanJSONToByteSlice{} case BytesScanner: @@ -104,19 +121,6 @@ func (JSONCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan return &scanPlanSQLScanner{formatCode: format} } - // This is to fix **string scanning. It seems wrong to special case sql.Scanner and pointer to pointer, but it's not - // clear what a better solution would be. - // - // https://github.com/jackc/pgx/issues/1470 - if wrapperPlan, nextDst, ok := TryPointerPointerScanPlan(target); ok { - if nextPlan := m.planScan(oid, format, nextDst); nextPlan != nil { - if _, failed := nextPlan.(*scanPlanFail); !failed { - wrapperPlan.SetNext(nextPlan) - return wrapperPlan - } - } - } - return scanPlanJSONToJSONUnmarshal{} } diff --git a/vendor/github.com/jackc/pgx/v5/pgtype/numeric.go b/vendor/github.com/jackc/pgx/v5/pgtype/numeric.go index 62e457873c..0e58fd0765 100644 --- a/vendor/github.com/jackc/pgx/v5/pgtype/numeric.go +++ b/vendor/github.com/jackc/pgx/v5/pgtype/numeric.go @@ -144,20 +144,20 @@ func (n *Numeric) toBigInt() (*big.Int, error) { } func parseNumericString(str string) (n *big.Int, exp int32, err error) { - parts := strings.SplitN(str, ".", 2) - digits := strings.Join(parts, "") + idx := strings.IndexByte(str, '.') - if len(parts) > 1 { - exp = int32(-len(parts[1])) - } else { - for len(digits) > 1 && digits[len(digits)-1] == '0' && digits[len(digits)-2] != '-' { - digits = digits[:len(digits)-1] + if idx == -1 { + for len(str) > 1 && str[len(str)-1] == '0' && str[len(str)-2] != '-' { + str = str[:len(str)-1] exp++ } + } else { + exp = int32(-(len(str) - idx - 1)) + str = str[:idx] + str[idx+1:] } accum := &big.Int{} - if _, ok := accum.SetString(digits, 10); !ok { + if _, ok := accum.SetString(str, 10); !ok { return nil, 0, fmt.Errorf("%s is not a number", str) } diff --git a/vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go b/vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go index 8b636763a1..59d833a19e 100644 --- a/vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go +++ b/vendor/github.com/jackc/pgx/v5/pgtype/pgtype.go @@ -44,7 +44,7 @@ const ( MacaddrOID = 829 InetOID = 869 BoolArrayOID = 1000 - QCharArrayOID = 1003 + QCharArrayOID = 1002 NameArrayOID = 1003 Int2ArrayOID = 1005 Int4ArrayOID = 1007 diff --git a/vendor/github.com/jackc/pgx/v5/rows.go b/vendor/github.com/jackc/pgx/v5/rows.go index 055a6645aa..1b1c8ac9a3 100644 --- a/vendor/github.com/jackc/pgx/v5/rows.go +++ b/vendor/github.com/jackc/pgx/v5/rows.go @@ -306,7 +306,7 @@ func (rows *baseRows) Values() ([]any, error) { copy(newBuf, buf) values = append(values, newBuf) default: - rows.fatal(errors.New("Unknown format code")) + rows.fatal(errors.New("unknown format code")) } } @@ -496,7 +496,8 @@ func (rs *mapRowScanner) ScanRow(rows Rows) error { } // RowToStructByPos returns a T scanned from row. T must be a struct. T must have the same number a public fields as row -// has fields. The row and T fields will by matched by position. +// has fields. The row and T fields will by matched by position. If the "db" struct tag is "-" then the field will be +// ignored. func RowToStructByPos[T any](row CollectableRow) (T, error) { var value T err := row.Scan(&positionalStructRowScanner{ptrToStruct: &value}) @@ -504,7 +505,8 @@ func RowToStructByPos[T any](row CollectableRow) (T, error) { } // RowToAddrOfStructByPos returns the address of a T scanned from row. T must be a struct. T must have the same number a -// public fields as row has fields. The row and T fields will by matched by position. +// public fields as row has fields. The row and T fields will by matched by position. If the "db" struct tag is "-" then +// the field will be ignored. func RowToAddrOfStructByPos[T any](row CollectableRow) (*T, error) { var value T err := row.Scan(&positionalStructRowScanner{ptrToStruct: &value}) @@ -545,6 +547,11 @@ func (rs *positionalStructRowScanner) appendScanTargets(dstElemValue reflect.Val if sf.Anonymous && sf.Type.Kind() == reflect.Struct { scanTargets = rs.appendScanTargets(dstElemValue.Field(i), scanTargets) } else if sf.PkgPath == "" { + dbTag, _ := sf.Tag.Lookup(structTagKey) + if dbTag == "-" { + // Field is ignored, skip it. + continue + } scanTargets = append(scanTargets, dstElemValue.Field(i).Addr().Interface()) } } diff --git a/vendor/github.com/jackc/pgx/v5/tx.go b/vendor/github.com/jackc/pgx/v5/tx.go index 575c17a716..8feeb51233 100644 --- a/vendor/github.com/jackc/pgx/v5/tx.go +++ b/vendor/github.com/jackc/pgx/v5/tx.go @@ -152,7 +152,6 @@ type Tx interface { // called on the dbTx. type dbTx struct { conn *Conn - err error savepointNum int64 closed bool } diff --git a/vendor/modules.txt b/vendor/modules.txt index e0370d4dc4..39206cc179 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -176,7 +176,7 @@ github.com/jackc/pgservicefile # github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59 ## explicit; go 1.12 github.com/jackc/pgtype -# github.com/jackc/pgx/v5 v5.4.2 +# github.com/jackc/pgx/v5 v5.4.3 ## explicit; go 1.19 github.com/jackc/pgx/v5 github.com/jackc/pgx/v5/internal/anynil