datamapper 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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