# frozen_string_literal: true

# Defines extension to ActiveRecord/AREL to use this library
module PostgreSQLCursor
  module ActiveRecord
    module Relation
      module CursorIterators

        # Public: Executes the query, returning each row as a hash
        # to the given block.
        #
        # options     - Hash to control
        #   fraction: 0.1..1.0    - The cursor_tuple_fraction (default 1.0)
        #   block_size: 1..n      - The number of rows to fetch per db block fetch
        #   while: value          - Exits loop when block does not return this value.
        #   until: value          - Exits loop when block returns this value.
        #   cursor_name: string   - Allows you to name your cursor.
        #
        # Example:
        #   Post.where(user_id:123).each_row { |hash| Post.process(hash) }
        #   Post.each_row.map {|r| r["id"].to_i }
        #
        # Returns the number of rows yielded to the block
        def each_row(options={}, &block)
          options = {:connection => self.connection}.merge(options)
          cursor  = PostgreSQLCursor::Cursor.new(to_unprepared_sql, options)
          return cursor.each_row(&block) if block_given?
          cursor
        end
        alias :each_hash :each_row

        # Public: Like each_row, but returns an instantiated model object to the block
        #
        # Paramaters: same as each_row
        #
        # Example:
        #   Post.where(user_id:123).each_instance { |post| post.process }
        #   Post.where(user_id:123).each_instance.map { |post| post.process }
        #
        # Returns the number of rows yielded to the block
        def each_instance(options={}, &block)
          options = {:connection => self.connection}.merge(options)
          cursor = PostgreSQLCursor::Cursor.new(to_unprepared_sql, options)
          return cursor.each_instance(self, &block) if block_given?
          cursor.iterate_type(self)
        end

        # Public: Executes the query, yielding each batch of up to block_size
        # rows where each row is a hash to the given block.
        #
        # Parameters: same as each_row
        #
        # Example:
        #   Post.where(user_id:123).each_row_batch do |batch|
        #     Post.process_batch(batch)
        #   end
        #   Post.each_row_batch.map { |batch| Post.transform_batch(batch) }
        #
        # Returns the number of rows yielded to the block
        def each_row_batch(options={}, &block)
          options = {:connection => self.connection}.merge(options)
          cursor  = PostgreSQLCursor::Cursor.new(to_unprepared_sql, options)
          return cursor.each_row_batch(&block) if block_given?
          cursor.iterate_batched
        end
        alias :each_hash_batch :each_row_batch

        # Public: Like each_row, but yields an array of instantiated model
        # objects to the block
        #
        # Parameters: same as each_row
        #
        # Example:
        #   Post.where(user_id:123).each_instance_batch do |batch|
        #     Post.process_batch(batch)
        #   end
        #   Post.where(user_id:123).each_instance_batch.map do |batch|
        #     Post.transform_batch(batch)
        #   end
        #
        # Returns the number of rows yielded to the block
        def each_instance_batch(options={}, &block)
          options = {:connection => self.connection}.merge(options)
          cursor = PostgreSQLCursor::Cursor.new(to_unprepared_sql, options)
          return cursor.each_instance_batch(self, &block) if block_given?
          cursor.iterate_type(self).iterate_batched
        end

        # Plucks the column names from the rows, and return them in an array
        def pluck_rows(*cols)
          options = cols.last.is_a?(Hash) ? cols.pop : {}
          options[:connection] = self.connection
          self.each_row(options).pluck(*cols)
        end
        alias :pluck_row :pluck_rows

        # Plucks the column names from the instances, and return them in an array
        def pluck_instances(*cols)
          options = cols.last.is_a?(Hash) ? cols.pop : {}
          options[:connection] = self.connection
          self.each_instance(options).pluck(*cols)
        end
        alias :pluck_instance :pluck_instances

        private

        # Returns sql string like #to_sql, but with bind parameters interpolated.
        # ActiveRecord sets up query as prepared statements with bind variables.
        # Cursors will prepare statements regardless.
        def to_unprepared_sql
          if self.connection.respond_to?(:unprepared_statement)
            self.connection.unprepared_statement do
              to_sql
            end
          else
            to_sql
          end
        end

      end
    end
  end
end