odbc_adapter 3.2.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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