|  | 
| 3 | 3 | require "logstash/config/mixin" | 
| 4 | 4 | require "time" | 
| 5 | 5 | require "date" | 
|  | 6 | +require "thread" # Monitor | 
| 6 | 7 | require_relative "value_tracking" | 
| 7 | 8 | require_relative "timezone_proxy" | 
| 8 | 9 | require_relative "statement_handler" | 
| 9 | 10 | require_relative "value_handler" | 
| 10 | 11 | 
 | 
| 11 |  | -java_import java.util.concurrent.locks.ReentrantLock | 
| 12 |  | - | 
| 13 | 12 | # Tentative of abstracting JDBC logic to a mixin | 
| 14 | 13 | # for potential reuse in other plugins (input/output) | 
|  | 14 | +# | 
|  | 15 | +# CAUTION: implementation of this "potential reuse" module is | 
|  | 16 | +#          VERY tightly-coupled with the JDBC Input's implementation. | 
| 15 | 17 | module LogStash  module PluginMixins module Jdbc | 
| 16 | 18 |   module Jdbc | 
| 17 | 19 |     include LogStash::PluginMixins::Jdbc::ValueHandler | 
| @@ -159,82 +161,94 @@ def log_java_exception(e) | 
| 159 | 161 |     end | 
| 160 | 162 | 
 | 
| 161 | 163 |     def open_jdbc_connection | 
| 162 |  | -      # at this point driver is already loaded | 
| 163 |  | -      Sequel.application_timezone = @plugin_timezone.to_sym | 
| 164 |  | - | 
| 165 |  | -      @database = jdbc_connect() | 
| 166 |  | -      @database.extension(:pagination) | 
| 167 |  | -      if @jdbc_default_timezone | 
| 168 |  | -        @database.extension(:named_timezones) | 
| 169 |  | -        @database.timezone = TimezoneProxy.load(@jdbc_default_timezone) | 
| 170 |  | -      end | 
| 171 |  | -      if @jdbc_validate_connection | 
| 172 |  | -        @database.extension(:connection_validator) | 
| 173 |  | -        @database.pool.connection_validation_timeout = @jdbc_validation_timeout | 
| 174 |  | -      end | 
| 175 |  | -      @database.fetch_size = @jdbc_fetch_size unless @jdbc_fetch_size.nil? | 
| 176 |  | -      begin | 
| 177 |  | -        @database.test_connection | 
| 178 |  | -      rescue Java::JavaSql::SQLException => e | 
| 179 |  | -        @logger.warn("Failed test_connection with java.sql.SQLException.", :exception => e) | 
| 180 |  | -      rescue Sequel::DatabaseConnectionError => e | 
| 181 |  | -        @logger.warn("Failed test_connection.", :exception => e) | 
| 182 |  | -        #TODO return false and let the plugin raise a LogStash::ConfigurationError | 
| 183 |  | -        raise e | 
| 184 |  | -      end | 
|  | 164 | +      @connection_lock.synchronize do | 
|  | 165 | +        # at this point driver is already loaded | 
|  | 166 | +        Sequel.application_timezone = @plugin_timezone.to_sym | 
|  | 167 | + | 
|  | 168 | +        @database = jdbc_connect() | 
|  | 169 | +        @database.extension(:pagination) | 
|  | 170 | +        if @jdbc_default_timezone | 
|  | 171 | +          @database.extension(:named_timezones) | 
|  | 172 | +          @database.timezone = TimezoneProxy.load(@jdbc_default_timezone) | 
|  | 173 | +        end | 
|  | 174 | +        if @jdbc_validate_connection | 
|  | 175 | +          @database.extension(:connection_validator) | 
|  | 176 | +          @database.pool.connection_validation_timeout = @jdbc_validation_timeout | 
|  | 177 | +        end | 
|  | 178 | +        @database.fetch_size = @jdbc_fetch_size unless @jdbc_fetch_size.nil? | 
|  | 179 | +        begin | 
|  | 180 | +          @database.test_connection | 
|  | 181 | +        rescue Java::JavaSql::SQLException => e | 
|  | 182 | +          @logger.warn("Failed test_connection with java.sql.SQLException.", :exception => e) | 
|  | 183 | +        rescue Sequel::DatabaseConnectionError => e | 
|  | 184 | +          @logger.warn("Failed test_connection.", :exception => e) | 
|  | 185 | +          #TODO return false and let the plugin raise a LogStash::ConfigurationError | 
|  | 186 | +          raise e | 
|  | 187 | +        end | 
| 185 | 188 | 
 | 
| 186 |  | -      @database.sql_log_level = @sql_log_level.to_sym | 
| 187 |  | -      @database.logger = @logger | 
|  | 189 | +        @database.sql_log_level = @sql_log_level.to_sym | 
|  | 190 | +        @database.logger = @logger | 
| 188 | 191 | 
 | 
| 189 |  | -      @database.extension :identifier_mangling | 
|  | 192 | +        @database.extension :identifier_mangling | 
| 190 | 193 | 
 | 
| 191 |  | -      if @lowercase_column_names | 
| 192 |  | -        @database.identifier_output_method = :downcase | 
| 193 |  | -      else | 
| 194 |  | -        @database.identifier_output_method = :to_s | 
|  | 194 | +        if @lowercase_column_names | 
|  | 195 | +          @database.identifier_output_method = :downcase | 
|  | 196 | +        else | 
|  | 197 | +          @database.identifier_output_method = :to_s | 
|  | 198 | +        end | 
| 195 | 199 |       end | 
| 196 | 200 |     end | 
| 197 | 201 | 
 | 
|  | 202 | +    public | 
|  | 203 | +    def prepare_jdbc_connection | 
|  | 204 | +      @connection_lock = Monitor.new # aka ReentrantLock | 
|  | 205 | +    end | 
|  | 206 | + | 
| 198 | 207 |     public | 
| 199 | 208 |     def close_jdbc_connection | 
| 200 |  | -      begin | 
|  | 209 | +      @connection_lock.synchronize do | 
| 201 | 210 |         # pipeline restarts can also close the jdbc connection, block until the current executing statement is finished to avoid leaking connections | 
| 202 | 211 |         # connections in use won't really get closed | 
| 203 | 212 |         @database.disconnect if @database | 
| 204 |  | -      rescue => e | 
| 205 |  | -        @logger.warn("Failed to close connection", :exception => e) | 
| 206 | 213 |       end | 
|  | 214 | +    rescue => e | 
|  | 215 | +      @logger.warn("Failed to close connection", :exception => e) | 
| 207 | 216 |     end | 
| 208 | 217 | 
 | 
| 209 | 218 |     public | 
| 210 |  | -    def execute_statement | 
|  | 219 | +    def execute_statement(&result_handler) | 
| 211 | 220 |       success = false | 
| 212 | 221 |       retry_attempts = @statement_retry_attempts | 
| 213 | 222 | 
 | 
| 214 |  | -      begin | 
| 215 |  | -        retry_attempts -= 1 | 
| 216 |  | -        sql_last_value = @use_column_value ? @value_tracker.value : Time.now.utc | 
| 217 |  | -        @tracking_column_warning_sent = false | 
| 218 |  | -        @statement_handler.perform_query(@database, @value_tracker.value) do |row| | 
| 219 |  | -          sql_last_value = get_column_value(row) if @use_column_value | 
| 220 |  | -          yield extract_values_from(row) | 
| 221 |  | -        end | 
| 222 |  | -        success = true | 
| 223 |  | -      rescue Sequel::Error, Java::JavaSql::SQLException => e | 
| 224 |  | -        details = { exception: e.class, message: e.message } | 
| 225 |  | -        details[:cause] = e.cause.inspect if e.cause | 
| 226 |  | -        details[:backtrace] = e.backtrace if @logger.debug? | 
| 227 |  | -        @logger.warn("Exception when executing JDBC query", details) | 
| 228 |  | - | 
| 229 |  | -        if retry_attempts == 0 | 
| 230 |  | -          @logger.error("Unable to execute statement. Tried #{@statement_retry_attempts} times.") | 
|  | 223 | +      @connection_lock.synchronize do | 
|  | 224 | +        begin | 
|  | 225 | +          retry_attempts -= 1 | 
|  | 226 | +          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.") | 
|  | 242 | +          else | 
|  | 243 | +            @logger.error("Unable to execute statement. Trying again.") | 
|  | 244 | +            sleep(@statement_retry_attempts_wait_time) | 
|  | 245 | +            retry | 
|  | 246 | +          end | 
| 231 | 247 |         else | 
| 232 |  | -          @logger.error("Unable to execute statement. Trying again.") | 
| 233 |  | -          sleep(@statement_retry_attempts_wait_time) | 
| 234 |  | -          retry | 
|  | 248 | +          @value_tracker.set_value(sql_last_value) | 
|  | 249 | +        ensure | 
|  | 250 | +          close_jdbc_connection | 
| 235 | 251 |         end | 
| 236 |  | -      else | 
| 237 |  | -        @value_tracker.set_value(sql_last_value) | 
| 238 | 252 |       end | 
| 239 | 253 | 
 | 
| 240 | 254 |       return success | 
|  | 
0 commit comments