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 +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