activerecord-sqlanywhere-adapter-in4systems 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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: []