activerecord-sqlanywhere-adapter 0.1.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.
@@ -0,0 +1,5 @@
1
+ =CHANGE LOG
2
+
3
+ =====0.1.0 -- 2008/10/15
4
+ - Initial Release
5
+
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ /*====================================================
2
+ *
3
+ * Copyright 2008 iAnywhere Solutions, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ *
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ *
19
+ * While not a requirement of the license, if you do modify this file, we
20
+ * would appreciate hearing about it. Please email sqlany_interfaces@sybase.com
21
+ *
22
+ *
23
+ *====================================================*/
data/README ADDED
@@ -0,0 +1,71 @@
1
+ =SQL Anywhere ActiveRecord Driver
2
+
3
+ This is a SQL Anywhere driver for Ruby ActiveRecord. This driver requires the
4
+ native SQL Anywhere Ruby driver. To get the native driver, use:
5
+
6
+ gem install sqlanywhere
7
+
8
+ This driver is designed for use with ActiveRecord 2.0.2 and greater.
9
+
10
+ This driver is licensed under the Apache License, Version 2.
11
+
12
+ ==Making a Connection
13
+
14
+ The following code is a sample database configuration object.
15
+
16
+ ActiveRecord::Base.configurations = {
17
+ 'arunit' => {
18
+ :adapter => 'sqlanywhere',
19
+ :database => 'arunit', #equivalent to the "dbn" parameter
20
+ :server => 'arunit', #equivalent to the "eng" parameter
21
+ :username => 'dba', #equivalent to the "uid" parameter
22
+ :password => 'sql' #equivalent to the "pwd" parameter
23
+ }
24
+
25
+ ==Running the ActiveRecord Unit Test Suite
26
+
27
+ 1. Open <tt><ACTIVERECORD_INSTALL_DIR>/rakefile</tt> and modify the line:
28
+
29
+ for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase )
30
+
31
+ to include <tt>sqlanywhere</tt>. It should now look like:
32
+
33
+ for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase sqlanywhere )
34
+
35
+ 2. Create directory to hold the connection definition:
36
+
37
+ mkdir <ACTIVERECORD_INSTALL_DIR>/test/connections/native_sqlanywhere
38
+
39
+ 3. Copy <tt>test/connection.rb</tt> into the newly created directory.
40
+
41
+ NOTE: If using ActiveRecord 2.0.2, change the second line of the file from:
42
+
43
+ require_dependency 'models/course'
44
+
45
+ to:
46
+
47
+ require_dependency 'fixtures/course'
48
+
49
+ 4. Create the two test datbases. These can be created in any directory.
50
+
51
+ dbinit arunit
52
+ dbinit arunit2
53
+ dbsrv11 arunit arunit2
54
+
55
+ <b>If the commands cannot be found, make sure you have set up the SQL Anywhere environment variables correctly.</b> For more information
56
+ review the online documentation here[http://dcx.sybase.com/index.php#http%3A%2F%2Fdcx.sybase.com%2F1100en%2Fdbadmin_en11%2Fda-envvar-sect1-3672410.html].
57
+
58
+ 5. If you are using ActiveRecord 2.0.2, you must load the test tables.
59
+
60
+ dbisql -c "eng=arunit;dbn=arunit;uid=dba;pwd=sql" sqlanywhere.sql
61
+ dbisql -c "eng=arunit;dbn=arunit2;uid=dba;pwd=sql" sqlanywhere2.sql
62
+
63
+ If you are using a newer version of ActiveRecord, this schema is automatically
64
+ migrated for you.
65
+
66
+ 6. Run the unit test suite from the ActiveRecord install directory:
67
+
68
+ rake test_sqlanywhere
69
+
70
+ <b>If the migration tests fail, make sure you have set up the SQL Anywhere environment variables correctly.</b> For more information
71
+ review the online documentation here[http://dcx.sybase.com/index.php#http%3A%2F%2Fdcx.sybase.com%2F1100en%2Fdbadmin_en11%2Fda-envvar-sect1-3672410.html].
@@ -0,0 +1,510 @@
1
+ #====================================================
2
+ #
3
+ # Copyright 2008 iAnywhere Solutions, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ #
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ # While not a requirement of the license, if you do modify this file, we
20
+ # would appreciate hearing about it. Please email sqlany_interfaces@sybase.com
21
+ #
22
+ #
23
+ #====================================================
24
+
25
+ require 'active_record/connection_adapters/abstract_adapter'
26
+
27
+ # Singleton class to hold a valid instance of the SQLAnywhereInterface across all connections
28
+ class SA
29
+ include Singleton
30
+ attr_accessor :api
31
+
32
+ def initialize
33
+ unless defined? SQLAnywhere
34
+ require_library_or_gem 'sqlanywhere'
35
+ end
36
+
37
+ @api = SQLAnywhere::SQLAnywhereInterface.new()
38
+ result = SQLAnywhere::API.sqlany_initialize_interface(@api)
39
+ if result == 0
40
+ raise LoadError, "Could not load SQLAnywhere adapter DLL"
41
+ end
42
+ result = @api.sqlany_init()
43
+ if result == 0
44
+ raise LoadError, "Could not initialize SQLAnywhere adapter DLL"
45
+ end
46
+ end
47
+ end
48
+
49
+ module ActiveRecord
50
+ class Base
51
+ # Main connection function to SQL Anywhere
52
+ # Connection Adapter takes four parameters:
53
+ # * :database (required, no default). Corresponds to "ENG=" in connection string
54
+ # * :server (optional, defaults to :databse). Corresponds to "DBN=" in connection string
55
+ # * :username (optional, default to 'dba')
56
+ # * :password (optioanl, deafult to 'sql')
57
+ def self.sqlanywhere_connection(config)
58
+
59
+ if config.has_key?(:database)
60
+ database = config[:database]
61
+ else
62
+ raise ArgumentError, "No database name was given. Please add a :database option."
63
+ end
64
+
65
+ database = config[:database]
66
+ server = config[:server] ? config[:server].to_s : database
67
+ username = config[:username] ? config[:username].to_s : 'dba'
68
+ password = config[:password] ? config[:password].to_s : 'sql'
69
+
70
+ db = SA.instance.api.sqlany_new_connection()
71
+ SA.instance.api.sqlany_connect(db, "eng=#{server};dbn=#{database};uid=#{username};pwd=#{password}")
72
+ SA.instance.api.sqlany_execute_immediate(db, "SET OPTION non_keywords = 'LOGIN'")
73
+ ConnectionAdapters::SQLAnywhereAdapter.new(db, logger)
74
+ end
75
+ end
76
+
77
+ module ConnectionAdapters
78
+ class SQLAnywhereColumn < Column
79
+ private
80
+ # Overridden to handle SQL Anywhere integer, varchar, binary, and timestamp types
81
+ def simplified_type(field_type)
82
+ return :boolean if field_type =~ /tinyint/i
83
+ return :string if field_type =~ /varchar/i
84
+ return :binary if field_type =~ /long binary/i
85
+ return :datetime if field_type =~ /timestamp/i
86
+ return :integer if field_type =~ /smallint|bigint/i
87
+ super
88
+ end
89
+
90
+ def extract_limit(sql_type)
91
+ case sql_type
92
+ when /^tinyint/i: 1
93
+ when /^smallint/i: 2
94
+ when /^integer/i: 4
95
+ when /^bigint/i: 8
96
+ else super
97
+ end
98
+ end
99
+
100
+ protected
101
+ # Handles the encoding of a binary object into SQL Anywhere
102
+ # SQL Anywhere requires that binary values be encoded as \xHH, where HH is a hexadecimal number
103
+ # This function encodes the binary string in this format
104
+ def self.string_to_binary(value)
105
+ if value
106
+ result = ''
107
+ value.each_byte do |c|
108
+ res = sprintf('\x%x', c)
109
+ res.insert(2, '0') if res.length == 3
110
+ result << res
111
+ end
112
+ result
113
+ end
114
+ end
115
+
116
+ def self.binary_to_string(value)
117
+ %Q/#{value}/
118
+ end
119
+
120
+ end
121
+
122
+
123
+ class SQLAnywhereAdapter < AbstractAdapter
124
+ def adapter_name #:nodoc:
125
+ 'SQLAnywhere'
126
+ end
127
+
128
+ def supports_migrations? #:nodoc:
129
+ true
130
+ end
131
+
132
+ def requires_reloading?
133
+ false
134
+ end
135
+
136
+ def disconnect!
137
+ SA.instance.api.sqlany_disconnect( @connection ) rescue nil
138
+ SA.instance.api.sqlany_free_connection( @connection ) rescue nil
139
+ super
140
+ end
141
+
142
+ def supports_count_distinct? #:nodoc:
143
+ true
144
+ end
145
+
146
+ def supports_autoincrement? #:nodoc:
147
+ true
148
+ end
149
+
150
+ # Maps native ActiveRecord/Ruby types into SQLAnywhere types
151
+ # TINYINTs are treated as the default boolean value
152
+ # ActiveRecord allows NULLs in boolean columns, and the SQL Anywhere BIT type does not
153
+ # As a result, TINYINT must be used. All TINYINT columns will be assumed to be boolean and
154
+ # should not be used as single-byte integer columns. This restriction is similar to other ActiveRecord database drivers
155
+ def native_database_types #:nodoc:
156
+ {
157
+ :primary_key => 'INTEGER PRIMARY KEY DEFAULT AUTOINCREMENT NOT NULL',
158
+ :string => { :name => "varchar", :limit => 255 },
159
+ :text => { :name => "long varchar" },
160
+ :integer => { :name => "integer" },
161
+ :float => { :name => "float" },
162
+ :decimal => { :name => "decimal" },
163
+ :datetime => { :name => "datetime" },
164
+ :timestamp => { :name => "datetime" },
165
+ :time => { :name => "datetime" },
166
+ :date => { :name => "date" },
167
+ :binary => { :name => "long binary" },
168
+ :boolean => { :name => "tinyint"}
169
+ }
170
+ end
171
+
172
+ # QUOTING ==================================================
173
+
174
+ # Applies quotations around column names in generated queries
175
+ def quote_column_name(name) #:nodoc:
176
+ %Q("#{name}")
177
+ end
178
+
179
+ # Handles special quoting of binary columns. Binary columns will be treated as strings inside of ActiveRecord.
180
+ # ActiveRecord requires that any strings it inserts into databases must escape the backslash (\).
181
+ # Since in the binary case, the (\x) is significant to SQL Anywhere, it cannot be escaped.
182
+ def quote(value, column = nil)
183
+ case value
184
+ when String, ActiveSupport::Multibyte::Chars
185
+ value_S = value.to_s
186
+ if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
187
+ "#{quoted_string_prefix}'#{column.class.string_to_binary(value_S)}'"
188
+ else
189
+ super(value, column)
190
+ end
191
+ else
192
+ super(value, column)
193
+ end
194
+ end
195
+
196
+ def quoted_true
197
+ '1'
198
+ end
199
+
200
+ def quoted_false
201
+ '0'
202
+ end
203
+
204
+
205
+ # SQL Anywhere, in accordance with the SQL Standard, does not allow a column to appear in the ORDER BY list
206
+ # that is not also in the SELECT with when obtaining DISTINCT rows beacuse the actual semantics of this query
207
+ # are unclear. The following functions create a query that mimics the way that SQLite and MySQL handle this query.
208
+ #
209
+ # This function (distinct) is based on the Oracle ActiveRecord driver created by Graham Jenkins (2005)
210
+ # (http://svn.rubyonrails.org/rails/adapters/oracle/lib/active_record/connection_adapters/oracle_adapter.rb)
211
+ def distinct(columns, order_by)
212
+ return "DISTINCT #{columns}" if order_by.blank?
213
+ order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
214
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
215
+ "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
216
+ end
217
+ sql = "DISTINCT #{columns}, "
218
+ sql << order_columns * ", "
219
+
220
+ end
221
+
222
+ # This function (add_order_by_for_association_limiting) is based on the Oracle ActiveRecord driver created by Graham Jenkins (2005)
223
+ # (http://svn.rubyonrails.org/rails/adapters/oracle/lib/active_record/connection_adapters/oracle_adapter.rb)
224
+ def add_order_by_for_association_limiting!(sql, options)
225
+ return sql if options[:order].blank?
226
+
227
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
228
+ order.map! {|s| $1 if s =~ / (.*)/}
229
+ order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
230
+
231
+ sql << " ORDER BY #{order}"
232
+ end
233
+
234
+ # By default, ActiveRecord attempts to use the MySQL/SQLite syntax of limit and offset. That is, adding LIMIT XX OFFSET XX
235
+ # at the end of the query. SQL Anywhere uses TOP XX START XX to handle this, adding immediatly following SELECT or SELECT DISTINCT.
236
+ # This function adds the limit and offset to the appropriate place in the query.
237
+ def add_limit_offset!(sql, options)
238
+ temp_sql = ''
239
+ if limit = options[:limit]
240
+ temp_sql << " TOP #{self.respond_to?('sanitize_limit') ? sanitize_limit(limit) : limit} "
241
+ if offset = options[:offset]
242
+ temp_sql << " START AT #{offset + 1}"
243
+ end
244
+ if sql =~ /^select distinct.*/i
245
+ sql.insert(15, temp_sql);
246
+ elsif sql =~ /^select.*/i
247
+ sql.insert(6, temp_sql);
248
+ else
249
+ sql = temp_sql;
250
+ end
251
+ end
252
+ sql
253
+ end
254
+
255
+ # The database execution function
256
+ def execute(sql, name = nil) #:nodoc:
257
+ return if sql.nil?
258
+
259
+ # ActiveRecord allows a query to return TOP 0. SQL Anywhere requires that the TOP value is a positive integer.
260
+ if sql =~ /TOP 0/i
261
+ return Array.new()
262
+ end
263
+
264
+ # Executes the query, iterates through the results, and builds an array of hashes.
265
+ rs = SA.instance.api.sqlany_execute_direct(@connection, sql)
266
+ raise ActiveRecord::StatementInvalid.new("#{SA.instance.api.sqlany_error(@connection)}:#{sql}") if rs.nil?
267
+ record = []
268
+ while SA.instance.api.sqlany_fetch_next(rs) == 1
269
+ max_cols = SA.instance.api.sqlany_num_cols(rs)
270
+ result = Hash.new()
271
+ max_cols.times do |cols|
272
+ result[SA.instance.api.sqlany_get_column_info(rs, cols)[2]] = SA.instance.api.sqlany_get_column(rs, cols)[1]
273
+ end
274
+ record << result
275
+ end
276
+ SA.instance.api.sqlany_free_stmt(rs)
277
+ return record
278
+ end
279
+
280
+ # The database update function.
281
+ def update_sql(sql, name = nil)
282
+ rs = SA.instance.api.sqlany_execute_direct(@connection, sql)
283
+ raise ActiveRecord::StatementInvalid.new("#{SA.instance.api.sqlany_error(@connection)}:#{sql}") if rs.nil?
284
+ retVal = SA.instance.api.sqlany_affected_rows(rs)
285
+ SA.instance.api.sqlany_free_stmt(rs)
286
+ execute (nil) # test suite counts number of executions. Dummy execution needed to pass tests.
287
+ return retVal
288
+ end
289
+
290
+ # The database delete function.
291
+ def delete_sql(sql, name = nil) #:nodoc:
292
+ rs = SA.instance.api.sqlany_execute_direct(@connection, sql)
293
+ raise ActiveRecord::StatementInvalid.new("#{SA.instance.api.sqlany_error(@connection)}:#{sql}") if rs.nil?
294
+ retVal = SA.instance.api.sqlany_affected_rows(rs)
295
+ SA.instance.api.sqlany_free_stmt(rs)
296
+ execute (nil) # test suite counts number of executions. Dummy execution needed to pass tests.
297
+ return retVal
298
+ end
299
+
300
+ # The database insert function.
301
+ # ActiveRecord requires that insert_sql returns the primary key of the row just inserted. In most cases, this can be accomplished
302
+ # by immediatly querying the @@identity property. If the @@identity property is 0, then passed id_value is used
303
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
304
+ retval = 0
305
+ rs = SA.instance.api.sqlany_execute_direct(@connection, sql)
306
+ raise ActiveRecord::StatementInvalid.new("#{SA.instance.api.sqlany_error(@connection)}:#{sql}") if rs.nil?
307
+ identity = SA.instance.api.sqlany_execute_direct(@connection, 'SELECT @@identity')
308
+ raise ActiveRecord::StatementInvalid.new("#{SA.instance.api.sqlany_error(@connection)}:#{sql}") if identity.nil?
309
+ SA.instance.api.sqlany_fetch_next(identity)
310
+ retval = SA.instance.api.sqlany_get_column(identity, 0)[1]
311
+ SA.instance.api.sqlany_free_stmt(identity)
312
+ SA.instance.api.sqlany_free_stmt(rs)
313
+ retval = id_value if retval == 0
314
+ execute (nil) # test suite counts number of executions. Dummy execution needed to pass tests.
315
+ return retval
316
+ end
317
+
318
+ # Returns a query as an array of arrays
319
+ def select_rows(sql, name = nil)
320
+ rs = SA.instance.api.sqlany_execute_direct(@connection, sql)
321
+ raise ActiveRecord::StatementInvalid.new("#{SA.instance.api.sqlany_error(@connection)}:#{sql}") if rs.nil?
322
+ record = []
323
+ while SA.instance.api.sqlany_fetch_next(rs) == 1
324
+ max_cols = SA.instance.api.sqlany_num_cols(rs)
325
+ result = Array.new(max_cols)
326
+ max_cols.times do |cols|
327
+ result[cols] = SA.instance.api.sqlany_get_column(rs, cols)[1]
328
+ end
329
+ record << result
330
+ end
331
+ SA.instance.api.sqlany_free_stmt(rs)
332
+ return record
333
+ end
334
+
335
+ def begin_db_transaction #:nodoc:
336
+ end
337
+
338
+ def commit_db_transaction #:nodoc:
339
+ SA.instance.api.sqlany_commit(@connection)
340
+ end
341
+
342
+ def rollback_db_transaction #:nodoc:
343
+ SA.instance.api.sqlany_rollback(@connection)
344
+ end
345
+
346
+ def add_lock!(sql, options) #:nodoc:
347
+ sql
348
+ end
349
+
350
+ # SQL Anywhere does not support sizing of integers based on the sytax INTEGER(size). Integer sizes
351
+ # must be captured when generating the SQL and replaced with the appropriate size.
352
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
353
+ if native = native_database_types[type]
354
+ if type == :integer
355
+ case limit
356
+ when 1
357
+ column_type_sql = 'tinyint'
358
+ when 2
359
+ column_type_sql = 'smallint'
360
+ when 3..4
361
+ column_type_sql = 'integer'
362
+ when 5..8
363
+ column_type_sql = 'bigint'
364
+ else
365
+ column_type_sql = 'integer'
366
+ end
367
+ column_type_sql
368
+ else
369
+ super(type, limit, precision, scale)
370
+ end
371
+ else
372
+ super(type, limit, precision, scale)
373
+ end
374
+ end
375
+
376
+ # Do not return SYS-owned or DBO-owned tables
377
+ def tables(name = nil) #:nodoc:
378
+ sql = <<-SQL
379
+ SELECT table_name
380
+ FROM systable
381
+ WHERE creator not in (0,3)
382
+ SQL
383
+
384
+ select(sql, name).map do |row|
385
+ row["table_name"]
386
+ end
387
+ end
388
+
389
+ def columns(table_name, name = nil) #:nodoc:
390
+ table_structure(table_name).map do |field|
391
+ field['default'] = field['default'][1..-2] if (!field['default'].nil? and field['default'][0].chr == "'")
392
+ SQLAnywhereColumn.new(field['name'], field['default'], field['domain'], (field['nulls'] == 1))
393
+ end
394
+ end
395
+
396
+ def indexes(table_name, name = nil) #:nodoc:
397
+
398
+ sql = <<-SQL
399
+ SELECT index_name, "unique"
400
+ FROM systable join (sysidxcol join sysidx)
401
+ WHERE table_name = '#{table_name}' and index_category > 2
402
+ SQL
403
+ select(sql, name).map do |row|
404
+ index = IndexDefinition.new(table_name, row['index_name'])
405
+ index.unique = row['unique'] == 1
406
+ sql = <<-SQL
407
+ SELECT column_name
408
+ FROM (systable join systabcol) join (sysidxcol join sysidx)
409
+ WHERE table_name = '#{table_name}' and index_name = '#{row['index_name']}'
410
+ SQL
411
+ index.columns = select(sql).map { |col| col['column_name'] }
412
+ index
413
+ end
414
+ end
415
+
416
+ def primary_key(table_name) #:nodoc:
417
+ sql = <<-SQL
418
+ SELECT systabcol.column_name
419
+ FROM (systable join systabcol ) left outer join (sysidxcol join sysidx)
420
+ WHERE table_name = '#{table_name}' AND sysidxcol.sequence = 0
421
+ SQL
422
+ rs = select(sql)
423
+ if !rs.nil? and !rs[0].nil?
424
+ rs[0]['column_name']
425
+ else
426
+ nil
427
+ end
428
+ end
429
+
430
+ def remove_index(table_name, options={}) #:nodoc:
431
+ execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
432
+ end
433
+
434
+ def rename_table(name, new_name)
435
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME #{quote_table_name(new_name)}"
436
+ end
437
+
438
+ def remove_column(table_name, column_name) #:nodoc:
439
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
440
+ end
441
+
442
+ def change_column_default(table_name, column_name, default) #:nodoc:
443
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
444
+ end
445
+
446
+ def change_column_null(table_name, column_name, null, default = nil)
447
+ unless null || default.nil?
448
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
449
+ end
450
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? '' : 'NOT'} NULL")
451
+ end
452
+
453
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
454
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
455
+ add_column_options!(add_column_sql, options)
456
+ add_column_sql << ' NULL' if options[:null]
457
+ execute(add_column_sql)
458
+ end
459
+
460
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
461
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
462
+ end
463
+
464
+ def drop_column(table_name, column_name)
465
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
466
+ end
467
+
468
+
469
+ protected
470
+ def select(sql, name = nil) #:nodoc:
471
+ return execute(sql, name)
472
+ end
473
+
474
+ # Queries the structure of a table including the columns names, defaults, type, and nullability
475
+ # ActiveRecord uses the type to parse scale and precision information out of the types. As a result,
476
+ # chars, varchars, binary, nchars, nvarchars must all be returned in the form <i>type</i>(<i>width</i>)
477
+ # numeric and decimal must be returned in the form <i>type</i>(<i>width</i>, <i>scale</i>)
478
+ # Nullability is returned as 0 (no nulls allowed) or 1 (nulls allowed)
479
+ # Alos, ActiveRecord expects an autoincrement column to have default value of NULL
480
+
481
+ def table_structure(table_name)
482
+ sql = <<-SQL
483
+ SELECT syscolumn.column_name as name,
484
+ (IF syscolumn."default" = 'autoincrement' then null else syscolumn."default" endif) as "default",
485
+ (IF syscolumn.domain_id IN (7,8,9,11,33,34,35,3,27) THEN
486
+ (IF syscolumn.domain_id IN (3, 27)
487
+ THEN sysdomain.domain_name || '(' || syscolumn.width || ',' || syscolumn.scale || ')'
488
+ ELSE sysdomain.domain_name || '(' || syscolumn.width || ')'
489
+ END IF)
490
+ ELSE sysdomain.domain_name endif) as domain,
491
+ (if syscolumn.nulls = 'Y' then 1 else 0 endif) as nulls
492
+ FROM syscolumn, systable, sysdomain
493
+ WHERE syscolumn.table_id = systable.table_id AND
494
+ table_name = '#{table_name}' AND
495
+ syscolumn.domain_id = sysdomain.domain_id
496
+ SQL
497
+ returning structure = select(sql) do
498
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if false
499
+ end
500
+ end
501
+
502
+ # Required to prevent DEFAULT NULL being added to primary keys
503
+ def options_include_default?(options)
504
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
505
+ end
506
+ end
507
+ end
508
+ end
509
+
510
+
@@ -0,0 +1,25 @@
1
+ print "Using native SQLAnywhere Interface\n"
2
+ require_dependency 'models/course'
3
+ require 'logger'
4
+
5
+ ActiveRecord::Base.logger = Logger.new("debug.log")
6
+
7
+ ActiveRecord::Base.configurations = {
8
+ 'arunit' => {
9
+ :adapter => 'sqlanywhere',
10
+ :database => 'arunit',
11
+ :server => 'arunit',
12
+ :username => 'dba',
13
+ :password => 'sql'
14
+ },
15
+ 'arunit2' => {
16
+ :adapter => 'sqlanywhere',
17
+ :database => 'arunit2',
18
+ :server => 'arunit',
19
+ :username => 'dba',
20
+ :password => 'sql'
21
+ }
22
+ }
23
+
24
+ ActiveRecord::Base.establish_connection 'arunit'
25
+ Course.establish_connection 'arunit2'
@@ -0,0 +1,35 @@
1
+ DROP TABLE accounts
2
+ DROP TABLE funny_jokes
3
+ DROP TABLE companies
4
+ DROP TABLE topics
5
+ DROP TABLE developers
6
+ DROP TABLE projects
7
+ DROP TABLE developers_projects
8
+ DROP TABLE customers
9
+ DROP TABLE orders
10
+ DROP TABLE movies
11
+ DROP TABLE subscribers
12
+ DROP TABLE booleantests
13
+ DROP TABLE auto_id_tests
14
+ DROP TABLE entrants
15
+ DROP TABLE colnametests
16
+ DROP TABLE mixins
17
+ DROP TABLE people
18
+ DROP TABLE readers
19
+ DROP TABLE binaries
20
+ DROP TABLE computers
21
+ DROP TABLE tasks
22
+ DROP TABLE posts
23
+ DROP TABLE comments
24
+ DROP TABLE authors
25
+ DROP TABLE categories
26
+ DROP TABLE categories_posts
27
+ DROP TABLE fk_test_has_fk
28
+ DROP TABLE fk_test_has_pk
29
+ DROP TABLE keyboards
30
+ DROP TABLE legacy_things
31
+ DROP TABLE numeric_data
32
+ DROP TABLE mixed_case_monkeys
33
+ DROP TABLE minimalistics
34
+ DROP TABLE schema_info
35
+ go
@@ -0,0 +1,222 @@
1
+ CREATE TABLE accounts (
2
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
3
+ firm_id int NULL,
4
+ credit_limit int NULL
5
+ )
6
+
7
+ CREATE TABLE funny_jokes (
8
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
9
+ name varchar(50) NULL
10
+ )
11
+
12
+ CREATE TABLE companies (
13
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
14
+ type varchar(50) NULL,
15
+ ruby_type varchar(50) NULL,
16
+ firm_id int NULL,
17
+ name varchar(50) NULL,
18
+ client_of int NULL,
19
+ rating int default 1
20
+ )
21
+
22
+
23
+ CREATE TABLE topics (
24
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
25
+ title varchar(255) NULL,
26
+ author_name varchar(255) NULL,
27
+ author_email_address varchar(255) NULL,
28
+ written_on datetime NULL,
29
+ bonus_time time NULL,
30
+ last_read date NULL,
31
+ content varchar(255) NULL,
32
+ approved tinyint default 1,
33
+ replies_count int default 0,
34
+ parent_id int NULL,
35
+ type varchar(50) NULL
36
+ )
37
+
38
+ CREATE TABLE developers (
39
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
40
+ name varchar(100) NULL,
41
+ salary int default 70000,
42
+ created_at datetime NULL,
43
+ updated_at datetime NULL
44
+ )
45
+
46
+ CREATE TABLE projects (
47
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
48
+ name varchar(100) NULL,
49
+ type varchar(255) NULL
50
+ )
51
+
52
+ CREATE TABLE developers_projects (
53
+ developer_id int NOT NULL,
54
+ project_id int NOT NULL,
55
+ joined_on datetime NULL,
56
+ access_level smallint default 1
57
+ )
58
+
59
+ CREATE TABLE orders (
60
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
61
+ name varchar(100) NULL,
62
+ billing_customer_id int NULL,
63
+ shipping_customer_id int NULL
64
+ )
65
+
66
+ CREATE TABLE customers (
67
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
68
+ name varchar(100) NULL,
69
+ balance int default 0,
70
+ address_street varchar(100) NULL,
71
+ address_city varchar(100) NULL,
72
+ address_country varchar(100) NULL,
73
+ gps_location varchar(100) NULL
74
+ )
75
+
76
+ CREATE TABLE movies (
77
+ movieid integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
78
+ name varchar(100) NULL
79
+ )
80
+
81
+ CREATE TABLE subscribers (
82
+ nick varchar(100) PRIMARY KEY,
83
+ name varchar(100) NULL
84
+ )
85
+
86
+ CREATE TABLE booleantests (
87
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
88
+ value int NULL
89
+ )
90
+
91
+ CREATE TABLE auto_id_tests (
92
+ auto_id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
93
+ value int NULL
94
+ )
95
+
96
+ CREATE TABLE entrants (
97
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
98
+ name varchar(255) NOT NULL,
99
+ course_id int NOT NULL
100
+ )
101
+
102
+ CREATE TABLE colnametests (
103
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
104
+ [references] int NOT NULL
105
+ )
106
+
107
+ CREATE TABLE mixins (
108
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
109
+ parent_id int NULL,
110
+ pos int NULL,
111
+ created_at datetime NULL,
112
+ updated_at datetime NULL,
113
+ lft int NULL,
114
+ rgt int NULL,
115
+ root_id int NULL,
116
+ type varchar(40) NULL
117
+ )
118
+
119
+ CREATE TABLE people (
120
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
121
+ first_name varchar(40) NULL,
122
+ lock_version int DEFAULT 0
123
+ )
124
+
125
+ CREATE TABLE readers (
126
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
127
+ post_id int NOT NULL,
128
+ person_id int NOT NULL
129
+ )
130
+
131
+ CREATE TABLE binaries (
132
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
133
+ data long binary NULL
134
+ )
135
+
136
+ CREATE TABLE computers (
137
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
138
+ developer int NOT NULL,
139
+ extendedWarranty int NOT NULL
140
+ )
141
+
142
+ CREATE TABLE posts (
143
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
144
+ author_id int NULL,
145
+ title varchar(255) NOT NULL,
146
+ body varchar(2048) NOT NULL,
147
+ type varchar(255) DEFAULT NULL
148
+ )
149
+
150
+ CREATE TABLE comments (
151
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
152
+ post_id int NOT NULL,
153
+ body varchar(2048) NOT NULL,
154
+ type varchar(255) NOT NULL
155
+ )
156
+
157
+ CREATE TABLE authors (
158
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
159
+ name varchar(255) NOT NULL
160
+ )
161
+
162
+ CREATE TABLE tasks (
163
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
164
+ starting datetime NULL,
165
+ ending datetime NULL
166
+ )
167
+
168
+ CREATE TABLE categories (
169
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
170
+ name varchar(255) NOT NULL,
171
+ type varchar(255) NOT NULL
172
+ )
173
+
174
+ CREATE TABLE categories_posts (
175
+ category_id int NOT NULL,
176
+ post_id int NOT NULL
177
+ )
178
+
179
+ CREATE TABLE fk_test_has_pk (
180
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY
181
+ )
182
+
183
+ CREATE TABLE fk_test_has_fk (
184
+ id integer PRIMARY KEY,
185
+ fk_id integer NOT NULL,
186
+
187
+ FOREIGN KEY (fk_id) REFERENCES fk_test_has_pk(id)
188
+ )
189
+
190
+
191
+ CREATE TABLE keyboards (
192
+ key_number integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
193
+ name varchar(50) NULL
194
+ )
195
+
196
+ --This table has an altered lock_version column name.
197
+ CREATE TABLE legacy_things (
198
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
199
+ tps_report_number int default NULL,
200
+ version int default 0
201
+ )
202
+
203
+
204
+ CREATE TABLE numeric_data (
205
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
206
+ bank_balance numeric(10,2),
207
+ big_bank_balance numeric(15,2),
208
+ world_population numeric(10),
209
+ my_house_population numeric(2),
210
+ decimal_number_with_default numeric(3,2) DEFAULT 2.78
211
+ )
212
+
213
+ CREATE TABLE mixed_case_monkeys (
214
+ monkeyID integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY,
215
+ fleaCount integer
216
+ );
217
+
218
+ CREATE TABLE minimalistics (
219
+ id integer DEFAULT AUTOINCREMENT NOT NULL PRIMARY KEY
220
+ );
221
+
222
+ go
@@ -0,0 +1,4 @@
1
+ DROP TABLE courses
2
+ go
3
+
4
+
@@ -0,0 +1,5 @@
1
+ CREATE TABLE courses (
2
+ id int NOT NULL PRIMARY KEY,
3
+ name varchar(255) NOT NULL
4
+ )
5
+ go
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: activerecord-sqlanywhere-adapter
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2008-11-03 00:00:00 -05:00
8
+ summary: ActiveRecord driver for SQL Anywhere
9
+ require_paths:
10
+ - lib
11
+ email: eric.farrar@ianywhere.com
12
+ homepage: http://sqlanywhere.rubyforge.org
13
+ rubyforge_project: sqlanywhere
14
+ description: ActiveRecord driver for SQL Anywhere
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.6
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Eric Farrar
31
+ files:
32
+ - lib/active_record/connection_adapters/sqlanywhere_adapter.rb
33
+ - test/connection.rb
34
+ - test/sqlanywhere.drop.sql
35
+ - test/sqlanywhere.sql
36
+ - test/sqlanywhere2.drop.sql
37
+ - test/sqlanywhere2.sql
38
+ - README
39
+ - CHANGELOG
40
+ - LICENSE
41
+ test_files: []
42
+
43
+ rdoc_options:
44
+ - --title
45
+ - ActiveRecord Driver for SQL Anywhere
46
+ - --main
47
+ - README
48
+ - --line-numbers
49
+ extra_rdoc_files:
50
+ - README
51
+ - CHANGELOG
52
+ - LICENSE
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ requirements: []
58
+
59
+ dependencies:
60
+ - !ruby/object:Gem::Dependency
61
+ name: sqlanywhere
62
+ version_requirement:
63
+ version_requirements: !ruby/object:Gem::Version::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 0.1.0
68
+ version:
69
+ - !ruby/object:Gem::Dependency
70
+ name: activerecord
71
+ version_requirement:
72
+ version_requirements: !ruby/object:Gem::Version::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 2.0.2
77
+ version: