activerecord-sqlanywhere-jdbc-in4systems-adapter 1.0.12-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +33 -0
- data/LICENSE +23 -0
- data/README +56 -0
- data/lib/active_record/connection_adapters/sqlanywhere_jdbc_in4systems_adapter.rb +492 -0
- data/lib/arel/visitors/sqlanywhere.rb +159 -0
- data/test/connection.rb +25 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 754ffa261c7b9d3bfc68476c8463161b11fb7fbc
|
4
|
+
data.tar.gz: ba39ec75ea5143074d62966c70a608255bb89205
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3cbf49e0b0bc3657d7ca89e9fc378551c693c0b6104946650799245af164a05bbf3d623e813bd9cfe475fa6a052345882df555c399034ab25e8f0212a26f135a
|
7
|
+
data.tar.gz: 6cca0835a18ce4e7cbbbfde53ea4bfac99b322bb8a4d83295f1a4da6e842a1374d922ebb5d13931d92b35167dbd29dde2aeb67bf9b2a16d72aff7de74bb8f277
|
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,492 @@
|
|
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
|
+
#require 'active_record'
|
26
|
+
require 'activerecord-jdbc-adapter'
|
27
|
+
|
28
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
29
|
+
require 'arel/visitors/sqlanywhere.rb'
|
30
|
+
require 'pathname'
|
31
|
+
|
32
|
+
module ActiveRecord
|
33
|
+
class Base
|
34
|
+
DEFAULT_CONFIG = { :username => 'dba', :password => 'sql' }
|
35
|
+
# Main connection function to SQL Anywhere
|
36
|
+
# Connection Adapter takes four parameters:
|
37
|
+
# * :database (required, no default). Corresponds to "DatabaseName=" in connection string
|
38
|
+
# * :server (optional, defaults to :databse). Corresponds to "ServerName=" in connection string
|
39
|
+
# * :username (optional, default to 'dba')
|
40
|
+
# * :password (optional, deafult to 'sql')
|
41
|
+
# * :encoding (optional, defaults to charset of OS)
|
42
|
+
# * :commlinks (optional). Corresponds to "CommLinks=" in connection string
|
43
|
+
# * :connection_name (optional). Corresponds to "ConnectionName=" in connection string
|
44
|
+
|
45
|
+
def self.sqlanywhere_jdbc_in4systems_connection(config)
|
46
|
+
|
47
|
+
if config[:connection_string]
|
48
|
+
connection_string = config[:connection_string]
|
49
|
+
else
|
50
|
+
config = DEFAULT_CONFIG.merge(config)
|
51
|
+
|
52
|
+
raise ArgumentError, "No database name was given. Please add a :database option." unless config.has_key?(:database)
|
53
|
+
|
54
|
+
connection_string = "ServerName=#{(config[:server] || config[:database])};DatabaseName=#{config[:database]};UserID=#{config[:username]};Password=#{config[:password]};"
|
55
|
+
connection_string += "CommLinks=#{config[:commlinks]};" unless config[:commlinks].nil?
|
56
|
+
connection_string += "ConnectionName=#{config[:connection_name]};" unless config[:connection_name].nil?
|
57
|
+
connection_string += "CharSet=#{config[:encoding]};" unless config[:encoding].nil?
|
58
|
+
connection_string += "Idle=0" # Prevent the server from disconnecting us if we're idle for >240mins (by default)
|
59
|
+
end
|
60
|
+
|
61
|
+
url = 'jdbc:sqlanywhere:' + connection_string
|
62
|
+
|
63
|
+
if ENV['SQLANY16']
|
64
|
+
$CLASSPATH << 'sajdbc4.jar'
|
65
|
+
$CLASSPATH << Pathname.new(ENV['SQLANY16']).join('java').join('sajdbc4.jar').to_s
|
66
|
+
driver = 'sybase.jdbc4.sqlanywhere.IDriver'
|
67
|
+
elsif ENV['SQLANY12']
|
68
|
+
$CLASSPATH << 'sajdbc4.jar'
|
69
|
+
$CLASSPATH << Pathname.new(ENV['SQLANY12']).join('java').join('sajdbc4.jar').to_s
|
70
|
+
driver = 'sybase.jdbc4.sqlanywhere.IDriver'
|
71
|
+
elsif ENV['SQLANY11']
|
72
|
+
$CLASSPATH << 'sajdbc.jar'
|
73
|
+
$CLASSPATH << Pathname.new(ENV['SQLANY11']).join('java').join('sajdbc.jar').to_s
|
74
|
+
driver = 'sybase.jdbc.sqlanywhere.IDriver'
|
75
|
+
else
|
76
|
+
raise "Cannot find SqlAnywhere installation directory"
|
77
|
+
end
|
78
|
+
|
79
|
+
conn = ActiveRecord::Base.jdbc_connection({adapter: 'jdbc', driver: driver, url: url})
|
80
|
+
|
81
|
+
ConnectionAdapters::SQLAnywhereJdbcIn4systemsAdapter.new( conn, logger, connection_string)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module ConnectionAdapters
|
86
|
+
class JdbcTypeConverter
|
87
|
+
AR_TO_JDBC_TYPES[:text] << lambda {|r| r['type_name'] =~ /^long varchar$/i}
|
88
|
+
end
|
89
|
+
|
90
|
+
class SQLAnywhereException < StandardError
|
91
|
+
attr_reader :errno
|
92
|
+
attr_reader :sql
|
93
|
+
|
94
|
+
def initialize(message, errno, sql)
|
95
|
+
super(message)
|
96
|
+
@errno = errno
|
97
|
+
@sql = sql
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class SQLAnywhereColumn < Column
|
102
|
+
private
|
103
|
+
# Overridden to handle SQL Anywhere integer, varchar, binary, and timestamp types
|
104
|
+
def simplified_type(field_type)
|
105
|
+
return :boolean if field_type =~ /tinyint/i
|
106
|
+
return :boolean if field_type =~ /bit/i
|
107
|
+
return :text if field_type =~ /long varchar/i
|
108
|
+
return :string if field_type =~ /varchar/i
|
109
|
+
return :binary if field_type =~ /long binary/i
|
110
|
+
return :datetime if field_type =~ /timestamp/i
|
111
|
+
return :integer if field_type =~ /smallint|bigint/i
|
112
|
+
return :text if field_type =~ /xml/i
|
113
|
+
return :integer if field_type =~ /uniqueidentifier/i
|
114
|
+
super
|
115
|
+
end
|
116
|
+
|
117
|
+
def extract_limit(sql_type)
|
118
|
+
case sql_type
|
119
|
+
when /^tinyint/i
|
120
|
+
1
|
121
|
+
when /^smallint/i
|
122
|
+
2
|
123
|
+
when /^integer/i
|
124
|
+
4
|
125
|
+
when /^bigint/i
|
126
|
+
8
|
127
|
+
else super
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
# Handles the encoding of a binary object into SQL Anywhere
|
133
|
+
# SQL Anywhere requires that binary values be encoded as \xHH, where HH is a hexadecimal number
|
134
|
+
# This function encodes the binary string in this format
|
135
|
+
def self.string_to_binary(value)
|
136
|
+
"\\x" + value.unpack("H*")[0].scan(/../).join("\\x")
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.binary_to_string(value)
|
140
|
+
value.gsub(/\\x[0-9]{2}/) { |byte| byte[2..3].hex }
|
141
|
+
end
|
142
|
+
|
143
|
+
# Should override the time column values.
|
144
|
+
# Sybase doesn't like the time zones.
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
class SQLAnywhereJdbcIn4systemsAdapter < AbstractAdapter
|
149
|
+
delegate :select, :select_rows, :execute, to: :conn
|
150
|
+
|
151
|
+
attr_reader :conn
|
152
|
+
def initialize( conn, logger, connection_string = "") #:nodoc:
|
153
|
+
super
|
154
|
+
@visitor = Arel::Visitors::SQLAnywhere.new self
|
155
|
+
@conn = conn
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.visitor_for(pool)
|
159
|
+
config = pool.spec.config
|
160
|
+
|
161
|
+
if config.fetch(:prepared_statements) {true}
|
162
|
+
Arel::Visitors::SQLAnywhere.new pool
|
163
|
+
else
|
164
|
+
BindSubstitution.new pool
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def adapter_name #:nodoc:
|
169
|
+
'SQLAnywhere'
|
170
|
+
end
|
171
|
+
|
172
|
+
def supports_migrations? #:nodoc:
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
def requires_reloading?
|
177
|
+
true
|
178
|
+
end
|
179
|
+
|
180
|
+
def active?
|
181
|
+
# The liveness variable is used a low-cost "no-op" to test liveness
|
182
|
+
SA.instance.api.sqlany_execute_immediate(@connection, "SET liveness = 1") == 1
|
183
|
+
rescue
|
184
|
+
false
|
185
|
+
end
|
186
|
+
|
187
|
+
def supports_count_distinct? #:nodoc:
|
188
|
+
true
|
189
|
+
end
|
190
|
+
|
191
|
+
def supports_autoincrement? #:nodoc:
|
192
|
+
true
|
193
|
+
end
|
194
|
+
|
195
|
+
# Maps native ActiveRecord/Ruby types into SQLAnywhere types
|
196
|
+
# TINYINTs are treated as the default boolean value
|
197
|
+
# ActiveRecord allows NULLs in boolean columns, and the SQL Anywhere BIT type does not
|
198
|
+
# As a result, TINYINT must be used. All TINYINT columns will be assumed to be boolean and
|
199
|
+
# should not be used as single-byte integer columns. This restriction is similar to other ActiveRecord database drivers
|
200
|
+
def native_database_types #:nodoc:
|
201
|
+
{
|
202
|
+
:primary_key => 'INTEGER PRIMARY KEY DEFAULT AUTOINCREMENT NOT NULL',
|
203
|
+
:string => { :name => "varchar", :limit => 255 },
|
204
|
+
:text => { :name => "long varchar" },
|
205
|
+
:integer => { :name => "integer", :limit => 4 },
|
206
|
+
:float => { :name => "float" },
|
207
|
+
:decimal => { :name => "decimal" },
|
208
|
+
:datetime => { :name => "datetime" },
|
209
|
+
:timestamp => { :name => "datetime" },
|
210
|
+
:time => { :name => "time" },
|
211
|
+
:date => { :name => "date" },
|
212
|
+
:binary => { :name => "binary" },
|
213
|
+
:boolean => { :name => "tinyint", :limit => 1}
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
# QUOTING ==================================================
|
218
|
+
|
219
|
+
# Applies quotations around column names in generated queries
|
220
|
+
def quote_column_name(name) #:nodoc:
|
221
|
+
%Q("#{name}")
|
222
|
+
end
|
223
|
+
|
224
|
+
def quote_table_name(name)
|
225
|
+
owner, table = name.to_s.split('.', 2)
|
226
|
+
if table == nil
|
227
|
+
table = owner
|
228
|
+
owner = :dba
|
229
|
+
end
|
230
|
+
"#{quote_column_name(owner)}.#{quote_column_name(table)}"
|
231
|
+
end
|
232
|
+
|
233
|
+
def quote_table_alias_name(name)
|
234
|
+
quote_column_name name
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
# Handles special quoting of binary columns. Binary columns will be treated as strings inside of ActiveRecord.
|
239
|
+
# ActiveRecord requires that any strings it inserts into databases must escape the backslash (\).
|
240
|
+
# Since in the binary case, the (\x) is significant to SQL Anywhere, it cannot be escaped.
|
241
|
+
def quote(value, column = nil)
|
242
|
+
case value
|
243
|
+
when String, ActiveSupport::Multibyte::Chars
|
244
|
+
value_S = value.to_s
|
245
|
+
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
246
|
+
"'#{column.class.string_to_binary(value_S)}'"
|
247
|
+
else
|
248
|
+
super(value, column)
|
249
|
+
end
|
250
|
+
else
|
251
|
+
super(value, column)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def quoted_true
|
256
|
+
'1'
|
257
|
+
end
|
258
|
+
|
259
|
+
def quoted_false
|
260
|
+
'0'
|
261
|
+
end
|
262
|
+
|
263
|
+
# SQL Anywhere does not support sizing of integers based on the sytax INTEGER(size). Integer sizes
|
264
|
+
# must be captured when generating the SQL and replaced with the appropriate size.
|
265
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
266
|
+
type = type.to_sym
|
267
|
+
if native = native_database_types[type]
|
268
|
+
if type == :integer
|
269
|
+
case limit
|
270
|
+
when 1
|
271
|
+
column_type_sql = 'tinyint'
|
272
|
+
when 2
|
273
|
+
column_type_sql = 'smallint'
|
274
|
+
when 3..4
|
275
|
+
column_type_sql = 'integer'
|
276
|
+
when 5..8
|
277
|
+
column_type_sql = 'bigint'
|
278
|
+
else
|
279
|
+
column_type_sql = 'integer'
|
280
|
+
end
|
281
|
+
column_type_sql
|
282
|
+
elsif type == :string and !limit.nil?
|
283
|
+
"varchar (#{limit})"
|
284
|
+
elsif type == :boolean
|
285
|
+
column_type_sql = 'tinyint'
|
286
|
+
else
|
287
|
+
super(type, limit, precision, scale)
|
288
|
+
end
|
289
|
+
else
|
290
|
+
super(type, limit, precision, scale)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def viewed_tables(name = nil)
|
295
|
+
list_of_tables(['view'], name)
|
296
|
+
end
|
297
|
+
|
298
|
+
def base_tables(name = nil)
|
299
|
+
list_of_tables(['base'], name)
|
300
|
+
end
|
301
|
+
|
302
|
+
# Do not return SYS-owned or DBO-owned tables or RS_systabgroup-owned
|
303
|
+
def tables(name = nil) #:nodoc:
|
304
|
+
list_of_tables(['base', 'view'])
|
305
|
+
end
|
306
|
+
|
307
|
+
def columns(table_name, name = nil) #:nodoc:
|
308
|
+
table_structure(table_name).map do |field|
|
309
|
+
default = field['default']
|
310
|
+
if default == nil # Nil is the usual case
|
311
|
+
elsif default.starts_with?("'") # If string, remove first and last quotes and the last \n character
|
312
|
+
default = default[1..-2]
|
313
|
+
elsif default =~ /^-?\d+(\.\d+)?$/ # If a number string, leave as it is
|
314
|
+
else # Otherwise, it's probably something (LAST USER, CURRENT TIMESTAMP, etc) that wouldn't work in Rails, so return nil
|
315
|
+
default = nil
|
316
|
+
end
|
317
|
+
SQLAnywhereColumn.new(field['name'], default, field['domain'], (field['nulls'] == 1))
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def indexes(table_name, name = nil) #:nodoc:
|
322
|
+
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"
|
323
|
+
select(sql, name).map do |row|
|
324
|
+
index = IndexDefinition.new(table_name, row['index_name'])
|
325
|
+
index.unique = row['unique'] == 1
|
326
|
+
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']}'"
|
327
|
+
index.columns = select(sql).map { |col| col['column_name'] }
|
328
|
+
index
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def primary_key(table_name) #:nodoc:
|
333
|
+
sql = "SELECT cname from SYS.SYSCOLUMNS where tname = '#{table_name}' and in_primary_key = 'Y'"
|
334
|
+
rs = exec_query(sql)
|
335
|
+
if !rs.nil? and !rs.first.nil?
|
336
|
+
rs.first['cname']
|
337
|
+
else
|
338
|
+
nil
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def remove_index(table_name, options={}) #:nodoc:
|
343
|
+
execute "DROP INDEX #{quote_table_name(table_name)}.#{quote_column_name(index_name(table_name, options))}"
|
344
|
+
end
|
345
|
+
|
346
|
+
def rename_table(name, new_name)
|
347
|
+
execute "ALTER TABLE #{quote_table_name(name)} RENAME #{quote_table_name(new_name)}"
|
348
|
+
end
|
349
|
+
|
350
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
351
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
|
352
|
+
end
|
353
|
+
|
354
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
355
|
+
unless null || default.nil?
|
356
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
357
|
+
end
|
358
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? '' : 'NOT'} NULL")
|
359
|
+
end
|
360
|
+
|
361
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
362
|
+
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])}"
|
363
|
+
add_column_options!(add_column_sql, options)
|
364
|
+
add_column_sql << ' NULL' if options[:null]
|
365
|
+
execute(add_column_sql)
|
366
|
+
end
|
367
|
+
|
368
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
369
|
+
if column_name.downcase == new_column_name.downcase
|
370
|
+
whine = "if_the_only_change_is_case_sqlanywhere_doesnt_rename_the_column"
|
371
|
+
rename_column table_name, column_name, "#{new_column_name}#{whine}"
|
372
|
+
rename_column table_name, "#{new_column_name}#{whine}", new_column_name
|
373
|
+
else
|
374
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def remove_column(table_name, *column_names)
|
379
|
+
column_names = column_names.flatten
|
380
|
+
column_names.zip(columns_for_remove(table_name, *column_names)).each do |unquoted_column_name, column_name|
|
381
|
+
sql = <<-SQL
|
382
|
+
SELECT "index_name" FROM SYS.SYSTAB join SYS.SYSTABCOL join SYS.SYSIDXCOL join SYS.SYSIDX
|
383
|
+
WHERE "column_name" = '#{unquoted_column_name}' AND "table_name" = '#{table_name}'
|
384
|
+
SQL
|
385
|
+
select(sql, nil).each do |row|
|
386
|
+
execute "DROP INDEX \"#{table_name}\".\"#{row['index_name']}\""
|
387
|
+
end
|
388
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}"
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def exec_query(sql, name = 'SQL', binds = [])
|
393
|
+
binds.map! do |column, value|
|
394
|
+
type = column.type
|
395
|
+
if value != nil
|
396
|
+
case type
|
397
|
+
when :boolean
|
398
|
+
[column, value ? 1 : 0]
|
399
|
+
else
|
400
|
+
[column, value]
|
401
|
+
end
|
402
|
+
else
|
403
|
+
[column, value]
|
404
|
+
end
|
405
|
+
end
|
406
|
+
conn.exec_query(sql, name, binds)
|
407
|
+
end
|
408
|
+
|
409
|
+
def last_inserted_id(result)
|
410
|
+
select_value('SELECT @@identity')
|
411
|
+
end
|
412
|
+
|
413
|
+
def select_user(cache=true)
|
414
|
+
@user_id = (cache && @user_id) || select_value('SELECT USER')
|
415
|
+
end
|
416
|
+
|
417
|
+
# Set the database user to be user_id.
|
418
|
+
# If a block is given, then after running the block, the previous user_id is restored.
|
419
|
+
def set_user(user_id)
|
420
|
+
previous_user_id = select_user(true)
|
421
|
+
if previous_user_id != user_id
|
422
|
+
execute("SETUSER #{quote_column_name(user_id)}")
|
423
|
+
@user_id = user_id
|
424
|
+
end
|
425
|
+
if block_given?
|
426
|
+
begin
|
427
|
+
yield
|
428
|
+
ensure
|
429
|
+
set_user(previous_user_id)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
protected
|
435
|
+
|
436
|
+
def list_of_tables(types, name = nil)
|
437
|
+
sql = "SELECT table_name FROM SYS.SYSTABLE WHERE table_type in (#{types.map{|t| quote(t)}.join(', ')}) and creator NOT IN (0,3,5)"
|
438
|
+
select(sql, name).map { |row| row["table_name"] }
|
439
|
+
end
|
440
|
+
|
441
|
+
# Queries the structure of a table including the columns names, defaults, type, and nullability
|
442
|
+
# ActiveRecord uses the type to parse scale and precision information out of the types. As a result,
|
443
|
+
# chars, varchars, binary, nchars, nvarchars must all be returned in the form <i>type</i>(<i>width</i>)
|
444
|
+
# numeric and decimal must be returned in the form <i>type</i>(<i>width</i>, <i>scale</i>)
|
445
|
+
# Nullability is returned as 0 (no nulls allowed) or 1 (nulls allowed)
|
446
|
+
# Alos, ActiveRecord expects an autoincrement column to have default value of NULL
|
447
|
+
|
448
|
+
def table_structure(table_name)
|
449
|
+
sql = <<-SQL
|
450
|
+
SELECT SYS.SYSCOLUMN.column_name AS name,
|
451
|
+
"default" AS "default",
|
452
|
+
IF SYS.SYSCOLUMN.domain_id IN (7,8,9,11,33,34,35,3,27) THEN
|
453
|
+
IF SYS.SYSCOLUMN.domain_id IN (3,27) THEN
|
454
|
+
SYS.SYSDOMAIN.domain_name || '(' || SYS.SYSCOLUMN.width || ',' || SYS.SYSCOLUMN.scale || ')'
|
455
|
+
ELSE
|
456
|
+
SYS.SYSDOMAIN.domain_name || '(' || SYS.SYSCOLUMN.width || ')'
|
457
|
+
ENDIF
|
458
|
+
ELSE
|
459
|
+
SYS.SYSDOMAIN.domain_name
|
460
|
+
ENDIF AS domain,
|
461
|
+
IF SYS.SYSCOLUMN.nulls = 'Y' THEN 1 ELSE 0 ENDIF AS nulls
|
462
|
+
FROM
|
463
|
+
SYS.SYSCOLUMN
|
464
|
+
INNER JOIN SYS.SYSTABLE ON SYS.SYSCOLUMN.table_id = SYS.SYSTABLE.table_id
|
465
|
+
INNER JOIN SYS.SYSDOMAIN ON SYS.SYSCOLUMN.domain_id = SYS.SYSDOMAIN.domain_id
|
466
|
+
INNER JOIN sys.sysUser ON sys.systable.creator = sys.sysuser.user_id AND sys.sysuser.user_name = 'dba'
|
467
|
+
WHERE
|
468
|
+
table_name = '#{table_name}'
|
469
|
+
SQL
|
470
|
+
structure = exec_query(sql, :skip_logging)
|
471
|
+
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure == false
|
472
|
+
structure
|
473
|
+
end
|
474
|
+
|
475
|
+
# Required to prevent DEFAULT NULL being added to primary keys
|
476
|
+
def options_include_default?(options)
|
477
|
+
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
|
478
|
+
end
|
479
|
+
|
480
|
+
private
|
481
|
+
|
482
|
+
def set_connection_options
|
483
|
+
SA.instance.api.sqlany_execute_immediate(@connection, "SET TEMPORARY OPTION non_keywords = 'LOGIN'") rescue nil
|
484
|
+
SA.instance.api.sqlany_execute_immediate(@connection, "SET TEMPORARY OPTION timestamp_format = 'YYYY-MM-DD HH:NN:SS'") rescue nil
|
485
|
+
#SA.instance.api.sqlany_execute_immediate(@connection, "SET OPTION reserved_keywords = 'LIMIT'") rescue nil
|
486
|
+
# The liveness variable is used a low-cost "no-op" to test liveness
|
487
|
+
SA.instance.api.sqlany_execute_immediate(@connection, "CREATE VARIABLE liveness INT") rescue nil
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Arel
|
2
|
+
module Visitors
|
3
|
+
class SQLAnywhere < Arel::Visitors::ToSql
|
4
|
+
def initialize connection
|
5
|
+
super
|
6
|
+
@quoted_table_aliases = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def visit_Arel_Nodes_SelectStatement o
|
11
|
+
o = order_hacks(o)
|
12
|
+
|
13
|
+
is_distinct = using_distinct?(o)
|
14
|
+
|
15
|
+
o.limit = 1000000 if (o.offset && !o.limit)
|
16
|
+
o.limit = o.limit.expr if(o.limit.is_a?(Arel::Nodes::Limit))
|
17
|
+
o.limit = o.limit if(o.limit.is_a?(Fixnum))
|
18
|
+
|
19
|
+
[
|
20
|
+
"SELECT",
|
21
|
+
("DISTINCT" if is_distinct),
|
22
|
+
("TOP #{o.limit}" if o.limit),
|
23
|
+
(visit_Arel_Nodes_Offset(o.offset) if o.offset),
|
24
|
+
o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
|
25
|
+
("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
|
26
|
+
#("LIMIT #{o.limit}" if o.limit),
|
27
|
+
#(visit(o.offset) if o.offset),
|
28
|
+
(visit(o.lock) if o.lock),
|
29
|
+
].compact.join ' '
|
30
|
+
end
|
31
|
+
|
32
|
+
def visit_Arel_Nodes_SelectCore o
|
33
|
+
[
|
34
|
+
"#{o.projections.map { |x| visit x }.join ', '}",
|
35
|
+
("FROM #{visit o.source}" if o.source), # Joins
|
36
|
+
("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
|
37
|
+
("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
|
38
|
+
(visit(o.having) if o.having),
|
39
|
+
].compact.join ' '
|
40
|
+
end
|
41
|
+
|
42
|
+
def visit_Arel_Nodes_Group o
|
43
|
+
expr = o.expr.clone
|
44
|
+
if expr.class == Arel::Nodes::NamedFunction
|
45
|
+
expr.alias = nil
|
46
|
+
end
|
47
|
+
visit expr
|
48
|
+
end
|
49
|
+
|
50
|
+
def visit_Arel_Nodes_Offset o
|
51
|
+
"START AT #{visit(o.expr) + 1}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def visit_Arel_Nodes_True o
|
55
|
+
"1=1"
|
56
|
+
end
|
57
|
+
|
58
|
+
def visit_Arel_Nodes_False o
|
59
|
+
"1=0"
|
60
|
+
end
|
61
|
+
|
62
|
+
def visit_Arel_Nodes_Matches o
|
63
|
+
# The version in arel cannot like integer columns
|
64
|
+
left = visit o.left # This method sets last column
|
65
|
+
# If last column was left, visit o.right would return 0
|
66
|
+
self.last_column = nil
|
67
|
+
"#{left} LIKE #{visit o.right}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def visit_Arel_Nodes_TableAlias o
|
71
|
+
"#{visit o.relation} #{quote_table_alias_name o.name}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def visit_Arel_Table o, a=nil
|
75
|
+
if o.table_alias
|
76
|
+
"#{quote_table_name o.name} #{quote_table_alias_name o.table_alias}"
|
77
|
+
else
|
78
|
+
quote_table_name o.name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def visit_Arel_Attributes_Attribute o, a=nil
|
83
|
+
if o.relation.table_alias
|
84
|
+
join_name = o.relation.table_alias
|
85
|
+
"#{quote_table_alias_name join_name}.#{quote_column_name o.name}"
|
86
|
+
else
|
87
|
+
join_name = o.relation.name
|
88
|
+
"#{quote_table_name join_name}.#{quote_column_name o.name}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def quote_table_alias_name name
|
93
|
+
return name if Arel::Nodes::SqlLiteral === name
|
94
|
+
@quoted_table_aliases[name] ||= @connection.quote_table_alias_name(name)
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
def using_distinct?(o)
|
102
|
+
o.cores.any? do |core|
|
103
|
+
core.set_quantifier.class == Arel::Nodes::Distinct
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# The functions (order_hacks, split_order_string) are based on the Oracle Enhacned ActiveRecord driver maintained by Raimonds Simanovskis (2010)
|
108
|
+
# (https://github.com/rsim/oracle-enhanced)
|
109
|
+
|
110
|
+
###
|
111
|
+
# Hacks for the order clauses
|
112
|
+
def order_hacks o
|
113
|
+
return o if o.orders.empty?
|
114
|
+
return o unless o.cores.any? do |core|
|
115
|
+
core.projections.any? do |projection|
|
116
|
+
/DISTINCT.*FIRST_VALUE/ === projection
|
117
|
+
end
|
118
|
+
end
|
119
|
+
# Previous version with join and split broke ORDER BY clause
|
120
|
+
# if it contained functions with several arguments (separated by ',').
|
121
|
+
#
|
122
|
+
# orders = o.orders.map { |x| visit x }.join(', ').split(',')
|
123
|
+
orders = o.orders.map do |x|
|
124
|
+
string = visit x
|
125
|
+
if string.include?(',')
|
126
|
+
split_order_string(string)
|
127
|
+
else
|
128
|
+
string
|
129
|
+
end
|
130
|
+
end.flatten
|
131
|
+
o.orders = []
|
132
|
+
orders.each_with_index do |order, i|
|
133
|
+
o.orders <<
|
134
|
+
Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i === order}")
|
135
|
+
end
|
136
|
+
o
|
137
|
+
end
|
138
|
+
|
139
|
+
# Split string by commas but count opening and closing brackets
|
140
|
+
# and ignore commas inside brackets.
|
141
|
+
def split_order_string(string)
|
142
|
+
array = []
|
143
|
+
i = 0
|
144
|
+
string.split(',').each do |part|
|
145
|
+
if array[i]
|
146
|
+
array[i] << ',' << part
|
147
|
+
else
|
148
|
+
# to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
|
149
|
+
array[i] = '' << part
|
150
|
+
end
|
151
|
+
i += 1 if array[i].count('(') == array[i].count(')')
|
152
|
+
end
|
153
|
+
array
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
Arel::Visitors::VISITORS['sqlanywhere'] = Arel::Visitors::SQLAnywhere
|
data/test/connection.rb
ADDED
@@ -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,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord-sqlanywhere-jdbc-in4systems-adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.12
|
5
|
+
platform: java
|
6
|
+
authors:
|
7
|
+
- Eric Farar
|
8
|
+
- Sri Kalai
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-02-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord-jdbc-adapter
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
requirement: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - '>='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
prerelease: false
|
27
|
+
type: :runtime
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: activerecord
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 3.0.3
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 3.0.3
|
40
|
+
prerelease: false
|
41
|
+
type: :runtime
|
42
|
+
description: ActiveRecord JDBC driver for SQL Anywhere customized for in4systems
|
43
|
+
email: chris.couzens@in4systems.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- CHANGELOG
|
49
|
+
- LICENSE
|
50
|
+
- README
|
51
|
+
- lib/active_record/connection_adapters/sqlanywhere_jdbc_in4systems_adapter.rb
|
52
|
+
- lib/arel/visitors/sqlanywhere.rb
|
53
|
+
- test/connection.rb
|
54
|
+
homepage: https://github.com/in4systems/activerecord-sqlanywhere-adapter
|
55
|
+
licenses:
|
56
|
+
- Apache License Version 2.0
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 2.2.2
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: ActiveRecord driver for SQL Anywhere
|
78
|
+
test_files: []
|