logstash-input-jdbc 4.3.14 → 4.3.16

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da0726e9534ffb2d10744efb977233487a21fb9e05e047294d5d2349e5a789fc
4
- data.tar.gz: 1ea1948f5846b04ef7ecff9c9088f6d1d8c823ac38eceb07b0d83641be01666d
3
+ metadata.gz: 05a9c38896f4f81325e5284a4cd46085105ecc31d5339526a67a42ff668d8c41
4
+ data.tar.gz: b8d46982d68047d258d0587f66e53cc324c419e8a97952b75db717894e453c24
5
5
  SHA512:
6
- metadata.gz: 1c5ea4dbfba07fb58625b7163740769c36b0288b8dae3a8877dc5feb2af089c71c8fc39982f17f48268bf30b290adf3f5ee942bfe7cba5bbc8f25157c538052b
7
- data.tar.gz: a2d8a17d3f33a9c60fff84376566d909d2b29506a7b257c65ec739e9d1fcf953e832d039f9806698dcc34362281264c4f64b460cef41966ca95567996991cc39
6
+ metadata.gz: b6ab529a8eac82086d7518a1cb282189c1ad96faf138637ab168889030f09c65a6159bfc0e79eaf8c2fff8806b0e71bec46dba15ba81fe08151ac3173953202b
7
+ data.tar.gz: e0071180f548ceac30f79b18e28b3804c29e559c873bf4a9d45bb11b61736f7a583fbc5a9e53b11d46ed58c3e3686e50de218db89535e231ba5a631cb0b3a941
@@ -1,3 +1,14 @@
1
+ ## 4.3.16
2
+ - Add support for prepared statements [Issue 233](https://github.com/logstash-plugins/logstash-input-jdbc/issues/233)
3
+
4
+ ## 4.3.15
5
+ - Use atomic booleam to load drivers once
6
+ - Added CHANGELOG entries
7
+
8
+ ## 4.3.14
9
+ - Added support for driver loading in JDK 9+ [Issue 331](https://github.com/logstash-plugins/logstash-input-jdbc/issues/331)
10
+ - Gem released without CHANGELOG additions
11
+
1
12
  ## 4.3.13
2
13
  - Changed documentation to generalize the PATH location [#297](https://github.com/logstash-plugins/logstash-input-jdbc/pull/297)
3
14
 
@@ -140,6 +140,35 @@ input {
140
140
  }
141
141
  ---------------------------------------------------------------------------------------------------
142
142
 
143
+ ==== Prepared Statements
144
+
145
+ Using server side prepared statements can speed up execution times as the server optimises the query plan and execution.
146
+
147
+ NOTE: Not all JDBC accessible technologies will support prepared statements.
148
+
149
+ With the introduction of Prepared Statement support comes a different code execution path and some new settings. Most of the existing settings are still useful but there are several new settings for Prepared Statements to read up on.
150
+ Use the boolean setting `use_prepared_statements` to enable this execution mode. Use the `prepared_statement_name` setting to specify a name for the Prepared Statement, this identifies the prepared statement locally and remotely and it should be unique in your config and on the database. Use the `prepared_statement_bind_values` array setting to specify the bind values, use the exact string `:sql_last_value` (multiple times if necessary) for the predefined parameter mentioned before. The `statement` (or `statement_path`) setting still holds the SQL statement but to use bind variables you must use the `?` character as a placeholder in the exact order found in the `prepared_statement_bind_values` array.
151
+
152
+ NOTE: Building count queries around a prepared statement is not supported at this time and because jdbc paging uses count queries under the hood, jdbc paging is not supported with prepared statements at this time either. Therefore, `jdbc_paging_enabled`, `jdbc_page_size` settings are ignored when using prepared statements.
153
+
154
+ Example:
155
+ [source,ruby]
156
+ ---------------------------------------------------------------------------------------------------
157
+ input {
158
+ jdbc {
159
+ statement => "SELECT * FROM mgd.seq_sequence WHERE _sequence_key > ? AND _sequence_key < ? + ? ORDER BY _sequence_key ASC"
160
+ prepared_statement_bind_values => [":sql_last_value", ":sql_last_value", 4]
161
+ prepared_statement_name => "foobar"
162
+ use_prepared_statements => true
163
+ use_column_value => true
164
+ tracking_column_type => "numeric"
165
+ tracking_column => "_sequence_key"
166
+ last_run_metadata_path => "/elastic/tmp/testing/confs/test-jdbc-int-sql_last_value.yml"
167
+ # ... other configuration bits
168
+ }
169
+ }
170
+ ---------------------------------------------------------------------------------------------------
171
+
143
172
 
144
173
  [id="plugins-{type}s-{plugin}-options"]
145
174
  ==== Jdbc Input Configuration Options
@@ -149,7 +178,6 @@ This plugin supports the following configuration options plus the <<plugins-{typ
149
178
  [cols="<,<,<",options="header",]
150
179
  |=======================================================================
151
180
  |Setting |Input type|Required
152
- | <<plugins-{type}s-{plugin}-plugin_timezone>> |<<string,string>>, one of `["local", "utc"]`|No
153
181
  | <<plugins-{type}s-{plugin}-clean_run>> |<<boolean,boolean>>|No
154
182
  | <<plugins-{type}s-{plugin}-columns_charset>> |<<hash,hash>>|No
155
183
  | <<plugins-{type}s-{plugin}-connection_retry_attempts>> |<<number,number>>|No
@@ -170,6 +198,9 @@ This plugin supports the following configuration options plus the <<plugins-{typ
170
198
  | <<plugins-{type}s-{plugin}-last_run_metadata_path>> |<<string,string>>|No
171
199
  | <<plugins-{type}s-{plugin}-lowercase_column_names>> |<<boolean,boolean>>|No
172
200
  | <<plugins-{type}s-{plugin}-parameters>> |<<hash,hash>>|No
201
+ | <<plugins-{type}s-{plugin}-plugin_timezone>> |<<string,string>>, one of `["local", "utc"]`|No
202
+ | <<plugins-{type}s-{plugin}-prepared_statement_bind_values>> |<<array,array>>|No
203
+ | <<plugins-{type}s-{plugin}-prepared_statement_name>> |<<string,string>>|No
173
204
  | <<plugins-{type}s-{plugin}-record_last_run>> |<<boolean,boolean>>|No
174
205
  | <<plugins-{type}s-{plugin}-schedule>> |<<string,string>>|No
175
206
  | <<plugins-{type}s-{plugin}-sequel_opts>> |<<hash,hash>>|No
@@ -179,6 +210,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
179
210
  | <<plugins-{type}s-{plugin}-tracking_column>> |<<string,string>>|No
180
211
  | <<plugins-{type}s-{plugin}-tracking_column_type>> |<<string,string>>, one of `["numeric", "timestamp"]`|No
181
212
  | <<plugins-{type}s-{plugin}-use_column_value>> |<<boolean,boolean>>|No
213
+ | <<plugins-{type}s-{plugin}-use_prepared_statements>> |<<boolean,boolean>>|No
182
214
  |=======================================================================
183
215
 
184
216
  Also see <<plugins-{type}s-{plugin}-common-options>> for a list of options supported by all
@@ -284,7 +316,7 @@ database timezone is UTC then you do not need to set either of these settings.
284
316
  * Value type is <<string,string>>
285
317
  * There is no default value for this setting.
286
318
 
287
- JDBC driver class to load, for exmaple, "org.apache.derby.jdbc.ClientDriver"
319
+ JDBC driver class to load, for example, "org.apache.derby.jdbc.ClientDriver"
288
320
  NB per https://github.com/logstash-plugins/logstash-input-jdbc/issues/43 if you are using
289
321
  the Oracle JDBC driver (ojdbc6.jar) the correct `jdbc_driver_class` is `"Java::oracle.jdbc.driver.OracleDriver"`
290
322
 
@@ -409,6 +441,22 @@ Whether to force the lowercasing of identifier fields
409
441
 
410
442
  Hash of query parameter, for example `{ "target_id" => "321" }`
411
443
 
444
+ [id="plugins-{type}s-{plugin}-prepared_statement_bind_values"]
445
+ ===== `prepared_statement_bind_values`
446
+
447
+ * Value type is <<array,array>>
448
+ * Default value is `[]`
449
+
450
+ Array of bind values for the prepared statement. `:sql_last_value` is a reserved predefined string
451
+
452
+ [id="plugins-{type}s-{plugin}-prepared_statement_name"]
453
+ ===== `prepared_statement_name`
454
+
455
+ * Value type is <<string,string>>
456
+ * Default value is `""`
457
+
458
+ Name given to the prepared statement. It must be unique in your config and in the database
459
+
412
460
  [id="plugins-{type}s-{plugin}-record_last_run"]
413
461
  ===== `record_last_run`
414
462
 
@@ -506,6 +554,13 @@ When set to `true`, uses the defined
506
554
  <<plugins-{type}s-{plugin}-tracking_column>> value as the `:sql_last_value`. When set
507
555
  to `false`, `:sql_last_value` reflects the last time the query was executed.
508
556
 
557
+ [id="plugins-{type}s-{plugin}-use_prepared_statements"]
558
+ ===== `use_prepared_statements`
559
+
560
+ * Value type is <<boolean,boolean>>
561
+ * Default value is `false`
562
+
563
+ When set to `true`, enables prepare statement usage
509
564
 
510
565
  [id="plugins-{type}s-{plugin}-common-options"]
511
566
  include::{include_path}/{type}.asciidoc[]
@@ -201,6 +201,12 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
201
201
  # this will only convert column0 that has ISO-8859-1 as an original encoding.
202
202
  config :columns_charset, :validate => :hash, :default => {}
203
203
 
204
+ config :use_prepared_statements, :validate => :boolean, :default => false
205
+
206
+ config :prepared_statement_name, :validate => :string, :default => ""
207
+
208
+ config :prepared_statement_bind_values, :validate => :array, :default => []
209
+
204
210
  attr_reader :database # for test mocking/stubbing
205
211
 
206
212
  public
@@ -217,17 +223,25 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
217
223
  end
218
224
  end
219
225
 
220
- set_value_tracker(LogStash::PluginMixins::Jdbc::ValueTracking.build_last_value_tracker(self))
221
- set_statement_logger(LogStash::PluginMixins::Jdbc::CheckedCountLogger.new(@logger))
222
-
223
- @enable_encoding = !@charset.nil? || !@columns_charset.empty?
224
-
225
226
  unless @statement.nil? ^ @statement_filepath.nil?
226
227
  raise(LogStash::ConfigurationError, "Must set either :statement or :statement_filepath. Only one may be set at a time.")
227
228
  end
228
229
 
229
230
  @statement = ::File.read(@statement_filepath) if @statement_filepath
230
231
 
232
+ # must validate prepared statement mode after trying to read in from @statement_filepath
233
+ if @use_prepared_statements
234
+ validation_errors = validate_prepared_statement_mode
235
+ unless validation_errors.empty?
236
+ raise(LogStash::ConfigurationError, "Prepared Statement Mode validation errors: " + validation_errors.join(", "))
237
+ end
238
+ end
239
+
240
+ set_value_tracker(LogStash::PluginMixins::Jdbc::ValueTracking.build_last_value_tracker(self))
241
+ set_statement_logger(LogStash::PluginMixins::Jdbc::CheckedCountLogger.new(@logger))
242
+
243
+ @enable_encoding = !@charset.nil? || !@columns_charset.empty?
244
+
231
245
  if (@jdbc_password_filepath and @jdbc_password)
232
246
  raise(LogStash::ConfigurationError, "Only one of :jdbc_password, :jdbc_password_filepath may be set at a time.")
233
247
  end
@@ -248,7 +262,7 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
248
262
 
249
263
  # test injection points
250
264
  def set_statement_logger(instance)
251
- @statement_logger = instance
265
+ @statement_handler = LogStash::PluginMixins::Jdbc::StatementHandler.build_statement_handler(self, instance)
252
266
  end
253
267
 
254
268
  def set_value_tracker(instance)
@@ -275,10 +289,24 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
275
289
 
276
290
  private
277
291
 
292
+ def validate_prepared_statement_mode
293
+ error_messages = []
294
+ if @prepared_statement_name.empty?
295
+ error_messages << "must provide a name for the Prepared Statement, it must be unique for the db session"
296
+ end
297
+ if @statement.count("?") != @prepared_statement_bind_values.size
298
+ # mismatch in number of bind value elements to placeholder characters
299
+ error_messages << "there is a mismatch between the number of statement `?` placeholders and :prepared_statement_bind_values array setting elements"
300
+ end
301
+ if @jdbc_paging_enabled
302
+ # Pagination is not supported when using prepared statements
303
+ error_messages << "JDBC pagination cannot be used at this time"
304
+ end
305
+ error_messages
306
+ end
307
+
278
308
  def execute_query(queue)
279
- # update default parameters
280
- @parameters['sql_last_value'] = @value_tracker.value
281
- execute_statement(@statement, @parameters) do |row|
309
+ execute_statement do |row|
282
310
  if enable_encoding?
283
311
  ## do the necessary conversions to string elements
284
312
  row = Hash[row.map { |k, v| [k.to_s, convert(k, v)] }]
@@ -9,9 +9,14 @@ module LogStash module PluginMixins module Jdbc
9
9
  @in_debug = @logger.debug?
10
10
  end
11
11
 
12
- def log_statement_parameters(query, statement, parameters)
12
+ def disable_count
13
+ @needs_check = false
14
+ @count_is_supported = false
15
+ end
16
+
17
+ def log_statement_parameters(statement, parameters, query)
13
18
  return unless @in_debug
14
- check_count_query(query) if @needs_check
19
+ check_count_query(query) if @needs_check && query
15
20
  if @count_is_supported
16
21
  @logger.debug("Executing JDBC query", :statement => statement, :parameters => parameters, :count => execute_count(query))
17
22
  else
@@ -35,4 +40,4 @@ module LogStash module PluginMixins module Jdbc
35
40
  query.count
36
41
  end
37
42
  end
38
- end end end
43
+ end end end
@@ -6,6 +6,7 @@ require "date"
6
6
  require_relative "value_tracking"
7
7
  require_relative "checked_count_logger"
8
8
  require_relative "wrapped_driver"
9
+ require_relative "statement_handler"
9
10
 
10
11
  java_import java.util.concurrent.locks.ReentrantLock
11
12
 
@@ -171,22 +172,24 @@ module LogStash module PluginMixins module Jdbc
171
172
  require "sequel/adapters/jdbc"
172
173
 
173
174
  Sequel.application_timezone = @plugin_timezone.to_sym
174
-
175
- begin
176
- load_drivers
177
- Sequel::JDBC.load_driver(@jdbc_driver_class)
178
- rescue LogStash::Error => e
179
- # raised in load_drivers, e.cause should be the caught Java exceptions
180
- raise LogStash::PluginLoadingError, "#{e.message} and #{e.cause.message}"
181
- rescue Sequel::AdapterNotFound => e
182
- # fix this !!!
183
- message = if @jdbc_driver_library.nil?
184
- ":jdbc_driver_library is not set, are you sure you included
185
- the proper driver client libraries in your classpath?"
186
- else
187
- "Are you sure you've included the correct jdbc driver in :jdbc_driver_library?"
175
+ if @drivers_loaded.false?
176
+ begin
177
+ load_drivers
178
+ Sequel::JDBC.load_driver(@jdbc_driver_class)
179
+ rescue LogStash::Error => e
180
+ # raised in load_drivers, e.cause should be the caught Java exceptions
181
+ raise LogStash::PluginLoadingError, "#{e.message} and #{e.cause.message}"
182
+ rescue Sequel::AdapterNotFound => e
183
+ # fix this !!!
184
+ message = if @jdbc_driver_library.nil?
185
+ ":jdbc_driver_library is not set, are you sure you included
186
+ the proper driver client libraries in your classpath?"
187
+ else
188
+ "Are you sure you've included the correct jdbc driver in :jdbc_driver_library?"
189
+ end
190
+ raise LogStash::PluginLoadingError, "#{e}. #{message}"
188
191
  end
189
- raise LogStash::PluginLoadingError, "#{e}. #{message}"
192
+ @drivers_loaded.make_true
190
193
  end
191
194
  @database = jdbc_connect()
192
195
  @database.extension(:pagination)
@@ -226,6 +229,7 @@ module LogStash module PluginMixins module Jdbc
226
229
  public
227
230
  def prepare_jdbc_connection
228
231
  @connection_lock = ReentrantLock.new
232
+ @drivers_loaded = Concurrent::AtomicBoolean.new
229
233
  end
230
234
 
231
235
  public
@@ -243,18 +247,14 @@ module LogStash module PluginMixins module Jdbc
243
247
  end
244
248
 
245
249
  public
246
- def execute_statement(statement, parameters)
247
- # sql_last_value has been set in params by caller
250
+ def execute_statement
248
251
  success = false
249
252
  @connection_lock.lock
250
253
  open_jdbc_connection
251
254
  begin
252
- params = symbolized_params(parameters)
253
- query = @database[statement, params]
254
255
  sql_last_value = @use_column_value ? @value_tracker.value : Time.now.utc
255
256
  @tracking_column_warning_sent = false
256
- @statement_logger.log_statement_parameters(query, statement, params)
257
- perform_query(query) do |row|
257
+ @statement_handler.perform_query(@database, @value_tracker.value) do |row|
258
258
  sql_last_value = get_column_value(row) if @use_column_value
259
259
  yield extract_values_from(row)
260
260
  end
@@ -270,24 +270,6 @@ module LogStash module PluginMixins module Jdbc
270
270
  return success
271
271
  end
272
272
 
273
- # Performs the query, respecting our pagination settings, yielding once per row of data
274
- # @param query [Sequel::Dataset]
275
- # @yieldparam row [Hash{Symbol=>Object}]
276
- private
277
- def perform_query(query)
278
- if @jdbc_paging_enabled
279
- query.each_page(@jdbc_page_size) do |paged_dataset|
280
- paged_dataset.each do |row|
281
- yield row
282
- end
283
- end
284
- else
285
- query.each do |row|
286
- yield row
287
- end
288
- end
289
- end
290
-
291
273
  public
292
274
  def get_column_value(row)
293
275
  if !row.has_key?(@tracking_column.to_sym)
@@ -295,7 +277,7 @@ module LogStash module PluginMixins module Jdbc
295
277
  @logger.warn("tracking_column not found in dataset.", :tracking_column => @tracking_column)
296
278
  @tracking_column_warning_sent = true
297
279
  end
298
- # If we can't find the tracking column, return the current value_tracker value
280
+ # If we can't find the tracking column, return the current value in the ivar
299
281
  @value_tracker.value
300
282
  else
301
283
  # Otherwise send the updated tracking column
@@ -303,22 +285,8 @@ module LogStash module PluginMixins module Jdbc
303
285
  end
304
286
  end
305
287
 
306
- # Symbolize parameters keys to use with Sequel
307
- private
308
- def symbolized_params(parameters)
309
- parameters.inject({}) do |hash,(k,v)|
310
- case v
311
- when LogStash::Timestamp
312
- hash[k.to_sym] = v.time
313
- else
314
- hash[k.to_sym] = v
315
- end
316
- hash
317
- end
318
- end
319
-
320
288
  private
321
- #Stringify row keys and decorate values when necessary
289
+ #Stringify row keys and decorate values when necessary
322
290
  def extract_values_from(row)
323
291
  Hash[row.map { |k, v| [k.to_s, decorate_value(v)] }]
324
292
  end
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+
3
+ module LogStash module PluginMixins module Jdbc
4
+ class StatementHandler
5
+ def self.build_statement_handler(plugin, logger)
6
+ klass = plugin.use_prepared_statements ? PreparedStatementHandler : NormalStatementHandler
7
+ klass.new(plugin, logger)
8
+ end
9
+
10
+ attr_reader :statement, :parameters, :statement_logger
11
+
12
+ def initialize(plugin, statement_logger)
13
+ @statement = plugin.statement
14
+ @statement_logger = statement_logger
15
+ post_init(plugin)
16
+ end
17
+
18
+ def build_query(db, sql_last_value)
19
+ # override in subclass
20
+ end
21
+
22
+ def post_init(plugin)
23
+ # override in subclass, if needed
24
+ end
25
+ end
26
+
27
+ class NormalStatementHandler < StatementHandler
28
+ # Performs the query, respecting our pagination settings, yielding once per row of data
29
+ # @param db [Sequel::Database]
30
+ # @param sql_last_value [Integet|DateTime|Time]
31
+ # @yieldparam row [Hash{Symbol=>Object}]
32
+ def perform_query(db, sql_last_value)
33
+ query = build_query(db, sql_last_value)
34
+ if @jdbc_paging_enabled
35
+ query.each_page(@jdbc_page_size) do |paged_dataset|
36
+ paged_dataset.each do |row|
37
+ yield row
38
+ end
39
+ end
40
+ else
41
+ query.each do |row|
42
+ yield row
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def build_query(db, sql_last_value)
50
+ parameters[:sql_last_value] = sql_last_value
51
+ query = db[statement, parameters]
52
+ statement_logger.log_statement_parameters(statement, parameters, query)
53
+ query
54
+ end
55
+
56
+ def post_init(plugin)
57
+ @parameter_keys = ["sql_last_value"] + plugin.parameters.keys
58
+ @parameters = plugin.parameters.inject({}) do |hash,(k,v)|
59
+ case v
60
+ when LogStash::Timestamp
61
+ hash[k.to_sym] = v.time
62
+ else
63
+ hash[k.to_sym] = v
64
+ end
65
+ hash
66
+ end
67
+ end
68
+ end
69
+
70
+ class PreparedStatementHandler < StatementHandler
71
+ attr_reader :name, :bind_values_array, :statement_prepared, :prepared
72
+
73
+ # Performs the query, ignoring our pagination settings, yielding once per row of data
74
+ # @param db [Sequel::Database]
75
+ # @param sql_last_value [Integet|DateTime|Time]
76
+ # @yieldparam row [Hash{Symbol=>Object}]
77
+ def perform_query(db, sql_last_value)
78
+ query = build_query(db, sql_last_value)
79
+ query.each do |row|
80
+ yield row
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def build_query(db, sql_last_value)
87
+ @parameters = create_bind_values_hash
88
+ if statement_prepared.false?
89
+ prepended = parameters.keys.map{|v| v.to_s.prepend("$").to_sym}
90
+ @prepared = db[statement, *prepended].prepare(:select, name)
91
+ statement_prepared.make_true
92
+ end
93
+ # under the scheduler the Sequel database instance is recreated each time
94
+ # so the previous prepared statements are lost, add back
95
+ if db.prepared_statement(name).nil?
96
+ db.set_prepared_statement(name, prepared)
97
+ end
98
+ bind_value_sql_last_value(sql_last_value)
99
+ statement_logger.log_statement_parameters(statement, parameters, nil)
100
+ db.call(name, parameters)
101
+ end
102
+
103
+ def post_init(plugin)
104
+ # don't log statement count when using prepared statements for now...
105
+ # needs enhancement to allow user to supply a bindable count prepared statement in settings.
106
+ @statement_logger.disable_count
107
+
108
+ @name = plugin.prepared_statement_name.to_sym
109
+ @bind_values_array = plugin.prepared_statement_bind_values
110
+ @parameters = plugin.parameters
111
+ @statement_prepared = Concurrent::AtomicBoolean.new(false)
112
+ end
113
+
114
+ def create_bind_values_hash
115
+ hash = {}
116
+ bind_values_array.each_with_index {|v,i| hash[:"p#{i}"] = v}
117
+ hash
118
+ end
119
+
120
+ def bind_value_sql_last_value(sql_last_value)
121
+ parameters.keys.each do |key|
122
+ value = parameters[key]
123
+ if value == ":sql_last_value"
124
+ parameters[key] = sql_last_value
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end end end
@@ -5,28 +5,26 @@ module LogStash module PluginMixins module Jdbc
5
5
  class ValueTracking
6
6
 
7
7
  def self.build_last_value_tracker(plugin)
8
+ handler = plugin.record_last_run ? FileHandler.new(plugin.last_run_metadata_path) : NullFileHandler.new(plugin.last_run_metadata_path)
9
+ if plugin.record_last_run
10
+ handler = FileHandler.new(plugin.last_run_metadata_path)
11
+ end
12
+ if plugin.clean_run
13
+ handler.clean
14
+ end
15
+
8
16
  if plugin.use_column_value && plugin.tracking_column_type == "numeric"
9
17
  # use this irrespective of the jdbc_default_timezone setting
10
- klass = NumericValueTracker
18
+ NumericValueTracker.new(handler)
11
19
  else
12
20
  if plugin.jdbc_default_timezone.nil? || plugin.jdbc_default_timezone.empty?
13
21
  # no TZ stuff for Sequel, use Time
14
- klass = TimeValueTracker
22
+ TimeValueTracker.new(handler)
15
23
  else
16
24
  # Sequel does timezone handling on DateTime only
17
- klass = DateTimeValueTracker
25
+ DateTimeValueTracker.new(handler)
18
26
  end
19
27
  end
20
-
21
- handler = NullFileHandler.new(plugin.last_run_metadata_path)
22
- if plugin.record_last_run
23
- handler = FileHandler.new(plugin.last_run_metadata_path)
24
- end
25
- if plugin.clean_run
26
- handler.clean
27
- end
28
-
29
- instance = klass.new(handler)
30
28
  end
31
29
 
32
30
  attr_reader :value
@@ -51,6 +49,7 @@ module LogStash module PluginMixins module Jdbc
51
49
  private
52
50
  def common_set_initial(method_symbol, default)
53
51
  persisted = @file_handler.read
52
+
54
53
  if persisted && persisted.respond_to?(method_symbol)
55
54
  @value = persisted
56
55
  else
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-input-jdbc'
3
- s.version = '4.3.14'
3
+ s.version = '4.3.16'
4
4
  s.licenses = ['Apache License (2.0)']
5
5
  s.summary = "Creates events from JDBC data"
6
6
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -1310,4 +1310,142 @@ describe LogStash::Inputs::Jdbc do
1310
1310
  end
1311
1311
  end
1312
1312
  end
1313
+
1314
+ context "when using prepared statements" do
1315
+ let(:last_run_value) { 250 }
1316
+ let(:expected_queue_size) { 100 }
1317
+ let(:num_rows) { 1000 }
1318
+
1319
+ context "check validation" do
1320
+ context "with an empty name setting" do
1321
+ let(:settings) do
1322
+ {
1323
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT ? ROWS ONLY",
1324
+ "prepared_statement_bind_values" => [100],
1325
+ "use_prepared_statements" => true,
1326
+ }
1327
+ end
1328
+
1329
+ it "should fail to register" do
1330
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
1331
+ end
1332
+ end
1333
+
1334
+ context "with an mismatched placeholder vs bind values" do
1335
+ let(:settings) do
1336
+ {
1337
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT ? ROWS ONLY",
1338
+ "prepared_statement_bind_values" => [],
1339
+ "use_prepared_statements" => true,
1340
+ }
1341
+ end
1342
+
1343
+ it "should fail to register" do
1344
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
1345
+ end
1346
+ end
1347
+
1348
+ context "with jdbc paging enabled" do
1349
+ let(:settings) do
1350
+ {
1351
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT 100 ROWS ONLY",
1352
+ "prepared_statement_bind_values" => [],
1353
+ "prepared_statement_name" => "pstmt_test_without",
1354
+ "use_prepared_statements" => true,
1355
+ "jdbc_paging_enabled" => true,
1356
+ "jdbc_page_size" => 20,
1357
+ "jdbc_fetch_size" => 10
1358
+ }
1359
+ end
1360
+
1361
+ it "should fail to register" do
1362
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
1363
+ end
1364
+ end
1365
+
1366
+ end
1367
+
1368
+ context "and no validation failures" do
1369
+ before do
1370
+ ::File.write(settings["last_run_metadata_path"], YAML.dump(last_run_value))
1371
+ num_rows.times do |n|
1372
+ db[:test_table].insert(:num => n.succ, :string => SecureRandom.hex(8), :custom_time => Time.now.utc, :created_at => Time.now.utc)
1373
+ end
1374
+ end
1375
+
1376
+ after do
1377
+ plugin.stop
1378
+ end
1379
+
1380
+ context "with jdbc paging enabled" do
1381
+ let(:settings) do
1382
+ {
1383
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT 100 ROWS ONLY",
1384
+ "prepared_statement_bind_values" => [],
1385
+ "prepared_statement_name" => "pstmt_test_without",
1386
+ "use_prepared_statements" => true,
1387
+ "tracking_column_type" => "numeric",
1388
+ "tracking_column" => "num",
1389
+ "use_column_value" => true,
1390
+ "last_run_metadata_path" => Stud::Temporary.pathname,
1391
+ "jdbc_paging_enabled" => true,
1392
+ "jdbc_page_size" => 20,
1393
+ "jdbc_fetch_size" => 10
1394
+ }
1395
+ end
1396
+
1397
+ it "should fail to register" do
1398
+ expect{ plugin.register }.to raise_error(LogStash::ConfigurationError)
1399
+ end
1400
+ end
1401
+
1402
+ context "without placeholder and bind parameters" do
1403
+ let(:settings) do
1404
+ {
1405
+ "statement" => "SELECT * FROM test_table ORDER BY num FETCH NEXT 100 ROWS ONLY",
1406
+ "prepared_statement_bind_values" => [],
1407
+ "prepared_statement_name" => "pstmt_test_without",
1408
+ "use_prepared_statements" => true,
1409
+ "tracking_column_type" => "numeric",
1410
+ "tracking_column" => "num",
1411
+ "use_column_value" => true,
1412
+ "last_run_metadata_path" => Stud::Temporary.pathname
1413
+ }
1414
+ end
1415
+
1416
+ it "should fetch some rows" do
1417
+ plugin.register
1418
+ plugin.run(queue)
1419
+
1420
+ expect(queue.size).to eq(expected_queue_size)
1421
+ expect(YAML.load(File.read(settings["last_run_metadata_path"]))).to eq(expected_queue_size)
1422
+ end
1423
+ end
1424
+
1425
+
1426
+ context "with bind parameters" do
1427
+ let(:settings) do
1428
+ {
1429
+ # 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
1430
+ "statement" => "SELECT * FROM test_table WHERE (num > ?) ORDER BY num FETCH NEXT ? ROWS ONLY",
1431
+ "prepared_statement_bind_values" => [":sql_last_value", expected_queue_size],
1432
+ "prepared_statement_name" => "pstmt_test_with",
1433
+ "use_prepared_statements" => true,
1434
+ "tracking_column_type" => "numeric",
1435
+ "tracking_column" => "num",
1436
+ "use_column_value" => true,
1437
+ "last_run_metadata_path" => Stud::Temporary.pathname
1438
+ }
1439
+ end
1440
+
1441
+ it "should fetch some rows" do
1442
+ plugin.register
1443
+ plugin.run(queue)
1444
+
1445
+ expect(queue.size).to eq(expected_queue_size)
1446
+ expect(YAML.load(File.read(settings["last_run_metadata_path"]))).to eq(last_run_value + expected_queue_size)
1447
+ end
1448
+ end
1449
+ end
1450
+ end
1313
1451
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-jdbc
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.14
4
+ version: 4.3.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-29 00:00:00.000000000 Z
11
+ date: 2019-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -174,6 +174,7 @@ files:
174
174
  - lib/logstash/inputs/tzinfo_jruby_patch.rb
175
175
  - lib/logstash/plugin_mixins/jdbc/checked_count_logger.rb
176
176
  - lib/logstash/plugin_mixins/jdbc/jdbc.rb
177
+ - lib/logstash/plugin_mixins/jdbc/statement_handler.rb
177
178
  - lib/logstash/plugin_mixins/jdbc/value_tracking.rb
178
179
  - lib/logstash/plugin_mixins/jdbc/wrapped_driver.rb
179
180
  - logstash-input-jdbc.gemspec