logstash-integration-jdbc 5.0.0.alpha1

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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +8 -0
  3. data/CONTRIBUTORS +22 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE +13 -0
  6. data/NOTICE.TXT +5 -0
  7. data/README.md +105 -0
  8. data/docs/filter-jdbc_static.asciidoc +606 -0
  9. data/docs/filter-jdbc_streaming.asciidoc +317 -0
  10. data/docs/index.asciidoc +32 -0
  11. data/docs/input-jdbc.asciidoc +573 -0
  12. data/lib/logstash/filters/jdbc/basic_database.rb +125 -0
  13. data/lib/logstash/filters/jdbc/column.rb +39 -0
  14. data/lib/logstash/filters/jdbc/db_object.rb +101 -0
  15. data/lib/logstash/filters/jdbc/loader.rb +119 -0
  16. data/lib/logstash/filters/jdbc/loader_schedule.rb +64 -0
  17. data/lib/logstash/filters/jdbc/lookup.rb +253 -0
  18. data/lib/logstash/filters/jdbc/lookup_processor.rb +100 -0
  19. data/lib/logstash/filters/jdbc/lookup_result.rb +40 -0
  20. data/lib/logstash/filters/jdbc/read_only_database.rb +57 -0
  21. data/lib/logstash/filters/jdbc/read_write_database.rb +108 -0
  22. data/lib/logstash/filters/jdbc/repeating_load_runner.rb +13 -0
  23. data/lib/logstash/filters/jdbc/single_load_runner.rb +46 -0
  24. data/lib/logstash/filters/jdbc/validatable.rb +46 -0
  25. data/lib/logstash/filters/jdbc_static.rb +240 -0
  26. data/lib/logstash/filters/jdbc_streaming.rb +196 -0
  27. data/lib/logstash/inputs/jdbc.rb +341 -0
  28. data/lib/logstash/inputs/tzinfo_jruby_patch.rb +57 -0
  29. data/lib/logstash/plugin_mixins/jdbc/checked_count_logger.rb +43 -0
  30. data/lib/logstash/plugin_mixins/jdbc/jdbc.rb +298 -0
  31. data/lib/logstash/plugin_mixins/jdbc/statement_handler.rb +129 -0
  32. data/lib/logstash/plugin_mixins/jdbc/value_tracking.rb +140 -0
  33. data/lib/logstash/plugin_mixins/jdbc_streaming/cache_payload.rb +28 -0
  34. data/lib/logstash/plugin_mixins/jdbc_streaming/parameter_handler.rb +64 -0
  35. data/lib/logstash/plugin_mixins/jdbc_streaming/statement_handler.rb +143 -0
  36. data/lib/logstash/plugin_mixins/jdbc_streaming.rb +100 -0
  37. data/lib/logstash/plugin_mixins/statement_handler.rb +0 -0
  38. data/lib/logstash-integration-jdbc_jars.rb +5 -0
  39. data/logstash-integration-jdbc.gemspec +44 -0
  40. data/spec/filters/env_helper.rb +10 -0
  41. data/spec/filters/integration/jdbc_static_spec.rb +154 -0
  42. data/spec/filters/integration/jdbcstreaming_spec.rb +173 -0
  43. data/spec/filters/jdbc/column_spec.rb +70 -0
  44. data/spec/filters/jdbc/db_object_spec.rb +81 -0
  45. data/spec/filters/jdbc/loader_spec.rb +77 -0
  46. data/spec/filters/jdbc/lookup_processor_spec.rb +132 -0
  47. data/spec/filters/jdbc/lookup_spec.rb +253 -0
  48. data/spec/filters/jdbc/read_only_database_spec.rb +67 -0
  49. data/spec/filters/jdbc/read_write_database_spec.rb +90 -0
  50. data/spec/filters/jdbc/repeating_load_runner_spec.rb +24 -0
  51. data/spec/filters/jdbc/single_load_runner_spec.rb +16 -0
  52. data/spec/filters/jdbc_static_file_local_spec.rb +83 -0
  53. data/spec/filters/jdbc_static_spec.rb +162 -0
  54. data/spec/filters/jdbc_streaming_spec.rb +350 -0
  55. data/spec/filters/remote_server_helper.rb +24 -0
  56. data/spec/filters/shared_helpers.rb +34 -0
  57. data/spec/helpers/WHY-THIS-JAR.txt +4 -0
  58. data/spec/helpers/derbyrun.jar +0 -0
  59. data/spec/inputs/integration/integ_spec.rb +78 -0
  60. data/spec/inputs/jdbc_spec.rb +1431 -0
  61. data/vendor/jar-dependencies/org/apache/derby/derby/10.14.1.0/derby-10.14.1.0.jar +0 -0
  62. data/vendor/jar-dependencies/org/apache/derby/derbyclient/10.14.1.0/derbyclient-10.14.1.0.jar +0 -0
  63. metadata +319 -0
@@ -0,0 +1,1431 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/inputs/jdbc"
4
+ require "jdbc/derby"
5
+ require "sequel"
6
+ require "sequel/adapters/jdbc"
7
+ require "timecop"
8
+ require "stud/temporary"
9
+ require "time"
10
+ require "date"
11
+
12
+ # We do not need to set TZ env var anymore because we can have 'Sequel.application_timezone' set to utc by default now.
13
+
14
+ describe LogStash::Inputs::Jdbc do
15
+ let(:mixin_settings) do
16
+ { "jdbc_user" => ENV['USER'], "jdbc_driver_class" => "org.apache.derby.jdbc.EmbeddedDriver",
17
+ "jdbc_connection_string" => "jdbc:derby:memory:testdb;create=true"}
18
+ end
19
+ let(:settings) { {} }
20
+ let(:plugin) { LogStash::Inputs::Jdbc.new(mixin_settings.merge(settings)) }
21
+ let(:queue) { Queue.new }
22
+ let (:db) do
23
+ Sequel.connect(mixin_settings['jdbc_connection_string'], :user=> nil, :password=> nil)
24
+ end
25
+
26
+ before :each do
27
+ if !RSpec.current_example.metadata[:no_connection]
28
+ # before body
29
+ Jdbc::Derby.load_driver
30
+ db.create_table :test_table do
31
+ DateTime :created_at
32
+ Integer :num
33
+ String :string
34
+ DateTime :custom_time
35
+ end
36
+ db << "CREATE TABLE types_table (num INTEGER, string VARCHAR(255), started_at DATE, custom_time TIMESTAMP, ranking DECIMAL(16,6))"
37
+ db << "CREATE TABLE test1_table (num INTEGER, string VARCHAR(255), custom_time TIMESTAMP, created_at TIMESTAMP)"
38
+ end
39
+ end
40
+
41
+ after :each do
42
+ if !RSpec.current_example.metadata[:no_connection]
43
+ db.drop_table(:test_table)
44
+ db.drop_table(:types_table)
45
+ db.drop_table(:test1_table)
46
+ end
47
+ end
48
+
49
+ context "when registering and tearing down" do
50
+ let(:settings) { {"statement" => "SELECT 1 as col1 FROM test_table"} }
51
+
52
+ it "should register without raising exception" do
53
+ expect { plugin.register }.to_not raise_error
54
+ plugin.stop
55
+ end
56
+
57
+ it "should register with password set" do
58
+ mixin_settings['jdbc_password'] = 'pass'
59
+ expect { plugin.register }.to_not raise_error
60
+ plugin.stop
61
+ end
62
+
63
+ it "should stop without raising exception" do
64
+ plugin.register
65
+ expect { plugin.stop }.to_not raise_error
66
+ end
67
+
68
+ it_behaves_like "an interruptible input plugin" do
69
+ let(:settings) do
70
+ {
71
+ "statement" => "SELECT 1 FROM test_table",
72
+ "schedule" => "* * * * * UTC"
73
+ }
74
+ end
75
+ let(:config) { mixin_settings.merge(settings) }
76
+ end
77
+ end
78
+
79
+ context "when both jdbc_password and jdbc_password_filepath arguments are passed" do
80
+ let(:statement) { "SELECT * from test_table" }
81
+ let(:jdbc_password) { "secret" }
82
+ let(:jdbc_password_file_path) { Stud::Temporary.pathname }
83
+ let(:settings) { { "jdbc_password_filepath" => jdbc_password_file_path,
84
+ "jdbc_password" => jdbc_password,
85
+ "statement" => statement } }
86
+
87
+ it "should fail to register" do
88
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
89
+ end
90
+ end
91
+
92
+ context "when jdbc_password is passed in from a file" do
93
+ let(:statement) { "SELECT * from test_table" }
94
+ let(:jdbc_password) { "secret" }
95
+ let(:jdbc_password_file_path) { Stud::Temporary.pathname }
96
+ let(:settings) { { "jdbc_password_filepath" => jdbc_password_file_path,
97
+ "statement" => statement } }
98
+
99
+ before do
100
+ File.write(jdbc_password_file_path, jdbc_password)
101
+ plugin.register
102
+ end
103
+
104
+ after do
105
+ plugin.stop
106
+ end
107
+
108
+ it "should read in jdbc_password from file" do
109
+ expect(plugin.jdbc_password.value).to eq(jdbc_password)
110
+ end
111
+ end
112
+
113
+
114
+ context "when neither statement and statement_filepath arguments are passed" do
115
+ it "should fail to register" do
116
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
117
+ end
118
+ end
119
+
120
+ context "when both statement and statement_filepath arguments are passed" do
121
+ let(:statement) { "SELECT * from test_table" }
122
+ let(:statement_file_path) { Stud::Temporary.pathname }
123
+ let(:settings) { { "statement_filepath" => statement_file_path, "statement" => statement } }
124
+
125
+ it "should fail to register" do
126
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
127
+ end
128
+ end
129
+
130
+ context "when statement is passed in from a file" do
131
+ let(:statement) { "SELECT * from test_table" }
132
+ let(:statement_file_path) { Stud::Temporary.pathname }
133
+ let(:settings) { { "statement_filepath" => statement_file_path } }
134
+
135
+ before do
136
+ File.write(statement_file_path, statement)
137
+ plugin.register
138
+ end
139
+
140
+ after do
141
+ plugin.stop
142
+ end
143
+
144
+ it "should read in statement from file" do
145
+ expect(plugin.statement).to eq(statement)
146
+ end
147
+ end
148
+
149
+ context "when passing parameters" do
150
+ let(:settings) do
151
+ {
152
+ "statement" => "SELECT :num_param as num_param FROM SYSIBM.SYSDUMMY1",
153
+ "parameters" => { "num_param" => 10}
154
+ }
155
+ end
156
+
157
+ before do
158
+ plugin.register
159
+ end
160
+
161
+ after do
162
+ plugin.stop
163
+ end
164
+
165
+ it "should retrieve params correctly from Event" do
166
+ plugin.run(queue)
167
+ expect(queue.pop.get('num_param')).to eq(settings['parameters']['num_param'])
168
+ end
169
+ end
170
+
171
+ context "when scheduling" do
172
+ let(:settings) { {"statement" => "SELECT 1 as num_param FROM SYSIBM.SYSDUMMY1", "schedule" => "* * * * * UTC"} }
173
+
174
+ before do
175
+ plugin.register
176
+ end
177
+
178
+ it "should properly schedule" do
179
+ Timecop.travel(Time.new(2000))
180
+ Timecop.scale(60)
181
+ runner = Thread.new do
182
+ plugin.run(queue)
183
+ end
184
+ sleep 3
185
+ plugin.stop
186
+ runner.kill
187
+ runner.join
188
+ expect(queue.size).to eq(2)
189
+ Timecop.return
190
+ end
191
+
192
+ end
193
+
194
+ context "when scheduling and previous runs are to be preserved" do
195
+ let(:settings) do
196
+ {
197
+ "statement" => "SELECT 1 as num_param FROM SYSIBM.SYSDUMMY1",
198
+ "schedule" => "* * * * * UTC",
199
+ "last_run_metadata_path" => Stud::Temporary.pathname
200
+ }
201
+ end
202
+
203
+ let(:last_run_time) { Time.at(1).utc }
204
+
205
+ before do
206
+ plugin.register
207
+ end
208
+
209
+ it "should flush previous run metadata per query execution" do
210
+ Timecop.travel(Time.new(2000))
211
+ Timecop.scale(60)
212
+ runner = Thread.new do
213
+ plugin.run(queue)
214
+ end
215
+ sleep 1
216
+ for i in 0..1
217
+ sleep 1
218
+ updated_last_run = YAML.load(File.read(settings["last_run_metadata_path"]))
219
+ expect(updated_last_run).to be > last_run_time
220
+ last_run_time = updated_last_run
221
+ end
222
+
223
+ plugin.stop
224
+ runner.join
225
+ Timecop.return
226
+ end
227
+
228
+ end
229
+
230
+ context "when iterating result-set via paging" do
231
+
232
+ let(:settings) do
233
+ {
234
+ "statement" => "SELECT * from test_table",
235
+ "jdbc_paging_enabled" => true,
236
+ "jdbc_page_size" => 20
237
+ }
238
+ end
239
+
240
+ let(:num_rows) { 1000 }
241
+
242
+ before do
243
+ plugin.register
244
+ end
245
+
246
+ after do
247
+ plugin.stop
248
+ end
249
+
250
+ it "should fetch all rows" do
251
+ num_rows.times do
252
+ db[:test_table].insert(:num => 1, :custom_time => Time.now.utc, :created_at => Time.now.utc)
253
+ end
254
+
255
+ plugin.run(queue)
256
+
257
+ expect(queue.size).to eq(num_rows)
258
+ end
259
+
260
+ end
261
+
262
+ context "when fetching time data" do
263
+
264
+ let(:settings) do
265
+ {
266
+ "statement" => "SELECT * from test_table",
267
+ }
268
+ end
269
+
270
+ let(:num_rows) { 10 }
271
+
272
+ before do
273
+ num_rows.times do
274
+ db[:test_table].insert(:num => 1, :custom_time => Time.now.utc, :created_at => Time.now.utc)
275
+ end
276
+
277
+ plugin.register
278
+ end
279
+
280
+ after do
281
+ plugin.stop
282
+ end
283
+
284
+ it "should convert it to LogStash::Timestamp " do
285
+ plugin.run(queue)
286
+ event = queue.pop
287
+ expect(event.get("custom_time")).to be_a(LogStash::Timestamp)
288
+ end
289
+ end
290
+
291
+ describe "when jdbc_default_timezone is set" do
292
+ let(:mixin_settings) do
293
+ { "jdbc_user" => ENV['USER'], "jdbc_driver_class" => "org.apache.derby.jdbc.EmbeddedDriver",
294
+ "jdbc_connection_string" => "jdbc:derby:memory:testdb;create=true",
295
+ "jdbc_default_timezone" => "America/Chicago"
296
+ }
297
+ end
298
+
299
+ let(:hours) { [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] }
300
+
301
+ context "when fetching time data and the tracking column is set and tracking column type defaults to 'numeric'" do
302
+ let(:settings) do
303
+ {
304
+ "statement" => "SELECT * from test_table WHERE num > :sql_last_value",
305
+ "last_run_metadata_path" => Stud::Temporary.pathname,
306
+ "tracking_column" => "num",
307
+ "use_column_value" => true
308
+ }
309
+ end
310
+
311
+ it "should convert the time to reflect the timezone " do
312
+ File.write(settings["last_run_metadata_path"], YAML.dump(42))
313
+
314
+ db[:test_table].insert(:num => 42, :custom_time => "2015-01-01 10:10:10", :created_at => Time.now.utc)
315
+ db[:test_table].insert(:num => 43, :custom_time => "2015-01-01 11:11:11", :created_at => Time.now.utc)
316
+
317
+ plugin.register
318
+ plugin.run(queue)
319
+ plugin.stop
320
+ expect(queue.size).to eq(1)
321
+ event = queue.pop
322
+ expect(event.get("num")).to eq(43)
323
+ expect(event.get("custom_time").time).to eq(Time.iso8601("2015-01-01T17:11:11.000Z"))
324
+ end
325
+ end
326
+
327
+ context "when fetching time data and the tracking column is NOT set, sql_last_value is time of run" do
328
+
329
+ let(:settings) do
330
+ {
331
+ "statement" => "SELECT * from test_table WHERE custom_time > :sql_last_value",
332
+ "last_run_metadata_path" => Stud::Temporary.pathname
333
+ }
334
+ end
335
+
336
+ before do
337
+ last_run_value = DateTime.iso8601("2000-01-01T12:00:00.000Z")
338
+ File.write(settings["last_run_metadata_path"], last_run_value)
339
+ Timecop.travel(DateTime.iso8601("2015-01-01T15:50:01.000Z")) do
340
+ # simulate earlier records written
341
+ hours.each do |i|
342
+ db[:test_table].insert(:num => i, :custom_time => "2015-01-01 #{i}:00:00", :created_at => Time.now.utc)
343
+ end
344
+ end
345
+ end
346
+
347
+ it "should convert the time to reflect the timezone " do
348
+ Timecop.travel(DateTime.iso8601("2015-01-02T02:10:00.000Z")) do
349
+ # simulate the first plugin run after the custom time of the last record
350
+ plugin.register
351
+ plugin.run(queue)
352
+ expected = hours.map{|hour| Time.iso8601("2015-01-01T06:00:00.000Z") + (hour * 3600) }# because Sequel converts the column values to Time instances.
353
+ actual = queue.size.times.map { queue.pop.get("custom_time").time }
354
+ expect(actual).to eq(expected)
355
+ plugin.stop
356
+ end
357
+ Timecop.travel(DateTime.iso8601("2015-01-02T02:20:00.000Z")) do
358
+ # simulate a run 10 minutes later
359
+ plugin.register
360
+ plugin.run(queue)
361
+ expect(queue.size).to eq(0) # no new records
362
+ plugin.stop
363
+ # now add records
364
+ db[:test_table].insert(:num => 11, :custom_time => "2015-01-01 20:20:20", :created_at => Time.now.utc)
365
+ db[:test_table].insert(:num => 12, :custom_time => "2015-01-01 21:21:21", :created_at => Time.now.utc)
366
+ end
367
+ Timecop.travel(DateTime.iso8601("2015-01-02T03:30:00.000Z")) do
368
+ # simulate another run later than the custom time of the last record
369
+ plugin.register
370
+ plugin.run(queue)
371
+ expect(queue.size).to eq(2)
372
+ plugin.stop
373
+ end
374
+ event = queue.pop
375
+ expect(event.get("num")).to eq(11)
376
+ expect(event.get("custom_time").time).to eq(Time.iso8601("2015-01-02T02:20:20.000Z"))
377
+ event = queue.pop
378
+ expect(event.get("num")).to eq(12)
379
+ expect(event.get("custom_time").time).to eq(Time.iso8601("2015-01-02T03:21:21.000Z"))
380
+ end
381
+ end
382
+
383
+ context "when fetching time data and the tracking column is set, sql_last_value is sourced from a column, sub-second precision is maintained" do
384
+ let(:settings) do
385
+ {
386
+ "statement" => "SELECT * from test1_table WHERE custom_time > :sql_last_value ORDER BY custom_time",
387
+ "use_column_value" => true,
388
+ "tracking_column" => "custom_time",
389
+ "tracking_column_type" => "timestamp",
390
+ "last_run_metadata_path" => Stud::Temporary.pathname
391
+ }
392
+ end
393
+
394
+ let(:msecs) { [111, 122, 233, 244, 355, 366, 477, 488, 599, 611, 722] }
395
+
396
+ it "should convert the time to reflect the timezone " do
397
+ # Sequel only does the *correct* timezone calc on a DateTime instance
398
+ last_run_value = DateTime.iso8601("2000-01-01T00:00:00.987Z")
399
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_value))
400
+ hours.each_with_index do |i, j|
401
+ time_value = Time.utc(2015, 1, 1, i, 0, 0, msecs[j] * 1000)
402
+ db[:test1_table].insert(:num => i, :custom_time => time_value, :created_at => Time.now.utc)
403
+ end
404
+
405
+ plugin.register
406
+
407
+ plugin.run(queue)
408
+ expected = hours.map.with_index {|hour, i| Time.iso8601("2015-01-01T06:00:00.000Z") + (hour * 3600 + (msecs[i] / 1000.0)) }
409
+ actual = queue.size.times.map { queue.pop.get("custom_time").time }
410
+ expect(actual).to eq(expected)
411
+ plugin.stop
412
+ raw_last_run_value = File.read(settings["last_run_metadata_path"])
413
+ last_run_value = YAML.load(raw_last_run_value)
414
+ expect(last_run_value).to be_a(DateTime)
415
+ expect(last_run_value.strftime("%F %T.%N %Z")).to eq("2015-01-02 02:00:00.722000000 +00:00")
416
+
417
+ plugin.run(queue)
418
+ plugin.stop
419
+ db[:test1_table].insert(:num => 11, :custom_time => "2015-01-01 11:00:00.099", :created_at => Time.now.utc)
420
+ db[:test1_table].insert(:num => 12, :custom_time => "2015-01-01 21:00:00.811", :created_at => Time.now.utc)
421
+ expect(queue.size).to eq(0)
422
+ plugin.run(queue)
423
+ expect(queue.size).to eq(1)
424
+ event = queue.pop
425
+ plugin.stop
426
+ expect(event.get("num")).to eq(12)
427
+ expect(event.get("custom_time").time).to eq(Time.iso8601("2015-01-02T03:00:00.811Z"))
428
+ last_run_value = YAML.load(File.read(settings["last_run_metadata_path"]))
429
+ expect(last_run_value).to be_a(DateTime)
430
+ # verify that sub-seconds are recorded to the file
431
+ expect(last_run_value.strftime("%F %T.%N %Z")).to eq("2015-01-02 03:00:00.811000000 +00:00")
432
+ end
433
+ end
434
+ end
435
+
436
+ context "when fetching time data without jdbc_default_timezone set" do
437
+ let(:mixin_settings) do
438
+ { "jdbc_user" => ENV['USER'], "jdbc_driver_class" => "org.apache.derby.jdbc.EmbeddedDriver",
439
+ "jdbc_connection_string" => "jdbc:derby:memory:testdb;create=true"
440
+ }
441
+ end
442
+
443
+ let(:settings) do
444
+ {
445
+ "statement" => "SELECT * from test_table",
446
+ }
447
+ end
448
+
449
+ let(:num_rows) { 1 }
450
+
451
+ before do
452
+ num_rows.times do
453
+ db.run "INSERT INTO test_table (created_at, num, custom_time) VALUES (TIMESTAMP('2015-01-01 12:00:00'), 1, TIMESTAMP('2015-01-01 12:00:00'))"
454
+ end
455
+
456
+ plugin.register
457
+ end
458
+
459
+ after do
460
+ plugin.stop
461
+ end
462
+
463
+ it "should not convert the time to reflect the timezone " do
464
+ plugin.run(queue)
465
+ event = queue.pop
466
+ # With no timezone set, no change should occur
467
+ expect(event.get("custom_time").time).to eq(Time.iso8601("2015-01-01T12:00:00Z"))
468
+ end
469
+ end
470
+
471
+ context "when iteratively running plugin#run" do
472
+ let(:settings) do
473
+ {"statement" => "SELECT num, created_at FROM test_table WHERE created_at > :sql_last_value"}
474
+ end
475
+
476
+ let(:nums) { [10, 20, 30, 40, 50] }
477
+
478
+ before do
479
+ plugin.register
480
+ end
481
+
482
+ after do
483
+ plugin.stop
484
+ end
485
+
486
+ it "should successfully iterate table with respect to field values" do
487
+ test_table = db[:test_table]
488
+
489
+ plugin.run(queue)
490
+ test_table.insert(:num => nums[0], :created_at => Time.now.utc)
491
+ test_table.insert(:num => nums[1], :created_at => Time.now.utc)
492
+ plugin.run(queue)
493
+ test_table.insert(:num => nums[2], :created_at => Time.now.utc)
494
+ test_table.insert(:num => nums[3], :created_at => Time.now.utc)
495
+ test_table.insert(:num => nums[4], :created_at => Time.now.utc)
496
+ plugin.run(queue)
497
+
498
+ actual_sum = 0
499
+ until queue.empty? do
500
+ actual_sum += queue.pop.get('num')
501
+ end
502
+
503
+ expect(actual_sum).to eq(nums.inject{|sum,x| sum + x })
504
+ end
505
+ end
506
+
507
+ context "when iteratively running plugin#run with tracking_column" do
508
+ let(:mixin_settings) do
509
+ { "jdbc_user" => ENV['USER'], "jdbc_driver_class" => "org.apache.derby.jdbc.EmbeddedDriver",
510
+ "jdbc_connection_string" => "jdbc:derby:memory:testdb;create=true"
511
+ }
512
+ end
513
+
514
+ let(:settings) do
515
+ { "statement" => "SELECT num, created_at FROM test_table WHERE num > :sql_last_value",
516
+ "use_column_value" => true,
517
+ "tracking_column" => "num",
518
+ "last_run_metadata_path" => Stud::Temporary.pathname }
519
+ end
520
+
521
+ let(:nums) { [10, 20, 30, 40, 50] }
522
+
523
+ before do
524
+ plugin.register
525
+ end
526
+
527
+ after do
528
+ plugin.stop
529
+ end
530
+
531
+ it "should successfully update sql_last_value" do
532
+ test_table = db[:test_table]
533
+
534
+ plugin.run(queue)
535
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(0)
536
+ test_table.insert(:num => nums[0], :created_at => Time.now.utc)
537
+ test_table.insert(:num => nums[1], :created_at => Time.now.utc)
538
+ plugin.run(queue)
539
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(20)
540
+ test_table.insert(:num => nums[2], :created_at => Time.now.utc)
541
+ test_table.insert(:num => nums[3], :created_at => Time.now.utc)
542
+ test_table.insert(:num => nums[4], :created_at => Time.now.utc)
543
+ plugin.run(queue)
544
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(50)
545
+ end
546
+ end
547
+
548
+ context "when iteratively running plugin#run with timestamp tracking column with column value" do
549
+ let(:mixin_settings) do
550
+ { "jdbc_user" => ENV['USER'], "jdbc_driver_class" => "org.apache.derby.jdbc.EmbeddedDriver",
551
+ "jdbc_connection_string" => "jdbc:derby:memory:testdb;create=true"
552
+ }
553
+ end
554
+
555
+ let(:settings) do
556
+ { "statement" => "SELECT num, created_at, custom_time FROM test_table WHERE custom_time > :sql_last_value",
557
+ "use_column_value" => true,
558
+ "tracking_column" => "custom_time",
559
+ "tracking_column_type" => "timestamp",
560
+ "last_run_metadata_path" => Stud::Temporary.pathname }
561
+ end
562
+
563
+ let(:nums) { [10, 20, 30, 40, 50] }
564
+ let(:times) {["2015-05-06 13:14:15","2015-05-07 13:14:15","2015-05-08 13:14:15","2015-05-09 13:14:15","2015-05-10 13:14:15"]}
565
+
566
+ before do
567
+ plugin.register
568
+ end
569
+
570
+ after do
571
+ plugin.stop
572
+ end
573
+
574
+ it "should successfully update sql_last_value" do
575
+ test_table = db[:test_table]
576
+
577
+ plugin.run(queue)
578
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(Time.parse("1970-01-01 00:00:00.000000000 +0000"))
579
+ test_table.insert(:num => nums[0], :created_at => Time.now.utc, :custom_time => times[0])
580
+ test_table.insert(:num => nums[1], :created_at => Time.now.utc, :custom_time => times[1])
581
+ plugin.run(queue)
582
+ expect(plugin.instance_variable_get("@value_tracker").value.class).to eq(Time.parse(times[0]).class)
583
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(Time.parse(times[1]))
584
+ test_table.insert(:num => nums[2], :created_at => Time.now.utc, :custom_time => times[2])
585
+ test_table.insert(:num => nums[3], :created_at => Time.now.utc, :custom_time => times[3])
586
+ test_table.insert(:num => nums[4], :created_at => Time.now.utc, :custom_time => times[4])
587
+ plugin.run(queue)
588
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(Time.parse(times[4]))
589
+ end
590
+ end
591
+
592
+ context "when iteratively running plugin#run with tracking_column and stored metadata" do
593
+ let(:mixin_settings) do
594
+ { "jdbc_user" => ENV['USER'], "jdbc_driver_class" => "org.apache.derby.jdbc.EmbeddedDriver",
595
+ "jdbc_connection_string" => "jdbc:derby:memory:testdb;create=true"
596
+ }
597
+ end
598
+
599
+ let(:settings) do
600
+ { "statement" => "SELECT num, created_at FROM test_table WHERE num > :sql_last_value",
601
+ "use_column_value" => true,
602
+ "tracking_column" => "num",
603
+ "last_run_metadata_path" => Stud::Temporary.pathname }
604
+ end
605
+
606
+ let(:nums) { [10, 20, 30, 40, 50] }
607
+ let(:last_run_value) { 20 }
608
+
609
+ before do
610
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_value))
611
+ plugin.register
612
+ end
613
+
614
+ after do
615
+ plugin.stop
616
+ end
617
+
618
+ it "should successfully update sql_last_value and only add appropriate events" do
619
+ test_table = db[:test_table]
620
+
621
+ plugin.run(queue)
622
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(20)
623
+ expect(queue.length).to eq(0) # Shouldn't grab anything here.
624
+ test_table.insert(:num => nums[0], :created_at => Time.now.utc)
625
+ test_table.insert(:num => nums[1], :created_at => Time.now.utc)
626
+ plugin.run(queue)
627
+ expect(queue.length).to eq(0) # Shouldn't grab anything here either.
628
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(20)
629
+ test_table.insert(:num => nums[2], :created_at => Time.now.utc)
630
+ test_table.insert(:num => nums[3], :created_at => Time.now.utc)
631
+ test_table.insert(:num => nums[4], :created_at => Time.now.utc)
632
+ plugin.run(queue)
633
+ expect(queue.length).to eq(3) # Only values greater than 20 should be grabbed.
634
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(50)
635
+ end
636
+ end
637
+
638
+ context "when iteratively running plugin#run with BAD tracking_column and stored metadata" do
639
+ let(:mixin_settings) do
640
+ { "jdbc_user" => ENV['USER'], "jdbc_driver_class" => "org.apache.derby.jdbc.EmbeddedDriver",
641
+ "jdbc_connection_string" => "jdbc:derby:memory:testdb;create=true"
642
+ }
643
+ end
644
+
645
+ let(:settings) do
646
+ { "statement" => "SELECT num, created_at FROM test_table WHERE num > :sql_last_value",
647
+ "use_column_value" => true,
648
+ "tracking_column" => "not_num",
649
+ "last_run_metadata_path" => Stud::Temporary.pathname }
650
+ end
651
+
652
+ let(:nums) { [10, 20, 30, 40, 50] }
653
+ let(:last_run_value) { 20 }
654
+
655
+ before do
656
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_value))
657
+ plugin.register
658
+ end
659
+
660
+ after do
661
+ plugin.stop
662
+ end
663
+
664
+ it "should send a warning and not update sql_last_value" do
665
+ test_table = db[:test_table]
666
+
667
+ plugin.run(queue)
668
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(20)
669
+ expect(queue.length).to eq(0) # Shouldn't grab anything here.
670
+ test_table.insert(:num => nums[0], :created_at => Time.now.utc)
671
+ test_table.insert(:num => nums[1], :created_at => Time.now.utc)
672
+ plugin.run(queue)
673
+ expect(queue.length).to eq(0) # Shouldn't grab anything here either.
674
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(20)
675
+ test_table.insert(:num => nums[2], :created_at => Time.now.utc)
676
+ test_table.insert(:num => nums[3], :created_at => Time.now.utc)
677
+ test_table.insert(:num => nums[4], :created_at => Time.now.utc)
678
+ plugin.run(queue)
679
+ expect(queue.length).to eq(3) # Only values greater than 20 should be grabbed.
680
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(20)
681
+ expect(plugin.instance_variable_get("@tracking_column_warning_sent")).to eq(true)
682
+ end
683
+ end
684
+
685
+ context "when previous runs are to be respected upon successful query execution (by time)" do
686
+
687
+ let(:settings) do
688
+ { "statement" => "SELECT 1 as num_param FROM SYSIBM.SYSDUMMY1",
689
+ "last_run_metadata_path" => Stud::Temporary.pathname }
690
+ end
691
+
692
+ let(:last_run_time) { Time.now.utc }
693
+
694
+ before do
695
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_time))
696
+ plugin.register
697
+ end
698
+
699
+ after do
700
+ plugin.stop
701
+ end
702
+
703
+ it "should respect last run metadata" do
704
+ plugin.run(queue)
705
+
706
+ expect(plugin.instance_variable_get("@value_tracker").value).to be > last_run_time
707
+ end
708
+ end
709
+
710
+ context "when previous runs are to be respected upon successful query execution (by time string)" do
711
+
712
+ let(:settings) do
713
+ { "statement" => "SELECT custom_time FROM test_table WHERE custom_time > :sql_last_value",
714
+ "use_column_value" => true,
715
+ "tracking_column" => "custom_time",
716
+ "tracking_column_type" => "timestamp",
717
+ "last_run_metadata_path" => Stud::Temporary.pathname }
718
+ end
719
+
720
+ let(:last_run_time) { '2010-03-19T14:48:40.483Z' }
721
+
722
+ before do
723
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_time))
724
+ test_table = db[:test_table]
725
+ test_table.insert(:num => 0, :custom_time => Time.now.utc)
726
+ plugin.register
727
+ end
728
+
729
+ after do
730
+ plugin.stop
731
+ end
732
+
733
+ it "should respect last run metadata" do
734
+ plugin.run(queue)
735
+ expect(plugin.instance_variable_get("@value_tracker").value).to be > DateTime.parse(last_run_time).to_time
736
+ end
737
+ end
738
+
739
+ context "when previous runs are to be respected upon successful query execution (by date/time string)" do
740
+
741
+ let(:settings) do
742
+ { "statement" => "SELECT custom_time FROM test_table WHERE custom_time > :sql_last_value",
743
+ "use_column_value" => true,
744
+ "tracking_column" => "custom_time",
745
+ "tracking_column_type" => "timestamp",
746
+ "jdbc_default_timezone" => "UTC", #this triggers the last_run_time to be treated as date/time
747
+ "last_run_metadata_path" => Stud::Temporary.pathname }
748
+ end
749
+
750
+ let(:last_run_time) { '2010-03-19T14:48:40.483Z' }
751
+
752
+ before do
753
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_time))
754
+ test_table = db[:test_table]
755
+ test_table.insert(:num => 0, :custom_time => Time.now.utc)
756
+ plugin.register
757
+ end
758
+
759
+ after do
760
+ plugin.stop
761
+ end
762
+
763
+ it "should respect last run metadata" do
764
+ plugin.run(queue)
765
+ expect(plugin.instance_variable_get("@value_tracker").value).to be > DateTime.parse(last_run_time)
766
+ end
767
+ end
768
+
769
+ context "when previous runs are to be respected upon successful query execution (by column)" do
770
+
771
+ let(:settings) do
772
+ { "statement" => "SELECT 1 as num_param FROM SYSIBM.SYSDUMMY1",
773
+ "use_column_value" => true,
774
+ "tracking_column" => "num_param",
775
+ "last_run_metadata_path" => Stud::Temporary.pathname }
776
+ end
777
+
778
+ let(:last_run_value) { 1 }
779
+
780
+ before do
781
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_value))
782
+ plugin.register
783
+ end
784
+
785
+ after do
786
+ plugin.stop
787
+ end
788
+
789
+ it "metadata should equal last_run_value" do
790
+ plugin.run(queue)
791
+
792
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(last_run_value)
793
+ end
794
+ end
795
+
796
+ context "when previous runs are to be respected upon query failure (by time)" do
797
+ let(:settings) do
798
+ { "statement" => "SELECT col from non_existent_table",
799
+ "last_run_metadata_path" => Stud::Temporary.pathname }
800
+ end
801
+
802
+ let(:last_run_time) { Time.now.utc }
803
+
804
+ before do
805
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_time))
806
+ plugin.register
807
+ end
808
+
809
+ after do
810
+ plugin.stop
811
+ end
812
+
813
+ it "should not respect last run metadata" do
814
+ plugin.run(queue)
815
+
816
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(last_run_time)
817
+ end
818
+ end
819
+
820
+ context "when previous runs are to be respected upon query failure (by column)" do
821
+ let(:settings) do
822
+ { "statement" => "SELECT col from non_existent_table",
823
+ "use_column_value" => true,
824
+ "tracking_column" => "num_param",
825
+ "last_run_metadata_path" => Stud::Temporary.pathname
826
+ }
827
+ end
828
+
829
+ let(:last_run_value) { 1 }
830
+
831
+ before do
832
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_value))
833
+ plugin.register
834
+ end
835
+
836
+ after do
837
+ plugin.stop
838
+ end
839
+
840
+ it "metadata should still reflect last value" do
841
+ plugin.run(queue)
842
+
843
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(last_run_value)
844
+ end
845
+ end
846
+
847
+ context "when doing a clean run (by time)" do
848
+
849
+ let(:settings) do
850
+ {
851
+ "statement" => "SELECT * FROM test_table",
852
+ "last_run_metadata_path" => Stud::Temporary.pathname,
853
+ "clean_run" => true
854
+ }
855
+ end
856
+
857
+ let(:last_run_time) { Time.at(1).utc }
858
+
859
+ before do
860
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_time))
861
+ plugin.register
862
+ end
863
+
864
+ after do
865
+ plugin.stop
866
+ end
867
+
868
+ it "should ignore last run metadata if :clean_run set to true" do
869
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(Time.at(0).utc)
870
+ end
871
+ end
872
+
873
+ context "when doing a clean run (by value)" do
874
+
875
+ let(:settings) do
876
+ {
877
+ "statement" => "SELECT * FROM test_table",
878
+ "last_run_metadata_path" => Stud::Temporary.pathname,
879
+ "use_column_value" => true,
880
+ "tracking_column" => "num_param",
881
+ "clean_run" => true
882
+ }
883
+ end
884
+
885
+ let(:last_run_value) { 1000 }
886
+
887
+ before do
888
+ File.write(settings["last_run_metadata_path"], YAML.dump(last_run_value))
889
+ plugin.register
890
+ end
891
+
892
+ after do
893
+ plugin.stop
894
+ end
895
+
896
+ it "should ignore last run metadata if :clean_run set to true" do
897
+ expect(plugin.instance_variable_get("@value_tracker").value).to eq(0)
898
+ end
899
+ end
900
+
901
+
902
+ context "when state is not to be persisted" do
903
+ let(:settings) do
904
+ {
905
+ "statement" => "SELECT * FROM test_table",
906
+ "last_run_metadata_path" => Stud::Temporary.pathname,
907
+ "record_last_run" => false
908
+ }
909
+ end
910
+
911
+ before do
912
+ plugin.register
913
+ end
914
+
915
+ after do
916
+ plugin.stop
917
+ end
918
+
919
+ it "should not save state if :record_last_run is false" do
920
+ expect(File).not_to exist(settings["last_run_metadata_path"])
921
+ end
922
+ end
923
+
924
+ context "when setting fetch size" do
925
+
926
+ let(:settings) do
927
+ {
928
+ "statement" => "SELECT * from test_table",
929
+ "jdbc_fetch_size" => 1
930
+ }
931
+ end
932
+
933
+ let(:num_rows) { 10 }
934
+
935
+ before do
936
+ num_rows.times do
937
+ db[:test_table].insert(:num => 1, :custom_time => Time.now.utc, :created_at => Time.now.utc)
938
+ end
939
+
940
+ plugin.register
941
+ end
942
+
943
+ after do
944
+ plugin.stop
945
+ end
946
+
947
+ it "should fetch all rows" do
948
+ plugin.run(queue)
949
+ expect(queue.size).to eq(num_rows)
950
+ end
951
+ end
952
+
953
+ context "when driver is not found" do
954
+ let(:settings) { { "statement" => "SELECT * FROM test_table" } }
955
+
956
+ before do
957
+ mixin_settings['jdbc_driver_class'] = "org.not.ExistsDriver"
958
+ end
959
+
960
+ it "should fail" do
961
+ expect do
962
+ plugin.register
963
+ plugin.run(queue) # load when first run
964
+ end.to raise_error(LogStash::PluginLoadingError)
965
+ end
966
+ end
967
+
968
+ context "when timing out on connection" do
969
+ let(:settings) do
970
+ {
971
+ "statement" => "SELECT * FROM test_table",
972
+ "jdbc_pool_timeout" => 0,
973
+ "jdbc_connection_string" => 'mock://localhost:1527/db',
974
+ "sequel_opts" => {
975
+ "max_connections" => 1
976
+ }
977
+ }
978
+ end
979
+
980
+ it "should raise PoolTimeout error" do
981
+ plugin.register
982
+ plugin.run(queue)
983
+ db = plugin.instance_variable_get(:@database)
984
+ expect(db.pool.instance_variable_get(:@timeout)).to eq(0)
985
+ expect(db.pool.instance_variable_get(:@max_size)).to eq(1)
986
+
987
+ q, q1 = Queue.new, Queue.new
988
+ t = Thread.new{db.pool.hold{|c| q1.push nil; q.pop}}
989
+ q1.pop
990
+ expect{db.pool.hold {|c|}}.to raise_error(Sequel::PoolTimeout)
991
+ q.push nil
992
+ t.join
993
+ end
994
+
995
+ it "should log error message" do
996
+ allow(Sequel).to receive(:connect).and_raise(Sequel::PoolTimeout)
997
+ expect(plugin.logger).to receive(:error).with("Failed to connect to database. 0 second timeout exceeded. Tried 1 times.")
998
+ expect do
999
+ plugin.register
1000
+ plugin.run(queue)
1001
+ end.to raise_error(Sequel::PoolTimeout)
1002
+ end
1003
+ end
1004
+
1005
+ context "when using logging" do
1006
+
1007
+ let(:settings) do
1008
+ {
1009
+ "statement" => "SELECT * from test_table", "sql_log_level" => "debug"
1010
+ }
1011
+ end
1012
+
1013
+ let(:num_rows) { 5 }
1014
+
1015
+ before do
1016
+ allow(plugin.logger).to receive(:debug?)
1017
+ num_rows.times do
1018
+ db[:test_table].insert(:num => 1)
1019
+ end
1020
+
1021
+ plugin.register
1022
+ end
1023
+
1024
+ after do
1025
+ plugin.stop
1026
+ end
1027
+
1028
+ it "should report the statements to logging" do
1029
+ expect(plugin.logger).to receive(:debug).once
1030
+ plugin.run(queue)
1031
+ end
1032
+ end
1033
+
1034
+ describe "config option lowercase_column_names behaviour" do
1035
+ let(:settings) { { "statement" => "SELECT * FROM ttt" } }
1036
+ let(:events) { [] }
1037
+
1038
+ before do
1039
+ db.create_table(:ttt) do
1040
+ Integer(:num)
1041
+ String(:somestring)
1042
+ end
1043
+ db[:ttt].insert(:num => 42, :somestring => "This is a string")
1044
+ plugin.register
1045
+ end
1046
+
1047
+ after do
1048
+ plugin.stop
1049
+ db.drop_table(:ttt)
1050
+ end
1051
+
1052
+ context "when lowercase_column_names is on (default)" do
1053
+ it "the field names are lower case" do
1054
+ plugin.run(events)
1055
+ expect(events.first.to_hash.keys.sort).to eq(
1056
+ ["@timestamp", "@version","num", "somestring"])
1057
+ end
1058
+ end
1059
+
1060
+ context "when lowercase_column_names is off" do
1061
+ let(:settings) { { "statement" => "SELECT * FROM ttt", "lowercase_column_names" => false } }
1062
+ it "the field names are UPPER case (natural for Derby DB)" do
1063
+ plugin.run(events)
1064
+ expect(events.first.to_hash.keys.sort).to eq(
1065
+ ["@timestamp", "@version","NUM", "SOMESTRING"])
1066
+ end
1067
+ end
1068
+ end
1069
+
1070
+ context "when specifying connection_retry_attempts" do
1071
+ let(:settings) { {"statement" => "SELECT 1 as col1 FROM test_table"} }
1072
+
1073
+ it "should try to connect connection_retry_attempts times" do
1074
+ mixin_settings['connection_retry_attempts'] = 2
1075
+ mixin_settings['jdbc_pool_timeout'] = 0
1076
+ allow(Sequel).to receive(:connect).and_raise(Sequel::PoolTimeout)
1077
+ expect(plugin.logger).to receive(:error).with("Failed to connect to database. 0 second timeout exceeded. Trying again.")
1078
+ expect(plugin.logger).to receive(:error).with("Failed to connect to database. 0 second timeout exceeded. Tried 2 times.")
1079
+ expect do
1080
+ plugin.register
1081
+ plugin.run(queue)
1082
+ end.to raise_error(Sequel::PoolTimeout)
1083
+ end
1084
+
1085
+ it "should not fail when passed a non-positive value" do
1086
+ mixin_settings['connection_retry_attempts'] = -2
1087
+ expect { plugin.register }.to_not raise_error
1088
+ plugin.stop
1089
+ end
1090
+ end
1091
+
1092
+ context "when encoding of some columns need to be changed" do
1093
+
1094
+ let(:settings) {{ "statement" => "SELECT * from test_table" }}
1095
+ let(:events) { [] }
1096
+ let(:row) do
1097
+ {
1098
+ "column0" => "foo",
1099
+ "column1" => "bar".force_encoding(Encoding::ISO_8859_1),
1100
+ "column2" => 3
1101
+ }
1102
+ end
1103
+
1104
+ before(:each) do
1105
+ dataset = double("Dataset")
1106
+ allow(dataset).to receive(:each).and_yield(row)
1107
+ allow(plugin).to receive(:jdbc_connect).and_wrap_original do |m, *args|
1108
+ _db = m.call(*args)
1109
+ allow(_db).to receive(:[]).and_return(dataset)
1110
+ _db
1111
+ end
1112
+ # allow_any_instance_of(Sequel::JDBC::Derby::Dataset).to receive(:each).and_yield(row)
1113
+ plugin.register
1114
+ end
1115
+
1116
+ after(:each) do
1117
+ plugin.stop
1118
+ end
1119
+
1120
+ it "should not convert any column by default" do
1121
+ encoded_row = {
1122
+ "column0" => "foo",
1123
+ "column1" => "bar".force_encoding(Encoding::ISO_8859_1),
1124
+ "column2" => 3
1125
+ }
1126
+ event = LogStash::Event.new(row)
1127
+ expect(LogStash::Event).to receive(:new) do |row|
1128
+ row.each do |k, v|
1129
+ next unless v.is_a?(String)
1130
+ expect(row[k].encoding).to eq(encoded_row[k].encoding)
1131
+ end
1132
+
1133
+ event
1134
+ end
1135
+ plugin.run(events)
1136
+ end
1137
+
1138
+ context "when all string columns should be encoded" do
1139
+
1140
+ let(:settings) do
1141
+ {
1142
+ "statement" => "SELECT * from test_table",
1143
+ "charset" => "ISO-8859-1"
1144
+ }
1145
+ end
1146
+
1147
+ let(:row) do
1148
+ {
1149
+ "column0" => "foo".force_encoding(Encoding::ISO_8859_1),
1150
+ "column1" => "bar".force_encoding(Encoding::ISO_8859_1),
1151
+ "column2" => 3
1152
+ }
1153
+ end
1154
+
1155
+ it "should transform all column string to UTF-8, default encoding" do
1156
+ encoded_row = {
1157
+ "column0" => "foo",
1158
+ "column1" => "bar",
1159
+ "column2" => 3
1160
+ }
1161
+ event = LogStash::Event.new(row)
1162
+ expect(LogStash::Event).to receive(:new) do |row|
1163
+ row.each do |k, v|
1164
+ next unless v.is_a?(String)
1165
+ expect(row[k].encoding).to eq(encoded_row[k].encoding)
1166
+ end
1167
+
1168
+ event
1169
+ end
1170
+ plugin.run(events)
1171
+ end
1172
+ end
1173
+
1174
+ context "when only an specific column should be converted" do
1175
+
1176
+ let(:settings) do
1177
+ {
1178
+ "statement" => "SELECT * from test_table",
1179
+ "columns_charset" => { "column1" => "ISO-8859-1" }
1180
+ }
1181
+ end
1182
+
1183
+ let(:row) do
1184
+ {
1185
+ "column0" => "foo",
1186
+ "column1" => "bar".force_encoding(Encoding::ISO_8859_1),
1187
+ "column2" => 3,
1188
+ "column3" => "berlin".force_encoding(Encoding::ASCII_8BIT)
1189
+ }
1190
+ end
1191
+
1192
+ it "should only convert the selected column" do
1193
+ encoded_row = {
1194
+ "column0" => "foo",
1195
+ "column1" => "bar",
1196
+ "column2" => 3,
1197
+ "column3" => "berlin".force_encoding(Encoding::ASCII_8BIT)
1198
+ }
1199
+ event = LogStash::Event.new(row)
1200
+ expect(LogStash::Event).to receive(:new) do |row|
1201
+ row.each do |k, v|
1202
+ next unless v.is_a?(String)
1203
+ expect(row[k].encoding).to eq(encoded_row[k].encoding)
1204
+ end
1205
+
1206
+ event
1207
+ end
1208
+ plugin.run(events)
1209
+ end
1210
+ end
1211
+ end
1212
+
1213
+ context "when fetching Various Typed data" do
1214
+
1215
+ let(:settings) do
1216
+ {
1217
+ "statement" => "SELECT * from types_table"
1218
+ }
1219
+ end
1220
+
1221
+ before do
1222
+ db << "INSERT INTO types_table (num, string, started_at, custom_time, ranking) VALUES (1, 'A test', '1999-12-31', '1999-12-31 23:59:59', 95.67)"
1223
+
1224
+ plugin.register
1225
+ end
1226
+
1227
+ after do
1228
+ plugin.stop
1229
+ end
1230
+
1231
+ it "should convert all columns to valid Event acceptable data types" do
1232
+ plugin.run(queue)
1233
+ event = queue.pop
1234
+ expect(event.get("num")).to eq(1)
1235
+ expect(event.get("string")).to eq("A test")
1236
+ expect(event.get("started_at")).to be_a(LogStash::Timestamp)
1237
+ expect(event.get("started_at").to_s).to eq("1999-12-31T00:00:00.000Z")
1238
+ expect(event.get("custom_time")).to be_a(LogStash::Timestamp)
1239
+ expect(event.get("custom_time").to_s).to eq("1999-12-31T23:59:59.000Z")
1240
+ expect(event.get("ranking").to_f).to eq(95.67)
1241
+ end
1242
+ end
1243
+
1244
+ context "when debug logging and a count query raises a count related error" do
1245
+ let(:settings) do
1246
+ { "statement" => "SELECT * from types_table" }
1247
+ end
1248
+ let(:logger) { double("logger", :debug? => true) }
1249
+ let(:statement_logger) { LogStash::PluginMixins::Jdbc::CheckedCountLogger.new(logger) }
1250
+ let(:value_tracker) { double("value tracker", :set_value => nil, :write => nil) }
1251
+ let(:msg) { 'Java::JavaSql::SQLSyntaxErrorException: Dynamic SQL Error; SQL error code = -104; Token unknown - line 1, column 105; LIMIT [SQLState:42000, ISC error code:335544634]' }
1252
+ let(:error_args) do
1253
+ {"exception" => msg}
1254
+ end
1255
+
1256
+ before do
1257
+ db << "INSERT INTO types_table (num, string, started_at, custom_time, ranking) VALUES (1, 'A test', '1999-12-31', '1999-12-31 23:59:59', 95.67)"
1258
+ plugin.register
1259
+ plugin.set_statement_logger(statement_logger)
1260
+ plugin.set_value_tracker(value_tracker)
1261
+ allow(value_tracker).to receive(:value).and_return("bar")
1262
+ allow(statement_logger).to receive(:execute_count).once.and_raise(StandardError.new(msg))
1263
+ end
1264
+
1265
+ after do
1266
+ plugin.stop
1267
+ end
1268
+
1269
+ context "if the count query raises an error" do
1270
+ it "should log a debug line without a count key as its unknown whether a count works at this stage" do
1271
+ expect(logger).to receive(:warn).once.with("Attempting a count query raised an error, the generated count statement is most likely incorrect but check networking, authentication or your statement syntax", error_args)
1272
+ expect(logger).to receive(:warn).once.with("Ongoing count statement generation is being prevented")
1273
+ expect(logger).to receive(:debug).once.with("Executing JDBC query", :statement => settings["statement"], :parameters => {:sql_last_value=>"bar"})
1274
+ plugin.run(queue)
1275
+ queue.pop
1276
+ end
1277
+
1278
+ it "should create an event normally" do
1279
+ allow(logger).to receive(:warn)
1280
+ allow(logger).to receive(:debug)
1281
+ plugin.run(queue)
1282
+ event = queue.pop
1283
+ expect(event.get("num")).to eq(1)
1284
+ expect(event.get("string")).to eq("A test")
1285
+ expect(event.get("started_at")).to be_a(LogStash::Timestamp)
1286
+ expect(event.get("started_at").to_s).to eq("1999-12-31T00:00:00.000Z")
1287
+ expect(event.get("custom_time")).to be_a(LogStash::Timestamp)
1288
+ expect(event.get("custom_time").to_s).to eq("1999-12-31T23:59:59.000Z")
1289
+ expect(event.get("ranking").to_f).to eq(95.67)
1290
+ end
1291
+ end
1292
+ end
1293
+
1294
+ context "when using prepared statements" do
1295
+ let(:last_run_value) { 250 }
1296
+ let(:expected_queue_size) { 100 }
1297
+ let(:num_rows) { 1000 }
1298
+
1299
+ context "check validation" do
1300
+ context "with an empty name setting" do
1301
+ let(:settings) do
1302
+ {
1303
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT ? ROWS ONLY",
1304
+ "prepared_statement_bind_values" => [100],
1305
+ "use_prepared_statements" => true,
1306
+ }
1307
+ end
1308
+
1309
+ it "should fail to register" do
1310
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
1311
+ end
1312
+ end
1313
+
1314
+ context "with an mismatched placeholder vs bind values" do
1315
+ let(:settings) do
1316
+ {
1317
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT ? ROWS ONLY",
1318
+ "prepared_statement_bind_values" => [],
1319
+ "use_prepared_statements" => true,
1320
+ }
1321
+ end
1322
+
1323
+ it "should fail to register" do
1324
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
1325
+ end
1326
+ end
1327
+
1328
+ context "with jdbc paging enabled" do
1329
+ let(:settings) do
1330
+ {
1331
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT 100 ROWS ONLY",
1332
+ "prepared_statement_bind_values" => [],
1333
+ "prepared_statement_name" => "pstmt_test_without",
1334
+ "use_prepared_statements" => true,
1335
+ "jdbc_paging_enabled" => true,
1336
+ "jdbc_page_size" => 20,
1337
+ "jdbc_fetch_size" => 10
1338
+ }
1339
+ end
1340
+
1341
+ it "should fail to register" do
1342
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
1343
+ end
1344
+ end
1345
+
1346
+ end
1347
+
1348
+ context "and no validation failures" do
1349
+ before do
1350
+ ::File.write(settings["last_run_metadata_path"], YAML.dump(last_run_value))
1351
+ num_rows.times do |n|
1352
+ db[:test_table].insert(:num => n.succ, :string => SecureRandom.hex(8), :custom_time => Time.now.utc, :created_at => Time.now.utc)
1353
+ end
1354
+ end
1355
+
1356
+ after do
1357
+ plugin.stop
1358
+ end
1359
+
1360
+ context "with jdbc paging enabled" do
1361
+ let(:settings) do
1362
+ {
1363
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT 100 ROWS ONLY",
1364
+ "prepared_statement_bind_values" => [],
1365
+ "prepared_statement_name" => "pstmt_test_without",
1366
+ "use_prepared_statements" => true,
1367
+ "tracking_column_type" => "numeric",
1368
+ "tracking_column" => "num",
1369
+ "use_column_value" => true,
1370
+ "last_run_metadata_path" => Stud::Temporary.pathname,
1371
+ "jdbc_paging_enabled" => true,
1372
+ "jdbc_page_size" => 20,
1373
+ "jdbc_fetch_size" => 10
1374
+ }
1375
+ end
1376
+
1377
+ it "should fail to register" do
1378
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
1379
+ end
1380
+ end
1381
+
1382
+ context "without placeholder and bind parameters" do
1383
+ let(:settings) do
1384
+ {
1385
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT 100 ROWS ONLY",
1386
+ "prepared_statement_bind_values" => [],
1387
+ "prepared_statement_name" => "pstmt_test_without",
1388
+ "use_prepared_statements" => true,
1389
+ "tracking_column_type" => "numeric",
1390
+ "tracking_column" => "num",
1391
+ "use_column_value" => true,
1392
+ "last_run_metadata_path" => Stud::Temporary.pathname
1393
+ }
1394
+ end
1395
+
1396
+ it "should fetch some rows" do
1397
+ plugin.register
1398
+ plugin.run(queue)
1399
+
1400
+ expect(queue.size).to eq(expected_queue_size)
1401
+ expect(YAML.load(File.read(settings["last_run_metadata_path"]))).to eq(expected_queue_size)
1402
+ end
1403
+ end
1404
+
1405
+
1406
+ context "with bind parameters" do
1407
+ let(:settings) do
1408
+ {
1409
+ # Sadly, postgres does but derby doesn't - It is not allowed for both operands of '+' to be ? parameters.: PREPARE pstmt_test: SELECT * FROM test_table WHERE (num > ?) AND (num <= ? + ?) ORDER BY num
1410
+ "statement" => "SELECT * FROM test_table WHERE (num > ?) ORDER BY num FETCH NEXT ? ROWS ONLY",
1411
+ "prepared_statement_bind_values" => [":sql_last_value", expected_queue_size],
1412
+ "prepared_statement_name" => "pstmt_test_with",
1413
+ "use_prepared_statements" => true,
1414
+ "tracking_column_type" => "numeric",
1415
+ "tracking_column" => "num",
1416
+ "use_column_value" => true,
1417
+ "last_run_metadata_path" => Stud::Temporary.pathname
1418
+ }
1419
+ end
1420
+
1421
+ it "should fetch some rows" do
1422
+ plugin.register
1423
+ plugin.run(queue)
1424
+
1425
+ expect(queue.size).to eq(expected_queue_size)
1426
+ expect(YAML.load(File.read(settings["last_run_metadata_path"]))).to eq(last_run_value + expected_queue_size)
1427
+ end
1428
+ end
1429
+ end
1430
+ end
1431
+ end