rdo-postgres 0.0.1

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.
@@ -0,0 +1,15 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include <stdio.h>
9
+ #include <ruby.h>
10
+
11
+ /** Factory to create a new StatementExecutor */
12
+ VALUE rdo_postgres_statement_executor_new(VALUE driver, VALUE cmd, VALUE name);
13
+
14
+ /** Initializer for the statements framework */
15
+ void Init_rdo_postgres_statements(void);
@@ -0,0 +1,85 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include "tuples.h"
9
+ #include "casts.h"
10
+ #include <stdlib.h>
11
+
12
+ /** Wrapper for the TupleList class */
13
+ typedef struct {
14
+ PGresult * res;
15
+ int encoding;
16
+ } RDOPostgresTupleList;
17
+
18
+ /** class RDO::Postgres::TupleList */
19
+ static VALUE rdo_postgres_cTupleList;
20
+
21
+ /** Used to free the struct wrapped by TupleList during GC */
22
+ static void rdo_postgres_tuple_list_free(RDOPostgresTupleList * list) {
23
+ PQclear(list->res);
24
+ free(list);
25
+ }
26
+
27
+ /** Factory to return a new instance of TupleList for a result */
28
+ VALUE rdo_postgres_tuple_list_new(PGresult * res, int encoding) {
29
+ RDOPostgresTupleList * list = malloc(sizeof(RDOPostgresTupleList));
30
+ list->res = res;
31
+ list->encoding = encoding;
32
+
33
+ VALUE obj = Data_Wrap_Struct(rdo_postgres_cTupleList, 0,
34
+ rdo_postgres_tuple_list_free, list);
35
+
36
+ rb_obj_call_init(obj, 0, NULL);
37
+
38
+ return obj;
39
+ }
40
+
41
+ /** Allow iteration over all tuples, yielding Hashes into a block */
42
+ static VALUE rdo_postgres_tuple_list_each(VALUE self) {
43
+ if (!rb_block_given_p()) {
44
+ return self;
45
+ }
46
+
47
+ RDOPostgresTupleList * list;
48
+ Data_Get_Struct(self, RDOPostgresTupleList, list);
49
+
50
+ int i = 0;
51
+ int ntups = PQntuples(list->res);
52
+
53
+ for (; i < ntups; ++i) {
54
+ VALUE hash = rb_hash_new();
55
+ int j = 0;
56
+ int nfields = PQnfields(list->res);
57
+
58
+ for (; j < nfields; ++j) {
59
+ rb_hash_aset(hash,
60
+ ID2SYM(rb_intern(PQfname(list->res, j))),
61
+ rdo_postgres_cast_value(list->res, i, j, list->encoding));
62
+ }
63
+
64
+ rb_yield(hash);
65
+ }
66
+
67
+ return self;
68
+ }
69
+
70
+ /**
71
+ * Invoked during driver initialization to set up the TupleList.
72
+ */
73
+ void Init_rdo_postgres_tuples(void) {
74
+ VALUE mPostgres = rb_path2class("RDO::Postgres");
75
+
76
+ rdo_postgres_cTupleList = rb_define_class_under(mPostgres,
77
+ "TupleList", rb_cObject);
78
+
79
+ rb_define_method(rdo_postgres_cTupleList,
80
+ "each", rdo_postgres_tuple_list_each, 0);
81
+
82
+ rb_include_module(rdo_postgres_cTupleList, rb_mEnumerable);
83
+
84
+ Init_rdo_postgres_casts();
85
+ }
@@ -0,0 +1,20 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include <stdio.h>
9
+ #include <ruby.h>
10
+ #include <libpq-fe.h>
11
+
12
+ /**
13
+ * Create a new RDO::Postgres::TupleList.
14
+ */
15
+ VALUE rdo_postgres_tuple_list_new(PGresult * res, int encoding);
16
+
17
+ /**
18
+ * Called during driver initialization to define needed tuple classes.
19
+ */
20
+ void Init_rdo_postgres_tuples(void);
@@ -0,0 +1 @@
1
+ require "rdo/postgres"
@@ -0,0 +1,16 @@
1
+ ##
2
+ # RDO PostgreSQL driver.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ require "rdo"
9
+ require "rdo/postgres/version"
10
+ require "rdo/postgres/driver"
11
+ require "rdo/rdo_postgres" # c ext
12
+
13
+ # Register name variants for postgresql schemes
14
+ %w[postgres postgresql].each do |name|
15
+ RDO::Connection.register_driver(name, RDO::Postgres::Driver)
16
+ end
@@ -0,0 +1,68 @@
1
+ ##
2
+ # RDO PostgreSQL driver.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ module RDO
9
+ module Postgres
10
+ # Driver for the Postgres server.
11
+ #
12
+ # All default behaviour is overloaded.
13
+ class Driver < RDO::Driver
14
+ # most implementation defined by C extension
15
+
16
+ # Internally this driver uses prepared statements.
17
+ #
18
+ # @param [String] stmt
19
+ # the statement to execute
20
+ #
21
+ # @param [Object...] *args
22
+ # bind parameters to execute with
23
+ #
24
+ # @return [RDO::Result]
25
+ # a result containing any tuples and query info
26
+ def execute(stmt, *args)
27
+ prepare(stmt).execute(*args)
28
+ end
29
+
30
+ private
31
+
32
+ # Passed to PQconnectdb().
33
+ #
34
+ # e.g. "host=localhost user=bob password=secret dbname=bobs_db"
35
+ def connect_db_string
36
+ {
37
+ host: options[:host],
38
+ port: options[:port],
39
+ dbname: options[:database],
40
+ user: options[:user],
41
+ password: options[:password],
42
+ connect_timeout: options[:connect_timeout]
43
+ }.reject{|k,v| v.nil?}.map{|pair| pair.join("=")}.join(" ")
44
+ end
45
+
46
+ def after_open
47
+ set_time_zone
48
+ set_encoding
49
+ end
50
+
51
+ def set_time_zone
52
+ if options[:time_zone]
53
+ execute("SET TIME ZONE '#{quote(options[:time_zone])}'")
54
+ else
55
+ execute("SET TIME ZONE interval '#{quote(RDO::Util.system_time_zone)}' hour to minute")
56
+ end
57
+ end
58
+
59
+ def set_encoding
60
+ execute("SET NAMES '#{quote(encoding)}'")
61
+ end
62
+
63
+ def encoding
64
+ options.fetch(:encoding, "utf-8")
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,12 @@
1
+ ##
2
+ # RDO PostgreSQL driver.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ module RDO
9
+ module Postgres
10
+ VERSION = "0.0.1"
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rdo/postgres/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["d11wtq"]
6
+ gem.email = ["chris@w3style.co.uk"]
7
+ gem.description = "Provides access to PostgreSQL using the RDO interface"
8
+ gem.summary = "PostgreSQL Adapter for RDO"
9
+ gem.homepage = "https://github.com/d11wtq/rdo-postgres"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "rdo-postgres"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = RDO::Postgres::VERSION
17
+ gem.extensions = ["ext/rdo_postgres/extconf.rb"]
18
+
19
+ gem.add_runtime_dependency "rdo", ">= 0.0.1"
20
+
21
+ gem.add_development_dependency "rspec"
22
+ gem.add_development_dependency "rake-compiler"
23
+ end
@@ -0,0 +1,902 @@
1
+ require "spec_helper"
2
+ require "bigdecimal"
3
+ require "date"
4
+
5
+ describe RDO::Postgres::Driver, "bind parameter support" do
6
+ let(:connection) { RDO.connect(connection_uri) }
7
+ let(:table) { "" }
8
+
9
+ before(:each) do
10
+ connection.execute("DROP SCHEMA IF EXISTS rdo_test CASCADE")
11
+ connection.execute("CREATE SCHEMA rdo_test")
12
+ connection.execute("SET search_path = rdo_test")
13
+ connection.execute(table)
14
+ end
15
+
16
+ after(:each) do
17
+ begin
18
+ connection.execute("DROP SCHEMA IF EXISTS rdo_test CASCADE")
19
+ rescue RDO::Exception => e
20
+ # accept that tests may fail for other reasons, don't also fail on cleanup
21
+ ensure
22
+ connection.close rescue nil
23
+ end
24
+ end
25
+
26
+ describe "String param" do
27
+ context "against a text field" do
28
+ let(:table) { "CREATE TABLE test (id serial primary key, name text)" }
29
+ let(:tuple) do
30
+ connection.execute(
31
+ "INSERT INTO test (name) VALUES (?) RETURNING *",
32
+ "Fern Cotton"
33
+ ).first
34
+ end
35
+
36
+ it "is inferred correctly" do
37
+ tuple.should == {id: 1, name: "Fern Cotton"}
38
+ end
39
+ end
40
+
41
+ context "against an integer field" do
42
+ let(:table) { "CREATE TABLE test (id serial primary key, age integer)" }
43
+ let(:tuple) do
44
+ connection.execute("INSERT INTO test (age) VALUES (?) RETURNING *", "32").first
45
+ end
46
+
47
+ it "is inferred correctly" do
48
+ tuple.should == {id: 1, age: 32}
49
+ end
50
+ end
51
+
52
+ context "against a boolean field" do
53
+ let(:table) { "CREATE TABLE test (id serial primary key, rad boolean)" }
54
+ let(:tuple) do
55
+ connection.execute("INSERT INTO test (rad) VALUES (?) RETURNING *", "true").first
56
+ end
57
+
58
+ it "is inferred correctly" do
59
+ tuple.should == {id: 1, rad: true}
60
+ end
61
+ end
62
+
63
+ context "against a float field" do
64
+ let(:table) { "CREATE TABLE test (id serial primary key, score float)" }
65
+ let(:tuple) do
66
+ connection.execute("INSERT INTO test (score) VALUES (?) RETURNING *", "85.4").first
67
+ end
68
+
69
+ it "is inferred correctly" do
70
+ tuple.should == {id: 1, score: 85.4}
71
+ end
72
+ end
73
+
74
+ context "against a decimal field" do
75
+ let(:table) { "CREATE TABLE test (id serial primary key, score decimal)" }
76
+ let(:tuple) do
77
+ connection.execute("INSERT INTO test (score) VALUES (?) RETURNING *", "85.4").first
78
+ end
79
+
80
+ it "is inferred correctly" do
81
+ tuple.should == {id: 1, score: BigDecimal("85.4")}
82
+ end
83
+ end
84
+
85
+ context "against a date field" do
86
+ let(:table) { "CREATE TABLE test (id serial primary key, dob date)" }
87
+ let(:tuple) do
88
+ connection.execute("INSERT INTO test (dob) VALUES (?) RETURNING *", "1983-05-03").first
89
+ end
90
+
91
+ it "is inferred correctly" do
92
+ tuple.should == {id: 1, dob: Date.new(1983, 5, 3)}
93
+ end
94
+ end
95
+
96
+ context "against a timestamp field" do
97
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamp)" }
98
+ let(:tuple) do
99
+ connection.execute(
100
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
101
+ "2012-09-22 10:00:05"
102
+ ).first
103
+ end
104
+
105
+ it "is inferred correctly" do
106
+ tuple.should == {id: 1, created_at: DateTime.new(2012, 9, 22, 10, 0, 5, DateTime.now.zone)}
107
+ end
108
+ end
109
+
110
+ context "against a timestamptz field" do
111
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamptz)" }
112
+ let(:tuple) do
113
+ connection.execute(
114
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
115
+ "2012-09-22 10:00:05 -5"
116
+ ).first
117
+ end
118
+
119
+ it "is inferred correctly" do
120
+ tuple.should == {id: 1, created_at: DateTime.new(2012, 9, 22, 10, 0, 5, "-5")}
121
+ end
122
+ end
123
+
124
+ context "against a bytea field" do
125
+ let(:table) { "CREATE TABLE test (id serial primary key, salt bytea)" }
126
+ let(:tuple) do
127
+ connection.execute(
128
+ "INSERT INTO test (salt) VALUES (?) RETURNING *", "\x00\x01\x02"
129
+ ).first
130
+ end
131
+
132
+ it "is inferred correctly" do
133
+ tuple.should == {id: 1, salt: "\x00\x01\x02"}
134
+ end
135
+
136
+ context "that is empty" do
137
+ let(:table) { "CREATE TABLE test (id serial primary key, salt bytea)" }
138
+ let(:tuple) do
139
+ connection.execute(
140
+ "INSERT INTO test (salt) VALUES (?) RETURNING *", ""
141
+ ).first
142
+ end
143
+
144
+ it "is inferred correctly" do
145
+ tuple.should == {id: 1, salt: ""}
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "Fixnum param" do
152
+ context "against an integer field" do
153
+ let(:table) { "CREATE TABLE test (id serial primary key, age integer)" }
154
+ let(:tuple) do
155
+ connection.execute(
156
+ "INSERT INTO test (age) VALUES (?) RETURNING *",
157
+ 42
158
+ ).first
159
+ end
160
+
161
+ it "is inferred correctly" do
162
+ tuple.should == {id: 1, age: 42}
163
+ end
164
+ end
165
+
166
+ context "against a text field" do
167
+ let(:table) { "CREATE TABLE test (id serial primary key, name text)" }
168
+ let(:tuple) do
169
+ connection.execute(
170
+ "INSERT INTO test (name) VALUES (?) RETURNING *",
171
+ 42
172
+ ).first
173
+ end
174
+
175
+ it "is inferred correctly" do
176
+ tuple.should == {id: 1, name: "42"}
177
+ end
178
+ end
179
+
180
+ context "against a float field" do
181
+ let(:table) { "CREATE TABLE test (id serial primary key, score float)" }
182
+ let(:tuple) do
183
+ connection.execute(
184
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
185
+ 42
186
+ ).first
187
+ end
188
+
189
+ it "is inferred correctly" do
190
+ tuple.should == {id: 1, score: 42.0}
191
+ end
192
+ end
193
+
194
+ context "agsinst a decimal field" do
195
+ let(:table) { "CREATE TABLE test (id serial primary key, score decimal)" }
196
+ let(:tuple) do
197
+ connection.execute(
198
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
199
+ 42
200
+ ).first
201
+ end
202
+
203
+ it "is inferred correctly" do
204
+ tuple.should == {id: 1, score: BigDecimal("42")}
205
+ end
206
+ end
207
+
208
+ context "against a boolean field" do
209
+ let(:table) { "CREATE TABLE test (id serial primary key, rad boolean)" }
210
+
211
+ context "when it is 0" do
212
+ let(:tuple) do
213
+ connection.execute(
214
+ "INSERT INTO test (rad) VALUES (?) RETURNING *",
215
+ 0
216
+ ).first
217
+ end
218
+
219
+ it "is inferred correctly (false)" do
220
+ tuple.should == {id: 1, rad: false}
221
+ end
222
+ end
223
+
224
+ context "when it is 1" do
225
+ let(:tuple) do
226
+ connection.execute(
227
+ "INSERT INTO test (rad) VALUES (?) RETURNING *",
228
+ 1
229
+ ).first
230
+ end
231
+
232
+ it "is inferred correctly (true)" do
233
+ tuple.should == {id: 1, rad: true}
234
+ end
235
+ end
236
+ end
237
+
238
+ context "against a bytea field" do
239
+ let(:table) { "CREATE TABLE test (id serial primary key, salt bytea)" }
240
+ let(:tuple) do
241
+ connection.execute(
242
+ "INSERT INTO test (salt) VALUES (?) RETURNING *",
243
+ 42
244
+ ).first
245
+ end
246
+
247
+ it "is inferred correctly" do
248
+ tuple.should == {id: 1, salt: "42"}
249
+ end
250
+ end
251
+ end
252
+
253
+ describe "Float param" do
254
+ context "against a float field" do
255
+ let(:table) { "CREATE TABLE test (id serial primary key, score float)" }
256
+
257
+ context "when it is NaN" do
258
+ let(:tuple) do
259
+ connection.execute(
260
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
261
+ Float::NAN
262
+ ).first
263
+ end
264
+
265
+ it "is inferred correctly" do
266
+ tuple.should == {id: 1, score: Float::NAN}
267
+ end
268
+ end
269
+
270
+ context "when it is Infinity" do
271
+ let(:tuple) do
272
+ connection.execute(
273
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
274
+ Float::INFINITY
275
+ ).first
276
+ end
277
+
278
+ it "is inferred correctly" do
279
+ tuple.should == {id: 1, score: Float::INFINITY}
280
+ end
281
+ end
282
+
283
+ context "when it is -Infinity" do
284
+ let(:tuple) do
285
+ connection.execute(
286
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
287
+ -Float::INFINITY
288
+ ).first
289
+ end
290
+
291
+ it "is inferred correctly" do
292
+ tuple.should == {id: 1, score: -Float::INFINITY}
293
+ end
294
+ end
295
+
296
+ context "when it is a real number" do
297
+ let(:tuple) do
298
+ connection.execute(
299
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
300
+ 12.5
301
+ ).first
302
+ end
303
+
304
+ it "is inferred correctly" do
305
+ tuple.should == {id: 1, score: 12.5}
306
+ end
307
+ end
308
+ end
309
+
310
+ context "against a text field" do
311
+ let(:table) { "CREATE TABLE test (id serial primary key, name text)" }
312
+ let(:tuple) do
313
+ connection.execute(
314
+ "INSERT INTO test (name) VALUES (?) RETURNING *",
315
+ 12.5
316
+ ).first
317
+ end
318
+
319
+ it "is inferred correctly" do
320
+ tuple.should == {id: 1, name: "12.5"}
321
+ end
322
+ end
323
+
324
+ context "against a decimal field" do
325
+ let(:table) { "CREATE TABLE test (id serial primary key, score decimal)" }
326
+ let(:tuple) do
327
+ connection.execute(
328
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
329
+ 12.2
330
+ ).first
331
+ end
332
+
333
+ it "is inferred correctly" do
334
+ tuple.should == {id: 1, score: BigDecimal("12.2")}
335
+ end
336
+ end
337
+ end
338
+
339
+ describe "BigDecimal param" do
340
+ context "against a decimal field" do
341
+ let(:table) { "CREATE TABLE test (id serial primary key, score decimal)" }
342
+
343
+ context "when it is NaN" do
344
+ let(:tuple) do
345
+ connection.execute(
346
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
347
+ BigDecimal("NaN")
348
+ ).first
349
+ end
350
+
351
+ it "is inferred correctly" do
352
+ tuple[:score].should be_a_kind_of(BigDecimal)
353
+ tuple[:score].should be_nan
354
+ end
355
+ end
356
+
357
+ context "when it is a real number" do
358
+ let(:tuple) do
359
+ connection.execute(
360
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
361
+ BigDecimal("12.2")
362
+ ).first
363
+ end
364
+
365
+ it "is inferred correctly" do
366
+ tuple.should == {id: 1, score: BigDecimal("12.2")}
367
+ end
368
+ end
369
+ end
370
+
371
+ context "against a text field" do
372
+ let(:table) { "CREATE TABLE test (id serial primary key, name text)" }
373
+ let(:tuple) do
374
+ connection.execute(
375
+ "INSERT INTO test (name) VALUES (?) RETURNING *",
376
+ BigDecimal("12.7")
377
+ ).first
378
+ end
379
+
380
+ it "is inferred correctly" do
381
+ tuple.should == {id: 1, name: BigDecimal("12.7").to_s}
382
+ end
383
+ end
384
+
385
+ context "against a float field" do
386
+ let(:table) { "CREATE TABLE test (id serial primary key, score float)" }
387
+ let(:tuple) do
388
+ connection.execute(
389
+ "INSERT INTO test (score) VALUES (?) RETURNING *",
390
+ BigDecimal("12.7")
391
+ ).first
392
+ end
393
+
394
+ it "is inferred correctly" do
395
+ tuple.should == {id: 1, score: 12.7}
396
+ end
397
+ end
398
+ end
399
+
400
+ describe "Date param" do
401
+ context "against a Date field" do
402
+ let(:table) { "CREATE TABLE test (id serial primary key, dob date)" }
403
+ let(:tuple) do
404
+ connection.execute(
405
+ "INSERT INTO test (dob) VALUES (?) RETURNING *",
406
+ Date.new(1983, 5, 3)
407
+ ).first
408
+ end
409
+
410
+ it "is inferred correctly" do
411
+ tuple.should == {id: 1, dob: Date.new(1983, 5, 3)}
412
+ end
413
+ end
414
+
415
+ context "against a text field" do
416
+ let(:table) { "CREATE TABLE test (id serial primary key, name text)" }
417
+ let(:tuple) do
418
+ connection.execute(
419
+ "INSERT INTO test (name) VALUES (?) RETURNING *",
420
+ Date.new(1983, 5, 3)
421
+ ).first
422
+ end
423
+
424
+ it "is inferred correctly" do
425
+ tuple.should == {id: 1, name: "1983-05-03"}
426
+ end
427
+ end
428
+
429
+ context "against a timestamp field" do
430
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamp)" }
431
+ let(:tuple) do
432
+ connection.execute(
433
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
434
+ Date.new(1983, 5, 3)
435
+ ).first
436
+ end
437
+
438
+ it "is inferred correctly" do
439
+ tuple.should == {id: 1, created_at: DateTime.new(1983, 5, 3, 0, 0, 0, DateTime.now.zone)}
440
+ end
441
+ end
442
+
443
+ context "against a timestamptz field" do
444
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamptz)" }
445
+ let(:tuple) do
446
+ connection.execute(
447
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
448
+ Date.new(1983, 5, 3)
449
+ ).first
450
+ end
451
+
452
+ it "is inferred correctly" do
453
+ tuple.should == {id: 1, created_at: DateTime.new(1983, 5, 3, 0, 0, 0, DateTime.now.zone)}
454
+ end
455
+ end
456
+ end
457
+
458
+ describe "Time param" do
459
+ context "against a timestamp field" do
460
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamp)" }
461
+ let(:tuple) do
462
+ connection.execute(
463
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
464
+ Time.new(2012, 9, 22, 5, 16, 58)
465
+ ).first
466
+ end
467
+
468
+ it "is inferred correctly" do
469
+ tuple.should == {id: 1, created_at: DateTime.new(2012, 9, 22, 5, 16, 58, DateTime.now.zone)}
470
+ end
471
+ end
472
+
473
+ context "against a timestamptz field" do
474
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamptz)" }
475
+
476
+ context "without a timezone given" do
477
+ let(:tuple) do
478
+ connection.execute(
479
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
480
+ Time.new(2012, 9, 22, 5, 16, 58)
481
+ ).first
482
+ end
483
+
484
+ it "is inferred correctly" do
485
+ tuple.should == {id: 1, created_at: DateTime.new(2012, 9, 22, 5, 16, 58, DateTime.now.zone)}
486
+ end
487
+ end
488
+
489
+ context "with a timezone given" do
490
+ let(:tuple) do
491
+ connection.execute(
492
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
493
+ Time.new(2012, 9, 22, 5, 16, 58, "-07:00")
494
+ ).first
495
+ end
496
+
497
+ it "is inferred correctly" do
498
+ tuple.should == {id: 1, created_at: DateTime.new(2012, 9, 22, 5, 16, 58, "-07:00")}
499
+ end
500
+ end
501
+ end
502
+
503
+ context "against a date field" do
504
+ let(:table) { "CREATE TABLE test (id serial primary key, dob date)" }
505
+ let(:tuple) do
506
+ connection.execute(
507
+ "INSERT INTO test (dob) VALUES (?) RETURNING *",
508
+ Time.new(1983, 5, 3, 6, 13, 0)
509
+ ).first
510
+ end
511
+
512
+ it "is inferred correctly" do
513
+ tuple.should == {id: 1, dob: Date.new(1983, 5, 3)}
514
+ end
515
+ end
516
+
517
+ context "against a text field" do
518
+ let(:table) { "CREATE TABLE test (id serial primary key, name text)" }
519
+ let(:tuple) do
520
+ connection.execute(
521
+ "INSERT INTO test (name) VALUES (?) RETURNING *",
522
+ Time.new(2012, 9, 22, 5, 16, 58)
523
+ ).first
524
+ end
525
+
526
+ it "is inferred correctly" do
527
+ tuple.should == {id: 1, name: Time.new(2012, 9, 22, 5, 16, 58).to_s}
528
+ end
529
+ end
530
+ end
531
+
532
+ describe "DateTime param" do
533
+ context "against a timestamp field" do
534
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamp)" }
535
+ let(:tuple) do
536
+ connection.execute(
537
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
538
+ DateTime.new(1983, 5, 3, 6, 13, 0)
539
+ ).first
540
+ end
541
+
542
+ it "is inferred correctly" do
543
+ tuple.should == {id: 1, created_at: DateTime.new(1983, 5, 3, 6, 13, 0, DateTime.now.zone)}
544
+ end
545
+ end
546
+
547
+ context "against a timestamptz field" do
548
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamptz)" }
549
+
550
+ context "with a time zone given" do
551
+ let(:tuple) do
552
+ connection.execute(
553
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
554
+ DateTime.new(1983, 5, 3, 6, 13, 0, "-07:00")
555
+ ).first
556
+ end
557
+
558
+ it "is inferred correctly" do
559
+ tuple.should == {id: 1, created_at: DateTime.new(1983, 5, 3, 6, 13, 0, "-07:00")}
560
+ end
561
+ end
562
+
563
+ context "without a time zone given" do
564
+ let(:tuple) do
565
+ connection.execute(
566
+ "INSERT INTO test (created_at) VALUES (?) RETURNING *",
567
+ DateTime.new(1983, 5, 3, 6, 13, 0)
568
+ ).first
569
+ end
570
+
571
+ it "is inferred correctly" do
572
+ tuple.should == {id: 1, created_at: DateTime.new(1983, 5, 3, 6, 13, 0)}
573
+ end
574
+ end
575
+ end
576
+
577
+ context "against a date field" do
578
+ let(:table) { "CREATE TABLE test (id serial primary key, dob date)" }
579
+ let(:tuple) do
580
+ connection.execute(
581
+ "INSERT INTO test (dob) VALUES (?) RETURNING *",
582
+ DateTime.new(1983, 5, 3, 6, 13, 0)
583
+ ).first
584
+ end
585
+
586
+ it "is inferred correctly" do
587
+ tuple.should == {id: 1, dob: Date.new(1983, 5, 3)}
588
+ end
589
+ end
590
+
591
+ context "against a text field" do
592
+ let(:table) { "CREATE TABLE test (id serial primary key, name text)" }
593
+ let(:tuple) do
594
+ connection.execute(
595
+ "INSERT INTO test (name) VALUES (?) RETURNING *",
596
+ DateTime.new(1983, 5, 3, 6, 13, 0)
597
+ ).first
598
+ end
599
+
600
+ it "is inferred correctly" do
601
+ tuple.should == {id: 1, name: DateTime.new(1983, 5, 3, 6, 13, 0).to_s}
602
+ end
603
+ end
604
+ end
605
+
606
+ describe "nil param" do
607
+ context "against a text field" do
608
+ let(:table) { "CREATE TABLE test (id serial primary key, name text)" }
609
+ let(:tuple) do
610
+ connection.execute("INSERT INTO test (name) VALUES (?) RETURNING *", nil).first
611
+ end
612
+
613
+ it "is inferred correctly" do
614
+ tuple.should == {id: 1, name: nil}
615
+ end
616
+ end
617
+
618
+ context "against an integer field" do
619
+ let(:table) { "CREATE TABLE test (id serial primary key, age integer)" }
620
+ let(:tuple) do
621
+ connection.execute("INSERT INTO test (age) VALUES (?) RETURNING *", nil).first
622
+ end
623
+
624
+ it "is inferred correctly" do
625
+ tuple.should == {id: 1, age: nil}
626
+ end
627
+ end
628
+
629
+ context "against a float field" do
630
+ let(:table) { "CREATE TABLE test (id serial primary key, score float)" }
631
+ let(:tuple) do
632
+ connection.execute("INSERT INTO test (score) VALUES (?) RETURNING *", nil).first
633
+ end
634
+
635
+ it "is inferred correctly" do
636
+ tuple.should == {id: 1, score: nil}
637
+ end
638
+ end
639
+
640
+ context "against a decimal field" do
641
+ let(:table) { "CREATE TABLE test (id serial primary key, score decimal)" }
642
+ let(:tuple) do
643
+ connection.execute("INSERT INTO test (score) VALUES (?) RETURNING *", nil).first
644
+ end
645
+
646
+ it "is inferred correctly" do
647
+ tuple.should == {id: 1, score: nil}
648
+ end
649
+ end
650
+
651
+ context "against a boolean field" do
652
+ let(:table) { "CREATE TABLE test (id serial primary key, rad boolean)" }
653
+ let(:tuple) do
654
+ connection.execute("INSERT INTO test (rad) VALUES (?) RETURNING *", nil).first
655
+ end
656
+
657
+ it "is inferred correctly" do
658
+ tuple.should == {id: 1, rad: nil}
659
+ end
660
+ end
661
+
662
+ context "against a date field" do
663
+ let(:table) { "CREATE TABLE test (id serial primary key, dob date)" }
664
+ let(:tuple) do
665
+ connection.execute("INSERT INTO test (dob) VALUES (?) RETURNING *", nil).first
666
+ end
667
+
668
+ it "is inferred correctly" do
669
+ tuple.should == {id: 1, dob: nil}
670
+ end
671
+ end
672
+
673
+ context "against a timestamp field" do
674
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamp)" }
675
+ let(:tuple) do
676
+ connection.execute("INSERT INTO test (created_at) VALUES (?) RETURNING *", nil).first
677
+ end
678
+
679
+ it "is inferred correctly" do
680
+ tuple.should == {id: 1, created_at: nil}
681
+ end
682
+ end
683
+
684
+ context "against a timestamptz field" do
685
+ let(:table) { "CREATE TABLE test (id serial primary key, created_at timestamptz)" }
686
+ let(:tuple) do
687
+ connection.execute("INSERT INTO test (created_at) VALUES (?) RETURNING *", nil).first
688
+ end
689
+
690
+ it "is inferred correctly" do
691
+ tuple.should == {id: 1, created_at: nil}
692
+ end
693
+ end
694
+
695
+ context "against a bytea field" do
696
+ let(:table) { "CREATE TABLE test (id serial primary key, salt bytea)" }
697
+ let(:tuple) do
698
+ connection.execute("INSERT INTO test (salt) VALUES (?) RETURNING *", nil).first
699
+ end
700
+
701
+ it "is inferred correctly" do
702
+ tuple.should == {id: 1, salt: nil}
703
+ end
704
+ end
705
+ end
706
+
707
+ describe "arbitrary Object param" do
708
+ context "against a text field" do
709
+ let(:value) { Object.new }
710
+ let(:table) { "CREATE TABLE test (id serial primary key, name text)" }
711
+ let(:tuple) do
712
+ connection.execute("INSERT INTO test (name) VALUES (?) RETURNING *", value).first
713
+ end
714
+
715
+ it "is inferred correctly (via #to_s)" do
716
+ tuple.should == {id: 1, name: value.to_s}
717
+ end
718
+ end
719
+ end
720
+
721
+ describe "multiple params" do
722
+ let(:table) do
723
+ <<-SQL
724
+ CREATE TABLE test (
725
+ id serial primary key,
726
+ name text,
727
+ age integer,
728
+ admin boolean,
729
+ created_at timestamptz
730
+ )
731
+ SQL
732
+ end
733
+
734
+ let(:tuple) do
735
+ connection.execute(<<-SQL, "bob", 17, false, Time.new(2012, 9, 22, 6, 34)).first
736
+ INSERT INTO test (
737
+ name, age, admin, created_at
738
+ ) VALUES (
739
+ ?, ?, ?, ?
740
+ ) RETURNING *
741
+ SQL
742
+ end
743
+
744
+ it "interprets them individually" do
745
+ tuple.should == {
746
+ id: 1,
747
+ name: "bob",
748
+ age: 17,
749
+ admin: false,
750
+ created_at: DateTime.new(2012, 9, 22, 6, 34, 0, DateTime.now.zone)
751
+ }
752
+ end
753
+ end
754
+
755
+ describe "when a bind marker is contained in a string literal" do
756
+ let(:table) do
757
+ <<-SQL
758
+ CREATE TABLE test (
759
+ id serial primary key,
760
+ name text,
761
+ age integer
762
+ )
763
+ SQL
764
+ end
765
+
766
+ context "without quoted apostrophes" do
767
+ let(:tuple) do
768
+ connection.execute(<<-SQL, 17).first
769
+ INSERT INTO test (
770
+ name, age
771
+ ) VALUES (
772
+ '?', ?
773
+ ) RETURNING *
774
+ SQL
775
+ end
776
+
777
+ it "does not consider the quoted marker" do
778
+ tuple.should == {id: 1, name: "?", age: 17}
779
+ end
780
+ end
781
+
782
+ context "with quoted apostrophes" do
783
+ let(:tuple) do
784
+ connection.execute(<<-SQL, 17).first
785
+ INSERT INTO test (
786
+ name, age
787
+ ) VALUES (
788
+ 'you say ''hello?''', ?
789
+ ) RETURNING *
790
+ SQL
791
+ end
792
+
793
+ it "does not consider the quoted marker" do
794
+ tuple.should == {id: 1, name: "you say 'hello?'", age: 17}
795
+ end
796
+ end
797
+ end
798
+
799
+ describe "when a bind marker is contained in a multi-line comment" do
800
+ let(:table) do
801
+ <<-SQL
802
+ CREATE TABLE test (
803
+ id serial primary key,
804
+ name text,
805
+ age integer
806
+ )
807
+ SQL
808
+ end
809
+
810
+ context "without nesting" do
811
+ let(:tuple) do
812
+ connection.execute(<<-SQL, "jim", 17).first
813
+ INSERT INTO test (
814
+ name, age
815
+ ) VALUES (
816
+ /*
817
+ Are these are the values you're looking for?
818
+ */
819
+ ?, ?
820
+ ) RETURNING *
821
+ SQL
822
+ end
823
+
824
+ it "does not consider the commented marker" do
825
+ tuple.should == {id: 1, name: "jim", age: 17}
826
+ end
827
+ end
828
+
829
+ context "with nesting" do
830
+ let(:tuple) do
831
+ connection.execute(<<-SQL, "jim", 17).first
832
+ INSERT INTO test (
833
+ name, age
834
+ ) VALUES (
835
+ /*
836
+ Are these the /* nested */ values you're looking for?
837
+ */
838
+ ?, ?
839
+ ) RETURNING *
840
+ SQL
841
+ end
842
+
843
+ it "does not consider the commented marker" do
844
+ tuple.should == {id: 1, name: "jim", age: 17}
845
+ end
846
+ end
847
+ end
848
+
849
+ context "when a bind marker is contained in a single line comment" do
850
+ let(:table) do
851
+ <<-SQL
852
+ CREATE TABLE test (
853
+ id serial primary key,
854
+ name text,
855
+ age integer
856
+ )
857
+ SQL
858
+ end
859
+
860
+ let(:tuple) do
861
+ connection.execute(<<-SQL, "jim", 17).first
862
+ INSERT INTO test (
863
+ name, age
864
+ ) VALUES (
865
+ -- Are these are the values you're looking for? choker! /*
866
+ ?, ?
867
+ ) RETURNING *
868
+ SQL
869
+ end
870
+
871
+ it "does not consider the commented marker" do
872
+ tuple.should == {id: 1, name: "jim", age: 17}
873
+ end
874
+ end
875
+
876
+ context "when a bind parameter is quoted as an identifier" do
877
+ let(:table) do
878
+ <<-SQL
879
+ CREATE TABLE test (
880
+ id serial primary key,
881
+ name text,
882
+ age integer,
883
+ "admin?" boolean
884
+ )
885
+ SQL
886
+ end
887
+
888
+ let(:tuple) do
889
+ connection.execute(<<-SQL, "jim", 17, true).first
890
+ INSERT INTO test (
891
+ name, age, "admin?"
892
+ ) VALUES (
893
+ ?, ?, ?
894
+ ) RETURNING *
895
+ SQL
896
+ end
897
+
898
+ it "does not confuse the quoted identifier" do
899
+ tuple.should == {id: 1, name: "jim", age: 17, admin?: true}
900
+ end
901
+ end
902
+ end