odbc_adapter 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,230 @@
1
+ module ODBCAdapter
2
+ module Adapters
3
+ # Overrides specific to PostgreSQL. Mostly taken from
4
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
5
+ class PostgreSQLODBCAdapter < ActiveRecord::ConnectionAdapters::ODBCAdapter
6
+ class BindSubstitution < Arel::Visitors::PostgreSQL
7
+ include Arel::Visitors::BindVisitor
8
+ end
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
+ PRIMARY_KEY = 'SERIAL PRIMARY KEY'
55
+
56
+ # Override the default column class
57
+ def column_class
58
+ PostgreSQLColumn
59
+ end
60
+
61
+ # Filter for ODBCAdapter#tables
62
+ # Omits table from #tables if table_filter returns true
63
+ def table_filter(schema_name, table_type)
64
+ %w[information_schema pg_catalog].include?(schema_name) || table_type !~ /TABLE/i
65
+ end
66
+
67
+ # Returns the sequence name for a table's primary key or some other specified key.
68
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
69
+ serial_sequence(table_name, pk || 'id').split('.').last
70
+ rescue ActiveRecord::StatementInvalid
71
+ "#{table_name}_#{pk || 'id'}_seq"
72
+ end
73
+
74
+ # Returns the current ID of a table's sequence.
75
+ def last_insert_id(sequence_name)
76
+ r = exec_query("SELECT currval('#{sequence_name}')", 'SQL')
77
+ Integer(r.rows.first.first)
78
+ end
79
+
80
+ # Executes an INSERT query and returns the new record's ID
81
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
82
+ unless pk
83
+ table_ref = extract_table_ref_from_insert_sql(sql)
84
+ pk = primary_key(table_ref) if table_ref
85
+ end
86
+
87
+ if pk
88
+ select_value("#{sql} RETURNING #{quote_column_name(pk)}")
89
+ else
90
+ super
91
+ end
92
+ end
93
+ alias :create :insert
94
+
95
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
96
+ unless pk
97
+ table_ref = extract_table_ref_from_insert_sql(sql)
98
+ pk = primary_key(table_ref) if table_ref
99
+ end
100
+
101
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
102
+ [sql, binds]
103
+ end
104
+
105
+ def type_cast(value, column)
106
+ return super unless column
107
+
108
+ case value
109
+ when String
110
+ return super unless 'bytea' == column.sql_type
111
+ { value: value, format: 1 }
112
+ else
113
+ super
114
+ end
115
+ end
116
+
117
+ # Quotes a string, escaping any ' (single quote) and \ (backslash)
118
+ # characters.
119
+ def quote_string(string)
120
+ string.gsub(/\\/, '\&\&').gsub(/'/, "''")
121
+ end
122
+
123
+ def quoted_true
124
+ "'t'"
125
+ end
126
+
127
+ def quoted_false
128
+ "'f'"
129
+ end
130
+
131
+ def disable_referential_integrity #:nodoc:
132
+ execute(tables.map { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(';'))
133
+ yield
134
+ ensure
135
+ execute(tables.map { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(';'))
136
+ end
137
+
138
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
139
+ # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL
140
+ # uses <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
141
+ #
142
+ # Example:
143
+ # create_database config[:database], config
144
+ # create_database 'foo_development', :encoding => 'unicode'
145
+ def create_database(name, options = {})
146
+ options = options.reverse_merge(encoding: 'utf8')
147
+
148
+ option_string = options.symbolize_keys.sum do |key, value|
149
+ case key
150
+ when :owner
151
+ " OWNER = \"#{value}\""
152
+ when :template
153
+ " TEMPLATE = \"#{value}\""
154
+ when :encoding
155
+ " ENCODING = '#{value}'"
156
+ when :tablespace
157
+ " TABLESPACE = \"#{value}\""
158
+ when :connection_limit
159
+ " CONNECTION LIMIT = #{value}"
160
+ else
161
+ ""
162
+ end
163
+ end
164
+
165
+ execute("CREATE DATABASE #{quote_table_name(name)}#{option_string}")
166
+ end
167
+
168
+ # Drops a PostgreSQL database.
169
+ #
170
+ # Example:
171
+ # drop_database 'matt_development'
172
+ def drop_database(name) #:nodoc:
173
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
174
+ end
175
+
176
+ # Renames a table.
177
+ def rename_table(name, new_name)
178
+ execute("ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}")
179
+ end
180
+
181
+ def change_column(table_name, column_name, type, options = {})
182
+ execute("ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
183
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
184
+ end
185
+
186
+ def change_column_default(table_name, column_name, default)
187
+ execute("ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{quote(default)}")
188
+ end
189
+
190
+ def rename_column(table_name, column_name, new_column_name)
191
+ execute("ALTER TABLE #{table_name} RENAME #{column_name} TO #{new_column_name}")
192
+ end
193
+
194
+ def remove_index!(_table_name, index_name)
195
+ execute("DROP INDEX #{quote_table_name(index_name)}")
196
+ end
197
+
198
+ def rename_index(table_name, old_name, new_name)
199
+ execute("ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}")
200
+ end
201
+
202
+ # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
203
+ #
204
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
205
+ # requires that the ORDER BY include the distinct column.
206
+ #
207
+ # distinct("posts.id", "posts.created_at desc")
208
+ def distinct(columns, orders)
209
+ return "DISTINCT #{columns}" if orders.empty?
210
+
211
+ # Construct a clean list of column names from the ORDER BY clause, removing
212
+ # any ASC/DESC modifiers
213
+ order_columns = orders.map { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
214
+ order_columns.reject! { |c| c.blank? }
215
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
216
+
217
+ "DISTINCT #{columns}, #{order_columns * ', '}"
218
+ end
219
+
220
+ private
221
+
222
+ def serial_sequence(table, column)
223
+ result = exec_query(<<-eosql, 'SCHEMA')
224
+ SELECT pg_get_serial_sequence('#{table}', '#{column}')
225
+ eosql
226
+ result.rows.first.first
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,67 @@
1
+ module ODBCAdapter
2
+ class Column < ActiveRecord::ConnectionAdapters::Column
3
+ def initialize(name, default, sql_type, native_type, null = true, scale = nil, native_types = nil, limit = nil)
4
+ @name = name
5
+ @default = default
6
+ @sql_type = native_type.to_s
7
+ @native_type = native_type.to_s
8
+ @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
16
+
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}]"
48
+ end
49
+ end
50
+
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
62
+
63
+ def numeric_type(scale, native_types)
64
+ scale.nil? || scale == 0 ? :integer : (native_types[:decimal].nil? ? :float : :decimal)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,77 @@
1
+ module ODBCAdapter
2
+ class ColumnMetadata
3
+ GENERICS = {
4
+ primary_key: [ODBC::SQL_INTEGER, ODBC::SQL_SMALLINT],
5
+ string: [ODBC::SQL_VARCHAR],
6
+ text: [ODBC::SQL_LONGVARCHAR, ODBC::SQL_VARCHAR],
7
+ integer: [ODBC::SQL_INTEGER, ODBC::SQL_SMALLINT],
8
+ decimal: [ODBC::SQL_NUMERIC, ODBC::SQL_DECIMAL],
9
+ float: [ODBC::SQL_DOUBLE, ODBC::SQL_REAL],
10
+ datetime: [ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
11
+ timestamp: [ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
12
+ time: [ODBC::SQL_TYPE_TIME, ODBC::SQL_TIME, ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
13
+ date: [ODBC::SQL_TYPE_DATE, ODBC::SQL_DATE, ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
14
+ binary: [ODBC::SQL_LONGVARBINARY, ODBC::SQL_VARBINARY],
15
+ boolean: [ODBC::SQL_BIT, ODBC::SQL_TINYINT, ODBC::SQL_SMALLINT, ODBC::SQL_INTEGER]
16
+ }
17
+
18
+ attr_reader :adapter
19
+
20
+ def initialize(adapter)
21
+ @adapter = adapter
22
+ end
23
+
24
+ # TODO: implement boolean column surrogates
25
+ def native_database_types
26
+ grouped = reported_types.group_by { |row| row[1] }
27
+
28
+ GENERICS.each_with_object({}) do |(abstract, candidates), mapped|
29
+ candidates.detect do |candidate|
30
+ next unless grouped[candidate]
31
+ mapped[abstract] = native_type_mapping(abstract, grouped[candidate])
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # Creates a Hash describing a mapping from an abstract type to a
39
+ # DBMS native type for use by #native_database_types
40
+ def native_type_mapping(abstract, rows)
41
+ # The appropriate SQL for :primary_key is hard to derive as
42
+ # ODBC doesn't provide any info on a DBMS's native syntax for
43
+ # autoincrement columns. So we use a lookup instead.
44
+ return adapter.class::PRIMARY_KEY if abstract == :primary_key
45
+ selected_row = rows[0]
46
+
47
+ # If more than one native type corresponds to the SQL type we're
48
+ # handling, the type in the first descriptor should be the
49
+ # best match, because the ODBC specification states that
50
+ # SQLGetTypeInfo returns the results ordered by SQL type and then by
51
+ # how closely the native type maps to that SQL type.
52
+ # But, for :text and :binary, select the native type with the
53
+ # largest capacity. (Compare SQLGetTypeInfo:COLUMN_SIZE values)
54
+ selected_row = rows.max_by { |row| row[2] } if [:text, :binary].include?(abstract)
55
+ result = { name: selected_row[0] } # SQLGetTypeInfo: TYPE_NAME
56
+
57
+ create_params = selected_row[5]
58
+ # Depending on the column type, the CREATE_PARAMS keywords can
59
+ # include length, precision or scale.
60
+ if create_params && create_params.strip.length > 0 && abstract != :decimal
61
+ result[:limit] = selected_row[2] # SQLGetTypeInfo: COL_SIZE
62
+ end
63
+
64
+ result
65
+ end
66
+
67
+ def reported_types
68
+ @reported_types ||=
69
+ begin
70
+ stmt = adapter.raw_connection.types
71
+ stmt.fetch_all
72
+ ensure
73
+ stmt.drop unless stmt.nil?
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,10 @@
1
+ module ODBCAdapter
2
+ module DatabaseLimits
3
+ # Returns the maximum length of a table name.
4
+ def table_alias_length
5
+ max_identifier_length = dbms.field_for(ODBC::SQL_MAX_IDENTIFIER_LEN)
6
+ max_table_name_length = dbms.field_for(ODBC::SQL_MAX_TABLE_NAME_LEN)
7
+ [max_identifier_length, max_table_name_length].max
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,254 @@
1
+ module ODBCAdapter
2
+ module DatabaseStatements
3
+ # ODBC constants missing from Christian Werner's Ruby ODBC driver
4
+ SQL_NO_NULLS = 0
5
+ SQL_NULLABLE = 1
6
+ SQL_NULLABLE_UNKNOWN = 2
7
+
8
+ # Returns an array of arrays containing the field values.
9
+ # Order is the same as that returned by #columns.
10
+ def select_rows(sql, name = nil)
11
+ log(sql, name) do
12
+ stmt = @connection.run(sql)
13
+ result = stmt.fetch_all
14
+ stmt.drop
15
+ result
16
+ end
17
+ end
18
+
19
+ # Executes the SQL statement in the context of this connection.
20
+ # Returns the number of rows affected.
21
+ def execute(sql, name = nil)
22
+ log(sql, name) do
23
+ @connection.do(sql)
24
+ end
25
+ end
26
+
27
+ # Executes +sql+ statement in the context of this connection using
28
+ # +binds+ as the bind substitutes. +name+ is logged along with
29
+ # the executed +sql+ statement.
30
+ def exec_query(sql, name = 'SQL', binds = [])
31
+ log(sql, name) do
32
+ stmt = @connection.run(sql)
33
+ columns = stmt.columns
34
+ values = stmt.to_a
35
+ stmt.drop
36
+
37
+ casters = TypeCaster.build_from(columns.values)
38
+ if casters.any?
39
+ values.each do |row|
40
+ casters.each { |caster| row[caster.idx] = caster.cast(row[caster.idx]) }
41
+ end
42
+ end
43
+
44
+ values = dbms_type_cast(columns.values, values)
45
+ column_names = columns.keys.map { |key| format_case(key) }
46
+ result = ActiveRecord::Result.new(column_names, values)
47
+ end
48
+ end
49
+
50
+ # Begins the transaction (and turns off auto-committing).
51
+ def begin_db_transaction
52
+ @connection.autocommit = false
53
+ end
54
+
55
+ # Commits the transaction (and turns on auto-committing).
56
+ def commit_db_transaction
57
+ @connection.commit
58
+ @connection.autocommit = true
59
+ end
60
+
61
+ # Rolls back the transaction (and turns on auto-committing). Must be
62
+ # done if the transaction block raises an exception or returns false.
63
+ def rollback_db_transaction
64
+ @connection.rollback
65
+ @connection.autocommit = true
66
+ end
67
+
68
+ # Returns the default sequence name for a table.
69
+ # Used for databases which don't support an autoincrementing column
70
+ # type, but do support sequences.
71
+ def default_sequence_name(table, _column)
72
+ "#{table}_seq"
73
+ end
74
+
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
+ protected
174
+
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
+ # Returns the last auto-generated ID from the affected table.
182
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
183
+ begin
184
+ stmt = log(sql, name) { @connection.run(sql) }
185
+ table = extract_table_ref_from_insert_sql(sql)
186
+
187
+ seq = sequence_name || default_sequence_name(table, pk)
188
+ res = id_value || last_insert_id(table, seq, stmt)
189
+ ensure
190
+ stmt.drop unless stmt.nil?
191
+ end
192
+ res
193
+ end
194
+
195
+ private
196
+
197
+ def dbms_type_cast(columns, values)
198
+ values
199
+ end
200
+
201
+ def extract_table_ref_from_insert_sql(sql)
202
+ sql[/into\s+([^\(]*).*values\s*\(/i]
203
+ $1.strip if $1
204
+ end
205
+
206
+ # Assume received identifier is in DBMS's data dictionary case.
207
+ def format_case(identifier)
208
+ case dbms.field_for(ODBC::SQL_IDENTIFIER_CASE)
209
+ when ODBC::SQL_IC_UPPER
210
+ identifier =~ /[a-z]/ ? identifier : identifier.downcase
211
+ else
212
+ identifier
213
+ end
214
+ end
215
+
216
+ # In general, ActiveRecord uses lowercase attribute names. This may
217
+ # conflict with the database's data dictionary case.
218
+ #
219
+ # The ODBCAdapter uses the following conventions for databases
220
+ # which report SQL_IDENTIFIER_CASE = SQL_IC_UPPER:
221
+ # * if a name is returned from the DBMS in all uppercase, convert it
222
+ # to lowercase before returning it to ActiveRecord.
223
+ # * if a name is returned from the DBMS in lowercase or mixed case,
224
+ # assume the underlying schema object's name was quoted when
225
+ # the schema object was created. Leave the name untouched before
226
+ # returning it to ActiveRecord.
227
+ # * before making an ODBC catalog call, if a supplied identifier is all
228
+ # lowercase, convert it to uppercase. Leave mixed case or all
229
+ # uppercase identifiers unchanged.
230
+ # * columns created with quoted lowercase names are not supported.
231
+ #
232
+ # Converts an identifier to the case conventions used by the DBMS.
233
+ # Assume received identifier is in ActiveRecord case.
234
+ def native_case(identifier)
235
+ case dbms.field_for(ODBC::SQL_IDENTIFIER_CASE)
236
+ when ODBC::SQL_IC_UPPER
237
+ identifier =~ /[A-Z]/ ? identifier : identifier.upcase
238
+ else
239
+ identifier
240
+ end
241
+ end
242
+
243
+ # Assume column is nullable if nullable == SQL_NULLABLE_UNKNOWN
244
+ def nullability(col_name, is_nullable, nullable)
245
+ not_nullable = (!is_nullable || nullable.to_s.match('NO') != nil)
246
+ result = !(not_nullable || nullable == SQL_NO_NULLS)
247
+
248
+ # HACK!
249
+ # MySQL native ODBC driver doesn't report nullability accurately.
250
+ # So force nullability of 'id' columns
251
+ col_name == 'id' ? false : result
252
+ end
253
+ end
254
+ end