sqlpostgres 1.2.6 → 1.3.0

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 (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