activerecord-sqlanywhere-adapter-in4systems 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,33 @@
1
+ =CHANGE LOG
2
+
3
+ =====0.2.0 -- 2010/12/02
4
+ - Added support for Rails 3.0.3
5
+ - Added support for Arel 2
6
+ - Removed test instructions for ActiveRecord 2.2.2
7
+ - Updated license to 2010
8
+
9
+ =====0.1.3 -- 2010/02/01
10
+ - Added :encoding option to connection string
11
+ - Fixed bug associated with dangling connections in development mode (http://groups.google.com/group/sql-anywhere-web-development/browse_thread/thread/79fa81bdfcf84c13/e29074e5b8b7ad6a?lnk=gst&q=activerecord#e29074e5b8b7ad6a)
12
+
13
+ =====0.1.2 -- 2008/12/30
14
+ - Fixed bug in ActiveRecord::ConnectionAdapters::SQLAnywhereAdapter#table_structure SQL (Paul Smith)
15
+ - Added options for :commlinks and :connection_name to database.yml configuration (Paul Smith)
16
+ - Fixed ActiveRecord::ConnectionAdapters::SQLAnywhereColumn.string_to_binary and binary_to_string (Paul Smith)
17
+ - Added :time as a native datatype (Paul Smith)
18
+ - Override SQLAnywhereAdapter#active? to prevent stale connections (Paul Smith)
19
+ - 'Fixed' coding style to match Rails standards (Paul Smith)
20
+ - Added temporary option for timestamp_format
21
+ - Fixed bug to let migrations drop columns with indexes
22
+ - Formatted code
23
+ - Fixed bug to raise proper exceptions when a query with a bad column in executed
24
+
25
+ =====0.1.1 -- 2008/11/06
26
+ - Changed file permissions on archives
27
+ - Changed archives to be specific to platform (.zip on windows, .tar.gz
28
+ otherwise)
29
+ - Removed the default rake task
30
+
31
+ =====0.1.0 -- 2008/10/15
32
+ - Initial Release
33
+
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ /*====================================================
2
+ *
3
+ * Copyright 2008-2010 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,56 @@
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 3.0.3 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 "DatabaseName" parameter
20
+ :server => 'arunit', #equivalent to the "ServerName" parameter
21
+ :username => 'dba', #equivalent to the "UserID" parameter
22
+ :password => 'sql', #equivalent to the "Password" parameter
23
+ :encoding => 'Windows-1252', #equivalent to the "CharSet" parameter
24
+ :commlinks => 'TCPIP()', #equivalent to the "Commlinks" parameter
25
+ :connection_name => 'Rails' #equivalent to the "ConnectionName" parameter
26
+ }
27
+
28
+ ==Running the ActiveRecord Unit Test Suite
29
+
30
+ 1. Open <tt><ACTIVERECORD_INSTALL_DIR>/rakefile</tt> and modify the line:
31
+
32
+ for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase )
33
+
34
+ to include <tt>sqlanywhere</tt>. It should now look like:
35
+
36
+ for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase sqlanywhere )
37
+
38
+ 2. Create directory to hold the connection definition:
39
+
40
+ mkdir <ACTIVERECORD_INSTALL_DIR>/test/connections/native_sqlanywhere
41
+
42
+ 3. Copy <tt>test/connection.rb</tt> into the newly created directory.
43
+
44
+ 4. Create the two test databases. These can be created in any directory.
45
+
46
+ dbinit -c arunit
47
+ dbinit -c arunit2
48
+ dbsrv11 arunit arunit2
49
+
50
+ <b>If the commands cannot be found, make sure you have set up the SQL Anywhere environment variables correctly.</b> For more information 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].
51
+
52
+ 6. Run the unit test suite from the ActiveRecord install directory:
53
+
54
+ rake test_sqlanywhere
55
+
56
+ <b>If the migration tests fail, make sure you have set up the SQL Anywhere environment variables correctly.</b> For more information 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,58 @@
1
+ # Taken from https://github.com/rsim/oracle-enhanced/blob/master/lib/active_record/connection_adapters/oracle_enhanced.rake
2
+
3
+ # implementation idea taken from JDBC adapter
4
+ # added possibility to execute previously defined task (passed as argument to task block)
5
+ def redefine_task(*args, &block)
6
+ task_name = Hash === args.first ? args.first.keys[0] : args.first
7
+ existing_task = Rake.application.lookup task_name
8
+ existing_actions = nil
9
+ if existing_task
10
+ class << existing_task; public :instance_variable_set, :instance_variable_get; end
11
+ existing_task.instance_variable_set "@prerequisites", FileList[]
12
+ existing_actions = existing_task.instance_variable_get "@actions"
13
+ existing_task.instance_variable_set "@actions", []
14
+ end
15
+ task(*args) do
16
+ block.call(existing_actions)
17
+ end
18
+ end
19
+
20
+
21
+ # https://github.com/rsim/oracle-enhanced/blob/master/lib/active_record/connection_adapters/oracle_enhanced.rake
22
+
23
+ if defined?(drop_database) == 'method'
24
+ def drop_database_with_sqlanywhere(config)
25
+ if config['adapter'] == 'sqlanywhere'
26
+ ActiveRecord::Base.establish_connection(config)
27
+ ActiveRecord::Base.connection.purge_database
28
+ else
29
+ drop_database_without_sqlanywhere(config)
30
+ end
31
+ end
32
+ alias :drop_database_without_sqlanywhere :drop_database
33
+ alias :drop_database :drop_database_with_sqlanywhere
34
+ end
35
+
36
+ namespace :db do
37
+ namespace :test do
38
+ redefine_task :purge => :environment do |existing_actions|
39
+ abcs = ActiveRecord::Base.configurations
40
+ if abcs['test']['adapter'] == 'sqlanywhere'
41
+ ActiveRecord::Base.establish_connection(:test)
42
+ ActiveRecord::Base.connection.purge_database
43
+ else
44
+ Array(existing_actions).each{|action| action.call}
45
+ end
46
+ end
47
+ end
48
+
49
+ namespace :schema do
50
+ redefine_task :dump => :environment do |existing_actions|
51
+ if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'sqlanywhere'
52
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env])
53
+ ActiveRecord::SchemaDumper.ignore_tables = ActiveRecord::Base.connection.viewed_tables
54
+ end
55
+ Array(existing_actions).each{|action| action.call}
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,793 @@
1
+ #encoding: utf-8
2
+ #====================================================
3
+ #
4
+ # Copyright 2008-2010 iAnywhere Solutions, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ #
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # While not a requirement of the license, if you do modify this file, we
21
+ # would appreciate hearing about it. Please email sqlany_interfaces@sybase.com
22
+ #
23
+ #
24
+ #====================================================
25
+
26
+ require 'active_record/connection_adapters/abstract_adapter'
27
+ require 'arel/visitors/sqlanywhere.rb'
28
+
29
+ # Singleton class to hold a valid instance of the SQLAnywhereInterface across all connections
30
+ class SA
31
+ include Singleton
32
+ attr_accessor :api
33
+
34
+ def initialize
35
+ require 'sqlanywhere' unless defined? SQLAnywhere
36
+ @api = SQLAnywhere::SQLAnywhereInterface.new()
37
+ raise LoadError, "Could not load SQLAnywhere DBCAPI library" if SQLAnywhere::API.sqlany_initialize_interface(@api) == 0
38
+ raise LoadError, "Could not initialize SQLAnywhere DBCAPI library" if @api.sqlany_init() == 0
39
+ end
40
+ end
41
+
42
+ module ActiveRecord
43
+ class Base
44
+ DEFAULT_CONFIG = { :username => 'dba', :password => 'sql' }
45
+ # Main connection function to SQL Anywhere
46
+ # Connection Adapter takes four parameters:
47
+ # * :database (required, no default). Corresponds to "DatabaseName=" in connection string
48
+ # * :server (optional, defaults to :databse). Corresponds to "ServerName=" in connection string
49
+ # * :username (optional, default to 'dba')
50
+ # * :password (optional, deafult to 'sql')
51
+ # * :encoding (optional, defaults to charset of OS)
52
+ # * :commlinks (optional). Corresponds to "CommLinks=" in connection string
53
+ # * :connection_name (optional). Corresponds to "ConnectionName=" in connection string
54
+
55
+ def self.sqlanywhere_connection(config)
56
+
57
+ if config[:connection_string]
58
+ connection_string = config[:connection_string]
59
+ else
60
+ config = DEFAULT_CONFIG.merge(config)
61
+
62
+ raise ArgumentError, "No database name was given. Please add a :database option." unless config.has_key?(:database)
63
+
64
+ connection_string = "ServerName=#{(config[:server] || config[:database])};DatabaseName=#{config[:database]};UserID=#{config[:username]};Password=#{config[:password]};"
65
+ connection_string += "CommLinks=#{config[:commlinks]};" unless config[:commlinks].nil?
66
+ connection_string += "ConnectionName=#{config[:connection_name]};" unless config[:connection_name].nil?
67
+ connection_string += "CharSet=#{config[:encoding]};" unless config[:encoding].nil?
68
+ connection_string += "Idle=0" # Prevent the server from disconnecting us if we're idle for >240mins (by default)
69
+ end
70
+
71
+ db = SA.instance.api.sqlany_new_connection()
72
+
73
+ ConnectionAdapters::SQLAnywhereAdapter.new(db, logger, connection_string)
74
+ end
75
+ end
76
+
77
+ module ConnectionAdapters
78
+ class SQLAnywhereException < StandardError
79
+ attr_reader :errno
80
+ attr_reader :sql
81
+
82
+ def initialize(message, errno, sql)
83
+ super(message)
84
+ @errno = errno
85
+ @sql = sql
86
+ end
87
+ end
88
+
89
+ class SQLAnywhereColumn < Column
90
+ private
91
+ # Overridden to handle SQL Anywhere integer, varchar, binary, and timestamp types
92
+ def simplified_type(field_type)
93
+ return :boolean if field_type =~ /tinyint/i
94
+ return :boolean if field_type =~ /bit/i
95
+ return :text if field_type =~ /long varchar/i
96
+ return :string if field_type =~ /varchar/i
97
+ return :binary if field_type =~ /long binary/i
98
+ return :datetime if field_type =~ /timestamp/i
99
+ return :integer if field_type =~ /smallint|bigint/i
100
+ return :text if field_type =~ /xml/i
101
+ return :integer if field_type =~ /uniqueidentifier/i
102
+ super
103
+ end
104
+
105
+ def extract_limit(sql_type)
106
+ case sql_type
107
+ when /^tinyint/i
108
+ 1
109
+ when /^smallint/i
110
+ 2
111
+ when /^integer/i
112
+ 4
113
+ when /^bigint/i
114
+ 8
115
+ else super
116
+ end
117
+ end
118
+
119
+ protected
120
+ # Handles the encoding of a binary object into SQL Anywhere
121
+ # SQL Anywhere requires that binary values be encoded as \xHH, where HH is a hexadecimal number
122
+ # This function encodes the binary string in this format
123
+ def self.string_to_binary(value)
124
+ "\\x" + value.unpack("H*")[0].scan(/../).join("\\x")
125
+ end
126
+
127
+ def self.binary_to_string(value)
128
+ value.gsub(/\\x[0-9]{2}/) { |byte| byte[2..3].hex }
129
+ end
130
+
131
+ # Should override the time column values.
132
+ # Sybase doesn't like the time zones.
133
+
134
+ end
135
+
136
+ class SQLAnywhereAdapter < AbstractAdapter
137
+ def initialize( connection, logger, connection_string = "") #:nodoc:
138
+ super(connection, logger)
139
+ @auto_commit = true
140
+ @affected_rows = 0
141
+ @connection_string = connection_string
142
+ @visitor = Arel::Visitors::SQLAnywhere.new self
143
+ connect!
144
+ end
145
+
146
+ def self.visitor_for(pool)
147
+ config = pool.spec.config
148
+
149
+ if config.fetch(:prepared_statements) {true}
150
+ Arel::Visitors::SQLAnywhere.new pool
151
+ else
152
+ BindSubstitution.new pool
153
+ end
154
+ end
155
+
156
+ def adapter_name #:nodoc:
157
+ 'SQLAnywhere'
158
+ end
159
+
160
+ def supports_migrations? #:nodoc:
161
+ true
162
+ end
163
+
164
+ def requires_reloading?
165
+ true
166
+ end
167
+
168
+ def active?
169
+ # The liveness variable is used a low-cost "no-op" to test liveness
170
+ SA.instance.api.sqlany_execute_immediate(@connection, "SET liveness = 1") == 1
171
+ rescue
172
+ false
173
+ end
174
+
175
+ def disconnect!
176
+ result = SA.instance.api.sqlany_disconnect( @connection )
177
+ super
178
+ end
179
+
180
+ def reconnect!
181
+ disconnect!
182
+ connect!
183
+ end
184
+
185
+ def supports_count_distinct? #:nodoc:
186
+ true
187
+ end
188
+
189
+ def supports_autoincrement? #:nodoc:
190
+ true
191
+ end
192
+
193
+ # Maps native ActiveRecord/Ruby types into SQLAnywhere types
194
+ # TINYINTs are treated as the default boolean value
195
+ # ActiveRecord allows NULLs in boolean columns, and the SQL Anywhere BIT type does not
196
+ # As a result, TINYINT must be used. All TINYINT columns will be assumed to be boolean and
197
+ # should not be used as single-byte integer columns. This restriction is similar to other ActiveRecord database drivers
198
+ def native_database_types #:nodoc:
199
+ {
200
+ :primary_key => 'INTEGER PRIMARY KEY DEFAULT AUTOINCREMENT NOT NULL',
201
+ :string => { :name => "varchar", :limit => 255 },
202
+ :text => { :name => "long varchar" },
203
+ :integer => { :name => "integer", :limit => 4 },
204
+ :float => { :name => "float" },
205
+ :decimal => { :name => "decimal" },
206
+ :datetime => { :name => "datetime" },
207
+ :timestamp => { :name => "datetime" },
208
+ :time => { :name => "time" },
209
+ :date => { :name => "date" },
210
+ :binary => { :name => "binary" },
211
+ :boolean => { :name => "tinyint", :limit => 1}
212
+ }
213
+ end
214
+
215
+ # QUOTING ==================================================
216
+
217
+ # Applies quotations around column names in generated queries
218
+ def quote_column_name(name) #:nodoc:
219
+ %Q("#{name}")
220
+ end
221
+
222
+ # Handles special quoting of binary columns. Binary columns will be treated as strings inside of ActiveRecord.
223
+ # ActiveRecord requires that any strings it inserts into databases must escape the backslash (\).
224
+ # Since in the binary case, the (\x) is significant to SQL Anywhere, it cannot be escaped.
225
+ def quote(value, column = nil)
226
+ case value
227
+ when String, ActiveSupport::Multibyte::Chars
228
+ value_S = value.to_s
229
+ if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
230
+ "'#{column.class.string_to_binary(value_S)}'"
231
+ else
232
+ super(value, column)
233
+ end
234
+ else
235
+ super(value, column)
236
+ end
237
+ end
238
+
239
+ def quoted_true
240
+ '1'
241
+ end
242
+
243
+ def quoted_false
244
+ '0'
245
+ end
246
+
247
+
248
+ # This function (distinct) is based on the Oracle Enhacned ActiveRecord driver maintained by Raimonds Simanovskis (2010)
249
+ # (https://github.com/rsim/oracle-enhanced)
250
+ def distinct(columns, order_by) #:nodoc:
251
+ return "DISTINCT #{columns}" if order_by.blank?
252
+
253
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
254
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
255
+ order_columns = if order_by.is_a?(String)
256
+ order_by.split(',').map { |s| s.strip }.reject(&:blank?)
257
+ else # in latest ActiveRecord versions order_by is already Array
258
+ order_by
259
+ end
260
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
261
+ # remove any ASC/DESC modifiers
262
+ value = c =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : c
263
+ "FIRST_VALUE(#{value}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
264
+ end
265
+ sql = "DISTINCT #{columns}, "
266
+ sql << order_columns * ", "
267
+ end
268
+
269
+ # The database execution function
270
+ def execute(sql, name = nil) #:nodoc:
271
+ if name == :skip_logging
272
+ r = SA.instance.api.sqlany_execute_immediate(@connection, sql)
273
+ sqlanywhere_error_test(sql) if r==0
274
+ else
275
+ log(sql, name) { execute(sql, :skip_logging) }
276
+ end
277
+ end
278
+
279
+ def sqlanywhere_error_test(sql = '')
280
+ error_code, error_message = SA.instance.api.sqlany_error(@connection)
281
+ if error_code != 0
282
+ sqlanywhere_error(error_code, error_message, sql)
283
+ end
284
+ end
285
+
286
+ def sqlanywhere_error(code, message, sql)
287
+ raise SQLAnywhereException.new(message, code, sql)
288
+ end
289
+
290
+ def translate_exception(exception, message)
291
+ return super unless exception.respond_to?(:errno)
292
+ case exception.errno
293
+ when -143
294
+ if exception.sql !~ /^SELECT/i then
295
+ raise ActiveRecord::ActiveRecordError.new(message)
296
+ else
297
+ super
298
+ end
299
+ when -194
300
+ raise InvalidForeignKey.new(message, exception)
301
+ when -196
302
+ raise RecordNotUnique.new(message, exception)
303
+ when -183
304
+ raise ArgumentError, message
305
+ else
306
+ super
307
+ end
308
+ end
309
+
310
+ # The database update function.
311
+ def update_sql(sql, name = nil)
312
+ execute( sql, name )
313
+ return @affected_rows
314
+ end
315
+
316
+ # The database delete function.
317
+ def delete_sql(sql, name = nil) #:nodoc:
318
+ execute( sql, name )
319
+ return @affected_rows
320
+ end
321
+
322
+ # The database insert function.
323
+ # ActiveRecord requires that insert_sql returns the primary key of the row just inserted. In most cases, this can be accomplished
324
+ # by immediatly querying the @@identity property. If the @@identity property is 0, then passed id_value is used
325
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
326
+ execute(sql, name)
327
+
328
+ retval = last_inserted_id(nil)
329
+ retval = id_value if retval == 0
330
+ return retval
331
+ end
332
+
333
+ def exec_delete(sql, name = 'SQL', binds = [])
334
+ exec_query(sql, name, binds)
335
+ @affected_rows
336
+ end
337
+ alias :exec_update :exec_delete
338
+
339
+ def last_inserted_id(result)
340
+ identity = SA.instance.api.sqlany_execute_direct(@connection, 'SELECT @@identity')
341
+ raise ActiveRecord::StatementInvalid.new("#{SA.instance.api.sqlany_error(@connection)}:#{sql}") if identity.nil?
342
+ SA.instance.api.sqlany_fetch_next(identity)
343
+ retval = SA.instance.api.sqlany_get_column(identity, 0)[1]
344
+ SA.instance.api.sqlany_free_stmt(identity)
345
+
346
+ return retval
347
+ end
348
+
349
+ # Returns a query as an array of arrays
350
+ def select_rows(sql, name = nil)
351
+ rs = SA.instance.api.sqlany_execute_direct(@connection, sql)
352
+ raise ActiveRecord::StatementInvalid.new("#{SA.instance.api.sqlany_error(@connection)}:#{sql}") if rs.nil?
353
+ record = []
354
+ while SA.instance.api.sqlany_fetch_next(rs) == 1
355
+ max_cols = SA.instance.api.sqlany_num_cols(rs)
356
+ result = Array.new(max_cols)
357
+ max_cols.times do |cols|
358
+ result[cols] = SA.instance.api.sqlany_get_column(rs, cols)[1]
359
+ end
360
+ record << result
361
+ end
362
+ SA.instance.api.sqlany_free_stmt(rs)
363
+ return record
364
+ end
365
+
366
+ def begin_db_transaction #:nodoc:
367
+ @auto_commit = false;
368
+ end
369
+
370
+ def commit_db_transaction #:nodoc:
371
+ SA.instance.api.sqlany_commit(@connection)
372
+ @auto_commit = true;
373
+ end
374
+
375
+ def rollback_db_transaction #:nodoc:
376
+ SA.instance.api.sqlany_rollback(@connection)
377
+ @auto_commit = true;
378
+ end
379
+
380
+ def add_lock!(sql, options) #:nodoc:
381
+ sql
382
+ end
383
+
384
+ # SQL Anywhere does not support sizing of integers based on the sytax INTEGER(size). Integer sizes
385
+ # must be captured when generating the SQL and replaced with the appropriate size.
386
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
387
+ type = type.to_sym
388
+ if native = native_database_types[type]
389
+ if type == :integer
390
+ case limit
391
+ when 1
392
+ column_type_sql = 'tinyint'
393
+ when 2
394
+ column_type_sql = 'smallint'
395
+ when 3..4
396
+ column_type_sql = 'integer'
397
+ when 5..8
398
+ column_type_sql = 'bigint'
399
+ else
400
+ column_type_sql = 'integer'
401
+ end
402
+ column_type_sql
403
+ elsif type == :string and !limit.nil?
404
+ "varchar (#{limit})"
405
+ elsif type == :boolean
406
+ column_type_sql = 'tinyint'
407
+ else
408
+ super(type, limit, precision, scale)
409
+ end
410
+ else
411
+ super(type, limit, precision, scale)
412
+ end
413
+ end
414
+
415
+ def viewed_tables(name = nil)
416
+ list_of_tables(['view'], name)
417
+ end
418
+
419
+ def base_tables(name = nil)
420
+ list_of_tables(['base'], name)
421
+ end
422
+
423
+ # Do not return SYS-owned or DBO-owned tables or RS_systabgroup-owned
424
+ def tables(name = nil) #:nodoc:
425
+ list_of_tables(['base', 'view'])
426
+ end
427
+
428
+ def columns(table_name, name = nil) #:nodoc:
429
+ table_structure(table_name).map do |field|
430
+ SQLAnywhereColumn.new(field['name'], field['default'], field['domain'], (field['nulls'] == 1))
431
+ end
432
+ end
433
+
434
+ def indexes(table_name, name = nil) #:nodoc:
435
+ if @major_version <= 11 # the sql doesn't work in older databases.
436
+ return []
437
+ end
438
+ sql = "SELECT DISTINCT index_name, \"unique\" FROM SYS.SYSTABLE INNER JOIN SYS.SYSIDXCOL ON SYS.SYSTABLE.table_id = SYS.SYSIDXCOL.table_id INNER JOIN SYS.SYSIDX ON SYS.SYSTABLE.table_id = SYS.SYSIDX.table_id AND SYS.SYSIDXCOL.index_id = SYS.SYSIDX.index_id WHERE table_name = '#{table_name}' AND index_category > 2"
439
+ select(sql, name).map do |row|
440
+ index = IndexDefinition.new(table_name, row['index_name'])
441
+ index.unique = row['unique'] == 1
442
+ sql = "SELECT column_name FROM SYS.SYSIDX INNER JOIN SYS.SYSIDXCOL ON SYS.SYSIDXCOL.table_id = SYS.SYSIDX.table_id AND SYS.SYSIDXCOL.index_id = SYS.SYSIDX.index_id INNER JOIN SYS.SYSCOLUMN ON SYS.SYSCOLUMN.table_id = SYS.SYSIDXCOL.table_id AND SYS.SYSCOLUMN.column_id = SYS.SYSIDXCOL.column_id WHERE index_name = '#{row['index_name']}'"
443
+ index.columns = select(sql).map { |col| col['column_name'] }
444
+ index
445
+ end
446
+ end
447
+
448
+ def primary_key(table_name) #:nodoc:
449
+ sql = "SELECT cname from SYS.SYSCOLUMNS where tname = '#{table_name}' and in_primary_key = 'Y'"
450
+ rs = exec_query(sql)
451
+ if !rs.nil? and !rs.first.nil?
452
+ rs.first['cname']
453
+ else
454
+ nil
455
+ end
456
+ end
457
+
458
+ def remove_index(table_name, options={}) #:nodoc:
459
+ execute "DROP INDEX #{quote_table_name(table_name)}.#{quote_column_name(index_name(table_name, options))}"
460
+ end
461
+
462
+ def rename_table(name, new_name)
463
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME #{quote_table_name(new_name)}"
464
+ end
465
+
466
+ def change_column_default(table_name, column_name, default) #:nodoc:
467
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
468
+ end
469
+
470
+ def change_column_null(table_name, column_name, null, default = nil)
471
+ unless null || default.nil?
472
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
473
+ end
474
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? '' : 'NOT'} NULL")
475
+ end
476
+
477
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
478
+ 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])}"
479
+ add_column_options!(add_column_sql, options)
480
+ add_column_sql << ' NULL' if options[:null]
481
+ execute(add_column_sql)
482
+ end
483
+
484
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
485
+ if column_name.downcase == new_column_name.downcase
486
+ whine = "if_the_only_change_is_case_sqlanywhere_doesnt_rename_the_column"
487
+ rename_column table_name, column_name, "#{new_column_name}#{whine}"
488
+ rename_column table_name, "#{new_column_name}#{whine}", new_column_name
489
+ else
490
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
491
+ end
492
+ end
493
+
494
+ def remove_column(table_name, *column_names)
495
+ column_names = column_names.flatten
496
+ column_names.zip(columns_for_remove(table_name, *column_names)).each do |unquoted_column_name, column_name|
497
+ sql = <<-SQL
498
+ SELECT "index_name" FROM SYS.SYSTAB join SYS.SYSTABCOL join SYS.SYSIDXCOL join SYS.SYSIDX
499
+ WHERE "column_name" = '#{unquoted_column_name}' AND "table_name" = '#{table_name}'
500
+ SQL
501
+ select(sql, nil).each do |row|
502
+ execute "DROP INDEX \"#{table_name}\".\"#{row['index_name']}\""
503
+ end
504
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}"
505
+ end
506
+ end
507
+
508
+
509
+ def purge_database
510
+ base_tables.each do |base_table_name|
511
+ drop_table(base_table_name)
512
+ end
513
+ end
514
+
515
+ def select(sql, name = nil, binds = []) #:nodoc:
516
+ exec_query(sql, name, binds).to_a
517
+ end
518
+
519
+ protected
520
+
521
+ def list_of_tables(types, name = nil)
522
+ sql = "SELECT table_name FROM SYS.SYSTABLE WHERE table_type in (#{types.map{|t| quote(t)}.join(', ')}) and creator NOT IN (0,3,5)"
523
+ select(sql, name).map { |row| row["table_name"] }
524
+ end
525
+
526
+ # ActiveRecord uses the OFFSET/LIMIT keywords at the end of query to limit the number of items in the result set.
527
+ # This syntax is NOT supported by SQL Anywhere. In previous versions of this adapter this adapter simply
528
+ # overrode the add_limit_offset function and added the appropriate TOP/START AT keywords to the start of the query.
529
+ # However, this will not work for cases where add_limit_offset is being used in a subquery since add_limit_offset
530
+ # is called with the WHERE clause.
531
+ #
532
+ # As a result, the following function must be called before every SELECT statement against the database. It
533
+ # recursivly walks through all subqueries in the SQL statment and replaces the instances of OFFSET/LIMIT with the
534
+ # corresponding TOP/START AT. It was my intent to do the entire thing using regular expressions, but it would seem
535
+ # that it is not possible given that it must count levels of nested brackets.
536
+ def modify_limit_offset(sql)
537
+ modified_sql = ""
538
+ subquery_sql = ""
539
+ in_single_quote = false
540
+ in_double_quote = false
541
+ nesting_level = 0
542
+ if sql =~ /(OFFSET|LIMIT)/xmi then
543
+ if sql =~ /\(/ then
544
+ sql.split(//).each_with_index do |x, i|
545
+ case x[0]
546
+ when 40 # left brace - (
547
+ modified_sql << x if nesting_level == 0
548
+ subquery_sql << x if nesting_level > 0
549
+ nesting_level = nesting_level + 1 unless in_double_quote || in_single_quote
550
+ when 41 # right brace - )
551
+ nesting_level = nesting_level - 1 unless in_double_quote || in_single_quote
552
+ if nesting_level == 0 and !in_double_quote and !in_single_quote then
553
+ modified_sql << modify_limit_offset(subquery_sql)
554
+ subquery_sql = ""
555
+ end
556
+ modified_sql << x if nesting_level == 0
557
+ subquery_sql << x if nesting_level > 0
558
+ when 39 # single quote - '
559
+ in_single_quote = in_single_quote ^ true unless in_double_quote
560
+ modified_sql << x if nesting_level == 0
561
+ subquery_sql << x if nesting_level > 0
562
+ when 34 # double quote - "
563
+ in_double_quote = in_double_quote ^ true unless in_single_quote
564
+ modified_sql << x if nesting_level == 0
565
+ subquery_sql << x if nesting_level > 0
566
+ else
567
+ modified_sql << x if nesting_level == 0
568
+ subquery_sql << x if nesting_level > 0
569
+ end
570
+ raise ActiveRecord::StatementInvalid.new("Braces do not match: #{sql}") if nesting_level < 0
571
+ end
572
+ else
573
+ modified_sql = sql
574
+ end
575
+ raise ActiveRecord::StatementInvalid.new("Quotes do not match: #{sql}") if in_double_quote or in_single_quote
576
+ return "" if modified_sql.nil?
577
+ select_components = modified_sql.scan(/\ASELECT\s+(DISTINCT)?(.*?)(?:\s+LIMIT\s+(.*?))?(?:\s+OFFSET\s+(.*?))?\Z/xmi)
578
+ return modified_sql if select_components[0].nil?
579
+ final_sql = "SELECT #{select_components[0][0]} "
580
+ final_sql << "TOP #{select_components[0][2].nil? ? 1000000 : select_components[0][2]} "
581
+ final_sql << "START AT #{(select_components[0][3].to_i + 1).to_s} " unless select_components[0][3].nil?
582
+ final_sql << "#{select_components[0][1]}"
583
+ return final_sql
584
+ else
585
+ return sql
586
+ end
587
+ end
588
+
589
+ # Queries the structure of a table including the columns names, defaults, type, and nullability
590
+ # ActiveRecord uses the type to parse scale and precision information out of the types. As a result,
591
+ # chars, varchars, binary, nchars, nvarchars must all be returned in the form <i>type</i>(<i>width</i>)
592
+ # numeric and decimal must be returned in the form <i>type</i>(<i>width</i>, <i>scale</i>)
593
+ # Nullability is returned as 0 (no nulls allowed) or 1 (nulls allowed)
594
+ # Alos, ActiveRecord expects an autoincrement column to have default value of NULL
595
+
596
+ def table_structure(table_name)
597
+ sql = <<-SQL
598
+ SELECT SYS.SYSCOLUMN.column_name AS name,
599
+ if left("default",1)='''' then substring("default", 2, length("default")-2) // remove the surrounding quotes
600
+ else NULLIF(SYS.SYSCOLUMN."default", 'autoincrement')
601
+ endif AS "default",
602
+ IF SYS.SYSCOLUMN.domain_id IN (7,8,9,11,33,34,35,3,27) THEN
603
+ IF SYS.SYSCOLUMN.domain_id IN (3,27) THEN
604
+ SYS.SYSDOMAIN.domain_name || '(' || SYS.SYSCOLUMN.width || ',' || SYS.SYSCOLUMN.scale || ')'
605
+ ELSE
606
+ SYS.SYSDOMAIN.domain_name || '(' || SYS.SYSCOLUMN.width || ')'
607
+ ENDIF
608
+ ELSE
609
+ SYS.SYSDOMAIN.domain_name
610
+ ENDIF AS domain,
611
+ IF SYS.SYSCOLUMN.nulls = 'Y' THEN 1 ELSE 0 ENDIF AS nulls
612
+ FROM
613
+ SYS.SYSCOLUMN
614
+ INNER JOIN SYS.SYSTABLE ON SYS.SYSCOLUMN.table_id = SYS.SYSTABLE.table_id
615
+ INNER JOIN SYS.SYSDOMAIN ON SYS.SYSCOLUMN.domain_id = SYS.SYSDOMAIN.domain_id
616
+ WHERE
617
+ SYS.SYSTABLE.creator = 1 AND
618
+ table_name = '#{table_name}'
619
+ SQL
620
+ structure = exec_query(sql, :skip_logging)
621
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure == false
622
+ structure
623
+ end
624
+
625
+ # Required to prevent DEFAULT NULL being added to primary keys
626
+ def options_include_default?(options)
627
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
628
+ end
629
+
630
+ private
631
+
632
+ def connect!
633
+ result = SA.instance.api.sqlany_connect(@connection, @connection_string)
634
+ if result == 1 then
635
+ set_connection_options
636
+ else
637
+ error = SA.instance.api.sqlany_error(@connection)
638
+ raise ActiveRecord::ActiveRecordError.new("#{error}: Cannot Establish Connection")
639
+ end
640
+ version = exec_query('select @@version').rows[0][0]
641
+ @major_version = /^\d+/.match(version).to_s.to_i
642
+ end
643
+
644
+ def set_connection_options
645
+ SA.instance.api.sqlany_execute_immediate(@connection, "SET TEMPORARY OPTION non_keywords = 'LOGIN'") rescue nil
646
+ SA.instance.api.sqlany_execute_immediate(@connection, "SET TEMPORARY OPTION timestamp_format = 'YYYY-MM-DD HH:NN:SS'") rescue nil
647
+ #SA.instance.api.sqlany_execute_immediate(@connection, "SET OPTION reserved_keywords = 'LIMIT'") rescue nil
648
+ # The liveness variable is used a low-cost "no-op" to test liveness
649
+ SA.instance.api.sqlany_execute_immediate(@connection, "CREATE VARIABLE liveness INT") rescue nil
650
+ end
651
+
652
+ def exec_query(sql, name = 'SQL', binds = [])
653
+ log(sql, name, binds) do
654
+ stmt = SA.instance.api.sqlany_prepare(@connection, sql)
655
+
656
+ if stmt.nil?
657
+ sqlanywhere_error_test(sql)
658
+ end
659
+
660
+ for i in 0...binds.length
661
+ bind_type = binds[i][0].type
662
+ bind_value = binds[i][1]
663
+ result, bind_param = SA.instance.api.sqlany_describe_bind_param(stmt, i)
664
+ sqlanywhere_error_test(sql) if result==0
665
+
666
+ bind_param.set_direction(:input)
667
+ if bind_value.nil?
668
+ bind_param.set_value(nil)
669
+ elsif bind_type == :datetime
670
+ bind_param.set_value(bind_value.to_datetime.to_s :db)
671
+ elsif bind_type == :boolean
672
+ bind_param.set_value(bind_value ? 1 : 0)
673
+ elsif bind_type == :decimal
674
+ bind_param.set_value(bind_value.to_s)
675
+ elsif bind_type == :date
676
+ bind_param.set_value(bind_value.to_s)
677
+ else
678
+ bind_param.set_value(bind_value)
679
+ end
680
+ result = SA.instance.api.sqlany_bind_param(stmt, i, bind_param)
681
+ sqlanywhere_error_test(sql) if result==0
682
+
683
+ end
684
+
685
+ if SA.instance.api.sqlany_execute(stmt) == 0
686
+ sqlanywhere_error_test(sql)
687
+ end
688
+
689
+ fields = []
690
+ native_types = []
691
+
692
+ num_cols = SA.instance.api.sqlany_num_cols(stmt)
693
+ sqlanywhere_error_test(sql) if num_cols == -1
694
+
695
+ for i in 0...num_cols
696
+ result, col_num, name, ruby_type, native_type, precision, scale, max_size, nullable = SA.instance.api.sqlany_get_column_info(stmt, i)
697
+ sqlanywhere_error_test(sql) if result==0
698
+ fields << name
699
+ native_types << native_type
700
+ end
701
+ rows = []
702
+ while SA.instance.api.sqlany_fetch_next(stmt) == 1
703
+ row = []
704
+ for i in 0...num_cols
705
+ r, value = SA.instance.api.sqlany_get_column(stmt, i)
706
+ row << native_type_to_ruby_type(native_types[i], value)
707
+ end
708
+ rows << row
709
+ end
710
+ SA.instance.api.sqlany_free_stmt(stmt)
711
+
712
+ if @auto_commit
713
+ result = SA.instance.api.sqlany_commit(@connection)
714
+ sqlanywhere_error_test(sql) if result==0
715
+ end
716
+ return ActiveRecord::Result.new(fields, rows)
717
+ end
718
+ end
719
+
720
+ def query(sql)
721
+ return if sql.nil?
722
+ #sql = modify_limit_offset(sql)
723
+
724
+ # ActiveRecord allows a query to return TOP 0. SQL Anywhere requires that the TOP value is a positive integer.
725
+ return Array.new() if sql =~ /TOP 0/i
726
+
727
+ # Executes the query, iterates through the results, and builds an array of hashes.
728
+ rs = SA.instance.api.sqlany_execute_direct(@connection, sql)
729
+ if rs.nil?
730
+ result, errstr = SA.instance.api.sqlany_error(@connection)
731
+ raise SQLAnywhereException.new(errstr, result, sql)
732
+ end
733
+
734
+ record = []
735
+ if( SA.instance.api.sqlany_num_cols(rs) > 0 )
736
+ while SA.instance.api.sqlany_fetch_next(rs) == 1
737
+ max_cols = SA.instance.api.sqlany_num_cols(rs)
738
+ result = Hash.new()
739
+ max_cols.times do |cols|
740
+ col_content=SA.instance.api.sqlany_get_column(rs, cols)[1]
741
+ if !col_content.nil? && col_content.is_a?(String)
742
+ puts ":encoding missing in database.yml" if ActiveRecord::Base.configurations[Rails.env]['encoding'].nil?
743
+ col_content = col_content.force_encoding(ActiveRecord::Base.configurations[Rails.env]['encoding'])
744
+ end
745
+ result[SA.instance.api.sqlany_get_column_info(rs, cols)[2]] = col_content
746
+ end
747
+ record << result
748
+ end
749
+ @affected_rows = 0
750
+ else
751
+ @affected_rows = SA.instance.api.sqlany_affected_rows(rs)
752
+ end
753
+ SA.instance.api.sqlany_free_stmt(rs)
754
+
755
+ SA.instance.api.sqlany_commit(@connection) if @auto_commit
756
+ return record
757
+ end
758
+
759
+ # convert sqlany type to ruby type
760
+ # the types are taken from here
761
+ # http://dcx.sybase.com/1101/en/dbprogramming_en11/pg-c-api-native-type-enum.html
762
+ def native_type_to_ruby_type(native_type, value)
763
+ return nil if value.nil?
764
+ case native_type
765
+ when :decimal # (also and more importantly numeric)
766
+ BigDecimal.new(value)
767
+ when :var_char, :fix_char, :long_var_char, :string, :long_n_var_char
768
+ # hack, not sure how to manage proper encoding
769
+ value = value.force_encoding(ActiveRecord::Base.connection_config['encoding'] || 'UTF-8')
770
+ value = value.encode('UTF-8')
771
+ # Why am I removing the whitespace from the end of the string?
772
+ #
773
+ # Sqlanywhere allowed us to create a string foreign key.
774
+ # Somehow on only one end of the foreign key, the values got spaces at the end.
775
+ # The foreign key was still valid in Sqlanywhere: It worked for joins and it worked for referencing constraints.
776
+ #
777
+ # It however does not work for the ActiveRecord includes method.
778
+ # Rails will bring back the associated records, but then it fails to pair the records correctly.
779
+ # Removing whitespace from the ends of all strings fixes this. It is a hack however, so I'm open
780
+ # for suggestions on coming up with a better method.
781
+ #
782
+ begin
783
+ value = value.rstrip
784
+ rescue ArgumentError # invalid byte sequence in UTF-8
785
+ end
786
+ else
787
+ value
788
+ end
789
+ end
790
+ end
791
+ end
792
+ end
793
+
@@ -0,0 +1,16 @@
1
+ # https://github.com/rsim/oracle-enhanced/blob/master/lib/activerecord-oracle_enhanced-adapter.rb
2
+
3
+ if defined?(::Rails::Railtie)
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ class SqlanywhereRailtie < ::Rails::Railtie
8
+ rake_tasks do
9
+ load 'active_record/connection_adapters/sqlanywhere.rake'
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,125 @@
1
+ module Arel
2
+ module Visitors
3
+ class SQLAnywhere < Arel::Visitors::ToSql
4
+ private
5
+ def visit_Arel_Nodes_SelectStatement o
6
+ o = order_hacks(o)
7
+
8
+ is_distinct = using_distinct?(o)
9
+
10
+ o.limit = 1000000 if (o.offset && !o.limit)
11
+ o.limit = o.limit.expr if(o.limit.is_a?(Arel::Nodes::Limit))
12
+ o.limit = o.limit if(o.limit.is_a?(Fixnum))
13
+
14
+ [
15
+ "SELECT",
16
+ ("DISTINCT" if is_distinct),
17
+ ("TOP #{o.limit}" if o.limit),
18
+ (visit_Arel_Nodes_Offset(o.offset) if o.offset),
19
+ o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
20
+ ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
21
+ #("LIMIT #{o.limit}" if o.limit),
22
+ #(visit(o.offset) if o.offset),
23
+ (visit(o.lock) if o.lock),
24
+ ].compact.join ' '
25
+ end
26
+
27
+ def visit_Arel_Nodes_SelectCore o
28
+ [
29
+ "#{o.projections.map { |x| visit x }.join ', '}",
30
+ ("FROM #{visit o.source}" if o.source), # Joins
31
+ ("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
32
+ ("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
33
+ (visit(o.having) if o.having),
34
+ ].compact.join ' '
35
+ end
36
+
37
+ def visit_Arel_Nodes_Group o
38
+ expr = o.expr.clone
39
+ if expr.class == Arel::Nodes::NamedFunction
40
+ expr.alias = nil
41
+ end
42
+ visit expr
43
+ end
44
+
45
+ def visit_Arel_Nodes_Offset o
46
+ "START AT #{visit(o.expr) + 1}"
47
+ end
48
+
49
+ def visit_Arel_Nodes_True o
50
+ "1=1"
51
+ end
52
+
53
+ def visit_Arel_Nodes_False o
54
+ "1=0"
55
+ end
56
+
57
+ def visit_Arel_Nodes_Matches o
58
+ # The version in arel cannot like integer columns
59
+ left = visit o.left # This method sets last column
60
+ # If last column was left, visit o.right would return 0
61
+ self.last_column = nil
62
+ "#{left} LIKE #{visit o.right}"
63
+ end
64
+
65
+
66
+
67
+ def using_distinct?(o)
68
+ o.cores.any? do |core|
69
+ core.set_quantifier.class == Arel::Nodes::Distinct
70
+ end
71
+ end
72
+
73
+ # The functions (order_hacks, split_order_string) are based on the Oracle Enhacned ActiveRecord driver maintained by Raimonds Simanovskis (2010)
74
+ # (https://github.com/rsim/oracle-enhanced)
75
+
76
+ ###
77
+ # Hacks for the order clauses
78
+ def order_hacks o
79
+ return o if o.orders.empty?
80
+ return o unless o.cores.any? do |core|
81
+ core.projections.any? do |projection|
82
+ /DISTINCT.*FIRST_VALUE/ === projection
83
+ end
84
+ end
85
+ # Previous version with join and split broke ORDER BY clause
86
+ # if it contained functions with several arguments (separated by ',').
87
+ #
88
+ # orders = o.orders.map { |x| visit x }.join(', ').split(',')
89
+ orders = o.orders.map do |x|
90
+ string = visit x
91
+ if string.include?(',')
92
+ split_order_string(string)
93
+ else
94
+ string
95
+ end
96
+ end.flatten
97
+ o.orders = []
98
+ orders.each_with_index do |order, i|
99
+ o.orders <<
100
+ Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i === order}")
101
+ end
102
+ o
103
+ end
104
+
105
+ # Split string by commas but count opening and closing brackets
106
+ # and ignore commas inside brackets.
107
+ def split_order_string(string)
108
+ array = []
109
+ i = 0
110
+ string.split(',').each do |part|
111
+ if array[i]
112
+ array[i] << ',' << part
113
+ else
114
+ # to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
115
+ array[i] = '' << part
116
+ end
117
+ i += 1 if array[i].count('(') == array[i].count(')')
118
+ end
119
+ array
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ Arel::Visitors::VISITORS['sqlanywhere'] = Arel::Visitors::SQLAnywhere
@@ -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'
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-sqlanywhere-adapter-in4systems
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Eric Farar
9
+ - Chris Couzens
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-01-21 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sqlanywhere-ffi
17
+ version_requirements: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0
22
+ none: false
23
+ requirement: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ! '>='
26
+ - !ruby/object:Gem::Version
27
+ version: 1.0.0
28
+ none: false
29
+ prerelease: false
30
+ type: :runtime
31
+ - !ruby/object:Gem::Dependency
32
+ name: activerecord
33
+ version_requirements: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 3.0.3
38
+ none: false
39
+ requirement: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 3.0.3
44
+ none: false
45
+ prerelease: false
46
+ type: :runtime
47
+ description: ActiveRecord driver for SQL Anywhere customized for in4systems
48
+ email: eric.farrar@ianywhere.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - CHANGELOG
54
+ - LICENSE
55
+ - README
56
+ - test/connection.rb
57
+ - lib/active_record/connection_adapters/sqlanywhere_adapter.rb
58
+ - lib/arel/visitors/sqlanywhere.rb
59
+ - lib/active_record/connection_adapters/sqlanywhere.rake
60
+ - lib/activerecord-sqlanywhere-adapter.rb
61
+ homepage: https://github.com/in4systems/activerecord-sqlanywhere-adapter
62
+ licenses:
63
+ - Apache License Version 2.0
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: !binary |-
73
+ MA==
74
+ none: false
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: !binary |-
80
+ MA==
81
+ none: false
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.24
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: ActiveRecord driver for SQL Anywhere
88
+ test_files: []