datamapper 0.2.2 → 0.2.3
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 +10 -2
- data/environment.rb +0 -3
- data/lib/data_mapper.rb +2 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +2 -2
- data/lib/data_mapper/adapters/data_object_adapter.rb +63 -73
- data/lib/data_mapper/adapters/mysql_adapter.rb +2 -13
- data/lib/data_mapper/adapters/postgresql_adapter.rb +3 -2
- data/lib/data_mapper/adapters/sql/coersion.rb +29 -11
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +13 -9
- data/lib/data_mapper/adapters/sql/mappings/table.rb +7 -3
- data/lib/data_mapper/adapters/sql/quoting.rb +0 -53
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +2 -1
- data/lib/data_mapper/base.rb +25 -9
- data/lib/data_mapper/context.rb +2 -2
- data/lib/data_mapper/database.rb +6 -6
- data/lib/data_mapper/support/active_record_impersonation.rb +1 -1
- data/lib/data_mapper/support/serialization.rb +10 -2
- data/lib/data_mapper/support/silence.rb +10 -0
- data/lib/data_mapper/support/string.rb +21 -1
- data/lib/data_mapper/validations/confirmation_validator.rb +1 -3
- data/lib/data_mapper/validations/format_validator.rb +6 -6
- data/lib/data_mapper/validations/generic_validator.rb +0 -5
- data/lib/data_mapper/validations/length_validator.rb +14 -13
- data/lib/data_mapper/validations/required_field_validator.rb +2 -8
- data/lib/data_mapper/validations/unique_validator.rb +1 -7
- data/plugins/dataobjects/do.rb +94 -2
- data/plugins/dataobjects/do_mysql.rb +17 -17
- data/plugins/dataobjects/do_postgres.rb +34 -15
- data/plugins/dataobjects/do_sqlite3.rb +9 -6
- data/rakefile.rb +3 -2
- data/spec/base_spec.rb +28 -0
- data/spec/dataobjects_spec.rb +2 -1
- data/spec/load_command_spec.rb +1 -1
- data/spec/serialization_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/support_spec.rb +7 -0
- data/spec/validations_spec.rb +13 -0
- metadata +14 -6
- data/plugins/dataobjects/swig_mysql/Makefile +0 -146
- data/plugins/dataobjects/swig_mysql/mysql_c.o +0 -0
- data/plugins/dataobjects/swig_postgres/Makefile +0 -146
- data/plugins/dataobjects/swig_sqlite/db +0 -0
data/plugins/dataobjects/do.rb
CHANGED
@@ -32,6 +32,14 @@ module DataObject
|
|
32
32
|
def initialize(connection_string)
|
33
33
|
end
|
34
34
|
|
35
|
+
def logger
|
36
|
+
@logger || @logger = Logger.new(nil)
|
37
|
+
end
|
38
|
+
|
39
|
+
def logger=(value)
|
40
|
+
@logger = value
|
41
|
+
end
|
42
|
+
|
35
43
|
def begin_transaction
|
36
44
|
# TODO: Hook this up
|
37
45
|
Transaction.new
|
@@ -50,6 +58,7 @@ module DataObject
|
|
50
58
|
end
|
51
59
|
|
52
60
|
def create_command(text)
|
61
|
+
logger.debug { text }
|
53
62
|
Command.new(self, text)
|
54
63
|
end
|
55
64
|
|
@@ -215,11 +224,11 @@ module DataObject
|
|
215
224
|
@connection, @text = connection, text
|
216
225
|
end
|
217
226
|
|
218
|
-
def execute_non_query
|
227
|
+
def execute_non_query(*args)
|
219
228
|
raise LostConnectionError, "the connection to the database has been lost" if @connection.closed?
|
220
229
|
end
|
221
230
|
|
222
|
-
def execute_reader
|
231
|
+
def execute_reader(*args)
|
223
232
|
raise LostConnectionError, "the connection to the database has been lost" if @connection.closed?
|
224
233
|
end
|
225
234
|
|
@@ -227,6 +236,89 @@ module DataObject
|
|
227
236
|
raise NotImplementedError
|
228
237
|
end
|
229
238
|
|
239
|
+
# Escape a string of SQL with a set of arguments.
|
240
|
+
# The first argument is assumed to be the SQL to escape,
|
241
|
+
# the remaining arguments (if any) are assumed to be
|
242
|
+
# values to escape and interpolate.
|
243
|
+
#
|
244
|
+
# ==== Examples
|
245
|
+
# escape_sql("SELECT * FROM zoos")
|
246
|
+
# # => "SELECT * FROM zoos"
|
247
|
+
#
|
248
|
+
# escape_sql("SELECT * FROM zoos WHERE name = ?", "Dallas")
|
249
|
+
# # => "SELECT * FROM zoos WHERE name = `Dallas`"
|
250
|
+
#
|
251
|
+
# escape_sql("SELECT * FROM zoos WHERE name = ? AND acreage > ?", "Dallas", 40)
|
252
|
+
# # => "SELECT * FROM zoos WHERE name = `Dallas` AND acreage > 40"
|
253
|
+
#
|
254
|
+
# ==== Warning
|
255
|
+
# This method is meant mostly for adapters that don't support
|
256
|
+
# bind-parameters.
|
257
|
+
def escape_sql(args)
|
258
|
+
sql = text.dup
|
259
|
+
|
260
|
+
unless args.empty?
|
261
|
+
sql.gsub!(/\?/) do |x|
|
262
|
+
quote_value(args.shift)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
sql
|
267
|
+
end
|
268
|
+
|
269
|
+
def quote_value(value)
|
270
|
+
return 'NULL' if value.nil?
|
271
|
+
|
272
|
+
case value
|
273
|
+
when Numeric then quote_numeric(value)
|
274
|
+
when String then quote_string(value)
|
275
|
+
when Class then quote_class(value)
|
276
|
+
when Time then quote_time(value)
|
277
|
+
when DateTime then quote_datetime(value)
|
278
|
+
when Date then quote_date(value)
|
279
|
+
when TrueClass, FalseClass then quote_boolean(value)
|
280
|
+
when Array then quote_array(value)
|
281
|
+
else
|
282
|
+
if value.respond_to?(:to_sql)
|
283
|
+
value.to_sql
|
284
|
+
else
|
285
|
+
raise "Don't know how to quote #{value.inspect}"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def quote_numeric(value)
|
291
|
+
value.to_s
|
292
|
+
end
|
293
|
+
|
294
|
+
def quote_string(value)
|
295
|
+
"'#{value.gsub("'", "''")}'"
|
296
|
+
end
|
297
|
+
|
298
|
+
def quote_class(value)
|
299
|
+
"'#{value.name}'"
|
300
|
+
end
|
301
|
+
|
302
|
+
def quote_time(value)
|
303
|
+
"'#{value.xmlschema}'"
|
304
|
+
end
|
305
|
+
|
306
|
+
def quote_datetime(value)
|
307
|
+
"'#{value.dup}'"
|
308
|
+
end
|
309
|
+
|
310
|
+
def quote_date(value)
|
311
|
+
"'#{value.strftime("%Y-%m-%d")}'"
|
312
|
+
end
|
313
|
+
|
314
|
+
def quote_boolean(value)
|
315
|
+
value.to_s.upcase
|
316
|
+
end
|
317
|
+
|
318
|
+
def quote_array(value)
|
319
|
+
"(#{value.map { |entry| quote_value(entry) }.join(', ')})"
|
320
|
+
end
|
321
|
+
|
230
322
|
end
|
231
323
|
|
232
324
|
class NotImplementedError < StandardError
|
@@ -10,21 +10,9 @@ module DataObject
|
|
10
10
|
|
11
11
|
class Connection < DataObject::Connection
|
12
12
|
|
13
|
-
def self.conns
|
14
|
-
@conns
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.conns=(val)
|
18
|
-
@conns = val
|
19
|
-
end
|
20
|
-
|
21
|
-
self.conns = 0
|
22
|
-
|
23
13
|
attr_reader :db
|
24
14
|
|
25
|
-
def initialize(connection_string)
|
26
|
-
@num = (self.class.conns += 1)
|
27
|
-
|
15
|
+
def initialize(connection_string)
|
28
16
|
@state = STATE_CLOSED
|
29
17
|
@connection_string = connection_string
|
30
18
|
opts = connection_string.split(" ")
|
@@ -60,6 +48,7 @@ module DataObject
|
|
60
48
|
end
|
61
49
|
|
62
50
|
def create_command(text)
|
51
|
+
logger.debug { text }
|
63
52
|
Command.new(self, text)
|
64
53
|
end
|
65
54
|
|
@@ -180,9 +169,9 @@ module DataObject
|
|
180
169
|
|
181
170
|
class Command < DataObject::Command
|
182
171
|
|
183
|
-
def execute_reader
|
172
|
+
def execute_reader(*args)
|
184
173
|
super
|
185
|
-
result = Mysql_c.mysql_query(@connection.db,
|
174
|
+
result = Mysql_c.mysql_query(@connection.db, escape_sql(args))
|
186
175
|
# TODO: Real Error
|
187
176
|
raise QueryError, "Your query failed.\n#{Mysql_c.mysql_error(@connection.db)}\n#{@text}" unless result == 0
|
188
177
|
reader = Reader.new(@connection.db, Mysql_c.mysql_use_result(@connection.db))
|
@@ -195,9 +184,9 @@ module DataObject
|
|
195
184
|
end
|
196
185
|
end
|
197
186
|
|
198
|
-
def execute_non_query
|
187
|
+
def execute_non_query(*args)
|
199
188
|
super
|
200
|
-
result = Mysql_c.mysql_query(@connection.db,
|
189
|
+
result = Mysql_c.mysql_query(@connection.db, escape_sql(args))
|
201
190
|
raise QueryError, "Your query failed.\n#{Mysql_c.mysql_error(@connection.db)}\n#{@text}" unless result == 0
|
202
191
|
reader = Mysql_c.mysql_store_result(@connection.db)
|
203
192
|
raise QueryError, "You called execute_non_query on a query: #{@text}" if reader
|
@@ -206,6 +195,17 @@ module DataObject
|
|
206
195
|
return ResultData.new(@connection, rows_affected, Mysql_c.mysql_insert_id(@connection.db))
|
207
196
|
end
|
208
197
|
|
198
|
+
def quote_time(value)
|
199
|
+
"DATE('#{value.xmlschema}')"
|
200
|
+
end
|
201
|
+
|
202
|
+
def quote_datetime(value)
|
203
|
+
"DATE('#{value}')"
|
204
|
+
end
|
205
|
+
|
206
|
+
def quote_date(value)
|
207
|
+
"DATE('#{value.strftime("%Y-%m-%d")}')"
|
208
|
+
end
|
209
209
|
end
|
210
210
|
|
211
211
|
end
|
@@ -60,10 +60,8 @@ module DataObject
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
63
|
+
def real_close
|
64
64
|
Postgres_c.PQclear(@reader)
|
65
|
-
@state = STATE_CLOSED
|
66
|
-
true
|
67
65
|
end
|
68
66
|
|
69
67
|
def data_type_name(col)
|
@@ -91,6 +89,15 @@ module DataObject
|
|
91
89
|
typecast(val, @field_types[idx])
|
92
90
|
end
|
93
91
|
|
92
|
+
def each
|
93
|
+
return unless has_rows?
|
94
|
+
|
95
|
+
while(true) do
|
96
|
+
yield
|
97
|
+
break unless self.next
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
94
101
|
def next
|
95
102
|
super
|
96
103
|
if @cursor >= @rows - 1
|
@@ -117,11 +124,11 @@ module DataObject
|
|
117
124
|
when "FLOAT4", "FLOAT8", "NUMERIC", "CASH"
|
118
125
|
val.to_f
|
119
126
|
when "TIMESTAMP", "TIMETZ", "TIMESTAMPTZ"
|
120
|
-
DateTime.parse(val)
|
127
|
+
DateTime.parse(val) rescue nil
|
121
128
|
when "TIME"
|
122
|
-
DateTime.parse(val).to_time
|
129
|
+
DateTime.parse(val).to_time rescue nil
|
123
130
|
when "DATE"
|
124
|
-
Date.parse(val)
|
131
|
+
Date.parse(val) rescue nil
|
125
132
|
else
|
126
133
|
val
|
127
134
|
end
|
@@ -146,25 +153,37 @@ module DataObject
|
|
146
153
|
|
147
154
|
class Command < DataObject::Command
|
148
155
|
|
149
|
-
def execute_reader
|
156
|
+
def execute_reader(*args)
|
150
157
|
super
|
151
|
-
|
152
|
-
|
153
|
-
|
158
|
+
sql = escape_sql(args)
|
159
|
+
@connection.logger.debug { sql }
|
160
|
+
ptr = Postgres_c.PQexec(@connection.db, sql)
|
161
|
+
unless [Postgres_c::PGRES_COMMAND_OK, Postgres_c::PGRES_TUPLES_OK].include?(Postgres_c.PQresultStatus(ptr))
|
162
|
+
raise QueryError, "Your query failed.\n#{Postgres_c.PQerrorMessage(@connection.db)}QUERY: \"#{sql}\""
|
163
|
+
else
|
164
|
+
reader = Reader.new(@connection.db, ptr)
|
165
|
+
if block_given?
|
166
|
+
return_value = yield(reader)
|
167
|
+
reader.close
|
168
|
+
return_value
|
169
|
+
else
|
170
|
+
reader
|
171
|
+
end
|
154
172
|
end
|
155
|
-
Reader.new(@connection.db, reader)
|
156
173
|
end
|
157
174
|
|
158
|
-
def execute_non_query
|
175
|
+
def execute_non_query(*args)
|
159
176
|
super
|
160
|
-
|
177
|
+
sql = escape_sql(args)
|
178
|
+
@connection.logger.debug { sql }
|
179
|
+
results = Postgres_c.PQexec(@connection.db, sql)
|
161
180
|
status = Postgres_c.PQresultStatus(results)
|
162
181
|
if status == Postgres_c::PGRES_TUPLES_OK
|
163
182
|
Postgres_c.PQclear(results)
|
164
|
-
raise QueryError, "Your query failed or you tried to execute a SELECT query through execute_non_reader\n#{Postgres_c.PQerrorMessage(@connection.db)}\nQUERY: \"#{
|
183
|
+
raise QueryError, "Your query failed or you tried to execute a SELECT query through execute_non_reader\n#{Postgres_c.PQerrorMessage(@connection.db)}\nQUERY: \"#{sql}\""
|
165
184
|
elsif status != Postgres_c::PGRES_COMMAND_OK
|
166
185
|
Postgres_c.PQclear(results)
|
167
|
-
raise QueryError, "Your query failed.\n#{Postgres_c.PQerrorMessage(@connection.db)}\nQUERY: \"#{
|
186
|
+
raise QueryError, "Your query failed.\n#{Postgres_c.PQerrorMessage(@connection.db)}\nQUERY: \"#{sql}\""
|
168
187
|
end
|
169
188
|
rows_affected = Postgres_c.PQcmdTuples(results).to_i
|
170
189
|
Postgres_c.PQclear(results)
|
@@ -35,6 +35,7 @@ module DataObject
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def create_command(text)
|
38
|
+
logger.debug { text }
|
38
39
|
Command.new(self, text)
|
39
40
|
end
|
40
41
|
|
@@ -113,11 +114,12 @@ module DataObject
|
|
113
114
|
|
114
115
|
class Command < DataObject::Command
|
115
116
|
|
116
|
-
def execute_reader
|
117
|
+
def execute_reader(*args)
|
117
118
|
super
|
118
|
-
|
119
|
+
sql = escape_sql(args)
|
120
|
+
result, ptr = Sqlite3_c.sqlite3_prepare_v2(@connection.db, sql, sql.size + 1)
|
119
121
|
unless result == Sqlite3_c::SQLITE_OK
|
120
|
-
raise QueryError, "Your query failed.\n#{Sqlite3_c.sqlite3_errmsg(@connection.db)}\nQUERY: \"#{
|
122
|
+
raise QueryError, "Your query failed.\n#{Sqlite3_c.sqlite3_errmsg(@connection.db)}\nQUERY: \"#{sql}\""
|
121
123
|
else
|
122
124
|
reader = Reader.new(@connection.db, ptr)
|
123
125
|
|
@@ -131,12 +133,13 @@ module DataObject
|
|
131
133
|
end
|
132
134
|
end
|
133
135
|
|
134
|
-
def execute_non_query
|
136
|
+
def execute_non_query(*args)
|
135
137
|
super
|
136
|
-
|
138
|
+
sql = escape_sql(args)
|
139
|
+
result, reader = Sqlite3_c.sqlite3_prepare_v2(@connection.db, sql, -1)
|
137
140
|
unless result == Sqlite3_c::SQLITE_OK
|
138
141
|
Sqlite3_c.sqlite3_finalize(reader)
|
139
|
-
raise QueryError, "Your query failed.\n#{Sqlite3_c.sqlite3_errmsg(@connection.db)}\nQUERY: \"#{
|
142
|
+
raise QueryError, "Your query failed.\n#{Sqlite3_c.sqlite3_errmsg(@connection.db)}\nQUERY: \"#{sql}\""
|
140
143
|
else
|
141
144
|
exec_result = Sqlite3_c.sqlite3_step(reader)
|
142
145
|
Sqlite3_c.sqlite3_finalize(reader)
|
data/rakefile.rb
CHANGED
@@ -37,7 +37,7 @@ namespace :dm do
|
|
37
37
|
|
38
38
|
end
|
39
39
|
|
40
|
-
PACKAGE_VERSION = '0.2.
|
40
|
+
PACKAGE_VERSION = '0.2.3'
|
41
41
|
|
42
42
|
PACKAGE_FILES = FileList[
|
43
43
|
'README',
|
@@ -48,7 +48,7 @@ PACKAGE_FILES = FileList[
|
|
48
48
|
'spec/**/*.{rb,yaml}',
|
49
49
|
'tasks/**/*',
|
50
50
|
'plugins/**/*'
|
51
|
-
].to_a.reject { |path| path =~
|
51
|
+
].to_a.reject { |path| path =~ /(\/db|Makefile|\.bundle|\.log|\.o)$/ }
|
52
52
|
|
53
53
|
DOCUMENTED_FILES = PACKAGE_FILES.reject do |path|
|
54
54
|
FileTest.directory?(path) || path =~ /(^spec|\/spec|\/swig\_)/
|
@@ -86,6 +86,7 @@ gem_spec = Gem::Specification.new do |s|
|
|
86
86
|
s.requirements << 'none'
|
87
87
|
s.autorequire = 'data_mapper'
|
88
88
|
s.add_dependency('fastthread')
|
89
|
+
s.add_dependency('rspec')
|
89
90
|
|
90
91
|
s.has_rdoc = true
|
91
92
|
s.rdoc_options << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
data/spec/base_spec.rb
CHANGED
@@ -18,6 +18,34 @@ describe DataMapper::Base do
|
|
18
18
|
a.test.should == 'testing!'
|
19
19
|
end
|
20
20
|
|
21
|
+
it "should be dirty" do
|
22
|
+
x = Person.create(:name => 'a')
|
23
|
+
x.should_not be_dirty
|
24
|
+
x.name = 'dslfay'
|
25
|
+
x.should be_dirty
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return a diff" do
|
29
|
+
x = Person.new(:name => 'Sam', :age => 30, :occupation => 'Programmer')
|
30
|
+
y = Person.new(:name => 'Amy', :age => 21, :occupation => 'Programmer')
|
31
|
+
|
32
|
+
diff = (x ^ y)
|
33
|
+
diff.should include(:name)
|
34
|
+
diff.should include(:age)
|
35
|
+
diff[:name].should eql(['Sam', 'Amy'])
|
36
|
+
diff[:age].should eql([30, 21])
|
37
|
+
|
38
|
+
x.destroy!
|
39
|
+
y.destroy!
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should update attributes" do
|
43
|
+
x = Person.create(:name => 'Sam')
|
44
|
+
x.update_attributes(:age => 30).should eql(true)
|
45
|
+
x.age.should eql(30)
|
46
|
+
x.should_not be_dirty
|
47
|
+
end
|
48
|
+
|
21
49
|
end
|
22
50
|
|
23
51
|
describe 'A new record' do
|
data/spec/dataobjects_spec.rb
CHANGED
@@ -15,7 +15,8 @@ describe DataObject do
|
|
15
15
|
# SELECT * FROM tbl WHERE auto IS NULL;
|
16
16
|
it "should return an empty reader" do
|
17
17
|
database.adapter.connection do |connection|
|
18
|
-
|
18
|
+
sql = 'SELECT `id`, `name` FROM `zoos` WHERE (`id` IS NULL)'.gsub(/\`/, database.adapter.class::COLUMN_QUOTING_CHARACTER)
|
19
|
+
command = connection.create_command(sql)
|
19
20
|
|
20
21
|
command.execute_reader do |reader|
|
21
22
|
reader.has_rows?.should eql(ENV['ADAPTER'] == 'mysql')
|
data/spec/load_command_spec.rb
CHANGED
@@ -104,7 +104,7 @@ describe DataMapper::Adapters::Sql::Commands::LoadCommand do
|
|
104
104
|
end
|
105
105
|
|
106
106
|
it 'non-standard options should be considered part of the conditions' do
|
107
|
-
database.
|
107
|
+
database.logger.debug { 'non-standard options should be considered part of the conditions' }
|
108
108
|
zebra = Animal.first(:name => 'Zebra')
|
109
109
|
zebra.name.should == 'Zebra'
|
110
110
|
|
data/spec/serialization_spec.rb
CHANGED
@@ -29,7 +29,7 @@ describe DataMapper::Support::Serialization do
|
|
29
29
|
<zoo id="2">
|
30
30
|
<name>San Diego</name>
|
31
31
|
<notes/>
|
32
|
-
<updated_at>#{san_diego_zoo.updated_at}</updated_at>
|
32
|
+
<updated_at>#{san_diego_zoo.updated_at.dup}</updated_at>
|
33
33
|
</zoo>
|
34
34
|
EOS
|
35
35
|
end
|
@@ -50,7 +50,7 @@ describe DataMapper::Support::Serialization do
|
|
50
50
|
"id": 2,
|
51
51
|
"name": "San Diego",
|
52
52
|
"notes": null,
|
53
|
-
"updated_at": #{san_diego_zoo.updated_at.to_json}
|
53
|
+
"updated_at": #{san_diego_zoo.updated_at.dup.to_json}
|
54
54
|
}
|
55
55
|
EOS
|
56
56
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -6,7 +6,7 @@ def fixtures(name)
|
|
6
6
|
entry = YAML::load_file(File.dirname(__FILE__) + "/fixtures/#{name}.yaml")
|
7
7
|
klass = Kernel::const_get(Inflector.classify(Inflector.singularize(name)))
|
8
8
|
|
9
|
-
database.
|
9
|
+
database.logger.debug { "AUTOMIGRATE: #{klass}" }
|
10
10
|
klass.auto_migrate!
|
11
11
|
|
12
12
|
(entry.kind_of?(Array) ? entry : [entry]).each do |hash|
|