mattermost-community/focalboard · warning
getBlockHistoryNewestChildren unable to replace sql placehol
Error message
getBlockHistoryNewestChildren unable to replace sql placeholders: %w
What it means
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.
Source
Thrown at server/services/store/sqlstore/blocks.go:722
if opts.PerPage > 0 {
// limit+1 to detect if more records available
query = query.Limit(uint64(opts.PerPage + 1))
}
sql, args, err := query.ToSql()
if err != nil {
return nil, false, fmt.Errorf("getBlockHistoryNewestChildren unable to generate sql: %w", err)
}
// if we're using postgres or sqlite, we need to replace the
// question mark placeholder with the numbered dollar one, now
// that the full query is built
if s.dbType == model.PostgresDBType || s.dbType == model.SqliteDBType {
var rErr error
sql, rErr = sq.Dollar.ReplacePlaceholders(sql)
if rErr != nil {
return nil, false, fmt.Errorf("getBlockHistoryNewestChildren unable to replace sql placeholders: %w", rErr)
}
}
rows, err := db.Query(sql, args...)
if err != nil {
s.logger.Error(`getBlockHistoryNewestChildren ERROR`, mlog.Err(err))
return nil, false, err
}
defer s.CloseRows(rows)
blocks, err := s.blocksFromRows(rows)
if err != nil {
return nil, false, err
}
hasMore := false
if opts.PerPage > 0 && len(blocks) > opts.PerPage {
blocks = blocks[:opts.PerPage]View on GitHub (pinned to a84bbb65e3)
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
Example fix
// before
query = query.Where("bh.id IN (" + idsRaw + ")") // raw injection breaks placeholders
// after
query = query.Where(sq.Eq{"bh.id": ids}) // squirrel-safe Defensive patterns
Strategy: fallback
Try / catch
hist, hasMore, err := store.GetBlockHistoryNewestChildren(blockID, opts)
if err != nil && strings.Contains(err.Error(), "unable to replace sql placeholders") {
log.Printf("placeholder conversion failed: %v", err)
return nil, err // or fall back to MySQL-compatible store
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- SearchBoardsForUser unable to replace unionSQL placeholders:
- SearchBoardsForUserInTeam unable to replace unionSQL placeho
- SearchBoardsForUser error getting teamMembersSQL: %w
- SearchBoardsForUser error getting channelMembersSQL: %w
- SearchBoardsForUser error getting unionSQL: %w
AI-assisted analysis of mattermost-community/focalboard@a84bbb65e3 (2026-08-30).
Data as JSON: /api/errors/3a7a8825f3c60e95.
Report an issue: GitHub.