datamapper 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +65 -0
- data/README +193 -1
- data/do_performance.rb +153 -0
- data/environment.rb +45 -0
- data/example.rb +119 -22
- data/lib/data_mapper.rb +36 -16
- data/lib/data_mapper/adapters/abstract_adapter.rb +8 -0
- data/lib/data_mapper/adapters/data_object_adapter.rb +360 -0
- data/lib/data_mapper/adapters/mysql_adapter.rb +30 -179
- data/lib/data_mapper/adapters/postgresql_adapter.rb +90 -199
- data/lib/data_mapper/adapters/sql/coersion.rb +32 -3
- data/lib/data_mapper/adapters/sql/commands/conditions.rb +97 -128
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +234 -231
- data/lib/data_mapper/adapters/sql/commands/loader.rb +99 -0
- data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +30 -0
- data/lib/data_mapper/adapters/sql/mappings/column.rb +68 -6
- data/lib/data_mapper/adapters/sql/mappings/schema.rb +6 -3
- data/lib/data_mapper/adapters/sql/mappings/table.rb +71 -42
- data/lib/data_mapper/adapters/sql/quoting.rb +8 -2
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +32 -201
- data/lib/data_mapper/associations.rb +21 -7
- data/lib/data_mapper/associations/belongs_to_association.rb +96 -80
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +158 -67
- data/lib/data_mapper/associations/has_many_association.rb +96 -78
- data/lib/data_mapper/associations/has_n_association.rb +64 -0
- data/lib/data_mapper/associations/has_one_association.rb +49 -79
- data/lib/data_mapper/associations/reference.rb +47 -0
- data/lib/data_mapper/base.rb +216 -50
- data/lib/data_mapper/callbacks.rb +71 -24
- data/lib/data_mapper/{session.rb → context.rb} +20 -8
- data/lib/data_mapper/database.rb +176 -45
- data/lib/data_mapper/embedded_value.rb +65 -0
- data/lib/data_mapper/identity_map.rb +12 -4
- data/lib/data_mapper/support/active_record_impersonation.rb +12 -8
- data/lib/data_mapper/support/enumerable.rb +8 -0
- data/lib/data_mapper/support/serialization.rb +13 -0
- data/lib/data_mapper/support/string.rb +1 -12
- data/lib/data_mapper/support/symbol.rb +3 -0
- data/lib/data_mapper/validations/unique_validator.rb +1 -2
- data/lib/data_mapper/validations/validation_helper.rb +18 -1
- data/performance.rb +109 -34
- data/plugins/can_has_sphinx/LICENSE +23 -0
- data/plugins/can_has_sphinx/README +4 -0
- data/plugins/can_has_sphinx/REVISION +1 -0
- data/plugins/can_has_sphinx/Rakefile +22 -0
- data/plugins/can_has_sphinx/init.rb +1 -0
- data/plugins/can_has_sphinx/install.rb +1 -0
- data/plugins/can_has_sphinx/lib/acts_as_sphinx.rb +123 -0
- data/plugins/can_has_sphinx/lib/sphinx.rb +460 -0
- data/plugins/can_has_sphinx/scripts/sphinx.sh +47 -0
- data/plugins/can_has_sphinx/tasks/acts_as_sphinx_tasks.rake +41 -0
- data/plugins/dataobjects/REVISION +1 -0
- data/plugins/dataobjects/Rakefile +7 -0
- data/plugins/dataobjects/do.rb +246 -0
- data/plugins/dataobjects/do_mysql.rb +179 -0
- data/plugins/dataobjects/do_postgres.rb +181 -0
- data/plugins/dataobjects/do_sqlite3.rb +153 -0
- data/plugins/dataobjects/spec/do_spec.rb +150 -0
- data/plugins/dataobjects/spec/spec_helper.rb +81 -0
- data/plugins/dataobjects/swig_mysql/do_mysql.bundle +0 -0
- data/plugins/dataobjects/swig_mysql/extconf.rb +33 -0
- data/plugins/dataobjects/swig_mysql/mysql_c.c +18800 -0
- data/plugins/dataobjects/swig_mysql/mysql_c.i +8 -0
- data/plugins/dataobjects/swig_mysql/mysql_supp.i +46 -0
- data/plugins/dataobjects/swig_postgres/Makefile +146 -0
- data/plugins/dataobjects/swig_postgres/extconf.rb +29 -0
- data/plugins/dataobjects/swig_postgres/postgres_c.bundle +0 -0
- data/plugins/dataobjects/swig_postgres/postgres_c.c +8185 -0
- data/plugins/dataobjects/swig_postgres/postgres_c.i +73 -0
- data/plugins/dataobjects/swig_sqlite/db +0 -0
- data/plugins/dataobjects/swig_sqlite/extconf.rb +9 -0
- data/plugins/dataobjects/swig_sqlite/sqlite3_c.c +4725 -0
- data/plugins/dataobjects/swig_sqlite/sqlite_c.i +168 -0
- data/rakefile.rb +45 -23
- data/spec/acts_as_tree_spec.rb +39 -0
- data/spec/associations_spec.rb +220 -0
- data/spec/attributes_spec.rb +15 -0
- data/spec/base_spec.rb +44 -0
- data/spec/callbacks_spec.rb +45 -0
- data/spec/can_has_sphinx.rb +6 -0
- data/spec/coersion_spec.rb +34 -0
- data/spec/conditions_spec.rb +49 -0
- data/spec/conversions_to_yaml_spec.rb +17 -0
- data/spec/count_command_spec.rb +11 -0
- data/spec/delete_command_spec.rb +1 -1
- data/spec/embedded_value_spec.rb +23 -0
- data/spec/fixtures/animals_exhibits.yaml +2 -0
- data/spec/fixtures/people.yaml +18 -1
- data/spec/{legacy.rb → legacy_spec.rb} +3 -3
- data/spec/load_command_spec.rb +157 -20
- data/spec/magic_columns_spec.rb +9 -0
- data/spec/mock_adapter.rb +20 -0
- data/spec/models/animal.rb +1 -1
- data/spec/models/animals_exhibit.rb +6 -0
- data/spec/models/exhibit.rb +2 -0
- data/spec/models/person.rb +26 -1
- data/spec/models/project.rb +19 -0
- data/spec/models/sales_person.rb +1 -0
- data/spec/models/section.rb +6 -0
- data/spec/models/zoo.rb +3 -1
- data/spec/query_spec.rb +9 -0
- data/spec/save_command_spec.rb +65 -1
- data/spec/schema_spec.rb +89 -0
- data/spec/single_table_inheritance_spec.rb +27 -0
- data/spec/spec_helper.rb +9 -55
- data/spec/{symbolic_operators.rb → symbolic_operators_spec.rb} +9 -5
- data/spec/{validates_confirmation_of.rb → validates_confirmation_of_spec.rb} +4 -3
- data/spec/{validates_format_of.rb → validates_format_of_spec.rb} +5 -4
- data/spec/{validates_length_of.rb → validates_length_of_spec.rb} +8 -7
- data/spec/{validates_uniqueness_of.rb → validates_uniqueness_of_spec.rb} +7 -10
- data/spec/{validations.rb → validations_spec.rb} +24 -6
- data/tasks/drivers.rb +20 -0
- data/tasks/fixtures.rb +42 -0
- metadata +181 -42
- data/lib/data_mapper/adapters/sql/commands/advanced_load_command.rb +0 -140
- data/lib/data_mapper/adapters/sql/commands/delete_command.rb +0 -113
- data/lib/data_mapper/adapters/sql/commands/save_command.rb +0 -141
- data/lib/data_mapper/adapters/sql/commands/table_exists_command.rb +0 -33
- data/lib/data_mapper/adapters/sql_adapter.rb +0 -163
- data/lib/data_mapper/associations/advanced_has_many_association.rb +0 -55
- data/lib/data_mapper/support/blank_slate.rb +0 -3
- data/lib/data_mapper/support/proc.rb +0 -69
- data/lib/data_mapper/support/struct.rb +0 -26
- data/lib/data_mapper/unit_of_work.rb +0 -38
- data/spec/basic_finder.rb +0 -67
- data/spec/belongs_to.rb +0 -47
- data/spec/has_and_belongs_to_many.rb +0 -25
- data/spec/has_many.rb +0 -34
- data/spec/new_record.rb +0 -24
- data/spec/sub_select.rb +0 -16
- data/spec/support/string_spec.rb +0 -7
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
require 'bigdecimal'
|
|
2
|
+
require 'bigdecimal/util'
|
|
3
|
+
|
|
1
4
|
module DataMapper
|
|
2
5
|
module Adapters
|
|
3
6
|
module Sql
|
|
@@ -8,8 +11,11 @@ module DataMapper
|
|
|
8
11
|
# it.
|
|
9
12
|
module Coersion
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
class CoersionError < StandardError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
TRUE_ALIASES = ['true'.freeze, 'TRUE'.freeze, '1'.freeze]
|
|
18
|
+
FALSE_ALIASES = [nil, '0'.freeze]
|
|
13
19
|
|
|
14
20
|
def self.included(base)
|
|
15
21
|
base.const_set('TRUE_ALIASES', TRUE_ALIASES.dup)
|
|
@@ -47,6 +53,18 @@ module DataMapper
|
|
|
47
53
|
nil
|
|
48
54
|
end
|
|
49
55
|
|
|
56
|
+
def type_cast_decimal(raw_value)
|
|
57
|
+
return nil if raw_value.blank?
|
|
58
|
+
raw_value.to_d
|
|
59
|
+
rescue ArgumentError
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def type_cast_float(raw_value)
|
|
64
|
+
return nil if raw_value.blank?
|
|
65
|
+
raw_value.to_f
|
|
66
|
+
end
|
|
67
|
+
|
|
50
68
|
def type_cast_datetime(raw_value)
|
|
51
69
|
return nil if raw_value.blank?
|
|
52
70
|
|
|
@@ -54,7 +72,18 @@ module DataMapper
|
|
|
54
72
|
when DateTime then raw_value
|
|
55
73
|
when Date then DateTime.new(raw_value)
|
|
56
74
|
when String then DateTime::parse(raw_value)
|
|
57
|
-
else "Can't type-cast #{raw_value.inspect} to a datetime"
|
|
75
|
+
else raise CoersionError.new("Can't type-cast #{raw_value.inspect} to a datetime")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def type_cast_date(raw_value)
|
|
80
|
+
return nil if raw_value.blank?
|
|
81
|
+
|
|
82
|
+
case raw_value
|
|
83
|
+
when Date then raw_value
|
|
84
|
+
when DateTime, Time then Date::civil(raw_value.year, raw_value.month, raw_value.day)
|
|
85
|
+
when String then Date::parse(raw_value)
|
|
86
|
+
else raise CoersionError.new("Can't type-cast #{raw_value.inspect} to a date")
|
|
58
87
|
end
|
|
59
88
|
end
|
|
60
89
|
|
|
@@ -5,154 +5,123 @@ module DataMapper
|
|
|
5
5
|
|
|
6
6
|
class Conditions
|
|
7
7
|
|
|
8
|
-
def initialize(adapter, loader)
|
|
8
|
+
def initialize(adapter, loader, conditions_hash)
|
|
9
9
|
@adapter, @loader = adapter, loader
|
|
10
|
-
@
|
|
10
|
+
@conditions = parse_conditions(conditions_hash)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
attr_reader :inner_error
|
|
16
|
-
|
|
17
|
-
def initialize(clause, inner_error = nil)
|
|
18
|
-
@clause = clause
|
|
19
|
-
@inner_error = inner_error
|
|
20
|
-
|
|
21
|
-
message = "Failed to normalize clause: #{clause.inspect}"
|
|
22
|
-
message << ", Error: #{inner_error.inspect}" unless inner_error.nil?
|
|
23
|
-
|
|
24
|
-
super(message)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def normalize(clause, collector)
|
|
30
|
-
case clause
|
|
31
|
-
when Hash then
|
|
32
|
-
clause.each_pair do |k,v|
|
|
33
|
-
if k.kind_of?(Symbol::Operator)
|
|
34
|
-
if k.type == :select
|
|
35
|
-
k.options[:class] ||= @loader.klass
|
|
36
|
-
|
|
37
|
-
k.options[:select] ||= if k.value.to_s == @adapter[k.options[:class]].default_foreign_key
|
|
38
|
-
@adapter[k.options[:class]].key.column_name
|
|
39
|
-
else
|
|
40
|
-
k.value
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
sub_select = @adapter.select_statement(k.options.merge(v))
|
|
44
|
-
normalize(["#{@adapter[@loader.klass][k.value.to_sym].to_sql} IN ?", sub_select], collector)
|
|
45
|
-
else
|
|
46
|
-
@has_id = true if k.value == :id
|
|
47
|
-
op = case k.type
|
|
48
|
-
when :gt then '>'
|
|
49
|
-
when :gte then '>='
|
|
50
|
-
when :lt then '<'
|
|
51
|
-
when :lte then '<='
|
|
52
|
-
when :not then v.nil? ? 'IS NOT' : (v.kind_of?(Array) ? 'NOT IN' : '<>')
|
|
53
|
-
when :eql then v.nil? ? 'IS' : (v.kind_of?(Array) ? 'IN' : '=')
|
|
54
|
-
when :like then 'LIKE'
|
|
55
|
-
when :in then 'IN'
|
|
56
|
-
else raise ArgumentError.new('Operator type not supported')
|
|
57
|
-
end
|
|
58
|
-
normalize(["#{@adapter[@loader.klass][k.value.to_sym].to_sql} #{op} ?", v], collector)
|
|
59
|
-
end
|
|
60
|
-
else
|
|
61
|
-
@has_id = true if k == :id
|
|
62
|
-
case v
|
|
63
|
-
when Array then
|
|
64
|
-
normalize(["#{@adapter[@loader.klass][k.to_sym].to_sql} IN ?", v], collector)
|
|
65
|
-
when LoadCommand then
|
|
66
|
-
normalize(["#{@adapter[@loader.klass][k.to_sym].to_sql} IN ?", v], collector)
|
|
67
|
-
else
|
|
68
|
-
normalize(["#{@adapter[@loader.klass][k.to_sym].to_sql} = ?", v], collector)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
when Array then
|
|
73
|
-
return collector if clause.empty?
|
|
74
|
-
@has_id = true if clause.first =~ /(^|\s|\`)id(\`|\s|\=|\<)/ && !clause[1].kind_of?(LoadCommand)
|
|
75
|
-
collector << escape(clause)
|
|
76
|
-
when String then
|
|
77
|
-
@has_id = true if clause =~ /(^|\s|\`)id(\`|\s|\=|\<)/
|
|
78
|
-
collector << clause
|
|
79
|
-
else raise NormalizationError.new(clause)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
return collector
|
|
13
|
+
def empty?
|
|
14
|
+
@conditions.empty?
|
|
83
15
|
end
|
|
84
16
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
case
|
|
17
|
+
def to_parameterized_sql
|
|
18
|
+
sql = []
|
|
19
|
+
parameters = []
|
|
20
|
+
|
|
21
|
+
@conditions.each do |condition|
|
|
22
|
+
case condition
|
|
23
|
+
when String then sql << condition
|
|
91
24
|
when Array then
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
'(' << conditions.shift.to_sql << ')'
|
|
25
|
+
sql << condition.shift
|
|
26
|
+
parameters += condition
|
|
95
27
|
else
|
|
96
|
-
|
|
28
|
+
raise "Unable to parse condition: #{condition.inspect}" if condition
|
|
97
29
|
end
|
|
98
30
|
end
|
|
31
|
+
|
|
32
|
+
parameters.unshift("(#{sql.join(') AND (')})")
|
|
99
33
|
end
|
|
100
|
-
|
|
101
|
-
def has_id?
|
|
102
|
-
normalized_conditions
|
|
103
|
-
@has_id
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def normalized_conditions
|
|
107
|
-
|
|
108
|
-
if @normalized_conditions.nil?
|
|
109
|
-
@normalized_conditions = []
|
|
110
|
-
|
|
111
|
-
normalize(implicits, @normalized_conditions)
|
|
112
34
|
|
|
113
|
-
|
|
114
|
-
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
class ConditionsError < StandardError
|
|
38
|
+
|
|
39
|
+
attr_reader :inner_error
|
|
40
|
+
|
|
41
|
+
def initialize(clause, value, inner_error)
|
|
42
|
+
@clause, @value, @inner_error = clause, value, inner_error
|
|
115
43
|
end
|
|
116
|
-
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
return @normalized_conditions
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def table
|
|
123
|
-
@table || (@table = @adapter[@loader.klass])
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def implicits
|
|
127
|
-
@implicits || @implicits = begin
|
|
128
44
|
|
|
129
|
-
|
|
45
|
+
def message
|
|
46
|
+
"Conditions (:clause => #{@clause.inspect}, :value => #{@value.inspect}) failed: #{@inner_error}"
|
|
47
|
+
end
|
|
130
48
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
49
|
+
def backtrace
|
|
50
|
+
@inner_error.backtrace
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def expression_to_sql(clause, value, collector)
|
|
56
|
+
qualify_columns = @loader.qualify_columns?
|
|
57
|
+
|
|
58
|
+
case clause
|
|
59
|
+
when Symbol::Operator then
|
|
60
|
+
operator = case clause.type
|
|
61
|
+
when :gt then '>'
|
|
62
|
+
when :gte then '>='
|
|
63
|
+
when :lt then '<'
|
|
64
|
+
when :lte then '<='
|
|
65
|
+
when :not then inequality_operator(value)
|
|
66
|
+
when :eql then equality_operator(value)
|
|
67
|
+
when :like then equality_operator(value, 'LIKE')
|
|
68
|
+
when :in then equality_operator(value)
|
|
69
|
+
else raise ArgumentError.new('Operator type not supported')
|
|
70
|
+
end
|
|
71
|
+
collector << ["#{primary_class_table[clause].to_sql(qualify_columns)} #{operator} ?", value]
|
|
72
|
+
when Symbol then
|
|
73
|
+
collector << ["#{primary_class_table[clause].to_sql(qualify_columns)} #{equality_operator(value)} ?", value]
|
|
74
|
+
when String then
|
|
75
|
+
collector << [clause, value]
|
|
76
|
+
when Mappings::Column then
|
|
77
|
+
collector << ["#{clause.to_sql(qualify_columns)} #{equality_operator(value)} ?", value]
|
|
78
|
+
else raise "CAN HAS CRASH? #{clause.inspect}"
|
|
135
79
|
end
|
|
80
|
+
rescue => e
|
|
81
|
+
raise ConditionsError.new(clause, value, e)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def equality_operator(value, default = '=')
|
|
85
|
+
case value
|
|
86
|
+
when NilClass then 'IS'
|
|
87
|
+
when Array then 'IN'
|
|
88
|
+
else default
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def inequality_operator(value, default = '<>')
|
|
93
|
+
case value
|
|
94
|
+
when NilClass then 'IS NOT'
|
|
95
|
+
when Array then 'NOT IN'
|
|
96
|
+
else default
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def parse_conditions(conditions_hash)
|
|
101
|
+
collection = []
|
|
136
102
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
103
|
+
case x = conditions_hash.delete(:conditions)
|
|
104
|
+
when Array then
|
|
105
|
+
clause = x.shift
|
|
106
|
+
expression_to_sql(clause, x, collection)
|
|
107
|
+
when Hash then
|
|
108
|
+
x.each_pair do |key,value|
|
|
109
|
+
expression_to_sql(key, value, collection)
|
|
140
110
|
end
|
|
141
|
-
|
|
142
|
-
raise "Invalid options: #{invalid_keys.inspect}" unless invalid_keys.nil?
|
|
143
111
|
else
|
|
144
|
-
|
|
112
|
+
raise "Unable to parse conditions: #{x.inspect}" if x
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
conditions_hash.each_pair do |key,value|
|
|
116
|
+
expression_to_sql(key, value, collection)
|
|
145
117
|
end
|
|
118
|
+
|
|
119
|
+
collection
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def primary_class_table
|
|
123
|
+
@primary_class_table || (@primary_class_table = @loader.send(:primary_class_table))
|
|
146
124
|
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def empty?
|
|
150
|
-
!@loader.options.has_key?(:conditions) && implicits.empty?
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def to_a
|
|
154
|
-
normalized_conditions
|
|
155
|
-
end
|
|
156
125
|
end
|
|
157
126
|
|
|
158
127
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
require
|
|
1
|
+
require 'data_mapper/adapters/sql/commands/conditions'
|
|
2
|
+
require 'data_mapper/adapters/sql/commands/loader'
|
|
2
3
|
|
|
3
4
|
module DataMapper
|
|
4
5
|
module Adapters
|
|
@@ -7,287 +8,289 @@ module DataMapper
|
|
|
7
8
|
|
|
8
9
|
class LoadCommand
|
|
9
10
|
|
|
10
|
-
attr_reader :
|
|
11
|
+
attr_reader :conditions, :session, :options
|
|
11
12
|
|
|
12
|
-
def initialize(adapter, session,
|
|
13
|
-
@adapter, @session, @
|
|
13
|
+
def initialize(adapter, session, primary_class, options = {})
|
|
14
|
+
@adapter, @session, @primary_class = adapter, session, primary_class
|
|
15
|
+
|
|
16
|
+
@options, conditions_hash = partition_options(options)
|
|
14
17
|
|
|
15
18
|
@order = @options[:order]
|
|
16
19
|
@limit = @options[:limit]
|
|
20
|
+
@offset = @options[:offset]
|
|
17
21
|
@reload = @options[:reload]
|
|
18
|
-
@instance_id =
|
|
19
|
-
@conditions = Conditions.new(@adapter, self)
|
|
22
|
+
@instance_id = conditions_hash[:id]
|
|
23
|
+
@conditions = Conditions.new(@adapter, self, conditions_hash)
|
|
24
|
+
@loaders = Hash.new { |h,k| h[k] = Loader.new(self, k) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Display an overview of load options at a glance.
|
|
28
|
+
def inspect
|
|
29
|
+
<<-EOS.compress_lines % (object_id * 2)
|
|
30
|
+
#<#{self.class.name}:0x%x
|
|
31
|
+
@database=#{@adapter.name}
|
|
32
|
+
@reload=#{@reload.inspect}
|
|
33
|
+
@order=#{@order.inspect}
|
|
34
|
+
@limit=#{@limit.inspect}
|
|
35
|
+
@offset=#{@offset.inspect}
|
|
36
|
+
@options=#{@options.inspect}>
|
|
37
|
+
EOS
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Access the Conditions instance
|
|
41
|
+
def conditions
|
|
42
|
+
@conditions
|
|
20
43
|
end
|
|
21
44
|
|
|
45
|
+
# If +true+ then force the command to reload any objects
|
|
46
|
+
# already existing in the IdentityMap when executing.
|
|
22
47
|
def reload?
|
|
23
48
|
@reload
|
|
24
49
|
end
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def
|
|
31
|
-
@
|
|
50
|
+
|
|
51
|
+
# Determine if there is a limitation on the number of
|
|
52
|
+
# instances returned in the results. If +nil+, no limit
|
|
53
|
+
# is set. Can be used in conjunction with #offset for
|
|
54
|
+
# paging through a set of results.
|
|
55
|
+
def limit
|
|
56
|
+
@limit
|
|
32
57
|
end
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
|
|
59
|
+
# Used in conjunction with #limit to page through a set
|
|
60
|
+
# of results.
|
|
61
|
+
def offset
|
|
62
|
+
@offset
|
|
37
63
|
end
|
|
38
|
-
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
64
|
+
|
|
65
|
+
def call
|
|
66
|
+
|
|
67
|
+
# Check to see if the query is for a specific id and return if found
|
|
68
|
+
#
|
|
69
|
+
# NOTE: If the :id option is an Array:
|
|
70
|
+
# We could search for loaded instance ids and reject from
|
|
71
|
+
# the Array for already loaded instances, but working under the
|
|
72
|
+
# assumption that we'll probably have to issue a query to find
|
|
73
|
+
# at-least some of the instances we're looking for, it's faster to
|
|
74
|
+
# just skip that and go straight for the query.
|
|
75
|
+
unless reload? || @instance_id.blank? || @instance_id.is_a?(Array)
|
|
76
|
+
# If the id is for only a single record, attempt to find it.
|
|
77
|
+
if instance = @session.identity_map.get(@primary_class, @instance_id)
|
|
78
|
+
return instance
|
|
79
|
+
end
|
|
44
80
|
end
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
select_columns.map { |column| @adapter.quote_column_name(column.to_s) }
|
|
81
|
+
|
|
82
|
+
results = []
|
|
83
|
+
|
|
84
|
+
# Execute the statement and load the objects.
|
|
85
|
+
@adapter.execute(*to_parameterized_sql) do |reader, num_rows|
|
|
86
|
+
if @options.has_key?(:intercept_load)
|
|
87
|
+
load(reader, &@options[:intercept_load])
|
|
53
88
|
else
|
|
54
|
-
|
|
55
|
-
include?(column.name) || !column.lazy?
|
|
56
|
-
end.map { |column| column.to_sql }
|
|
89
|
+
load(reader)
|
|
57
90
|
end
|
|
58
91
|
end
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@
|
|
63
|
-
|
|
92
|
+
|
|
93
|
+
results += @loaders[@primary_class].loaded_set
|
|
94
|
+
|
|
95
|
+
if @limit == 1 || (@instance_id && !@instance_id.is_a?(Array))
|
|
96
|
+
results.first
|
|
64
97
|
else
|
|
65
|
-
|
|
98
|
+
results
|
|
66
99
|
end
|
|
67
100
|
end
|
|
68
101
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
102
|
+
def load(reader)
|
|
103
|
+
# The following blocks are identical aside from the yield.
|
|
104
|
+
# It's written this way to avoid a conditional within each
|
|
105
|
+
# iterator, and to take advantage of the performance of
|
|
106
|
+
# yield vs. Proc#call.
|
|
107
|
+
if block_given?
|
|
108
|
+
reader.each do
|
|
109
|
+
@loaders.each_pair do |klass,loader|
|
|
110
|
+
row = reader.current_row
|
|
111
|
+
yield(loader.materialize(row), @columns, row)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
reader.each do
|
|
116
|
+
@loaders.each_pair do |klass,loader|
|
|
117
|
+
loader.materialize(reader.current_row)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
86
120
|
end
|
|
87
|
-
|
|
88
|
-
return sql
|
|
89
121
|
end
|
|
90
122
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return instance unless instance.nil?
|
|
102
|
-
end
|
|
123
|
+
# Generate a select statement based on the initialization
|
|
124
|
+
# arguments.
|
|
125
|
+
def to_parameterized_sql
|
|
126
|
+
parameters = []
|
|
127
|
+
|
|
128
|
+
sql = 'SELECT ' << columns_for_select.join(', ')
|
|
129
|
+
sql << ' FROM ' << from_table_name
|
|
130
|
+
|
|
131
|
+
included_associations.each do |association|
|
|
132
|
+
sql << ' ' << association.to_sql
|
|
103
133
|
end
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
results = if eof?(reader)
|
|
108
|
-
nil
|
|
109
|
-
elsif limit == 1 || ( instance_id && !instance_id.kind_of?(Array) )
|
|
110
|
-
fetch_one(reader)
|
|
111
|
-
else
|
|
112
|
-
fetch_all(reader)
|
|
134
|
+
|
|
135
|
+
shallow_included_associations.each do |association|
|
|
136
|
+
sql << ' ' << association.to_shallow_sql
|
|
113
137
|
end
|
|
114
138
|
|
|
115
|
-
|
|
139
|
+
unless conditions.empty?
|
|
140
|
+
where_clause, *parameters = conditions.to_parameterized_sql
|
|
141
|
+
sql << ' WHERE ' << where_clause
|
|
142
|
+
end
|
|
116
143
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def load(hash, set = [])
|
|
121
|
-
|
|
122
|
-
instance_class = unless hash['type'].nil?
|
|
123
|
-
Kernel::const_get(hash['type'])
|
|
124
|
-
else
|
|
125
|
-
klass
|
|
144
|
+
unless @order.nil?
|
|
145
|
+
sql << ' ORDER BY ' << @order.to_s
|
|
126
146
|
end
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
instance_id = mapping.key.type_cast_value(hash['id'])
|
|
131
|
-
instance = @session.identity_map.get(instance_class, instance_id)
|
|
132
|
-
|
|
133
|
-
if instance.nil? || reload?
|
|
134
|
-
instance ||= instance_class.new
|
|
135
|
-
instance.class.callbacks.execute(:before_materialize, instance)
|
|
136
|
-
|
|
137
|
-
instance.instance_variable_set(:@new_record, false)
|
|
138
|
-
hash.each_pair do |name_as_string,raw_value|
|
|
139
|
-
name = name_as_string.to_sym
|
|
140
|
-
if column = mapping.find_by_column_name(name)
|
|
141
|
-
value = column.type_cast_value(raw_value)
|
|
142
|
-
instance.instance_variable_set(column.instance_variable_name, value)
|
|
143
|
-
else
|
|
144
|
-
instance.instance_variable_set("@#{name}", value)
|
|
145
|
-
end
|
|
146
|
-
instance.original_hashes[name] = value.hash
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
instance.instance_variable_set(:@__key, instance_id)
|
|
150
|
-
|
|
151
|
-
instance.class.callbacks.execute(:after_materialize, instance)
|
|
152
|
-
|
|
153
|
-
@session.identity_map.set(instance)
|
|
147
|
+
|
|
148
|
+
unless @limit.nil?
|
|
149
|
+
sql << ' LIMIT ' << @limit.to_s
|
|
154
150
|
end
|
|
155
|
-
|
|
156
|
-
instance.instance_variable_set(:@loaded_set, set)
|
|
157
|
-
instance.session = @session
|
|
158
|
-
set << instance
|
|
159
|
-
return instance
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def load_instances(fields, rows)
|
|
163
|
-
table = @adapter[klass]
|
|
164
151
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
key_ordinal = nil
|
|
168
|
-
key_column = table.key
|
|
169
|
-
type_ordinal = nil
|
|
170
|
-
type_column = nil
|
|
171
|
-
|
|
172
|
-
fields.each_with_index do |field, i|
|
|
173
|
-
column = table.find_by_column_name(field.to_sym)
|
|
174
|
-
key_ordinal = i if column.key?
|
|
175
|
-
type_ordinal, type_column = i, column if column.name == :type
|
|
176
|
-
columns[column] = i
|
|
152
|
+
unless @offset.nil?
|
|
153
|
+
sql << ' OFFSET ' << @offset.to_s
|
|
177
154
|
end
|
|
178
155
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
156
|
+
parameters.unshift(sql)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def qualify_columns?
|
|
160
|
+
return @qualify_columns unless @qualify_columns.nil?
|
|
161
|
+
@qualify_columns = !(included_associations.empty? && shallow_included_associations.empty?)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
# Return the Sql-escaped columns names to be selected in the results.
|
|
166
|
+
def columns_for_select
|
|
167
|
+
@columns_for_select || begin
|
|
168
|
+
qualify_columns = qualify_columns?
|
|
169
|
+
@columns_for_select = []
|
|
186
170
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
171
|
+
columns.each_with_index do |column,i|
|
|
172
|
+
class_for_loader = column.table.klass
|
|
173
|
+
@loaders[class_for_loader].add_column(column, i) if class_for_loader
|
|
174
|
+
@columns_for_select << column.to_sql(qualify_columns)
|
|
191
175
|
end
|
|
192
176
|
|
|
193
|
-
|
|
177
|
+
@columns_for_select
|
|
194
178
|
end
|
|
195
179
|
|
|
196
|
-
|
|
197
|
-
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Returns the DataMapper::Adapters::Sql::Mappings::Column instances to
|
|
183
|
+
# be selected in the results.
|
|
184
|
+
def columns
|
|
185
|
+
@columns || begin
|
|
186
|
+
@columns = primary_class_columns
|
|
187
|
+
@columns += included_columns
|
|
198
188
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
end
|
|
209
|
-
else
|
|
210
|
-
rows.each do |row|
|
|
211
|
-
load_instance(
|
|
212
|
-
create_instance(
|
|
213
|
-
klass,
|
|
214
|
-
key_column.type_cast_value(row[key_ordinal])
|
|
215
|
-
),
|
|
216
|
-
columns,
|
|
217
|
-
row,
|
|
218
|
-
set
|
|
219
|
-
)
|
|
189
|
+
included_associations.each do |assoc|
|
|
190
|
+
@columns += assoc.association_columns
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
shallow_included_associations.each do |assoc|
|
|
194
|
+
@columns += assoc.join_columns
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
@columns
|
|
220
198
|
end
|
|
221
199
|
end
|
|
222
200
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
instance.instance_variable_set(:@__key, instance_id)
|
|
239
|
-
instance.instance_variable_set(:@new_record, false)
|
|
240
|
-
@session.identity_map.set(instance)
|
|
201
|
+
# Returns the default columns for the primary_class_table,
|
|
202
|
+
# or maps symbols specified in a +:select+ option to columns
|
|
203
|
+
# in the primary_class_table.
|
|
204
|
+
def primary_class_columns
|
|
205
|
+
@primary_class_columns || @primary_class_columns = begin
|
|
206
|
+
if @options.has_key?(:select)
|
|
207
|
+
case x = @options[:select]
|
|
208
|
+
when Array then x
|
|
209
|
+
when Symbol then [x]
|
|
210
|
+
else raise ':select option must be a Symbol, or an Array of Symbols'
|
|
211
|
+
end.map { |name| primary_class_table[name] }
|
|
212
|
+
else
|
|
213
|
+
primary_class_table.columns.reject { |column| column.lazy? }
|
|
214
|
+
end
|
|
215
|
+
end
|
|
241
216
|
end
|
|
242
217
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
218
|
+
def included_associations
|
|
219
|
+
@included_associations || @included_associations = begin
|
|
220
|
+
associations = primary_class_table.associations
|
|
221
|
+
include_options.map do |name|
|
|
222
|
+
associations[name]
|
|
223
|
+
end.compact
|
|
224
|
+
end
|
|
225
|
+
end
|
|
249
226
|
|
|
250
|
-
|
|
227
|
+
def shallow_included_associations
|
|
228
|
+
@shallow_included_associations || @shallow_included_associations = begin
|
|
229
|
+
associations = primary_class_table.associations
|
|
230
|
+
shallow_include_options.map do |name|
|
|
231
|
+
associations[name]
|
|
232
|
+
end.compact
|
|
233
|
+
end
|
|
234
|
+
end
|
|
251
235
|
|
|
252
|
-
|
|
236
|
+
def included_columns
|
|
237
|
+
@included_columns || @included_columns = begin
|
|
238
|
+
include_options.map do |name|
|
|
239
|
+
primary_class_table[name]
|
|
240
|
+
end.compact
|
|
241
|
+
end
|
|
242
|
+
end
|
|
253
243
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
244
|
+
def include_options
|
|
245
|
+
@include_options || @include_options = begin
|
|
246
|
+
case x = @options[:include]
|
|
247
|
+
when Array then x
|
|
248
|
+
when Symbol then [x]
|
|
249
|
+
else []
|
|
250
|
+
end
|
|
251
|
+
end
|
|
259
252
|
end
|
|
260
253
|
|
|
261
|
-
|
|
254
|
+
def shallow_include_options
|
|
255
|
+
@shallow_include_options || @shallow_include_options = begin
|
|
256
|
+
case x = @options[:shallow_include]
|
|
257
|
+
when Array then x
|
|
258
|
+
when Symbol then [x]
|
|
259
|
+
else []
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
262
263
|
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
# Determine if a Column should be included based on the
|
|
265
|
+
# value of the +:include+ option.
|
|
266
|
+
def include_column?(name)
|
|
267
|
+
!primary_class_table[name].lazy? || include_options.includes?(name)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Return the Sql-escaped table name of the +primary_class+.
|
|
271
|
+
def from_table_name
|
|
272
|
+
@from_table_name || (@from_table_name = @adapter.table(@primary_class).to_sql)
|
|
273
|
+
end
|
|
265
274
|
|
|
266
|
-
|
|
275
|
+
# Returns the DataMapper::Adapters::Sql::Mappings::Table for the +primary_class+.
|
|
276
|
+
def primary_class_table
|
|
277
|
+
@primary_class_table || (@primary_class_table = @adapter.table(@primary_class))
|
|
278
|
+
end
|
|
267
279
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
def fetch_one(reader)
|
|
285
|
-
raise NotImplementedError.new
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def fetch_all(reader)
|
|
289
|
-
raise NotImplementedError.new
|
|
290
|
-
end
|
|
280
|
+
def partition_options(options)
|
|
281
|
+
find_options = @adapter.class::FIND_OPTIONS
|
|
282
|
+
conditions_hash = {}
|
|
283
|
+
options_hash = {}
|
|
284
|
+
options.each do |key,value|
|
|
285
|
+
if key != :conditions && find_options.include?(key)
|
|
286
|
+
options_hash[key] = value
|
|
287
|
+
else
|
|
288
|
+
conditions_hash[key] = value
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
[ options_hash, conditions_hash ]
|
|
293
|
+
end
|
|
291
294
|
|
|
292
295
|
end # class LoadCommand
|
|
293
296
|
end # module Commands
|