module TinyTds class Client @default_query_options = { as: :hash, symbolize_keys: false, cache_rows: true, timezone: :local, empty_sets: true } attr_reader :query_options attr_reader :message_handler class << self attr_reader :default_query_options # Most, if not all, iconv encoding names can be found by ruby. Just in case, you can # overide this method to return a string name that Encoding.find would work with. Default # is to return the passed encoding. # def transpose_iconv_encoding(encoding) encoding end def local_offset ::Time.local(2010).utc_offset.to_r / 86_400 end end # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/PerceivedComplexity def initialize(opts = {}) if opts[:dataserver].to_s.empty? && opts[:host].to_s.empty? raise ArgumentError, 'missing :host option if no :dataserver given' end @message_handler = opts[:message_handler] if @message_handler && !@message_handler.respond_to?(:call) raise ArgumentError, ':message_handler must implement `call` (eg, a Proc or a Method)' end opts[:username] = parse_username(opts) @query_options = self.class.default_query_options.dup opts[:password] = opts[:password].to_s if opts[:password] && opts[:password].to_s.strip != '' opts[:appname] ||= 'TinyTds' opts[:tds_version] = tds_versions_setter(opts) opts[:use_utf16] = opts[:use_utf16].nil? || ["true", "1", "yes"].include?(opts[:use_utf16].to_s) opts[:login_timeout] ||= 60 opts[:timeout] ||= 5 opts[:encoding] = opts[:encoding].nil? || opts[:encoding].casecmp('utf8').zero? ? 'UTF-8' : opts[:encoding].upcase opts[:port] ||= 1433 opts[:dataserver] = "#{opts[:host]}:#{opts[:port]}" if opts[:dataserver].to_s.empty? forced_integer_keys = [:login_timeout, :port, :timeout] forced_integer_keys.each { |k| opts[k] = opts[k].to_i if opts[k] } connect(opts) end def tds_73? tds_version >= 11 end def tds_version_info info = TDS_VERSIONS_GETTERS[tds_version] "#{info[:name]} - #{info[:description]}" if info end def active? !closed? && !dead? end private def parse_username(opts) host = opts[:host] username = opts[:username] return username if username.nil? || !opts[:azure] return username if username.include?('@') && !username.include?('database.windows.net') user, domain = username.split('@') domain ||= host "#{user}@#{domain.split('.').first}" end def tds_versions_setter(opts = {}) v = opts[:tds_version] || ENV['TDSVER'] || '7.3' TDS_VERSIONS_SETTERS[v.to_s] end # From sybdb.h comments: # DBVERSION_xxx are used with dbsetversion() # TDS_VERSIONS_SETTERS = { 'unknown' => 0, '46' => 1, '100' => 2, '42' => 3, '70' => 4, '7.0' => 4, '71' => 5, '7.1' => 5, '80' => 5, '8.0' => 5, '72' => 6, '7.2' => 6, '90' => 6, '9.0' => 6, '73' => 7, '7.3' => 7 }.freeze # From sybdb.h comments: # DBTDS_xxx are returned by DBTDS() # The integer values of the constants are poorly chosen. # TDS_VERSIONS_GETTERS = { 0 => { name: 'DBTDS_UNKNOWN', description: 'Unknown' }, 1 => { name: 'DBTDS_2_0', description: 'Pre 4.0 SQL Server' }, 2 => { name: 'DBTDS_3_4', description: 'Microsoft SQL Server (3.0)' }, 3 => { name: 'DBTDS_4_0', description: '4.0 SQL Server' }, 4 => { name: 'DBTDS_4_2', description: '4.2 SQL Server' }, 5 => { name: 'DBTDS_4_6', description: '2.0 OpenServer and 4.6 SQL Server.' }, 6 => { name: 'DBTDS_4_9_5', description: '4.9.5 (NCR) SQL Server' }, 7 => { name: 'DBTDS_5_0', description: '5.0 SQL Server' }, 8 => { name: 'DBTDS_7_0', description: 'Microsoft SQL Server 7.0' }, 9 => { name: 'DBTDS_7_1/DBTDS_8_0', description: 'Microsoft SQL Server 2000' }, 10 => { name: 'DBTDS_7_2/DBTDS_9_0', description: 'Microsoft SQL Server 2005' }, 11 => { name: 'DBTDS_7_3', description: 'Microsoft SQL Server 2008' }, 12 => { name: 'DBTDS_7_4', description: 'Microsoft SQL Server 2012/2014' } }.freeze end end