believer 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -0
- data/lib/believer.rb +42 -0
- data/lib/believer/base.rb +45 -0
- data/lib/believer/batch.rb +37 -0
- data/lib/believer/batch_delete.rb +27 -0
- data/lib/believer/callbacks.rb +276 -0
- data/lib/believer/columns.rb +166 -0
- data/lib/believer/command.rb +44 -0
- data/lib/believer/connection.rb +22 -0
- data/lib/believer/ddl.rb +36 -0
- data/lib/believer/delete.rb +11 -0
- data/lib/believer/empty_result.rb +32 -0
- data/lib/believer/environment.rb +51 -0
- data/lib/believer/environment/rails_environment.rb +19 -0
- data/lib/believer/insert.rb +18 -0
- data/lib/believer/limit.rb +20 -0
- data/lib/believer/model_schema.rb +176 -0
- data/lib/believer/observer.rb +36 -0
- data/lib/believer/order_by.rb +22 -0
- data/lib/believer/owner.rb +48 -0
- data/lib/believer/persistence.rb +31 -0
- data/lib/believer/primary_key.rb +5 -0
- data/lib/believer/query.rb +140 -0
- data/lib/believer/querying.rb +6 -0
- data/lib/believer/scoped_command.rb +19 -0
- data/lib/believer/scoping.rb +16 -0
- data/lib/believer/test/rspec/test_run_life_cycle.rb +51 -0
- data/lib/believer/values.rb +40 -0
- data/lib/believer/version.rb +5 -0
- data/lib/believer/where_clause.rb +40 -0
- data/spec/believer/base_spec.rb +6 -0
- data/spec/believer/callback_spec.rb +49 -0
- data/spec/believer/delete_spec.rb +12 -0
- data/spec/believer/insert_spec.rb +9 -0
- data/spec/believer/limit_spec.rb +7 -0
- data/spec/believer/order_by_spec.rb +14 -0
- data/spec/believer/query_spec.rb +31 -0
- data/spec/believer/time_series_spec.rb +18 -0
- data/spec/believer/where_spec.rb +28 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/setup_database.rb +20 -0
- data/spec/support/test_classes.rb +60 -0
- metadata +180 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
module Believer
|
2
|
+
class OrderBy
|
3
|
+
attr_reader :field, :dir
|
4
|
+
|
5
|
+
def initialize(field, dir = :asc)
|
6
|
+
raise "Invalid field: #{field}" unless field.is_a?(Symbol) || field.is_a?(String)
|
7
|
+
raise "Direction must be one of (:asc|:desc): #{dir}" unless dir == :asc || dir == :desc
|
8
|
+
@field = field
|
9
|
+
@dir = dir
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_cql
|
13
|
+
"ORDER BY #{@field} #{@dir.present? ? @dir.to_s.upcase : 'ASC'}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def inverse
|
17
|
+
OrderBy.new(@field, @dir == :asc ? :desc : :asc)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Believer
|
2
|
+
module Owner
|
3
|
+
extend ::ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def cql_record_relations
|
7
|
+
@cql_record_relations ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def has_some(name, opts = {})
|
11
|
+
defaults = {
|
12
|
+
}
|
13
|
+
options = opts.merge(defaults)
|
14
|
+
|
15
|
+
relation_class = options[:class]
|
16
|
+
if relation_class.nil?
|
17
|
+
relation_class = Kernel.const_get(name.to_s.camelize[0, (name.to_s.size - 1)])
|
18
|
+
elsif relation_class.is_a?(String)
|
19
|
+
relation_class = Kernel.const_get(relation_type)
|
20
|
+
end
|
21
|
+
|
22
|
+
foreign_key = options[:foreign_key]
|
23
|
+
foreign_key = "#{self.name.underscore}_id" if foreign_key.nil?
|
24
|
+
foreign_key = foreign_key.to_sym
|
25
|
+
|
26
|
+
key_attribute = options[:key_attribute]
|
27
|
+
if key_attribute.nil?
|
28
|
+
key_attribute = foreign_key
|
29
|
+
end
|
30
|
+
|
31
|
+
cql_record_relations[name] = options
|
32
|
+
|
33
|
+
self.redefine_method(name) do
|
34
|
+
q = relation_class.where(foreign_key => self.send(key_attribute))
|
35
|
+
if options[:filter]
|
36
|
+
q = options[:filter].call(self, q)
|
37
|
+
return EmptyResult.new unless q
|
38
|
+
end
|
39
|
+
q
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Believer
|
2
|
+
|
3
|
+
module Persistence
|
4
|
+
|
5
|
+
extend ::ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
def create(attributes = nil, &block)
|
10
|
+
if attributes.is_a?(Array)
|
11
|
+
attributes.collect { |attr| create(attr, &block) }
|
12
|
+
else
|
13
|
+
object = new(attributes, &block)
|
14
|
+
object.save
|
15
|
+
object
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
# Saves the model.
|
22
|
+
def save
|
23
|
+
Insert.new(:record_class => self.class, :values => self).execute
|
24
|
+
end
|
25
|
+
|
26
|
+
def destroy
|
27
|
+
Delete.new(:record_class => self.class).where(key_values).execute
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module Believer
|
2
|
+
class Query < ScopedCommand
|
3
|
+
|
4
|
+
attr_accessor :record_class, :selects, :order_by, :limit_to
|
5
|
+
|
6
|
+
delegate *(Enumerable.instance_methods(false).map {|enum_method| enum_method.to_sym}), :to => :to_a
|
7
|
+
|
8
|
+
def initialize(attrs)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def query_attributes
|
13
|
+
attrs = super
|
14
|
+
attrs.merge(:order_by => @order_by, :selects => @selects, :limit_to => @limit_to)
|
15
|
+
end
|
16
|
+
|
17
|
+
def select(*fields)
|
18
|
+
q = clone
|
19
|
+
q.selects ||= []
|
20
|
+
q.selects += fields
|
21
|
+
q.selects.flatten!
|
22
|
+
#puts "New selects: #{q.selects}, added: #{fields}"
|
23
|
+
q
|
24
|
+
end
|
25
|
+
|
26
|
+
def order(field, order = :asc)
|
27
|
+
q = clone
|
28
|
+
q.order_by = OrderBy.new(field, order)
|
29
|
+
q
|
30
|
+
end
|
31
|
+
|
32
|
+
def limit(l)
|
33
|
+
q = clone
|
34
|
+
q.limit_to = Limit.new(l)
|
35
|
+
q
|
36
|
+
end
|
37
|
+
|
38
|
+
def limit_to=(l)
|
39
|
+
if l.nil?
|
40
|
+
@limit_to = nil
|
41
|
+
else
|
42
|
+
@limit_to = Limit.new(l.to_i)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_cql
|
47
|
+
cql = "SELECT "
|
48
|
+
if @selects && @selects.any?
|
49
|
+
cql << "#{@selects.join(', ')}"
|
50
|
+
else
|
51
|
+
cql << @record_class.columns.keys.join(',')
|
52
|
+
#cql << "*"
|
53
|
+
end
|
54
|
+
|
55
|
+
cql << " FROM #{@record_class.table_name}"
|
56
|
+
cql << " WHERE #{@wheres.map { |wc| "#{wc.to_cql}" }.join(' AND ')}" if @wheres && @wheres.any?
|
57
|
+
cql << " #{@order_by.to_cql}" unless @order_by.nil?
|
58
|
+
cql << " #{@limit_to.to_cql}" unless @limit_to.nil?
|
59
|
+
cql
|
60
|
+
end
|
61
|
+
|
62
|
+
def destroy_all
|
63
|
+
objects = to_a
|
64
|
+
objects.each do |obj|
|
65
|
+
obj.destroy
|
66
|
+
end
|
67
|
+
objects.size
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete_all(options = {})
|
71
|
+
cnt = count
|
72
|
+
chunk_size = options[:delete_batch_chunk_size] || (self.limit_to.nil? ? nil : self.limit_to.size) || cnt
|
73
|
+
key_cols = self.record_class.primary_key_columns
|
74
|
+
deleted_count = 0
|
75
|
+
while deleted_count < cnt
|
76
|
+
batch = Batch.new(:record_class => @record_class)
|
77
|
+
rows_to_delete = clone.select(key_cols).limit(chunk_size).execute
|
78
|
+
rows_to_delete.each do |row_to_delete|
|
79
|
+
batch << Delete.new(:record_class => self.record_class).where(row_to_delete)
|
80
|
+
end
|
81
|
+
batch.execute
|
82
|
+
deleted_count += batch.commands.size
|
83
|
+
end
|
84
|
+
deleted_count
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_a
|
88
|
+
unless @result_rows
|
89
|
+
result = execute
|
90
|
+
@result_rows = []
|
91
|
+
start = Time.now
|
92
|
+
result.each do |row|
|
93
|
+
@result_rows << @record_class.instantiate_from_result_rows(row)
|
94
|
+
end
|
95
|
+
puts "Took #{sprintf "%.3f", (Time.now - start)*1000.0} ms to deserialize #{@result_rows.size} object(s)"
|
96
|
+
end
|
97
|
+
@result_rows
|
98
|
+
end
|
99
|
+
|
100
|
+
def size
|
101
|
+
to_a.size
|
102
|
+
end
|
103
|
+
|
104
|
+
def count
|
105
|
+
count_q = clone
|
106
|
+
count_q.selects = ['COUNT(*)']
|
107
|
+
result = count_q.execute
|
108
|
+
|
109
|
+
cnt = -1
|
110
|
+
result.each do |row|
|
111
|
+
cnt = row['count']
|
112
|
+
end
|
113
|
+
cnt
|
114
|
+
end
|
115
|
+
|
116
|
+
def each
|
117
|
+
to_a.each do |r|
|
118
|
+
yield r
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def [](index)
|
123
|
+
to_a[index]
|
124
|
+
end
|
125
|
+
|
126
|
+
def first
|
127
|
+
return @result_rows.first unless @result_rows.nil?
|
128
|
+
clone.limit(1)[0]
|
129
|
+
end
|
130
|
+
|
131
|
+
def last
|
132
|
+
return @result_rows.last unless @result_rows.nil?
|
133
|
+
raise "Cannot retrieve last of no order column is set" if @order_by.nil?
|
134
|
+
lq = clone.limit(1)
|
135
|
+
lq.order_by = @order_by.inverse
|
136
|
+
lq[0]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Believer
|
2
|
+
class ScopedCommand < Command
|
3
|
+
|
4
|
+
attr_accessor :wheres
|
5
|
+
|
6
|
+
def query_attributes
|
7
|
+
attrs = super
|
8
|
+
attrs.merge(:wheres => @wheres)
|
9
|
+
end
|
10
|
+
|
11
|
+
def where(*args)
|
12
|
+
q = clone
|
13
|
+
q.wheres ||= []
|
14
|
+
q.wheres << WhereClause.new(*args)
|
15
|
+
q
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Believer
|
2
|
+
module Test
|
3
|
+
module RSpec
|
4
|
+
# Controls the life cycle for all objects created in a test
|
5
|
+
module TestRunLifeCycle
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
Believer::Base.observers = Destructor
|
10
|
+
|
11
|
+
after(:each) do
|
12
|
+
Destructor.instance.cleanup
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def saved_models
|
17
|
+
@saved_models ||= []
|
18
|
+
end
|
19
|
+
|
20
|
+
def after_save(model)
|
21
|
+
saved_models << model
|
22
|
+
end
|
23
|
+
|
24
|
+
# Detroys all CqlRecord::Base instances created
|
25
|
+
class Destructor < Believer::Observer
|
26
|
+
observe Believer::Base
|
27
|
+
|
28
|
+
def cleanup
|
29
|
+
saved_models.each do |model|
|
30
|
+
begin
|
31
|
+
model.destroy
|
32
|
+
rescue Exception => e
|
33
|
+
puts "Could not destroy model #{model}: #{e}\n#{e.backtrace.join("\n")}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def saved_models
|
39
|
+
@saved_models ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
def after_save(model)
|
43
|
+
saved_models << model
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Believer
|
2
|
+
|
3
|
+
module Values
|
4
|
+
|
5
|
+
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S%z'
|
6
|
+
|
7
|
+
def to_cql_literal(value)
|
8
|
+
return 'NULL' if value.nil?
|
9
|
+
return "'#{value}'" if value.is_a?(String)
|
10
|
+
return "#{value}" if value.is_a?(Numeric)
|
11
|
+
return "'#{value.strftime(TIMESTAMP_FORMAT)}'" if value.is_a?(Time) || value.is_a?(DateTime)
|
12
|
+
#return "#{value.to_i * 1000}" if value.is_a?(Time) || value.is_a?(DateTime)
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def convert_to_integer(v)
|
17
|
+
return v.to_i unless v.nil?
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def convert_to_float(v)
|
22
|
+
return v.to_f unless v.nil?
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def convert_to_boolean(v)
|
27
|
+
return v.to_bool if v.respond_to?(:to_bool)
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def convert_to_time(v)
|
32
|
+
return nil if v.nil?
|
33
|
+
return v if v.is_a?(Time)
|
34
|
+
return Time.parse(v) if v.is_a?(String)
|
35
|
+
Time.at(v.to_i)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Believer
|
2
|
+
|
3
|
+
class WhereClause
|
4
|
+
include Values
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
if args.any?
|
8
|
+
if args[0].is_a?(Hash)
|
9
|
+
@value_map = args[0]
|
10
|
+
else
|
11
|
+
@where_string = args[0]
|
12
|
+
@bindings = args.slice(1, args.length - 1)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_cql
|
18
|
+
if @value_map && @value_map.any?
|
19
|
+
return @value_map.keys.map {|k| create_expression(k, @value_map[k])}.join(' AND ')
|
20
|
+
end
|
21
|
+
binding_index = 0
|
22
|
+
ws = @where_string.gsub(/\?/) { |match|
|
23
|
+
rep_val = to_cql_literal(@bindings[binding_index])
|
24
|
+
binding_index += 1
|
25
|
+
rep_val
|
26
|
+
}
|
27
|
+
ws
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def create_expression(key, value)
|
32
|
+
if value.is_a?(Enumerable)
|
33
|
+
values = value.map {|v| to_cql_literal(v)}.join(',')
|
34
|
+
return "#{key} IN (#{values})"
|
35
|
+
end
|
36
|
+
"#{key} = #{to_cql_literal(value)}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|