redis/redis-rb · error · ArgumentError

collect fields must be :all or a non-empty list

Error message

collect fields must be :all or a non-empty list

What it means

The COLLECT reducer in an FT.AGGREGATE GROUPBY pipeline must project fields: either all fields materialized by the pipeline (FIELDS *) via fields: :all, or an explicit list (FIELDS count name...). An empty list has no valid server-side rendering, so Reducers.collect raises ArgumentError when fields is anything that Array() turns into an empty list (lib/redis/commands/modules/search/aggregation.rb:353). fields: nil also raises, because Array(nil) is [].

Source

Thrown at lib/redis/commands/modules/search/aggregation.rb:353

        #   +LIMIT <offset> <count>+; with +sort_by+ this is a bounded top-N selection
        # @param alias_name [String, nil] the reducer output column name (+AS+)
        # @return [Reducers]
        # @raise [ArgumentError] if +fields+ is an empty list and not +:all+
        def self.collect(fields:, distinct: false, sort_by: nil, limit: nil, alias_name: nil)
          tokens = collect_fields_tokens(fields)
          tokens << "DISTINCT" if distinct
          tokens.concat(collect_sortby_tokens(sort_by))
          tokens.concat(["LIMIT", *limit]) if limit

          new("COLLECT", *tokens, alias_name: alias_name)
        end

        # Render the +FIELDS+ clause tokens for {collect}.
        def self.collect_fields_tokens(fields)
          return ["FIELDS", "*"] if fields == :all

          fields = Array(fields)
          raise ArgumentError, "collect fields must be :all or a non-empty list" if fields.empty?

          ["FIELDS", fields.size.to_s, *fields]
        end
        private_class_method :collect_fields_tokens

        # Render the optional +SORTBY+ clause tokens for {collect}. Each Asc/Desc wrapper emits a
        # name and its direction (2 tokens); a plain String emits just the name (ASC is implicit).
        def self.collect_sortby_tokens(sort_by)
          return [] if sort_by.nil? || sort_by.empty?

          sort_tokens = sort_by.flat_map do |field|
            field.is_a?(Asc) || field.is_a?(Desc) ? [field.name, field.order] : [field]
          end
          ["SORTBY", sort_tokens.size.to_s, *sort_tokens]
        end
        private_class_method :collect_sortby_tokens

        # @return [String] the reducer function name

View on GitHub (pinned to 2ba9010b91)

Solutions

  1. Pass fields: :all when every materialized field should be collected
  2. Guard dynamic lists: fall back to :all when the computed list is empty
  3. Validate the column config at load time, where the failure is easier to attribute than at query time

Example fix

// before
Reducers.collect(fields: picked, alias_name: "rows") # picked == []
// after
Reducers.collect(fields: picked.empty? ? :all : picked, alias_name: "rows")
Defensive patterns

Strategy: validation

Validate before calling

def collect_reducer(fields:, **opts)
  fields = :all if fields.nil? || (fields.respond_to?(:empty?) && fields.empty?)
  Reducers.collect(fields: fields, **opts)
end

Type guard

fields == :all || (fields.is_a?(Array) && !fields.empty?)

Prevention

When it happens

Trigger: Reducers.collect(fields: [], alias_name: "rows"); Reducers.collect(fields: nil); a computed field list that is empty at runtime, e.g. fields: columns & allowed where the intersection is empty.

Common situations: Config-driven aggregation column lists that can legitimately be empty; intending project-everything but passing [] instead of :all; refactoring a hardcoded field list into a variable that starts empty.

Related errors


AI-assisted analysis of redis/redis-rb@2ba9010b91 (2026-08-23). Data as JSON: /api/errors/edf312f295d0ecec. Report an issue: GitHub.