odbc_adapter 3.2.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e903865d2eae4fef5a33f881da4d1890b7f2400c
4
- data.tar.gz: e30af49c10265dcd7f189113b32ec59cf9d7267b
3
+ metadata.gz: 791ccc95087c9361555daf8c3f464da82ac3b003
4
+ data.tar.gz: cc8210e283ac73556cd90c89b7fc6902a7611cd2
5
5
  SHA512:
6
- metadata.gz: aa920bf163813b47069442b75e1290b3d418910f92c01904f5a0976e5809b768950382a3b693482f7f49e11a3532598a4e01c9929ac976e0aba7996115923b28
7
- data.tar.gz: b85c71c0d26ccd11075308bf745f8592565e2434cd9038a38748f57f2866583386ca3bcadf4901a134a854122e90967b30a1f942c637bfafb891bfa0dd3399e7
6
+ metadata.gz: 3aab9df767fd6f5079549eb4c075d4a769d27d53d7d2ec769ddde3ca1c063fa78446d025cae91ca8bc815e35871a5370ba178fb36248be61d2b964d63882b3a7
7
+ data.tar.gz: 22c3bbef81248cc70ee6bc1e69c16d1bec90ddc7795cb5a6a4db54ff969cfd6e8769591c370222042dc1431b7447a34d9de4888496cec5b8bf2334dbf68890e7
data/Gemfile CHANGED
@@ -2,5 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'activerecord', '3.2.22.1'
5
+ gem 'activerecord', '4.2.7.1'
6
6
  gem 'pry', '0.10.4'
@@ -74,6 +74,7 @@ module ActiveRecord
74
74
  include ::ODBCAdapter::SchemaStatements
75
75
 
76
76
  ADAPTER_NAME = 'ODBC'.freeze
77
+ ERR_DUPLICATE_KEY_VALUE = 23505
77
78
 
78
79
  attr_reader :dbms
79
80
 
@@ -115,13 +116,69 @@ module ActiveRecord
115
116
  else
116
117
  ODBC::Database.new.drvconnect(options[:driver])
117
118
  end
119
+ super
118
120
  end
121
+ alias :reset! :reconnect!
119
122
 
120
123
  # Disconnects from the database if already connected. Otherwise, this
121
124
  # method does nothing.
122
125
  def disconnect!
123
126
  @connection.disconnect if @connection.connected?
124
127
  end
128
+
129
+ protected
130
+
131
+ def initialize_type_map(map)
132
+ map.register_type ODBC::SQL_BIT, Type::Boolean.new
133
+ map.register_type ODBC::SQL_CHAR, Type::String.new
134
+ map.register_type ODBC::SQL_LONGVARCHAR, Type::Text.new
135
+ map.register_type ODBC::SQL_TINYINT, Type::Integer.new(limit: 4)
136
+ map.register_type ODBC::SQL_SMALLINT, Type::Integer.new(limit: 8)
137
+ map.register_type ODBC::SQL_INTEGER, Type::Integer.new(limit: 16)
138
+ map.register_type ODBC::SQL_BIGINT, Type::BigInteger.new(limit: 32)
139
+ map.register_type ODBC::SQL_REAL, Type::Float.new(limit: 24)
140
+ map.register_type ODBC::SQL_FLOAT, Type::Float.new
141
+ map.register_type ODBC::SQL_DOUBLE, Type::Float.new(limit: 53)
142
+ map.register_type ODBC::SQL_DECIMAL, Type::Float.new
143
+ map.register_type ODBC::SQL_NUMERIC, Type::Integer.new
144
+ map.register_type ODBC::SQL_BINARY, Type::Binary.new
145
+ map.register_type ODBC::SQL_DATE, Type::Date.new
146
+ map.register_type ODBC::SQL_DATETIME, Type::DateTime.new
147
+ map.register_type ODBC::SQL_TIME, Type::Time.new
148
+ map.register_type ODBC::SQL_TIMESTAMP, Type::DateTime.new
149
+ map.register_type ODBC::SQL_GUID, Type::String.new
150
+
151
+ alias_type map, ODBC::SQL_VARCHAR, ODBC::SQL_CHAR
152
+ alias_type map, ODBC::SQL_WCHAR, ODBC::SQL_CHAR
153
+ alias_type map, ODBC::SQL_WVARCHAR, ODBC::SQL_CHAR
154
+ alias_type map, ODBC::SQL_WLONGVARCHAR, ODBC::SQL_LONGVARCHAR
155
+ alias_type map, ODBC::SQL_VARBINARY, ODBC::SQL_BINARY
156
+ alias_type map, ODBC::SQL_LONGVARBINARY, ODBC::SQL_BINARY
157
+ alias_type map, ODBC::SQL_TYPE_DATE, ODBC::SQL_DATE
158
+ alias_type map, ODBC::SQL_TYPE_TIME, ODBC::SQL_TIME
159
+ alias_type map, ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP
160
+ end
161
+
162
+ def translate_exception(exception, message)
163
+ case exception.message[/^\d+/].to_i
164
+ when ERR_DUPLICATE_KEY_VALUE
165
+ ActiveRecord::RecordNotUnique.new(message, exception)
166
+ else
167
+ super
168
+ end
169
+ end
170
+
171
+ def new_column(name, default, cast_type, sql_type = nil, null = true, native_type = nil, scale = nil, limit = nil)
172
+ ::ODBCAdapter::Column.new(name, default, cast_type, sql_type, null, native_type, scale, limit)
173
+ end
174
+
175
+ private
176
+
177
+ def alias_type(map, new_type, old_type)
178
+ map.register_type(new_type) do |_, *args|
179
+ map.lookup(old_type, *args)
180
+ end
181
+ end
125
182
  end
126
183
  end
127
184
  end
@@ -9,6 +9,10 @@ module ODBCAdapter
9
9
 
10
10
  PRIMARY_KEY = 'INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'
11
11
 
12
+ def truncate(table_name, name = nil)
13
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)}", name)
14
+ end
15
+
12
16
  def limited_update_conditions(where_sql, _quoted_table_name, _quoted_primary_key)
13
17
  where_sql
14
18
  end
@@ -98,7 +102,7 @@ module ODBCAdapter
98
102
 
99
103
  def rename_column(table_name, column_name, new_column_name)
100
104
  col = columns(table_name).detect { |c| c.name == column_name.to_s }
101
- current_type = col.sql_type
105
+ current_type = col.native_type
102
106
  current_type << "(#{col.limit})" if col.limit
103
107
  execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}")
104
108
  end
@@ -111,7 +115,7 @@ module ODBCAdapter
111
115
  def options_include_default?(options)
112
116
  # MySQL 5.x doesn't allow DEFAULT NULL for first timestamp column in a table
113
117
  if options.include?(:default) && options[:default].nil?
114
- if options.include?(:column) && options[:column].sql_type =~ /timestamp/i
118
+ if options.include?(:column) && options[:column].native_type =~ /timestamp/i
115
119
  options.delete(:default)
116
120
  end
117
121
  end
@@ -134,7 +138,7 @@ module ODBCAdapter
134
138
  end
135
139
 
136
140
  def last_inserted_id(_result)
137
- @connection.last_id
141
+ select_value('SELECT LAST_INSERT_ID()').to_i
138
142
  end
139
143
  end
140
144
  end
@@ -7,63 +7,18 @@ module ODBCAdapter
7
7
  include Arel::Visitors::BindVisitor
8
8
  end
9
9
 
10
- class PostgreSQLColumn < Column
11
- def initialize(name, default, sql_type, native_type, null = true, scale = nil, native_types = nil, limit = nil)
12
- super
13
- @default = extract_default
14
- end
15
-
16
- private
17
-
18
- def extract_default
19
- case @default
20
- when NilClass
21
- nil
22
- # Numeric types
23
- when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/ then $1
24
- # Character types
25
- when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m then $1
26
- # Binary data types
27
- when /\A'(.*)'::bytea\z/m then $1
28
- # Date/time types
29
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/ then $1
30
- when /\A'(.*)'::interval\z/ then $1
31
- # Boolean type
32
- when 'true' then true
33
- when 'false' then false
34
- # Geometric types
35
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/ then $1
36
- # Network address types
37
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/ then $1
38
- # Bit string types
39
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/ then $1
40
- # XML type
41
- when /\A'(.*)'::xml\z/m then $1
42
- # Arrays
43
- when /\A'(.*)'::"?\D+"?\[\]\z/ then $1
44
- # Object identifier types
45
- when /\A-?\d+\z/ then $1
46
- else
47
- # Anything else is blank, some user type, or some function
48
- # and we can't know the value of that, so return nil.
49
- nil
50
- end
51
- end
52
- end
53
-
54
10
  PRIMARY_KEY = 'SERIAL PRIMARY KEY'
55
11
 
56
- # Override the default column class
57
- def column_class
58
- PostgreSQLColumn
59
- end
60
-
61
12
  # Filter for ODBCAdapter#tables
62
13
  # Omits table from #tables if table_filter returns true
63
14
  def table_filter(schema_name, table_type)
64
15
  %w[information_schema pg_catalog].include?(schema_name) || table_type !~ /TABLE/i
65
16
  end
66
17
 
18
+ def truncate(table_name, name = nil)
19
+ exec_query("TRUNCATE TABLE #{quote_table_name(table_name)}", name)
20
+ end
21
+
67
22
  # Returns the sequence name for a table's primary key or some other specified key.
68
23
  def default_sequence_name(table_name, pk = nil) #:nodoc:
69
24
  serial_sequence(table_name, pk || 'id').split('.').last
@@ -1,67 +1,25 @@
1
1
  module ODBCAdapter
2
2
  class Column < ActiveRecord::ConnectionAdapters::Column
3
- def initialize(name, default, sql_type, native_type, null = true, scale = nil, native_types = nil, limit = nil)
3
+ attr_reader :native_type
4
+
5
+ def initialize(name, default, cast_type, sql_type, null, native_type, scale, limit)
4
6
  @name = name
5
7
  @default = default
6
- @sql_type = native_type.to_s
7
- @native_type = native_type.to_s
8
+ @cast_type = cast_type
9
+ @sql_type = sql_type
8
10
  @null = null
9
- @precision = extract_precision(sql_type, limit)
10
- @scale = extract_scale(sql_type, scale)
11
- @type = genericize(sql_type, @scale, native_types)
12
- @primary = nil
13
- end
14
-
15
- private
11
+ @native_type = native_type
16
12
 
17
- # Maps an ODBC SQL type to an ActiveRecord abstract data type
18
- #
19
- # c.f. Mappings in ConnectionAdapters::Column#simplified_type based on
20
- # native column type declaration
21
- #
22
- # See also:
23
- # Column#klass (schema_definitions.rb) for the Ruby class corresponding
24
- # to each abstract data type.
25
- def genericize(sql_type, scale, native_types)
26
- case sql_type
27
- when ODBC::SQL_BIT then :boolean
28
- when ODBC::SQL_CHAR, ODBC::SQL_VARCHAR then :string
29
- when ODBC::SQL_LONGVARCHAR then :text
30
- when ODBC::SQL_WCHAR, ODBC::SQL_WVARCHAR then :string
31
- when ODBC::SQL_WLONGVARCHAR then :text
32
- when ODBC::SQL_TINYINT, ODBC::SQL_SMALLINT, ODBC::SQL_INTEGER, ODBC::SQL_BIGINT then :integer
33
- when ODBC::SQL_REAL, ODBC::SQL_FLOAT, ODBC::SQL_DOUBLE then :float
34
- # If SQLGetTypeInfo output of ODBC driver doesn't include a mapping
35
- # to a native type from SQL_DECIMAL/SQL_NUMERIC, map to :float
36
- when ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC then numeric_type(scale, native_types)
37
- when ODBC::SQL_BINARY, ODBC::SQL_VARBINARY, ODBC::SQL_LONGVARBINARY then :binary
38
- # SQL_DATETIME is an alias for SQL_DATE in ODBC's sql.h & sqlext.h
39
- when ODBC::SQL_DATE, ODBC::SQL_TYPE_DATE, ODBC::SQL_DATETIME then :date
40
- when ODBC::SQL_TIME, ODBC::SQL_TYPE_TIME then :time
41
- when ODBC::SQL_TIMESTAMP, ODBC::SQL_TYPE_TIMESTAMP then :timestamp
42
- when ODBC::SQL_GUID then :string
43
- else
44
- # when SQL_UNKNOWN_TYPE
45
- # (ruby-odbc driver doesn't support following ODBC SQL types:
46
- # SQL_WCHAR, SQL_WVARCHAR, SQL_WLONGVARCHAR, SQL_INTERVAL_xxx)
47
- raise ArgumentError, "Unsupported ODBC SQL type [#{odbcSqlType}]"
13
+ if [ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC].include?(sql_type)
14
+ set_numeric_params(scale, limit)
48
15
  end
49
16
  end
50
17
 
51
- # Ignore the ODBC precision of SQL types which don't take
52
- # an explicit precision when defining a column
53
- def extract_precision(sql_type, precision)
54
- precision if [ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC].include?(sql_type)
55
- end
56
-
57
- # Ignore the ODBC scale of SQL types which don't take
58
- # an explicit scale when defining a column
59
- def extract_scale(sql_type, scale)
60
- scale || 0 if [ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC].include?(sql_type)
61
- end
18
+ private
62
19
 
63
- def numeric_type(scale, native_types)
64
- scale.nil? || scale == 0 ? :integer : (native_types[:decimal].nil? ? :float : :decimal)
20
+ def set_numeric_params(scale, limit)
21
+ @cast_type.instance_variable_set(:@scale, scale || 0)
22
+ @cast_type.instance_variable_set(:@precision, limit)
65
23
  end
66
24
  end
67
25
  end
@@ -21,7 +21,6 @@ module ODBCAdapter
21
21
  @adapter = adapter
22
22
  end
23
23
 
24
- # TODO: implement boolean column surrogates
25
24
  def native_database_types
26
25
  grouped = reported_types.group_by { |row| row[1] }
27
26
 
@@ -18,7 +18,8 @@ module ODBCAdapter
18
18
 
19
19
  # Executes the SQL statement in the context of this connection.
20
20
  # Returns the number of rows affected.
21
- def execute(sql, name = nil)
21
+ # TODO: Currently ignoring binds until we can get prepared statements working.
22
+ def execute(sql, name = nil, binds = [])
22
23
  log(sql, name) do
23
24
  @connection.do(sql)
24
25
  end
@@ -47,6 +48,14 @@ module ODBCAdapter
47
48
  end
48
49
  end
49
50
 
51
+ # Executes delete +sql+ statement in the context of this connection using
52
+ # +binds+ as the bind substitutes. +name+ is logged along with
53
+ # the executed +sql+ statement.
54
+ def exec_delete(sql, name, binds)
55
+ execute(sql, name, binds)
56
+ end
57
+ alias :exec_update :exec_delete
58
+
50
59
  # Begins the transaction (and turns off auto-committing).
51
60
  def begin_db_transaction
52
61
  @connection.autocommit = false
@@ -60,7 +69,7 @@ module ODBCAdapter
60
69
 
61
70
  # Rolls back the transaction (and turns on auto-committing). Must be
62
71
  # done if the transaction block raises an exception or returns false.
63
- def rollback_db_transaction
72
+ def exec_rollback_db_transaction
64
73
  @connection.rollback
65
74
  @connection.autocommit = true
66
75
  end
@@ -72,112 +81,8 @@ module ODBCAdapter
72
81
  "#{table}_seq"
73
82
  end
74
83
 
75
- def recreate_database(name, options = {})
76
- drop_database(name)
77
- create_database(name, options)
78
- end
79
-
80
- def current_database
81
- dbms.field_for(ODBC::SQL_DATABASE_NAME).strip
82
- end
83
-
84
- # Returns an array of table names, for database tables visible on the
85
- # current connection.
86
- def tables(_name = nil)
87
- stmt = @connection.tables
88
- result = stmt.fetch_all || []
89
- stmt.drop
90
-
91
- result.each_with_object([]) do |row, table_names|
92
- schema_name, table_name, table_type = row[1..3]
93
- next if respond_to?(:table_filtered?) && table_filtered?(schema_name, table_type)
94
- table_names << format_case(table_name)
95
- end
96
- end
97
-
98
- # The class of the column to instantiate
99
- def column_class
100
- ::ODBCAdapter::Column
101
- end
102
-
103
- # Returns an array of Column objects for the table specified by +table_name+.
104
- def columns(table_name, name = nil)
105
- stmt = @connection.columns(native_case(table_name.to_s))
106
- result = stmt.fetch_all || []
107
- stmt.drop
108
-
109
- result.each_with_object([]) do |col, cols|
110
- col_name = col[3] # SQLColumns: COLUMN_NAME
111
- col_default = col[12] # SQLColumns: COLUMN_DEF
112
- col_sql_type = col[4] # SQLColumns: DATA_TYPE
113
- col_native_type = col[5] # SQLColumns: TYPE_NAME
114
- col_limit = col[6] # SQLColumns: COLUMN_SIZE
115
- col_scale = col[8] # SQLColumns: DECIMAL_DIGITS
116
-
117
- # SQLColumns: IS_NULLABLE, SQLColumns: NULLABLE
118
- col_nullable = nullability(col_name, col[17], col[10])
119
-
120
- cols << column_class.new(format_case(col_name), col_default, col_sql_type, col_native_type, col_nullable, col_scale, native_database_types, col_limit)
121
- end
122
- end
123
-
124
- # Returns an array of indexes for the given table.
125
- def indexes(table_name, name = nil)
126
- stmt = @connection.indexes(native_case(table_name.to_s))
127
- result = stmt.fetch_all || []
128
- stmt.drop unless stmt.nil?
129
-
130
- index_cols = []
131
- index_name = nil
132
- unique = nil
133
-
134
- result.each_with_object([]).with_index do |(row, indices), row_idx|
135
- # Skip table statistics
136
- next if row[6] == 0 # SQLStatistics: TYPE
137
-
138
- if row[7] == 1 # SQLStatistics: ORDINAL_POSITION
139
- # Start of column descriptor block for next index
140
- index_cols = []
141
- unique = row[3].zero? # SQLStatistics: NON_UNIQUE
142
- index_name = String.new(row[5]) # SQLStatistics: INDEX_NAME
143
- end
144
-
145
- index_cols << format_case(row[8]) # SQLStatistics: COLUMN_NAME
146
- next_row = result[row_idx + 1]
147
-
148
- if (row_idx == result.length - 1) || (next_row[6] == 0 || next_row[7] == 1)
149
- indices << IndexDefinition.new(table_name, format_case(index_name), unique, index_cols)
150
- end
151
- end
152
- end
153
-
154
- # Returns just a table's primary key
155
- def primary_key(table_name)
156
- stmt = @connection.primary_keys(native_case(table_name.to_s))
157
- result = stmt.fetch_all || []
158
- stmt.drop unless stmt.nil?
159
- result[0] && result[0][3]
160
- end
161
-
162
- ERR_DUPLICATE_KEY_VALUE = 23505
163
-
164
- def translate_exception(exception, message)
165
- case exception.message[/^\d+/].to_i
166
- when ERR_DUPLICATE_KEY_VALUE
167
- ActiveRecord::RecordNotUnique.new(message, exception)
168
- else
169
- super
170
- end
171
- end
172
-
173
84
  protected
174
85
 
175
- # Returns an array of record hashes with the column names as keys and
176
- # column values as values.
177
- def select(sql, name = nil, binds = [])
178
- exec_query(sql, name, binds).to_a
179
- end
180
-
181
86
  # Returns the last auto-generated ID from the affected table.
182
87
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
183
88
  begin
@@ -1,44 +1,5 @@
1
1
  module ODBCAdapter
2
2
  module Quoting
3
- # Quotes the column value to help prevent
4
- # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
5
- def quote(value, column = nil)
6
- # records are quoted as their primary key
7
- return value.quoted_id if value.respond_to?(:quoted_id)
8
-
9
- case value
10
- when String, ActiveSupport::Multibyte::Chars
11
- value = value.to_s
12
- return "'#{quote_string(value)}'" unless column
13
-
14
- case column.type
15
- when :binary then "'#{quote_string(column.string_to_binary(value))}'"
16
- when :integer then value.to_i.to_s
17
- when :float then value.to_f.to_s
18
- else
19
- "'#{quote_string(value)}'"
20
- end
21
-
22
- when true, false
23
- if column && column.type == :integer
24
- value ? '1' : '0'
25
- else
26
- value ? quoted_true : quoted_false
27
- end
28
- # BigDecimals need to be put in a non-normalized form and quoted.
29
- when nil then "NULL"
30
- when BigDecimal then value.to_s('F')
31
- when Numeric then value.to_s
32
- when Symbol then "'#{quote_string(value.to_s)}'"
33
- else
34
- if value.acts_like?(:date) || value.acts_like?(:time)
35
- quoted_date(value)
36
- else
37
- super
38
- end
39
- end
40
- end
41
-
42
3
  # Quotes a string, escaping any ' (single quote) characters.
43
4
  def quote_string(string)
44
5
  string.gsub(/\'/, "''")
@@ -71,10 +32,15 @@ module ODBCAdapter
71
32
  # Ideally, we'd return an ODBC date or timestamp literal escape
72
33
  # sequence, but not all ODBC drivers support them.
73
34
  def quoted_date(value)
74
- if value.acts_like?(:time) # Time, DateTime
75
- "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
76
- else # Date
77
- "'#{value.strftime("%Y-%m-%d")}'"
35
+ if value.acts_like?(:time)
36
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
37
+
38
+ if value.respond_to?(zone_conversion_method)
39
+ value = value.send(zone_conversion_method)
40
+ end
41
+ value.strftime("%Y-%m-%d %H:%M:%S") # Time, DateTime
42
+ else
43
+ value.strftime("%Y-%m-%d") # Date
78
44
  end
79
45
  end
80
46
  end
@@ -12,5 +12,83 @@ module ODBCAdapter
12
12
  maximum = dbms.field_for(ODBC::SQL_MAX_IDENTIFIER_LEN) || 255
13
13
  super(table_name, options)[0...maximum]
14
14
  end
15
+
16
+ def current_database
17
+ dbms.field_for(ODBC::SQL_DATABASE_NAME).strip
18
+ end
19
+
20
+ # Returns an array of table names, for database tables visible on the
21
+ # current connection.
22
+ def tables(_name = nil)
23
+ stmt = @connection.tables
24
+ result = stmt.fetch_all || []
25
+ stmt.drop
26
+
27
+ result.each_with_object([]) do |row, table_names|
28
+ schema_name, table_name, table_type = row[1..3]
29
+ next if respond_to?(:table_filtered?) && table_filtered?(schema_name, table_type)
30
+ table_names << format_case(table_name)
31
+ end
32
+ end
33
+
34
+ # Returns an array of Column objects for the table specified by +table_name+.
35
+ def columns(table_name, name = nil)
36
+ stmt = @connection.columns(native_case(table_name.to_s))
37
+ result = stmt.fetch_all || []
38
+ stmt.drop
39
+
40
+ result.each_with_object([]) do |col, cols|
41
+ col_name = col[3] # SQLColumns: COLUMN_NAME
42
+ col_default = col[12] # SQLColumns: COLUMN_DEF
43
+ col_sql_type = col[4] # SQLColumns: DATA_TYPE
44
+ col_native_type = col[5] # SQLColumns: TYPE_NAME
45
+ col_limit = col[6] # SQLColumns: COLUMN_SIZE
46
+ col_scale = col[8] # SQLColumns: DECIMAL_DIGITS
47
+
48
+ # SQLColumns: IS_NULLABLE, SQLColumns: NULLABLE
49
+ col_nullable = nullability(col_name, col[17], col[10])
50
+
51
+ cast_type = lookup_cast_type(col_sql_type)
52
+ cols << new_column(format_case(col_name), col_default, cast_type, col_sql_type, col_nullable, col_native_type, col_scale, col_limit)
53
+ end
54
+ end
55
+
56
+ # Returns an array of indexes for the given table.
57
+ def indexes(table_name, name = nil)
58
+ stmt = @connection.indexes(native_case(table_name.to_s))
59
+ result = stmt.fetch_all || []
60
+ stmt.drop unless stmt.nil?
61
+
62
+ index_cols = []
63
+ index_name = nil
64
+ unique = nil
65
+
66
+ result.each_with_object([]).with_index do |(row, indices), row_idx|
67
+ # Skip table statistics
68
+ next if row[6] == 0 # SQLStatistics: TYPE
69
+
70
+ if row[7] == 1 # SQLStatistics: ORDINAL_POSITION
71
+ # Start of column descriptor block for next index
72
+ index_cols = []
73
+ unique = row[3].zero? # SQLStatistics: NON_UNIQUE
74
+ index_name = String.new(row[5]) # SQLStatistics: INDEX_NAME
75
+ end
76
+
77
+ index_cols << format_case(row[8]) # SQLStatistics: COLUMN_NAME
78
+ next_row = result[row_idx + 1]
79
+
80
+ if (row_idx == result.length - 1) || (next_row[6] == 0 || next_row[7] == 1)
81
+ indices << IndexDefinition.new(table_name, format_case(index_name), unique, index_cols)
82
+ end
83
+ end
84
+ end
85
+
86
+ # Returns just a table's primary key
87
+ def primary_key(table_name)
88
+ stmt = @connection.primary_keys(native_case(table_name.to_s))
89
+ result = stmt.fetch_all || []
90
+ stmt.drop unless stmt.nil?
91
+ result[0] && result[0][3]
92
+ end
15
93
  end
16
94
  end
@@ -1,3 +1,3 @@
1
1
  module ODBCAdapter
2
- VERSION = '3.2.0'
2
+ VERSION = '4.2.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: odbc_adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Localytics