logstash-integration-jdbc 5.6.3 → 5.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9afa5771b7e338b50c59cd6f2bd3d28fa23a5ac2df2804fedbde7f372d52c54f
4
- data.tar.gz: a50c4f2c24c347363f4398ef0fe8584c6db86fcf6ff9f1bafccdf82ef1989226
3
+ metadata.gz: 819be7f98076d1baa5ccf68299025f16441218c8a8fbde49818a4711ee153598
4
+ data.tar.gz: f746524e0a8e2ae44fbc4e817299165a174ffff09fadc987d6cfc21b62c43c08
5
5
  SHA512:
6
- metadata.gz: 76770166eb5ea2b20758e75d223998bc45d29b0fcc2e78864af6ca936db98c40075be88bbb9292c9473663d734c6be700e9ef5500dc7c5beaa03dcf078ec4a5a
7
- data.tar.gz: 3a91177b9028fa7e956c8b4b100bfc20ecd18ea5451e7c625f3d31cce6ae952fdb9c2eb724dbb767fa606dc88c93fec87a55ef4dff37eff0b2b394ab4c7ff232
6
+ metadata.gz: 8bf0523bfd1e0a687d8f3ec0833ba80c9e98524fd1992dede58195ec8562e94f5fda0f9c1afbf14e840480fd8e0196f92b0c22191b072721007f0ffb03b3a8b3
7
+ data.tar.gz: ee270f7fc7c07ce78ba08272d3407652dc59668ac50a251652ac4cf71ef0dda17fdb42cad43325477db04efc5d6e4331e695c93bc554ab7041ac46dc0ae27b29
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 5.6.5
2
+ - Prevent concurrent Sequel JDBC subadapter initialization races by preloading adapter at driver load time [#203](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/203)
3
+
4
+ ## 5.6.4
5
+ - Fix connection leak on statement retry by opening JDBC connection once outside retry loop [#201](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/201)
6
+
1
7
  ## 5.6.3
2
8
  - Fix: replace deprecated `File.exists?` with `File.exist?` for Ruby 3.4 (JRuby 10) compatibility [#192](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/192)
3
9
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  Logstash Integration Plugin for JDBC, including Logstash Input and Filter Plugins
3
3
  # Logstash Plugin
4
4
 
5
- [![Travis Build Status](https://travis-ci.com/logstash-plugins/logstash-integration-jdbc.svg)](https://travis-ci.com/logstash-plugins/logstash-integration-jdbc)
5
+ [![Unit Tests](https://github.com/logstash-plugins/logstash-integration-jdbc/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/logstash-plugins/logstash-integration-jdbc/actions/workflows/unit-tests.yml)
6
6
 
7
7
  This is a plugin for [Logstash](https://github.com/elastic/logstash).
8
8
 
@@ -32,6 +32,7 @@ module LogStash module PluginMixins module Jdbc
32
32
  # concurrency related problems with multiple pipelines and multiple drivers
33
33
  DRIVERS_LOADING_LOCK.lock()
34
34
  begin
35
+ preload_sequel_jdbc_subadapter
35
36
  load_driver_jars
36
37
  begin
37
38
  @driver_impl = load_jdbc_driver_class
@@ -50,6 +51,24 @@ module LogStash module PluginMixins module Jdbc
50
51
  end
51
52
  end
52
53
 
54
+ def preload_sequel_jdbc_subadapter
55
+ subadapter = jdbc_subadapter_scheme
56
+ return unless subadapter
57
+
58
+ # Ensure first-time Sequel jdbc/<subadapter> loading happens serially.
59
+ Sequel::Database.load_adapter(subadapter, :map => Sequel::JDBC::DATABASE_SETUP, :subdir => 'jdbc')
60
+ rescue Sequel::AdapterNotFound
61
+ # Some JDBC URLs can work without a dedicated Sequel sub-adapter.
62
+ @logger.debug("Skipping Sequel JDBC sub-adapter preload", :subadapter => subadapter, :jdbc_connection_string => @jdbc_connection_string)
63
+ nil
64
+ end
65
+
66
+ def jdbc_subadapter_scheme
67
+ return nil unless @jdbc_connection_string
68
+
69
+ @jdbc_connection_string[/\Ajdbc:([^:]+):/, 1]&.downcase&.to_sym
70
+ end
71
+
53
72
  def load_driver_jars
54
73
  if jdbc_driver_library_set?
55
74
  @jdbc_driver_library.split(",").each do |driver_jar|
@@ -160,6 +160,13 @@ module LogStash module PluginMixins module Jdbc
160
160
  logger.error('', e) # prints nested causes
161
161
  end
162
162
 
163
+ def log_jdbc_query_exception(e)
164
+ details = { exception: e.class, message: e.message }
165
+ details[:cause] = e.cause.inspect if e.cause
166
+ details[:backtrace] = e.backtrace if @logger.debug?
167
+ @logger.warn("Exception when executing JDBC query", details)
168
+ end
169
+
163
170
  def open_jdbc_connection
164
171
  @connection_lock.synchronize do
165
172
  # at this point driver is already loaded
@@ -222,30 +229,31 @@ module LogStash module PluginMixins module Jdbc
222
229
 
223
230
  @connection_lock.synchronize do
224
231
  begin
225
- retry_attempts -= 1
226
232
  open_jdbc_connection
227
- sql_last_value = @use_column_value ? @value_tracker.value : Time.now.utc
228
- @tracking_column_warning_sent = false
229
- @statement_handler.perform_query(@database, @value_tracker.value) do |row|
230
- sql_last_value = get_column_value(row) if @use_column_value
231
- yield extract_values_from(row)
232
- end
233
- success = true
234
- rescue Sequel::Error, Java::JavaSql::SQLException => e
235
- details = { exception: e.class, message: e.message }
236
- details[:cause] = e.cause.inspect if e.cause
237
- details[:backtrace] = e.backtrace if @logger.debug?
238
- @logger.warn("Exception when executing JDBC query", details)
239
-
240
- if retry_attempts == 0
241
- @logger.error("Unable to execute statement. Tried #{@statement_retry_attempts} times.")
233
+ begin
234
+ retry_attempts -= 1
235
+ sql_last_value = @use_column_value ? @value_tracker.value : Time.now.utc
236
+ @tracking_column_warning_sent = false
237
+ @statement_handler.perform_query(@database, @value_tracker.value) do |row|
238
+ sql_last_value = get_column_value(row) if @use_column_value
239
+ yield extract_values_from(row)
240
+ end
241
+ success = true
242
+ rescue Sequel::Error, Java::JavaSql::SQLException => e
243
+ log_jdbc_query_exception(e)
244
+
245
+ if retry_attempts == 0
246
+ @logger.error("Unable to execute statement. Tried #{@statement_retry_attempts} times.")
247
+ else
248
+ @logger.error("Unable to execute statement. Trying again.")
249
+ sleep(@statement_retry_attempts_wait_time)
250
+ retry
251
+ end
242
252
  else
243
- @logger.error("Unable to execute statement. Trying again.")
244
- sleep(@statement_retry_attempts_wait_time)
245
- retry
253
+ @value_tracker.set_value(sql_last_value)
246
254
  end
247
- else
248
- @value_tracker.set_value(sql_last_value)
255
+ rescue Sequel::Error, Java::JavaSql::SQLException => e
256
+ log_jdbc_query_exception(e)
249
257
  ensure
250
258
  close_jdbc_connection
251
259
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-integration-jdbc'
3
- s.version = '5.6.3'
3
+ s.version = ::File.read('version').split("\n").first
4
4
  s.licenses = ['Apache License (2.0)']
5
5
  s.summary = "Integration with JDBC - input and filter plugins"
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"
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.require_paths = ["lib", "vendor/jar-dependencies"]
11
11
 
12
12
  # Files
13
- s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "vendor/jar-dependencies/**/*.jar", "vendor/jar-dependencies/**/*.rb", "VERSION", "docs/**/*"]
13
+ s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "vendor/jar-dependencies/**/*.jar", "vendor/jar-dependencies/**/*.rb", "version", "docs/**/*"]
14
14
 
15
15
  # Tests
16
16
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
@@ -1310,7 +1310,8 @@ describe LogStash::Inputs::Jdbc do
1310
1310
  plugin.register
1311
1311
  plugin.run(queue)
1312
1312
  db = plugin.instance_variable_get(:@database)
1313
- expect(db.pool).to be_a_kind_of(::Sequel::ThreadedConnectionPool) # pries into internal details
1313
+ # Sequel defaults to TimedQueueConnectionPool on Ruby 3.2+ (JRuby 10+), ThreadedConnectionPool on older versions under the Sequel::ConnectionPool superclass
1314
+ expect(db.pool).to be_a_kind_of(::Sequel::ConnectionPool)
1314
1315
  expect(db.pool.instance_variable_get(:@timeout)).to eq(0)
1315
1316
  expect(db.pool.instance_variable_get(:@max_size)).to eq(1)
1316
1317
 
@@ -1447,6 +1448,58 @@ describe LogStash::Inputs::Jdbc do
1447
1448
  plugin.run(queue)
1448
1449
  plugin.stop
1449
1450
  end
1451
+
1452
+ it "opens the connection only once across statement retries" do
1453
+ mixin_settings['statement_retry_attempts'] = 3
1454
+ mixin_settings['statement_retry_attempts_wait_time'] = 0
1455
+ queue = Queue.new
1456
+ plugin.register
1457
+
1458
+ handler = plugin.instance_variable_get(:@statement_handler)
1459
+ allow(handler).to receive(:perform_query).and_raise(Sequel::PoolTimeout)
1460
+ allow(plugin.logger).to receive(:warn)
1461
+ allow(plugin.logger).to receive(:error)
1462
+
1463
+ expect(plugin).to receive(:open_jdbc_connection).once.and_call_original
1464
+
1465
+ plugin.run(queue)
1466
+ plugin.stop
1467
+ end
1468
+
1469
+ it "closes the connection after statement retries are exhausted" do
1470
+ mixin_settings['statement_retry_attempts'] = 2
1471
+ mixin_settings['statement_retry_attempts_wait_time'] = 0
1472
+ queue = Queue.new
1473
+ plugin.register
1474
+
1475
+ handler = plugin.instance_variable_get(:@statement_handler)
1476
+ allow(handler).to receive(:perform_query).and_raise(Sequel::PoolTimeout)
1477
+ allow(plugin.logger).to receive(:warn)
1478
+ allow(plugin.logger).to receive(:error)
1479
+
1480
+ expect(plugin).to receive(:close_jdbc_connection).at_least(:once).and_call_original
1481
+
1482
+ plugin.run(queue)
1483
+ plugin.stop
1484
+ end
1485
+
1486
+ it "closes the connection when open_jdbc_connection fails after database is assigned" do
1487
+ queue = Queue.new
1488
+ plugin.register
1489
+
1490
+ allow(plugin).to receive(:open_jdbc_connection).and_wrap_original do |m, *args|
1491
+ # simulate partial initialization: jdbc_connect succeeds but test_connection fails
1492
+ plugin.instance_variable_set(:@database, db)
1493
+ raise Sequel::DatabaseConnectionError, "test_connection failed"
1494
+ end
1495
+ allow(plugin.logger).to receive(:warn)
1496
+ allow(plugin.logger).to receive(:error)
1497
+
1498
+ expect(plugin).to receive(:close_jdbc_connection).at_least(:once).and_call_original
1499
+
1500
+ plugin.run(queue)
1501
+ plugin.stop
1502
+ end
1450
1503
  end
1451
1504
 
1452
1505
  context "when encoding of some columns need to be changed" do
@@ -1854,4 +1907,54 @@ describe LogStash::Inputs::Jdbc do
1854
1907
  end
1855
1908
  end
1856
1909
  end
1910
+
1911
+ describe "jdbc adapter preloading", :no_connection do
1912
+ before do
1913
+ allow(plugin).to receive(:load_jdbc_driver_class).and_return(double("driver_class"))
1914
+ end
1915
+
1916
+ it "extracts jdbc sub-adapter scheme in lowercase" do
1917
+ plugin.instance_variable_set(:@jdbc_connection_string, "jdbc:Oracle:thin:@//localhost:1521/FREEPDB1")
1918
+
1919
+ expect(plugin.send(:jdbc_subadapter_scheme)).to eq(:oracle)
1920
+ end
1921
+
1922
+ it "preloads sequel jdbc sub-adapter for jdbc URLs" do
1923
+ expect(Sequel::Database).to receive(:load_adapter).with(:derby, :map => Sequel::JDBC::DATABASE_SETUP, :subdir => 'jdbc').and_call_original
1924
+
1925
+ plugin.send(:load_driver)
1926
+ end
1927
+
1928
+ it "uses normalized jdbc sub-adapter scheme while preloading" do
1929
+ plugin.instance_variable_set(:@jdbc_connection_string, "jdbc:Oracle:thin:@//localhost:1521/FREEPDB1")
1930
+
1931
+ expect(Sequel::Database).to receive(:load_adapter).with(:oracle, :map => Sequel::JDBC::DATABASE_SETUP, :subdir => 'jdbc').and_return(nil)
1932
+
1933
+ expect { plugin.send(:load_driver) }.not_to raise_error
1934
+ end
1935
+
1936
+ it "does not try to preload when URL is not jdbc" do
1937
+ plugin.instance_variable_set(:@jdbc_connection_string, "mock://localhost:1527/db")
1938
+
1939
+ expect(Sequel::Database).not_to receive(:load_adapter)
1940
+
1941
+ plugin.send(:load_driver)
1942
+ end
1943
+
1944
+ it "ignores unknown jdbc sub-adapters" do
1945
+ plugin.instance_variable_set(:@jdbc_connection_string, "jdbc:unknown://localhost:1527/db")
1946
+
1947
+ expect(Sequel::Database).to receive(:load_adapter).with(:unknown, :map => Sequel::JDBC::DATABASE_SETUP, :subdir => 'jdbc').and_raise(Sequel::AdapterNotFound)
1948
+
1949
+ expect { plugin.send(:load_driver) }.not_to raise_error
1950
+ end
1951
+
1952
+ it "treats nil-returning jdbc sub-adapter load as a no-op" do
1953
+ plugin.instance_variable_set(:@jdbc_connection_string, "jdbc:unknown://localhost:1527/db")
1954
+
1955
+ expect(Sequel::Database).to receive(:load_adapter).with(:unknown, :map => Sequel::JDBC::DATABASE_SETUP, :subdir => 'jdbc').and_return(nil)
1956
+
1957
+ expect { plugin.send(:load_driver) }.not_to raise_error
1958
+ end
1959
+ end
1857
1960
  end
data/version ADDED
@@ -0,0 +1 @@
1
+ 5.6.5
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-integration-jdbc
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.3
4
+ version: 5.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-11 00:00:00.000000000 Z
10
+ date: 2026-08-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: logstash-core-plugin-api
@@ -286,6 +286,7 @@ files:
286
286
  - vendor/jar-dependencies/org/apache/derby/derbyclient/10.15.2.1/derbyclient-10.15.2.1.jar
287
287
  - vendor/jar-dependencies/org/apache/derby/derbyshared/10.15.2.1/derbyshared-10.15.2.1.jar
288
288
  - vendor/jar-dependencies/org/apache/derby/derbytools/10.15.2.1/derbytools-10.15.2.1.jar
289
+ - version
289
290
  homepage: http://www.elastic.co/guide/en/logstash/current/index.html
290
291
  licenses:
291
292
  - Apache License (2.0)