pod4 0.10.6 → 1.0.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.
- checksums.yaml +5 -5
- data/.bugs/bugs +2 -1
- data/.bugs/details/b5368c7ef19065fc597b5692314da71772660963.txt +53 -0
- data/.hgtags +1 -0
- data/Gemfile +5 -5
- data/README.md +157 -46
- data/lib/pod4/basic_model.rb +9 -22
- data/lib/pod4/connection.rb +67 -0
- data/lib/pod4/connection_pool.rb +154 -0
- data/lib/pod4/errors.rb +20 -0
- data/lib/pod4/interface.rb +34 -12
- data/lib/pod4/model.rb +32 -27
- data/lib/pod4/nebulous_interface.rb +25 -30
- data/lib/pod4/null_interface.rb +22 -16
- data/lib/pod4/pg_interface.rb +84 -104
- data/lib/pod4/sequel_interface.rb +138 -82
- data/lib/pod4/tds_interface.rb +83 -70
- data/lib/pod4/tweaking.rb +105 -0
- data/lib/pod4/version.rb +1 -1
- data/md/breaking_changes.md +80 -0
- data/spec/common/basic_model_spec.rb +67 -70
- data/spec/common/connection_pool_parallelism_spec.rb +154 -0
- data/spec/common/connection_pool_spec.rb +246 -0
- data/spec/common/connection_spec.rb +129 -0
- data/spec/common/model_ai_missing_id_spec.rb +256 -0
- data/spec/common/model_plus_encrypting_spec.rb +16 -4
- data/spec/common/model_plus_tweaking_spec.rb +128 -0
- data/spec/common/model_plus_typecasting_spec.rb +10 -4
- data/spec/common/model_spec.rb +283 -363
- data/spec/common/nebulous_interface_spec.rb +159 -108
- data/spec/common/null_interface_spec.rb +88 -65
- data/spec/common/sequel_interface_pg_spec.rb +217 -161
- data/spec/common/shared_examples_for_interface.rb +50 -50
- data/spec/jruby/sequel_encrypting_jdbc_pg_spec.rb +1 -1
- data/spec/jruby/sequel_interface_jdbc_ms_spec.rb +3 -3
- data/spec/jruby/sequel_interface_jdbc_pg_spec.rb +3 -23
- data/spec/mri/pg_encrypting_spec.rb +1 -1
- data/spec/mri/pg_interface_spec.rb +311 -223
- data/spec/mri/sequel_encrypting_spec.rb +1 -1
- data/spec/mri/sequel_interface_spec.rb +177 -180
- data/spec/mri/tds_encrypting_spec.rb +1 -1
- data/spec/mri/tds_interface_spec.rb +296 -212
- data/tags +340 -174
- metadata +19 -11
- data/md/fixme.md +0 -3
- data/md/roadmap.md +0 -125
- data/md/typecasting.md +0 -80
- data/spec/common/model_new_validate_spec.rb +0 -204
@@ -1,53 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require "pod4/tds_interface"
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "tiny_tds"
|
4
|
+
require "date"
|
5
5
|
|
6
|
-
require_relative
|
7
|
-
require_relative
|
6
|
+
require_relative "../common/shared_examples_for_interface"
|
7
|
+
require_relative "../fixtures/database"
|
8
8
|
|
9
9
|
|
10
10
|
describe "TdsInterface" do
|
11
11
|
|
12
|
-
let(:tds_interface_class) do
|
13
|
-
Class.new TdsInterface do
|
14
|
-
set_db :pod4_test
|
15
|
-
set_table :customer
|
16
|
-
set_id_fld :id
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
let(:schema_interface_class) do
|
21
|
-
Class.new TdsInterface do
|
22
|
-
set_db :pod4_test
|
23
|
-
set_schema :public
|
24
|
-
set_table :customer
|
25
|
-
set_id_fld :id
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
let(:bad_interface_class1) do
|
30
|
-
Class.new TdsInterface do
|
31
|
-
set_db :pod4_test
|
32
|
-
set_table :customer
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
let(:bad_interface_class2) do
|
37
|
-
Class.new TdsInterface do
|
38
|
-
set_db :pod4_test
|
39
|
-
set_id_fld :id
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
let(:prod_interface_class) do
|
44
|
-
Class.new TdsInterface do
|
45
|
-
set_db :pod4_test
|
46
|
-
set_table :product
|
47
|
-
set_id_fld :code
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
12
|
def db_setup(connect)
|
52
13
|
client = TinyTds::Client.new(connect)
|
53
14
|
client.execute(%Q|use [pod4_test];|).do
|
@@ -82,7 +43,6 @@ describe "TdsInterface" do
|
|
82
43
|
client.close if client
|
83
44
|
end
|
84
45
|
|
85
|
-
|
86
46
|
def fill_data(ifce)
|
87
47
|
@data.each{|r| ifce.create(r) }
|
88
48
|
end
|
@@ -91,185 +51,261 @@ describe "TdsInterface" do
|
|
91
51
|
ifce.create( {code: "foo", name: "bar"} )
|
92
52
|
end
|
93
53
|
|
54
|
+
def float_price(row)
|
55
|
+
row[:price] = row[:price].to_f
|
56
|
+
row
|
57
|
+
end
|
58
|
+
|
59
|
+
def list_contains(ifce, id)
|
60
|
+
ifce.list.find {|x| x[ifce.id_fld] == id }
|
61
|
+
end
|
62
|
+
|
63
|
+
let(:tds_interface_class) do
|
64
|
+
Class.new TdsInterface do
|
65
|
+
set_db :pod4_test
|
66
|
+
set_table :customer
|
67
|
+
set_id_fld :id
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
let(:schema_interface_class) do
|
72
|
+
Class.new TdsInterface do
|
73
|
+
set_db :pod4_test
|
74
|
+
set_schema :public
|
75
|
+
set_table :customer
|
76
|
+
set_id_fld :id, autoincrement: true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
let(:bad_interface_class1) do
|
81
|
+
Class.new TdsInterface do
|
82
|
+
set_table :customer
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
let(:bad_interface_class2) do
|
87
|
+
Class.new TdsInterface do
|
88
|
+
set_db :pod4_test
|
89
|
+
set_id_fld :id
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
let(:prod_interface_class) do
|
94
|
+
Class.new TdsInterface do
|
95
|
+
set_db :pod4_test
|
96
|
+
set_table :product
|
97
|
+
set_id_fld :code, autoincrement: false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
let(:interface) do
|
102
|
+
tds_interface_class.new(@connect_hash)
|
103
|
+
end
|
104
|
+
|
105
|
+
let(:prod_interface) do
|
106
|
+
prod_interface_class.new(@connect_hash)
|
107
|
+
end
|
94
108
|
|
95
109
|
before(:all) do
|
96
110
|
@connect_hash = DB[:tds]
|
97
111
|
db_setup(@connect_hash)
|
98
112
|
|
99
113
|
@data = []
|
100
|
-
@data << { name:
|
114
|
+
@data << { name: "Barney",
|
101
115
|
level: 1.23,
|
102
116
|
day: Date.parse("2016-01-01"),
|
103
|
-
timestamp: Time.parse(
|
117
|
+
timestamp: Time.parse("2015-01-01 12:11"),
|
104
118
|
price: BigDecimal("1.24"),
|
105
119
|
qty: BigDecimal("1.25") }
|
106
120
|
|
107
|
-
@data << { name:
|
121
|
+
@data << { name: "Fred",
|
108
122
|
level: 2.34,
|
109
123
|
day: Date.parse("2016-02-02"),
|
110
|
-
timestamp: Time.parse(
|
124
|
+
timestamp: Time.parse("2015-01-02 12:22"),
|
111
125
|
price: BigDecimal("2.35"),
|
112
126
|
qty: BigDecimal("2.36") }
|
113
127
|
|
114
|
-
@data << { name:
|
128
|
+
@data << { name: "Betty",
|
115
129
|
level: 3.45,
|
116
130
|
day: Date.parse("2016-03-03"),
|
117
|
-
timestamp: Time.parse(
|
131
|
+
timestamp: Time.parse("2015-01-03 12:33"),
|
118
132
|
price: BigDecimal("3.46"),
|
119
133
|
qty: BigDecimal("3.47") }
|
120
134
|
|
121
135
|
end
|
122
136
|
|
123
|
-
|
124
|
-
before do
|
137
|
+
before(:each) do
|
125
138
|
# TRUNCATE TABLE also resets the identity counter
|
126
139
|
interface.execute(%Q|truncate table customer;|)
|
127
140
|
end
|
128
141
|
|
129
142
|
|
130
|
-
let(:interface) do
|
131
|
-
tds_interface_class.new(@connect_hash)
|
132
|
-
end
|
133
|
-
|
134
|
-
let(:prod_interface) do
|
135
|
-
prod_interface_class.new(@connect_hash)
|
136
|
-
end
|
137
|
-
|
138
|
-
#####
|
139
|
-
|
140
|
-
|
141
|
-
it_behaves_like 'an interface' do
|
142
143
|
|
144
|
+
it_behaves_like "an interface" do
|
143
145
|
let(:interface) do
|
144
146
|
tds_interface_class.new(@connect_hash)
|
145
147
|
end
|
146
148
|
|
147
|
-
|
148
|
-
let(:record) { {name: 'Barney'} }
|
149
|
+
let(:record) { {name: "Barney"} }
|
149
150
|
|
150
151
|
end
|
151
|
-
##
|
152
152
|
|
153
153
|
|
154
|
-
describe
|
155
|
-
|
154
|
+
describe "TdsInterface.set_db" do
|
155
|
+
|
156
|
+
it "takes one argument" do
|
156
157
|
expect( TdsInterface ).to respond_to(:set_db).with(1).argument
|
157
158
|
end
|
159
|
+
|
158
160
|
end
|
159
|
-
##
|
160
161
|
|
161
162
|
|
162
|
-
describe
|
163
|
-
|
163
|
+
describe "TdsInterface.db" do
|
164
|
+
|
165
|
+
it "returns the table" do
|
164
166
|
expect( tds_interface_class.db ).to eq :pod4_test
|
165
167
|
end
|
168
|
+
|
166
169
|
end
|
167
|
-
##
|
168
170
|
|
169
171
|
|
170
|
-
describe
|
171
|
-
|
172
|
+
describe "TdsInterface.set_schema" do
|
173
|
+
|
174
|
+
it "takes one argument" do
|
172
175
|
expect( TdsInterface ).to respond_to(:set_schema).with(1).argument
|
173
176
|
end
|
177
|
+
|
174
178
|
end
|
175
|
-
##
|
176
179
|
|
177
180
|
|
178
|
-
describe
|
179
|
-
|
181
|
+
describe "TdsInterface.schema" do
|
182
|
+
|
183
|
+
it "returns the schema" do
|
180
184
|
expect( schema_interface_class.schema ).to eq :public
|
181
185
|
end
|
182
186
|
|
183
|
-
it
|
187
|
+
it "is optional" do
|
184
188
|
expect{ tds_interface_class.schema }.not_to raise_exception
|
185
189
|
expect( tds_interface_class.schema ).to eq nil
|
186
190
|
end
|
191
|
+
|
187
192
|
end
|
188
|
-
##
|
189
193
|
|
190
194
|
|
191
|
-
describe
|
192
|
-
|
195
|
+
describe "TdsInterface.set_table" do
|
196
|
+
|
197
|
+
it "takes one argument" do
|
193
198
|
expect( TdsInterface ).to respond_to(:set_table).with(1).argument
|
194
199
|
end
|
200
|
+
|
195
201
|
end
|
196
|
-
##
|
197
202
|
|
198
203
|
|
199
|
-
describe
|
200
|
-
|
204
|
+
describe "TdsInterface.table" do
|
205
|
+
|
206
|
+
it "returns the table" do
|
201
207
|
expect( tds_interface_class.table ).to eq :customer
|
202
208
|
end
|
209
|
+
|
203
210
|
end
|
204
|
-
##
|
205
211
|
|
206
212
|
|
207
|
-
describe
|
208
|
-
|
213
|
+
describe "TdsInterface.set_id_fld" do
|
214
|
+
|
215
|
+
it "takes one argument" do
|
209
216
|
expect( TdsInterface ).to respond_to(:set_id_fld).with(1).argument
|
210
217
|
end
|
218
|
+
|
219
|
+
it "takes an optional second 'autoincrement' argument" do
|
220
|
+
expect{ TdsInterface.set_id_fld(:foo, autoincrement: false) }.not_to raise_error
|
221
|
+
end
|
222
|
+
|
211
223
|
end
|
212
|
-
##
|
213
224
|
|
214
225
|
|
215
|
-
describe
|
216
|
-
|
226
|
+
describe "TdsInterface.id_ai" do
|
227
|
+
|
228
|
+
it "returns true if autoincrement is true" do
|
229
|
+
expect( schema_interface_class.id_ai ).to eq true
|
230
|
+
end
|
231
|
+
|
232
|
+
it "returns false if autoincrement is false" do
|
233
|
+
expect( prod_interface_class.id_ai ).to eq false
|
234
|
+
end
|
235
|
+
|
236
|
+
it "returns true if autoincrement is not specified" do
|
237
|
+
expect( tds_interface_class.id_ai ).to eq true
|
238
|
+
end
|
239
|
+
|
240
|
+
end # of PgInterface.id_ai
|
241
|
+
|
242
|
+
|
243
|
+
describe "TdsInterface.id_fld" do
|
244
|
+
|
245
|
+
it "returns the ID field name" do
|
217
246
|
expect( tds_interface_class.id_fld ).to eq :id
|
218
247
|
end
|
219
|
-
end
|
220
|
-
##
|
221
248
|
|
249
|
+
end
|
222
250
|
|
223
|
-
describe '#new' do
|
224
251
|
|
225
|
-
|
226
|
-
expect{ tds_interface_class.new }.to raise_exception ArgumentError
|
227
|
-
expect{ tds_interface_class.new(nil) }.to raise_exception ArgumentError
|
228
|
-
expect{ tds_interface_class.new('foo') }.to raise_exception ArgumentError
|
252
|
+
describe "#new" do
|
229
253
|
|
230
|
-
|
254
|
+
it "creates a ConnectionPool when passed a TDS connection hash" do
|
255
|
+
ifce = tds_interface_class.new(@connect_hash)
|
256
|
+
expect( ifce._connection ).to be_a ConnectionPool
|
231
257
|
end
|
232
258
|
|
233
|
-
it
|
234
|
-
|
259
|
+
it "uses the ConnectionPool when given one" do
|
260
|
+
pool = ConnectionPool.new(interface: tds_interface_class)
|
261
|
+
ifce = tds_interface_class.new(pool)
|
235
262
|
|
236
|
-
expect
|
237
|
-
|
263
|
+
expect( ifce._connection ).to eq pool
|
264
|
+
end
|
238
265
|
|
239
|
-
|
240
|
-
to raise_exception
|
266
|
+
it "raises ArgumentError when passed something else" do
|
267
|
+
expect{ tds_interface_class.new }.to raise_exception ArgumentError
|
268
|
+
expect{ tds_interface_class.new(nil) }.to raise_exception ArgumentError
|
269
|
+
expect{ tds_interface_class.new("foo") }.to raise_exception ArgumentError
|
270
|
+
end
|
241
271
|
|
272
|
+
it "requires the table and id field to be defined in the class" do
|
273
|
+
expect{ TdsInterface.new(@connect_hash) }.to raise_exception Pod4Error
|
274
|
+
expect{ bad_interface_class1.new(@connect_hash) }.to raise_exception Pod4Error
|
275
|
+
expect{ bad_interface_class2.new(@connect_hash) }.to raise_exception Pod4Error
|
242
276
|
end
|
243
277
|
|
244
|
-
end
|
245
|
-
##
|
278
|
+
end # of #new
|
246
279
|
|
247
280
|
|
248
|
-
describe
|
281
|
+
describe "#quoted_table" do
|
249
282
|
|
250
|
-
it
|
283
|
+
it "returns just the table when the schema is not set" do
|
251
284
|
expect( interface.quoted_table ).to eq( %Q|[customer]| )
|
252
285
|
end
|
253
286
|
|
254
|
-
it
|
287
|
+
it "returns the schema plus table when the schema is set" do
|
255
288
|
ifce = schema_interface_class.new(@connect_hash)
|
256
289
|
expect( ifce.quoted_table ).to eq( %|[public].[customer]| )
|
257
290
|
end
|
258
291
|
|
259
|
-
end
|
260
|
-
##
|
292
|
+
end # of #quoted_table
|
261
293
|
|
262
294
|
|
263
|
-
describe
|
295
|
+
describe "#create" do
|
296
|
+
let(:hash) { {name: "Bam-Bam", price: 4.44} }
|
297
|
+
let(:ot) { Octothorpe.new(name: "Wilma", price: 5.55) }
|
264
298
|
|
265
|
-
|
266
|
-
|
299
|
+
it "raises a Pod4::DatabaseError if anything goes wrong" do
|
300
|
+
expect{ interface.create(one: "two") }.to raise_exception DatabaseError
|
301
|
+
end
|
267
302
|
|
268
|
-
it
|
269
|
-
|
303
|
+
it "raises an ArgumentError if ID field is missing in hash and not AI" do
|
304
|
+
hash = {name: "bar"}
|
305
|
+
expect{ prod_interface.create(Octothorpe.new hash) }.to raise_error ArgumentError
|
270
306
|
end
|
271
307
|
|
272
|
-
it
|
308
|
+
it "creates the record when given a hash" do
|
273
309
|
# kinda impossible to seperate these two tests
|
274
310
|
id = interface.create(hash)
|
275
311
|
|
@@ -277,28 +313,28 @@ describe "TdsInterface" do
|
|
277
313
|
expect( interface.read(id).to_h ).to include hash
|
278
314
|
end
|
279
315
|
|
280
|
-
it
|
316
|
+
it "creates the record when given an Octothorpe" do
|
281
317
|
id = interface.create(ot)
|
282
318
|
|
283
319
|
expect{ interface.read(id) }.not_to raise_exception
|
284
320
|
expect( interface.read(id).to_h ).to include ot.to_h
|
285
321
|
end
|
286
322
|
|
287
|
-
it
|
288
|
-
hash2 = {name:
|
323
|
+
it "has no problem with record values of nil" do
|
324
|
+
hash2 = {name: "Ranger", price: nil}
|
289
325
|
expect{ interface.create(hash2) }.not_to raise_exception
|
290
326
|
id = interface.create(hash2)
|
291
327
|
expect( interface.read(id).to_h ).to include(hash2)
|
292
328
|
end
|
293
329
|
|
294
|
-
it
|
330
|
+
it "has no problem with strings containing special characters" do
|
295
331
|
hash2 = {name: "T'Challa[]", price: nil}
|
296
332
|
expect{ interface.create(hash2) }.not_to raise_exception
|
297
333
|
id = interface.create(hash2)
|
298
334
|
expect( interface.read(id).to_h ).to include(hash2)
|
299
335
|
end
|
300
336
|
|
301
|
-
it
|
337
|
+
it "has no problem with non-integer keys" do
|
302
338
|
hash = {code: "foo", name: "bar"}
|
303
339
|
id = prod_interface.create( Octothorpe.new(hash) )
|
304
340
|
|
@@ -307,37 +343,51 @@ describe "TdsInterface" do
|
|
307
343
|
expect( prod_interface.read("foo").to_h ).to include hash
|
308
344
|
end
|
309
345
|
|
310
|
-
|
311
|
-
|
346
|
+
it "calls ConnectionPool#client" do
|
347
|
+
expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
|
348
|
+
interface.create(ot)
|
349
|
+
end
|
312
350
|
|
351
|
+
it "copes with an OT with the ID field in it when the ID field autoincrements" do
|
352
|
+
h = {id: nil, name: "Bam-Bam", qty: 4.44}
|
353
|
+
expect{ interface.create(h) }.not_to raise_error
|
313
354
|
|
314
|
-
|
315
|
-
|
355
|
+
id = interface.create(h)
|
356
|
+
expect( id ).not_to be_nil
|
357
|
+
expect{ interface.read(id) }.not_to raise_exception
|
358
|
+
expect( interface.read(id).to_h ).to include( {name: "Bam-Bam", qty: 4.44} )
|
359
|
+
end
|
316
360
|
|
317
|
-
|
361
|
+
end # of #create
|
362
|
+
|
363
|
+
|
364
|
+
describe "#read" do
|
365
|
+
before(:each) { fill_data(interface) }
|
366
|
+
|
367
|
+
it "returns the record for the id as an Octothorpe" do
|
318
368
|
rec = interface.read(2)
|
319
369
|
expect( rec ).to be_a_kind_of Octothorpe
|
320
|
-
expect( rec.>>.name ).to eq
|
370
|
+
expect( rec.>>.name ).to eq "Fred"
|
321
371
|
end
|
322
372
|
|
323
|
-
it
|
373
|
+
it "raises a Pod4::CantContinue if anything goes wrong with the ID" do
|
324
374
|
expect{ interface.read(:foo) }.to raise_exception CantContinue
|
325
375
|
end
|
326
376
|
|
327
|
-
it
|
377
|
+
it "returns an empty Octothorpe if no record matches the ID" do
|
328
378
|
expect{ interface.read(99) }.not_to raise_exception
|
329
379
|
expect( interface.read(99) ).to be_a_kind_of Octothorpe
|
330
380
|
expect( interface.read(99) ).to be_empty
|
331
381
|
end
|
332
382
|
|
333
|
-
it
|
383
|
+
it "returns real fields as Float" do
|
334
384
|
level = interface.read(1).>>.level
|
335
385
|
|
336
386
|
expect( level ).to be_a_kind_of Float
|
337
387
|
expect( level ).to be_within(0.001).of( @data.first[:level] )
|
338
388
|
end
|
339
389
|
|
340
|
-
it
|
390
|
+
it "returns date fields as Date" do
|
341
391
|
skip "not supported by TinyTds ¬_¬ "
|
342
392
|
date = interface.read(1).>>.day
|
343
393
|
|
@@ -345,28 +395,28 @@ describe "TdsInterface" do
|
|
345
395
|
expect( date ).to eq @data.first[:day]
|
346
396
|
end
|
347
397
|
|
348
|
-
it
|
398
|
+
it "returns datetime fields as Time" do
|
349
399
|
timestamp = interface.read(1).>>.timestamp
|
350
400
|
|
351
401
|
expect( timestamp ).to be_a_kind_of Time
|
352
402
|
expect( timestamp ).to eq @data.first[:timestamp]
|
353
403
|
end
|
354
404
|
|
355
|
-
it
|
405
|
+
it "returns numeric fields as BigDecimal" do
|
356
406
|
qty = interface.read(1).>>.qty
|
357
407
|
|
358
408
|
expect( qty ).to be_a_kind_of BigDecimal
|
359
409
|
expect( qty ).to eq @data.first[:qty]
|
360
410
|
end
|
361
411
|
|
362
|
-
it
|
412
|
+
it "returns money fields as BigDecimal" do
|
363
413
|
price = interface.read(1).>>.price
|
364
414
|
|
365
415
|
expect( price ).to be_a_kind_of BigDecimal
|
366
416
|
expect( price ).to eq @data.first[:price]
|
367
417
|
end
|
368
418
|
|
369
|
-
it
|
419
|
+
it "has no problem with non-integer keys" do
|
370
420
|
# this is a 100% overlap with the create test above...
|
371
421
|
fill_product_data(prod_interface)
|
372
422
|
|
@@ -374,57 +424,60 @@ describe "TdsInterface" do
|
|
374
424
|
expect( prod_interface.read("foo").to_h ).to include(code: "foo", name: "bar")
|
375
425
|
end
|
376
426
|
|
377
|
-
|
378
|
-
|
427
|
+
it "calls ConnectionPool#client" do
|
428
|
+
expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
|
429
|
+
interface.read(2)
|
430
|
+
end
|
379
431
|
|
432
|
+
end # of #read
|
380
433
|
|
381
|
-
describe '#list' do
|
382
|
-
before { fill_data(interface) }
|
383
434
|
|
384
|
-
|
435
|
+
describe "#list" do
|
436
|
+
before(:each) { fill_data(interface) }
|
437
|
+
|
438
|
+
it "has an optional selection parameter, a hash" do
|
385
439
|
# Actually it does not have to be a hash, but FTTB we only support that.
|
386
|
-
expect{ interface.list(name:
|
440
|
+
expect{ interface.list(name: "Barney") }.not_to raise_exception
|
387
441
|
end
|
388
442
|
|
389
|
-
it
|
443
|
+
it "returns an array of Octothorpes that match the records" do
|
390
444
|
list = interface.list
|
391
445
|
|
392
446
|
expect( list.first ).to be_a_kind_of Octothorpe
|
393
447
|
expect( list.map{|x| x.>>.name} ).to match_array @data.map{|y| y[:name]}
|
394
448
|
end
|
395
449
|
|
396
|
-
it
|
397
|
-
expect( interface.list(name:
|
450
|
+
it "returns a subset of records based on the selection parameter" do
|
451
|
+
expect( interface.list(name: "Fred").size ).to eq 1
|
398
452
|
|
399
|
-
expect( interface.list(name:
|
400
|
-
to include(name:
|
453
|
+
expect( interface.list(name: "Betty").first.to_h ).
|
454
|
+
to include(name: "Betty", price: 3.46)
|
401
455
|
|
402
456
|
end
|
403
457
|
|
404
|
-
it
|
405
|
-
expect( interface.list(name:
|
458
|
+
it "returns an empty Array if nothing matches" do
|
459
|
+
expect( interface.list(name: "Yogi") ).to eq([])
|
406
460
|
end
|
407
461
|
|
408
|
-
it
|
409
|
-
expect{ interface.list(
|
462
|
+
it "raises DatabaseError if the selection criteria is nonsensical" do
|
463
|
+
expect{ interface.list("foo") }.to raise_exception Pod4::DatabaseError
|
410
464
|
end
|
411
465
|
|
412
|
-
|
413
|
-
|
466
|
+
it "calls ConnectionPool#client" do
|
467
|
+
expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
|
468
|
+
interface.list
|
469
|
+
end
|
470
|
+
|
471
|
+
end # of #list
|
414
472
|
|
415
473
|
|
416
|
-
describe
|
417
|
-
before { fill_data(interface) }
|
474
|
+
describe "#update" do
|
475
|
+
before(:each) { fill_data(interface) }
|
418
476
|
|
419
477
|
let(:id) { interface.list.first[:id] }
|
420
478
|
|
421
|
-
|
422
|
-
|
423
|
-
row
|
424
|
-
end
|
425
|
-
|
426
|
-
it 'updates the record at ID with record parameter' do
|
427
|
-
record = {name: 'Booboo', price: 99.99}
|
479
|
+
it "updates the record at ID with record parameter" do
|
480
|
+
record = {name: "Booboo", price: 99.99}
|
428
481
|
interface.update(id, record)
|
429
482
|
|
430
483
|
# It so happens that TinyTds returns money as BigDecimal --
|
@@ -432,138 +485,148 @@ describe "TdsInterface" do
|
|
432
485
|
expect( float_price( interface.read(id).to_h ) ).to include(record)
|
433
486
|
end
|
434
487
|
|
435
|
-
it
|
436
|
-
expect{ interface.update(99, name:
|
488
|
+
it "raises a CantContinue if anything weird happens with the ID" do
|
489
|
+
expect{ interface.update(99, name: "Booboo") }.
|
437
490
|
to raise_exception CantContinue
|
438
491
|
|
439
492
|
end
|
440
493
|
|
441
|
-
it
|
442
|
-
expect{ interface.update(id, smarts:
|
494
|
+
it "raises a DatabaseError if anything weird happens with the record" do
|
495
|
+
expect{ interface.update(id, smarts: "more") }.
|
443
496
|
to raise_exception DatabaseError
|
444
497
|
|
445
498
|
end
|
446
499
|
|
447
|
-
it
|
448
|
-
record = {name:
|
500
|
+
it "has no problem with record values of nil" do
|
501
|
+
record = {name: "Ranger", price: nil}
|
449
502
|
expect{ interface.update(id, record) }.not_to raise_exception
|
450
503
|
expect( interface.read(id).to_h ).to include(record)
|
451
504
|
end
|
452
505
|
|
453
|
-
it
|
506
|
+
it "has no problem with strings containing special characters" do
|
454
507
|
record = {name: "T'Challa[]", price: nil}
|
455
508
|
expect{ interface.update(id, record) }.not_to raise_exception
|
456
509
|
expect( interface.read(id).to_h ).to include(record)
|
457
510
|
end
|
458
511
|
|
459
|
-
it
|
512
|
+
it "has no problem with non-integer keys" do
|
460
513
|
fill_product_data(prod_interface)
|
461
514
|
expect{ prod_interface.update("foo", name: "baz") }.not_to raise_error
|
462
515
|
expect( prod_interface.read("foo").to_h[:name] ).to eq "baz"
|
463
516
|
end
|
464
517
|
|
465
|
-
|
466
|
-
|
467
|
-
|
518
|
+
it "calls ConnectionPool#client" do
|
519
|
+
expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
|
520
|
+
record = {name: "Booboo", price: 99.99}
|
521
|
+
interface.update(id, record)
|
522
|
+
end
|
468
523
|
|
469
|
-
|
524
|
+
end # of #update
|
470
525
|
|
471
|
-
def list_contains(ifce, id)
|
472
|
-
ifce.list.find {|x| x[ifce.id_fld] == id }
|
473
|
-
end
|
474
526
|
|
527
|
+
describe "#delete" do
|
475
528
|
let(:id) { interface.list.first[:id] }
|
476
529
|
|
477
|
-
before { fill_data(interface) }
|
530
|
+
before(:each) { fill_data(interface) }
|
478
531
|
|
479
|
-
it
|
532
|
+
it "raises CantContinue if anything hinky happens with the ID" do
|
480
533
|
expect{ interface.delete(:foo) }.to raise_exception CantContinue
|
481
534
|
expect{ interface.delete(99) }.to raise_exception CantContinue
|
482
535
|
end
|
483
536
|
|
484
|
-
it
|
537
|
+
it "makes the record at ID go away" do
|
485
538
|
expect( list_contains(interface, id) ).to be_truthy
|
486
539
|
interface.delete(id)
|
487
540
|
expect( list_contains(interface, id) ).to be_falsy
|
488
541
|
end
|
489
542
|
|
490
|
-
it
|
543
|
+
it "has no problem with non-integer keys" do
|
491
544
|
fill_product_data(prod_interface)
|
492
545
|
expect( list_contains(prod_interface, "foo") ).to be_truthy
|
493
546
|
prod_interface.delete("foo")
|
494
547
|
expect( list_contains(prod_interface, "foo") ).to be_falsy
|
495
548
|
end
|
496
549
|
|
497
|
-
|
498
|
-
|
550
|
+
it "calls ConnectionPool#client" do
|
551
|
+
expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
|
552
|
+
interface.delete(id)
|
553
|
+
end
|
499
554
|
|
555
|
+
end # of #delete
|
500
556
|
|
501
|
-
describe '#execute' do
|
502
557
|
|
503
|
-
|
558
|
+
describe "#execute" do
|
559
|
+
let(:sql) { "delete from customer where price < 2.0;" }
|
504
560
|
|
505
|
-
before { fill_data(interface) }
|
561
|
+
before(:each) { fill_data(interface) }
|
506
562
|
|
507
|
-
it
|
563
|
+
it "requires an SQL string" do
|
508
564
|
expect{ interface.execute }.to raise_exception ArgumentError
|
509
565
|
expect{ interface.execute(nil) }.to raise_exception ArgumentError
|
510
566
|
expect{ interface.execute(14) }.to raise_exception ArgumentError
|
511
567
|
end
|
512
568
|
|
513
|
-
it
|
514
|
-
expect{ interface.execute(
|
569
|
+
it "raises some sort of Pod4 error if it runs into problems" do
|
570
|
+
expect{ interface.execute("delete from not_a_table") }.
|
515
571
|
to raise_exception Pod4Error
|
516
572
|
|
517
573
|
end
|
518
574
|
|
519
|
-
it
|
575
|
+
it "executes the string" do
|
520
576
|
expect{ interface.execute(sql) }.not_to raise_exception
|
521
577
|
expect( interface.list.size ).to eq(@data.size - 1)
|
522
|
-
expect( interface.list.map{|r| r[:name] } ).not_to include
|
578
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
|
523
579
|
end
|
524
580
|
|
525
|
-
|
526
|
-
|
581
|
+
it "calls ConnectionPool#client" do
|
582
|
+
expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
|
583
|
+
interface.execute(sql)
|
584
|
+
end
|
527
585
|
|
586
|
+
end # of #execute
|
528
587
|
|
529
|
-
describe '#select' do
|
530
588
|
|
531
|
-
|
589
|
+
describe "#select" do
|
590
|
+
before(:each) { fill_data(interface) }
|
532
591
|
|
533
|
-
it
|
592
|
+
it "requires an SQL string" do
|
534
593
|
expect{ interface.select }.to raise_exception ArgumentError
|
535
594
|
expect{ interface.select(nil) }.to raise_exception ArgumentError
|
536
595
|
expect{ interface.select(14) }.to raise_exception ArgumentError
|
537
596
|
end
|
538
597
|
|
539
|
-
it
|
540
|
-
expect{ interface.select(
|
598
|
+
it "raises some sort of Pod4 error if it runs into problems" do
|
599
|
+
expect{ interface.select("select * from not_a_table") }.
|
541
600
|
to raise_exception Pod4Error
|
542
601
|
|
543
602
|
end
|
544
603
|
|
545
|
-
it
|
546
|
-
sql1 =
|
547
|
-
sql2 =
|
604
|
+
it "returns the result of the sql" do
|
605
|
+
sql1 = "select name from customer where price < 2.0;"
|
606
|
+
sql2 = "select name from customer where price < 0.0;"
|
548
607
|
|
549
608
|
expect{ interface.select(sql1) }.not_to raise_exception
|
550
|
-
expect( interface.select(sql1) ).to eq( [{name:
|
609
|
+
expect( interface.select(sql1) ).to eq( [{name: "Barney"}] )
|
551
610
|
expect( interface.select(sql2) ).to eq( [] )
|
552
611
|
end
|
553
612
|
|
554
|
-
it
|
613
|
+
it "works if you pass a non-select" do
|
555
614
|
# By which I mean: still executes the SQL; returns []
|
556
|
-
sql =
|
615
|
+
sql = "delete from customer where price < 2.0;"
|
557
616
|
ret = interface.select(sql)
|
558
617
|
|
559
618
|
expect( interface.list.size ).to eq(@data.size - 1)
|
560
|
-
expect( interface.list.map{|r| r[:name] } ).not_to include
|
619
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
|
561
620
|
expect( ret ).to eq( [] )
|
562
621
|
end
|
563
622
|
|
623
|
+
it "calls ConnectionPool#client" do
|
624
|
+
expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
|
625
|
+
sql = "delete from customer where price < 2.0;"
|
626
|
+
interface.select(sql)
|
627
|
+
end
|
564
628
|
|
565
|
-
end
|
566
|
-
##
|
629
|
+
end # of #select
|
567
630
|
|
568
631
|
|
569
632
|
describe "#escape" do
|
@@ -579,9 +642,30 @@ describe "TdsInterface" do
|
|
579
642
|
expect( interface.escape "G'Kar" ).to eq %Q|G''Kar|
|
580
643
|
end
|
581
644
|
|
582
|
-
end
|
583
|
-
|
645
|
+
end # of #escape
|
646
|
+
|
647
|
+
|
648
|
+
describe "#new_connection" do
|
649
|
+
|
650
|
+
it "returns a PG Client object" do
|
651
|
+
expect( interface.new_connection @connect_hash ).to be_a TinyTds::Client
|
652
|
+
end
|
653
|
+
|
654
|
+
end # of #new_connection
|
655
|
+
|
656
|
+
|
657
|
+
describe "#close_connection" do
|
658
|
+
|
659
|
+
it "closes the given PG Client object" do
|
660
|
+
client = interface.new_connection(@connect_hash)
|
661
|
+
|
662
|
+
expect( client ).to receive(:close)
|
663
|
+
|
664
|
+
interface.close_connection(client)
|
665
|
+
end
|
666
|
+
|
667
|
+
end # of #close_connection
|
668
|
+
|
584
669
|
|
585
|
-
|
586
670
|
end
|
587
671
|
|