# encoding: utf-8 require "yaml" # persistence require "date" require "bigdecimal" module LogStash module PluginMixins module Jdbc class ValueTracking def self.build_last_value_tracker(plugin) handler = plugin.record_last_run ? FileHandler.new(plugin.last_run_metadata_file_path) : NullFileHandler.new(plugin.last_run_metadata_file_path) if plugin.clean_run handler.clean end if plugin.use_column_value && plugin.tracking_column_type == "numeric" # use this irrespective of the jdbc_default_timezone setting NumericValueTracker.new(handler) else if plugin.jdbc_default_timezone.nil? # no TZ stuff for Sequel, use Time TimeValueTracker.new(handler) else # Sequel does timezone handling on DateTime only DateTimeValueTracker.new(handler) end end end attr_reader :value def initialize(handler) @file_handler = handler set_initial end if Psych::VERSION&.split('.')&.first.to_i >= 4 YAML_PERMITTED_CLASSES = [::DateTime, ::Time, ::BigDecimal].freeze def self.load_yaml(source) Psych::safe_load(source, permitted_classes: YAML_PERMITTED_CLASSES) end else def self.load_yaml(source) YAML::load(source) end end def set_initial # override in subclass end def set_value(value) # override in subclass end def write @file_handler.write(@value) end private def common_set_initial(method_symbol, default) persisted = @file_handler.read if persisted && persisted.respond_to?(method_symbol) @value = persisted else @file_handler.clean @value = default end end end class NumericValueTracker < ValueTracking def set_initial common_set_initial(:abs, 0) end def set_value(value) return unless value.is_a?(Numeric) @value = value end end class DateTimeValueTracker < ValueTracking def set_initial common_set_initial(:to_datetime, DateTime.new(1970)) end def set_value(value) if value.respond_to?(:to_datetime) @value = value.to_datetime else @value = DateTime.parse(value) end end end class TimeValueTracker < ValueTracking def set_initial common_set_initial(:to_time, Time.at(0).utc) end def set_value(value) if value.respond_to?(:to_time) @value = value.to_time else @value = DateTime.parse(value).to_time end end end class FileHandler attr_reader :path def initialize(path) @path = path @exists = ::File.exist?(@path) end def clean return unless @exists ::File.delete(@path) @exists = false end def read return unless @exists ValueTracking.load_yaml(::File.read(@path)) end def write(value) ::File.write(@path, YAML.dump(value)) @exists = true end end class NullFileHandler def initialize(path) end def clean end def read end def write(value) end end end end end