rdo-postgres 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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