Skip to content

Commit

Permalink
driver loading: provide loaded driver to Sequel::connect (#34)
Browse files Browse the repository at this point in the history
`Sequel::connect` will first attempt to auto-detect the driver based on the
jdbc connection string, and will [fall-back][] to the driver given as
opts[:driver] when auto-detection fails.

By memoizing the loaded driver from our previous `Sequel::JDBC::load_driver`
and providing it to `Sequel::connect`, we provide one more chance for loading
to work.

This specifically seems to be the only path forward for Sybase drivers, whose
jdbc connection strings begin with `jdbc:sybase:Tds:` but use the driver with
path `com.sybase.jdbc4.jdbc.SybDriver`.

[fall-back]: https://github.com/jeremyevans/sequel/blob/5.19.0/lib/sequel/adapters/jdbc.rb#L211-L213

Fixes: #23
Fixes: #30
  • Loading branch information
yaauie authored Jun 11, 2020
1 parent b7d2ee7 commit 94821e5
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 21 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 5.0.4
- Fixed issue where JDBC Drivers that don't correctly register with Java's DriverManager fail to load (such as Sybase) [#34](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/34)

## 5.0.3
- Fixed issue where a lost connection to the database can cause errors when using prepared statements with the scheduler [#25](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/25)

Expand Down
2 changes: 1 addition & 1 deletion lib/logstash/filters/jdbc/basic_database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def verify_connection(connection_string, driver_class, driver_library, user, pas
end
begin
db = nil
::Sequel::JDBC.load_driver(driver_class)
@options_hash[:driver] = ::Sequel::JDBC.load_driver(driver_class)
@connection_string = connection_string
if user
@options_hash[:user] = user
Expand Down
5 changes: 3 additions & 2 deletions lib/logstash/plugin_mixins/jdbc/jdbc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,14 @@ def jdbc_connect
:user => @jdbc_user,
:password => @jdbc_password.nil? ? nil : @jdbc_password.value,
:pool_timeout => @jdbc_pool_timeout,
:driver => @driver_impl, # Sequel uses this as a fallback, if given URI doesn't auto-load the driver correctly
:keep_reference => false
}.merge(@sequel_opts)
retry_attempts = @connection_retry_attempts
loop do
retry_attempts -= 1
begin
return Sequel.connect(@jdbc_connection_string, opts=opts)
return Sequel.connect(@jdbc_connection_string, opts)
rescue Sequel::PoolTimeout => e
if retry_attempts <= 0
@logger.error("Failed to connect to database. #{@jdbc_pool_timeout} second timeout exceeded. Tried #{@connection_retry_attempts} times.")
Expand Down Expand Up @@ -147,7 +148,7 @@ def load_driver

load_driver_jars
begin
Sequel::JDBC.load_driver(@jdbc_driver_class)
@driver_impl = Sequel::JDBC.load_driver(@jdbc_driver_class)
rescue Sequel::AdapterNotFound => e # Sequel::AdapterNotFound, "#{@jdbc_driver_class} not loaded"
# fix this !!!
message = if jdbc_driver_library_set?
Expand Down
2 changes: 1 addition & 1 deletion lib/logstash/plugin_mixins/jdbc_streaming.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def prepare_jdbc_connection
@sequel_opts_symbols[:user] = @jdbc_user unless @jdbc_user.nil? || @jdbc_user.empty?
@sequel_opts_symbols[:password] = @jdbc_password.value unless @jdbc_password.nil?

Sequel::JDBC.load_driver(@jdbc_driver_class)
@sequel_opts_symbols[:driver] = Sequel::JDBC.load_driver(@jdbc_driver_class)
@database = Sequel.connect(@jdbc_connection_string, @sequel_opts_symbols)
if @jdbc_validate_connection
@database.extension(:connection_validator)
Expand Down
2 changes: 1 addition & 1 deletion logstash-integration-jdbc.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = 'logstash-integration-jdbc'
s.version = '5.0.3'
s.version = '5.0.4'
s.licenses = ['Apache License (2.0)']
s.summary = "Integration with JDBC - input and filter plugins"
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"
Expand Down
29 changes: 19 additions & 10 deletions spec/filters/jdbc/read_only_database_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,37 @@ module LogStash module Filters module Jdbc
let(:driver_class) { "org.apache.derby.jdbc.EmbeddedDriver" }
let(:driver_library) { nil }
subject(:read_only_db) { described_class.create(connection_string, driver_class, driver_library) }
let(:stub_driver_class) { double(driver_class).as_null_object }

describe "basic operations" do
describe "initializing" do
before(:each) do
allow(Sequel::JDBC).to receive(:load_driver).and_return(stub_driver_class)
end

it "tests the connection with defaults" do
expect(Sequel::JDBC).to receive(:load_driver).once.with(driver_class)
expect(Sequel).to receive(:connect).once.with(connection_string, {:test => true})
expect(Sequel).to receive(:connect).once.with(connection_string, {:test => true, :driver => stub_driver_class})
expect(read_only_db.empty_record_set).to eq([])
end

it "tests the connection with fully specified arguments" do
connection_str = "a connection string"
user = "a user"
password = Util::Password.new("secret")
expect(Sequel::JDBC).to receive(:load_driver).once.with("a driver class")
expect(Sequel).to receive(:connect).once.with(connection_str, {:user => user, :password => password.value, :test => true}).and_return(db)
described_class.create(connection_str, "a driver class", nil, user, password)
context 'with fully-specified arguments' do
let(:connection_string) { "a connection string" }
let(:user) { "a user" }
let(:password) { Util::Password.new("secret") }
let(:driver_class) { "a driver class" }

it "tests the connection" do
expect(Sequel::JDBC).to receive(:load_driver).once.with(driver_class)
expect(Sequel).to receive(:connect).once.with(connection_string, {:user => user, :password => password.value, :test => true, :driver => stub_driver_class}).and_return(db)
described_class.create(connection_string, driver_class, nil, user, password)
end
end

it "connects with defaults" do
expect(Sequel::JDBC).to receive(:load_driver).once.with(driver_class)
expect(Sequel).to receive(:connect).once.with(connection_string, {:test => true}).and_return(db)
expect(Sequel).to receive(:connect).once.with(connection_string, {}).and_return(db)
expect(Sequel).to receive(:connect).once.with(connection_string, {:test => true, :driver => stub_driver_class}).and_return(db)
expect(Sequel).to receive(:connect).once.with(connection_string, {:driver => stub_driver_class}).and_return(db)
expect(read_only_db.connected?).to be_falsey
read_only_db.connect("a caller specific error message")
expect(read_only_db.connected?).to be_truthy
Expand Down
14 changes: 8 additions & 6 deletions spec/filters/jdbc/read_write_database_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,22 @@ module LogStash module Filters module Jdbc
describe "basic operations" do
context "connecting to a db" do
it "connects with defaults" do
expect(::Sequel::JDBC).to receive(:load_driver).once.with("org.apache.derby.jdbc.EmbeddedDriver")
stub_driver_class = double('org.apache.derby.jdbc.EmbeddedDriver').as_null_object
expect(::Sequel::JDBC).to receive(:load_driver).once.with("org.apache.derby.jdbc.EmbeddedDriver").and_return(stub_driver_class)
# two calls to connect because ReadWriteDatabase does verify_connection and connect
expect(::Sequel).to receive(:connect).once.with(connection_string_regex, {:test => true}).and_return(db)
expect(::Sequel).to receive(:connect).once.with(connection_string_regex, {}).and_return(db)
expect(::Sequel).to receive(:connect).once.with(connection_string_regex, {:driver => stub_driver_class, :test => true}).and_return(db)
expect(::Sequel).to receive(:connect).once.with(connection_string_regex, {:driver => stub_driver_class}).and_return(db)
expect(read_write_db.empty_record_set).to eq([])
end

it "connects with fully specified arguments" do
connection_str = "a connection string"
user = "a user"
password = Util::Password.new("secret")
expect(::Sequel::JDBC).to receive(:load_driver).once.with("a driver class")
expect(::Sequel).to receive(:connect).once.with(connection_str, {:user => user, :password => password.value, :test => true}).and_return(db)
expect(::Sequel).to receive(:connect).once.with(connection_str, {:user => user, :password => password.value}).and_return(db)
stub_driver_class = double('com.example.Driver')
expect(::Sequel::JDBC).to receive(:load_driver).once.with("a driver class").and_return(stub_driver_class)
expect(::Sequel).to receive(:connect).once.with(connection_str, {:driver => stub_driver_class, :user => user, :password => password.value, :test => true}).and_return(db)
expect(::Sequel).to receive(:connect).once.with(connection_str, {:driver => stub_driver_class, :user => user, :password => password.value}).and_return(db)
described_class.create(connection_str, "a driver class", nil, user, password)
end
end
Expand Down

0 comments on commit 94821e5

Please sign in to comment.