# frozen_string_literal: true require 'date' require_relative 'interval' require_relative 'converter/int_to_sym' module DuckDB QueryProgress = Struct.new(:percentage, :rows_processed, :total_rows_to_process) module Converter # :nodoc: all HALF_HUGEINT_BIT = 64 HALF_HUGEINT = 1 << HALF_HUGEINT_BIT FLIP_HUGEINT = 1 << 63 EPOCH = Time.local(1970, 1, 1) EPOCH_UTC = Time.new(1970, 1, 1, 0, 0, 0, 0) module_function def _to_infinity(value) if value.positive? DuckDB::Infinity::POSITIVE else DuckDB::Infinity::NEGATIVE end end def _to_date(year, month, day) Date.new(year, month, day) end def _to_time(year, month, day, hour, minute, second, microsecond) Time.local(year, month, day, hour, minute, second, microsecond) end def _to_time_from_duckdb_time(hour, minute, second, microsecond) Time.parse( format( '%02d:%02d:%02d.%06d', hour: hour, minute: minute, second: second, microsecond: microsecond ) ) end def _to_time_from_duckdb_timestamp_s(time) EPOCH + time end def _to_time_from_duckdb_timestamp_ms(time) tm = EPOCH + (time / 1000) Time.local(tm.year, tm.month, tm.day, tm.hour, tm.min, tm.sec, time % 1000 * 1000) end def _to_time_from_duckdb_timestamp_ns(time) tm = EPOCH + (time / 1_000_000_000) Time.local(tm.year, tm.month, tm.day, tm.hour, tm.min, tm.sec, time % 1_000_000_000 / 1000) end def _to_time_from_duckdb_time_tz(hour, min, sec, micro, timezone) sign = '+' if timezone.negative? timezone = -timezone sign = '-' end tzhour = timezone / 3600 tzmin = (timezone % 3600) / 60 Time.parse( format( '%02d:%02d:%02d.%06d%s%02d:%02d', hour: hour, min: min, sec: sec, micro: micro, sign: sign, tzhour: tzhour, tzmin: tzmin ) ) end def _to_time_from_duckdb_timestamp_tz(bits) micro = bits % 1_000_000 sec = (bits / 1_000_000) time = EPOCH_UTC + sec Time.parse( format( '%04d-%02d-%02d %02d:%02d:%02d.%06d +0000', year: time.year, mon: time.month, day: time.day, hour: time.hour, min: time.min, sec: time.sec, micro: micro ) ) end def _to_hugeint_from_vector(lower, upper) (upper << HALF_HUGEINT_BIT) + lower end def _to_decimal_from_hugeint(width, scale, upper, lower = nil) v = lower.nil? ? upper : _to_hugeint_from_vector(lower, upper) _to_decimal_from_value(width, scale, v) end def _to_decimal_from_value(_width, scale, value) v = value.to_s v = v.rjust(scale + 1, '0') if v.length < scale v[-scale, 0] = '.' if scale.positive? BigDecimal(v) end def _to_interval_from_vector(months, days, micros) Interval.new(interval_months: months, interval_days: days, interval_micros: micros) end def _to_uuid_from_vector(lower, upper) upper ^= FLIP_HUGEINT upper += HALF_HUGEINT if upper.negative? str = _to_hugeint_from_vector(lower, upper).to_s(16).rjust(32, '0') "#{str[0, 8]}-#{str[8, 4]}-#{str[12, 4]}-#{str[16, 4]}-#{str[20, 12]}" end def _parse_date(value) case value when Date, Time value else begin Date.parse(value) rescue StandardError => e raise(ArgumentError, "Cannot parse `#{value.inspect}` to Date object. #{e.message}") end end end def _parse_time(value) case value when Time value else begin Time.parse(value) rescue StandardError => e raise(ArgumentError, "Cannot parse `#{value.inspect}` to Time object. #{e.message}") end end end def _parse_deciaml(value) case value when BigDecimal value else begin BigDecimal(value.to_s) rescue StandardError => e raise(ArgumentError, "Cannot parse `#{value.inspect}` to BigDecimal object. #{e.message}") end end end def _to_query_progress(percentage, rows_processed, total_rows_to_process) DuckDB::QueryProgress.new(percentage, rows_processed, total_rows_to_process).freeze end private def integer_to_hugeint(value) case value when Integer upper = value >> HALF_HUGEINT_BIT lower = value - (upper << HALF_HUGEINT_BIT) [lower, upper] else raise(ArgumentError, "The argument `#{value.inspect}` must be Integer.") end end def decimal_to_hugeint(value) integer_value = (value * (10 ** value.scale)).to_i integer_to_hugeint(integer_value) rescue FloatDomainError => e raise(ArgumentError, "The argument `#{value.inspect}` must be converted to Integer. #{e.message}") end end end