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 +4 -4
- data/Gemfile +1 -1
- data/lib/active_record/connection_adapters/odbc_adapter.rb +57 -0
- data/lib/odbc_adapter/adapters/mysql_odbc_adapter.rb +7 -3
- data/lib/odbc_adapter/adapters/postgresql_odbc_adapter.rb +4 -49
- data/lib/odbc_adapter/column.rb +12 -54
- data/lib/odbc_adapter/column_metadata.rb +0 -1
- data/lib/odbc_adapter/database_statements.rb +11 -106
- data/lib/odbc_adapter/quoting.rb +9 -43
- data/lib/odbc_adapter/schema_statements.rb +78 -0
- data/lib/odbc_adapter/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 791ccc95087c9361555daf8c3f464da82ac3b003
|
4
|
+
data.tar.gz: cc8210e283ac73556cd90c89b7fc6902a7611cd2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3aab9df767fd6f5079549eb4c075d4a769d27d53d7d2ec769ddde3ca1c063fa78446d025cae91ca8bc815e35871a5370ba178fb36248be61d2b964d63882b3a7
|
7
|
+
data.tar.gz: 22c3bbef81248cc70ee6bc1e69c16d1bec90ddc7795cb5a6a4db54ff969cfd6e8769591c370222042dc1431b7447a34d9de4888496cec5b8bf2334dbf68890e7
|
data/Gemfile
CHANGED
@@ -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.
|
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].
|
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
|
-
|
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
|
data/lib/odbc_adapter/column.rb
CHANGED
@@ -1,67 +1,25 @@
|
|
1
1
|
module ODBCAdapter
|
2
2
|
class Column < ActiveRecord::ConnectionAdapters::Column
|
3
|
-
|
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
|
-
@
|
7
|
-
@
|
8
|
+
@cast_type = cast_type
|
9
|
+
@sql_type = sql_type
|
8
10
|
@null = null
|
9
|
-
@
|
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
|
-
|
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
|
-
|
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
|
64
|
-
scale
|
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
|
@@ -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
|
-
|
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
|
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
|
data/lib/odbc_adapter/quoting.rb
CHANGED
@@ -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)
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
data/lib/odbc_adapter/version.rb
CHANGED