amalgalite 0.10.1-x86-mingw32

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 (100) hide show
  1. data/HISTORY +201 -0
  2. data/LICENSE +29 -0
  3. data/README +51 -0
  4. data/bin/amalgalite-pack +126 -0
  5. data/examples/a.rb +9 -0
  6. data/examples/blob.rb +88 -0
  7. data/examples/bootstrap.rb +36 -0
  8. data/examples/define_aggregate.rb +75 -0
  9. data/examples/define_function.rb +104 -0
  10. data/examples/gem-db.rb +94 -0
  11. data/examples/gems.db +0 -0
  12. data/examples/require_me.rb +11 -0
  13. data/examples/requires.rb +42 -0
  14. data/examples/schema-info.rb +34 -0
  15. data/ext/amalgalite/amalgalite3.c +290 -0
  16. data/ext/amalgalite/amalgalite3.h +151 -0
  17. data/ext/amalgalite/amalgalite3_blob.c +240 -0
  18. data/ext/amalgalite/amalgalite3_constants.c +221 -0
  19. data/ext/amalgalite/amalgalite3_database.c +1148 -0
  20. data/ext/amalgalite/amalgalite3_requires_bootstrap.c +210 -0
  21. data/ext/amalgalite/amalgalite3_statement.c +639 -0
  22. data/ext/amalgalite/extconf.rb +36 -0
  23. data/ext/amalgalite/gen_constants.rb +130 -0
  24. data/ext/amalgalite/sqlite3.c +106729 -0
  25. data/ext/amalgalite/sqlite3.h +5626 -0
  26. data/ext/amalgalite/sqlite3_options.h +4 -0
  27. data/ext/amalgalite/sqlite3ext.h +380 -0
  28. data/gemspec.rb +60 -0
  29. data/lib/amalgalite.rb +43 -0
  30. data/lib/amalgalite/1.8/amalgalite3.so +0 -0
  31. data/lib/amalgalite/1.9/amalgalite3.so +0 -0
  32. data/lib/amalgalite/aggregate.rb +67 -0
  33. data/lib/amalgalite/blob.rb +186 -0
  34. data/lib/amalgalite/boolean.rb +42 -0
  35. data/lib/amalgalite/busy_timeout.rb +47 -0
  36. data/lib/amalgalite/column.rb +97 -0
  37. data/lib/amalgalite/core_ext/kernel/require.rb +21 -0
  38. data/lib/amalgalite/database.rb +947 -0
  39. data/lib/amalgalite/function.rb +61 -0
  40. data/lib/amalgalite/index.rb +43 -0
  41. data/lib/amalgalite/packer.rb +226 -0
  42. data/lib/amalgalite/paths.rb +70 -0
  43. data/lib/amalgalite/profile_tap.rb +131 -0
  44. data/lib/amalgalite/progress_handler.rb +21 -0
  45. data/lib/amalgalite/requires.rb +120 -0
  46. data/lib/amalgalite/schema.rb +191 -0
  47. data/lib/amalgalite/sqlite3.rb +6 -0
  48. data/lib/amalgalite/sqlite3/constants.rb +80 -0
  49. data/lib/amalgalite/sqlite3/database/function.rb +48 -0
  50. data/lib/amalgalite/sqlite3/database/status.rb +68 -0
  51. data/lib/amalgalite/sqlite3/status.rb +60 -0
  52. data/lib/amalgalite/sqlite3/version.rb +37 -0
  53. data/lib/amalgalite/statement.rb +414 -0
  54. data/lib/amalgalite/table.rb +90 -0
  55. data/lib/amalgalite/taps.rb +2 -0
  56. data/lib/amalgalite/taps/console.rb +27 -0
  57. data/lib/amalgalite/taps/io.rb +71 -0
  58. data/lib/amalgalite/trace_tap.rb +35 -0
  59. data/lib/amalgalite/type_map.rb +63 -0
  60. data/lib/amalgalite/type_maps/default_map.rb +167 -0
  61. data/lib/amalgalite/type_maps/storage_map.rb +40 -0
  62. data/lib/amalgalite/type_maps/text_map.rb +22 -0
  63. data/lib/amalgalite/version.rb +37 -0
  64. data/lib/amalgalite/view.rb +26 -0
  65. data/spec/aggregate_spec.rb +169 -0
  66. data/spec/amalgalite_spec.rb +4 -0
  67. data/spec/blob_spec.rb +81 -0
  68. data/spec/boolean_spec.rb +23 -0
  69. data/spec/busy_handler.rb +165 -0
  70. data/spec/database_spec.rb +494 -0
  71. data/spec/default_map_spec.rb +87 -0
  72. data/spec/function_spec.rb +94 -0
  73. data/spec/integeration_spec.rb +111 -0
  74. data/spec/packer_spec.rb +60 -0
  75. data/spec/paths_spec.rb +28 -0
  76. data/spec/progress_handler_spec.rb +105 -0
  77. data/spec/requires_spec.rb +23 -0
  78. data/spec/rtree_spec.rb +71 -0
  79. data/spec/schema_spec.rb +120 -0
  80. data/spec/spec_helper.rb +27 -0
  81. data/spec/sqlite3/constants_spec.rb +65 -0
  82. data/spec/sqlite3/database_status_spec.rb +36 -0
  83. data/spec/sqlite3/status_spec.rb +18 -0
  84. data/spec/sqlite3/version_spec.rb +14 -0
  85. data/spec/sqlite3_spec.rb +53 -0
  86. data/spec/statement_spec.rb +161 -0
  87. data/spec/storage_map_spec.rb +41 -0
  88. data/spec/tap_spec.rb +59 -0
  89. data/spec/text_map_spec.rb +23 -0
  90. data/spec/type_map_spec.rb +17 -0
  91. data/spec/version_spec.rb +15 -0
  92. data/tasks/announce.rake +43 -0
  93. data/tasks/config.rb +107 -0
  94. data/tasks/distribution.rake +77 -0
  95. data/tasks/documentation.rake +32 -0
  96. data/tasks/extension.rake +141 -0
  97. data/tasks/rspec.rake +33 -0
  98. data/tasks/rubyforge.rake +59 -0
  99. data/tasks/utils.rb +80 -0
  100. metadata +237 -0
@@ -0,0 +1,494 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require File.expand_path( File.join( File.dirname(__FILE__), 'spec_helper'))
4
+
5
+ $: << File.expand_path(File.join(File.dirname(__FILE__),"..","lib"))
6
+ require 'amalgalite'
7
+ require 'amalgalite/taps/io'
8
+ require 'amalgalite/taps/console'
9
+ require 'amalgalite/database'
10
+
11
+ describe Amalgalite::Database do
12
+ before(:each) do
13
+ @schema = IO.read( SpecInfo.test_schema_file )
14
+ @iso_db_file = SpecInfo.make_iso_db
15
+ @iso_db = Amalgalite::Database.new( SpecInfo.make_iso_db )
16
+ end
17
+
18
+ after(:each) do
19
+ File.unlink SpecInfo.test_db if File.exist?( SpecInfo.test_db )
20
+ @iso_db.close
21
+ File.unlink @iso_db_file if File.exist?( @iso_db_file )
22
+ end
23
+
24
+ it "can create a new database" do
25
+ db = Amalgalite::Database.new( SpecInfo.test_db )
26
+ db.instance_of?(Amalgalite::Database)
27
+ db.api.instance_of?(Amalgalite::SQLite3::Database)
28
+ File.exist?( SpecInfo.test_db ).should eql(true)
29
+ end
30
+
31
+ it "creates a new UTF-8 database (need exec to check pragma encoding)" do
32
+ db = Amalgalite::Database.new( SpecInfo.test_db )
33
+ db.execute_batch( @schema );
34
+ db.should_not be_utf16
35
+ db.encoding.should eql("UTF-8")
36
+ end
37
+
38
+ it "creates a new UTF-16 database (need exec to check pragma encoding)"
39
+
40
+ it "raises an error if the file does not exist and the database is opened with a non-create mode" do
41
+ lambda { Amalgalite::Database.new( SpecInfo.test_db, "r") }.should raise_error(Amalgalite::SQLite3::Error)
42
+ lambda { Amalgalite::Database.new( SpecInfo.test_db, "r+") }.should raise_error(Amalgalite::SQLite3::Error)
43
+ end
44
+
45
+ it "raises an error if an invalid mode is used" do
46
+ lambda { Amalgalite::Database.new( SpecInfo.test_db, "b+" ) }.should raise_error(Amalgalite::Database::InvalidModeError)
47
+ end
48
+
49
+ it "can be in autocommit mode, and is by default" do
50
+ @iso_db.autocommit?.should eql(true)
51
+ end
52
+
53
+ it "reports false for autocommit? when inside a transaction" do
54
+ @iso_db.execute(" BEGIN ")
55
+ @iso_db.autocommit?.should eql(false)
56
+ @iso_db.in_transaction?.should eql(true)
57
+ @iso_db.execute(" COMMIT")
58
+ @iso_db.in_transaction?.should eql(false)
59
+ end
60
+
61
+ it "prepares a statment" do
62
+ db = Amalgalite::Database.new( SpecInfo.test_db )
63
+ stmt = db.prepare("SELECT datetime()")
64
+ stmt.instance_of?(Amalgalite::Statement)
65
+ stmt.api.instance_of?(Amalgalite::SQLite3::Statement)
66
+ end
67
+
68
+ it "raises an error on invalid syntax when preparing a bad sql statement" do
69
+ db = Amalgalite::Database.new( SpecInfo.test_db )
70
+ lambda { db.prepare("SELECT nothing FROM stuf") }.should raise_error(Amalgalite::SQLite3::Error)
71
+ end
72
+
73
+ it "closes normally" do
74
+ db = Amalgalite::Database.new( SpecInfo.test_db )
75
+ lambda { db.close }.should_not raise_error( Amalgalite::SQLite3::Error )
76
+ end
77
+
78
+ it "returns the id of the last inserted row" do
79
+ db = Amalgalite::Database.new( SpecInfo.test_db )
80
+ db.last_insert_rowid.should eql(0)
81
+ end
82
+
83
+ it "is in autocommit mode by default" do
84
+ db = Amalgalite::Database.new( SpecInfo.test_db )
85
+ db.should be_autocommit
86
+ end
87
+
88
+ it "report the number of rows changed with an insert" do
89
+ db = Amalgalite::Database.new( SpecInfo.test_db )
90
+ db.execute_batch <<-sql
91
+ CREATE TABLE t1( x );
92
+ INSERT INTO t1( x ) values ( 1 );
93
+ INSERT INTO t1( x ) values ( 2 );
94
+ INSERT INTO t1( x ) values ( 3 );
95
+ sql
96
+
97
+ db.row_changes.should eql(1)
98
+ db.total_changes.should eql(3)
99
+ db.close
100
+ end
101
+
102
+ it "reports the number of rows deleted" do
103
+ db = Amalgalite::Database.new( SpecInfo.test_db )
104
+ db.execute_batch <<-sql
105
+ CREATE TABLE t1( x );
106
+ INSERT INTO t1( x ) values ( 1 );
107
+ INSERT INTO t1( x ) values ( 2 );
108
+ INSERT INTO t1( x ) values ( 3 );
109
+ DELETE FROM t1 where x < 3;
110
+ sql
111
+ db.row_changes.should eql(2)
112
+ db.close
113
+ end
114
+
115
+ it "can immediately execute an sql statement " do
116
+ db = Amalgalite::Database.new( SpecInfo.test_db )
117
+ db.execute( "CREATE TABLE t1( x, y, z )" ).should be_empty
118
+ end
119
+
120
+ it "can execute a batch of commands" do
121
+ db = Amalgalite::Database.new( SpecInfo.test_db )
122
+ db.execute_batch( @schema ).should eql(5)
123
+ end
124
+
125
+ it "returns an empty array when there are no results" do
126
+ row = @iso_db.execute("SELECT * from subcountry where country = 'Antarctica'")
127
+ row.should be_empty
128
+ end
129
+
130
+ it "traces the execution of code" do
131
+ db = Amalgalite::Database.new( SpecInfo.test_db )
132
+ sql = "CREATE TABLE trace_test( x, y, z)"
133
+ s = db.trace_tap = ::Amalgalite::Taps::StringIO.new
134
+ db.execute( sql )
135
+ db.trace_tap.string.should eql("registered as trace tap\n#{sql}\n")
136
+ db.trace_tap = nil
137
+ s.string.should eql("registered as trace tap\n#{sql}\nunregistered as trace tap\n")
138
+ end
139
+
140
+ it "raises an exception if the wrong type of object is used for tracing" do
141
+ db = Amalgalite::Database.new( SpecInfo.test_db )
142
+ lambda { db.trace_tap = Object.new }.should raise_error(Amalgalite::Error)
143
+ end
144
+
145
+ it "raises an exception if the wrong type of object is used for profile" do
146
+ db = Amalgalite::Database.new( SpecInfo.test_db )
147
+ lambda { db.profile_tap = Object.new }.should raise_error(Amalgalite::Error)
148
+ end
149
+
150
+ it "profiles the execution of code" do
151
+ db = Amalgalite::Database.new( SpecInfo.test_db )
152
+ s = db.profile_tap = ::Amalgalite::Taps::StringIO.new
153
+ db.execute_batch( @schema )
154
+ db.profile_tap.samplers.size.should eql(6)
155
+ db.profile_tap = nil
156
+ s.string.should =~ /unregistered as profile tap/m
157
+ end
158
+
159
+ it "#execute yields each row when called with a block" do
160
+ count = 0
161
+ @iso_db.execute( "SELECT * FROM country LIMIT 10") do |row|
162
+ count += 1
163
+ end
164
+ count.should eql(10)
165
+ end
166
+
167
+ it "#pragma yields each row when called with a block" do
168
+ count = 0
169
+ @iso_db.pragma( "index_info( subcountry_country )" ) do |row|
170
+ count += 1
171
+ end
172
+ count.should eql(1)
173
+ end
174
+
175
+ it "can use something that responds to 'write' as a tap" do
176
+ db = Amalgalite::Database.new( SpecInfo.test_db )
177
+ s2 = db.trace_tap = StringIO.new
178
+ s2.string.should eql("registered as trace tap")
179
+ end
180
+
181
+ it "can clear all registered taps" do
182
+ db = Amalgalite::Database.new( SpecInfo.test_db )
183
+ s = db.profile_tap = ::Amalgalite::Taps::StringIO.new
184
+ db.trace_tap = s
185
+ db.execute_batch( @schema )
186
+ db.profile_tap.samplers.size.should eql(6)
187
+ db.clear_taps!
188
+ s.string.should =~ /unregistered as trace tap/m
189
+ s.string.should =~ /unregistered as profile tap/m
190
+ end
191
+
192
+ it "allows nested transactions even if SQLite under the covers does not" do
193
+ db = Amalgalite::Database.new( SpecInfo.test_db )
194
+ r = db.transaction do |db2|
195
+ r2 = db.transaction { 42 }
196
+ r2.should eql(42)
197
+ r2
198
+ end
199
+ r.should eql(42)
200
+ end
201
+
202
+ %w[ transaction deferred_transaction immediate_transaction exclusive_transaction ].each do |trans|
203
+ it "returns the result of the #{trans} when a block is yielded" do
204
+ db = Amalgalite::Database.new( SpecInfo.test_db )
205
+ (db.send( trans ){ 42 }).should eql(42)
206
+ end
207
+ end
208
+
209
+ it "#reload_schema!" do
210
+ @iso_db = Amalgalite::Database.new( SpecInfo.make_iso_db )
211
+ schema = @iso_db.schema
212
+ schema.instance_of?( Amalgalite::Schema ).should eql(true)
213
+ s2 = @iso_db.reload_schema!
214
+ s2.object_id.should_not eql(schema.object_id)
215
+ end
216
+
217
+ it "can rollback a transaction" do
218
+ @iso_db.transaction
219
+ r = @iso_db.execute("SELECT count(1) as cnt FROM country");
220
+ r.first['cnt'].should eql(242)
221
+ @iso_db.execute("DELETE FROM country")
222
+ r = @iso_db.execute("SELECT count(1) as cnt FROM country");
223
+ r.first['cnt'].should eql(0)
224
+ @iso_db.rollback
225
+
226
+ r = @iso_db.execute("SELECT count(1) as cnt FROM country");
227
+ r.first['cnt'].should eql(242)
228
+ end
229
+
230
+ it "rolls back if an exception happens during a transaction block" do
231
+ begin
232
+ @iso_db.transaction do |db|
233
+ r = db.execute("SELECT count(1) as cnt FROM country");
234
+ r.first['cnt'].should eql(242)
235
+ db.execute("DELETE FROM country")
236
+ db.in_transaction?.should eql(true)
237
+ raise "testing rollback"
238
+ end
239
+ rescue => e
240
+ @iso_db.in_transaction?.should eql(false)
241
+ @iso_db.execute("SELECT count(1) as cnt FROM country").first['cnt'].should eql(242)
242
+ end
243
+ end
244
+
245
+ it "commits if an exception happens during a transaction block but is rescued within the block" do
246
+ @iso_db.transaction do |db|
247
+ begin
248
+ r = db.execute("SELECT count(1) as cnt FROM country");
249
+ r.first['cnt'].should eql(242)
250
+ db.execute("DELETE FROM country")
251
+ db.in_transaction?.should eql(true)
252
+ raise "testing rollback"
253
+ rescue => e
254
+ e.message.should == "testing rollback"
255
+ end
256
+ $!.should == nil
257
+ end
258
+ @iso_db.in_transaction?.should eql(false)
259
+ @iso_db.first_value_from("select count(1) as cnt from country").should eql(0)
260
+ end
261
+
262
+ it "does not reraise an exception that exits before the transaction starts" do
263
+ class MyExceptionTest < RuntimeError; end
264
+ db = Amalgalite::Database.new( ":memory:" )
265
+
266
+ lambda {
267
+ begin
268
+ raise MyExceptionTest, "James pointed this out"
269
+ rescue MyExceptionTest
270
+ db.transaction("EXCLUSIVE") { }
271
+ end
272
+ }.should_not raise_error( MyExceptionTest )
273
+ end
274
+
275
+ describe "#define_function" do
276
+ it "does not allow mixing of arbitrary and mandatory arguments to an SQL function" do
277
+ class FunctionTest2 < ::Amalgalite::Function
278
+ def initialize
279
+ super( 'ftest2', -2 )
280
+ end
281
+ def call( a, *args ); end
282
+ end
283
+ lambda { @iso_db.define_function("ftest2", FunctionTest2.new ) }.should raise_error( ::Amalgalite::Database::FunctionError )
284
+ end
285
+
286
+ it "does not allow outrageous arity" do
287
+ class FunctionTest3 < ::Amalgalite::Function
288
+ def initialize
289
+ super( 'ftest3', 128 )
290
+ end
291
+ def call( *args) ; end
292
+ end
293
+ lambda { @iso_db.define_function("ftest3", FunctionTest3.new ) }.should raise_error( ::Amalgalite::SQLite3::Error )
294
+ end
295
+
296
+ end
297
+
298
+ describe "#remove_function" do
299
+ it "unregisters a single function by name and arity" do
300
+ @iso_db.define_function( "rtest1" ) do
301
+ "rtest1 called"
302
+ end
303
+
304
+ @iso_db.functions.size.should eql(1 )
305
+
306
+ r = @iso_db.execute( "select rtest1() AS r" )
307
+ r.first['r'].should eql("rtest1 called")
308
+ #@iso_db.remove_function("rtest1", -1)
309
+ # the arity of rtest1 is different in 1.9 vs. 1.8
310
+ @iso_db.remove_function("rtest1")
311
+
312
+ lambda { @iso_db.execute( "select rtest1() as r" )}.should raise_error( ::Amalgalite::SQLite3::Error, /no such function: rtest1/ )
313
+ @iso_db.functions.size.should eql(0)
314
+ end
315
+
316
+ it "unregisters a function by instances" do
317
+ class FunctionTest5 < ::Amalgalite::Function
318
+ def initialize
319
+ super( 'ftest5', 0)
320
+ end
321
+ def call( *args) "ftest5 called"; end
322
+ end
323
+ @iso_db.define_function("ftest5", FunctionTest5.new )
324
+ @iso_db.functions.size.should eql(1)
325
+ r = @iso_db.execute( "select ftest5() AS r" )
326
+ r.first['r'].should eql("ftest5 called")
327
+ @iso_db.remove_function("ftest5", FunctionTest5.new )
328
+ lambda { @iso_db.execute( "select ftest5() as r" )}.should raise_error( ::Amalgalite::SQLite3::Error, /no such function: ftest5/ )
329
+ @iso_db.functions.size.should eql(0)
330
+ end
331
+
332
+ it "unregisters all functions with the same name" do
333
+ @iso_db.function( "rtest" ) do |x|
334
+ "rtest #{x} called"
335
+ end
336
+
337
+ @iso_db.function( "rtest" ) do ||
338
+ "rtest/0 called"
339
+ end
340
+
341
+ @iso_db.functions.size.should eql(2)
342
+ r = @iso_db.execute( "select rtest(1) AS r")
343
+ r.first['r'].should eql("rtest 1 called")
344
+ r = @iso_db.execute( "select rtest() AS r")
345
+ r.first['r'].should eql("rtest/0 called")
346
+ @iso_db.remove_function( 'rtest' )
347
+ lambda { @iso_db.execute( "select rtest(1) AS r") }.should raise_error( ::Amalgalite::SQLite3::Error )
348
+ lambda { @iso_db.execute( "select rtest() AS r") }.should raise_error( ::Amalgalite::SQLite3::Error )
349
+ @iso_db.functions.size.should eql(0)
350
+ end
351
+ end
352
+
353
+ it "can interrupt another thread that is also running in this database" do
354
+ executions = 0
355
+ other = Thread.new( @iso_db ) do |db|
356
+ loop do
357
+ begin
358
+ db.execute("select count(id) from country")
359
+ executions += 1
360
+ rescue => e
361
+ Thread.current[:had_error] = e
362
+ break
363
+ end
364
+ end
365
+ end
366
+
367
+ rudeness = Thread.new( @iso_db ) do |db|
368
+ sleep 0.05
369
+ @iso_db.interrupt!
370
+ end
371
+
372
+ rudeness.join
373
+
374
+ executions.should > 10
375
+ other[:had_error].should be_an_instance_of( ::Amalgalite::SQLite3::Error )
376
+ other[:had_error].message.should =~ / interrupted/
377
+ end
378
+
379
+ it "savepoints are considered 'in_transaction'" do
380
+ @iso_db.savepoint( 'test1' ) do |db|
381
+ db.should be_in_transaction
382
+ end
383
+ end
384
+
385
+ it "releases a savepoint" do
386
+ us_sub = @iso_db.execute( "select count(1) as cnt from subcountry where country = 'US'" ).first['cnt']
387
+ us_sub.should eql(57)
388
+ other_sub = @iso_db.execute( "select count(1) as cnt from subcountry where country != 'US'" ).first['cnt']
389
+
390
+ @iso_db.transaction
391
+ @iso_db.savepoint( "t1" ) do |s|
392
+ s.execute("DELETE FROM subcountry where country = 'US'")
393
+ end
394
+
395
+ all_sub = @iso_db.execute("SELECT count(*) as cnt from subcountry").first['cnt']
396
+
397
+ all_sub.should eql(other_sub)
398
+ @iso_db.rollback
399
+ all_sub = @iso_db.execute("SELECT count(*) as cnt from subcountry").first['cnt']
400
+ all_sub.should eql(( us_sub + other_sub ))
401
+
402
+ end
403
+
404
+ it "rolls back a savepoint" do
405
+ all_sub = @iso_db.execute("SELECT count(*) as cnt from subcountry").first['cnt']
406
+ lambda {
407
+ @iso_db.savepoint( "t1" ) do |s|
408
+ s.execute("DELETE FROM subcountry where country = 'US'")
409
+ raise "sample error"
410
+ end
411
+ }.should raise_error( StandardError, /sample error/ )
412
+
413
+ @iso_db.execute("SELECT count(*) as cnt from subcountry").first['cnt'].should eql(all_sub)
414
+ end
415
+
416
+ it "rolling back the outermost savepoint is still 'in_transaction'" do
417
+ @iso_db.savepoint( "t1" )
418
+ @iso_db.execute("DELETE FROM subcountry where country = 'US'")
419
+ @iso_db.rollback_to( "t1" )
420
+ @iso_db.should be_in_transaction
421
+ @iso_db.rollback
422
+ @iso_db.should_not be_in_transaction
423
+ end
424
+
425
+ it "can escape quoted strings" do
426
+ @iso_db.escape( "It's a happy day!" ).should eql("It''s a happy day!")
427
+ end
428
+
429
+ it "can quote and escape single quoted strings" do
430
+ @iso_db.quote( "It's a happy day!" ).should eql("'It''s a happy day!'")
431
+ end
432
+
433
+ it "can escape a symbol" do
434
+ @iso_db.escape( :stuff ).should eql("stuff")
435
+ end
436
+
437
+ it "can quote a symbol" do
438
+ @iso_db.quote( :stuff ).should eql("'stuff'")
439
+ end
440
+
441
+ it "returns the first row of results as a convenience" do
442
+ row = @iso_db.first_row_from("SELECT c.name, c.two_letter, count(*) AS count
443
+ FROM country c
444
+ JOIN subcountry sc
445
+ ON c.two_letter = sc.country
446
+ GROUP BY c.name, c.two_letter
447
+ ORDER BY count DESC")
448
+ row.length.should eql(3)
449
+ row[0].should eql("United Kingdom")
450
+ row[1].should eql("GB")
451
+ row[2].should eql(232)
452
+ row['name'].should eql("United Kingdom")
453
+ row['two_letter'].should eql("GB")
454
+ row['count'].should eql(232)
455
+ end
456
+
457
+ it "returns and empty row if there are no results for the first row" do
458
+ row = @iso_db.first_row_from("SELECT * from subcountry where country = 'Antarctica'")
459
+ row.should be_empty
460
+ end
461
+
462
+ it "returns nil if there is no value in the first value" do
463
+ val = @iso_db.first_value_from("select * from subcountry where country = 'Antarctica'" )
464
+ val.should eql(nil)
465
+ end
466
+
467
+ it "returns the first value of results as a conveinience" do
468
+ val = @iso_db.first_value_from("SELECT count(*) from subcountry ")
469
+ val.should eql(3995)
470
+ end
471
+
472
+ it "replicates a database to memory" do
473
+ mem_db = @iso_db.replicate_to( ":memory:" )
474
+ @iso_db.close
475
+ val = mem_db.first_value_from("SELECT count(*) from subcountry" )
476
+ val.should eql(3995)
477
+ end
478
+
479
+ it "replicates a database to a database file" do
480
+ all_sub = @iso_db.execute("SELECT count(*) as cnt from subcountry").first['cnt']
481
+
482
+ fdb = Amalgalite::Database.new( SpecInfo.test_db )
483
+ @iso_db.replicate_to( fdb )
484
+ @iso_db.close
485
+
486
+ File.exist?( SpecInfo.test_db ).should == true
487
+ fdb.execute("SELECT count(*) as cnt from subcountry").first['cnt'].should == all_sub
488
+ end
489
+
490
+ it "raises an error if it is given an invalid location to replicate to" do
491
+ lambda { @iso_db.replicate_to( false ) }.should raise_error( ArgumentError, /must be a String or a Database/ )
492
+ end
493
+
494
+ end