Class: OccamsRecord::Cursor

Inherits:
Object
  • Object
show all
Defined in:
lib/occams-record/cursor.rb

Overview

An interface to database cursors. Supported databases:

* PostgreSQL

Constant Summary collapse

SCROLL =
{
  true => "SCROLL",
  false => "NO SCROLL",
  nil => "",
}.freeze
HOLD =
{
  true => "WITH HOLD",
  false => "WITHOUT HOLD",
  nil => "",
}.freeze
DIRECTIONS =
{
  next: "NEXT",
  prior: "PRIOR",
  first: "FIRST",
  last: "LAST",
  absolute: "ABSOLUTE",
  relative: "RELATIVE",
  forward: "FORWARD",
  backward: "BACKWARD",
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(conn, sql, name: nil, scroll: nil, hold: nil, use: nil, query_logger: nil, eager_loaders: nil) ⇒ Cursor

Initializes a new Cursor. NOTE all operations must be performed within a block passed to #open.

While you CAN manually initialize a cursor, it’s more common to get one via OccamsRecord::Query#cursor or OccamsRecord::RawQuery#cursor.



61
62
63
64
65
66
67
68
# File 'lib/occams-record/cursor.rb', line 61

def initialize(conn, sql, name: nil, scroll: nil, hold: nil, use: nil, query_logger: nil, eager_loaders: nil)
  @conn, @sql = conn, sql
  @scroll = SCROLL.fetch(scroll)
  @hold = HOLD.fetch(hold)
  @use, @query_logger, @eager_loaders = use, query_logger, eager_loaders
  @name = name || "occams_cursor_#{SecureRandom.hex 4}"
  @quoted_name = conn.quote_table_name(@name)
end

Instance Attribute Details

#connActiveRecord::Connection (readonly)



36
37
38
# File 'lib/occams-record/cursor.rb', line 36

def conn
  @conn
end

#nameString (readonly)

Name of the cursor



40
41
42
# File 'lib/occams-record/cursor.rb', line 40

def name
  @name
end

#quoted_nameString (readonly)

Name of the cursor (safely SQL-escaped)



44
45
46
# File 'lib/occams-record/cursor.rb', line 44

def quoted_name
  @quoted_name
end

Instance Method Details

#each(batch_size: 1000) ⇒ Object

Loads records in batches of N and yields each record to a block (if given). If no block is given, returns an Enumerator.

cursor.open do |c|
  c.each do |record|
    ...
  end
end


104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/occams-record/cursor.rb', line 104

def each(batch_size: 1000)
  enum = Enumerator.new { |y|
    each_batch(batch_size: batch_size).each { |batch|
      batch.each { |record| y.yield record }
    }
  }
  if block_given?
    enum.each { |record| yield record }
  else
    enum
  end
end

#each_batch(batch_size: 1000) ⇒ Object

Loads records in batches of N and yields each batch to a block (if given). If no block is given, returns an Enumerator.

cursor.open do |c|
  c.each_batch do |batch|
    ...
  end
end


129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/occams-record/cursor.rb', line 129

def each_batch(batch_size: 1000)
  enum = Enumerator.new { |y|
    out_of_records = false
    until out_of_records
      results = fetch :forward, batch_size
      y.yield results if results.any?
      out_of_records = results.size < batch_size
    end
  }
  if block_given?
    enum.each { |batch| yield batch }
  else
    enum
  end
end

#execute(sql, binds = {}) ⇒ Object

Run an arbitrary command on the cursor. Use ‘binds’ to escape inputs.

cursor.open do |c|
  c.execute("MOVE FORWARD %{num} FOR #{c.quoted_name}", {num: 100})
  ...
end


205
206
207
208
209
# File 'lib/occams-record/cursor.rb', line 205

def execute(sql, binds = {})
  conn.execute(sql % binds.each_with_object({}) { |(key, val), acc|
    acc[key] = conn.quote(val)
  })
end

#fetch(direction, num = nil) ⇒ OccamsRecord::Results::Row

Fetch records in the given direction.

cursor.open do |c|
  c.fetch :forward, 100
  ...
end


157
158
159
160
161
162
163
# File 'lib/occams-record/cursor.rb', line 157

def fetch(direction, num = nil)
  query "FETCH %{dir} %{num} FROM %{name}".freeze % {
    dir: DIRECTIONS.fetch(direction),
    num: num&.to_i,
    name: @quoted_name,
  }
end

#move(direction, num = nil) ⇒ Object

Move the cursor the given direction.

cursor.open do |c|
  ...
  c.move :backward, 100
  ...
end


177
178
179
180
181
182
183
# File 'lib/occams-record/cursor.rb', line 177

def move(direction, num = nil)
  query "MOVE %{dir} %{num} FROM %{name}".freeze % {
    dir: DIRECTIONS.fetch(direction),
    num: num&.to_i,
    name: @quoted_name,
  }
end

#open(use_transaction: true) {|self| ... } ⇒ Object

Declares and opens the cursor, runs the given block (yielding self), and closes the cursor.

cursor.open do |c|
  c.fetch :forward, 100
end

Yields:

  • (self)

Raises:

  • (ArgumentError)


81
82
83
84
85
86
87
88
89
90
# File 'lib/occams-record/cursor.rb', line 81

def open(use_transaction: true)
  raise ArgumentError, "A block is required" unless block_given?
  if use_transaction and conn.open_transactions == 0
    conn.transaction {
      perform { yield self }
    }
  else
    perform { yield self }
  end
end

#query(sql, binds = {}) ⇒ Object

Run an arbitrary query on the cursor. Use ‘binds’ to escape inputs.

cursor.open do |c|
  c.query("FETCH FORWARD %{num} FOR #{c.quoted_name}", {num: 100})
  ...
end


193
194
195
# File 'lib/occams-record/cursor.rb', line 193

def query(sql, binds = {})
  ::OccamsRecord::RawQuery.new(sql, binds, use: @use, query_logger: @query_logger, eager_loaders: @eager_loaders, connection: conn).run
end