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

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.
@@ -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