{"record":{"id":"3a7a8825f3c60e95","repo":"mattermost-community/focalboard","slug":"getblockhistorynewestchildren-unable-to-replace-sq","errorCode":null,"errorMessage":"getBlockHistoryNewestChildren unable to replace sql placeholders: %w","messagePattern":"getBlockHistoryNewestChildren unable to replace sql placeholders: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/services/store/sqlstore/blocks.go","lineNumber":722,"sourceCode":"\n\tif opts.PerPage > 0 {\n\t\t// limit+1 to detect if more records available\n\t\tquery = query.Limit(uint64(opts.PerPage + 1))\n\t}\n\n\tsql, args, err := query.ToSql()\n\tif err != nil {\n\t\treturn nil, false, fmt.Errorf(\"getBlockHistoryNewestChildren unable to generate sql: %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\tsql, rErr = sq.Dollar.ReplacePlaceholders(sql)\n\t\tif rErr != nil {\n\t\t\treturn nil, false, fmt.Errorf(\"getBlockHistoryNewestChildren unable to replace sql placeholders: %w\", rErr)\n\t\t}\n\t}\n\n\trows, err := db.Query(sql, args...)\n\tif err != nil {\n\t\ts.logger.Error(`getBlockHistoryNewestChildren ERROR`, mlog.Err(err))\n\t\treturn nil, false, err\n\t}\n\tdefer s.CloseRows(rows)\n\n\tblocks, err := s.blocksFromRows(rows)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\thasMore := false\n\tif opts.PerPage > 0 && len(blocks) > opts.PerPage {\n\t\tblocks = blocks[:opts.PerPage]","sourceCodeStart":704,"sourceCodeEnd":740,"githubUrl":"https://github.com/mattermost-community/focalboard/blob/a84bbb65e32edf972856b329417096ac413518e9/server/services/store/sqlstore/blocks.go#L704-L740","documentation":"After building the SQL, the store converts '?' placeholders to Postgres/SQLite '$n' format via sq.Dollar.ReplacePlaceholders; if that conversion fails, this error is returned. This should almost never occur in practice — it signals a corrupted or pathological SQL string from the query builder.","triggerScenarios":"Running getBlockHistoryNewestChildren against Postgres or SQLite where ReplacePlaceholders chokes on the generated SQL (e.g. unexpected placeholder content from user data embedded via earlier code changes).","commonSituations":"Custom forks that inject raw SQL fragments with non-standard placeholders; extremely long or malformed queries after deep pagination edge cases; upstream squirrel/dialect incompatibilities after version changes.","solutions":["Log the generated sql string to see what placeholder pattern broke the replacement","Verify no raw SQL is concatenated into the query outside squirrel's PlaceholderFormat handling","Upgrade/align squirrel dependency versions if the failure appeared after a dependency bump","Switch to the built-in store path (unmodified code) to confirm it's a local customization issue"],"exampleFix":"// before\nquery = query.Where(\"bh.id IN (\" + idsRaw + \")\") // raw injection breaks placeholders\n// after\nquery = query.Where(sq.Eq{\"bh.id\": ids}) // squirrel-safe","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"hist, hasMore, err := store.GetBlockHistoryNewestChildren(blockID, opts)\nif err != nil && strings.Contains(err.Error(), \"unable to replace sql placeholders\") {\n    log.Printf(\"placeholder conversion failed: %v\", err)\n    return nil, err // or fall back to MySQL-compatible store\n}","preventionTips":["Do not concatenate raw SQL strings into squirrel queries","Keep sq.Dollar replacement on the stock query-building path","Test against Postgres and SQLite when adding custom clauses","Upgrade squirrel/lannister deps together to avoid placeholder-format regressions"],"tags":["sql","postgres","sqlite","placeholders"],"backgroundTag":"sql-generation-failed","analyzedSha":"a84bbb65e32edf972856b329417096ac413518e9","analyzedAt":"2026-08-30T09:22:20.720Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}