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.
Files changed (66) hide show
  1. data/AUTHORS +16 -0
  2. data/COPYING +21 -0
  3. data/ChangeLog +89 -0
  4. data/LICENSE +5 -0
  5. data/NEWS +12 -0
  6. data/README +282 -0
  7. data/lib/active_record/connection_adapters/odbc_adapter.rb +1792 -0
  8. data/lib/active_record/vendor/odbcext_db2.rb +87 -0
  9. data/lib/active_record/vendor/odbcext_informix.rb +132 -0
  10. data/lib/active_record/vendor/odbcext_informix_col.rb +45 -0
  11. data/lib/active_record/vendor/odbcext_ingres.rb +156 -0
  12. data/lib/active_record/vendor/odbcext_microsoftsqlserver.rb +185 -0
  13. data/lib/active_record/vendor/odbcext_microsoftsqlserver_col.rb +40 -0
  14. data/lib/active_record/vendor/odbcext_mysql.rb +136 -0
  15. data/lib/active_record/vendor/odbcext_oracle.rb +220 -0
  16. data/lib/active_record/vendor/odbcext_postgresql.rb +179 -0
  17. data/lib/active_record/vendor/odbcext_progress.rb +139 -0
  18. data/lib/active_record/vendor/odbcext_progress89.rb +259 -0
  19. data/lib/active_record/vendor/odbcext_sybase.rb +212 -0
  20. data/lib/active_record/vendor/odbcext_sybase_col.rb +49 -0
  21. data/lib/active_record/vendor/odbcext_virtuoso.rb +146 -0
  22. data/lib/odbc_adapter.rb +28 -0
  23. data/support/lib/active_record/connection_adapters/abstract/schema_definitions.rb +259 -0
  24. data/support/odbc_rails.diff +707 -0
  25. data/support/pack_odbc.rb +119 -0
  26. data/support/rake/rails_plugin_package_task.rb +212 -0
  27. data/support/test/base_test.rb +1349 -0
  28. data/support/test/migration_test.rb +566 -0
  29. data/test/connections/native_odbc/connection.rb +95 -0
  30. data/test/fixtures/db_definitions/db2.drop.sql +30 -0
  31. data/test/fixtures/db_definitions/db2.sql +217 -0
  32. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  33. data/test/fixtures/db_definitions/db22.sql +5 -0
  34. data/test/fixtures/db_definitions/informix.drop.sql +30 -0
  35. data/test/fixtures/db_definitions/informix.sql +205 -0
  36. data/test/fixtures/db_definitions/informix2.drop.sql +2 -0
  37. data/test/fixtures/db_definitions/informix2.sql +5 -0
  38. data/test/fixtures/db_definitions/ingres.drop.sql +62 -0
  39. data/test/fixtures/db_definitions/ingres.sql +232 -0
  40. data/test/fixtures/db_definitions/ingres2.drop.sql +2 -0
  41. data/test/fixtures/db_definitions/ingres2.sql +5 -0
  42. data/test/fixtures/db_definitions/mysql.drop.sql +30 -0
  43. data/test/fixtures/db_definitions/mysql.sql +219 -0
  44. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  45. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  46. data/test/fixtures/db_definitions/oracle_odbc.drop.sql +64 -0
  47. data/test/fixtures/db_definitions/oracle_odbc.sql +257 -0
  48. data/test/fixtures/db_definitions/oracle_odbc2.drop.sql +2 -0
  49. data/test/fixtures/db_definitions/oracle_odbc2.sql +6 -0
  50. data/test/fixtures/db_definitions/progress.drop.sql +61 -0
  51. data/test/fixtures/db_definitions/progress.sql +234 -0
  52. data/test/fixtures/db_definitions/progress2.drop.sql +2 -0
  53. data/test/fixtures/db_definitions/progress2.sql +6 -0
  54. data/test/fixtures/db_definitions/sqlserver.drop.sql +30 -0
  55. data/test/fixtures/db_definitions/sqlserver.sql +203 -0
  56. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  57. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  58. data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
  59. data/test/fixtures/db_definitions/sybase.sql +204 -0
  60. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  61. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  62. data/test/fixtures/db_definitions/virtuoso.drop.sql +30 -0
  63. data/test/fixtures/db_definitions/virtuoso.sql +200 -0
  64. data/test/fixtures/db_definitions/virtuoso2.drop.sql +2 -0
  65. data/test/fixtures/db_definitions/virtuoso2.sql +5 -0
  66. 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