libsql 0.1.0-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.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +60 -0
- data/HISTORY.md +6 -0
- data/LICENSE +31 -0
- data/Manifest.txt +96 -0
- data/README.md +59 -0
- data/Rakefile +28 -0
- data/TODO.md +57 -0
- data/examples/a.rb +9 -0
- data/examples/blob.rb +106 -0
- data/examples/define_aggregate.rb +75 -0
- data/examples/define_function.rb +104 -0
- data/examples/fts5.rb +152 -0
- data/examples/gem-db.rb +94 -0
- data/examples/schema-info.rb +34 -0
- data/ext/libsql/c/extconf.rb +86 -0
- data/ext/libsql/c/gen_constants.rb +353 -0
- data/ext/libsql/c/libsql_blob.c +240 -0
- data/ext/libsql/c/libsql_constants.c +1518 -0
- data/ext/libsql/c/libsql_database.c +1188 -0
- data/ext/libsql/c/libsql_ext.c +383 -0
- data/ext/libsql/c/libsql_ext.h +149 -0
- data/ext/libsql/c/libsql_statement.c +649 -0
- data/ext/libsql/c/notes.txt +134 -0
- data/ext/libsql/c/sqlite3.c +247030 -0
- data/ext/libsql/c/sqlite3.h +13436 -0
- data/lib/libsql/3.0/libsql_ext.so +0 -0
- data/lib/libsql/3.1/libsql_ext.so +0 -0
- data/lib/libsql/3.2/libsql_ext.so +0 -0
- data/lib/libsql/aggregate.rb +73 -0
- data/lib/libsql/blob.rb +186 -0
- data/lib/libsql/boolean.rb +42 -0
- data/lib/libsql/busy_timeout.rb +47 -0
- data/lib/libsql/column.rb +99 -0
- data/lib/libsql/csv_table_importer.rb +75 -0
- data/lib/libsql/database.rb +933 -0
- data/lib/libsql/function.rb +61 -0
- data/lib/libsql/index.rb +43 -0
- data/lib/libsql/memory_database.rb +15 -0
- data/lib/libsql/paths.rb +80 -0
- data/lib/libsql/profile_tap.rb +131 -0
- data/lib/libsql/progress_handler.rb +21 -0
- data/lib/libsql/schema.rb +225 -0
- data/lib/libsql/sqlite3/constants.rb +95 -0
- data/lib/libsql/sqlite3/database/function.rb +48 -0
- data/lib/libsql/sqlite3/database/status.rb +68 -0
- data/lib/libsql/sqlite3/libsql_version.rb +32 -0
- data/lib/libsql/sqlite3/status.rb +60 -0
- data/lib/libsql/sqlite3/version.rb +55 -0
- data/lib/libsql/sqlite3.rb +7 -0
- data/lib/libsql/statement.rb +421 -0
- data/lib/libsql/table.rb +91 -0
- data/lib/libsql/taps/console.rb +27 -0
- data/lib/libsql/taps/io.rb +74 -0
- data/lib/libsql/taps.rb +2 -0
- data/lib/libsql/trace_tap.rb +35 -0
- data/lib/libsql/type_map.rb +63 -0
- data/lib/libsql/type_maps/default_map.rb +166 -0
- data/lib/libsql/type_maps/storage_map.rb +38 -0
- data/lib/libsql/type_maps/text_map.rb +21 -0
- data/lib/libsql/version.rb +8 -0
- data/lib/libsql/view.rb +26 -0
- data/lib/libsql-ruby.rb +1 -0
- data/lib/libsql.rb +51 -0
- data/spec/aggregate_spec.rb +158 -0
- data/spec/blob_spec.rb +78 -0
- data/spec/boolean_spec.rb +24 -0
- data/spec/busy_handler.rb +157 -0
- data/spec/data/iso-3166-country.txt +242 -0
- data/spec/data/iso-3166-schema.sql +22 -0
- data/spec/data/iso-3166-subcountry.txt +3995 -0
- data/spec/data/make-iso-db.sh +12 -0
- data/spec/database_spec.rb +505 -0
- data/spec/default_map_spec.rb +92 -0
- data/spec/function_spec.rb +78 -0
- data/spec/integeration_spec.rb +97 -0
- data/spec/iso_3166_database.rb +58 -0
- data/spec/json_spec.rb +24 -0
- data/spec/libsql_spec.rb +4 -0
- data/spec/paths_spec.rb +28 -0
- data/spec/progress_handler_spec.rb +91 -0
- data/spec/rtree_spec.rb +66 -0
- data/spec/schema_spec.rb +131 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/sqlite3/constants_spec.rb +108 -0
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/libsql_version_spec.rb +16 -0
- data/spec/sqlite3/status_spec.rb +22 -0
- data/spec/sqlite3/version_spec.rb +28 -0
- data/spec/sqlite3_spec.rb +53 -0
- data/spec/statement_spec.rb +168 -0
- data/spec/storage_map_spec.rb +38 -0
- data/spec/tap_spec.rb +57 -0
- data/spec/text_map_spec.rb +20 -0
- data/spec/type_map_spec.rb +14 -0
- data/spec/version_spec.rb +8 -0
- data/tasks/custom.rake +134 -0
- data/tasks/default.rake +257 -0
- data/tasks/extension.rake +29 -0
- data/tasks/this.rb +208 -0
- metadata +330 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
DB="iso-3166.db"
|
4
|
+
SCHEMA="iso-3166-schema.sql"
|
5
|
+
|
6
|
+
rm -f ${DB}
|
7
|
+
sqlite3 ${DB} < ${SCHEMA}
|
8
|
+
echo ".import iso-3166-country.txt country" | sqlite3 ${DB}
|
9
|
+
echo ".import iso-3166-subcountry.txt subcountry" | sqlite3 ${DB}
|
10
|
+
|
11
|
+
sqlite3 ${DB} "select 'country_count', count(1) from country"
|
12
|
+
sqlite3 ${DB} "select 'subcountry_count', count(1) from subcountry"
|
@@ -0,0 +1,505 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'libsql'
|
5
|
+
require 'libsql/taps/io'
|
6
|
+
require 'libsql/taps/console'
|
7
|
+
require 'libsql/database'
|
8
|
+
|
9
|
+
describe ::Libsql::Database do
|
10
|
+
|
11
|
+
it "can create a new database" do
|
12
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
13
|
+
db.instance_of?(::Libsql::Database)
|
14
|
+
db.api.instance_of?(::Libsql::SQLite3::Database)
|
15
|
+
File.exist?( SpecInfo.test_db ).should eql(true)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "creates a new UTF-8 database (need exec to check pragma encoding)" do
|
19
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
20
|
+
db.execute_batch( @schema );
|
21
|
+
db.should_not be_utf16
|
22
|
+
db.encoding.should eql("UTF-8")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "creates a new UTF-16 database (need exec to check pragma encoding)"
|
26
|
+
|
27
|
+
it "raises an error if the file does not exist and the database is opened with a non-create mode" do
|
28
|
+
lambda { ::Libsql::Database.new( SpecInfo.test_db, "r") }.should raise_error(::Libsql::SQLite3::Error)
|
29
|
+
lambda { ::Libsql::Database.new( SpecInfo.test_db, "r+") }.should raise_error(::Libsql::SQLite3::Error)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "raises an error if an invalid mode is used" do
|
33
|
+
lambda { ::Libsql::Database.new( SpecInfo.test_db, "b+" ) }.should raise_error(::Libsql::Database::InvalidModeError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can be in autocommit mode, and is by default" do
|
37
|
+
@iso_db.autocommit?.should eql(true)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "reports false for autocommit? when inside a transaction" do
|
41
|
+
@iso_db.execute(" BEGIN ")
|
42
|
+
@iso_db.autocommit?.should eql(false)
|
43
|
+
@iso_db.in_transaction?.should eql(true)
|
44
|
+
@iso_db.execute(" COMMIT")
|
45
|
+
@iso_db.in_transaction?.should eql(false)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "prepares a statment" do
|
49
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
50
|
+
stmt = db.prepare("SELECT datetime()")
|
51
|
+
stmt.instance_of?(::Libsql::Statement)
|
52
|
+
stmt.api.instance_of?(::Libsql::SQLite3::Statement)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "raises an error on invalid syntax when preparing a bad sql statement" do
|
56
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
57
|
+
lambda { db.prepare("SELECT nothing FROM stuf") }.should raise_error(::Libsql::SQLite3::Error)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "closes normally" do
|
61
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
62
|
+
expect { db.close }.not_to raise_error
|
63
|
+
end
|
64
|
+
|
65
|
+
it "returns the id of the last inserted row" do
|
66
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
67
|
+
db.last_insert_rowid.should eql(0)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "is in autocommit mode by default" do
|
71
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
72
|
+
db.should be_autocommit
|
73
|
+
end
|
74
|
+
|
75
|
+
it "report the number of rows changed with an insert" do
|
76
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
77
|
+
db.execute_batch <<-sql
|
78
|
+
CREATE TABLE t1( x );
|
79
|
+
INSERT INTO t1( x ) values ( 1 );
|
80
|
+
INSERT INTO t1( x ) values ( 2 );
|
81
|
+
INSERT INTO t1( x ) values ( 3 );
|
82
|
+
sql
|
83
|
+
|
84
|
+
db.row_changes.should eql(1)
|
85
|
+
db.total_changes.should eql(3)
|
86
|
+
db.close
|
87
|
+
end
|
88
|
+
|
89
|
+
it "reports the number of rows deleted" do
|
90
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
91
|
+
db.execute_batch <<-sql
|
92
|
+
CREATE TABLE t1( x );
|
93
|
+
INSERT INTO t1( x ) values ( 1 );
|
94
|
+
INSERT INTO t1( x ) values ( 2 );
|
95
|
+
INSERT INTO t1( x ) values ( 3 );
|
96
|
+
DELETE FROM t1 where x < 3;
|
97
|
+
sql
|
98
|
+
db.row_changes.should eql(2)
|
99
|
+
db.close
|
100
|
+
end
|
101
|
+
|
102
|
+
it "can immediately execute an sql statement " do
|
103
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
104
|
+
db.execute( "CREATE TABLE t1( x, y, z )" ).should be_empty
|
105
|
+
end
|
106
|
+
|
107
|
+
it "can execute a batch of commands" do
|
108
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
109
|
+
db.execute_batch( @schema ).should eql(5)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "returns an empty array when there are no results" do
|
113
|
+
row = @iso_db.execute("SELECT * from subcountry where country = 'Antarctica'")
|
114
|
+
row.should be_empty
|
115
|
+
end
|
116
|
+
|
117
|
+
it "traces the execution of code" do
|
118
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
119
|
+
sql = "CREATE TABLE trace_test( x, y, z)"
|
120
|
+
s = db.trace_tap = ::Libsql::Taps::StringIO.new
|
121
|
+
db.execute( sql )
|
122
|
+
escaped_sql= Regexp.quote(sql)
|
123
|
+
db.trace_tap.string.should match(/registered as trace tap/)
|
124
|
+
db.trace_tap.string.should match(/#{escaped_sql}/)
|
125
|
+
s.trace_count.should eql(2)
|
126
|
+
db.trace_tap = nil
|
127
|
+
s.trace_count.should eql(3)
|
128
|
+
s.string.should match(/unregistered as trace tap/)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "raises an exception if the wrong type of object is used for tracing" do
|
132
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
133
|
+
lambda { db.trace_tap = Object.new }.should raise_error(::Libsql::Error)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "profiles the execution of code" do
|
137
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
138
|
+
s = db.trace_tap = ::Libsql::Taps::StringIO.new
|
139
|
+
db.execute_batch( @schema )
|
140
|
+
db.trace_tap.samplers.size.should eql(5)
|
141
|
+
db.trace_tap = nil
|
142
|
+
s.string.should =~ /unregistered as trace tap/
|
143
|
+
end
|
144
|
+
|
145
|
+
it "#execute yields each row when called with a block" do
|
146
|
+
count = 0
|
147
|
+
@iso_db.execute( "SELECT * FROM country LIMIT 10") do |row|
|
148
|
+
count += 1
|
149
|
+
end
|
150
|
+
count.should eql(10)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "#pragma yields each row when called with a block" do
|
154
|
+
count = 0
|
155
|
+
@iso_db.pragma( "index_info( subcountry_country )" ) do |row|
|
156
|
+
count += 1
|
157
|
+
end
|
158
|
+
count.should eql(1)
|
159
|
+
end
|
160
|
+
|
161
|
+
it "can use something that responds to 'write' as a tap" do
|
162
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
163
|
+
s2 = db.trace_tap = StringIO.new
|
164
|
+
s2.string.should eql("registered as trace tap")
|
165
|
+
end
|
166
|
+
|
167
|
+
it "can clear all registered taps" do
|
168
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
169
|
+
s = db.trace_tap = ::Libsql::Taps::StringIO.new
|
170
|
+
db.execute_batch( @schema )
|
171
|
+
db.trace_tap.samplers.size.should eql(5)
|
172
|
+
db.clear_taps!
|
173
|
+
s.string.should =~ /unregistered as trace tap/m
|
174
|
+
end
|
175
|
+
|
176
|
+
it "allows nested transactions even if SQLite under the covers does not" do
|
177
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
178
|
+
r = db.transaction do |db2|
|
179
|
+
r2 = db.transaction { 42 }
|
180
|
+
r2.should eql(42)
|
181
|
+
r2
|
182
|
+
end
|
183
|
+
r.should eql(42)
|
184
|
+
end
|
185
|
+
|
186
|
+
%w[ transaction deferred_transaction immediate_transaction exclusive_transaction ].each do |trans|
|
187
|
+
it "returns the result of the #{trans} when a block is yielded" do
|
188
|
+
db = ::Libsql::Database.new( SpecInfo.test_db )
|
189
|
+
(db.send( trans ){ 42 }).should eql(42)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
it "#reload_schema!" do
|
194
|
+
schema = @iso_db.schema
|
195
|
+
schema.instance_of?( ::Libsql::Schema ).should eql(true)
|
196
|
+
s2 = @iso_db.reload_schema!
|
197
|
+
s2.object_id.should_not eql(schema.object_id)
|
198
|
+
end
|
199
|
+
|
200
|
+
it "can rollback a transaction" do
|
201
|
+
@iso_db.transaction
|
202
|
+
r = @iso_db.execute("SELECT count(1) as cnt FROM country");
|
203
|
+
r.first['cnt'].should eql(242)
|
204
|
+
@iso_db.execute("DELETE FROM country")
|
205
|
+
r = @iso_db.execute("SELECT count(1) as cnt FROM country");
|
206
|
+
r.first['cnt'].should eql(0)
|
207
|
+
@iso_db.rollback
|
208
|
+
|
209
|
+
r = @iso_db.execute("SELECT count(1) as cnt FROM country");
|
210
|
+
r.first['cnt'].should eql(242)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "rolls back if an exception happens during a transaction block" do
|
214
|
+
begin
|
215
|
+
@iso_db.transaction do |db|
|
216
|
+
r = db.execute("SELECT count(1) as cnt FROM country");
|
217
|
+
r.first['cnt'].should eql(242)
|
218
|
+
db.execute("DELETE FROM country")
|
219
|
+
db.in_transaction?.should eql(true)
|
220
|
+
raise "testing rollback"
|
221
|
+
end
|
222
|
+
rescue
|
223
|
+
@iso_db.in_transaction?.should eql(false)
|
224
|
+
@iso_db.execute("SELECT count(1) as cnt FROM country").first['cnt'].should eql(242)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
it "commits if an exception happens during a transaction block but is rescued within the block" do
|
229
|
+
@iso_db.transaction do |db|
|
230
|
+
begin
|
231
|
+
r = db.execute("SELECT count(1) as cnt FROM country");
|
232
|
+
r.first['cnt'].should eql(242)
|
233
|
+
db.execute("DELETE FROM country")
|
234
|
+
db.in_transaction?.should eql(true)
|
235
|
+
raise "testing rollback"
|
236
|
+
rescue => e
|
237
|
+
e.message.should == "testing rollback"
|
238
|
+
end
|
239
|
+
$!.should == nil
|
240
|
+
end
|
241
|
+
@iso_db.in_transaction?.should eql(false)
|
242
|
+
@iso_db.first_value_from("select count(1) as cnt from country").should eql(0)
|
243
|
+
end
|
244
|
+
|
245
|
+
it "does not reraise an exception that exits before the transaction starts" do
|
246
|
+
class MyExceptionTest < RuntimeError; end
|
247
|
+
db = ::Libsql::Database.new( ":memory:" )
|
248
|
+
|
249
|
+
lambda {
|
250
|
+
begin
|
251
|
+
raise MyExceptionTest, "James pointed this out"
|
252
|
+
rescue MyExceptionTest
|
253
|
+
db.transaction("EXCLUSIVE") { }
|
254
|
+
end
|
255
|
+
}.should_not raise_error
|
256
|
+
end
|
257
|
+
|
258
|
+
describe "#define_function" do
|
259
|
+
it "does not allow mixing of arbitrary and mandatory arguments to an SQL function" do
|
260
|
+
class DBFunctionTest2 < ::Libsql::Function
|
261
|
+
def initialize
|
262
|
+
super( 'ftest2', -2 )
|
263
|
+
end
|
264
|
+
def call( a, *args ); end
|
265
|
+
end
|
266
|
+
lambda { @iso_db.define_function("ftest2", DBFunctionTest2.new ) }.should raise_error( ::Libsql::Database::FunctionError )
|
267
|
+
end
|
268
|
+
|
269
|
+
it "does not allow outrageous arity" do
|
270
|
+
class DBFunctionTest3 < ::Libsql::Function
|
271
|
+
def initialize
|
272
|
+
super( 'ftest3', 128 )
|
273
|
+
end
|
274
|
+
def call( *args) ; end
|
275
|
+
end
|
276
|
+
lambda { @iso_db.define_function("ftest3", DBFunctionTest3.new ) }.should raise_error( ::Libsql::SQLite3::Error )
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
describe "#remove_function" do
|
282
|
+
it "unregisters a single function by name and arity" do
|
283
|
+
@iso_db.define_function( "rtest1" ) do
|
284
|
+
"rtest1 called"
|
285
|
+
end
|
286
|
+
|
287
|
+
@iso_db.functions.size.should eql(1 )
|
288
|
+
|
289
|
+
r = @iso_db.execute( "select rtest1() AS r" )
|
290
|
+
r.first['r'].should eql("rtest1 called")
|
291
|
+
@iso_db.remove_function("rtest1")
|
292
|
+
|
293
|
+
lambda { @iso_db.execute( "select rtest1() as r" )}.should raise_error( ::Libsql::SQLite3::Error, /no such function: rtest1/ )
|
294
|
+
@iso_db.functions.size.should eql(0)
|
295
|
+
end
|
296
|
+
|
297
|
+
it "unregisters a function by instances" do
|
298
|
+
class FunctionTest5 < ::Libsql::Function
|
299
|
+
def initialize
|
300
|
+
super( 'ftest5', 0)
|
301
|
+
end
|
302
|
+
def call( *args) "ftest5 called"; end
|
303
|
+
end
|
304
|
+
@iso_db.define_function("ftest5", FunctionTest5.new )
|
305
|
+
@iso_db.functions.size.should eql(1)
|
306
|
+
r = @iso_db.execute( "select ftest5() AS r" )
|
307
|
+
r.first['r'].should eql("ftest5 called")
|
308
|
+
@iso_db.remove_function("ftest5", FunctionTest5.new )
|
309
|
+
lambda { @iso_db.execute( "select ftest5() as r" )}.should raise_error( ::Libsql::SQLite3::Error, /no such function: ftest5/ )
|
310
|
+
@iso_db.functions.size.should eql(0)
|
311
|
+
end
|
312
|
+
|
313
|
+
it "unregisters all functions with the same name" do
|
314
|
+
@iso_db.function( "rtest" ) do |x|
|
315
|
+
"rtest #{x} called"
|
316
|
+
end
|
317
|
+
|
318
|
+
@iso_db.function( "rtest" ) do ||
|
319
|
+
"rtest/0 called"
|
320
|
+
end
|
321
|
+
|
322
|
+
@iso_db.functions.size.should eql(2)
|
323
|
+
r = @iso_db.execute( "select rtest(1) AS r")
|
324
|
+
r.first['r'].should eql("rtest 1 called")
|
325
|
+
r = @iso_db.execute( "select rtest() AS r")
|
326
|
+
r.first['r'].should eql("rtest/0 called")
|
327
|
+
@iso_db.remove_function( 'rtest' )
|
328
|
+
lambda { @iso_db.execute( "select rtest(1) AS r") }.should raise_error( ::Libsql::SQLite3::Error )
|
329
|
+
lambda { @iso_db.execute( "select rtest() AS r") }.should raise_error( ::Libsql::SQLite3::Error )
|
330
|
+
@iso_db.functions.size.should eql(0)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
it "can interrupt another thread that is also running in this database" do
|
335
|
+
executions = 0
|
336
|
+
control_queue = Queue.new
|
337
|
+
other = Thread.new( @iso_db ) do |db|
|
338
|
+
looping_sent = false
|
339
|
+
c = 0
|
340
|
+
loop do
|
341
|
+
begin
|
342
|
+
db.execute("select * from subcountry")
|
343
|
+
executions += 1
|
344
|
+
if not looping_sent then
|
345
|
+
control_queue.enq :looping
|
346
|
+
looping_sent = true
|
347
|
+
end
|
348
|
+
if c > 20_000 then
|
349
|
+
break
|
350
|
+
end
|
351
|
+
c += 1
|
352
|
+
rescue => e
|
353
|
+
Thread.current[:loop_count] = c
|
354
|
+
Thread.current[:had_error] = e.dup
|
355
|
+
control_queue.enq :boom
|
356
|
+
break
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
rudeness = Thread.new( @iso_db ) do |db|
|
362
|
+
control_queue.deq
|
363
|
+
count = 0
|
364
|
+
loop do
|
365
|
+
@iso_db.interrupt!
|
366
|
+
break unless control_queue.empty?
|
367
|
+
if count > 20_000 then
|
368
|
+
break
|
369
|
+
end
|
370
|
+
count += 1
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
rudeness.join
|
375
|
+
other.join
|
376
|
+
|
377
|
+
#$stdout.puts " looped #{other[:loop_count]} times"
|
378
|
+
other[:had_error].should be_an_instance_of( ::Libsql::SQLite3::Error )
|
379
|
+
other[:had_error].message.should =~ / interrupted/
|
380
|
+
end
|
381
|
+
|
382
|
+
it "savepoints are considered 'in_transaction'" do
|
383
|
+
@iso_db.savepoint( 'test1' ) do |db|
|
384
|
+
db.should be_in_transaction
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
it "releases a savepoint" do
|
389
|
+
us_sub = @iso_db.execute( "select count(1) as cnt from subcountry where country = 'US'" ).first['cnt']
|
390
|
+
us_sub.should eql(57)
|
391
|
+
other_sub = @iso_db.execute( "select count(1) as cnt from subcountry where country != 'US'" ).first['cnt']
|
392
|
+
|
393
|
+
@iso_db.transaction
|
394
|
+
@iso_db.savepoint( "t1" ) do |s|
|
395
|
+
s.execute("DELETE FROM subcountry where country = 'US'")
|
396
|
+
end
|
397
|
+
|
398
|
+
all_sub = @iso_db.execute("SELECT count(*) as cnt from subcountry").first['cnt']
|
399
|
+
|
400
|
+
all_sub.should eql(other_sub)
|
401
|
+
@iso_db.rollback
|
402
|
+
all_sub = @iso_db.execute("SELECT count(*) as cnt from subcountry").first['cnt']
|
403
|
+
all_sub.should eql(( us_sub + other_sub ))
|
404
|
+
|
405
|
+
end
|
406
|
+
|
407
|
+
it "rolls back a savepoint" do
|
408
|
+
us_sub = @iso_db.execute("SELECT count(*) as cnt from subcountry where country = 'US'").first['cnt']
|
409
|
+
lambda {
|
410
|
+
@iso_db.savepoint( "t1" ) do |s|
|
411
|
+
s.execute("DELETE FROM subcountry where country = 'US'")
|
412
|
+
as = @iso_db.execute("SELECT count(*) as cnt from subcountry where country = 'US'").first['cnt']
|
413
|
+
as.should be == 0
|
414
|
+
raise "sample error"
|
415
|
+
end
|
416
|
+
}.should raise_error( StandardError, /sample error/ )
|
417
|
+
|
418
|
+
@iso_db.execute("SELECT count(*) as cnt from subcountry where country = 'US'").first['cnt'].should eql(us_sub)
|
419
|
+
end
|
420
|
+
|
421
|
+
it "rolling back the outermost savepoint is still 'in_transaction'" do
|
422
|
+
@iso_db.savepoint( "t1" )
|
423
|
+
@iso_db.execute("DELETE FROM subcountry where country = 'US'")
|
424
|
+
@iso_db.rollback_to( "t1" )
|
425
|
+
@iso_db.should be_in_transaction
|
426
|
+
@iso_db.rollback
|
427
|
+
@iso_db.should_not be_in_transaction
|
428
|
+
end
|
429
|
+
|
430
|
+
it "can escape quoted strings" do
|
431
|
+
@iso_db.escape( "It's a happy day!" ).should eql("It''s a happy day!")
|
432
|
+
end
|
433
|
+
|
434
|
+
it "can quote and escape single quoted strings" do
|
435
|
+
@iso_db.quote( "It's a happy day!" ).should eql("'It''s a happy day!'")
|
436
|
+
end
|
437
|
+
|
438
|
+
it "can escape a symbol" do
|
439
|
+
@iso_db.escape( :stuff ).should eql("stuff")
|
440
|
+
end
|
441
|
+
|
442
|
+
it "can quote a symbol" do
|
443
|
+
@iso_db.quote( :stuff ).should eql("'stuff'")
|
444
|
+
end
|
445
|
+
|
446
|
+
it "returns the first row of results as a convenience" do
|
447
|
+
row = @iso_db.first_row_from("SELECT c.name, c.two_letter, count(*) AS count
|
448
|
+
FROM country c
|
449
|
+
JOIN subcountry sc
|
450
|
+
ON c.two_letter = sc.country
|
451
|
+
GROUP BY c.name, c.two_letter
|
452
|
+
ORDER BY count DESC")
|
453
|
+
row.length.should eql(3)
|
454
|
+
row[0].should eql("United Kingdom")
|
455
|
+
row[1].should eql("GB")
|
456
|
+
row[2].should eql(232)
|
457
|
+
row['name'].should eql("United Kingdom")
|
458
|
+
row['two_letter'].should eql("GB")
|
459
|
+
row['count'].should eql(232)
|
460
|
+
end
|
461
|
+
|
462
|
+
it "returns and empty row if there are no results for the first row" do
|
463
|
+
row = @iso_db.first_row_from("SELECT * from subcountry where country = 'Antarctica'")
|
464
|
+
row.should be_empty
|
465
|
+
end
|
466
|
+
|
467
|
+
it "returns nil if there is no value in the first value" do
|
468
|
+
val = @iso_db.first_value_from("select * from subcountry where country = 'Antarctica'" )
|
469
|
+
val.should eql(nil)
|
470
|
+
end
|
471
|
+
|
472
|
+
it "returns the first value of results as a conveinience" do
|
473
|
+
val = @iso_db.first_value_from("SELECT count(*) from subcountry ")
|
474
|
+
val.should eql(3995)
|
475
|
+
end
|
476
|
+
|
477
|
+
it "replicates a database to memory" do
|
478
|
+
mem_db = @iso_db.replicate_to( ":memory:" )
|
479
|
+
@iso_db.close
|
480
|
+
val = mem_db.first_value_from("SELECT count(*) from subcountry" )
|
481
|
+
val.should eql(3995)
|
482
|
+
end
|
483
|
+
|
484
|
+
it "replicates a database to a database file" do
|
485
|
+
all_sub = @iso_db.execute("SELECT count(*) as cnt from subcountry").first['cnt']
|
486
|
+
|
487
|
+
fdb = ::Libsql::Database.new( SpecInfo.test_db )
|
488
|
+
@iso_db.replicate_to( fdb )
|
489
|
+
@iso_db.close
|
490
|
+
|
491
|
+
File.exist?( SpecInfo.test_db ).should be == true
|
492
|
+
fdb.execute("SELECT count(*) as cnt from subcountry").first['cnt'].should == all_sub
|
493
|
+
end
|
494
|
+
|
495
|
+
it "raises an error if it is given an invalid location to replicate to" do
|
496
|
+
lambda { @iso_db.replicate_to( false ) }.should raise_error( ArgumentError, /must be a String or a Database/ )
|
497
|
+
end
|
498
|
+
|
499
|
+
it "imports batch statements" do
|
500
|
+
db = ::Libsql::Database.new(":memory:")
|
501
|
+
db.import("CREATE TABLE things(stuff TEXT); INSERT INTO things (stuff) VALUES (\"foobar\");").should be_truthy
|
502
|
+
db.first_value_from("SELECT stuff FROM things").should == "foobar"
|
503
|
+
end
|
504
|
+
|
505
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'libsql/type_maps/default_map'
|
3
|
+
|
4
|
+
describe ::Libsql::TypeMaps::DefaultMap do
|
5
|
+
before(:each) do
|
6
|
+
@map = ::Libsql::TypeMaps::DefaultMap.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#bind_type_of" do
|
10
|
+
|
11
|
+
it "Float is bound to DataType::FLOAT" do
|
12
|
+
@map.bind_type_of( 3.14 ).should == ::Libsql::SQLite3::Constants::DataType::FLOAT
|
13
|
+
end
|
14
|
+
|
15
|
+
it "Integer is bound to DataType::INTGER" do
|
16
|
+
@map.bind_type_of( 42 ).should == ::Libsql::SQLite3::Constants::DataType::INTEGER
|
17
|
+
end
|
18
|
+
|
19
|
+
it "nil is bound to DataType::NULL" do
|
20
|
+
@map.bind_type_of( nil ).should == ::Libsql::SQLite3::Constants::DataType::NULL
|
21
|
+
end
|
22
|
+
|
23
|
+
it "::Libsql::Blob is bound to DataType::BLOB" do
|
24
|
+
@map.bind_type_of( ::Libsql::Blob.new( :column => true, :string => "just a test" ) ).should == ::Libsql::SQLite3::Constants::DataType::BLOB
|
25
|
+
end
|
26
|
+
|
27
|
+
it "everything else is bound to DataType::TEXT" do
|
28
|
+
@map.bind_type_of( "everything else" ).should == ::Libsql::SQLite3::Constants::DataType::TEXT
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
describe "#result_value_of" do
|
35
|
+
|
36
|
+
it "Numeric's are returned" do
|
37
|
+
y = 42
|
38
|
+
x = @map.result_value_of( "INT", 42 )
|
39
|
+
x.object_id.should == y.object_id
|
40
|
+
end
|
41
|
+
|
42
|
+
it "Nil is returned" do
|
43
|
+
@map.result_value_of( "NULL", nil ).should == nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "DateTime is returned for delcared types of 'datetime'" do
|
47
|
+
@map.result_value_of( "DaTeTiME", "2008-04-01 23:23:23" ).should be_kind_of(DateTime)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "Date is returned for declared types of 'date'" do
|
51
|
+
@map.result_value_of( "date", "2008-04-01 23:42:42" ).should be_kind_of(Date)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "Time is returned for declared types of 'time'" do
|
55
|
+
@map.result_value_of( "timestamp", "2008-04-01T23:42:42" ).should be_kind_of(Time)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "Float is returned for declared types of 'double'" do
|
59
|
+
@map.result_value_of( "double", "3.14" ).should be_kind_of(Float)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "Float is returned for declared types of 'float'" do
|
63
|
+
@map.result_value_of( "float", "3.14" ).should be_kind_of(Float)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "Integer is returned for declared types of 'int'" do
|
67
|
+
@map.result_value_of( "int", "42" ).should be_kind_of(Integer)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "boolean is returned for declared types of 'bool'" do
|
71
|
+
@map.result_value_of( "bool", "True" ).should == true
|
72
|
+
end
|
73
|
+
|
74
|
+
it "Blob is returned for declared types of 'blob'" do
|
75
|
+
blob = @map.result_value_of( "blob", "stuff")
|
76
|
+
blob.to_s.should == "stuff"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "String is returned for delcared types of 'string'" do
|
80
|
+
@map.result_value_of( "string", "stuff").should == "stuff"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "raises and error if an unknown sql type is returned" do
|
84
|
+
x = nil
|
85
|
+
lambda{ x = @map.result_value_of( "footype", "foo" ) }.should raise_error( ::Libsql::Error )
|
86
|
+
end
|
87
|
+
|
88
|
+
it "raises and error if an ruby class is attempted to be mapped" do
|
89
|
+
lambda{ @map.result_value_of( "footype", 1..3 ) }.should raise_error( ::Libsql::Error )
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Scalar SQL Functions" do
|
4
|
+
|
5
|
+
it "can define a custom SQL function as a block with 0 params" do
|
6
|
+
@iso_db.define_function("foo") do
|
7
|
+
"foo"
|
8
|
+
end
|
9
|
+
r = @iso_db.execute("SELECT foo() AS f");
|
10
|
+
r.first['f'].should == "foo"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has a signature" do
|
14
|
+
::Libsql::Function.new( "testing_name", 42 ).signature.should == "testing_name/42"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can define a custom SQL function as a lambda with 2 param" do
|
18
|
+
@iso_db.define_function("foo2", lambda{ |x,y| "foo2 -> #{x} #{y}" } )
|
19
|
+
r = @iso_db.execute("SELECT foo2( 'bar', 'baz' ) as f")
|
20
|
+
r.first['f'].should == "foo2 -> bar baz"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "can define a custom SQL function as a class with N params" do
|
24
|
+
class FunctionTest1 < ::Libsql::Function
|
25
|
+
def initialize
|
26
|
+
super('ftest', -1)
|
27
|
+
end
|
28
|
+
def call( *args )
|
29
|
+
"#{args.length} args #{args.join(', ')}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@iso_db.define_function("ftest1", FunctionTest1.new )
|
34
|
+
r = @iso_db.execute("SELECT ftest1(1,2,3,'baz') as f")
|
35
|
+
r.first['f'].should == "4 args 1, 2, 3, baz"
|
36
|
+
end
|
37
|
+
|
38
|
+
[ [ 1, lambda { true } ],
|
39
|
+
[ 0, lambda { false } ],
|
40
|
+
[ nil, lambda { nil } ],
|
41
|
+
[ "foo", lambda { "foo" } ],
|
42
|
+
[ 42, lambda { 42 } ],
|
43
|
+
[ 42.2 , lambda { 42.2 } ], ].each do |expected, func|
|
44
|
+
it "returns the appropriate class #{expected.class} " do
|
45
|
+
@iso_db.define_function("ctest", func )
|
46
|
+
r = @iso_db.execute( "SELECT ctest() AS c" )
|
47
|
+
r.first['c'].should == expected
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "does not allow outrageous arity" do
|
52
|
+
class FunctionTest3 < ::Libsql::Function
|
53
|
+
def initialize
|
54
|
+
super( 'ftest3', 128)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
lambda { @iso_db.define_function("ftest3", FunctionTest3.new) }.should raise_error( ::Libsql::SQLite3::Error, /SQLITE_ERROR .* Library used incorrectly/ )
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises an error if the function returns a complex Ruby object" do
|
61
|
+
l = lambda { Hash.new }
|
62
|
+
@iso_db.define_function("htest", l)
|
63
|
+
begin
|
64
|
+
@iso_db.execute( "SELECT htest() AS h" )
|
65
|
+
rescue => e
|
66
|
+
e.should be_instance_of( ::Libsql::SQLite3::Error )
|
67
|
+
e.message.should =~ /Unable to convert ruby object to an SQL function result/
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "an error raised during the sql function is handled correctly" do
|
72
|
+
@iso_db.define_function( "etest" ) do
|
73
|
+
raise "error from within an sql function"
|
74
|
+
end
|
75
|
+
lambda { @iso_db.execute( "SELECT etest() AS e" ) }.should raise_error( ::Libsql::SQLite3::Error, /error from within an sql function/ )
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|