Class: ActiveRecord::HyperBase

Inherits:
Base
  • Object
show all
Defined in:
lib/hyper_record.rb

Constant Summary collapse

BILLION =
1_000_000_000.0
ROW_KEY_OFFSET =
0
COLUMN_FAMILY_OFFSET =
1
COLUMN_QUALIFIER_OFFSET =
2
VALUE_OFFSET =
3
TIMESTAMP_OFFSET =
4

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

hypertable_connection, inherited, require_hypertable_thrift_client

Constructor Details

#initialize(attrs = {}) ⇒ HyperBase

Returns a new instance of HyperBase.



31
32
33
34
# File 'lib/hyper_record.rb', line 31

def initialize(attrs={})
  super(attrs)
  self.ROW = attrs[:ROW] if attrs[:ROW] && attrs[:ROW]
end

Class Attribute Details

.qualified_columnsObject

qualified_column :misc, :qualifiers => [:name, :url]



472
473
474
# File 'lib/hyper_record.rb', line 472

def qualified_columns
  @qualified_columns
end

Class Method Details

.abstract_class?Boolean

Returns:

  • (Boolean)


184
185
186
# File 'lib/hyper_record.rb', line 184

def abstract_class?
  self == ActiveRecord::HyperBase
end

.close_mutator(mutator, flush = true) ⇒ Object



492
493
494
# File 'lib/hyper_record.rb', line 492

def close_mutator(mutator, flush=true)
  self.connection.close_mutator(mutator, flush)
end

.close_scanner(scanner) ⇒ Object



505
506
507
# File 'lib/hyper_record.rb', line 505

def close_scanner(scanner)
  self.connection.close_scanner(scanner)
end

.column_families_without_row_keyObject



456
457
458
# File 'lib/hyper_record.rb', line 456

def column_families_without_row_key
  columns[1,columns.length]
end

.columnsObject

Returns array of column objects for table associated with this class.



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/hyper_record.rb', line 429

def columns
  unless @columns
    @columns = connection.columns(table_name, "#{name} Columns")
    @qualified_columns ||= []
    @qualified_columns.each{|qc|
      # Remove the column family from the column list
      @columns = @columns.reject{|c| c.name == qc[:column_name].to_s}
      connection.remove_column_from_name_map(table_name, qc[:column_name].to_s)

      # Add the new qualified column family to the column list
      @columns << connection.add_qualified_column(table_name, qc[:column_name].to_s, qc[:qualifiers])
    }
    @columns.each {|column| column.primary = column.name == primary_key}
  end
  @columns
end

.convert_cells_to_hashes(cells) ⇒ Object



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/hyper_record.rb', line 320

def convert_cells_to_hashes(cells)
  rows = []
  current_row = {}

  # Cells are guaranteed to come back in row key order, so assemble
  # a row by iterating over each cell and checking to see if the row key
  # has changed.  If it has, then the row is complete and needs to be
  # instantiated before processing the next cell.
  cells.each_with_index do |cell, i|
    current_row['ROW'] = cell[ROW_KEY_OFFSET]

    family = connection.rubify_column_name(cell[COLUMN_FAMILY_OFFSET])

    if !cell[COLUMN_QUALIFIER_OFFSET].blank?
      current_row[family] ||= {}
      current_row[family][cell[COLUMN_QUALIFIER_OFFSET]] = cell[VALUE_OFFSET]
    else
      current_row[family] = cell[VALUE_OFFSET]
    end

    # Instantiate the row if we've processed all cells for the row
    next_index = i + 1

    # Check to see if next cell has different row key or if we're at
    # the end of the cell stream.
    if (cells[next_index] and cells[next_index][ROW_KEY_OFFSET] != current_row['ROW']) or next_index >= cells.length
      # Make sure that the resulting object has attributes for all
      # columns - even ones that aren't in the response (due to limited
      # select)
      for col in column_families_without_row_key
        if !current_row.has_key?(col.name)
          if col.is_a?(ActiveRecord::ConnectionAdapters::QualifiedColumn)
            current_row[col.name] = {}
          else
            current_row[col.name] = nil
          end
        end
      end

      rows << current_row
      current_row = {}
    end
  end

  rows
end

.convert_cells_to_instantiated_rows(cells) ⇒ Object



367
368
369
# File 'lib/hyper_record.rb', line 367

def convert_cells_to_instantiated_rows(cells)
  convert_cells_to_hashes(cells).map{|row| instantiate(row)}
end

.delete(*ids) ⇒ Object



199
200
201
# File 'lib/hyper_record.rb', line 199

def delete(*ids)
  self.connection.delete_rows(table_name, ids.flatten)
end

.drop_tableObject



417
418
419
# File 'lib/hyper_record.rb', line 417

def drop_table
  connection.drop_table(table_name) if table_exists?
end

.each_cell(scanner, &block) ⇒ Object

Iterator methods



514
515
516
# File 'lib/hyper_record.rb', line 514

def each_cell(scanner, &block)
  self.connection.each_cell(scanner, &block)
end

.each_cell_as_arrays(scanner, &block) ⇒ Object



518
519
520
# File 'lib/hyper_record.rb', line 518

def each_cell_as_arrays(scanner, &block)
  self.connection.each_cell_as_arrays(scanner, &block)
end

.each_row(scanner, &block) ⇒ Object



522
523
524
# File 'lib/hyper_record.rb', line 522

def each_row(scanner, &block)
  self.connection.each_row(scanner, &block)
end

.each_row_as_arrays(scanner, &block) ⇒ Object



526
527
528
# File 'lib/hyper_record.rb', line 526

def each_row_as_arrays(scanner, &block)
  self.connection.each_row_as_arrays(scanner, &block)
end

.exists?(id_or_conditions) ⇒ Boolean

Returns:

  • (Boolean)


188
189
190
191
192
193
194
195
196
197
# File 'lib/hyper_record.rb', line 188

def exists?(id_or_conditions)
  case id_or_conditions
    when Fixnum, String
      !find(:first, :row_keys => [id_or_conditions]).nil?
    when Hash
      !find(:first, :conditions => id_or_conditions).nil?
    else
      raise "only Fixnum, String and Hash arguments supported"
  end
end

.find(*args) ⇒ Object



203
204
205
206
207
208
209
210
211
# File 'lib/hyper_record.rb', line 203

def find(*args)
  options = args.extract_options!

  case args.first
    when :first then find_initial(options)
    when :all   then find_by_options(options)
    else             find_from_ids(args, options)
  end
end

.find_by_hql(hql) ⇒ Object Also known as: find_by_sql



371
372
373
374
375
376
377
# File 'lib/hyper_record.rb', line 371

def find_by_hql(hql)
  hql_result = connection.execute(hql)
  cells_in_native_array_format = hql_result.cells.map do |c| 
    connection.cell_native_array(c.row_key, c.column_family, c.column_qualifier, c.value)
  end
  convert_cells_to_instantiated_rows(cells_in_native_array_format)
end

.find_by_options(options) ⇒ Object



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/hyper_record.rb', line 289

def find_by_options(options)
  set_default_options(options)

  # If requested, instead of retrieving the matching cells from
  # Hypertable, simply return a scan spec that matches the finder
  # options.
  if options[:scan_spec]
    return connection.convert_options_to_scan_spec(options)
  end

  cells = connection.execute_with_options(options)

  if HyperBase.log_calls
    msg = [ "Select" ]
    for key in options.keys
      case key
        when :columns
          msg << "  columns\t#{options[:columns].map{|c| c.name}.join(',')}"
        else
          msg << "  #{key}\t#{options[key]}"
      end
    end
    msg << "Returned #{cell_count} cells"

    RAILS_DEFAULT_LOGGER.info(msg)
    # puts msg
  end

  convert_cells_to_instantiated_rows(cells)
end

.find_each_row(*args) ⇒ Object

Returns each row matching the finder options as a HyperRecord object. Each object is yielded to the caller so that large queries can be processed one object at a time without pulling the entire result set into memory.

Page.find_each_row(:all) do |page|

...

end



233
234
235
236
237
# File 'lib/hyper_record.rb', line 233

def find_each_row(*args)
  find_each_row_as_arrays(*args) do |row|
    yield convert_cells_to_instantiated_rows(row).first
  end
end

.find_each_row_as_arrays(*args) ⇒ Object

Returns each row matching the finder options as an array of cells in native array format. Each row is yielded to the caller so that large queries can be processed one row at a time without pulling the entire result set into memory.

Page.find_each_row(:all) do |page_as_array_of_cells|

...

end



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/hyper_record.rb', line 247

def find_each_row_as_arrays(*args)
  scan_spec = find_to_scan_spec(*args)
  with_scanner(scan_spec) do |scanner|
    row = []
    current_row_key = nil

    each_cell_as_arrays(scanner) do |cell|
      current_row_key ||= cell[ROW_KEY_OFFSET]

      if cell[ROW_KEY_OFFSET] == current_row_key
        row << cell
      else
        yield row
        row = [cell]
        current_row_key = cell[ROW_KEY_OFFSET]
      end
    end 

    yield row unless row.empty?
  end
end

.find_from_ids(ids, options) ⇒ Object



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/hyper_record.rb', line 380

def find_from_ids(ids, options)
  expects_array = ids.first.kind_of?(Array)
  return ids.first if expects_array && ids.first.empty?
  ids = ids.flatten.compact.uniq

  case ids.size
    when 0
      raise RecordNotFound, "Couldn't find #{name} without an ID"
    when 1
      result = find_one(ids.first, options)
      expects_array ? [ result ] : result
    else
      find_some(ids, options)
  end
end

.find_initial(options) ⇒ Object



269
270
271
272
273
274
275
276
277
# File 'lib/hyper_record.rb', line 269

def find_initial(options)
  options.update(:limit => 1)

  if options[:scan_spec]
    find_by_options(options)
  else
    find_by_options(options).first
  end
end

.find_one(id, options) ⇒ Object



396
397
398
399
400
401
402
403
404
405
406
# File 'lib/hyper_record.rb', line 396

def find_one(id, options)
  return nil if id.blank?

  options[:row_keys] = [id.to_s]

  if result = find_initial(options)
    result
  else
    raise ::ActiveRecord::RecordNotFound, "Couldn't find #{name} with ID=#{id}"
  end
end

.find_some(ids, options) ⇒ Object



408
409
410
411
# File 'lib/hyper_record.rb', line 408

def find_some(ids, options)
  options[:row_keys] = [ids.map{|i| i.to_s}]
  find_by_options(options)
end

.find_to_scan_spec(*args) ⇒ Object



213
214
215
216
217
218
# File 'lib/hyper_record.rb', line 213

def find_to_scan_spec(*args)
  options = args.extract_options!
  options[:scan_spec] = true
  args << options
  find(*args)
end

.find_with_scanner(*args, &block) ⇒ Object



220
221
222
223
# File 'lib/hyper_record.rb', line 220

def find_with_scanner(*args, &block)
  scan_spec = find_to_scan_spec(*args)
  with_scanner(scan_spec, &block)
end

.flush_mutator(mutator) ⇒ Object



496
497
498
# File 'lib/hyper_record.rb', line 496

def flush_mutator(mutator)
  self.connection.flush_mutator(mutator)
end

.open_mutatorObject

Return an open mutator on this table.



488
489
490
# File 'lib/hyper_record.rb', line 488

def open_mutator
  self.connection.open_mutator(table_name)
end

.open_scanner(scan_spec) ⇒ Object

Scanner methods



501
502
503
# File 'lib/hyper_record.rb', line 501

def open_scanner(scan_spec)
  self.connection.open_scanner(self.table_name, scan_spec)
end

.primary_keyObject

Returns the primary key field for a table. In Hypertable, a single row key exists for each row. The row key is referred to as ROW in HQL, so we’ll refer to it the same way here.



424
425
426
# File 'lib/hyper_record.rb', line 424

def primary_key
  "ROW"
end

.qualified?(column_name) ⇒ Boolean

Returns:

  • (Boolean)


446
447
448
# File 'lib/hyper_record.rb', line 446

def qualified?(column_name)
  @qualified_columns.map{|c| c[:column_name]}.include?(column_name.to_sym)
end

.qualified_column(*attrs) ⇒ Object



473
474
475
476
477
478
479
480
481
482
483
# File 'lib/hyper_record.rb', line 473

def qualified_column(*attrs)
  @qualified_columns ||= []
  name = attrs.shift

  qualifiers = attrs.shift
  qualifiers = qualifiers.symbolize_keys[:qualifiers] if qualifiers
  @qualified_columns << {
    :column_name => name,
    :qualifiers => qualifiers || []
  }
end

.qualified_column_names_without_row_keyObject



460
461
462
463
464
465
466
467
468
469
# File 'lib/hyper_record.rb', line 460

def qualified_column_names_without_row_key
  cols = column_families_without_row_key.map{|c| c.name}
  for qc in @qualified_columns
    cols.delete(qc[:column_name].to_s)
    for qualifier in qc[:qualifiers]
      cols << "#{qc[:column_name]}:#{qualifier}"
    end
  end
  cols
end

.quoted_column_names(attributes = attributes_with_quotes) ⇒ Object



450
451
452
453
454
# File 'lib/hyper_record.rb', line 450

def quoted_column_names(attributes=attributes_with_quotes)
  attributes.keys.collect do |column_name|
    self.class.connection.quote_column_name_for_table(column_name, table_name)
  end
end

.set_default_options(options) ⇒ Object



279
280
281
282
283
284
285
286
287
# File 'lib/hyper_record.rb', line 279

def set_default_options(options)
  options[:table_name] ||= table_name
  options[:columns] ||= columns

  # Don't request the ROW key explicitly, it always comes back
  options[:select] ||= qualified_column_names_without_row_key.map{|c| 
    connection.hypertable_column_name(c, table_name)
  }
end

.table_exists?(name = table_name) ⇒ Boolean

Returns:

  • (Boolean)


413
414
415
# File 'lib/hyper_record.rb', line 413

def table_exists?(name=table_name)
  connection.tables.include?(name)
end

.with_scanner(scan_spec, &block) ⇒ Object



509
510
511
# File 'lib/hyper_record.rb', line 509

def with_scanner(scan_spec, &block)
  self.connection.with_scanner(self.table_name, scan_spec, &block)
end

.with_thrift_clientObject



530
531
532
# File 'lib/hyper_record.rb', line 530

def with_thrift_client
  self.connection.with_thrift_client
end

Instance Method Details

#attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true) ⇒ Object

Returns a copy of the attributes hash where all the values have been safely quoted for insertion. Translated qualified columns from a Hash value in Ruby to a flat list of attributes.

>
"ROW" => "page_1",
"name" => "name",
"url" => "http://www.icanhascheezburger.com"



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/hyper_record.rb', line 111

def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true)
  quoted = attributes.inject({}) do |quoted, (name, value)|
    if column = column_for_attribute(name)
      if column.is_a?(ConnectionAdapters::QualifiedColumn) and value.is_a?(Hash)
        value.keys.each{|k|
          quoted[self.class.connection.qualified_column_name(column.name, k)] = quote_value(value[k], column)
        }
      else
        quoted[name] = quote_value(value, column) unless !include_primary_key && column.primary
      end
    end
    quoted
  end
  include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
end

#create(mutator = nil) ⇒ Object



43
44
45
46
47
48
# File 'lib/hyper_record.rb', line 43

def create(mutator=nil)
  write_quoted_attributes(attributes_with_quotes(false, false), 
    self.class.table_name, mutator)
  @new_record = false
  self.attributes[self.class.primary_key]
end

#create_or_update_with_mutator(mutator) ⇒ Object

Raises:

  • (ReadOnlyRecord)


58
59
60
61
62
# File 'lib/hyper_record.rb', line 58

def create_or_update_with_mutator(mutator)
  raise ReadOnlyRecord if readonly?
  result = new_record? ? create(mutator) : update(mutator)
  result != false
end

#decrement(attribute, by = 1) ⇒ Object



94
95
96
# File 'lib/hyper_record.rb', line 94

def decrement(attribute, by=1)
  increment(attribute, -by)
end

#decrement!(attribute, by = 1) ⇒ Object



98
99
100
# File 'lib/hyper_record.rb', line 98

def decrement!(attribute, by=1)
  increment!(attribute, -by)
end

#delete_cells(cells, table = self.class.table_name) ⇒ Object

Delete an array of cells from Hypertable cells is an array of cell keys [[“row”, “column”], …]



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/hyper_record.rb', line 163

def delete_cells(cells, table=self.class.table_name)
  if HyperBase.log_calls
    msg = [
      "Deleting #{cells.length} cells from #{table} table",
      cells.map{|c| [ c[0], c[1] ].compact.join("\t")}
    ].join("\n")
    RAILS_DEFAULT_LOGGER.info(msg)
    # puts msg
  end

  connection.delete_cells(table, cells)
end

#delete_rows(row_keys, table = self.class.table_name) ⇒ Object

Delete an array of rows from Hypertable rows is an array of row keys [“row1”, “row2”, …]



178
179
180
# File 'lib/hyper_record.rb', line 178

def delete_rows(row_keys, table=self.class.table_name)
  connection.delete_rows(table, cells)
end

#destroyObject



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/hyper_record.rb', line 64

def destroy
  # check for associations and delete association cells as necessary
  for reflection_key in self.class.reflections.keys
    case self.class.reflections[reflection_key].macro
      when :has_and_belongs_to_many
        # remove all the association cells from the associated objects
        cells_to_delete = []

        for row_key in self.send(self.class.reflections[reflection_key].association_foreign_key).keys
          cells_to_delete << connection.cell_native_array(row_key, self.class.reflections[reflection_key].primary_key_name, self.ROW)
        end

        self.delete_cells(cells_to_delete, self.class.reflections[reflection_key].klass.table_name)
    end
  end

  self.class.connection.delete_rows(self.class.table_name, [self.ROW])
end

#increment(attribute, by = 1) ⇒ Object



83
84
85
86
87
# File 'lib/hyper_record.rb', line 83

def increment(attribute, by=1)
  self[attribute] = self[attribute].to_i
  self[attribute] += by
  self
end

#increment!(attribute, by = 1) ⇒ Object



89
90
91
92
# File 'lib/hyper_record.rb', line 89

def increment!(attribute, by=1)
  increment(attribute, by)
  self.save
end

#quoted_attributes_to_cells(quoted_attrs, table = self.class.table_name) ⇒ Object

Translates the output of attributes_with_quotes into an array of cells suitable for writing into Hypertable (through the write_cells method). Data format is native array format for cells. [

["row_key", "column_family", "column_qualifier", "value"],

]



133
134
135
136
137
138
139
140
141
# File 'lib/hyper_record.rb', line 133

def quoted_attributes_to_cells(quoted_attrs, table=self.class.table_name)
  cells = []
  pk = self.attributes[self.class.primary_key]
  quoted_attrs.keys.each{|key|
    name, qualifier = connection.hypertable_column_name(key, table).split(':', 2)
    cells << connection.cell_native_array(pk, name, qualifier, quoted_attrs[key])
  }
  cells
end

#save_with_mutator(mutator) ⇒ Object



50
51
52
# File 'lib/hyper_record.rb', line 50

def save_with_mutator(mutator)
  create_or_update_with_mutator(mutator)
end

#save_with_mutator!(mutator) ⇒ Object



54
55
56
# File 'lib/hyper_record.rb', line 54

def save_with_mutator!(mutator)
  create_or_update_with_mutator(mutator) || raise(RecordNotSaved)
end

#update(mutator = nil) ⇒ Object

Instance Methods



37
38
39
40
41
# File 'lib/hyper_record.rb', line 37

def update(mutator=nil)
  write_quoted_attributes(attributes_with_quotes(false, false),
    self.class.table_name, mutator)
  true
end

#write_cells(cells, table = self.class.table_name, mutator = nil) ⇒ Object

Write an array of cells to Hypertable



148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/hyper_record.rb', line 148

def write_cells(cells, table=self.class.table_name, mutator=nil)
  if HyperBase.log_calls
    msg = [
      "Writing #{cells.length} cells to #{table} table",
      cells.map{|c| [c[0], c[1], c[2], c[3].to_s.first(20)].compact.join("\t")}
    ].join("\n")
    RAILS_DEFAULT_LOGGER.info(msg)
    # puts msg
  end

  connection.write_cells(table, cells, mutator)
end

#write_quoted_attributes(quoted_attrs, table = self.class.table_name, mutator = nil) ⇒ Object



143
144
145
# File 'lib/hyper_record.rb', line 143

def write_quoted_attributes(quoted_attrs, table=self.class.table_name, mutator=nil)
  write_cells(quoted_attributes_to_cells(quoted_attrs, table), table, mutator)
end