pod4 0.10.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,152 +1,175 @@
|
|
1
|
-
require
|
1
|
+
require "pod4/null_interface"
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "shared_examples_for_interface"
|
4
4
|
|
5
5
|
|
6
6
|
describe NullInterface do
|
7
7
|
|
8
|
+
def list_contains(id)
|
9
|
+
interface.list.find {|x| x[interface.id_fld] == id }
|
10
|
+
end
|
11
|
+
|
12
|
+
# An autoincrementing interface
|
8
13
|
let(:data) do
|
9
|
-
[ {name:
|
10
|
-
{name:
|
11
|
-
{name:
|
14
|
+
[ {id: 1, name: "Barney", price: 1.11},
|
15
|
+
{id: 2, name: "Fred", price: 2.22},
|
16
|
+
{id: 3, name: "Betty", price: 3.33} ]
|
12
17
|
end
|
18
|
+
let (:interface) { NullInterface.new(:id, :name, :price, data) }
|
13
19
|
|
14
|
-
|
20
|
+
# A non-autoincrementing interface
|
21
|
+
let(:data2) do
|
22
|
+
[ {code: "foo", level: 1},
|
23
|
+
{code: "bar", level: 2},
|
24
|
+
{code: "baz", level: 3} ]
|
25
|
+
end
|
15
26
|
|
16
|
-
|
27
|
+
let (:interface2) do
|
28
|
+
i = NullInterface.new(:code, :level, data2)
|
29
|
+
i.id_ai = false
|
30
|
+
i
|
31
|
+
end
|
17
32
|
|
18
33
|
|
19
34
|
it_behaves_like "an interface" do
|
20
|
-
let(:record) { {name:
|
21
|
-
let(:interface) { NullInterface.new( :name, :price, [record]
|
35
|
+
let(:record) { {name: "barney", price:1.11} }
|
36
|
+
let(:interface) { NullInterface.new( :id, :name, :price, [record]) }
|
22
37
|
end
|
23
38
|
|
24
39
|
|
25
|
-
describe
|
40
|
+
describe "#new" do
|
26
41
|
|
27
|
-
it
|
42
|
+
it "requires a list of columns and an array of hashes" do
|
28
43
|
expect{ NullInterface.new }.to raise_exception ArgumentError
|
29
44
|
expect{ NullInterface.new(nil) }.to raise_exception ArgumentError
|
30
|
-
expect{ NullInterface.new(
|
45
|
+
expect{ NullInterface.new("foo") }.to raise_exception ArgumentError
|
31
46
|
|
32
47
|
expect{ NullInterface.new(:one, [{one:1}]) }.not_to raise_exception
|
33
48
|
end
|
34
49
|
|
35
|
-
|
36
|
-
|
50
|
+
it "defaults autoincrement to true" do
|
51
|
+
expect( interface.id_ai ).to eq true
|
52
|
+
end
|
37
53
|
|
54
|
+
it "allows you to set autoincrement to false" do
|
55
|
+
ni = NullInterface.new(:one, [{one:1}])
|
56
|
+
ni.id_ai = false
|
57
|
+
expect( ni.id_ai ).to eq false
|
58
|
+
end
|
38
59
|
|
39
|
-
|
60
|
+
end # of #new
|
40
61
|
|
41
|
-
let(:hash) { {name: 'Bam-Bam', price: 4.44} }
|
42
|
-
let(:ot) { Octothorpe.new(name: 'Wilma', price: 5.55) }
|
43
62
|
|
44
|
-
|
63
|
+
describe "#create" do
|
64
|
+
|
65
|
+
let(:hash) { {name: "Bam-Bam", price: 4.44} }
|
66
|
+
let(:ot) { Octothorpe.new(name: "Wilma", price: 5.55) }
|
67
|
+
|
68
|
+
it "creates the record when given a hash" do
|
45
69
|
id = interface.create(hash)
|
46
70
|
|
47
71
|
expect{ interface.read(id) }.not_to raise_exception
|
48
72
|
expect( interface.read(id).to_h ).to include hash
|
49
73
|
end
|
50
74
|
|
51
|
-
it
|
75
|
+
it "creates the record when given an Octothorpe" do
|
52
76
|
id = interface.create(ot)
|
53
77
|
|
54
78
|
expect{ interface.read(id) }.not_to raise_exception
|
55
79
|
expect( interface.read(id).to_h ).to include ot.to_h
|
56
80
|
end
|
57
81
|
|
58
|
-
|
59
|
-
|
82
|
+
it "raises an ArgumentError when the key is not AI and the record is missing the key" do
|
83
|
+
expect{ interface2.create(level: 55) }.to raise_error ArgumentError
|
84
|
+
end
|
60
85
|
|
86
|
+
it "sets the ID field to the correct integer when autoincrement is true" do
|
87
|
+
count = interface.list.size
|
88
|
+
id = interface.create(ot)
|
89
|
+
expect( id ).to eq(count + 1)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "sets the ID field to whatever you set in the record when autoincrement is false" do
|
93
|
+
id = interface2.create Octothorpe.new(code: "flong", level: 9)
|
94
|
+
expect( id ).to eq "flong"
|
95
|
+
end
|
96
|
+
|
97
|
+
end # of #create
|
61
98
|
|
62
|
-
describe '#read' do
|
63
99
|
|
64
|
-
|
65
|
-
expect( interface.read('Barney') ).to be_a_kind_of Octothorpe
|
66
|
-
expect( interface.read('Fred').to_h ).
|
67
|
-
to include(name: 'Fred', price: 2.22)
|
100
|
+
describe "#read" do
|
68
101
|
|
102
|
+
it "returns the record for the id as an Octothorpe" do
|
103
|
+
expect( interface.read(1) ).to be_a_kind_of Octothorpe
|
104
|
+
expect( interface.read(2).to_h ).to include(name: "Fred", price: 2.22)
|
69
105
|
end
|
70
106
|
|
71
|
-
it
|
107
|
+
it "returns an empty Octothorpe if no record matches the ID" do
|
72
108
|
expect{ interface.read(:foo) }.not_to raise_exception
|
73
109
|
expect( interface.read(:foo) ).to be_a_kind_of Octothorpe
|
74
110
|
expect( interface.read(:foo) ).to be_empty
|
75
111
|
end
|
76
112
|
|
77
|
-
end
|
78
|
-
##
|
113
|
+
end # of #read
|
79
114
|
|
80
115
|
|
81
|
-
describe
|
116
|
+
describe "#list" do
|
82
117
|
|
83
|
-
it
|
118
|
+
it "has an optional selection parameter, a hash" do
|
84
119
|
expect{ interface.list }.not_to raise_exception
|
85
|
-
expect{ interface.list(name:
|
120
|
+
expect{ interface.list(name: "Barney") }.not_to raise_exception
|
86
121
|
end
|
87
122
|
|
88
|
-
it
|
123
|
+
it "returns an array of Octothorpes that match the records" do
|
89
124
|
arr = interface.list.map(&:to_h)
|
90
125
|
expect( arr ).to match_array data
|
91
126
|
end
|
92
127
|
|
93
|
-
it
|
94
|
-
expect( interface.list(name:
|
128
|
+
it "returns a subset of records based on the selection parameter" do
|
129
|
+
expect( interface.list(name: "Fred").size ).to eq 1
|
95
130
|
|
96
|
-
expect( interface.list(name:
|
97
|
-
to include(name:
|
131
|
+
expect( interface.list(name: "Betty").first.to_h ).
|
132
|
+
to include(name: "Betty", price: 3.33)
|
98
133
|
|
99
134
|
end
|
100
135
|
|
101
|
-
it
|
102
|
-
expect( interface.list(name:
|
136
|
+
it "returns an empty Array if nothing matches" do
|
137
|
+
expect( interface.list(name: "Yogi") ).to eq([])
|
103
138
|
end
|
104
139
|
|
105
|
-
it
|
140
|
+
it "returns an empty array if there is no data" do
|
106
141
|
interface.list.each {|x| interface.delete(x[interface.id_fld]) }
|
107
142
|
expect( interface.list ).to eq([])
|
108
143
|
end
|
109
144
|
|
110
|
-
end
|
111
|
-
##
|
145
|
+
end # of #list
|
112
146
|
|
113
147
|
|
114
|
-
describe
|
115
|
-
|
116
|
-
let(:id) { interface.list.first[:name] }
|
148
|
+
describe "#update" do
|
117
149
|
|
118
|
-
it
|
150
|
+
it "updates the record at ID with record parameter" do
|
119
151
|
record = {price: 99.99}
|
120
|
-
interface.update(
|
152
|
+
interface.update(1, record)
|
121
153
|
|
122
|
-
expect( interface.read(
|
154
|
+
expect( interface.read(1).to_h ).to include(record)
|
123
155
|
end
|
124
156
|
|
125
|
-
end
|
126
|
-
##
|
127
|
-
|
128
|
-
|
129
|
-
describe '#delete' do
|
130
|
-
|
131
|
-
def list_contains(id)
|
132
|
-
interface.list.find {|x| x[interface.id_fld] == id }
|
133
|
-
end
|
157
|
+
end # of #update
|
134
158
|
|
135
|
-
let(:id) { interface.list.first[:name] }
|
136
159
|
|
137
|
-
|
160
|
+
describe "#delete" do
|
161
|
+
it "raises CantContinue if anything hinky happens with the ID" do
|
138
162
|
expect{ interface.delete(:foo) }.to raise_exception CantContinue
|
139
163
|
expect{ interface.delete(99) }.to raise_exception CantContinue
|
140
164
|
end
|
141
165
|
|
142
|
-
it
|
143
|
-
expect( list_contains(
|
144
|
-
interface.delete(
|
145
|
-
expect( list_contains(
|
166
|
+
it "makes the record at ID go away" do
|
167
|
+
expect( list_contains(1) ).to be_truthy
|
168
|
+
interface.delete(1)
|
169
|
+
expect( list_contains(1) ).to be_falsy
|
146
170
|
end
|
147
171
|
|
148
|
-
end
|
149
|
-
##
|
172
|
+
end # of #delete
|
150
173
|
|
151
174
|
|
152
175
|
end
|
@@ -2,16 +2,48 @@
|
|
2
2
|
# Supplemental test for Sequel -- tests using PG gem in MRI, jeremyevans-postgres-pg under jruby?
|
3
3
|
#
|
4
4
|
|
5
|
-
require
|
5
|
+
require "pod4/sequel_interface"
|
6
6
|
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
7
|
+
require "sequel"
|
8
|
+
require "date"
|
9
|
+
require "time"
|
10
|
+
require "bigdecimal"
|
11
11
|
|
12
12
|
|
13
13
|
describe "SequelInterface (Pg)" do
|
14
14
|
|
15
|
+
def fill_data(ifce, data)
|
16
|
+
data.each{|r| ifce.create(r) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def fill_product_data(ifce)
|
20
|
+
ifce.create( {code: "foo", name: "bar"} )
|
21
|
+
end
|
22
|
+
|
23
|
+
def list_contains(ifce, id)
|
24
|
+
ifce.list.find {|x| x[ifce.id_fld] == id }
|
25
|
+
end
|
26
|
+
|
27
|
+
def db_setup(db)
|
28
|
+
db.run %Q|
|
29
|
+
drop table if exists customer;
|
30
|
+
drop table if exists product;
|
31
|
+
|
32
|
+
create table customer (
|
33
|
+
id serial primary key,
|
34
|
+
name text,
|
35
|
+
level real null,
|
36
|
+
day date null,
|
37
|
+
timestamp timestamp null,
|
38
|
+
price money null,
|
39
|
+
qty numeric null );
|
40
|
+
|
41
|
+
create table product (
|
42
|
+
code text,
|
43
|
+
name text );|
|
44
|
+
|
45
|
+
end
|
46
|
+
|
15
47
|
let(:sequel_interface_class) do
|
16
48
|
Class.new SequelInterface do
|
17
49
|
set_table :customer
|
@@ -30,115 +62,139 @@ describe "SequelInterface (Pg)" do
|
|
30
62
|
let(:prod_interface_class) do
|
31
63
|
Class.new SequelInterface do
|
32
64
|
set_table :product
|
33
|
-
set_id_fld :code
|
65
|
+
set_id_fld :code, autoincrement: false
|
34
66
|
end
|
35
67
|
end
|
36
68
|
|
37
|
-
|
38
69
|
let(:data) do
|
39
70
|
d = []
|
40
|
-
d << { name:
|
71
|
+
d << { name: "Barney",
|
41
72
|
level: 1.23,
|
42
73
|
day: Date.parse("2016-01-01"),
|
43
|
-
timestamp: Time.parse(
|
74
|
+
timestamp: Time.parse("2015-01-01 12:11"),
|
44
75
|
qty: BigDecimal("1.24"),
|
45
76
|
price: BigDecimal("1.24") }
|
46
77
|
|
47
|
-
d << { name:
|
78
|
+
d << { name: "Fred",
|
48
79
|
level: 2.34,
|
49
80
|
day: Date.parse("2016-02-02"),
|
50
|
-
timestamp: Time.parse(
|
81
|
+
timestamp: Time.parse("2015-01-02 12:22"),
|
51
82
|
qty: BigDecimal("2.35"),
|
52
83
|
price: BigDecimal("2.35") }
|
53
84
|
|
54
|
-
d << { name:
|
85
|
+
d << { name: "Betty",
|
55
86
|
level: 3.45,
|
56
87
|
day: Date.parse("2016-03-03"),
|
57
|
-
timestamp: Time.parse(
|
88
|
+
timestamp: Time.parse("2015-01-03 12:33"),
|
58
89
|
qty: BigDecimal("3.46"),
|
59
90
|
price: BigDecimal("3.46") }
|
60
91
|
|
61
92
|
d
|
62
93
|
end
|
63
94
|
|
64
|
-
|
65
|
-
data.each{|r| ifce.create(r) }
|
66
|
-
end
|
67
|
-
|
68
|
-
def fill_product_data(ifce)
|
69
|
-
ifce.create( {code: "foo", name: "bar"} )
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
let (:db) do
|
74
|
-
db = Sequel.connect('postgres://pod4test:pod4test@centos7andy/pod4_test?search_path=public')
|
75
|
-
|
76
|
-
db.run %Q|
|
77
|
-
drop table if exists customer;
|
78
|
-
drop table if exists product;
|
79
|
-
|
80
|
-
create table customer (
|
81
|
-
id serial primary key,
|
82
|
-
name text,
|
83
|
-
level real null,
|
84
|
-
day date null,
|
85
|
-
timestamp timestamp null,
|
86
|
-
price money null,
|
87
|
-
qty numeric null );
|
88
|
-
|
89
|
-
create table product (
|
90
|
-
code text,
|
91
|
-
name text );|
|
95
|
+
let(:db_url) { "postgres://pod4test:pod4test@centos7andy/pod4_test?search_path=public" }
|
92
96
|
|
97
|
+
let(:db) do
|
98
|
+
db = Sequel.connect(db_url)
|
99
|
+
db_setup(db)
|
93
100
|
db
|
94
101
|
end
|
95
102
|
|
96
103
|
let(:interface) { sequel_interface_class.new(db) }
|
97
104
|
let(:prod_interface) { prod_interface_class.new(db) }
|
98
105
|
|
99
|
-
|
100
|
-
|
101
106
|
before do
|
102
107
|
interface.execute %Q|
|
103
108
|
truncate table customer restart identity;
|
104
109
|
truncate table product;|
|
105
110
|
|
106
|
-
fill_data(interface)
|
111
|
+
fill_data(interface, data)
|
107
112
|
end
|
108
113
|
|
109
114
|
after do
|
110
115
|
db.disconnect
|
111
116
|
end
|
112
117
|
|
113
|
-
##
|
114
118
|
|
119
|
+
describe "#new" do
|
120
|
+
|
121
|
+
context "when passed a Sequel DB object" do
|
122
|
+
let(:ifce) { sequel_interface_class.new(Sequel.connect db_url) }
|
123
|
+
|
124
|
+
it "uses it to create a connection" do
|
125
|
+
expect( ifce._connection ).to be_a Connection
|
126
|
+
expect( ifce._connection.interface_class ).to eq sequel_interface_class
|
127
|
+
end
|
128
|
+
|
129
|
+
it "calls Connection#client on first use" do
|
130
|
+
expect( ifce._connection ).to receive(:client).with(ifce).and_call_original
|
131
|
+
ifce.list
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "when passed a String" do
|
136
|
+
let(:ifce) { sequel_interface_class.new(db_url) }
|
137
|
+
|
138
|
+
it "uses it to create a connection" do
|
139
|
+
expect( ifce._connection ).to be_a Connection
|
140
|
+
expect( ifce._connection.interface_class ).to eq sequel_interface_class
|
141
|
+
end
|
142
|
+
|
143
|
+
# Normally we'd expect on _every_ use, but Sequel is different
|
144
|
+
it "calls Connection#client on first use" do
|
145
|
+
expect( ifce._connection ).to receive(:client).with(ifce).and_call_original
|
146
|
+
ifce.list
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context "when passed a Connection object" do
|
151
|
+
let(:conn) { Connection.new(interface: sequel_interface_class) }
|
152
|
+
let(:ifce) { sequel_interface_class.new(conn) }
|
153
|
+
|
154
|
+
it "uses that as its connection object" do
|
155
|
+
expect( ifce._connection ).to be_a Connection
|
156
|
+
expect( ifce._connection ).to eq conn
|
157
|
+
expect( ifce._connection.interface_class ).to eq sequel_interface_class
|
158
|
+
end
|
159
|
+
|
160
|
+
# Normally we'd expect on _every_ use, but Sequel is different
|
161
|
+
it "calls Connection#client on first use" do
|
162
|
+
# When we pass a connection object we are expected to set the data layer option
|
163
|
+
conn.data_layer_options = Sequel.connect(db_url)
|
115
164
|
|
116
|
-
|
165
|
+
expect( ifce._connection ).to receive(:client).with(ifce).and_call_original
|
117
166
|
|
118
|
-
|
167
|
+
ifce.list
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end # of #new
|
172
|
+
|
173
|
+
describe "#quoted_table" do
|
174
|
+
|
175
|
+
it "returns just the table when the schema is not set" do
|
119
176
|
expect( interface.quoted_table.downcase ).to eq( %Q|"customer"| )
|
120
177
|
end
|
121
178
|
|
122
|
-
it
|
179
|
+
it "returns the schema plus table when the schema is set" do
|
123
180
|
ifce = schema_interface_class.new(db)
|
124
181
|
expect( ifce.quoted_table.downcase ).to eq( %|"public"."customer"| )
|
125
182
|
end
|
126
183
|
|
127
|
-
end
|
128
|
-
##
|
184
|
+
end # of #quoted_table
|
129
185
|
|
130
186
|
|
131
|
-
describe
|
187
|
+
describe "#create" do
|
132
188
|
|
133
|
-
let(:hash) { {name:
|
134
|
-
let(:ot) { Octothorpe.new(name:
|
189
|
+
let(:hash) { {name: "Bam-Bam", qty: 4.44} }
|
190
|
+
let(:ot) { Octothorpe.new(name: "Wilma", qty: 5.55) }
|
135
191
|
|
136
192
|
|
137
|
-
it
|
138
|
-
expect{ interface.create(one:
|
193
|
+
it "raises a Pod4::DatabaseError if anything goes wrong" do
|
194
|
+
expect{ interface.create(one: "two") }.to raise_exception DatabaseError
|
139
195
|
end
|
140
196
|
|
141
|
-
it
|
197
|
+
it "creates the record when given a hash" do
|
142
198
|
# kinda impossible to seperate these two tests
|
143
199
|
id = interface.create(hash)
|
144
200
|
|
@@ -147,26 +203,26 @@ describe "SequelInterface (Pg)" do
|
|
147
203
|
expect( interface.read(id).to_h ).to include hash
|
148
204
|
end
|
149
205
|
|
150
|
-
it
|
206
|
+
it "creates the record when given an Octothorpe" do
|
151
207
|
id = interface.create(ot)
|
152
208
|
|
153
209
|
expect{ interface.read(id) }.not_to raise_exception
|
154
210
|
expect( interface.read(id).to_h ).to include ot.to_h
|
155
211
|
end
|
156
212
|
|
157
|
-
it
|
213
|
+
it "does not freak out if the hash has symbol values" do
|
158
214
|
# Which, Sequel does
|
159
215
|
expect{ interface.create(name: :Booboo) }.not_to raise_exception
|
160
216
|
end
|
161
217
|
|
162
|
-
it
|
163
|
-
record = {name:
|
218
|
+
it "has no problem with record values of nil" do
|
219
|
+
record = {name: "Ranger", price: nil}
|
164
220
|
expect{ interface.create(record) }.not_to raise_exception
|
165
221
|
id = interface.create(record)
|
166
222
|
expect( interface.read(id).to_h ).to include(record)
|
167
223
|
end
|
168
224
|
|
169
|
-
it
|
225
|
+
it "has no problem with strings containing special characters" do
|
170
226
|
# Note that in passing we retest that create returns the ID for identity columns
|
171
227
|
|
172
228
|
record = {name: "T'Challa[]", price: nil}
|
@@ -175,7 +231,7 @@ describe "SequelInterface (Pg)" do
|
|
175
231
|
expect( interface.read(id).to_h ).to include(record)
|
176
232
|
end
|
177
233
|
|
178
|
-
it
|
234
|
+
it "has no problem with non-integer keys" do
|
179
235
|
# Note that in passing we retest that create returns the ID for non-identity columns
|
180
236
|
|
181
237
|
hash = {code: "foo", name: "bar"}
|
@@ -186,55 +242,64 @@ describe "SequelInterface (Pg)" do
|
|
186
242
|
expect( prod_interface.read("foo").to_h ).to include hash
|
187
243
|
end
|
188
244
|
|
189
|
-
|
190
|
-
|
245
|
+
it "copes with an OT with the ID field in it when the ID field autoincrements" do
|
246
|
+
h = {id: nil, name: "Bam-Bam", qty: 4.44}
|
247
|
+
expect{ interface.create(h) }.not_to raise_error
|
191
248
|
|
249
|
+
id = interface.create(h)
|
250
|
+
expect( id ).not_to be_nil
|
251
|
+
expect{ interface.read(id) }.not_to raise_exception
|
252
|
+
expect( interface.read(id).to_h ).to include( {name: "Bam-Bam", qty: 4.44} )
|
253
|
+
end
|
192
254
|
|
193
|
-
|
255
|
+
end # of #create
|
194
256
|
|
195
|
-
|
196
|
-
|
257
|
+
|
258
|
+
describe "#read" do
|
259
|
+
|
260
|
+
it "returns the record for the id as an Octothorpe" do
|
261
|
+
expect( interface.read(2).to_h ).to include(name: "Fred", qty: 2.35)
|
197
262
|
end
|
198
263
|
|
199
|
-
it
|
264
|
+
it "raises a Pod4::CantContinue if the ID is bad" do
|
200
265
|
expect{ interface.read(:foo) }.to raise_exception CantContinue
|
201
266
|
end
|
202
267
|
|
203
|
-
it
|
268
|
+
it "returns an empty Octothorpe if no record matches the ID" do
|
204
269
|
expect{ interface.read(99) }.not_to raise_exception
|
205
270
|
expect( interface.read(99) ).to be_a_kind_of Octothorpe
|
206
271
|
expect( interface.read(99) ).to be_empty
|
207
272
|
end
|
208
273
|
|
209
|
-
it
|
274
|
+
it "returns real fields as Float" do
|
210
275
|
level = interface.read(1).>>.level
|
211
276
|
|
212
277
|
expect( level ).to be_a_kind_of Float
|
213
278
|
expect( level ).to be_within(0.001).of( data.first[:level] )
|
214
279
|
end
|
215
280
|
|
216
|
-
it
|
281
|
+
it "returns date fields as Date" do
|
217
282
|
date = interface.read(1).>>.day
|
218
283
|
|
219
284
|
expect( date ).to be_a_kind_of Date
|
220
285
|
expect( date ).to eq data.first[:day]
|
221
286
|
end
|
222
287
|
|
223
|
-
it
|
288
|
+
it "returns datetime fields as Time" do
|
224
289
|
timestamp = interface.read(1).>>.timestamp
|
225
290
|
|
226
291
|
expect( timestamp ).to be_a_kind_of Time
|
227
292
|
expect( timestamp ).to eq data.first[:timestamp]
|
228
293
|
end
|
229
294
|
|
230
|
-
it
|
295
|
+
it "returns numeric fields as BigDecimal" do
|
231
296
|
qty = interface.read(1).>>.qty
|
232
297
|
|
233
298
|
expect( qty ).to be_a_kind_of BigDecimal
|
234
299
|
expect( qty ).to eq data.first[:qty]
|
235
300
|
end
|
236
301
|
|
237
|
-
it
|
302
|
+
it "returns money fields as bigdecimal" do
|
238
303
|
pending "Sequel/PG returns a string for Money type"
|
239
304
|
|
240
305
|
price = interface.read(1).>>.price
|
@@ -243,7 +308,7 @@ describe "SequelInterface (Pg)" do
|
|
243
308
|
expect( price ).to eq data.first[:price]
|
244
309
|
end
|
245
310
|
|
246
|
-
it
|
311
|
+
it "has no problem with non-integer keys" do
|
247
312
|
# this is a 100% overlap with the create test above...
|
248
313
|
fill_product_data(prod_interface)
|
249
314
|
|
@@ -251,14 +316,12 @@ describe "SequelInterface (Pg)" do
|
|
251
316
|
expect( prod_interface.read("foo").to_h ).to include(code: "foo", name: "bar")
|
252
317
|
end
|
253
318
|
|
254
|
-
end
|
255
|
-
|
256
|
-
|
257
|
-
|
319
|
+
end # of #read
|
320
|
+
|
258
321
|
|
259
|
-
describe
|
322
|
+
describe "#list" do
|
260
323
|
|
261
|
-
it
|
324
|
+
it "returns an array of Octothorpes that match the records" do
|
262
325
|
arr = interface.list.map {|ot| x = ot.to_h}
|
263
326
|
|
264
327
|
expect( arr.size ).to eq(data.size)
|
@@ -274,43 +337,40 @@ describe "SequelInterface (Pg)" do
|
|
274
337
|
|
275
338
|
end
|
276
339
|
|
277
|
-
it
|
278
|
-
expect( interface.list(name:
|
340
|
+
it "returns a subset of records based on the selection parameter" do
|
341
|
+
expect( interface.list(name: "Fred").size ).to eq 1
|
279
342
|
|
280
|
-
expect( interface.list(name:
|
281
|
-
to include(name:
|
343
|
+
expect( interface.list(name: "Betty").first.to_h ).
|
344
|
+
to include(name: "Betty", qty: 3.46)
|
282
345
|
|
283
346
|
end
|
284
347
|
|
285
|
-
it
|
286
|
-
expect( interface.list(name:
|
348
|
+
it "returns an empty Array if nothing matches" do
|
349
|
+
expect( interface.list(name: "Yogi") ).to eq([])
|
287
350
|
end
|
288
351
|
|
289
|
-
it
|
290
|
-
expect{ interface.list(
|
352
|
+
it "raises DatabaseError if the selection criteria is nonsensical" do
|
353
|
+
expect{ interface.list("foo") }.to raise_exception Pod4::DatabaseError
|
291
354
|
end
|
292
355
|
|
293
|
-
it
|
356
|
+
it "returns an empty array if there is no data" do
|
294
357
|
interface.list.each {|x| interface.delete(x[interface.id_fld]) }
|
295
358
|
expect( interface.list ).to eq([])
|
296
359
|
end
|
297
360
|
|
298
|
-
it '
|
361
|
+
it "doesn't freak out if the hash has symbol values" do
|
299
362
|
# Which, Sequel does
|
300
363
|
expect{ interface.list(name: :Barney) }.not_to raise_exception
|
301
364
|
end
|
302
365
|
|
303
|
-
|
304
|
-
end
|
305
|
-
##
|
366
|
+
end # of #list
|
306
367
|
|
307
368
|
|
308
|
-
describe
|
309
|
-
|
369
|
+
describe "#update" do
|
310
370
|
let(:id) { interface.list.first[:id] }
|
311
371
|
|
312
|
-
it
|
313
|
-
record = {name:
|
372
|
+
it "updates the record at ID with record parameter" do
|
373
|
+
record = {name: "Booboo", qty: 99.99}
|
314
374
|
interface.update(id, record)
|
315
375
|
|
316
376
|
booboo = interface.read(id)
|
@@ -318,203 +378,199 @@ describe "SequelInterface (Pg)" do
|
|
318
378
|
expect( booboo.>>.qty.to_f ).to eq( record[:qty] )
|
319
379
|
end
|
320
380
|
|
321
|
-
it
|
322
|
-
expect{ interface.update(99, name:
|
381
|
+
it "raises a CantContinue if anything weird happens with the ID" do
|
382
|
+
expect{ interface.update(99, name: "Booboo") }.
|
323
383
|
to raise_exception CantContinue
|
324
384
|
|
325
385
|
end
|
326
386
|
|
327
|
-
it
|
328
|
-
expect{ interface.update(id, smarts:
|
387
|
+
it "raises a DatabaseError if anything weird happensi with the record" do
|
388
|
+
expect{ interface.update(id, smarts: "more") }.
|
329
389
|
to raise_exception DatabaseError
|
330
390
|
|
331
391
|
end
|
332
392
|
|
333
|
-
it '
|
393
|
+
it "doesn't freak out if the hash has symbol values" do
|
334
394
|
# Which, Sequel does
|
335
395
|
expect{ interface.update(id, name: :Booboo) }.not_to raise_exception
|
336
396
|
end
|
337
397
|
|
338
|
-
it
|
339
|
-
record = {name:
|
398
|
+
it "has no problem with record values of nil" do
|
399
|
+
record = {name: "Ranger", qty: nil}
|
340
400
|
expect{ interface.update(id, record) }.not_to raise_exception
|
341
401
|
expect( interface.read(id).to_h ).to include(record)
|
342
402
|
end
|
343
403
|
|
344
|
-
it
|
404
|
+
it "has no problem with strings containing special characters" do
|
345
405
|
record = {name: "T'Challa[]", qty: nil}
|
346
406
|
expect{ interface.update(id, record) }.not_to raise_exception
|
347
407
|
expect( interface.read(id).to_h ).to include(record)
|
348
408
|
end
|
349
409
|
|
350
|
-
it
|
410
|
+
it "has no problem with non-integer keys" do
|
351
411
|
fill_product_data(prod_interface)
|
352
412
|
expect{ prod_interface.update("foo", name: "baz") }.not_to raise_error
|
353
413
|
expect( prod_interface.read("foo").to_h[:name] ).to eq "baz"
|
354
414
|
end
|
355
415
|
|
356
|
-
end
|
357
|
-
##
|
358
|
-
|
359
|
-
|
360
|
-
describe '#delete' do
|
416
|
+
end # of #update
|
361
417
|
|
362
|
-
def list_contains(ifce, id)
|
363
|
-
ifce.list.find {|x| x[ifce.id_fld] == id }
|
364
|
-
end
|
365
418
|
|
419
|
+
describe "#delete" do
|
366
420
|
let(:id) { interface.list.first[:id] }
|
367
421
|
|
368
|
-
it
|
422
|
+
it "raises CantContinue if anything hinky happens with the ID" do
|
369
423
|
expect{ interface.delete(:foo) }.to raise_exception CantContinue
|
370
424
|
expect{ interface.delete(99) }.to raise_exception CantContinue
|
371
425
|
end
|
372
426
|
|
373
|
-
it
|
427
|
+
it "makes the record at ID go away" do
|
374
428
|
expect( list_contains(interface, id) ).to be_truthy
|
375
429
|
interface.delete(id)
|
376
430
|
expect( list_contains(interface, id) ).to be_falsy
|
377
431
|
end
|
378
432
|
|
379
|
-
it
|
433
|
+
it "has no problem with non-integer keys" do
|
380
434
|
fill_product_data(prod_interface)
|
381
435
|
expect( list_contains(prod_interface, "foo") ).to be_truthy
|
382
436
|
prod_interface.delete("foo")
|
383
437
|
expect( list_contains(prod_interface, "foo") ).to be_falsy
|
384
438
|
end
|
385
439
|
|
386
|
-
end
|
387
|
-
##
|
388
|
-
|
440
|
+
end # of #delete
|
389
441
|
|
390
|
-
describe '#execute' do
|
391
442
|
|
392
|
-
|
443
|
+
describe "#execute" do
|
444
|
+
let(:sql) { "delete from customer where qty < 2.0;" }
|
393
445
|
|
394
|
-
it
|
446
|
+
it "requires an SQL string" do
|
395
447
|
expect{ interface.execute }.to raise_exception ArgumentError
|
396
448
|
expect{ interface.execute(nil) }.to raise_exception ArgumentError
|
397
449
|
expect{ interface.execute(14) }.to raise_exception ArgumentError
|
398
450
|
end
|
399
451
|
|
400
|
-
it
|
401
|
-
expect{ interface.execute(
|
452
|
+
it "raises some sort of Pod4 error if it runs into problems" do
|
453
|
+
expect{ interface.execute("delete from not_a_table") }.
|
402
454
|
to raise_exception Pod4Error
|
403
455
|
|
404
456
|
end
|
405
457
|
|
406
|
-
it
|
458
|
+
it "executes the string" do
|
407
459
|
expect{ interface.execute(sql) }.not_to raise_exception
|
408
460
|
expect( interface.list.size ).to eq(data.size - 1)
|
409
|
-
expect( interface.list.map{|r| r[:name] } ).not_to include
|
461
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
|
410
462
|
end
|
411
463
|
|
412
|
-
end
|
413
|
-
##
|
464
|
+
end # of #execute
|
414
465
|
|
415
466
|
|
416
|
-
describe
|
467
|
+
describe "#select" do
|
417
468
|
|
418
|
-
it
|
469
|
+
it "requires an SQL string" do
|
419
470
|
expect{ interface.select }.to raise_exception ArgumentError
|
420
471
|
expect{ interface.select(nil) }.to raise_exception ArgumentError
|
421
472
|
expect{ interface.select(14) }.to raise_exception ArgumentError
|
422
473
|
end
|
423
474
|
|
424
|
-
it
|
425
|
-
expect{ interface.select(
|
475
|
+
it "raises some sort of Pod4 error if it runs into problems" do
|
476
|
+
expect{ interface.select("select * from not_a_table") }.
|
426
477
|
to raise_exception Pod4Error
|
427
478
|
|
428
479
|
end
|
429
480
|
|
430
|
-
it
|
431
|
-
sql1 =
|
432
|
-
sql2 =
|
481
|
+
it "returns the result of the sql" do
|
482
|
+
sql1 = "select name from customer where qty < 2.0;"
|
483
|
+
sql2 = "select name from customer where qty < 0.0;"
|
433
484
|
|
434
485
|
expect{ interface.select(sql1) }.not_to raise_exception
|
435
|
-
expect( interface.select(sql1) ).to eq( [{name:
|
486
|
+
expect( interface.select(sql1) ).to eq( [{name: "Barney"}] )
|
436
487
|
expect( interface.select(sql2) ).to eq( [] )
|
437
488
|
end
|
438
489
|
|
439
|
-
it
|
490
|
+
it "works if you pass a non-select" do
|
440
491
|
# By which I mean: still executes the SQL; returns []
|
441
|
-
sql =
|
492
|
+
sql = "delete from customer where qty < 2.0;"
|
442
493
|
ret = interface.select(sql)
|
443
494
|
|
444
495
|
expect( interface.list.size ).to eq(data.size - 1)
|
445
|
-
expect( interface.list.map{|r| r[:name] } ).not_to include
|
496
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
|
446
497
|
expect( ret ).to eq( [] )
|
447
498
|
end
|
448
499
|
|
449
|
-
end
|
450
|
-
##
|
500
|
+
end # of #select
|
451
501
|
|
452
502
|
|
453
503
|
describe "#executep" do
|
454
504
|
# For the time being lets assume that Sequel does its job and the three modes we are calling
|
455
505
|
# actually work
|
456
506
|
|
457
|
-
let(:sql) {
|
507
|
+
let(:sql) { "delete from customer where qty < ?;" }
|
458
508
|
|
459
|
-
it
|
509
|
+
it "requires an SQL string and a mode" do
|
460
510
|
expect{ interface.executep }.to raise_exception ArgumentError
|
461
511
|
expect{ interface.executep(nil) }.to raise_exception ArgumentError
|
462
512
|
expect{ interface.executep(14, :update) }.to raise_exception ArgumentError
|
463
513
|
expect{ interface.executep(14, :update, 2) }.to raise_exception ArgumentError
|
464
514
|
end
|
465
515
|
|
466
|
-
it
|
516
|
+
it "requires the mode to be valid" do
|
467
517
|
expect{ interface.executep(sql, :foo, 2) }.to raise_exception ArgumentError
|
468
518
|
end
|
469
519
|
|
470
|
-
it
|
471
|
-
expect{ interface.executep(
|
520
|
+
it "raises some sort of Pod4 error if it runs into problems" do
|
521
|
+
expect{ interface.executep("delete from not_a_table where thingy = ?", :delete, 14) }.
|
472
522
|
to raise_exception Pod4Error
|
473
523
|
|
474
524
|
end
|
475
525
|
|
476
|
-
it
|
526
|
+
it "executes the string" do
|
477
527
|
expect{ interface.executep(sql, :delete, 2.0) }.not_to raise_exception
|
478
528
|
expect( interface.list.size ).to eq(data.size - 1)
|
479
|
-
expect( interface.list.map{|r| r[:name] } ).not_to include
|
529
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
|
480
530
|
end
|
481
531
|
|
482
|
-
end
|
532
|
+
end # of #executep
|
483
533
|
|
484
534
|
|
485
535
|
describe "#selectp" do
|
486
536
|
|
487
|
-
it
|
537
|
+
it "requires an SQL string" do
|
488
538
|
expect{ interface.selectp }.to raise_exception ArgumentError
|
489
539
|
expect{ interface.selectp(nil) }.to raise_exception ArgumentError
|
490
540
|
expect{ interface.selectp(14) }.to raise_exception ArgumentError
|
491
541
|
end
|
492
542
|
|
493
|
-
it
|
494
|
-
expect{ interface.selectp(
|
543
|
+
it "raises some sort of Pod4 error if it runs into problems" do
|
544
|
+
expect{ interface.selectp("select * from not_a_table where thingy = ?", 14) }.
|
495
545
|
to raise_exception Pod4Error
|
496
546
|
|
497
547
|
end
|
498
548
|
|
499
|
-
it
|
500
|
-
sql =
|
549
|
+
it "returns the result of the sql" do
|
550
|
+
sql = "select name from customer where qty < ?;"
|
501
551
|
|
502
552
|
expect{ interface.selectp(sql, 2.0) }.not_to raise_exception
|
503
|
-
expect( interface.selectp(sql, 2.0) ).to eq( [{name:
|
553
|
+
expect( interface.selectp(sql, 2.0) ).to eq( [{name: "Barney"}] )
|
504
554
|
expect( interface.selectp(sql, 0.0) ).to eq( [] )
|
505
555
|
end
|
506
556
|
|
507
|
-
it
|
557
|
+
it "works if you pass a non-select" do
|
508
558
|
# By which I mean: still executes the SQL; returns []
|
509
|
-
sql =
|
559
|
+
sql = "delete from customer where qty < ?;"
|
510
560
|
ret = interface.selectp(sql, 2.0)
|
511
561
|
|
512
562
|
expect( interface.list.size ).to eq(data.size - 1)
|
513
|
-
expect( interface.list.map{|r| r[:name] } ).not_to include
|
563
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
|
514
564
|
expect( ret ).to eq( [] )
|
515
565
|
end
|
516
566
|
|
517
|
-
end
|
567
|
+
end # #selectp
|
568
|
+
|
569
|
+
|
570
|
+
#
|
571
|
+
# We'll not be testing #new_connection or #close_connection since for Sequel, they basically do
|
572
|
+
# nothing.
|
573
|
+
#
|
518
574
|
|
519
575
|
|
520
576
|
end
|