activerecord-odbc-adapter 2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +16 -0
- data/COPYING +21 -0
- data/ChangeLog +139 -0
- data/LICENSE +5 -0
- data/NEWS +25 -0
- data/README +229 -0
- data/lib/active_record/connection_adapters/odbc_adapter.rb +1950 -0
- data/lib/active_record/vendor/odbcext_db2.rb +87 -0
- data/lib/active_record/vendor/odbcext_informix.rb +144 -0
- data/lib/active_record/vendor/odbcext_informix_col.rb +45 -0
- data/lib/active_record/vendor/odbcext_ingres.rb +156 -0
- data/lib/active_record/vendor/odbcext_microsoftsqlserver.rb +216 -0
- data/lib/active_record/vendor/odbcext_microsoftsqlserver_col.rb +40 -0
- data/lib/active_record/vendor/odbcext_mysql.rb +174 -0
- data/lib/active_record/vendor/odbcext_oracle.rb +219 -0
- data/lib/active_record/vendor/odbcext_postgresql.rb +158 -0
- data/lib/active_record/vendor/odbcext_progress.rb +139 -0
- data/lib/active_record/vendor/odbcext_progress89.rb +259 -0
- data/lib/active_record/vendor/odbcext_sqlanywhere.rb +115 -0
- data/lib/active_record/vendor/odbcext_sqlanywhere_col.rb +49 -0
- data/lib/active_record/vendor/odbcext_sybase.rb +213 -0
- data/lib/active_record/vendor/odbcext_sybase_col.rb +49 -0
- data/lib/active_record/vendor/odbcext_virtuoso.rb +158 -0
- data/lib/odbc_adapter.rb +28 -0
- data/support/lib/active_record/connection_adapters/abstract/schema_definitions.rb +259 -0
- data/support/odbc_rails.diff +367 -0
- data/support/pack_odbc.rb +119 -0
- data/support/rake/rails_plugin_package_task.rb +212 -0
- data/support/rake_fixes/README +6 -0
- data/support/rake_fixes/databases.dif +13 -0
- data/support/test/base_test.rb +1765 -0
- data/support/test/migration_test.rb +1007 -0
- data/test/connections/native_odbc/connection.rb +137 -0
- data/test/fixtures/db_definitions/db2.drop.sql +33 -0
- data/test/fixtures/db_definitions/db2.sql +237 -0
- data/test/fixtures/db_definitions/db22.drop.sql +2 -0
- data/test/fixtures/db_definitions/db22.sql +5 -0
- data/test/fixtures/db_definitions/informix.drop.sql +33 -0
- data/test/fixtures/db_definitions/informix.sql +223 -0
- data/test/fixtures/db_definitions/informix2.drop.sql +2 -0
- data/test/fixtures/db_definitions/informix2.sql +5 -0
- data/test/fixtures/db_definitions/ingres.drop.sql +68 -0
- data/test/fixtures/db_definitions/ingres.sql +252 -0
- data/test/fixtures/db_definitions/ingres2.drop.sql +2 -0
- data/test/fixtures/db_definitions/ingres2.sql +5 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +33 -0
- data/test/fixtures/db_definitions/mysql.sql +238 -0
- data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql2.sql +5 -0
- data/test/fixtures/db_definitions/oracle_odbc.drop.sql +72 -0
- data/test/fixtures/db_definitions/oracle_odbc.sql +296 -0
- data/test/fixtures/db_definitions/oracle_odbc2.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle_odbc2.sql +6 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +38 -0
- data/test/fixtures/db_definitions/postgresql.sql +267 -0
- data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/postgresql2.sql +5 -0
- data/test/fixtures/db_definitions/progress.drop.sql +67 -0
- data/test/fixtures/db_definitions/progress.sql +255 -0
- data/test/fixtures/db_definitions/progress2.drop.sql +2 -0
- data/test/fixtures/db_definitions/progress2.sql +6 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +35 -0
- data/test/fixtures/db_definitions/sqlserver.sql +247 -0
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
- data/test/fixtures/db_definitions/sybase.sql +222 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/db_definitions/virtuoso.drop.sql +33 -0
- data/test/fixtures/db_definitions/virtuoso.sql +218 -0
- data/test/fixtures/db_definitions/virtuoso2.drop.sql +2 -0
- data/test/fixtures/db_definitions/virtuoso2.sql +5 -0
- metadata +166 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
#
|
2
|
+
# $Id: odbcext_progress.rb,v 1.3 2008/04/13 22:46:09 source Exp $
|
3
|
+
#
|
4
|
+
# OpenLink ODBC Adapter for Ruby on Rails
|
5
|
+
# Extension module for Progress v9 and later using SQL-92 engine
|
6
|
+
# Copyright (C) 2006 OpenLink Software
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
9
|
+
# a copy of this software and associated documentation files (the
|
10
|
+
# "Software"), to deal in the Software without restriction, including
|
11
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
12
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
13
|
+
# permit persons to whom the Software is furnished to do so, subject
|
14
|
+
# to the following conditions:
|
15
|
+
#
|
16
|
+
# The above copyright notice and this permission notice shall be
|
17
|
+
# included in all copies or substantial portions of the Software.
|
18
|
+
#
|
19
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
20
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
21
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
22
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
23
|
+
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
24
|
+
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
25
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26
|
+
#
|
27
|
+
|
28
|
+
module ODBCExt
|
29
|
+
|
30
|
+
# ------------------------------------------------------------------------
|
31
|
+
# Mandatory methods
|
32
|
+
#
|
33
|
+
|
34
|
+
# #last_insert_id must be implemented for any database which returns
|
35
|
+
# false from #prefetch_primary_key?
|
36
|
+
# (This adapter returns true for Progress)
|
37
|
+
#def last_insert_id(table, sequence_name, stmt = nil)
|
38
|
+
#end
|
39
|
+
|
40
|
+
# #next_sequence_value must be implemented for any database which returns
|
41
|
+
# true from #prefetch_primary_key?
|
42
|
+
#
|
43
|
+
# Returns the next sequence value from a sequence generator. Not generally
|
44
|
+
# called directly; used by ActiveRecord to get the next primary key value
|
45
|
+
# when inserting a new database record (see #prefetch_primary_key?).
|
46
|
+
def next_sequence_value(sequence_name)
|
47
|
+
@logger.unknown("ODBCAdapter#next_sequence_value>") if @trace
|
48
|
+
#@logger.unknown("args=[#{sequence_name}]") if @trace
|
49
|
+
select_one("select PUB.#{sequence_name}.NEXTVAL from SYSPROGRESS.SYSCALCTABLE")['sequence_next']
|
50
|
+
end
|
51
|
+
|
52
|
+
# ------------------------------------------------------------------------
|
53
|
+
# Method redefinitions
|
54
|
+
#
|
55
|
+
# DBMS specific methods which override the default implementation
|
56
|
+
# provided by the ODBCAdapter core.
|
57
|
+
|
58
|
+
def quote_column_name(name)
|
59
|
+
@logger.unknown("ODBCAdapter#quote_column_name>") if @trace
|
60
|
+
@logger.unknown("args=[#{name}]") if @trace
|
61
|
+
name = name.to_s if name.class == Symbol
|
62
|
+
idQuoteChar = @dsInfo.info[ODBC::SQL_IDENTIFIER_QUOTE_CHAR]
|
63
|
+
|
64
|
+
return name if !idQuoteChar || ((idQuoteChar = idQuoteChar.strip).length == 0)
|
65
|
+
idQuoteChar = idQuoteChar[0]
|
66
|
+
|
67
|
+
# Avoid quoting any already quoted name
|
68
|
+
return name if name[0] == idQuoteChar && name[-1] == idQuoteChar
|
69
|
+
|
70
|
+
# If a DBMS's SQL_IDENTIFIER_CASE is SQL_IC_UPPER, this adapter's base
|
71
|
+
# implementation of #quote_column_name only quotes mixed case names.
|
72
|
+
# But for Progress v9 or later, for which we force SQL_IDENTIFIER_CASE to
|
73
|
+
# SQL_IC_UPPER (see DSInfo#new), we want to quote *ALL* column names.
|
74
|
+
# This is done because many of the Rails tests and fixtures use a column
|
75
|
+
# named 'type', but type is a reserved word in Progress SQL. Progress 9
|
76
|
+
# accepts the quoting of all column names because its
|
77
|
+
# SQL_QUOTED_IDENTIFIER_CASE behaviour is SQL_IC_MIXED.
|
78
|
+
idQuoteChar.chr + name + idQuoteChar.chr
|
79
|
+
end
|
80
|
+
|
81
|
+
def create_table(name, options = {})
|
82
|
+
@logger.unknown("ODBCAdapter#create_table>") if @trace
|
83
|
+
super(name, options)
|
84
|
+
# Some ActiveRecord tests insert using an explicit id value. Starting the
|
85
|
+
# primary key sequence from 10000 eliminates collisions (and subsequent
|
86
|
+
# complaints from Progress of integrity constraint violations) between id's
|
87
|
+
# generated from the sequence and explicitly supplied ids.
|
88
|
+
# Using explicit and generated id's together should be avoided.
|
89
|
+
#
|
90
|
+
# Currently, OpenEdge only supports sequences in the PUBLIC (PUB) schema.
|
91
|
+
execute "CREATE SEQUENCE PUB.#{name}_seq MINVALUE 10000" unless options[:id] == false
|
92
|
+
rescue Exception => e
|
93
|
+
@logger.unknown("exception=#{e}") if @trace
|
94
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
95
|
+
end
|
96
|
+
|
97
|
+
def drop_table(name, options = {})
|
98
|
+
@logger.unknown("ODBCAdapter#drop_table>") if @trace
|
99
|
+
super(name, options)
|
100
|
+
execute "DROP SEQUENCE PUB.#{name}_seq"
|
101
|
+
rescue Exception => e
|
102
|
+
if e.message !~ /10520/
|
103
|
+
# Error "Sequence not found. (10520)" will be generated
|
104
|
+
# if the table was created with options[:id] == false
|
105
|
+
@logger.unknown("exception=#{e}") if @trace
|
106
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def change_column_default(table_name, column_name, default)
|
111
|
+
@logger.unknown("ODBCAdapter#change_column_default>") if @trace
|
112
|
+
@logger.unknown("args=[#{table_name}|#{column_name}]") if @trace
|
113
|
+
execute "ALTER TABLE #{table_name} ALTER #{column_name} SET DEFAULT #{quote(default)}"
|
114
|
+
rescue Exception => e
|
115
|
+
@logger.unknown("exception=#{e}") if @trace
|
116
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
117
|
+
end
|
118
|
+
|
119
|
+
def remove_column(table_name, column_name)
|
120
|
+
@logger.unknown("ODBCAdapter#remove_column>") if @trace
|
121
|
+
# Although this command is documented in the OpenEdge SQL Reference,
|
122
|
+
# it returns error -20024 ("Sorry, operation not yet implemented").
|
123
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
124
|
+
rescue Exception => e
|
125
|
+
@logger.unknown("exception=#{e}") if @trace
|
126
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
127
|
+
end
|
128
|
+
|
129
|
+
def tables(name = nil)
|
130
|
+
# Hide system tables.
|
131
|
+
super(name).delete_if {|t| t =~ /^sys/i }
|
132
|
+
end
|
133
|
+
|
134
|
+
def indexes(table_name, name = nil)
|
135
|
+
# Hide primary key indexes
|
136
|
+
super(table_name, name).delete_if { |i| i.unique && i.name =~ /^sys/i }
|
137
|
+
end
|
138
|
+
|
139
|
+
end # module
|
@@ -0,0 +1,259 @@
|
|
1
|
+
#
|
2
|
+
# $Id: odbcext_progress89.rb,v 1.3 2008/04/13 22:46:09 source Exp $
|
3
|
+
#
|
4
|
+
# OpenLink ODBC Adapter for Ruby on Rails
|
5
|
+
# Extension module for Progress v8 and earlier using SQL-89 engine
|
6
|
+
#
|
7
|
+
# Copyright (C) 2006 OpenLink Software
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
# a copy of this software and associated documentation files (the
|
11
|
+
# "Software"), to deal in the Software without restriction, including
|
12
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
# permit persons to whom the Software is furnished to do so, subject
|
15
|
+
# to the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be
|
18
|
+
# included in all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
23
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
24
|
+
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
25
|
+
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
+
#
|
28
|
+
|
29
|
+
module ODBCExt
|
30
|
+
|
31
|
+
# ------------------------------------------------------------------------
|
32
|
+
# Mandatory methods
|
33
|
+
#
|
34
|
+
|
35
|
+
# #last_insert_id must be implemented for any database which returns
|
36
|
+
# false from #prefetch_primary_key?
|
37
|
+
# (This adapter returns true for Progress)
|
38
|
+
#def last_insert_id(table, sequence_name, stmt = nil)
|
39
|
+
#end
|
40
|
+
|
41
|
+
# #next_sequence_value must be implemented for any database which returns
|
42
|
+
# true from #prefetch_primary_key?
|
43
|
+
#
|
44
|
+
# Returns the next sequence value from a sequence generator. Not generally
|
45
|
+
# called directly; used by ActiveRecord to get the next primary key value
|
46
|
+
# when inserting a new database record (see #prefetch_primary_key?).
|
47
|
+
def next_sequence_value(sequence_name)
|
48
|
+
@logger.unknown("ODBCAdapter#next_sequence_value>") if @trace
|
49
|
+
#@logger.unknown("args=[#{sequence_name}]") if @trace
|
50
|
+
sequence_next_val(sequence_name.to_s)
|
51
|
+
rescue Exception => e
|
52
|
+
@logger.unknown("exception=#{e}") if @trace
|
53
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
54
|
+
end
|
55
|
+
|
56
|
+
# ------------------------------------------------------------------------
|
57
|
+
# Method redefinitions
|
58
|
+
#
|
59
|
+
# DBMS specific methods which override the default implementation
|
60
|
+
# provided by the ODBCAdapter core.
|
61
|
+
|
62
|
+
# Progress SQL89 requires that the DEFAULT specification *follows*
|
63
|
+
# any NOT NULL constraint.
|
64
|
+
def add_column_options!(sql, options) #:nodoc:
|
65
|
+
@logger.unknown("ODBCAdapter#add_column_options!>") if @trace
|
66
|
+
@logger.unknown("args=[#{sql}]") if @trace
|
67
|
+
sql << " NOT NULL" if options[:null] == false
|
68
|
+
# Progress 89 doesn't accept 'DEFAULT NULL'
|
69
|
+
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options) && !options[:default].nil?
|
70
|
+
rescue Exception => e
|
71
|
+
@logger.unknown("exception=#{e}") if @trace
|
72
|
+
raise StatementInvalid, e.message
|
73
|
+
end
|
74
|
+
|
75
|
+
def quote_column_name(name)
|
76
|
+
@logger.unknown("ODBCAdapter#quote_column_name>") if @trace
|
77
|
+
@logger.unknown("args=[#{name}]") if @trace
|
78
|
+
# Progress v8 or earlier doesn't support quoted identifiers.
|
79
|
+
# ODBC::SQL_IDENTIFIER_QUOTE_CHAR is typically " "
|
80
|
+
name.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
def quoted_date(value)
|
84
|
+
@logger.unknown("ODBCAdapter#quoted_date>") if @trace
|
85
|
+
@logger.unknown("args=[#{value}]") if @trace
|
86
|
+
# Progress v8 doesn't support a datetime or time type,
|
87
|
+
# only a date type.
|
88
|
+
if value.acts_like?(:time) # Time, DateTime
|
89
|
+
#%Q!{ts '#{value.strftime("%Y-%m-%d %H:%M:%S")}'}!
|
90
|
+
%Q!{d '#{value.strftime("%Y-%m-%d")}'}!
|
91
|
+
else # Date
|
92
|
+
%Q!{d '#{value.strftime("%Y-%m-%d")}'}!
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Progress SQL-89 doesn't support column aliases
|
97
|
+
# Strip 'AS <alias>' from all selects
|
98
|
+
|
99
|
+
def select_all(sql, name = nil)
|
100
|
+
super(remove_select_column_aliases(sql), name)
|
101
|
+
end
|
102
|
+
|
103
|
+
def select_one(sql, name = nil)
|
104
|
+
super(remove_select_column_aliases(sql), name)
|
105
|
+
end
|
106
|
+
|
107
|
+
def select_value(sql, name = nil)
|
108
|
+
super(remove_select_column_aliases(sql), name)
|
109
|
+
end
|
110
|
+
|
111
|
+
def select_values(sql, name = nil)
|
112
|
+
super(remove_select_column_aliases(sql), name)
|
113
|
+
end
|
114
|
+
|
115
|
+
def indexes(table_name, name = nil)
|
116
|
+
# Hide primary key indexes
|
117
|
+
super(table_name, name).delete_if { |i| i.unique && i.name =~ /^sql/ }
|
118
|
+
end
|
119
|
+
|
120
|
+
def create_table(name, options = {})
|
121
|
+
@logger.unknown("ODBCAdapter#create_table>") if @trace
|
122
|
+
super(name, options)
|
123
|
+
# Some ActiveRecord tests insert using an explicit id value. Starting the
|
124
|
+
# primary key sequence from 10000 eliminates collisions (and subsequent
|
125
|
+
# complaints from Progress of integrity constraint violations) between id's
|
126
|
+
# generated from the sequence and explicitly supplied ids.
|
127
|
+
# Using explicit and generated id's together should be avoided.
|
128
|
+
create_sequence("#{name}_seq", 10000) unless options[:id] == false
|
129
|
+
rescue Exception => e
|
130
|
+
@logger.unknown("exception=#{e}") if @trace
|
131
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
132
|
+
end
|
133
|
+
|
134
|
+
def drop_table(name, options = {})
|
135
|
+
@logger.unknown("ODBCAdapter#drop_table>") if @trace
|
136
|
+
super(name, options)
|
137
|
+
drop_sequence("#{name}_seq")
|
138
|
+
rescue Exception => e
|
139
|
+
@logger.unknown("exception=#{e}") if @trace
|
140
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
141
|
+
end
|
142
|
+
|
143
|
+
def change_column_default(table_name, column_name, default)
|
144
|
+
@logger.unknown("ODBCAdapter#change_column_default>") if @trace
|
145
|
+
@logger.unknown("args=[#{table_name}|#{column_name}]") if @trace
|
146
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} DEFAULT #{quote(default)}"
|
147
|
+
rescue Exception => e
|
148
|
+
@logger.unknown("exception=#{e}") if @trace
|
149
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
150
|
+
end
|
151
|
+
|
152
|
+
def remove_column(table_name, column_name)
|
153
|
+
@logger.unknown("ODBCAdapter#remove_column>") if @trace
|
154
|
+
# Although this command is documented in the Progress SQL Reference,
|
155
|
+
# it returns error 247 ('Unable to understand after -- "alter") if
|
156
|
+
# executed via the ODBC driver. (Executing the same command through
|
157
|
+
# the Procedure Editor works)
|
158
|
+
execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
|
159
|
+
rescue Exception => e
|
160
|
+
@logger.unknown("exception=#{e}") if @trace
|
161
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
162
|
+
end
|
163
|
+
|
164
|
+
def remove_index(table_name, options = {})
|
165
|
+
@logger.unknown("ODBCAdapter#remove_index>") if @trace
|
166
|
+
execute "DROP INDEX #{index_name(table_name, options)}"
|
167
|
+
rescue Exception => e
|
168
|
+
@logger.unknown("exception=#{e}") if @trace
|
169
|
+
raise ActiveRecord::ActiveRecordError, e.message
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def remove_select_column_aliases(sql)
|
175
|
+
sql.gsub(/\s+as\s+\w+/i, '')
|
176
|
+
end
|
177
|
+
|
178
|
+
# ------------------------------------------------------------------------
|
179
|
+
# Sequence simulation
|
180
|
+
#
|
181
|
+
# It appears a native Progress (<= v8) sequence can only be created
|
182
|
+
# using the Data Dictionary tool, not through SQL89 DDL. Also, there's no
|
183
|
+
# way to retrieve a native sequence's next value through SQL89.
|
184
|
+
# Instead of using Progress's built-in sequences, we simulate them.
|
185
|
+
|
186
|
+
SEQ_DFLT_START_VAL = 10000
|
187
|
+
SEQS_DFLT_TBL_NAME = "railsseqs"
|
188
|
+
@@seqs_table_exists = false
|
189
|
+
|
190
|
+
# Creates a simulated sequence
|
191
|
+
def create_sequence(name, start_val = SEQ_DFLT_START_VAL)
|
192
|
+
raise ActiveRecordError, "sequence start value <= 0" if start_val <= 0
|
193
|
+
ensure_sequences_table unless @@seqs_table_exists
|
194
|
+
sql = "INSERT INTO #{SEQS_DFLT_TBL_NAME}(SEQ_NAME, SEQ_NEXT_VAL) VALUES ('#{name.upcase}', #{start_val})"
|
195
|
+
@connection.do(sql)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Drops a simulated sequence
|
199
|
+
def drop_sequence(name)
|
200
|
+
begin
|
201
|
+
sql = "DELETE FROM #{SEQS_DFLT_TBL_NAME} WHERE SEQ_NAME = '#{name.upcase}'"
|
202
|
+
@connection.do(sql)
|
203
|
+
rescue Exception => e
|
204
|
+
# Tables may be created without an accompanying sequence if
|
205
|
+
# #create_table wasn't used to create the table or :id => false was
|
206
|
+
# specified. So, the sequence table may not exist. Trap error 962:
|
207
|
+
# "Table <sequence table name> does not exist or cannot be accessed"
|
208
|
+
raise unless e.message =~ /962/
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def sequence_next_val(name)
|
213
|
+
begin
|
214
|
+
begin_db_transaction
|
215
|
+
sql = "SELECT SEQ_NEXT_VAL FROM #{SEQS_DFLT_TBL_NAME} WHERE SEQ_NAME = '#{name.upcase}'"
|
216
|
+
sql << " FOR UPDATE"
|
217
|
+
next_val = select_value(sql, 'next_sequence_value')
|
218
|
+
if next_val.nil?
|
219
|
+
# The table doesn't yet have an accompanying sequence.
|
220
|
+
# Assume the table was created using #execute('CREATE TABLE...')
|
221
|
+
# instead of #create_table. (Rails uses the former method when
|
222
|
+
# creating the test database schema from the development database.)
|
223
|
+
|
224
|
+
# Progress commits DDL immediately, ending the current transaction
|
225
|
+
commit_db_transaction
|
226
|
+
create_sequence(name)
|
227
|
+
begin_db_transaction
|
228
|
+
next_val = SEQ_DFLT_START_VAL
|
229
|
+
end
|
230
|
+
sql = "UPDATE #{SEQS_DFLT_TBL_NAME} SET SEQ_NEXT_VAL = SEQ_NEXT_VAL + 1 "
|
231
|
+
sql << "WHERE SEQ_NAME = '#{name.upcase}'"
|
232
|
+
@connection.do(sql)
|
233
|
+
commit_db_transaction
|
234
|
+
rescue Exception => e
|
235
|
+
if e.message =~ /962/
|
236
|
+
# Sequence table doesn't exist yet. Can happen if tables are created
|
237
|
+
# using #execute instead of #create_table.
|
238
|
+
rollback_db_transaction
|
239
|
+
ensure_sequences_table
|
240
|
+
retry
|
241
|
+
end
|
242
|
+
rollback_db_transaction
|
243
|
+
raise
|
244
|
+
end
|
245
|
+
|
246
|
+
next_val
|
247
|
+
end
|
248
|
+
|
249
|
+
def ensure_sequences_table
|
250
|
+
unless tables(SEQS_DFLT_TBL_NAME).include?(SEQS_DFLT_TBL_NAME)
|
251
|
+
sql = "CREATE TABLE #{dbmsIdentCase(SEQS_DFLT_TBL_NAME)} ("
|
252
|
+
sql << "SEQ_NAME CHARACTER(32) NOT NULL UNIQUE, "
|
253
|
+
sql << "SEQ_NEXT_VAL INTEGER NOT NULL DEFAULT #{SEQ_DFLT_START_VAL})"
|
254
|
+
@connection.do(sql)
|
255
|
+
end
|
256
|
+
@@seq_table_exists = true
|
257
|
+
end
|
258
|
+
|
259
|
+
end # module
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#
|
2
|
+
# $Id: odbcext_sqlanywhere.rb,v 1.2 2008/04/22 16:54:57 source Exp $
|
3
|
+
#
|
4
|
+
# OpenLink ODBC Adapter for Ruby on Rails
|
5
|
+
# Copyright (C) 2006 OpenLink Software
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
# a copy of this software and associated documentation files (the
|
9
|
+
# "Software"), to deal in the Software without restriction, including
|
10
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
# permit persons to whom the Software is furnished to do so, subject
|
13
|
+
# to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
21
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
22
|
+
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
23
|
+
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
#
|
26
|
+
|
27
|
+
module ODBCExt
|
28
|
+
|
29
|
+
# ------------------------------------------------------------------------
|
30
|
+
# Mandatory methods
|
31
|
+
#
|
32
|
+
|
33
|
+
# #last_insert_id must be implemented for any database which returns
|
34
|
+
# false from #prefetch_primary_key?
|
35
|
+
def last_insert_id(table, sequence_name, stmt = nil)
|
36
|
+
@logger.unknown("ODBCAdapter#last_insert_id>") if @trace
|
37
|
+
select_value("select @@IDENTITY", 'last_insert_id')
|
38
|
+
end
|
39
|
+
|
40
|
+
# ------------------------------------------------------------------------
|
41
|
+
# Optional methods
|
42
|
+
#
|
43
|
+
# These are supplied for a DBMS only if necessary.
|
44
|
+
# ODBCAdapter tests for optional methods using Object#respond_to?
|
45
|
+
|
46
|
+
# Pre action for ODBCAdapter#insert
|
47
|
+
def pre_insert(sql, name, pk, id_value, sequence_name)
|
48
|
+
@iiTable = get_table_name(sql)
|
49
|
+
@iiCol = get_autounique_column(@iiTable)
|
50
|
+
@iiEnabled = false
|
51
|
+
|
52
|
+
if @iiCol != nil
|
53
|
+
if query_contains_autounique_col(sql, @iiCol)
|
54
|
+
begin
|
55
|
+
@connection.do(enable_identity_insert(@iiTable, true))
|
56
|
+
@iiEnabled = true
|
57
|
+
rescue Exception => e
|
58
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned on"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Post action for ODBCAdapter#insert
|
65
|
+
def post_insert(sql, name, pk, id_value, sequence_name)
|
66
|
+
if @iiEnabled
|
67
|
+
begin
|
68
|
+
@connection.do(enable_identity_insert(@iiTable, false))
|
69
|
+
rescue Exception => e
|
70
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned off"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# ------------------------------------------------------------------------
|
76
|
+
# Method redefinitions
|
77
|
+
#
|
78
|
+
# DBMS specific methods which override the default implementation
|
79
|
+
# provided by the ODBCAdapter core.
|
80
|
+
|
81
|
+
def get_table_name(sql)
|
82
|
+
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
83
|
+
$1
|
84
|
+
elsif sql =~ /from\s+([^\(\s]+)\s*/i
|
85
|
+
$1
|
86
|
+
else
|
87
|
+
nil
|
88
|
+
end end
|
89
|
+
|
90
|
+
def get_autounique_column(table_name)
|
91
|
+
@table_columns = {} unless @table_columns
|
92
|
+
@table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
|
93
|
+
@table_columns[table_name].each do |col|
|
94
|
+
return col.name if col.auto_unique?
|
95
|
+
end
|
96
|
+
|
97
|
+
return nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def query_contains_autounique_col(sql, col)
|
101
|
+
sql =~ /(\[#{col}\])|("#{col}")/
|
102
|
+
end
|
103
|
+
|
104
|
+
def enable_identity_insert(table_name, enable = true)
|
105
|
+
if has_autounique_column(table_name)
|
106
|
+
"SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def has_autounique_column(table_name)
|
111
|
+
!get_autounique_column(table_name).nil?
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
end # module
|