logstash-integration-jdbc 5.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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