odbc-rails 1.2
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.
- data/AUTHORS +16 -0
- data/COPYING +21 -0
- data/ChangeLog +89 -0
- data/LICENSE +5 -0
- data/NEWS +12 -0
- data/README +282 -0
- data/lib/active_record/connection_adapters/odbc_adapter.rb +1792 -0
- data/lib/active_record/vendor/odbcext_db2.rb +87 -0
- data/lib/active_record/vendor/odbcext_informix.rb +132 -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 +185 -0
- data/lib/active_record/vendor/odbcext_microsoftsqlserver_col.rb +40 -0
- data/lib/active_record/vendor/odbcext_mysql.rb +136 -0
- data/lib/active_record/vendor/odbcext_oracle.rb +220 -0
- data/lib/active_record/vendor/odbcext_postgresql.rb +179 -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_sybase.rb +212 -0
- data/lib/active_record/vendor/odbcext_sybase_col.rb +49 -0
- data/lib/active_record/vendor/odbcext_virtuoso.rb +146 -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 +707 -0
- data/support/pack_odbc.rb +119 -0
- data/support/rake/rails_plugin_package_task.rb +212 -0
- data/support/test/base_test.rb +1349 -0
- data/support/test/migration_test.rb +566 -0
- data/test/connections/native_odbc/connection.rb +95 -0
- data/test/fixtures/db_definitions/db2.drop.sql +30 -0
- data/test/fixtures/db_definitions/db2.sql +217 -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 +30 -0
- data/test/fixtures/db_definitions/informix.sql +205 -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 +62 -0
- data/test/fixtures/db_definitions/ingres.sql +232 -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 +30 -0
- data/test/fixtures/db_definitions/mysql.sql +219 -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 +64 -0
- data/test/fixtures/db_definitions/oracle_odbc.sql +257 -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/progress.drop.sql +61 -0
- data/test/fixtures/db_definitions/progress.sql +234 -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 +30 -0
- data/test/fixtures/db_definitions/sqlserver.sql +203 -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 +31 -0
- data/test/fixtures/db_definitions/sybase.sql +204 -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 +30 -0
- data/test/fixtures/db_definitions/virtuoso.sql +200 -0
- data/test/fixtures/db_definitions/virtuoso2.drop.sql +2 -0
- data/test/fixtures/db_definitions/virtuoso2.sql +5 -0
- metadata +149 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
#
|
2
|
+
# $Id: odbcext_progress.rb,v 1.1 2006/12/06 14:42:11 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)
|
98
|
+
@logger.unknown("ODBCAdapter#drop_table>") if @trace
|
99
|
+
super(name)
|
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 #{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.1 2006/12/06 14:42:11 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
|
+
sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
|
69
|
+
rescue Exception => e
|
70
|
+
@logger.unknown("exception=#{e}") if @trace
|
71
|
+
raise StatementInvalid, e.message
|
72
|
+
end
|
73
|
+
|
74
|
+
def quote_column_name(name)
|
75
|
+
@logger.unknown("ODBCAdapter#quote_column_name>") if @trace
|
76
|
+
@logger.unknown("args=[#{name}]") if @trace
|
77
|
+
# Progress v8 or earlier doesn't support quoted identifiers.
|
78
|
+
# ODBC::SQL_IDENTIFIER_QUOTE_CHAR is typically " "
|
79
|
+
name.to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
def quoted_date(value)
|
83
|
+
@logger.unknown("ODBCAdapter#quoted_date>") if @trace
|
84
|
+
@logger.unknown("args=[#{value}]") if @trace
|
85
|
+
case value
|
86
|
+
# Progress v8 doesn't support a datetime or time type,
|
87
|
+
# only a date type.
|
88
|
+
when Time, DateTime
|
89
|
+
#%Q!{ts '#{value.strftime("%Y-%m-%d %H:%M:%S")}'}!
|
90
|
+
%Q!{d '#{value.strftime("%Y-%m-%d")}'}!
|
91
|
+
when 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)
|
135
|
+
@logger.unknown("ODBCAdapter#drop_table>") if @trace
|
136
|
+
super(name)
|
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,212 @@
|
|
1
|
+
#
|
2
|
+
# $Id: odbcext_sybase.rb,v 1.1 2006/12/06 14:42:11 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
|
+
#TODO: Fixme - Doesn't work with OpenLink TDS driver against Sybase
|
38
|
+
#select_value("select @@IDENTITY", 'last_insert_id')
|
39
|
+
select_value("select max(syb_identity) from #{table}", 'last_insert_id')
|
40
|
+
end
|
41
|
+
|
42
|
+
# ------------------------------------------------------------------------
|
43
|
+
# Optional methods
|
44
|
+
#
|
45
|
+
# These are supplied for a DBMS only if necessary.
|
46
|
+
# ODBCAdapter tests for optional methods using Object#respond_to?
|
47
|
+
|
48
|
+
# Pre action for ODBCAdapter#insert
|
49
|
+
def pre_insert(sql, name, pk, id_value, sequence_name)
|
50
|
+
@iiTable = get_table_name(sql)
|
51
|
+
@iiCol = get_autounique_column(@iiTable)
|
52
|
+
@iiEnabled = false
|
53
|
+
|
54
|
+
if @iiCol != nil
|
55
|
+
if query_contains_autounique_col(sql, @iiCol)
|
56
|
+
begin
|
57
|
+
@connection.do(enable_identity_insert(@iiTable, true))
|
58
|
+
@iiEnabled = true
|
59
|
+
rescue Exception => e
|
60
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned on"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Post action for ODBCAdapter#insert
|
67
|
+
def post_insert(sql, name, pk, id_value, sequence_name)
|
68
|
+
if @iiEnabled
|
69
|
+
begin
|
70
|
+
@connection.do(enable_identity_insert(@iiTable, false))
|
71
|
+
rescue Exception => e
|
72
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned off"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# ------------------------------------------------------------------------
|
78
|
+
# Method redefinitions
|
79
|
+
#
|
80
|
+
# DBMS specific methods which override the default implementation
|
81
|
+
# provided by the ODBCAdapter core.
|
82
|
+
|
83
|
+
def rename_table(name, new_name)
|
84
|
+
@logger.unknown("ODBCAdapter#rename_table>") if @trace
|
85
|
+
execute "EXEC sp_rename '#{name}', '#{new_name}'"
|
86
|
+
rescue Exception => e
|
87
|
+
@logger.unknown("exception=#{e}") if @trace
|
88
|
+
raise
|
89
|
+
end
|
90
|
+
|
91
|
+
def remove_column(table_name, column_name)
|
92
|
+
@logger.unknown("ODBCAdapter#remove_column>") if @trace
|
93
|
+
# Remove default constraints first
|
94
|
+
defaults = select_all "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
|
95
|
+
defaults.each {|constraint|
|
96
|
+
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
|
97
|
+
}
|
98
|
+
execute "ALTER TABLE #{table_name} DROP #{quote_column_name(column_name)}"
|
99
|
+
rescue Exception => e
|
100
|
+
@logger.unknown("exception=#{e}") if @trace
|
101
|
+
raise
|
102
|
+
end
|
103
|
+
|
104
|
+
def change_column(table_name, column_name, type, options = {})
|
105
|
+
@logger.unknown("ODBCAdapter#change_column>") if @trace
|
106
|
+
if options[:default]
|
107
|
+
# Sybase ASE's ALTER TABLE statement doesn't allow a column's DEFAULT to be changed.
|
108
|
+
raise ActiveRecord::ActiveRecordError,
|
109
|
+
"Sybase ASE does not support changing a column's DEFAULT definition"
|
110
|
+
end
|
111
|
+
execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
|
112
|
+
rescue Exception => e
|
113
|
+
@logger.unknown("exception=#{e}") if @trace
|
114
|
+
raise
|
115
|
+
end
|
116
|
+
|
117
|
+
def rename_column(table_name, column_name, new_column_name)
|
118
|
+
@logger.unknown("ODBCAdapter#rename_column>") if @trace
|
119
|
+
execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}'"
|
120
|
+
rescue Exception => e
|
121
|
+
@logger.unknown("exception=#{e}") if @trace
|
122
|
+
raise
|
123
|
+
end
|
124
|
+
|
125
|
+
def remove_index(table_name, options = {})
|
126
|
+
@logger.unknown("ODBCAdapter#remove_index>") if @trace
|
127
|
+
execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
|
128
|
+
rescue Exception => e
|
129
|
+
@logger.unknown("exception=#{e}") if @trace
|
130
|
+
raise
|
131
|
+
end
|
132
|
+
|
133
|
+
def tables(name = nil)
|
134
|
+
# Hide system tables.
|
135
|
+
super(name).delete_if {|t| t =~ /^sys/ }
|
136
|
+
end
|
137
|
+
|
138
|
+
def indexes(table_name, name = nil)
|
139
|
+
# Hide primary key indexes.
|
140
|
+
super(table_name, name).delete_if { |i| i.name =~ /^PK_/ }
|
141
|
+
end
|
142
|
+
|
143
|
+
def add_column_options!(sql, options) # :nodoc:
|
144
|
+
@logger.unknown("ODBCAdapter#add_column_options!>") if @trace
|
145
|
+
@logger.unknown("args=[#{sql}]") if @trace
|
146
|
+
sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
|
147
|
+
|
148
|
+
if column_type_allows_null?(sql, options)
|
149
|
+
sql << (options[:null] == false ? " NOT NULL" : " NULL")
|
150
|
+
end
|
151
|
+
sql
|
152
|
+
rescue Exception => e
|
153
|
+
@logger.unknown("exception=#{e}") if @trace
|
154
|
+
raise ActiveRecord::StatementInvalid, e.message
|
155
|
+
end
|
156
|
+
|
157
|
+
# ------------------------------------------------------------------------
|
158
|
+
# Private methods to support methods above
|
159
|
+
#
|
160
|
+
private
|
161
|
+
|
162
|
+
def get_table_name(sql)
|
163
|
+
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
164
|
+
$1
|
165
|
+
elsif sql =~ /from\s+([^\(\s]+)\s*/i
|
166
|
+
$1
|
167
|
+
else
|
168
|
+
nil
|
169
|
+
end end
|
170
|
+
|
171
|
+
def get_autounique_column(table_name)
|
172
|
+
@table_columns = {} unless @table_columns
|
173
|
+
@table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
|
174
|
+
@table_columns[table_name].each do |col|
|
175
|
+
return col.name if col.auto_unique?
|
176
|
+
end
|
177
|
+
|
178
|
+
return nil
|
179
|
+
end
|
180
|
+
|
181
|
+
def query_contains_autounique_col(sql, col)
|
182
|
+
sql =~ /(\[#{col}\])|("#{col}")/
|
183
|
+
end
|
184
|
+
|
185
|
+
def enable_identity_insert(table_name, enable = true)
|
186
|
+
if has_autounique_column(table_name)
|
187
|
+
"SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def has_autounique_column(table_name)
|
192
|
+
!get_autounique_column(table_name).nil?
|
193
|
+
end
|
194
|
+
|
195
|
+
def column_type_allows_null?(sql, options)
|
196
|
+
# Sybase columns are NOT NULL by default, so explicitly set NULL
|
197
|
+
# if :null option is omitted. Disallow NULLs for boolean.
|
198
|
+
col = options[:column]
|
199
|
+
return false if col && col[:type] == :primary_key
|
200
|
+
|
201
|
+
# Force options[:null] to be ignored for BIT (:boolea) columns
|
202
|
+
# by returning false
|
203
|
+
isBitCol = !(sql =~ /\s+bit(\s+default)?/i).nil? || (col && col[:type] == :boolean)
|
204
|
+
hasDefault = !$1.nil? || options[:default]
|
205
|
+
|
206
|
+
# If no default clause found on a boolean column, add one.
|
207
|
+
sql << " DEFAULT 0" if isBitCol && !hasDefault
|
208
|
+
|
209
|
+
!isBitCol
|
210
|
+
end
|
211
|
+
|
212
|
+
end # module
|