activerecord-jdbc-alt-adapter 70.1.0-java → 71.0.0.alpha1-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 +4 -4
- data/.github/workflows/main.yml +135 -21
- data/.github/workflows/ruby.yml +10 -10
- data/.gitignore +1 -0
- data/.solargraph.yml +15 -0
- data/Gemfile +17 -4
- data/README.md +7 -3
- data/RUNNING_TESTS.md +36 -0
- data/activerecord-jdbc-adapter.gemspec +2 -2
- data/activerecord-jdbc-alt-adapter.gemspec +1 -1
- data/lib/arel/visitors/sqlserver.rb +10 -0
- data/lib/arjdbc/abstract/connection_management.rb +23 -10
- data/lib/arjdbc/abstract/core.rb +5 -6
- data/lib/arjdbc/abstract/database_statements.rb +35 -25
- data/lib/arjdbc/abstract/statement_cache.rb +1 -6
- data/lib/arjdbc/abstract/transaction_support.rb +37 -9
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/column.rb +0 -34
- data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
- data/lib/arjdbc/mssql/adapter.rb +93 -80
- data/lib/arjdbc/mssql/column.rb +1 -0
- data/lib/arjdbc/mssql/connection_methods.rb +7 -55
- data/lib/arjdbc/mssql/database_statements.rb +182 -71
- data/lib/arjdbc/mssql/explain_support.rb +8 -5
- data/lib/arjdbc/mssql/schema_creation.rb +1 -1
- data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
- data/lib/arjdbc/mssql/schema_statements.rb +19 -11
- data/lib/arjdbc/mssql/server_version.rb +56 -0
- data/lib/arjdbc/mssql/utils.rb +23 -9
- data/lib/arjdbc/mysql/adapter.rb +64 -22
- data/lib/arjdbc/mysql/connection_methods.rb +43 -42
- data/lib/arjdbc/sqlite3/adapter.rb +218 -135
- data/lib/arjdbc/sqlite3/column.rb +103 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
- data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/02-test.rake +1 -1
- data/rakelib/rails.rake +2 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +4 -2
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
- metadata +11 -14
- data/lib/arel/visitors/sql_server/ng42.rb +0 -294
- data/lib/arel/visitors/sql_server.rb +0 -124
- data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
- data/lib/arjdbc/mssql/lock_methods.rb +0 -77
- data/lib/arjdbc/mssql/old_adapter.rb +0 -804
- data/lib/arjdbc/mssql/old_column.rb +0 -200
@@ -1,804 +0,0 @@
|
|
1
|
-
# NOTE: file contains code adapted from **sqlserver** adapter, license follows
|
2
|
-
=begin
|
3
|
-
Copyright (c) 2008-2015
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
-
=end
|
24
|
-
|
25
|
-
ArJdbc.load_java_part :MSSQL
|
26
|
-
|
27
|
-
require 'strscan'
|
28
|
-
|
29
|
-
module ArJdbc
|
30
|
-
module MSSQL
|
31
|
-
|
32
|
-
require 'arjdbc/mssql/utils'
|
33
|
-
require 'arjdbc/mssql/limit_helpers'
|
34
|
-
require 'arjdbc/mssql/lock_methods'
|
35
|
-
require 'arjdbc/mssql/column'
|
36
|
-
require 'arjdbc/mssql/explain_support'
|
37
|
-
require 'arjdbc/mssql/types' if AR42
|
38
|
-
require 'arel/visitors/sql_server'
|
39
|
-
|
40
|
-
include LimitHelpers
|
41
|
-
include Utils
|
42
|
-
include ExplainSupport
|
43
|
-
|
44
|
-
# @private
|
45
|
-
def self.extended(adapter)
|
46
|
-
initialize!
|
47
|
-
|
48
|
-
version = adapter.config[:sqlserver_version] ||= adapter.sqlserver_version
|
49
|
-
adapter.send(:setup_limit_offset!, version)
|
50
|
-
end
|
51
|
-
|
52
|
-
# @private
|
53
|
-
@@_initialized = nil
|
54
|
-
|
55
|
-
# @private
|
56
|
-
def self.initialize!
|
57
|
-
return if @@_initialized; @@_initialized = true
|
58
|
-
|
59
|
-
require 'arjdbc/util/serialized_attributes'
|
60
|
-
Util::SerializedAttributes.setup /image/i, 'after_save_with_mssql_lob'
|
61
|
-
end
|
62
|
-
|
63
|
-
# @private
|
64
|
-
@@update_lob_values = true
|
65
|
-
|
66
|
-
# Updating records with LOB values (binary/text columns) in a separate
|
67
|
-
# statement can be disabled using :
|
68
|
-
#
|
69
|
-
# ArJdbc::MSSQL.update_lob_values = false
|
70
|
-
#
|
71
|
-
# @note This only applies when prepared statements are not used.
|
72
|
-
def self.update_lob_values?; @@update_lob_values; end
|
73
|
-
# @see #update_lob_values?
|
74
|
-
def self.update_lob_values=(update); @@update_lob_values = update; end
|
75
|
-
|
76
|
-
# @private
|
77
|
-
@@cs_equality_operator = 'COLLATE Latin1_General_CS_AS_WS'
|
78
|
-
|
79
|
-
# Operator for sorting strings in SQLServer, setup as :
|
80
|
-
#
|
81
|
-
# ArJdbc::MSSQL.cs_equality_operator = 'COLLATE Latin1_General_CS_AS_WS'
|
82
|
-
#
|
83
|
-
def self.cs_equality_operator; @@cs_equality_operator; end
|
84
|
-
# @see #cs_equality_operator
|
85
|
-
def self.cs_equality_operator=(operator); @@cs_equality_operator = operator; end
|
86
|
-
|
87
|
-
# @see #quote
|
88
|
-
# @private
|
89
|
-
BLOB_VALUE_MARKER = "''"
|
90
|
-
|
91
|
-
# @see #update_lob_values?
|
92
|
-
# @see ArJdbc::Util::SerializedAttributes#update_lob_columns
|
93
|
-
def update_lob_value?(value, column = nil)
|
94
|
-
MSSQL.update_lob_values? && ! prepared_statements? # && value
|
95
|
-
end
|
96
|
-
|
97
|
-
# @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
|
98
|
-
def self.jdbc_connection_class
|
99
|
-
::ActiveRecord::ConnectionAdapters::MSSQLJdbcConnection
|
100
|
-
end
|
101
|
-
|
102
|
-
# @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_column_class
|
103
|
-
def jdbc_column_class; ::ActiveRecord::ConnectionAdapters::MSSQLColumn end
|
104
|
-
|
105
|
-
def configure_connection
|
106
|
-
use_database # config[:database]
|
107
|
-
end
|
108
|
-
|
109
|
-
def sqlserver_version
|
110
|
-
@sqlserver_version ||= begin
|
111
|
-
config_version = config[:sqlserver_version]
|
112
|
-
config_version ? config_version.to_s :
|
113
|
-
select_value("SELECT @@version")[/(Microsoft SQL Server\s+|Microsoft SQL Azure.+\n.+)(\d{4})/, 2]
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
NATIVE_DATABASE_TYPES = {
|
118
|
-
:primary_key => 'int NOT NULL IDENTITY(1,1) PRIMARY KEY',
|
119
|
-
:integer => { :name => 'int', }, # :limit => 4
|
120
|
-
:boolean => { :name => 'bit' },
|
121
|
-
:decimal => { :name => 'decimal' },
|
122
|
-
:float => { :name => 'float' },
|
123
|
-
:bigint => { :name => 'bigint' },
|
124
|
-
:real => { :name => 'real' },
|
125
|
-
:date => { :name => 'date' },
|
126
|
-
:time => { :name => 'time' },
|
127
|
-
:datetime => { :name => 'datetime' },
|
128
|
-
:timestamp => { :name => 'datetime' },
|
129
|
-
|
130
|
-
:string => { :name => 'nvarchar', :limit => 4000 },
|
131
|
-
#:varchar => { :name => 'varchar' }, # limit: 8000
|
132
|
-
:text => { :name => 'nvarchar(max)' },
|
133
|
-
:text_basic => { :name => 'text' },
|
134
|
-
#:ntext => { :name => 'ntext' },
|
135
|
-
:char => { :name => 'char' },
|
136
|
-
#:nchar => { :name => 'nchar' },
|
137
|
-
:binary => { :name => 'image' }, # NOTE: :name => 'varbinary(max)'
|
138
|
-
:binary_basic => { :name => 'binary' },
|
139
|
-
:uuid => { :name => 'uniqueidentifier' },
|
140
|
-
:money => { :name => 'money' },
|
141
|
-
#:smallmoney => { :name => 'smallmoney' },
|
142
|
-
}
|
143
|
-
|
144
|
-
def native_database_types
|
145
|
-
# NOTE: due compatibility we're using the generic type resolution
|
146
|
-
# ... NATIVE_DATABASE_TYPES won't be used at all on SQLServer 2K
|
147
|
-
sqlserver_2000? ? super : super.merge(NATIVE_DATABASE_TYPES)
|
148
|
-
end
|
149
|
-
|
150
|
-
def modify_types(types)
|
151
|
-
if sqlserver_2000?
|
152
|
-
types[:primary_key] = NATIVE_DATABASE_TYPES[:primary_key]
|
153
|
-
types[:string] = NATIVE_DATABASE_TYPES[:string]
|
154
|
-
types[:boolean] = NATIVE_DATABASE_TYPES[:boolean]
|
155
|
-
types[:text] = { :name => "ntext" }
|
156
|
-
types[:integer][:limit] = nil
|
157
|
-
types[:binary] = { :name => "image" }
|
158
|
-
else
|
159
|
-
# ~ private types for better "native" adapter compatibility
|
160
|
-
types[:varchar_max] = { :name => 'varchar(max)' }
|
161
|
-
types[:nvarchar_max] = { :name => 'nvarchar(max)' }
|
162
|
-
types[:varbinary_max] = { :name => 'varbinary(max)' }
|
163
|
-
end
|
164
|
-
types[:string][:limit] = 255 unless AR40 # backwards compatibility
|
165
|
-
types
|
166
|
-
end
|
167
|
-
|
168
|
-
# @private these cannot specify a limit
|
169
|
-
NO_LIMIT_TYPES = %w( text binary boolean date datetime )
|
170
|
-
|
171
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
172
|
-
type_s = type.to_s
|
173
|
-
# MSSQL's NVARCHAR(n | max) column supports either a number between 1 and
|
174
|
-
# 4000, or the word "MAX", which corresponds to 2**30-1 UCS-2 characters.
|
175
|
-
#
|
176
|
-
# It does not accept NVARCHAR(1073741823) here, so we have to change it
|
177
|
-
# to NVARCHAR(MAX), even though they are logically equivalent.
|
178
|
-
#
|
179
|
-
# MSSQL Server 2000 is skipped here because I don't know how it will behave.
|
180
|
-
#
|
181
|
-
# See: http://msdn.microsoft.com/en-us/library/ms186939.aspx
|
182
|
-
if type_s == 'string' && limit == 1073741823 && ! sqlserver_2000?
|
183
|
-
'NVARCHAR(MAX)'
|
184
|
-
elsif NO_LIMIT_TYPES.include?(type_s)
|
185
|
-
super(type)
|
186
|
-
elsif type_s == 'integer' || type_s == 'int'
|
187
|
-
if limit.nil? || limit == 4
|
188
|
-
'int'
|
189
|
-
elsif limit == 2
|
190
|
-
'smallint'
|
191
|
-
elsif limit == 1
|
192
|
-
'tinyint'
|
193
|
-
else
|
194
|
-
'bigint'
|
195
|
-
end
|
196
|
-
elsif type_s == 'uniqueidentifier'
|
197
|
-
type_s
|
198
|
-
else
|
199
|
-
super
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
# @override
|
204
|
-
def quote(value, column = nil)
|
205
|
-
return value.quoted_id if value.respond_to?(:quoted_id)
|
206
|
-
return value if sql_literal?(value)
|
207
|
-
|
208
|
-
case value
|
209
|
-
# SQL Server 2000 doesn't let you insert an integer into a NVARCHAR
|
210
|
-
when String, ActiveSupport::Multibyte::Chars, Integer
|
211
|
-
value = value.to_s
|
212
|
-
column_type = column && column.type
|
213
|
-
if column_type == :binary
|
214
|
-
if update_lob_value?(value, column)
|
215
|
-
BLOB_VALUE_MARKER
|
216
|
-
else
|
217
|
-
"'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
|
218
|
-
end
|
219
|
-
elsif column_type == :integer
|
220
|
-
value.to_i.to_s
|
221
|
-
elsif column_type == :float
|
222
|
-
value.to_f.to_s
|
223
|
-
elsif ! column.respond_to?(:is_utf8?) || column.is_utf8?
|
224
|
-
"N'#{quote_string(value)}'" # ' (for ruby-mode)
|
225
|
-
else
|
226
|
-
super
|
227
|
-
end
|
228
|
-
when Date, Time
|
229
|
-
if column && column.type == :time
|
230
|
-
"'#{quoted_time(value)}'"
|
231
|
-
else
|
232
|
-
"'#{quoted_date(value)}'"
|
233
|
-
end
|
234
|
-
when TrueClass then '1'
|
235
|
-
when FalseClass then '0'
|
236
|
-
else super
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
# @override
|
241
|
-
def quoted_date(value)
|
242
|
-
if value.respond_to?(:usec)
|
243
|
-
"#{super}.#{sprintf("%03d", value.usec / 1000)}"
|
244
|
-
else
|
245
|
-
super
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
# @private
|
250
|
-
def quoted_time(value)
|
251
|
-
if value.acts_like?(:time)
|
252
|
-
tz_value = get_time(value)
|
253
|
-
usec = value.respond_to?(:usec) ? ( value.usec / 1000 ) : 0
|
254
|
-
sprintf("%02d:%02d:%02d.%03d", tz_value.hour, tz_value.min, tz_value.sec, usec)
|
255
|
-
else
|
256
|
-
quoted_date(value)
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
# @deprecated no longer used
|
261
|
-
# @private
|
262
|
-
def quoted_datetime(value)
|
263
|
-
quoted_date(value)
|
264
|
-
end
|
265
|
-
|
266
|
-
# @deprecated no longer used
|
267
|
-
# @private
|
268
|
-
def quoted_full_iso8601(value)
|
269
|
-
if value.acts_like?(:time)
|
270
|
-
value.is_a?(Date) ?
|
271
|
-
get_time(value).to_time.xmlschema.to(18) :
|
272
|
-
get_time(value).iso8601(7).to(22)
|
273
|
-
else
|
274
|
-
quoted_date(value)
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
def quote_table_name(name)
|
279
|
-
quote_column_name(name)
|
280
|
-
end
|
281
|
-
|
282
|
-
def quote_column_name(name)
|
283
|
-
name = name.to_s.split('.')
|
284
|
-
name.map! { |n| quote_name_part(n) } # "[#{name}]"
|
285
|
-
name.join('.')
|
286
|
-
end
|
287
|
-
|
288
|
-
def quote_database_name(name)
|
289
|
-
quote_name_part(name.to_s)
|
290
|
-
end
|
291
|
-
|
292
|
-
# Does not quote function default values for UUID columns
|
293
|
-
def quote_default_value(value, column)
|
294
|
-
if column.type == :uuid && value =~ /\(\)/
|
295
|
-
value
|
296
|
-
else
|
297
|
-
quote(value)
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
ADAPTER_NAME = 'MSSQL'.freeze
|
302
|
-
|
303
|
-
def adapter_name
|
304
|
-
ADAPTER_NAME
|
305
|
-
end
|
306
|
-
|
307
|
-
def change_order_direction(order)
|
308
|
-
asc, desc = /\bASC\b/i, /\bDESC\b/i
|
309
|
-
order.split(",").collect do |fragment|
|
310
|
-
case fragment
|
311
|
-
when desc then fragment.gsub(desc, "ASC")
|
312
|
-
when asc then fragment.gsub(asc, "DESC")
|
313
|
-
else "#{fragment.split(',').join(' DESC,')} DESC"
|
314
|
-
end
|
315
|
-
end.join(",")
|
316
|
-
end
|
317
|
-
|
318
|
-
# @override
|
319
|
-
def supports_ddl_transactions?; true end
|
320
|
-
|
321
|
-
# @override
|
322
|
-
def supports_views?; true end
|
323
|
-
|
324
|
-
def tables(schema = current_schema)
|
325
|
-
@connection.tables(nil, schema)
|
326
|
-
end
|
327
|
-
|
328
|
-
# NOTE: Dynamic Name Resolution - SQL Server 2000 vs. 2005
|
329
|
-
#
|
330
|
-
# A query such as "select * from table1" in SQL Server 2000 goes through
|
331
|
-
# a set of steps to resolve and validate the object references before
|
332
|
-
# execution.
|
333
|
-
# The search first looks at the identity of the connection executing
|
334
|
-
# the query.
|
335
|
-
#
|
336
|
-
# However SQL Server 2005 provides a mechanism to allow finer control over
|
337
|
-
# name resolution to the administrators. By manipulating the value of the
|
338
|
-
# default_schema_name columns in the sys.database_principals.
|
339
|
-
#
|
340
|
-
# http://blogs.msdn.com/b/mssqlisv/archive/2007/03/23/upgrading-to-sql-server-2005-and-default-schema-setting.aspx
|
341
|
-
|
342
|
-
# Returns the default schema (to be used for table resolution) used for the {#current_user}.
|
343
|
-
def default_schema
|
344
|
-
return current_user if sqlserver_2000?
|
345
|
-
@default_schema ||=
|
346
|
-
@connection.execute_query_raw(
|
347
|
-
"SELECT default_schema_name FROM sys.database_principals WHERE name = CURRENT_USER"
|
348
|
-
).first['default_schema_name']
|
349
|
-
end
|
350
|
-
alias_method :current_schema, :default_schema
|
351
|
-
|
352
|
-
# Allows for changing of the default schema (to be used during unqualified
|
353
|
-
# table name resolution).
|
354
|
-
# @note This is not supported on SQL Server 2000 !
|
355
|
-
def default_schema=(default_schema) # :nodoc:
|
356
|
-
raise "changing DEFAULT_SCHEMA only supported on SQLServer 2005+" if sqlserver_2000?
|
357
|
-
execute("ALTER #{current_user} WITH DEFAULT_SCHEMA=#{default_schema}")
|
358
|
-
@default_schema = nil if defined?(@default_schema)
|
359
|
-
end
|
360
|
-
alias_method :current_schema=, :default_schema=
|
361
|
-
|
362
|
-
# `SELECT CURRENT_USER`
|
363
|
-
def current_user
|
364
|
-
@current_user ||= @connection.execute_query_raw("SELECT CURRENT_USER").first['']
|
365
|
-
end
|
366
|
-
|
367
|
-
def charset
|
368
|
-
select_value "SELECT SERVERPROPERTY('SqlCharSetName')"
|
369
|
-
end
|
370
|
-
|
371
|
-
def collation
|
372
|
-
select_value "SELECT SERVERPROPERTY('Collation')"
|
373
|
-
end
|
374
|
-
|
375
|
-
def current_database
|
376
|
-
select_value 'SELECT DB_NAME()'
|
377
|
-
end
|
378
|
-
|
379
|
-
def use_database(database = nil)
|
380
|
-
database ||= config[:database]
|
381
|
-
execute "USE #{quote_database_name(database)}" unless database.blank?
|
382
|
-
end
|
383
|
-
|
384
|
-
# @private
|
385
|
-
def recreate_database(name, options = {})
|
386
|
-
drop_database(name)
|
387
|
-
create_database(name, options)
|
388
|
-
end
|
389
|
-
|
390
|
-
# @private
|
391
|
-
def recreate_database!(database = nil)
|
392
|
-
current_db = current_database
|
393
|
-
database ||= current_db
|
394
|
-
use_database('master') if this_db = ( database.to_s == current_db )
|
395
|
-
drop_database(database)
|
396
|
-
create_database(database)
|
397
|
-
ensure
|
398
|
-
use_database(current_db) if this_db
|
399
|
-
end
|
400
|
-
|
401
|
-
def drop_database(name)
|
402
|
-
current_db = current_database
|
403
|
-
use_database('master') if current_db.to_s == name
|
404
|
-
execute "DROP DATABASE #{quote_database_name(name)}"
|
405
|
-
end
|
406
|
-
|
407
|
-
def create_database(name, options = {})
|
408
|
-
execute "CREATE DATABASE #{quote_database_name(name)}"
|
409
|
-
end
|
410
|
-
|
411
|
-
def database_exists?(name)
|
412
|
-
select_value "SELECT name FROM sys.databases WHERE name = '#{name}'"
|
413
|
-
end
|
414
|
-
|
415
|
-
# @override
|
416
|
-
def rename_table(table_name, new_table_name)
|
417
|
-
clear_cached_table(table_name)
|
418
|
-
execute "EXEC sp_rename '#{table_name}', '#{new_table_name}'"
|
419
|
-
end
|
420
|
-
|
421
|
-
# Adds a new column to the named table.
|
422
|
-
# @override
|
423
|
-
def add_column(table_name, column_name, type, options = {})
|
424
|
-
clear_cached_table(table_name)
|
425
|
-
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
426
|
-
add_column_options!(add_column_sql, options)
|
427
|
-
# TODO: Add support to mimic date columns, using constraints to mark them as such in the database
|
428
|
-
# add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
|
429
|
-
execute(add_column_sql)
|
430
|
-
end
|
431
|
-
|
432
|
-
# @override
|
433
|
-
def rename_column(table_name, column_name, new_column_name)
|
434
|
-
clear_cached_table(table_name)
|
435
|
-
execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
|
436
|
-
end
|
437
|
-
|
438
|
-
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
439
|
-
# MSSQL requires the ORDER BY columns in the select list for distinct queries.
|
440
|
-
def distinct(columns, order_by)
|
441
|
-
"DISTINCT #{columns_for_distinct(columns, order_by)}"
|
442
|
-
end
|
443
|
-
|
444
|
-
def columns_for_distinct(columns, orders)
|
445
|
-
return columns if orders.blank?
|
446
|
-
|
447
|
-
# construct a clean list of column names from the ORDER BY clause,
|
448
|
-
# removing any ASC/DESC modifiers
|
449
|
-
order_columns = [ orders ]; order_columns.flatten! # AR 3.x vs 4.x
|
450
|
-
order_columns.map! do |column|
|
451
|
-
column = column.to_sql unless column.is_a?(String) # handle AREL node
|
452
|
-
column.split(',').collect!{ |s| s.split.first }
|
453
|
-
end.flatten!
|
454
|
-
order_columns.reject!(&:blank?)
|
455
|
-
order_columns = order_columns.zip(0...order_columns.size).to_a
|
456
|
-
order_columns = order_columns.map{ |s, i| "#{s}" }
|
457
|
-
|
458
|
-
columns = [ columns ]; columns.flatten!
|
459
|
-
columns.push( *order_columns ).join(', ')
|
460
|
-
# return a DISTINCT clause that's distinct on the columns we want but
|
461
|
-
# includes all the required columns for the ORDER BY to work properly
|
462
|
-
end
|
463
|
-
|
464
|
-
# @override
|
465
|
-
def change_column(table_name, column_name, type, options = {})
|
466
|
-
column = columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
467
|
-
|
468
|
-
indexes = EMPTY_ARRAY
|
469
|
-
if options_include_default?(options) || (column && column.type != type.to_sym)
|
470
|
-
remove_default_constraint(table_name, column_name)
|
471
|
-
indexes = indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }
|
472
|
-
remove_indexes(table_name, column_name)
|
473
|
-
end
|
474
|
-
|
475
|
-
if ! options[:null].nil? && options[:null] == false && ! options[:default].nil?
|
476
|
-
execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(options[:default], column)} WHERE #{quote_column_name(column_name)} IS NULL"
|
477
|
-
clear_cached_table(table_name)
|
478
|
-
end
|
479
|
-
change_column_type(table_name, column_name, type, options)
|
480
|
-
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
481
|
-
|
482
|
-
indexes.each do |index| # add any removed indexes back
|
483
|
-
index_columns = index.columns.map { |c| quote_column_name(c) }.join(', ')
|
484
|
-
execute "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index_columns})"
|
485
|
-
end
|
486
|
-
end
|
487
|
-
|
488
|
-
def change_column_type(table_name, column_name, type, options = {})
|
489
|
-
sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
490
|
-
sql << (options[:null] ? " NULL" : " NOT NULL") if options.has_key?(:null)
|
491
|
-
result = execute(sql)
|
492
|
-
clear_cached_table(table_name)
|
493
|
-
result
|
494
|
-
end
|
495
|
-
|
496
|
-
def change_column_default(table_name, column_name, default)
|
497
|
-
remove_default_constraint(table_name, column_name)
|
498
|
-
unless default.nil?
|
499
|
-
column = columns(table_name).find { |c| c.name.to_s == column_name.to_s }
|
500
|
-
result = execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote_default_value(default, column)} FOR #{quote_column_name(column_name)}"
|
501
|
-
clear_cached_table(table_name)
|
502
|
-
result
|
503
|
-
end
|
504
|
-
end
|
505
|
-
|
506
|
-
def remove_columns(table_name, *column_names)
|
507
|
-
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
508
|
-
# remove_columns(:posts, :foo, :bar) old syntax : remove_columns(:posts, [:foo, :bar])
|
509
|
-
clear_cached_table(table_name)
|
510
|
-
|
511
|
-
column_names = column_names.flatten
|
512
|
-
return do_remove_column(table_name, column_names.first) if column_names.size == 1
|
513
|
-
column_names.each { |column_name| do_remove_column(table_name, column_name) }
|
514
|
-
end
|
515
|
-
|
516
|
-
def do_remove_column(table_name, column_name)
|
517
|
-
remove_check_constraints(table_name, column_name)
|
518
|
-
remove_default_constraint(table_name, column_name)
|
519
|
-
remove_indexes(table_name, column_name) unless sqlserver_2000?
|
520
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
521
|
-
end
|
522
|
-
private :do_remove_column
|
523
|
-
|
524
|
-
if ActiveRecord::VERSION::MAJOR >= 4
|
525
|
-
|
526
|
-
# @override
|
527
|
-
def remove_column(table_name, column_name, type = nil, options = {})
|
528
|
-
remove_columns(table_name, column_name)
|
529
|
-
end
|
530
|
-
|
531
|
-
else
|
532
|
-
|
533
|
-
def remove_column(table_name, *column_names); remove_columns(table_name, *column_names) end
|
534
|
-
|
535
|
-
end
|
536
|
-
|
537
|
-
def remove_default_constraint(table_name, column_name)
|
538
|
-
clear_cached_table(table_name)
|
539
|
-
if sqlserver_2000?
|
540
|
-
# NOTE: since SQLServer 2005 these are provided as sys.sysobjects etc.
|
541
|
-
# but only due backwards-compatibility views and should be avoided ...
|
542
|
-
defaults = select_values "SELECT d.name" <<
|
543
|
-
" FROM sysobjects d, syscolumns c, sysobjects t" <<
|
544
|
-
" WHERE c.cdefault = d.id AND c.name = '#{column_name}'" <<
|
545
|
-
" AND t.name = '#{table_name}' AND c.id = t.id"
|
546
|
-
else
|
547
|
-
defaults = select_values "SELECT d.name FROM sys.tables t" <<
|
548
|
-
" JOIN sys.default_constraints d ON d.parent_object_id = t.object_id" <<
|
549
|
-
" JOIN sys.columns c ON c.object_id = t.object_id AND c.column_id = d.parent_column_id" <<
|
550
|
-
" WHERE t.name = '#{table_name}' AND c.name = '#{column_name}'"
|
551
|
-
end
|
552
|
-
defaults.each do |def_name|
|
553
|
-
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{def_name}"
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
def remove_check_constraints(table_name, column_name)
|
558
|
-
clear_cached_table(table_name)
|
559
|
-
constraints = select_values "SELECT constraint_name" <<
|
560
|
-
" FROM information_schema.constraint_column_usage" <<
|
561
|
-
" WHERE table_name = '#{table_name}' AND column_name = '#{column_name}'"
|
562
|
-
constraints.each do |constraint_name|
|
563
|
-
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint_name}"
|
564
|
-
end
|
565
|
-
end
|
566
|
-
|
567
|
-
def remove_indexes(table_name, column_name)
|
568
|
-
indexes = self.indexes(table_name)
|
569
|
-
indexes.select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
|
570
|
-
remove_index(table_name, { :name => index.name })
|
571
|
-
end
|
572
|
-
end
|
573
|
-
|
574
|
-
def remove_index(table_name, options = {})
|
575
|
-
execute "DROP INDEX #{quote_table_name(table_name)}.#{index_name(table_name, options)}"
|
576
|
-
end
|
577
|
-
|
578
|
-
# @private
|
579
|
-
SKIP_COLUMNS_TABLE_NAMES_RE = /^information_schema\./i
|
580
|
-
|
581
|
-
# @private
|
582
|
-
EMPTY_ARRAY = [].freeze
|
583
|
-
|
584
|
-
def columns(table_name, name = nil, default = EMPTY_ARRAY)
|
585
|
-
# It's possible for table_name to be an empty string, or nil, if something
|
586
|
-
# attempts to issue SQL which doesn't involve a table.
|
587
|
-
# IE. "SELECT 1" or "SELECT * FROM someFunction()".
|
588
|
-
return default if table_name.blank?
|
589
|
-
|
590
|
-
table_name = unquote_table_name(table_name)
|
591
|
-
|
592
|
-
return default if table_name =~ SKIP_COLUMNS_TABLE_NAMES_RE
|
593
|
-
|
594
|
-
unless columns = ( @table_columns ||= {} )[table_name]
|
595
|
-
@table_columns[table_name] = columns = super(table_name, name)
|
596
|
-
end
|
597
|
-
columns
|
598
|
-
end
|
599
|
-
|
600
|
-
def clear_cached_table(table_name)
|
601
|
-
( @table_columns ||= {} ).delete(table_name.to_s)
|
602
|
-
end
|
603
|
-
|
604
|
-
def reset_column_information
|
605
|
-
@table_columns = nil if defined? @table_columns
|
606
|
-
end
|
607
|
-
|
608
|
-
# Turns IDENTITY_INSERT ON for table during execution of the block
|
609
|
-
# N.B. This sets the state of IDENTITY_INSERT to OFF after the
|
610
|
-
# block has been executed without regard to its previous state
|
611
|
-
def with_identity_insert_enabled(table_name)
|
612
|
-
set_identity_insert(table_name, true)
|
613
|
-
yield
|
614
|
-
ensure
|
615
|
-
set_identity_insert(table_name, false)
|
616
|
-
end
|
617
|
-
|
618
|
-
def set_identity_insert(table_name, enable = true)
|
619
|
-
execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
620
|
-
rescue Exception => e
|
621
|
-
raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned" +
|
622
|
-
" #{enable ? 'ON' : 'OFF'} for table #{table_name} due : #{e.inspect}"
|
623
|
-
end
|
624
|
-
|
625
|
-
def disable_referential_integrity
|
626
|
-
execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
|
627
|
-
yield
|
628
|
-
ensure
|
629
|
-
execute "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
|
630
|
-
end
|
631
|
-
|
632
|
-
# @private
|
633
|
-
# @see ArJdbc::MSSQL::LimitHelpers
|
634
|
-
def determine_order_clause(sql)
|
635
|
-
return $1 if sql =~ /ORDER BY (.*)$/i
|
636
|
-
columns = self.columns(table_name = get_table_name(sql))
|
637
|
-
primary_column = columns.find { |column| column.primary? || column.identity? }
|
638
|
-
unless primary_column # look for an id column and return it,
|
639
|
-
# without changing case, to cover DBs with a case-sensitive collation :
|
640
|
-
primary_column = columns.find { |column| column.name =~ /^id$/i }
|
641
|
-
raise "no columns for table: #{table_name} (SQL query: ' #{sql} ')" if columns.empty?
|
642
|
-
end
|
643
|
-
# NOTE: if still no PK column simply get something for ORDER BY ...
|
644
|
-
"#{quote_table_name(table_name)}.#{quote_column_name((primary_column || columns.first).name)}"
|
645
|
-
end
|
646
|
-
|
647
|
-
def truncate(table_name, name = nil)
|
648
|
-
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
649
|
-
end
|
650
|
-
|
651
|
-
# Support for executing a stored procedure.
|
652
|
-
def exec_proc(proc_name, *variables)
|
653
|
-
vars =
|
654
|
-
if variables.any? && variables.first.is_a?(Hash)
|
655
|
-
variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
|
656
|
-
else
|
657
|
-
variables.map { |v| quote(v) }
|
658
|
-
end.join(', ')
|
659
|
-
sql = "EXEC #{proc_name} #{vars}".strip
|
660
|
-
log(sql, 'Execute Procedure') do
|
661
|
-
result = @connection.execute_query_raw(sql)
|
662
|
-
result.map! do |row|
|
663
|
-
row = row.is_a?(Hash) ? row.with_indifferent_access : row
|
664
|
-
yield(row) if block_given?
|
665
|
-
row
|
666
|
-
end
|
667
|
-
result
|
668
|
-
end
|
669
|
-
end
|
670
|
-
alias_method :execute_procedure, :exec_proc # AR-SQLServer-Adapter naming
|
671
|
-
|
672
|
-
# @override
|
673
|
-
def exec_query(sql, name = 'SQL', binds = [])
|
674
|
-
# NOTE: we allow to execute SQL as requested returning a results.
|
675
|
-
# e.g. this allows to use SQLServer's EXEC with a result set ...
|
676
|
-
sql = to_sql(sql, binds) if sql.respond_to?(:to_sql)
|
677
|
-
|
678
|
-
sql = repair_special_columns(sql)
|
679
|
-
if prepared_statements?
|
680
|
-
log(sql, name, binds) { @connection.execute_query(sql, binds) }
|
681
|
-
else
|
682
|
-
log(sql, name) { @connection.execute_query(sql) }
|
683
|
-
end
|
684
|
-
end
|
685
|
-
|
686
|
-
# @override
|
687
|
-
def exec_query_raw(sql, name = 'SQL', binds = [], &block)
|
688
|
-
sql = to_sql(sql, binds) if sql.respond_to?(:to_sql)
|
689
|
-
|
690
|
-
sql = repair_special_columns(sql)
|
691
|
-
if prepared_statements?
|
692
|
-
log(sql, name, binds) { @connection.execute_query_raw(sql, binds, &block) }
|
693
|
-
else
|
694
|
-
log(sql, name) { @connection.execute_query_raw(sql, &block) }
|
695
|
-
end
|
696
|
-
end
|
697
|
-
|
698
|
-
# @override
|
699
|
-
def release_savepoint(name = current_savepoint_name(false))
|
700
|
-
if @connection.jtds_driver?
|
701
|
-
@connection.release_savepoint(name)
|
702
|
-
else # MS invented it's "own" way
|
703
|
-
@connection.rollback_savepoint(name)
|
704
|
-
end
|
705
|
-
end
|
706
|
-
|
707
|
-
private
|
708
|
-
|
709
|
-
def _execute(sql, name = nil)
|
710
|
-
# Match the start of the SQL to determine appropriate behavior.
|
711
|
-
# Be aware of multi-line SQL which might begin with 'create stored_proc'
|
712
|
-
# and contain 'insert into ...' lines.
|
713
|
-
# NOTE: ignoring comment blocks prior to the first statement ?!
|
714
|
-
if self.class.insert?(sql)
|
715
|
-
if id_insert_table_name = identity_insert_table_name(sql)
|
716
|
-
with_identity_insert_enabled(id_insert_table_name) do
|
717
|
-
@connection.execute_insert(sql)
|
718
|
-
end
|
719
|
-
else
|
720
|
-
@connection.execute_insert(sql)
|
721
|
-
end
|
722
|
-
elsif self.class.select?(sql)
|
723
|
-
@connection.execute_query_raw repair_special_columns(sql)
|
724
|
-
else # create | exec
|
725
|
-
@connection.execute_update(sql)
|
726
|
-
end
|
727
|
-
end
|
728
|
-
|
729
|
-
def identity_insert_table_name(sql)
|
730
|
-
table_name = get_table_name(sql)
|
731
|
-
id_column = identity_column_name(table_name)
|
732
|
-
if id_column && sql.strip =~ /INSERT INTO [^ ]+ ?\((.+?)\)/i
|
733
|
-
insert_columns = $1.split(/, */).map(&method(:unquote_column_name))
|
734
|
-
return table_name if insert_columns.include?(id_column)
|
735
|
-
end
|
736
|
-
end
|
737
|
-
|
738
|
-
def identity_column_name(table_name)
|
739
|
-
for column in columns(table_name)
|
740
|
-
return column.name if column.identity
|
741
|
-
end
|
742
|
-
nil
|
743
|
-
end
|
744
|
-
|
745
|
-
def repair_special_columns(sql)
|
746
|
-
qualified_table_name = get_table_name(sql, true)
|
747
|
-
if special_columns = special_column_names(qualified_table_name)
|
748
|
-
return sql if special_columns.empty?
|
749
|
-
special_columns = special_columns.sort { |n1, n2| n2.size <=> n1.size }
|
750
|
-
for column in special_columns
|
751
|
-
sql.gsub!(/\s?\[?#{column}\]?\s?=\s?/, " [#{column}] LIKE ")
|
752
|
-
sql.gsub!(/ORDER BY \[?#{column}([^\.\w]|$)\]?/i, '') # NOTE: a bit stupid
|
753
|
-
end
|
754
|
-
end
|
755
|
-
sql
|
756
|
-
end
|
757
|
-
|
758
|
-
def special_column_names(qualified_table_name)
|
759
|
-
columns = self.columns(qualified_table_name, nil, nil)
|
760
|
-
return columns if ! columns || columns.empty?
|
761
|
-
special = []
|
762
|
-
columns.each { |column| special << column.name if column.special? }
|
763
|
-
special
|
764
|
-
end
|
765
|
-
|
766
|
-
def sqlserver_2000?
|
767
|
-
sqlserver_version <= '2000'
|
768
|
-
end
|
769
|
-
|
770
|
-
end
|
771
|
-
end
|
772
|
-
|
773
|
-
require 'arjdbc/util/quoted_cache'
|
774
|
-
|
775
|
-
module ActiveRecord::ConnectionAdapters
|
776
|
-
|
777
|
-
class MSSQLAdapter < JdbcAdapter
|
778
|
-
include ::ArJdbc::MSSQL
|
779
|
-
include ::ArJdbc::Util::QuotedCache
|
780
|
-
|
781
|
-
def initialize(*args)
|
782
|
-
::ArJdbc::MSSQL.initialize!
|
783
|
-
|
784
|
-
super # configure_connection happens in super
|
785
|
-
|
786
|
-
setup_limit_offset!
|
787
|
-
end
|
788
|
-
|
789
|
-
def arel_visitor # :nodoc:
|
790
|
-
( config && config[:sqlserver_version].to_s == '2000' ) ?
|
791
|
-
::Arel::Visitors::SQLServer2000.new(self) :
|
792
|
-
::Arel::Visitors::SQLServer.new(self)
|
793
|
-
end
|
794
|
-
|
795
|
-
def self.cs_equality_operator; ::ArJdbc::MSSQL.cs_equality_operator end
|
796
|
-
def self.cs_equality_operator=(operator); ::ArJdbc::MSSQL.cs_equality_operator = operator end
|
797
|
-
|
798
|
-
end
|
799
|
-
|
800
|
-
class MSSQLColumn < JdbcColumn
|
801
|
-
include ::ArJdbc::MSSQL::Column
|
802
|
-
end
|
803
|
-
|
804
|
-
end
|