{"record":{"id":"6ef583c545de71b8","repo":"mattermost-community/focalboard","slug":"searchboardsforuser-unable-to-replace-unionsql-pla","errorCode":null,"errorMessage":"SearchBoardsForUser unable to replace unionSQL placeholders: %w","messagePattern":"SearchBoardsForUser unable to replace unionSQL placeholders: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/services/store/mattermostauthlayer/mattermostauthlayer.go","lineNumber":784,"sourceCode":"\t} else if includePublicBoards {\n\t\tunionQ = unionQ.\n\t\t\tPrefix(\"(\").\n\t\t\tSuffix(\") UNION (\"+teamMembersSQL+\")\", teamMembersArgs...)\n\t}\n\n\tunionSQL, unionArgs, err := unionQ.ToSql()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"SearchBoardsForUser 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(\"SearchBoardsForUser 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(`searchBoardsForUser 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\n// searchBoardsForUserInTeam returns all boards that match with the\n// term that are either private and which the user is a member of, or\n// they're open, regardless of the user membership.\n// Search is case-insensitive.\nfunc (s *MattermostAuthLayer) SearchBoardsForUserInTeam(teamID, term, userID string) ([]*model.Board, error) {","sourceCodeStart":766,"sourceCodeEnd":802,"githubUrl":"https://github.com/mattermost-community/focalboard/blob/a84bbb65e32edf972856b329417096ac413518e9/server/services/store/mattermostauthlayer/mattermostauthlayer.go#L766-L802","documentation":"After building the union SQL with '?' placeholders, the code rewrites placeholders to Postgres/SQLite '$N' form using sq.Dollar.ReplacePlaceholders(unionSQL). This error means that string rewriting failed — rare, since it operates purely on the rendered SQL string.","triggerScenarios":"s.dbType is postgres or sqlite and ReplacePlaceholders fails on the union SQL string — essentially only when the rendered SQL is malformed (e.g. a stray '?' or broken string from a bug in earlier builder steps).","commonSituations":"Custom SQL fragments injected via Suffix containing '?' characters that aren't placeholders; corrupted SQL from earlier build steps; very old squirrel versions with escaping bugs.","solutions":["Log unionSQL when this fires and look for literal '?' characters that are not placeholders (e.g. inside string literals or json operators).","Escape or remove literal '?' from injected SQL fragments.","Update the squirrel dependency to a current version.","If only MySQL is intended, verify s.dbType is detected correctly so Postgres/SQLite branch isn't taken wrongly."],"exampleFix":"// before\nunionSQL, rErr = sq.Dollar.ReplacePlaceholders(unionSQL)\nif rErr != nil {\n\treturn nil, fmt.Errorf(\"SearchBoardsForUser unable to replace unionSQL placeholders: %w\", rErr)\n}\n// after\n// ensure injected fragments don't contain raw '?'; log for diagnosis\ns.logger.Error(\"placeholder rewrite failed\", mlog.String(\"sql\", unionSQL), mlog.Err(rErr))\nunionSQL, rErr = sq.Dollar.ReplacePlaceholders(unionSQL)\nif rErr != nil {\n\treturn nil, fmt.Errorf(\"SearchBoardsForUser unable to replace unionSQL placeholders: %w\", rErr)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"boards, err := store.SearchBoardsForUser(userID, term)\nif err != nil {\n\tif strings.Contains(err.Error(), \"unable to replace unionSQL placeholders\") {\n\t\tlogger.Error(\"placeholder rewrite failed; check for literal '?' in SQL fragments\", \"err\", err)\n\t\treturn nil, ErrInternalSearch\n\t}\n\treturn nil, err\n}","preventionTips":["Avoid literal '?' characters in injected SQL fragments.","Test queries against both MySQL and Postgres/SQLite dialects.","Keep squirrel up to date."],"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"}