activerecord-sqlanywhere-jdbc-in4systems-adapter 1.0.12-java
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.
- 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: []
|