activerecord-sqlanywhere-adapter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: