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

  1. Log the generated sql string to see what placeholder pattern broke the replacement
  2. Verify no raw SQL is concatenated into the query outside squirrel's PlaceholderFormat handling
  3. Upgrade/align squirrel dependency versions if the failure appeared after a dependency bump
  4. 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

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


AI-assisted analysis of mattermost-community/focalboard@a84bbb65e3 (2026-08-30). Data as JSON: /api/errors/3a7a8825f3c60e95. Report an issue: GitHub.