sqlpostgres 1.2.6 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/Changelog.md +18 -0
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +12 -0
  4. data/README.rdoc +5 -2
  5. data/Rakefile +5 -24
  6. data/VERSION +1 -1
  7. data/lib/sqlpostgres/Connection.rb +8 -3
  8. data/lib/sqlpostgres/Cursor.rb +2 -2
  9. data/lib/sqlpostgres/Insert.rb +1 -1
  10. data/lib/sqlpostgres/PgBit.rb +1 -1
  11. data/lib/sqlpostgres/PgBox.rb +1 -1
  12. data/lib/sqlpostgres/PgCidr.rb +1 -1
  13. data/lib/sqlpostgres/PgCircle.rb +1 -1
  14. data/lib/sqlpostgres/PgInet.rb +1 -1
  15. data/lib/sqlpostgres/PgInterval.rb +1 -1
  16. data/lib/sqlpostgres/PgLineSegment.rb +1 -1
  17. data/lib/sqlpostgres/PgMacAddr.rb +1 -1
  18. data/lib/sqlpostgres/PgPath.rb +1 -1
  19. data/lib/sqlpostgres/PgPoint.rb +1 -1
  20. data/lib/sqlpostgres/PgPolygon.rb +1 -1
  21. data/lib/sqlpostgres/PgTime.rb +1 -1
  22. data/lib/sqlpostgres/PgTimeWithTimeZone.rb +1 -1
  23. data/lib/sqlpostgres/PgTimestamp.rb +1 -1
  24. data/lib/sqlpostgres/PgTwoPoints.rb +1 -1
  25. data/lib/sqlpostgres/PgWrapper.rb +1 -1
  26. data/lib/sqlpostgres/Select.rb +25 -25
  27. data/lib/sqlpostgres/Translate.rb +7 -29
  28. data/lib/sqlpostgres/Update.rb +1 -1
  29. data/rake_tasks/db.rake +17 -0
  30. data/rake_tasks/default.rake +1 -0
  31. data/rake_tasks/jeweler.rake +18 -0
  32. data/rake_tasks/test.rake +2 -0
  33. data/rake_tasks/test_spec.rake +3 -0
  34. data/rake_tasks/test_unit.rake +4 -0
  35. data/spec/Translate_spec.rb +533 -0
  36. data/spec/config/.gitignore +1 -0
  37. data/spec/config/config.yml +10 -0
  38. data/spec/config/database.yml.template +6 -0
  39. data/spec/connection_spec.rb +515 -0
  40. data/spec/cursor_spec.rb +288 -0
  41. data/spec/lib/database_config.rb +33 -0
  42. data/spec/lib/database_server.rb +42 -0
  43. data/spec/lib/postgres_template.rb +60 -0
  44. data/spec/lib/target_database_servers.rb +55 -0
  45. data/spec/lib/temporary_table.rb +45 -0
  46. data/spec/lib/test_config.rb +24 -0
  47. data/spec/lib/test_connection.rb +29 -0
  48. data/spec/lib/test_database.rb +57 -0
  49. data/spec/roundtrip_spec.rb +582 -0
  50. data/spec/spec_helper.rb +10 -0
  51. data/spec/support/all_characters.rb +18 -0
  52. data/spec/support/clear_default_connection.rb +5 -0
  53. data/spec/support/temporary_table.rb +24 -0
  54. data/spec/support/test_connections.rb +10 -0
  55. data/sqlpostgres.gemspec +35 -4
  56. data/test/Connection.test.rb +7 -5
  57. data/test/Select.test.rb +1 -1
  58. data/test/TestConfig.rb +9 -0
  59. data/test/TestUtil.rb +17 -3
  60. metadata +66 -9
  61. data/test/Translate.test.rb +0 -354
  62. data/test/roundtrip.test.rb +0 -565
@@ -0,0 +1 @@
1
+ task :default => :test
@@ -0,0 +1,18 @@
1
+ require 'jeweler'
2
+ Jeweler::Tasks.new do |gem|
3
+ # gem is a Gem::Specification... see
4
+ # http://docs.rubygems.org/read/chapter/20 for more options
5
+ gem.name = 'sqlpostgres'
6
+ gem.homepage = 'http://github.com/wconrad/sqlpostgres'
7
+ gem.license = 'MIT'
8
+ gem.summary = %Q{library for postgresql queries}
9
+ gem.description =
10
+ ('A mini-language for building and executing SQL statements '\
11
+ 'against a postgresql database. This is a very old library, '\
12
+ 'pre-dating active record and lacking many of its refinments. '\
13
+ 'New projects will probably not want to use it.')
14
+ gem.email = 'wconrad@yagni.com'
15
+ gem.authors = ['Wayne Conrad']
16
+ # dependencies defined in Gemfile
17
+ end
18
+ Jeweler::RubygemsDotOrgTasks.new
@@ -0,0 +1,2 @@
1
+ desc "Run all tests"
2
+ task :test => ['test:unit', 'test:spec']
@@ -0,0 +1,3 @@
1
+ require 'rspec/core/rake_task'
2
+ RSpec::Core::RakeTask.new 'test:spec'
3
+ task :spec => ['test:spec']
@@ -0,0 +1,4 @@
1
+ desc "Run (old style) Unit Tests"
2
+ task 'test:unit' do
3
+ system 'test/test'
4
+ end
@@ -0,0 +1,533 @@
1
+ $: << File.dirname(__FILE__)
2
+ require 'spec_helper'
3
+
4
+ module SqlPostgres
5
+
6
+ describe Translate do
7
+
8
+ describe '::escape_sql' do
9
+
10
+ def self.pi
11
+ 3.1415926535897932384626433832795028841971693993751058209749445923
12
+ end
13
+
14
+ def self.testCases
15
+ [
16
+ [nil, 'null'],
17
+ ["", "E''"],
18
+ ["foo", %q"E'foo'"],
19
+ ["fred's", %q"E'fred\\047s'"],
20
+ ['\\', %q"E'\\134'"],
21
+ [Time.local(2000, 1, 2, 3, 4, 5, 6),
22
+ "timestamp '2000-01-02 03:04:05.000006'"],
23
+ [Time.local(1999, 12, 31, 23, 59, 59, 999999),
24
+ "timestamp '1999-12-31 23:59:59.999999'"],
25
+ [1, '1'],
26
+ [-1, '-1'],
27
+ [1.0, "1"],
28
+ [-1.0, "-1"],
29
+ [pi, "3.1415926535898"],
30
+ [-pi, "-3.1415926535898"],
31
+ [1e100, "1e+100"],
32
+ [-1e-100, "-1e-100"],
33
+ [true, "true"],
34
+ [false, "false"],
35
+ [:default, "default"],
36
+ [['1 + %s', 1], '1 + 1'],
37
+ [[:in, 1, 2], '(1, 2)'],
38
+ [[:in, 'foo', 'bar'], "(E'foo', E'bar')"],
39
+ [BigDecimal('0'), '0.0'],
40
+ [BigDecimal('0.'), '0.0'],
41
+ [BigDecimal('1234567890.0987654321'), '1234567890.0987654321'],
42
+ [BigDecimal('0.000000000000000000000000000001'), '0.000000000000000000000000000001'],
43
+ [PgTime.new(0, 0, 0), "time '00:00:00'"],
44
+ [PgTime.new(23, 59, 59), "time '23:59:59'"],
45
+ [
46
+ PgTimeWithTimeZone.new,
47
+ "time with time zone '00:00:00+00:00'"
48
+ ],
49
+ [
50
+ PgTimeWithTimeZone.new(12, 0, 0, -8),
51
+ "time with time zone '12:00:00-08:00'"
52
+ ],
53
+ [
54
+ PgTimeWithTimeZone.new(23, 59, 59, 8),
55
+ "time with time zone '23:59:59+08:00'"
56
+ ],
57
+ [
58
+ DateTime.civil(2001, 1, 1, 0, 0, 0, Rational(7, 24)),
59
+ "timestamp with time zone '2001-01-01 00:00:00.000000000+0700'",
60
+ ],
61
+ [Date.civil(2001, 1, 1), "date '2001-01-01'"],
62
+ [
63
+ PgTimestamp.new(2001, 1, 2, 12, 0, 1),
64
+ "timestamp '2001-01-02 12:00:01.00000'"
65
+ ],
66
+ [PgInterval.new('hours'=>1), "interval '01:00'"],
67
+ [PgInterval.new('days'=>1), "interval '1 day'"],
68
+ [PgPoint.new(1, 2), "point '(1, 2)'"],
69
+ [PgPoint.new(3, 4), "point '(3, 4)'"],
70
+ [
71
+ PgLineSegment.new(PgPoint.new(1, 2), PgPoint.new(3, 4)),
72
+ "lseg '((1, 2), (3, 4))'"
73
+ ],
74
+ [
75
+ PgBox.new(PgPoint.new(1, 2), PgPoint.new(3, 4)),
76
+ "box '((1, 2), (3, 4))'"
77
+ ],
78
+ [
79
+ PgPath.new(true, PgPoint.new(1, 2), PgPoint.new(3, 4)),
80
+ "path '((1, 2), (3, 4))'"
81
+ ],
82
+ [
83
+ PgPolygon.new(PgPoint.new(1, 2), PgPoint.new(3, 4)),
84
+ "polygon '((1, 2), (3, 4))'"
85
+ ],
86
+ [["%s %s", 1, 'Fred'], "1 E'Fred'"],
87
+ ]
88
+
89
+ end
90
+
91
+ testCases.each do |raw, escaped|
92
+
93
+ context raw.inspect do
94
+
95
+ it do
96
+ Translate.escape_sql(raw).should == escaped
97
+ end
98
+
99
+ end
100
+ end
101
+ end
102
+
103
+ describe '::test_escape_array' do
104
+
105
+ def self.testCases
106
+ [
107
+ [nil, "null"],
108
+ [[], %q"'{}'"],
109
+ [[1], %q"ARRAY[1]"],
110
+ [[1, 2], %q"ARRAY[1, 2]"],
111
+ [['foo'], %q"ARRAY[E'foo']"],
112
+ [['\\'], %q"ARRAY[E'\\134']"],
113
+ [["a,b,c"], %q"ARRAY[E'a,b,c']"],
114
+ [["a", "b", "c"], %q"ARRAY[E'a', E'b', E'c']"],
115
+ [["\"Hello\""], %q"ARRAY[E'\"Hello\"']"],
116
+ [
117
+ [[0, 0], [0, 1], [1, 0], [1, 1]],
118
+ "ARRAY[ARRAY[0, 0], ARRAY[0, 1], ARRAY[1, 0], ARRAY[1, 1]]"
119
+ ],
120
+ ]
121
+ end
122
+
123
+ testCases.each do |raw, escaped|
124
+
125
+ context raw.inspect do
126
+
127
+ it do
128
+ Translate.escape_array(raw).should == escaped
129
+ end
130
+
131
+ end
132
+ end
133
+ end
134
+
135
+ describe '::escape_bytea_array' do
136
+
137
+ def self.testCases
138
+ [
139
+ [[], "'{}'"],
140
+ [["", "foo"], "'{\"\",\"foo\"}'"],
141
+ ["\000\037 ", "'{\"\\\\\\\\000\037 \"}'"],
142
+ [["'\\"], "'{\"\\'\\\\\\\\\\\\\\\\\"}'"],
143
+ [["~\177\377"], "'{\"~\177\377\"}'"],
144
+ [["\""], "'{\"\\\\\"\"}'"],
145
+ [nil, "null"],
146
+ ]
147
+ end
148
+
149
+ testCases.each do |raw, escaped|
150
+
151
+ context raw.inspect do
152
+
153
+ it do
154
+ Translate.escape_bytea_array(raw).should == escaped
155
+ end
156
+
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ describe '::sql_to_array' do
163
+
164
+ def self.goodTestCases
165
+ [
166
+ ["{}", []],
167
+ ["{foo}", ["foo"]],
168
+ ["{foo,bar,\"fool's gold\"}", ["foo", "bar", "fool's gold"]],
169
+ ["{\"\\\\\"}", ["\\"]],
170
+ ["{\"\\\\\",fool's}", ["\\", "fool's"]],
171
+ ["{\"a,b,c\"}", ["a,b,c"]],
172
+ ["{\"\\\"Hello!\\\"\"}", ["\"Hello!\""]],
173
+ ["{\"\001\n\037\"}", ["\001\n\037"]],
174
+ [
175
+ "{{f,f},{f,t},{t,f},{t,t}}",
176
+ [["f", "f"], ["f", "t"], ["t", "f"], ["t", "t"]],
177
+ ],
178
+ ]
179
+ end
180
+
181
+ def self.badTestCases
182
+ [
183
+ "",
184
+ "{",
185
+ "{foo",
186
+ "{foo}x",
187
+ ]
188
+ end
189
+
190
+ goodTestCases.each do |sql, array|
191
+
192
+ context sql.inspect do
193
+
194
+ it do
195
+ Translate.sql_to_array(sql).should == array
196
+ end
197
+
198
+ end
199
+
200
+ end
201
+
202
+ badTestCases.each do |sql|
203
+
204
+ context sql.inspect do
205
+
206
+ it do
207
+ expect do
208
+ Translate.sql_to_array(sql).should == array
209
+ end.to raise_error(ArgumentError, sql.inspect)
210
+ end
211
+
212
+ end
213
+
214
+ end
215
+
216
+ end
217
+
218
+ describe '::test_escape_qchar' do
219
+
220
+ def self.testCases
221
+ [
222
+ [nil, 'null'],
223
+ ["\000", %q"E'\\000'"],
224
+ ["\001", %q"E'\\001'"],
225
+ ["\377", %q"E'\\377'"],
226
+ ["a", "E'\\141'"],
227
+ ]
228
+ end
229
+
230
+ testCases.each do |raw, escaped|
231
+
232
+ context raw.inspect do
233
+
234
+ it do
235
+ Translate.escape_qchar(raw).should == escaped
236
+ end
237
+
238
+ end
239
+
240
+ end
241
+
242
+ end
243
+
244
+ describe '::escape_bytea' do
245
+
246
+ def self.testCases
247
+ [
248
+ [nil, 'null', 'null'],
249
+ [:default, "default", "default"],
250
+ ["", "''", "'\\x'"],
251
+ ["foo", %q"'foo'", %q"'\\x666f6f'"],
252
+ ["\000\037 ", %q"'\\000\\037 '", %q"'\\x001f20'"],
253
+ ["'\\", %q"'''\\\\'", %q"'\\x275c'"],
254
+ ["~\177\377", "'~\\177\\377'", "'\\x7e7fff'"],
255
+ ]
256
+ end
257
+
258
+ testCases.each do |raw, escaped_84, escaped_90|
259
+
260
+ test_connections.each do |test_context, test_connection|
261
+
262
+ context test_context do
263
+
264
+ context raw.inspect do
265
+
266
+ let(:connection) {test_connection}
267
+
268
+ it do
269
+ pgconn = connection.pgconn
270
+ escaped = if pgconn.server_version < 9_00_00
271
+ escaped_84
272
+ else
273
+ escaped_90
274
+ end
275
+ if pgconn.server_version < 9_01_00
276
+ escaped = escaped.gsub("\\", "\\\\\\")
277
+ escaped = 'E' + escaped if raw.is_a?(String)
278
+ end
279
+ Translate.escape_bytea(raw, pgconn).should == escaped
280
+ end
281
+ end
282
+
283
+ end
284
+
285
+ end
286
+
287
+ end
288
+
289
+ end
290
+
291
+ describe '::unescape_bytea' do
292
+
293
+ def self.testCases
294
+ [
295
+ ["", ""],
296
+ ["abc", "abc"],
297
+ ["\\\\", "\\"],
298
+ ["\\001", "\001"],
299
+ ["\\x01", "\001"],
300
+ ["\\037", "\037"],
301
+ ["\\x1f", "\037"],
302
+ ["\\177", "\177"],
303
+ ["\\200", "\200"],
304
+ ["\\377", "\377"],
305
+ ["\\477", "477"], #DEBUG why had we been testing handling of badly escaped bytea would badly unescape???
306
+ ["\\387", "387"],
307
+ ["\\378", "378"],
308
+ ["\\n", "n"],
309
+ ["abc\\", "abc"],
310
+ ["\\x779c", "w\234"]
311
+ ]
312
+ end
313
+
314
+ testCases.each do |escaped, raw|
315
+
316
+ test_connections.each do |test_context, test_connection|
317
+
318
+ context test_context do
319
+
320
+ context escaped.inspect do
321
+
322
+ let(:connection) {test_connection}
323
+
324
+ it do
325
+ pgconn = connection.pgconn
326
+ Translate.unescape_bytea(escaped, pgconn).should == raw
327
+ end
328
+ end
329
+ end
330
+ end
331
+ end
332
+ end
333
+
334
+ describe '::unescape_qchar' do
335
+
336
+ def self.testCases
337
+ [
338
+ ["", "\000"],
339
+ ["\001", "\001"],
340
+ ["\037", "\037"],
341
+ [" ", " "],
342
+ ["~", '~'],
343
+ ["\277", "\277"],
344
+ ["\300", "\300"],
345
+ ["\377", "\377"],
346
+ ]
347
+ end
348
+
349
+ testCases.each do |escaped, raw|
350
+
351
+ context escaped do
352
+
353
+ it do
354
+ Translate.unescape_qchar(escaped).should == raw
355
+ end
356
+
357
+ end
358
+
359
+ end
360
+
361
+ end
362
+
363
+ describe '::escapeSql' do
364
+
365
+ context 'Select' do
366
+
367
+ it do
368
+ select = mock(Select, :statement => 'foo')
369
+ Translate.escape_sql(select).should == "(foo)"
370
+ end
371
+
372
+ end
373
+
374
+ context "AllCharValues" do
375
+
376
+ RANGE = 1..255
377
+
378
+ test_connections.each do |test_context, test_connection|
379
+
380
+ context test_context do
381
+
382
+ let(:connection) {test_connection}
383
+ include_context('temporary table',
384
+ :table_name => 'escape_sql_test',
385
+ :columns => ['i int',
386
+ 't text'
387
+ ])
388
+
389
+ RANGE.each do |i|
390
+
391
+ context "char: #{i}" do
392
+
393
+ before(:each) do
394
+ insert = "insert into escape_sql_test (i, t) "\
395
+ "values (#{i}, #{Translate.escape_sql(i.chr)});"
396
+ connection.query(insert)
397
+ select = "select i, t, length(t) from escape_sql_test order by i;"
398
+ row = connection.query(select).first
399
+ @char_number, @text, @length = *row
400
+ end
401
+
402
+ it do
403
+ @length.should == '1'
404
+ end
405
+
406
+ it do
407
+ @text.should == i.chr
408
+ end
409
+ end
410
+
411
+ end
412
+ end
413
+
414
+ end
415
+
416
+ end
417
+
418
+ end
419
+
420
+ describe '::substitute_values' do
421
+
422
+ def self.testCases
423
+ [
424
+ ["foo", "foo"],
425
+ [["bar %s", 1], "bar 1"],
426
+ [["bar %s", "O'Malley"], "bar E'O\\047Malley'"],
427
+ [["%s %s", nil, 1.23], "null 1.23"],
428
+ ]
429
+ end
430
+
431
+ testCases.each do |raw, expected|
432
+
433
+ context raw do
434
+
435
+ it do
436
+ Translate.substitute_values(raw).should == expected
437
+ end
438
+ end
439
+
440
+ end
441
+ end
442
+
443
+ describe 'datetime' do
444
+ def self.testCases
445
+ [
446
+ [2003, 10, 18, 11, 30, 24, -7],
447
+ [2001, 1, 1, 0, 0, 0, 0],
448
+ [1970, 12, 31, 23, 59, 59, -11],
449
+ ]
450
+ end
451
+
452
+ testCases.each do |time_parts|
453
+
454
+ context time_parts.inspect do
455
+
456
+ let(:sql) {"%04d-%02d-%02d %02d:%02d:%02d.000000000%+03d00" % time_parts}
457
+ let(:dateTime) do
458
+ DateTime.civil(*time_parts[0..5] +
459
+ [Rational(time_parts[6], 24)])
460
+ end
461
+
462
+ describe 'sql_to' do
463
+
464
+ it do
465
+ Translate.sql_to_datetime(sql).should == dateTime
466
+ end
467
+
468
+ end
469
+
470
+ describe "to_sql" do
471
+
472
+ it do
473
+ Translate.datetime_to_sql(dateTime).should == sql
474
+ end
475
+
476
+ end
477
+
478
+ end
479
+ end
480
+ end
481
+
482
+ describe '::sql_to_date' do
483
+
484
+ def self.testCases
485
+ [
486
+ [2000, 1, 1],
487
+ [1899, 12, 31],
488
+ ]
489
+ end
490
+
491
+ testCases.each do |time_parts|
492
+
493
+ context time_parts.inspect do
494
+
495
+ it do
496
+ sql = "%04d-%02d-%02d" % time_parts
497
+ date = Date.civil(*time_parts)
498
+ Translate.sql_to_date(sql).should == date
499
+ end
500
+
501
+ end
502
+
503
+ end
504
+ end
505
+
506
+ describe '::deep_collect' do
507
+ def self.testCases
508
+ [
509
+ ["1", 1],
510
+ [[], []],
511
+ [["1"], [1]],
512
+ [["1", "2"], [1, 2]],
513
+ [["1", ["2", "3"], []], [1, [2, 3], []]],
514
+ ]
515
+ end
516
+
517
+ testCases.each do |input, output|
518
+
519
+ context input.inspect do
520
+
521
+ it do
522
+ Translate.deep_collect(input) do |e|
523
+ e.to_i
524
+ end.should == output
525
+ end
526
+ end
527
+ end
528
+ end
529
+
530
+ end
531
+
532
+
533
+ end