{"record":{"id":"ab0e39945d505a8e","repo":"mattermost-community/focalboard","slug":"searchboardsforuserinteam-unable-to-replace-unions","errorCode":null,"errorMessage":"SearchBoardsForUserInTeam unable to replace unionSQL placeholders: %w","messagePattern":"SearchBoardsForUserInTeam unable to replace unionSQL placeholders: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/services/store/mattermostauthlayer/mattermostauthlayer.go","lineNumber":882,"sourceCode":"\n\tunionQ := openBoardsQ.\n\t\tPrefix(\"(\").\n\t\tSuffix(\") UNION (\"+memberBoardsSQL, memberBoardsArgs...).\n\t\tSuffix(\") UNION (\"+channelMemberBoardsSQL+\")\", channelMemberBoardsArgs...)\n\n\tunionSQL, unionArgs, err := unionQ.ToSql()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SearchBoardsForUserInTeam error getting unionSQL: %w\", err)\n\t}\n\n\t// if we're using postgres or sqlite, we need to replace the\n\t// question mark placeholder with the numbered dollar one, now\n\t// that the full query is built\n\tif s.dbType == model.PostgresDBType || s.dbType == model.SqliteDBType {\n\t\tvar rErr error\n\t\tunionSQL, rErr = sq.Dollar.ReplacePlaceholders(unionSQL)\n\t\tif rErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"SearchBoardsForUserInTeam unable to replace unionSQL placeholders: %w\", rErr)\n\t\t}\n\t}\n\n\trows, err := s.mmDB.Query(unionSQL, unionArgs...)\n\tif err != nil {\n\t\ts.logger.Error(`searchBoardsForUserInTeam ERROR`, mlog.Err(err))\n\t\treturn nil, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\treturn s.boardsFromRows(rows, false)\n}\n\nfunc (s *MattermostAuthLayer) boardsFromRows(rows *sql.Rows, removeDuplicates bool) ([]*model.Board, error) {\n\tboards := []*model.Board{}\n\tidMap := make(map[string]struct{})\n\n\tfor rows.Next() {","sourceCodeStart":864,"sourceCodeEnd":900,"githubUrl":"https://github.com/mattermost-community/focalboard/blob/a84bbb65e32edf972856b329417096ac413518e9/server/services/store/mattermostauthlayer/mattermostauthlayer.go#L864-L900","documentation":"In SearchBoardsForUserInTeam, sq.Dollar.ReplacePlaceholders failed converting '?' placeholders to '$N' for Postgres/SQLite on the final union SQL. Pure string-rewrite failure over the rendered query, indicating malformed rendered SQL.","triggerScenarios":"s.dbType is postgres or sqlite and the union SQL string can't be placeholder-rewritten — malformed '?' usage or broken SQL text from the union build.","commonSituations":"Literal '?' characters in injected SQL fragments; corrupted union SQL from build bugs; old squirrel versions; wrong dbType detection.","solutions":["Log unionSQL on failure and scan for non-placeholder '?' characters.","Escape literal '?' in any hand-written SQL fragments.","Upgrade the squirrel dependency.","Verify s.dbType detection so the rewrite branch only runs for postgres/sqlite."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"boards, err := store.GetBoardsForUserAndTeam(userID, teamID, term)\nif err != nil {\n\tif strings.Contains(err.Error(), \"unable to replace unionSQL placeholders\") {\n\t\tlogger.Error(\"$N placeholder rewrite failed; check literal '?' in SQL\", \"err\", err)\n\t\treturn nil, ErrInternalSearch\n\t}\n\treturn nil, err\n}","preventionTips":["Escape literal '?' in hand-written SQL fragments.","Run CI queries against postgres and sqlite dialects.","Keep squirrel current and dbType detection verified."],"tags":["sql","squirrel","postgres","placeholders"],"backgroundTag":"placeholder-rewrite-failed","analyzedSha":"a84bbb65e32edf972856b329417096ac413518e9","analyzedAt":"2026-08-30T09:22:20.720Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}