libsql 0.1.0-x64-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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +60 -0
  3. data/HISTORY.md +6 -0
  4. data/LICENSE +31 -0
  5. data/Manifest.txt +96 -0
  6. data/README.md +59 -0
  7. data/Rakefile +28 -0
  8. data/TODO.md +57 -0
  9. data/examples/a.rb +9 -0
  10. data/examples/blob.rb +106 -0
  11. data/examples/define_aggregate.rb +75 -0
  12. data/examples/define_function.rb +104 -0
  13. data/examples/fts5.rb +152 -0
  14. data/examples/gem-db.rb +94 -0
  15. data/examples/schema-info.rb +34 -0
  16. data/ext/libsql/c/extconf.rb +86 -0
  17. data/ext/libsql/c/gen_constants.rb +353 -0
  18. data/ext/libsql/c/libsql_blob.c +240 -0
  19. data/ext/libsql/c/libsql_constants.c +1518 -0
  20. data/ext/libsql/c/libsql_database.c +1188 -0
  21. data/ext/libsql/c/libsql_ext.c +383 -0
  22. data/ext/libsql/c/libsql_ext.h +149 -0
  23. data/ext/libsql/c/libsql_statement.c +649 -0
  24. data/ext/libsql/c/notes.txt +134 -0
  25. data/ext/libsql/c/sqlite3.c +247030 -0
  26. data/ext/libsql/c/sqlite3.h +13436 -0
  27. data/lib/libsql/aggregate.rb +73 -0
  28. data/lib/libsql/blob.rb +186 -0
  29. data/lib/libsql/boolean.rb +42 -0
  30. data/lib/libsql/busy_timeout.rb +47 -0
  31. data/lib/libsql/column.rb +99 -0
  32. data/lib/libsql/csv_table_importer.rb +75 -0
  33. data/lib/libsql/database.rb +933 -0
  34. data/lib/libsql/function.rb +61 -0
  35. data/lib/libsql/index.rb +43 -0
  36. data/lib/libsql/libsql_ext.so +0 -0
  37. data/lib/libsql/memory_database.rb +15 -0
  38. data/lib/libsql/paths.rb +80 -0
  39. data/lib/libsql/profile_tap.rb +131 -0
  40. data/lib/libsql/progress_handler.rb +21 -0
  41. data/lib/libsql/schema.rb +225 -0
  42. data/lib/libsql/sqlite3/constants.rb +95 -0
  43. data/lib/libsql/sqlite3/database/function.rb +48 -0
  44. data/lib/libsql/sqlite3/database/status.rb +68 -0
  45. data/lib/libsql/sqlite3/libsql_version.rb +32 -0
  46. data/lib/libsql/sqlite3/status.rb +60 -0
  47. data/lib/libsql/sqlite3/version.rb +55 -0
  48. data/lib/libsql/sqlite3.rb +7 -0
  49. data/lib/libsql/statement.rb +421 -0
  50. data/lib/libsql/table.rb +91 -0
  51. data/lib/libsql/taps/console.rb +27 -0
  52. data/lib/libsql/taps/io.rb +74 -0
  53. data/lib/libsql/taps.rb +2 -0
  54. data/lib/libsql/trace_tap.rb +35 -0
  55. data/lib/libsql/type_map.rb +63 -0
  56. data/lib/libsql/type_maps/default_map.rb +166 -0
  57. data/lib/libsql/type_maps/storage_map.rb +38 -0
  58. data/lib/libsql/type_maps/text_map.rb +21 -0
  59. data/lib/libsql/version.rb +8 -0
  60. data/lib/libsql/view.rb +26 -0
  61. data/lib/libsql-ruby.rb +1 -0
  62. data/lib/libsql.rb +51 -0
  63. data/spec/aggregate_spec.rb +158 -0
  64. data/spec/blob_spec.rb +78 -0
  65. data/spec/boolean_spec.rb +24 -0
  66. data/spec/busy_handler.rb +157 -0
  67. data/spec/data/iso-3166-country.txt +242 -0
  68. data/spec/data/iso-3166-schema.sql +22 -0
  69. data/spec/data/iso-3166-subcountry.txt +3995 -0
  70. data/spec/data/make-iso-db.sh +12 -0
  71. data/spec/database_spec.rb +505 -0
  72. data/spec/default_map_spec.rb +92 -0
  73. data/spec/function_spec.rb +78 -0
  74. data/spec/integeration_spec.rb +97 -0
  75. data/spec/iso_3166_database.rb +58 -0
  76. data/spec/json_spec.rb +24 -0
  77. data/spec/libsql_spec.rb +4 -0
  78. data/spec/paths_spec.rb +28 -0
  79. data/spec/progress_handler_spec.rb +91 -0
  80. data/spec/rtree_spec.rb +66 -0
  81. data/spec/schema_spec.rb +131 -0
  82. data/spec/spec_helper.rb +48 -0
  83. data/spec/sqlite3/constants_spec.rb +108 -0
  84. data/spec/sqlite3/database_status_spec.rb +36 -0
  85. data/spec/sqlite3/libsql_version_spec.rb +16 -0
  86. data/spec/sqlite3/status_spec.rb +22 -0
  87. data/spec/sqlite3/version_spec.rb +28 -0
  88. data/spec/sqlite3_spec.rb +53 -0
  89. data/spec/statement_spec.rb +168 -0
  90. data/spec/storage_map_spec.rb +38 -0
  91. data/spec/tap_spec.rb +57 -0
  92. data/spec/text_map_spec.rb +20 -0
  93. data/spec/type_map_spec.rb +14 -0
  94. data/spec/version_spec.rb +8 -0
  95. data/tasks/custom.rake +134 -0
  96. data/tasks/default.rake +257 -0
  97. data/tasks/extension.rake +29 -0
  98. data/tasks/this.rb +208 -0
  99. metadata +328 -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
+