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 +4 -4
- data/CHANGELOG.md +11 -0
- data/docs/index.asciidoc +57 -2
- data/lib/logstash/inputs/jdbc.rb +37 -9
- data/lib/logstash/plugin_mixins/jdbc/checked_count_logger.rb +8 -3
- data/lib/logstash/plugin_mixins/jdbc/jdbc.rb +23 -55
- data/lib/logstash/plugin_mixins/jdbc/statement_handler.rb +129 -0
- data/lib/logstash/plugin_mixins/jdbc/value_tracking.rb +12 -13
- data/logstash-input-jdbc.gemspec +1 -1
- data/spec/inputs/jdbc_spec.rb +138 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05a9c38896f4f81325e5284a4cd46085105ecc31d5339526a67a42ff668d8c41
|
4
|
+
data.tar.gz: b8d46982d68047d258d0587f66e53cc324c419e8a97952b75db717894e453c24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6ab529a8eac82086d7518a1cb282189c1ad96faf138637ab168889030f09c65a6159bfc0e79eaf8c2fff8806b0e71bec46dba15ba81fe08151ac3173953202b
|
7
|
+
data.tar.gz: e0071180f548ceac30f79b18e28b3804c29e559c873bf4a9d45bb11b61736f7a583fbc5a9e53b11d46ed58c3e3686e50de218db89535e231ba5a631cb0b3a941
|
data/CHANGELOG.md
CHANGED
@@ -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
|
|
data/docs/index.asciidoc
CHANGED
@@ -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
|
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[]
|
data/lib/logstash/inputs/jdbc.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
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
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
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
|
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
|
-
@
|
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
|
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
|
-
|
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
|
-
|
22
|
+
TimeValueTracker.new(handler)
|
15
23
|
else
|
16
24
|
# Sequel does timezone handling on DateTime only
|
17
|
-
|
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
|
data/logstash-input-jdbc.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'logstash-input-jdbc'
|
3
|
-
s.version = '4.3.
|
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"
|
data/spec/inputs/jdbc_spec.rb
CHANGED
@@ -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.
|
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-
|
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
|