janko 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,400 @@
1
+ require "spec_helper"
2
+ require "janko/merge"
3
+
4
+ RSpec.shared_examples_for "a merger" do
5
+ around :each do |example|
6
+ begin
7
+ connection.exec(<<-END)
8
+ BEGIN;
9
+ CREATE TEMP TABLE merge_test(id SERIAL NOT NULL,
10
+ user_id INTEGER, title TEXT, content TEXT,
11
+ vector TSVECTOR, votes INTEGER DEFAULT 0)
12
+ ON COMMIT DROP;
13
+ CREATE UNIQUE INDEX index_merge_test_id ON merge_test(id);
14
+ CREATE UNIQUE INDEX index_merge_test_unique_content
15
+ ON merge_test(user_id, content);
16
+ END
17
+ merge.connect(connection)
18
+ example.run
19
+ rescue
20
+ raise
21
+ ensure
22
+ connection.exec("ROLLBACK")
23
+ end
24
+ end
25
+
26
+ let(:connection) { Janko::Connection.default }
27
+
28
+ let(:merge) { subject.set_table("merge_test") }
29
+
30
+ def select_all
31
+ result = connection.exec(<<-END)
32
+ SELECT * FROM merge_test ORDER BY id asc
33
+ END
34
+ output = []
35
+ result.each { |tuple| output.push(tuple) }
36
+ output
37
+ end
38
+
39
+ let(:results) { select_all }
40
+
41
+ it "#start preserves state" do
42
+ merge.start
43
+ expect(lambda { merge.insert(:id) }).to raise_error(RuntimeError)
44
+ end
45
+
46
+ it "#stop allows state changes" do
47
+ merge.start.stop
48
+ expect(lambda { merge.insert(:id) }).to_not raise_error
49
+ end
50
+
51
+ it "#push" do
52
+ record = { title: "Hello, world!" }
53
+ merge.start.push(record).stop
54
+ expect(results.count).to eq(1)
55
+ expect(results.first["title"]).to eq(record[:title])
56
+ end
57
+
58
+ describe "#insert" do
59
+ let(:row) { { id: 42, user_id: 1, title: "foo", content: "bar" } }
60
+
61
+ it "defaults to everything but id" do
62
+ merge.start.push(row).stop
63
+ expect(results.count).to eq(1)
64
+ expect(results.first["id"]).to eq("1")
65
+ expect(results.first["title"]).to eq(row[:title])
66
+ expect(results.first["content"]).to eq(row[:content])
67
+ expect(results.first["user_id"]).to eq(row[:user_id].to_s)
68
+ end
69
+
70
+ it "specific fields only" do
71
+ merge.insert(:title).start.push(row).stop
72
+ expect(results.count).to eq(1)
73
+ expect(results.first["id"]).to eq("1")
74
+ expect(results.first["title"]).to eq(row[:title])
75
+ expect(results.first["content"]).to be_nil
76
+ expect(results.first["user_id"]).to be_nil
77
+ end
78
+
79
+ it "multiple rows" do
80
+ merge.start.push(title: "foo").push(title: "bar").stop
81
+ expect(results.count).to eq(2)
82
+ end
83
+
84
+ describe "#alter" do
85
+ it "#default from user" do
86
+ merge.insert(:title)
87
+ merge.alter(:title) { |f| f.default("upper('foo')") }
88
+ merge.start.push(title: nil).stop
89
+ expect(results.first["title"]).to eq("FOO")
90
+ end
91
+
92
+ it "#default from database" do
93
+ merge.insert(:id)
94
+ merge.alter(:id) { |f| f.default(Janko::DEFAULT) }
95
+ merge.start.push(id: nil).stop
96
+ expect(results.first["id"]).to eq("1")
97
+ end
98
+
99
+ it "#default keep existing ignored" do
100
+ merge.insert(:title)
101
+ merge.alter(:title) { |f| f.default(Janko::KEEP) }
102
+ merge.start.push(id: 1, title: nil).stop
103
+ expect(results.count).to eq(1)
104
+ expect(results.first["title"]).to be_nil
105
+ end
106
+
107
+ it "#default keep if nil, otherwise use database default" do
108
+ merge.insert(:title).alter(:title) { |f|
109
+ f.default(Janko::DEFAULT | Janko::KEEP) }
110
+ merge.start.push(id: 1, title: nil).stop
111
+ expect(results.count).to eq(1)
112
+ expect(results.first["title"]).to be_nil
113
+ end
114
+
115
+ it "#wrap function" do
116
+ merge.insert(:title)
117
+ merge.alter(:title) { |f| f.wrap("upper($NEW)") }
118
+ merge.start.push(title: "foo").stop
119
+ expect(results.first["title"]).to eq("FOO")
120
+ end
121
+
122
+ it "#wrap cast" do
123
+ merge.insert(:vector)
124
+ merge.alter(:vector) { |f| f.wrap("$NEW::tsvector") }
125
+ merge.start.push(vector: "hello:1A world:2A").stop
126
+ expect(results.first["vector"]).to eq("'hello':1A 'world':2A")
127
+ end
128
+
129
+ it "#wrap value" do
130
+ merge.insert(:votes)
131
+ merge.alter(:votes) { |f| f.wrap("3") }
132
+ merge.start.push(votes: "42").stop
133
+ expect(results.first["votes"]).to eq("3")
134
+ end
135
+ end
136
+ end
137
+
138
+ describe "#key" do
139
+ it "defaults to id" do
140
+ original = { title: "Hello, world!" }
141
+ update = { id: 1, title: "こんにちは" }
142
+ merge.start.push(original).stop
143
+ merge.start.push(update).stop
144
+ expect(results.count).to eq(1)
145
+ expect(results.first["id"]).to eq("1")
146
+ expect(results.first["title"]).to eq(update[:title])
147
+ end
148
+
149
+ it "one field" do
150
+ original = { title: "foo", content: "bar" }
151
+ update = { title: "quux", content: "bar" }
152
+ merge.key(:content)
153
+ merge.start.push(original).stop
154
+ merge.start.push(update).stop
155
+ expect(results.count).to eq(1)
156
+ expect(results.first["title"]).to eq(update[:title])
157
+ expect(results.first["content"]).to eq(update[:content])
158
+ end
159
+
160
+ it "multiple fields" do
161
+ original = { title: "foo", content: "bar", user_id: 1 }
162
+ keep = { title: "foo", content: "bar", user_id: 2 }
163
+ update = { title: "foo", content: "baz", user_id: 1 }
164
+ merge.key(%w(title user_id))
165
+ merge.start.push(original).push(keep).stop
166
+ merge.start.push(update).stop
167
+ expect(results.count).to eq(2)
168
+ expect(results.first["content"]).to eq(update[:content])
169
+ expect(results.last["content"]).to eq(keep[:content])
170
+ expect(results.last["user_id"]).to eq(keep[:user_id].to_s)
171
+ end
172
+ end
173
+
174
+ describe "#update" do
175
+ let(:original) { { title: "foo", content: "bar", votes: 1 } }
176
+
177
+ let(:update) { { id: 1, title: "baz", content: "bang" } }
178
+
179
+ before(:each) { merge.start.push(original).stop }
180
+
181
+ it "defaults to everything but id" do
182
+ merge.start.push(update).stop
183
+ expect(results.count).to eq(1)
184
+ expect(results.first["id"]).to eq("1")
185
+ expect(results.first["title"]).to eq(update[:title])
186
+ expect(results.first["content"]).to eq(update[:content])
187
+ expect(results.first["user_id"]).to be_nil
188
+ end
189
+
190
+ it "specific fields only" do
191
+ merge.update(:title).start.push(update).stop
192
+ expect(results.count).to eq(1)
193
+ expect(results.first["title"]).to eq(update[:title])
194
+ expect(results.first["content"]).to eq(original[:content])
195
+ end
196
+
197
+ describe "#alter" do
198
+ it "#default NULL" do
199
+ merge.update(:votes)
200
+ merge.alter(:votes) { |f| f.default(nil) }
201
+ merge.start.push(update).stop
202
+ expect(results.count).to eq(1)
203
+ expect(results.first["votes"]).to be_nil
204
+ end
205
+
206
+ it "#default from user" do
207
+ merge.update(:votes)
208
+ merge.alter(:votes) { |f| f.default("round(3.14)") }
209
+ merge.start.push(update).stop
210
+ expect(results.count).to eq(1)
211
+ expect(results.first["votes"]).to eq("3")
212
+ end
213
+
214
+ it "#default from database" do
215
+ merge.update(:votes)
216
+ merge.alter(:votes) { |f| f.default(Janko::DEFAULT) }
217
+ merge.start.push(update).stop
218
+ expect(results.count).to eq(1)
219
+ expect(results.first["votes"]).to eq("0")
220
+ end
221
+
222
+ it "#default keep existing" do
223
+ merge.update(:title)
224
+ merge.alter(:title) { |f| f.default(Janko::KEEP) }
225
+ merge.start.push(id: 1, title: nil).stop
226
+ expect(results.count).to eq(1)
227
+ expect(results.first["title"]).to eq(original[:title])
228
+ end
229
+
230
+ it "#default keep if nil, otherwise use database default" do
231
+ merge.update(:title).alter(:title) { |f|
232
+ f.default(Janko::DEFAULT | Janko::KEEP) }
233
+ merge.start.push(id: 1, title: nil).stop
234
+ expect(results.count).to eq(1)
235
+ expect(results.first["title"]).to eq(original[:title])
236
+ end
237
+
238
+ it "#wrap function" do
239
+ merge.update(:title)
240
+ merge.alter(:title) { |f| f.wrap("upper($NEW)") }
241
+ merge.start.push(update).stop
242
+ expect(results.first["title"]).to eq("BAZ")
243
+ end
244
+
245
+ it "#wrap cast" do
246
+ merge.update(:title)
247
+ merge.alter(:title) { |f| f.wrap("$NEW::text") }
248
+ merge.start.push(update).stop
249
+ expect(results.first["title"]).to eq("baz")
250
+ end
251
+
252
+ it "#on_update alter existing value" do
253
+ merge.update(:title, :votes)
254
+ merge.alter(:votes) { |f| f.on_update("$OLD + 1") }
255
+ merge.start.push(update).stop
256
+ expect(results.first["votes"]).to eq("2")
257
+ end
258
+ end
259
+
260
+ end
261
+
262
+ describe "#returning" do
263
+ def insert_and_update
264
+ merge.start.push(title: "foo").stop
265
+ merge.start
266
+ merge.push(id: 1, title: "bar")
267
+ merge.push(title: "baz")
268
+ merge.stop
269
+ end
270
+
271
+ it "inserted" do
272
+ merge.returning(:inserted)
273
+ insert_and_update
274
+ expect(merge.result.inserted.count).to eq(1)
275
+ expect(merge.result.updated.count).to eq(0)
276
+ end
277
+
278
+ it "updated" do
279
+ merge.returning(:updated)
280
+ insert_and_update
281
+ expect(merge.result.inserted.count).to eq(0)
282
+ expect(merge.result.updated.count).to eq(1)
283
+ end
284
+
285
+ it "all" do
286
+ merge.returning(:all)
287
+ insert_and_update
288
+ expect(merge.result.inserted.count).to eq(1)
289
+ expect(merge.result.updated.count).to eq(1)
290
+ end
291
+
292
+ it "none" do
293
+ merge.returning(:none)
294
+ insert_and_update
295
+ expect(merge.result.inserted.count).to eq(0)
296
+ expect(merge.result.updated.count).to eq(0)
297
+ end
298
+
299
+ it "invalid" do
300
+ expect(lambda { merge.returning(:ducks) })
301
+ .to raise_error(RuntimeError)
302
+ end
303
+
304
+ it "into object" do
305
+ collector = double
306
+ merge.returning(:all).set_collector(collector)
307
+ expect(collector).to receive(:push).at_least(3).times
308
+ expect(collector).to receive(:clear).at_least(:once)
309
+ insert_and_update
310
+ end
311
+
312
+ pending "into table"
313
+ end
314
+
315
+ describe "#select" do
316
+ let(:record) { { title: "foo", content: "bar", user_id: 1 } }
317
+
318
+ let(:inserted) do
319
+ merge.returning(:inserted).start.push(record).stop
320
+ merge.result.inserted.first
321
+ end
322
+
323
+ it "all columns by default" do
324
+ expect(inserted["id"]).to eq("1")
325
+ expect(inserted["title"]).to eq(record[:title])
326
+ expect(inserted["content"]).to eq(record[:content])
327
+ expect(inserted["user_id"]).to eq(record[:user_id].to_s)
328
+ end
329
+
330
+ it "select columns" do
331
+ merge.select(:id, :title)
332
+ expect(inserted.count).to eq(2)
333
+ expect(inserted["id"]).to eq("1")
334
+ expect(inserted["title"]).to eq(record[:title])
335
+ end
336
+ end
337
+
338
+ describe "#set_locking" do
339
+ let(:record) { { title: "foo", content: "bar", user_id: 1 } }
340
+
341
+ it "true" do
342
+ merge.set_locking(true).start.push(record).stop
343
+ expect(results.count).to eq(1)
344
+ expect(results.first["title"]).to eq(record[:title])
345
+ end
346
+
347
+ it "false" do
348
+ merge.set_locking(false).start.push(record).stop
349
+ expect(results.count).to eq(1)
350
+ expect(results.first["title"]).to eq(record[:title])
351
+ end
352
+ end
353
+
354
+ describe "#set_transaction" do
355
+ let(:record) { { title: "foo", content: "bar", user_id: 1 } }
356
+
357
+ it "true" do
358
+ merge.set_transaction(true).start.push(record).stop
359
+ expect(results.count).to eq(1)
360
+ expect(results.first["title"]).to eq(record[:title])
361
+ end
362
+
363
+ it "false" do
364
+ merge.set_transaction(false).start.push(record).stop
365
+ expect(results.count).to eq(1)
366
+ expect(results.first["title"]).to eq(record[:title])
367
+ end
368
+ end
369
+
370
+ it "inserts on null key" do
371
+ merge.start.push(title: "foo", content: "bar").stop
372
+ merge.start.push(title: nil, content: "baz").stop
373
+ expect(select_all.count).to eq(2)
374
+ merge.key(:title).start
375
+ merge.push(title: nil, content: "bang")
376
+ merge.push(title: "foo", content: "quux")
377
+ merge.stop
378
+ expect(results.count).to eq(3)
379
+ expect(results).to be_any { |r|
380
+ r["title"].nil? and r["content"] == "baz" }
381
+ expect(results).to be_any { |r|
382
+ r["title"].nil? and r["content"] == "bang" }
383
+ expect(results).to be_any { |r|
384
+ r["title"] == "foo" and r["content"] == "quux" }
385
+ end
386
+ end
387
+
388
+ RSpec.describe Janko::Merge do
389
+ describe "#strategy single" do
390
+ let(:subject) { Janko::Merge.new.use(Janko::SingleMerge) }
391
+
392
+ it_behaves_like "a merger"
393
+ end
394
+
395
+ describe "#strategy bulk" do
396
+ let(:subject) { Janko::Merge.new.use(Janko::BulkMerge) }
397
+
398
+ it_behaves_like "a merger"
399
+ end
400
+ end
@@ -0,0 +1,32 @@
1
+ root = File.join(File.dirname(File.expand_path(__FILE__)), "..")
2
+ $LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root)
3
+
4
+ require "simplecov"
5
+ require "config/environment"
6
+
7
+ SimpleCov.start do
8
+ add_filter "/spec/"
9
+ add_filter "/config/"
10
+ end
11
+
12
+ #
13
+ # http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
14
+ #
15
+ RSpec.configure do |config|
16
+ config.filter_run(:focus)
17
+ config.run_all_when_everything_filtered = true
18
+ config.order = :random
19
+ config.default_formatter = "doc" if config.files_to_run.one?
20
+ Kernel.srand(config.seed)
21
+
22
+ # config.profile_examples = 10
23
+
24
+ config.expect_with :rspec do |expectations|
25
+ expectations.syntax = :expect
26
+ end
27
+
28
+ config.mock_with :rspec do |mocks|
29
+ mocks.syntax = :expect
30
+ mocks.verify_partial_doubles = true
31
+ end
32
+ end
@@ -0,0 +1,56 @@
1
+ require "spec_helper"
2
+ require "janko/tagged_column"
3
+
4
+ RSpec.describe Janko::TaggedColumn do
5
+ describe "parent" do
6
+ let(:target) { double }
7
+
8
+ let(:subject) { Janko::TaggedColumn.new(parent: target) }
9
+
10
+ it "#connection" do
11
+ expect(target).to receive(:connection)
12
+ subject.connection
13
+ end
14
+
15
+ it "#table" do
16
+ expect(target).to receive(:table)
17
+ subject.table
18
+ end
19
+ end
20
+
21
+ it "#set" do
22
+ subject = Janko::TaggedColumn.new
23
+ expect(subject.name).to be_nil
24
+ subject.set(name: "field")
25
+ expect(subject.name).to eq("field")
26
+ end
27
+
28
+ describe "#default" do
29
+ let(:parent) { double }
30
+
31
+ let(:subject) { Janko::TaggedColumn.new(parent: parent,
32
+ name: "field") }
33
+
34
+ it "none by default" do
35
+ expect(subject.to_setter("left", "right")).to \
36
+ eq('"field" = "right"."field"')
37
+ end
38
+
39
+ it "user-specified" do
40
+ subject.default("current_time")
41
+ expect(subject.to_setter("left", "right")).to \
42
+ eq('"field" = COALESCE("right"."field", current_time)')
43
+ end
44
+
45
+ it "from the database" do
46
+ connection = double
47
+ expect(parent).to receive(:connection).and_return(connection)
48
+ expect(parent).to receive(:table)
49
+ expect(connection).to receive(:column_default).and_return("NEXT")
50
+ subject.default(Janko::Constants::DEFAULT)
51
+ expect(subject.to_setter("left", "right")).to \
52
+ eq('"field" = COALESCE("right"."field", NEXT)')
53
+ end
54
+ end
55
+ end
56
+