File: odbc.rb

package info (click to toggle)
ruby-sequel 5.63.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 10,408 kB
  • sloc: ruby: 113,747; makefile: 3
file content (150 lines) | stat: -rw-r--r-- 4,007 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# frozen-string-literal: true

require 'odbc'

module Sequel
  module ODBC
    # Contains procs keyed on subadapter type that extend the
    # given database object so it supports the correct database type.
    DATABASE_SETUP = {}
      
    class Database < Sequel::Database
      set_adapter_scheme :odbc

      def connect(server)
        opts = server_opts(server)
        conn = if opts.include?(:drvconnect)
          ::ODBC::Database.new.drvconnect(opts[:drvconnect])
        elsif opts.include?(:driver)
          drv = ::ODBC::Driver.new
          drv.name = 'Sequel ODBC Driver130'
          opts.each do |param, value|
            if :driver == param && value !~ /\A\{.+\}\z/
              value = "{#{value}}"
            end
            drv.attrs[param.to_s.upcase] = value.to_s
          end
          ::ODBC::Database.new.drvconnect(drv)
        else
          ::ODBC::connect(opts[:database], opts[:user], opts[:password])
        end
        conn.autocommit = true
        conn
      end      

      def disconnect_connection(c)
        c.disconnect
      end

      def execute(sql, opts=OPTS)
        synchronize(opts[:server]) do |conn|
          begin
            r = log_connection_yield(sql, conn){conn.run(sql)}
            yield(r) if defined?(yield)
          rescue ::ODBC::Error, ArgumentError => e
            raise_error(e)
          ensure
            r.drop if r
          end
          nil
        end
      end
      
      def execute_dui(sql, opts=OPTS)
        synchronize(opts[:server]) do |conn|
          begin
            log_connection_yield(sql, conn){conn.do(sql)}
          rescue ::ODBC::Error, ArgumentError => e
            raise_error(e)
          end
        end
      end

      private
      
      def adapter_initialize
        if (db_type = @opts[:db_type]) && (prok = Sequel::Database.load_adapter(db_type.to_sym, :map=>DATABASE_SETUP, :subdir=>'odbc'))
          prok.call(self)
        end
      end

      def connection_execute_method
        :do
      end

      def database_error_classes
        [::ODBC::Error]
      end

      def dataset_class_default
        Dataset
      end

      def disconnect_error?(e, opts)
        super || (e.is_a?(::ODBC::Error) && /\A08S01/.match(e.message))
      end
    end
    
    class Dataset < Sequel::Dataset
      def fetch_rows(sql)
        execute(sql) do |s|
          i = -1
          cols = s.columns(true).map{|c| [output_identifier(c.name), c.type, i+=1]}
          columns = cols.map{|c| c[0]}
          self.columns = columns
          s.each do |row|
            hash = {}
            cols.each do |n,t,j|
              v = row[j]
              # We can assume v is not false, so this shouldn't convert false to nil.
              hash[n] = (convert_odbc_value(v, t) if v)
            end
            yield hash
          end
        end
        self
      end
      
      private

      def convert_odbc_value(v, t)
        # When fetching a result set, the Ruby ODBC driver converts all ODBC 
        # SQL types to an equivalent Ruby type; with the exception of
        # SQL_TYPE_DATE, SQL_TYPE_TIME and SQL_TYPE_TIMESTAMP.
        #
        # The conversions below are consistent with the mappings in
        # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
        case v
        when ::ODBC::TimeStamp
          db.to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second, v.fraction])
        when ::ODBC::Time
          Sequel::SQLTime.create(v.hour, v.minute, v.second)
        when ::ODBC::Date
          Date.new(v.year, v.month, v.day)
        else
          if t == ::ODBC::SQL_BIT
            v == 1
          else
            v
          end
        end
      end
      
      def default_timestamp_format
        "{ts '%Y-%m-%d %H:%M:%S'}"
      end

      def literal_date(v)
        v.strftime("{d '%Y-%m-%d'}")
      end
      
      def literal_false
        '0'
      end
      
      def literal_true
        '1'
      end
    end
  end
end