From aabda88010e4817d0fa73264c51acae33f6d65ae Mon Sep 17 00:00:00 2001 From: Nathan VanBenschoten Date: Thu, 22 Nov 2018 23:49:08 -0500 Subject: [PATCH] sql: lazily copy prepStmtNamespace to avoid repeat allocations Fixes #32427. This change reworks the handling of `prepStmtsNamespace` and `prepStmtsNamespaceAtTxnRewindPos` to avoid cloning the former indiscriminately after every statement. This clone was very expensive because it resulted in a series of map allocations and memory copies. The new approach avoids the cloning in almost all cases by performing the copy only when it is needed. In the past, this would translate to cases where `prepStmtsNamespace` and `prepStmtsNamespaceAtTxnRewindPos` actually differed. It does so by introducing a new `checkpointedPrepStmtNamespace` type which abstracts away the details of taking snapshots of the namespace and rewinding when necessary. The change also adds a fast-path that I've wanted for a while which allows us to avoid map accesses entirely for the unnamed prepared statement/portal. A large number of drivers only use the unnamed portal by default, so this should provide a nice speedup. Release note: None --- pkg/sql/conn_executor.go | 48 ++-- pkg/sql/conn_executor_exec.go | 4 +- pkg/sql/conn_executor_prepare.go | 382 +++++++++++++++++++++++-------- 3 files changed, 303 insertions(+), 131 deletions(-) diff --git a/pkg/sql/conn_executor.go b/pkg/sql/conn_executor.go index 43b46ce41e05..381a62f3ce4c 100644 --- a/pkg/sql/conn_executor.go +++ b/pkg/sql/conn_executor.go @@ -458,10 +458,6 @@ func (s *Server) newConnExecutor( mon: &sessionRootMon, sessionMon: &sessionMon, sessionData: sd, - prepStmtsNamespace: prepStmtNamespace{ - prepStmts: make(map[string]prepStmtEntry), - portals: make(map[string]portalEntry), - }, state: txnState{ mon: &txnMon, connCtx: ctx, @@ -684,10 +680,8 @@ func (ex *connExecutor) close(ctx context.Context, closeType closeType) { } if closeType != panicClose { - // Close all statements and prepared portals by first unifying the namespaces - // and the closing what remains. - ex.commitPrepStmtNamespace(ctx) - ex.prepStmtsNamespace.resetTo(ctx, &prepStmtNamespace{}) + // Close all statements and prepared portals. + ex.prepStmtsNamespace.clear(ctx) } if ex.sessionTracing.Enabled() { @@ -785,17 +779,6 @@ type connExecutor struct { // // Set via setTxnRewindPos(). txnRewindPos CmdPos - - // prepStmtsNamespaceAtTxnRewindPos is a snapshot of the prep stmts/portals - // (ex.prepStmtsNamespace) before processing the command at position - // txnRewindPos. - // Here's the deal: prepared statements are not transactional, but they do - // need to interact properly with automatic retries (i.e. rewinding the - // command buffer). When doing a rewind, we need to be able to restore the - // prep stmts as they were. We do this by taking a snapshot every time - // txnRewindPos is advanced. Prepared statements are shared between the two - // collections, but these collections are periodically reconciled. - prepStmtsNamespaceAtTxnRewindPos prepStmtNamespace } // sessionData contains the user-configurable connection variables. @@ -836,8 +819,13 @@ type connExecutor struct { parallelizeQueue ParallelizeQueue // prepStmtNamespace contains the prepared statements and portals that the - // session currently has access to. - prepStmtsNamespace prepStmtNamespace + // session currently has access to. The structure can be checkpointed and + // reverted, which is important because prepared statements are not + // transactional, but they do need to interact properly with automatic + // retries (i.e. rewinding the command buffer). When doing a rewind, we need + // to be able to restore the prep stmts as they were. We do this by taking a + // snapshot every time txnRewindPos is advanced. + prepStmtsNamespace checkpointedPrepStmtNamespace // mu contains of all elements of the struct that can be changed // after initialization, and may be accessed from another thread. @@ -1053,7 +1041,7 @@ func (ex *connExecutor) run( // ExecPortal is handled like ExecStmt, except that the placeholder info // is taken from the portal. - portal, ok := ex.prepStmtsNamespace.portals[tcmd.Name] + portal, ok := ex.prepStmtsNamespace.getPortal(tcmd.Name) if !ok { err := pgerror.NewErrorf( pgerror.CodeInvalidCursorNameError, "unknown portal %q", tcmd.Name) @@ -1226,7 +1214,7 @@ func (ex *connExecutor) run( return err } case rewind: - ex.rewindPrepStmtNamespace(ex.Ctx()) + ex.prepStmtsNamespace.rewind(ex.Ctx()) advInfo.rewCap.rewindAndUnlock(ex.Ctx()) case stayInPlace: // Nothing to do. The same statement will be executed again. @@ -1298,7 +1286,7 @@ func (ex *connExecutor) updateTxnRewindPosMaybe( case ExecStmt: canAdvance = ex.stmtDoesntNeedRetry(tcmd.Stmt) case ExecPortal: - portal := ex.prepStmtsNamespace.portals[tcmd.Name] + portal, _ := ex.prepStmtsNamespace.getPortal(tcmd.Name) canAdvance = ex.stmtDoesntNeedRetry(portal.Stmt.Statement) case PrepareStmt: canAdvance = true @@ -1340,7 +1328,7 @@ func (ex *connExecutor) setTxnRewindPos(ctx context.Context, pos CmdPos) { } ex.extraTxnState.txnRewindPos = pos ex.stmtBuf.ltrim(ctx, pos) - ex.commitPrepStmtNamespace(ctx) + ex.prepStmtsNamespace.snapshot(ctx) } // stmtDoesntNeedRetry returns true if the given statement does not need to be @@ -2075,6 +2063,7 @@ func (sc *StatementCounters) incrementCount(stmt tree.Statement) { // connExPrepStmtsAccessor is an implementation of preparedStatementsAccessor // that gives access to a connExecutor's prepared statements. +// TODO(nvanbenschoten): replace this. type connExPrepStmtsAccessor struct { ex *connExecutor } @@ -2083,7 +2072,7 @@ var _ preparedStatementsAccessor = connExPrepStmtsAccessor{} // Get is part of the preparedStatementsAccessor interface. func (ps connExPrepStmtsAccessor) Get(name string) (*PreparedStatement, bool) { - s, ok := ps.ex.prepStmtsNamespace.prepStmts[name] + s, ok := ps.ex.prepStmtsNamespace.getPrepStmt(name) return s.PreparedStatement, ok } @@ -2093,16 +2082,13 @@ func (ps connExPrepStmtsAccessor) Delete(ctx context.Context, name string) bool if !ok { return false } - ps.ex.deletePreparedStmt(ctx, name) + ps.ex.prepStmtsNamespace.delPrepStmt(ctx, name) return true } // DeleteAll is part of the preparedStatementsAccessor interface. func (ps connExPrepStmtsAccessor) DeleteAll(ctx context.Context) { - ps.ex.prepStmtsNamespace = prepStmtNamespace{ - prepStmts: make(map[string]prepStmtEntry), - portals: make(map[string]portalEntry), - } + ps.ex.prepStmtsNamespace.clear(ctx) } // contextStatementKey is an empty type for the handle associated with the diff --git a/pkg/sql/conn_executor_exec.go b/pkg/sql/conn_executor_exec.go index e0f0a3506e56..53746fb0714f 100644 --- a/pkg/sql/conn_executor_exec.go +++ b/pkg/sql/conn_executor_exec.go @@ -284,7 +284,7 @@ func (ex *connExecutor) execStmtInOpenState( // This is handling the SQL statement "PREPARE". See execPrepare for // handling of the protocol-level command for preparing statements. name := s.Name.String() - if _, ok := ex.prepStmtsNamespace.prepStmts[name]; ok { + if _, ok := ex.prepStmtsNamespace.getPrepStmt(name); ok { err := pgerror.NewErrorf( pgerror.CodeDuplicatePreparedStatementError, "prepared statement %q already exists", name, @@ -304,7 +304,7 @@ func (ex *connExecutor) execStmtInOpenState( // Replace the `EXECUTE foo` statement with the prepared statement, and // continue execution below. name := s.Name.String() - ps, ok := ex.prepStmtsNamespace.prepStmts[name] + ps, ok := ex.prepStmtsNamespace.getPrepStmt(name) if !ok { err := pgerror.NewErrorf( pgerror.CodeInvalidSQLStatementNameError, diff --git a/pkg/sql/conn_executor_prepare.go b/pkg/sql/conn_executor_prepare.go index a3082f571786..62bc73ac114d 100644 --- a/pkg/sql/conn_executor_prepare.go +++ b/pkg/sql/conn_executor_prepare.go @@ -20,93 +20,306 @@ import ( "strconv" "unsafe" + "github.com/lib/pq/oid" + "github.com/pkg/errors" + "github.com/cockroachdb/cockroach/pkg/internal/client" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgwirebase" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/util/fsm" "github.com/cockroachdb/cockroach/pkg/util/log" - "github.com/lib/pq/oid" - "github.com/pkg/errors" ) -type prepStmtNamespace struct { - // prepStmts contains the prepared statements currently available on the - // session. - prepStmts map[string]prepStmtEntry - // portals contains the portals currently available on the session. - portals map[string]portalEntry -} - type prepStmtEntry struct { *PreparedStatement portals map[string]struct{} } -func (pe *prepStmtEntry) copy() prepStmtEntry { +func (ps *prepStmtEntry) copy() prepStmtEntry { cpy := prepStmtEntry{} - cpy.PreparedStatement = pe.PreparedStatement - cpy.portals = make(map[string]struct{}) - for pname := range pe.portals { + cpy.PreparedStatement = ps.PreparedStatement + cpy.portals = make(map[string]struct{}, len(ps.portals)) + for pname := range ps.portals { cpy.portals[pname] = struct{}{} } return cpy } +type prepStmtEntries struct { + // unnamed special-cases access to the unnamed prepared statement. + unnamed struct { + prepStmtEntry + set bool + } + // m contains all other prepared statements. + m map[string]prepStmtEntry +} + +func (pse *prepStmtEntries) get(name string) (prepStmtEntry, bool) { + if name == "" { + return pse.unnamed.prepStmtEntry, pse.unnamed.set + } + ps, ok := pse.m[name] + return ps, ok +} + +func (pse *prepStmtEntries) add(name string, ps prepStmtEntry) { + if name == "" { + pse.unnamed.prepStmtEntry = ps + pse.unnamed.set = true + return + } + if pse.m == nil { + pse.m = make(map[string]prepStmtEntry) + } + pse.m[name] = ps +} + +func (pse *prepStmtEntries) del(name string) (prepStmtEntry, bool) { + if name == "" { + if !pse.unnamed.set { + return prepStmtEntry{}, false + } + ps := pse.unnamed.prepStmtEntry + pse.unnamed.prepStmtEntry = prepStmtEntry{} + pse.unnamed.set = false + return ps, true + } + ps, ok := pse.m[name] + if !ok { + return prepStmtEntry{}, false + } + delete(pse.m, name) + return ps, true +} + +func (pse *prepStmtEntries) copy() prepStmtEntries { + cpy := prepStmtEntries{} + cpy.unnamed = pse.unnamed + if len(pse.m) > 0 { + cpy.m = make(map[string]prepStmtEntry, len(pse.m)) + for name, ps := range pse.m { + cpy.m[name] = ps.copy() + } + } + return cpy +} + type portalEntry struct { *PreparedPortal psName string } -// resetTo resets a namespace to equate another one (`to`). Prep stmts and portals -// that are present in ns but not in to are deallocated. -// -// A (pointer to) empty `to` can be passed in to deallocate everything. -func (ns *prepStmtNamespace) resetTo(ctx context.Context, to *prepStmtNamespace) { - for name, ps := range ns.prepStmts { - bps, ok := to.prepStmts[name] - // If the prepared statement didn't exist before (including if a statement - // with the same name existed, but it was different), close it. - if !ok || bps.PreparedStatement != ps.PreparedStatement { - ps.close(ctx) +type portalEntries struct { + // unnamed special-cases access to the unnamed portal. + unnamed struct { + portalEntry + set bool + } + // m contains all other portals. + m map[string]portalEntry +} + +func (pe *portalEntries) get(name string) (portalEntry, bool) { + if name == "" { + return pe.unnamed.portalEntry, pe.unnamed.set + } + p, ok := pe.m[name] + return p, ok +} + +func (pe *portalEntries) add(name string, p portalEntry) { + if name == "" { + pe.unnamed.portalEntry = p + pe.unnamed.set = true + return + } + if pe.m == nil { + pe.m = make(map[string]portalEntry) + } + pe.m[name] = p +} + +func (pe *portalEntries) del(name string) (portalEntry, bool) { + if name == "" { + if !pe.unnamed.set { + return portalEntry{}, false } + p := pe.unnamed.portalEntry + pe.unnamed.portalEntry = portalEntry{} + pe.unnamed.set = false + return p, true } - for name, p := range ns.portals { - bp, ok := to.portals[name] - // If the prepared statement didn't exist before (including if a statement - // with the same name existed, but it was different), close it. - if !ok || bp.PreparedPortal != p.PreparedPortal { - p.close(ctx) + p, ok := pe.m[name] + if !ok { + return portalEntry{}, false + } + delete(pe.m, name) + return p, true +} + +func (pe *portalEntries) copy() portalEntries { + cpy := portalEntries{} + cpy.unnamed = pe.unnamed + if len(pe.m) > 0 { + cpy.m = make(map[string]portalEntry, len(pe.m)) + for name, p := range pe.m { + cpy.m[name] = p } } - *ns = to.copy() + return cpy +} + +type prepStmtNamespace struct { + prepStmts prepStmtEntries + portals portalEntries } func (ns *prepStmtNamespace) copy() prepStmtNamespace { var cpy prepStmtNamespace - cpy.prepStmts = make(map[string]prepStmtEntry) - for name, psEntry := range ns.prepStmts { - cpy.prepStmts[name] = psEntry.copy() + cpy.prepStmts = ns.prepStmts.copy() + cpy.portals = ns.portals.copy() + return cpy +} + +// clear clears all prepared statements and portals from ns. It closes any +// prepared statement or portal that isn't also in dontClose. +func (ns *prepStmtNamespace) clear(ctx context.Context, dontClose prepStmtNamespace) { + // If the unnamed prepared statement isn't the same, close it. + if ns.prepStmts.unnamed.set { + if ns.prepStmts.unnamed.PreparedStatement != dontClose.prepStmts.unnamed.PreparedStatement { + ns.prepStmts.unnamed.close(ctx) + } + } + // Close each other prepared statement from ns that differs. + for name, ps := range ns.prepStmts.m { + notPS, ok := dontClose.prepStmts.m[name] + if !ok || ps.PreparedStatement != notPS.PreparedStatement { + ps.close(ctx) + } } - cpy.portals = make(map[string]portalEntry) - for name, p := range ns.portals { - cpy.portals[name] = p + // If the unnamed portal isn't the same, close it. + if ns.portals.unnamed.set { + if ns.portals.unnamed.PreparedPortal != dontClose.portals.unnamed.PreparedPortal { + ns.portals.unnamed.close(ctx) + } } - return cpy + // Close each other prepared statement from ns that differs. + for name, p := range ns.portals.m { + notP, ok := dontClose.portals.m[name] + if !ok || p.PreparedPortal != notP.PreparedPortal { + p.close(ctx) + } + } + *ns = prepStmtNamespace{} +} + +// checkpointedPrepStmtNamespace is a prepared statement namespace that is +// capable of taking a snapshot and restoring to that snapshot. +// checkpointedPrepStmtNamespace's zero value can be used directly. +type checkpointedPrepStmtNamespace struct { + // checkpoint is the checkpointed prepStmtNamespace. It is treated as + // immutable and is only modified when replaced by workspace. + checkpoint prepStmtNamespace + // workspace is a prepStmtNamespace that is used for modifications. When + // in use, it replaces checkpoint when the namespace is checkpointed and + // it is discarded when the namespace is rewind. + workspace struct { + prepStmtNamespace + inUse bool + } +} + +// snapshot captures a snapshot of the prepared statement namespace. Future +// calls to rewind will return the namespace to this state. +func (cns *checkpointedPrepStmtNamespace) snapshot(ctx context.Context) { + if !cns.workspace.inUse { + return + } + // Clear the checkpoint and replace with the workspace. + cns.checkpoint.clear(ctx, cns.workspace.prepStmtNamespace) + cns.checkpoint = cns.workspace.prepStmtNamespace + cns.workspace.prepStmtNamespace = prepStmtNamespace{} + cns.workspace.inUse = false +} + +// Rewind reverts the namespace to the state of the last snapshot. +func (cns *checkpointedPrepStmtNamespace) rewind(ctx context.Context) { + if !cns.workspace.inUse { + return + } + // Clear the workspace. + cns.workspace.clear(ctx, cns.checkpoint) + cns.workspace.prepStmtNamespace = prepStmtNamespace{} + cns.workspace.inUse = false +} + +// clear clears all prepared statements in the namespace. +func (cns *checkpointedPrepStmtNamespace) clear(ctx context.Context) { + // Reconcile the namespaces and clear. + cns.snapshot(ctx) + cns.checkpoint.clear(ctx, prepStmtNamespace{}) +} + +func (cns *checkpointedPrepStmtNamespace) ns(mut bool) *prepStmtNamespace { + if cns.workspace.inUse { + return &cns.workspace.prepStmtNamespace + } + if mut { + cns.workspace.prepStmtNamespace = cns.checkpoint.copy() + cns.workspace.inUse = true + return &cns.workspace.prepStmtNamespace + } + return &cns.checkpoint +} + +func (cns *checkpointedPrepStmtNamespace) getPrepStmt(name string) (prepStmtEntry, bool) { + return cns.ns(false).prepStmts.get(name) +} + +func (cns *checkpointedPrepStmtNamespace) getMutablePrepStmt(name string) (prepStmtEntry, bool) { + return cns.ns(true).prepStmts.get(name) +} + +func (cns *checkpointedPrepStmtNamespace) addPrepStmt(name string, p prepStmtEntry) { + cns.ns(true).prepStmts.add(name, p) } -// commitPrepStmtNamespace deallocates everything in -// prepStmtsNamespaceAtTxnRewindPos that's not part of prepStmtsNamespace. -func (ex *connExecutor) commitPrepStmtNamespace(ctx context.Context) { - ex.extraTxnState.prepStmtsNamespaceAtTxnRewindPos.resetTo( - ctx, &ex.prepStmtsNamespace) +func (cns *checkpointedPrepStmtNamespace) delPrepStmt(ctx context.Context, name string) { + ps, ok := cns.ns(true).prepStmts.del(name) + if !ok { + return + } + ps2, ok := cns.checkpoint.prepStmts.get(name) + if !ok || ps.PreparedStatement != ps2.PreparedStatement { + ps.close(ctx) + } + for portalName := range ps.portals { + cns.delPortal(ctx, portalName) + } +} + +func (cns *checkpointedPrepStmtNamespace) getPortal(name string) (portalEntry, bool) { + return cns.ns(false).portals.get(name) } -// commitPrepStmtNamespace deallocates everything in prepStmtsNamespace that's -// not part of prepStmtsNamespaceAtTxnRewindPos. -func (ex *connExecutor) rewindPrepStmtNamespace(ctx context.Context) { - ex.prepStmtsNamespace.resetTo( - ctx, &ex.extraTxnState.prepStmtsNamespaceAtTxnRewindPos) +func (cns *checkpointedPrepStmtNamespace) addPortal(name string, p portalEntry) { + cns.ns(true).portals.add(name, p) +} + +func (cns *checkpointedPrepStmtNamespace) delPortal(ctx context.Context, name string) { + p, ok := cns.ns(true).portals.del(name) + if !ok { + return + } + p2, ok := cns.checkpoint.portals.get(name) + if !ok || p.PreparedPortal != p2.PreparedPortal { + p.close(ctx) + } + if ps, ok := cns.getMutablePrepStmt(p.psName); ok { + delete(ps.portals, name) + } } // addPreparedStmt creates a new PreparedStatement with the provided name using @@ -118,7 +331,7 @@ func (ex *connExecutor) rewindPrepStmtNamespace(ctx context.Context) { func (ex *connExecutor) addPreparedStmt( ctx context.Context, name string, stmt Statement, placeholderHints tree.PlaceholderTypes, ) (*PreparedStatement, error) { - if _, ok := ex.prepStmtsNamespace.prepStmts[name]; ok { + if _, ok := ex.prepStmtsNamespace.getPrepStmt(name); ok { panic(fmt.Sprintf("prepared statement already exists: %q", name)) } @@ -131,10 +344,10 @@ func (ex *connExecutor) addPreparedStmt( if err := prepared.memAcc.Grow(ctx, int64(len(name))); err != nil { return nil, err } - ex.prepStmtsNamespace.prepStmts[name] = prepStmtEntry{ + ex.prepStmtsNamespace.addPrepStmt(name, prepStmtEntry{ PreparedStatement: prepared, portals: make(map[string]struct{}), - } + }) return prepared, nil } @@ -150,7 +363,7 @@ func (ex *connExecutor) addPortal( qargs tree.QueryArguments, outFormats []pgwirebase.FormatCode, ) error { - if _, ok := ex.prepStmtsNamespace.portals[portalName]; ok { + if _, ok := ex.prepStmtsNamespace.getPortal(portalName); ok { panic(fmt.Sprintf("portal already exists: %q", portalName)) } @@ -159,43 +372,16 @@ func (ex *connExecutor) addPortal( return err } - ex.prepStmtsNamespace.portals[portalName] = portalEntry{ + ex.prepStmtsNamespace.addPortal(portalName, portalEntry{ PreparedPortal: portal, psName: psName, - } - ex.prepStmtsNamespace.prepStmts[psName].portals[portalName] = struct{}{} - return nil -} - -func (ex *connExecutor) deletePreparedStmt(ctx context.Context, name string) { - psEntry, ok := ex.prepStmtsNamespace.prepStmts[name] - if !ok { - return - } - // If the prepared statement only exists in prepStmtsNamespace, it's up to us - // to close it. - baseP, inBase := ex.extraTxnState.prepStmtsNamespaceAtTxnRewindPos.prepStmts[name] - if !inBase || (baseP.PreparedStatement != psEntry.PreparedStatement) { - psEntry.close(ctx) - } - for portalName := range psEntry.portals { - ex.deletePortal(ctx, portalName) - } - delete(ex.prepStmtsNamespace.prepStmts, name) -} - -func (ex *connExecutor) deletePortal(ctx context.Context, name string) { - portalEntry, ok := ex.prepStmtsNamespace.portals[name] + }) + ps, ok := ex.prepStmtsNamespace.getMutablePrepStmt(psName) if !ok { - return - } - // If the portal only exists in prepStmtsNamespace, it's up to us to close it. - baseP, inBase := ex.extraTxnState.prepStmtsNamespaceAtTxnRewindPos.portals[name] - if !inBase || (baseP.PreparedPortal != portalEntry.PreparedPortal) { - portalEntry.close(ctx) + panic(fmt.Sprintf("prepared statement does not exist: %q", psName)) } - delete(ex.prepStmtsNamespace.portals, name) - delete(ex.prepStmtsNamespace.prepStmts[portalEntry.psName].portals, name) + ps.portals[portalName] = struct{}{} + return nil } func (ex *connExecutor) execPrepare( @@ -206,9 +392,9 @@ func (ex *connExecutor) execPrepare( return eventNonRetriableErr{IsCommit: fsm.False}, eventNonRetriableErrPayload{err: err} } - // The anonymous statement can be overwritter. + // The anonymous statement can be overwritten. if parseCmd.Name != "" { - if _, ok := ex.prepStmtsNamespace.prepStmts[parseCmd.Name]; ok { + if _, ok := ex.prepStmtsNamespace.getPrepStmt(parseCmd.Name); ok { err := pgerror.NewErrorf( pgerror.CodeDuplicatePreparedStatementError, "prepared statement %q already exists", parseCmd.Name, @@ -217,7 +403,7 @@ func (ex *connExecutor) execPrepare( } } else { // Deallocate the unnamed statement, if it exists. - ex.deletePreparedStmt(ctx, "") + ex.prepStmtsNamespace.delPrepStmt(ctx, "") } ps, err := ex.addPreparedStmt( @@ -406,16 +592,16 @@ func (ex *connExecutor) execBind( portalName := bindCmd.PortalName // The unnamed portal can be freely overwritten. if portalName != "" { - if _, ok := ex.prepStmtsNamespace.portals[portalName]; ok { + if _, ok := ex.prepStmtsNamespace.getPortal(portalName); ok { return retErr(pgerror.NewErrorf( pgerror.CodeDuplicateCursorError, "portal %q already exists", portalName)) } } else { // Deallocate the unnamed portal, if it exists. - ex.deletePortal(ctx, "") + ex.prepStmtsNamespace.delPortal(ctx, "") } - ps, ok := ex.prepStmtsNamespace.prepStmts[bindCmd.PreparedStatementName] + ps, ok := ex.prepStmtsNamespace.getPrepStmt(bindCmd.PreparedStatementName) if !ok { return retErr(pgerror.NewErrorf( pgerror.CodeInvalidSQLStatementNameError, @@ -527,7 +713,7 @@ func (ex *connExecutor) execDelPrepStmt( ) (fsm.Event, fsm.EventPayload) { switch delCmd.Type { case pgwirebase.PrepareStatement: - _, ok := ex.prepStmtsNamespace.prepStmts[delCmd.Name] + _, ok := ex.prepStmtsNamespace.getPrepStmt(delCmd.Name) if !ok { // The spec says "It is not an error to issue Close against a nonexistent // statement or portal name". See @@ -535,13 +721,13 @@ func (ex *connExecutor) execDelPrepStmt( break } - ex.deletePreparedStmt(ctx, delCmd.Name) + ex.prepStmtsNamespace.delPrepStmt(ctx, delCmd.Name) case pgwirebase.PreparePortal: - _, ok := ex.prepStmtsNamespace.portals[delCmd.Name] + _, ok := ex.prepStmtsNamespace.getPortal(delCmd.Name) if !ok { break } - ex.deletePortal(ctx, delCmd.Name) + ex.prepStmtsNamespace.delPortal(ctx, delCmd.Name) default: panic(fmt.Sprintf("unknown del type: %s", delCmd.Type)) } @@ -558,7 +744,7 @@ func (ex *connExecutor) execDescribe( switch descCmd.Type { case pgwirebase.PrepareStatement: - ps, ok := ex.prepStmtsNamespace.prepStmts[descCmd.Name] + ps, ok := ex.prepStmtsNamespace.getPrepStmt(descCmd.Name) if !ok { return retErr(pgerror.NewErrorf( pgerror.CodeInvalidSQLStatementNameError, @@ -573,7 +759,7 @@ func (ex *connExecutor) execDescribe( res.SetPrepStmtOutput(ctx, ps.Columns) } case pgwirebase.PreparePortal: - portal, ok := ex.prepStmtsNamespace.portals[descCmd.Name] + portal, ok := ex.prepStmtsNamespace.getPortal(descCmd.Name) if !ok { return retErr(pgerror.NewErrorf( pgerror.CodeInvalidCursorNameError, "unknown portal %q", descCmd.Name))