amalgalite 0.5.1-x86-mswin32-60 → 0.6.0-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,8 +8,8 @@ module Amalgalite
8
8
  module Version
9
9
 
10
10
  MAJOR = 0
11
- MINOR = 5
12
- BUILD = 1
11
+ MINOR = 6
12
+ BUILD = 0
13
13
 
14
14
  #
15
15
  # return the Version as an array of MAJOR, MINOR, BUILD
data/lib/amalgalite3.so CHANGED
Binary file
@@ -0,0 +1,168 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ $: << File.expand_path(File.join(File.dirname(__FILE__),"..","lib"))
5
+ require 'amalgalite'
6
+ require 'amalgalite/database'
7
+
8
+ class AggregateTest1 < ::Amalgalite::Aggregate
9
+ def initialize
10
+ @name = 'atest1'
11
+ @arity = -1
12
+ @count = 0
13
+ end
14
+ def step( *args )
15
+ @count += 1
16
+ end
17
+ def finalize
18
+ return @count
19
+ end
20
+ end
21
+
22
+
23
+ describe "Aggregate SQL Functions" do
24
+
25
+ before(:each) do
26
+ @schema = IO.read( SpecInfo.test_schema_file )
27
+ @iso_db_file = SpecInfo.make_iso_db
28
+ @iso_db = Amalgalite::Database.new( SpecInfo.make_iso_db )
29
+ end
30
+
31
+ after(:each) do
32
+ File.unlink SpecInfo.test_db if File.exist?( SpecInfo.test_db )
33
+ @iso_db.close
34
+ File.unlink @iso_db_file if File.exist?( @iso_db_file )
35
+ end
36
+
37
+
38
+ it "must have a finalize method implemented" do
39
+ ag = ::Amalgalite::Aggregate.new
40
+ lambda { ag.finalize }.should raise_error( NotImplementedError, /Aggregate#finalize must be implemented/ )
41
+ end
42
+
43
+ it "can define a custom SQL aggregate as a class with N params" do
44
+ @iso_db.define_aggregate("atest1", AggregateTest1 )
45
+ r = @iso_db.execute("SELECT atest1(id,name) as a, count(*) as c FROM country")
46
+ r.first['a'].should == r.first['c']
47
+ r.first['a'].should == 242
48
+ end
49
+
50
+ it "can remove a custom SQL aggregate by class" do
51
+ @iso_db.define_aggregate("atest1", AggregateTest1 )
52
+ @iso_db.aggregates.size.should == 1
53
+ r = @iso_db.execute("SELECT atest1(id,name) as a, count(*) as c FROM country")
54
+ r.first['a'].should == r.first['c']
55
+ r.first['a'].should == 242
56
+ @iso_db.remove_aggregate( "atest1", AggregateTest1 )
57
+ @iso_db.aggregates.size.should == 0
58
+ lambda{ @iso_db.execute("SELECT atest1(id,name) as a, count(*) as c FROM country") }.should raise_error(::Amalgalite::SQLite3::Error, /no such function: atest1/ )
59
+ end
60
+
61
+ it "can remove a custom SQL aggregate by arity" do
62
+ @iso_db.define_aggregate("atest1", AggregateTest1 )
63
+ @iso_db.aggregates.size.should == 1
64
+ r = @iso_db.execute("SELECT atest1(id,name) as a, count(*) as c FROM country")
65
+ r.first['a'].should == r.first['c']
66
+ r.first['a'].should == 242
67
+ @iso_db.remove_aggregate( "atest1", -1)
68
+ @iso_db.aggregates.size.should == 0
69
+ lambda{ @iso_db.execute("SELECT atest1(id,name) as a, count(*) as c FROM country") }.should raise_error(::Amalgalite::SQLite3::Error, /no such function: atest1/ )
70
+ end
71
+
72
+ it "can remove all custom SQL aggregates with the same name" do
73
+ class AT2 < AggregateTest1
74
+ def arity() 1; end
75
+ end
76
+ @iso_db.define_aggregate("atest1", AggregateTest1 )
77
+ @iso_db.define_aggregate("atest1", AT2)
78
+ @iso_db.aggregates.size.should == 2
79
+ r = @iso_db.execute("SELECT atest1(id,name) as a, atest1(id), count(*) as c FROM country")
80
+ r.first['a'].should == r.first['c']
81
+ r.first['a'].should == 242
82
+ @iso_db.remove_aggregate( "atest1" )
83
+ @iso_db.aggregates.size.should == 0
84
+ lambda{ @iso_db.execute("SELECT atest1(id,name) as a, count(*) as c FROM country") }.should raise_error(::Amalgalite::SQLite3::Error, /no such function: atest1/ )
85
+ end
86
+
87
+
88
+
89
+ it "does not allow mixing of arbitrary and mandatory arguments to an SQL function" do
90
+ class AggregateTest2 < AggregateTest1
91
+ def name() "atest2"; end
92
+ def arity() -2; end
93
+ end
94
+ lambda { @iso_db.define_aggregate("atest2", AggregateTest2 ) }.should raise_error( ::Amalgalite::Database::AggregateError,
95
+ /Use only mandatory or arbitrary parameters in an SQL Aggregate, not both/ )
96
+ end
97
+
98
+ it "does not allow outrageous arity" do
99
+ class AggregateTest3 < AggregateTest1
100
+ def name() "atest3"; end
101
+ def arity() 101; end
102
+ end
103
+ lambda { @iso_db.define_aggregate("atest3", AggregateTest3 ) }.should raise_error( ::Amalgalite::SQLite3::Error, /SQLITE_ERROR .* bad parameters/ )
104
+ end
105
+
106
+ it "does not allow registering a function which does not match the defined name " do
107
+ class AggregateTest4 < AggregateTest1
108
+ def name() "name_mismatch"; end
109
+ end
110
+ lambda { @iso_db.define_aggregate("atest4", AggregateTest4 ) }.should raise_error( ::Amalgalite::Database::AggregateError,
111
+ /Aggregate implementation name 'name_mismatch' does not match defined name 'atest4'/)
112
+ end
113
+
114
+ it "handles an error being thrown during the step function" do
115
+ class AggregateTest5 < AggregateTest1
116
+ def initialize
117
+ @name = "atest5"
118
+ @arity = -1
119
+ @count = 0
120
+ end
121
+
122
+ def step( *args )
123
+ @count += 1
124
+ if @count > 50 then
125
+ raise "Stepwise error!" if @count > 50
126
+ end
127
+ end
128
+
129
+ end
130
+
131
+ @iso_db.define_aggregate( "atest5", AggregateTest5 )
132
+ lambda { @iso_db.execute( "SELECT atest5(*) AS a FROM country" ) }.should raise_error( ::Amalgalite::SQLite3::Error, /Stepwise error!/ )
133
+ end
134
+
135
+ it "handles an error being thrown during the finalize function" do
136
+ class AggregateTest6 < AggregateTest1
137
+ def initialize
138
+ @name = "atest6"
139
+ @count = 0
140
+ @arity = -1
141
+ end
142
+ def finalize
143
+ raise "Finalize error!"
144
+ end
145
+ end
146
+ @iso_db.define_aggregate( "atest6", AggregateTest6 )
147
+ lambda { @iso_db.execute( "SELECT atest6(*) AS a FROM country" ) }.should raise_error( ::Amalgalite::SQLite3::Error, /Finalize error!/ )
148
+ end
149
+
150
+ it "handles an error being thrown during initialization in the C extension" do
151
+ class AggregateTest7 < AggregateTest1
152
+ @@instance_count = 0
153
+ def initialize
154
+ @name = "atest7"
155
+ @count = 0
156
+ @arity = -1
157
+ if @@instance_count > 0 then
158
+ raise "Initialization error!"
159
+ else
160
+ @@instance_count += 1
161
+ end
162
+ end
163
+ end
164
+ @iso_db.define_aggregate( "atest7", AggregateTest7 )
165
+ lambda { @iso_db.execute( "SELECT atest7(*) AS a FROM country" ) }.should raise_error( ::Amalgalite::SQLite3::Error, /Initialization error!/ )
166
+ end
167
+ end
168
+
@@ -0,0 +1,164 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ $: << File.expand_path(File.join(File.dirname(__FILE__),"..","lib"))
5
+ require 'amalgalite'
6
+ require 'amalgalite/database'
7
+
8
+ class BusyHandlerTest < Amalgalite::BusyHandler
9
+ attr_accessor :call_count
10
+ def initialize( max = 5 )
11
+ @max = max
12
+ @call_count = 0
13
+ end
14
+
15
+ def call( c )
16
+ @call_count += 1
17
+ if call_count >= @max then
18
+ return false
19
+ end
20
+ return true
21
+ end
22
+ end
23
+
24
+ describe "Busy Handlers" do
25
+ before(:each) do
26
+ @db_name = SpecInfo.make_iso_db
27
+ @read_db = Amalgalite::Database.new( @db_name )
28
+ @write_db = Amalgalite::Database.new( @db_name )
29
+ end
30
+
31
+ after(:each) do
32
+ @write_db.close
33
+ @read_db.close
34
+ File.unlink @db_name if File.exist?( @db_name )
35
+ end
36
+
37
+ it "raises NotImplemented if #call is not overwritten" do
38
+ bh = ::Amalgalite::BusyHandler.new
39
+ lambda { bh.call( 42 ) }.should raise_error( ::NotImplementedError, /The busy handler call\(N\) method must be implemented/ )
40
+ end
41
+
42
+ it "can be registered as block" do
43
+ call_count = 0
44
+ @write_db.busy_handler do |x|
45
+ call_count = x
46
+ if call_count >= 20 then
47
+ false
48
+ else
49
+ true
50
+ end
51
+ end
52
+
53
+ # put a read lock on the database
54
+ @read_db.transaction( "DEFERRED" )
55
+
56
+ # put a read lock on the database, but want to go to an exclusive
57
+ @write_db.transaction( "IMMEDIATE" )
58
+
59
+ # do a read operation
60
+ @read_db.execute("SELECT count(*) FROM subcountry")
61
+
62
+ # attempt to do a write operation and commit it
63
+ @write_db.execute("DELETE FROM subcountry")
64
+ lambda { @write_db.execute("COMMIT"); }.should raise_error( ::Amalgalite::SQLite3::Error, /database is locked/ )
65
+ call_count.should == 20
66
+ end
67
+
68
+ it "can be registered as lambda" do
69
+ call_count = 0
70
+ callable = lambda do |x|
71
+ call_count = x
72
+ if call_count >= 40 then
73
+ false
74
+ else
75
+ true
76
+ end
77
+ end
78
+
79
+ @write_db.busy_handler( callable )
80
+
81
+ # put a read lock on the database
82
+ @read_db.transaction( "DEFERRED" )
83
+
84
+ # put a read lock on the database, but want to go to an exclusive
85
+ @write_db.transaction( "IMMEDIATE" )
86
+
87
+ # do a read operation
88
+ @read_db.execute("SELECT count(*) FROM subcountry")
89
+
90
+ # attempt to do a write operation and commit it
91
+ @write_db.execute("DELETE FROM subcountry")
92
+ lambda { @write_db.execute("COMMIT"); }.should raise_error( ::Amalgalite::SQLite3::Error, /database is locked/ )
93
+ call_count.should == 40
94
+ end
95
+
96
+ it "can be registered as a class" do
97
+ h = BusyHandlerTest.new( 10 )
98
+ @write_db.busy_handler( h )
99
+
100
+ # put a read lock on the database
101
+ @read_db.transaction( "DEFERRED" )
102
+
103
+ # put a read lock on the database, but want to go to an exclusive
104
+ @write_db.transaction( "IMMEDIATE" )
105
+
106
+ # do a read operation
107
+ @read_db.execute("SELECT count(*) FROM subcountry")
108
+
109
+ # attempt to do a write operation and commit it
110
+ @write_db.execute("DELETE FROM subcountry")
111
+ lambda { @write_db.execute("COMMIT"); }.should raise_error( ::Amalgalite::SQLite3::Error, /database is locked/ )
112
+ h.call_count.should == 10
113
+ end
114
+
115
+ it "has a default timeout class available " do
116
+ to = ::Amalgalite::BusyTimeout.new( 5, 10 )
117
+ @write_db.busy_handler( to )
118
+
119
+ # put a read lock on the database
120
+ @read_db.transaction( "DEFERRED" )
121
+
122
+ # put a read lock on the database, but want to go to an exclusive
123
+ @write_db.transaction( "IMMEDIATE" )
124
+
125
+ # do a read operation
126
+ @read_db.execute("SELECT count(*) FROM subcountry")
127
+
128
+ # attempt to do a write operation and commit it
129
+ @write_db.execute("DELETE FROM subcountry")
130
+ before = Time.now
131
+ lambda { @write_db.execute("COMMIT"); }.should raise_error( ::Amalgalite::SQLite3::Error, /database is locked/ )
132
+ after = Time.now
133
+ to.call_count.should > 5
134
+ (after - before).should > 0.05
135
+ end
136
+
137
+ it "cannot register a block with the wrong arity" do
138
+ lambda do
139
+ @write_db.define_busy_handler { |x,y| puts "What!" }
140
+ end.should raise_error( ::Amalgalite::Database::BusyHandlerError, /A busy handler expects 1 and only 1 argument/ )
141
+ end
142
+
143
+ it "can remove a busy handler" do
144
+ bht = BusyHandlerTest.new
145
+
146
+ @write_db.busy_handler( bht )
147
+
148
+ # put a read lock on the database
149
+ @read_db.transaction( "DEFERRED" )
150
+
151
+ # put a read lock on the database, but want to go to an exclusive
152
+ @write_db.transaction( "IMMEDIATE" )
153
+
154
+ # do a read operation
155
+ @read_db.execute("SELECT count(*) FROM subcountry")
156
+
157
+ # attempt to do a write operation and commit it
158
+ @write_db.execute("DELETE FROM subcountry")
159
+ @write_db.remove_busy_handler
160
+ lambda { @write_db.execute("COMMIT"); }.should raise_error( ::Amalgalite::SQLite3::Error, /database is locked/ )
161
+ bht.call_count.should == 0
162
+ end
163
+
164
+ end
@@ -5,6 +5,7 @@ $: << File.expand_path(File.join(File.dirname(__FILE__),"..","lib"))
5
5
  require 'amalgalite'
6
6
  require 'amalgalite/taps/io'
7
7
  require 'amalgalite/taps/console'
8
+ require 'amalgalite/database'
8
9
 
9
10
  describe Amalgalite::Database do
10
11
  before(:each) do
@@ -110,8 +111,6 @@ describe Amalgalite::Database do
110
111
  db.close
111
112
  end
112
113
 
113
-
114
-
115
114
  it "can immediately execute an sql statement " do
116
115
  db = Amalgalite::Database.new( SpecInfo.test_db )
117
116
  db.execute( "CREATE TABLE t1( x, y, z )" ).should be_empty
@@ -234,5 +233,100 @@ describe Amalgalite::Database do
234
233
  @iso_db.execute("SELECT count(1) as cnt FROM country").first['cnt'].should == 242
235
234
  end
236
235
  end
237
- end
238
236
 
237
+ describe "#define_function" do
238
+ it "does not allow mixing of arbitrary and mandatory arguments to an SQL function" do
239
+ class FunctionTest2 < ::Amalgalite::Function
240
+ def initialize
241
+ super( 'ftest2', -2 )
242
+ end
243
+ def call( a, *args ); end
244
+ end
245
+ lambda { @iso_db.define_function("ftest2", FunctionTest2.new ) }.should raise_error( ::Amalgalite::Database::FunctionError )
246
+ end
247
+
248
+ it "does not allow outrageous arity" do
249
+ class FunctionTest3 < ::Amalgalite::Function
250
+ def initialize
251
+ super( 'ftest3', 101 )
252
+ end
253
+ def call( *args) ; end
254
+ end
255
+ lambda { @iso_db.define_function("ftest3", FunctionTest3.new ) }.should raise_error( ::Amalgalite::SQLite3::Error )
256
+ end
257
+
258
+ end
259
+
260
+ describe "#remove_function" do
261
+ it "unregisters a single function by name and arity" do
262
+ @iso_db.define_function( "rtest" ) do
263
+ "rtest called"
264
+ end
265
+ @iso_db.functions.size.should == 1
266
+
267
+ r = @iso_db.execute( "select rtest() AS r" )
268
+ r.first['r'].should == "rtest called"
269
+ @iso_db.remove_function("rtest", -1)
270
+ lambda { @iso_db.execute( "select rtest() as r" )}.should raise_error( ::Amalgalite::SQLite3::Error, /no such function: rtest/ )
271
+ @iso_db.functions.size.should == 0
272
+ end
273
+
274
+ it "unregisters a function by instances" do
275
+ class FunctionTest5 < ::Amalgalite::Function
276
+ def initialize
277
+ super( 'ftest5', 0)
278
+ end
279
+ def call( *args) "ftest5 called"; end
280
+ end
281
+ @iso_db.define_function("ftest5", FunctionTest5.new )
282
+ @iso_db.functions.size.should == 1
283
+ r = @iso_db.execute( "select ftest5() AS r" )
284
+ r.first['r'].should == "ftest5 called"
285
+ @iso_db.remove_function("ftest5", FunctionTest5.new )
286
+ lambda { @iso_db.execute( "select ftest5() as r" )}.should raise_error( ::Amalgalite::SQLite3::Error, /no such function: ftest5/ )
287
+ @iso_db.functions.size.should == 0
288
+ end
289
+
290
+ it "unregisters all functions with the same name" do
291
+ @iso_db.function( "rtest" ) do |x|
292
+ "rtest #{x} called"
293
+ end
294
+
295
+ @iso_db.function( "rtest" ) do ||
296
+ "rtest/0 called"
297
+ end
298
+
299
+ @iso_db.functions.size.should == 2
300
+ r = @iso_db.execute( "select rtest(1) AS r")
301
+ r.first['r'].should == "rtest 1 called"
302
+ r = @iso_db.execute( "select rtest() AS r")
303
+ r.first['r'].should == "rtest/0 called"
304
+ @iso_db.remove_function( 'rtest' )
305
+ lambda { @iso_db.execute( "select rtest(1) AS r") }.should raise_error( ::Amalgalite::SQLite3::Error )
306
+ lambda { @iso_db.execute( "select rtest() AS r") }.should raise_error( ::Amalgalite::SQLite3::Error )
307
+ @iso_db.functions.size.should == 0
308
+ end
309
+ end
310
+
311
+ it "can interrupt another thread that is also running in this database" do
312
+ had_error = nil
313
+ executions = 0
314
+ other = Thread.new( @iso_db ) do |db|
315
+ loop do
316
+ begin
317
+ db.execute("select count(id) from country")
318
+ executions += 1
319
+ rescue => e
320
+ had_error = e
321
+ break
322
+ end
323
+ end
324
+ end
325
+ sleep 0.01
326
+ @iso_db.interrupt!
327
+ other.join
328
+ executions.should > 10
329
+ had_error.should be_an_instance_of( ::Amalgalite::SQLite3::Error )
330
+ had_error.message.should =~ / interrupted/
331
+ end
332
+ end