datamapper 0.1.0 → 0.1.1

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.
Files changed (73) hide show
  1. data/CHANGELOG +31 -1
  2. data/MIT-LICENSE +1 -1
  3. data/README +9 -1
  4. data/example.rb +23 -15
  5. data/lib/data_mapper.rb +5 -0
  6. data/lib/data_mapper/adapters/abstract_adapter.rb +9 -207
  7. data/lib/data_mapper/adapters/mysql_adapter.rb +132 -108
  8. data/lib/data_mapper/adapters/postgresql_adapter.rb +242 -0
  9. data/lib/data_mapper/adapters/sql/coersion.rb +74 -0
  10. data/lib/data_mapper/adapters/sql/commands/advanced_load_command.rb +140 -0
  11. data/lib/data_mapper/adapters/sql/commands/conditions.rb +161 -0
  12. data/lib/data_mapper/adapters/sql/commands/delete_command.rb +113 -0
  13. data/lib/data_mapper/adapters/sql/commands/load_command.rb +296 -0
  14. data/lib/data_mapper/adapters/sql/commands/save_command.rb +141 -0
  15. data/lib/data_mapper/adapters/sql/commands/table_exists_command.rb +33 -0
  16. data/lib/data_mapper/adapters/sql/mappings/column.rb +91 -0
  17. data/lib/data_mapper/adapters/sql/mappings/schema.rb +30 -0
  18. data/lib/data_mapper/adapters/sql/mappings/table.rb +143 -0
  19. data/lib/data_mapper/adapters/sql/quoting.rb +38 -0
  20. data/lib/data_mapper/adapters/sql_adapter.rb +163 -0
  21. data/lib/data_mapper/adapters/sqlite3_adapter.rb +155 -116
  22. data/lib/data_mapper/associations.rb +2 -0
  23. data/lib/data_mapper/associations/advanced_has_many_association.rb +55 -0
  24. data/lib/data_mapper/associations/belongs_to_association.rb +2 -2
  25. data/lib/data_mapper/associations/has_many_association.rb +3 -3
  26. data/lib/data_mapper/associations/has_one_association.rb +2 -2
  27. data/lib/data_mapper/base.rb +30 -11
  28. data/lib/data_mapper/callbacks.rb +4 -1
  29. data/lib/data_mapper/database.rb +8 -41
  30. data/lib/data_mapper/identity_map.rb +23 -3
  31. data/lib/data_mapper/session.rb +34 -186
  32. data/lib/data_mapper/{extensions → support}/active_record_impersonation.rb +16 -12
  33. data/lib/data_mapper/support/blank.rb +35 -0
  34. data/lib/data_mapper/support/connection_pool.rb +2 -1
  35. data/lib/data_mapper/support/string.rb +27 -0
  36. data/lib/data_mapper/support/struct.rb +26 -0
  37. data/lib/data_mapper/validations/unique_validator.rb +1 -3
  38. data/lib/data_mapper/validations/validation_helper.rb +1 -1
  39. data/performance.rb +24 -7
  40. data/profile_data_mapper.rb +24 -2
  41. data/rakefile.rb +2 -2
  42. data/spec/basic_finder.rb +2 -2
  43. data/spec/belongs_to.rb +1 -1
  44. data/spec/delete_command_spec.rb +9 -0
  45. data/spec/fixtures/zoos.yaml +4 -0
  46. data/spec/has_many.rb +1 -1
  47. data/spec/load_command_spec.rb +44 -0
  48. data/spec/models/zoo.rb +2 -0
  49. data/spec/save_command_spec.rb +13 -0
  50. data/spec/spec_helper.rb +10 -1
  51. data/spec/support/string_spec.rb +7 -0
  52. data/spec/validates_confirmation_of.rb +1 -1
  53. data/spec/validates_format_of.rb +1 -1
  54. data/spec/validates_length_of.rb +1 -1
  55. data/spec/validations.rb +1 -1
  56. metadata +23 -20
  57. data/lib/data_mapper/extensions/callback_helpers.rb +0 -35
  58. data/lib/data_mapper/loaded_set.rb +0 -45
  59. data/lib/data_mapper/mappings/column.rb +0 -78
  60. data/lib/data_mapper/mappings/schema.rb +0 -28
  61. data/lib/data_mapper/mappings/table.rb +0 -99
  62. data/lib/data_mapper/queries/conditions.rb +0 -141
  63. data/lib/data_mapper/queries/connection.rb +0 -34
  64. data/lib/data_mapper/queries/create_table_statement.rb +0 -38
  65. data/lib/data_mapper/queries/delete_statement.rb +0 -17
  66. data/lib/data_mapper/queries/drop_table_statement.rb +0 -17
  67. data/lib/data_mapper/queries/insert_statement.rb +0 -29
  68. data/lib/data_mapper/queries/reader.rb +0 -42
  69. data/lib/data_mapper/queries/result.rb +0 -19
  70. data/lib/data_mapper/queries/select_statement.rb +0 -103
  71. data/lib/data_mapper/queries/table_exists_statement.rb +0 -17
  72. data/lib/data_mapper/queries/truncate_table_statement.rb +0 -17
  73. data/lib/data_mapper/queries/update_statement.rb +0 -25
@@ -0,0 +1,161 @@
1
+ module DataMapper
2
+ module Adapters
3
+ module Sql
4
+ module Commands
5
+
6
+ class Conditions
7
+
8
+ def initialize(adapter, loader)
9
+ @adapter, @loader = adapter, loader
10
+ @has_id = false
11
+ end
12
+
13
+ class NormalizationError < StandardError
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
83
+ end
84
+
85
+ def escape(conditions)
86
+ clause = conditions.shift
87
+
88
+ clause.gsub(/\?/) do |x|
89
+ # Check if the condition is an in, clause.
90
+ case conditions.first
91
+ when Array then
92
+ '(' << conditions.shift.map { |c| @adapter.quote_value(c) }.join(', ') << ')'
93
+ when LoadCommand then
94
+ '(' << conditions.shift.to_sql << ')'
95
+ else
96
+ @adapter.quote_value(conditions.shift)
97
+ end
98
+ end
99
+ 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
+
113
+ if @loader.options.has_key?(:conditions)
114
+ normalize(@loader.options[:conditions], @normalized_conditions)
115
+ 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
+
129
+ invalid_keys = false
130
+
131
+ implicit_conditions = @loader.options.reject do |k,v|
132
+ standard_key = @adapter.class::FIND_OPTIONS.include?(k)
133
+ invalid_keys = true if !standard_key && table[k.to_sym].nil?
134
+ standard_key
135
+ end
136
+
137
+ if invalid_keys
138
+ invalid_keys = implicit_conditions.select do |k,v|
139
+ table[k.to_sym].nil?
140
+ end
141
+
142
+ raise "Invalid options: #{invalid_keys.inspect}" unless invalid_keys.nil?
143
+ else
144
+ implicit_conditions
145
+ end
146
+ 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
+ end
157
+
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,113 @@
1
+ module DataMapper
2
+ module Adapters
3
+ module Sql
4
+ module Commands
5
+
6
+ class DeleteCommand
7
+
8
+ def initialize(adapter, klass_or_instance, options = nil)
9
+ @adapter, @klass_or_instance, @options = adapter, klass_or_instance, options
10
+ end
11
+
12
+ def truncate?
13
+ return false if @options.nil?
14
+ @options[:truncate]
15
+ end
16
+
17
+ def drop?
18
+ return false if @options.nil?
19
+ @options[:drop]
20
+ end
21
+
22
+ def delete_all?
23
+ return !truncate? && !drop? && @klass_or_instance.kind_of?(Class)
24
+ end
25
+
26
+ def klass
27
+ @klass_or_instance.kind_of?(Class) ? @klass_or_instance : @klass_or_instance.class
28
+ end
29
+
30
+ def to_truncate_sql
31
+ "TRUNCATE TABLE " << @adapter[@klass_or_instance].to_sql
32
+ end
33
+
34
+ def to_drop_sql
35
+ "DROP TABLE #{@adapter[@klass_or_instance].to_sql}"
36
+ end
37
+
38
+ def to_delete_sql
39
+ sql = "DELETE FROM " << @adapter[klass].to_sql
40
+ sql << " WHERE id = " << @adapter.quote_value(@klass_or_instance.key) unless delete_all?
41
+ return sql
42
+ end
43
+
44
+ def to_sql
45
+ if truncate?
46
+ to_truncate_sql
47
+ elsif drop?
48
+ to_drop_sql
49
+ else
50
+ to_delete_sql
51
+ end
52
+ end
53
+
54
+ def session
55
+ return nil if @options.nil?
56
+ @options[:session]
57
+ end
58
+
59
+ def call
60
+ result = nil
61
+
62
+ if truncate?
63
+ result = execute_truncate(to_sql)
64
+ session.identity_map.clear!(klass) unless session.nil?
65
+ elsif drop?
66
+ result = execute_drop(to_sql)
67
+ session.identity_map.clear!(klass) unless session.nil?
68
+ elsif delete_all?
69
+ result = execute_delete_all(to_sql)
70
+ session.identity_map.clear!(klass) unless session.nil?
71
+ else
72
+ @klass_or_instance.class.callbacks.execute(:before_destroy, @klass_or_instance)
73
+
74
+ result = execute(to_sql)
75
+
76
+ if result
77
+ @klass_or_instance.instance_variable_set(:@new_record, true)
78
+ @klass_or_instance.session = session
79
+ @klass_or_instance.original_hashes.clear
80
+ session.identity_map.delete(@klass_or_instance) unless session.nil?
81
+ @klass_or_instance.class.callbacks.execute(:after_destroy, @klass_or_instance)
82
+ end
83
+ end
84
+
85
+ return result
86
+ rescue => error
87
+ @adapter.log.error(error)
88
+ raise error
89
+ end
90
+
91
+ protected
92
+ def execute_truncate(sql)
93
+ execute(sql)
94
+ end
95
+
96
+ def execute_drop(sql)
97
+ execute(sql)
98
+ end
99
+
100
+ def execute_delete_all(sql)
101
+ execute(sql)
102
+ end
103
+
104
+ def execute(sql)
105
+ raise NotImplementedError.new
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,296 @@
1
+ require File.dirname(__FILE__) + '/conditions'
2
+
3
+ module DataMapper
4
+ module Adapters
5
+ module Sql
6
+ module Commands
7
+
8
+ class LoadCommand
9
+
10
+ attr_reader :klass, :order, :limit, :instance_id, :conditions, :options
11
+
12
+ def initialize(adapter, session, klass, options)
13
+ @adapter, @session, @klass, @options = adapter, session, klass, options
14
+
15
+ @order = @options[:order]
16
+ @limit = @options[:limit]
17
+ @reload = @options[:reload]
18
+ @instance_id = @options[:id]
19
+ @conditions = Conditions.new(@adapter, self)
20
+ end
21
+
22
+ def reload?
23
+ @reload
24
+ end
25
+
26
+ def escape(conditions)
27
+ @adapter.escape(conditions)
28
+ end
29
+
30
+ def inspect
31
+ @options.inspect
32
+ end
33
+
34
+ def include?(association_name)
35
+ return false if includes.empty?
36
+ includes.include?(association_name)
37
+ end
38
+
39
+ def includes
40
+ @includes || @includes = begin
41
+ list = @options[:include] || []
42
+ list.kind_of?(Array) ? list : [list]
43
+ list
44
+ end
45
+ end
46
+
47
+ def select
48
+ @select_columns || @select_columns = begin
49
+ select_columns = @options[:select]
50
+ unless select_columns.nil?
51
+ select_columns = select_columns.kind_of?(Array) ? select_columns : [select_columns]
52
+ select_columns.map { |column| @adapter.quote_column_name(column.to_s) }
53
+ else
54
+ @options[:select] = @adapter[klass].columns.select do |column|
55
+ include?(column.name) || !column.lazy?
56
+ end.map { |column| column.to_sql }
57
+ end
58
+ end
59
+ end
60
+
61
+ def table_name
62
+ @table_name || @table_name = if @options.has_key?(:table)
63
+ @adapter.quote_table_name(@options[:table])
64
+ else
65
+ @adapter[klass].to_sql
66
+ end
67
+ end
68
+
69
+ def to_sql
70
+ sql = 'SELECT ' << select.join(', ') << ' FROM ' << table_name
71
+
72
+ where = []
73
+
74
+ where += conditions.to_a unless conditions.empty?
75
+
76
+ unless where.empty?
77
+ sql << ' WHERE (' << where.join(') AND (') << ')'
78
+ end
79
+
80
+ unless order.nil?
81
+ sql << ' ORDER BY ' << order.to_s
82
+ end
83
+
84
+ unless limit.nil?
85
+ sql << ' LIMIT ' << limit.to_s
86
+ end
87
+
88
+ return sql
89
+ end
90
+
91
+ def call
92
+ if instance_id && !reload?
93
+ if instance_id.kind_of?(Array)
94
+ instances = instance_id.map do |id|
95
+ @session.identity_map.get(klass, id)
96
+ end.compact
97
+
98
+ return instances if instances.size == instance_id.size
99
+ else
100
+ instance = @session.identity_map.get(klass, instance_id)
101
+ return instance unless instance.nil?
102
+ end
103
+ end
104
+
105
+ reader = execute(to_sql)
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)
113
+ end
114
+
115
+ close_reader(reader)
116
+
117
+ return results
118
+ end
119
+
120
+ def load(hash, set = [])
121
+
122
+ instance_class = unless hash['type'].nil?
123
+ Kernel::const_get(hash['type'])
124
+ else
125
+ klass
126
+ end
127
+
128
+ mapping = @adapter[instance_class]
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)
154
+ 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
+
165
+ set = []
166
+ columns = {}
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
177
+ end
178
+
179
+ if type_ordinal
180
+
181
+ tables = Hash.new() do |h,k|
182
+
183
+ table_for_row = @adapter[k.blank? ? klass : type_column.type_cast_value(k)]
184
+ key_ordinal_for_row = nil
185
+ columns_for_row = {}
186
+
187
+ fields.each_with_index do |field, i|
188
+ column = table_for_row.find_by_column_name(field.to_sym)
189
+ key_ordinal_for_row = i if column.key?
190
+ columns_for_row[column] = i
191
+ end
192
+
193
+ h[k] = [ table_for_row.klass, table_for_row.key, key_ordinal_for_row, columns_for_row ]
194
+ end
195
+
196
+ rows.each do |row|
197
+ klass_for_row, key_column_for_row, key_ordinal_for_row, columns_for_row = *tables[row[type_ordinal]]
198
+
199
+ load_instance(
200
+ create_instance(
201
+ klass_for_row,
202
+ key_column_for_row.type_cast_value(row[key_ordinal_for_row])
203
+ ),
204
+ columns_for_row,
205
+ row,
206
+ set
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
+ )
220
+ end
221
+ end
222
+
223
+ set.dup
224
+ end
225
+
226
+ # Create an instance for the specified Class and id in
227
+ # preparation for loading. This method first checks to
228
+ # see if the instance is in the IdentityMap.
229
+ # If not, then a new class is created, it's marked as
230
+ # not-new, the key is set and it's added to the IdentityMap.
231
+ # Afterwards the instance's Session is updated to the current
232
+ # session, and the instance returned.
233
+ def create_instance(instance_class, instance_id)
234
+ instance = @session.identity_map.get(instance_class, instance_id)
235
+
236
+ if instance.nil? || reload?
237
+ instance = instance_class.new()
238
+ instance.instance_variable_set(:@__key, instance_id)
239
+ instance.instance_variable_set(:@new_record, false)
240
+ @session.identity_map.set(instance)
241
+ end
242
+
243
+ instance.session = @session
244
+
245
+ return instance
246
+ end
247
+
248
+ def load_instance(instance, columns, values, set = [])
249
+
250
+ instance.class.callbacks.execute(:before_materialize, instance)
251
+
252
+ hashes = {}
253
+
254
+ columns.each_pair do |column, i|
255
+ hashes[column.name] = instance.instance_variable_set(
256
+ column.instance_variable_name,
257
+ column.type_cast_value(values[i])
258
+ ).hash
259
+ end
260
+
261
+ instance.instance_variable_set(:@original_hashes, hashes)
262
+
263
+ instance.instance_variable_set(:@loaded_set, set)
264
+ set << instance
265
+
266
+ instance.class.callbacks.execute(:after_materialize, instance)
267
+
268
+ return instance
269
+ end
270
+
271
+ protected
272
+ def count_rows(reader)
273
+ raise NotImplementedError.new
274
+ end
275
+
276
+ def close_reader(reader)
277
+ raise NotImplementedError.new
278
+ end
279
+
280
+ def execute(sql)
281
+ raise NotImplementedError.new
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
291
+
292
+ end # class LoadCommand
293
+ end # module Commands
294
+ end # module Sql
295
+ end # module Adapters
296
+ end # module DataMapper