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