activerecord-jdbc-adapter 60.0.rc1-java → 60.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +42 -30
  4. data/README.md +36 -18
  5. data/Rakefile +30 -4
  6. data/Rakefile.jdbc +8 -1
  7. data/activerecord-jdbc-adapter.gemspec +6 -9
  8. data/lib/arjdbc/abstract/connection_management.rb +5 -0
  9. data/lib/arjdbc/abstract/core.rb +1 -1
  10. data/lib/arjdbc/abstract/database_statements.rb +8 -21
  11. data/lib/arjdbc/db2/adapter.rb +11 -0
  12. data/lib/arjdbc/db2/column.rb +0 -39
  13. data/lib/arjdbc/derby/adapter.rb +1 -20
  14. data/lib/arjdbc/firebird/adapter.rb +0 -21
  15. data/lib/arjdbc/h2/adapter.rb +0 -15
  16. data/lib/arjdbc/hsqldb/adapter.rb +0 -14
  17. data/lib/arjdbc/informix/adapter.rb +0 -23
  18. data/lib/arjdbc/jdbc.rb +0 -4
  19. data/lib/arjdbc/jdbc/adapter.rb +4 -0
  20. data/lib/arjdbc/jdbc/column.rb +1 -5
  21. data/lib/arjdbc/mysql/adapter.rb +12 -1
  22. data/lib/arjdbc/mysql/connection_methods.rb +13 -7
  23. data/lib/arjdbc/postgresql/adapter.rb +10 -19
  24. data/lib/arjdbc/postgresql/column.rb +6 -3
  25. data/lib/arjdbc/postgresql/connection_methods.rb +3 -1
  26. data/lib/arjdbc/sqlite3/adapter.rb +14 -21
  27. data/lib/arjdbc/sqlite3/connection_methods.rb +1 -0
  28. data/lib/arjdbc/tasks/databases.rake +3 -1
  29. data/lib/arjdbc/version.rb +1 -1
  30. data/rakelib/02-test.rake +0 -3
  31. data/rakelib/rails.rake +1 -1
  32. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +103 -33
  33. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +259 -14
  34. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +1 -13
  35. data/src/java/arjdbc/util/DateTimeUtils.java +34 -12
  36. metadata +8 -22
  37. data/lib/active_record/connection_adapters/mssql_adapter.rb +0 -1
  38. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -1
  39. data/lib/arjdbc/mssql.rb +0 -7
  40. data/lib/arjdbc/mssql/adapter.rb +0 -804
  41. data/lib/arjdbc/mssql/column.rb +0 -200
  42. data/lib/arjdbc/mssql/connection_methods.rb +0 -79
  43. data/lib/arjdbc/mssql/explain_support.rb +0 -99
  44. data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
  45. data/lib/arjdbc/mssql/lock_methods.rb +0 -77
  46. data/lib/arjdbc/mssql/types.rb +0 -343
  47. data/lib/arjdbc/mssql/utils.rb +0 -82
@@ -38,7 +38,6 @@ import java.lang.reflect.InvocationTargetException;
38
38
  import java.math.BigDecimal;
39
39
  import java.sql.Connection;
40
40
  import java.sql.DatabaseMetaData;
41
- import java.sql.Date;
42
41
  import java.sql.PreparedStatement;
43
42
  import java.sql.ResultSet;
44
43
  import java.sql.ResultSetMetaData;
@@ -387,18 +386,7 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
387
386
  }
388
387
  }
389
388
 
390
- if ( ! "Date".equals(value.getMetaClass().getName()) && value.respondsTo("to_date") ) {
391
- value = value.callMethod(context, "to_date");
392
- }
393
-
394
- int year = RubyNumeric.num2int(value.callMethod(context, "year"));
395
- int month = RubyNumeric.num2int(value.callMethod(context, "month"));
396
- int day = RubyNumeric.num2int(value.callMethod(context, "day"));
397
-
398
- @SuppressWarnings("deprecation")
399
- Date date = new Date(year - 1900, month - 1, day);
400
-
401
- statement.setDate(index, date);
389
+ super.setDateParameter(context, connection, statement, index, value, attribute, type);
402
390
  }
403
391
 
404
392
  @Override
@@ -37,6 +37,7 @@ import org.jruby.Ruby;
37
37
  import org.jruby.RubyFloat;
38
38
  import org.jruby.RubyString;
39
39
  import org.jruby.RubyTime;
40
+ import org.jruby.ext.date.RubyDateTime;
40
41
  import org.jruby.javasupport.Java;
41
42
  import org.jruby.runtime.Block;
42
43
  import org.jruby.runtime.ThreadContext;
@@ -51,6 +52,9 @@ import static arjdbc.util.StringHelper.decByte;
51
52
  * @author kares
52
53
  */
53
54
  public abstract class DateTimeUtils {
55
+
56
+ private static final GJChronology CHRONO_ITALY_UTC = GJChronology.getInstance(DateTimeZone.UTC);
57
+
54
58
  public static RubyTime toTime(final ThreadContext context, final IRubyObject value) {
55
59
  if (!(value instanceof RubyTime)) { // unlikely
56
60
  return (RubyTime) TypeConverter.convertToTypeWithCheck(value, context.runtime.getTime(), "to_time");
@@ -218,9 +222,11 @@ public abstract class DateTimeUtils {
218
222
  final int hours = time.getHours();
219
223
  final int minutes = time.getMinutes();
220
224
  final int seconds = time.getSeconds();
221
- final int nanos = time.getNanos(); // max 999-999-999
225
+ int nanos = time.getNanos(); // max 999-999-999
226
+ final int millis = nanos / 1000000;
227
+ nanos = nanos % 1000000;
222
228
 
223
- DateTime dateTime = new DateTime(2000, 1, 1, hours, minutes, seconds, defaultZone);
229
+ DateTime dateTime = new DateTime(2000, 1, 1, hours, minutes, seconds, millis, defaultZone);
224
230
  return RubyTime.newTime(context.runtime, dateTime, nanos);
225
231
  }
226
232
 
@@ -233,9 +239,11 @@ public abstract class DateTimeUtils {
233
239
  final int hours = timestamp.getHours();
234
240
  final int minutes = timestamp.getMinutes();
235
241
  final int seconds = timestamp.getSeconds();
236
- final int nanos = timestamp.getNanos(); // max 999-999-999
242
+ int nanos = timestamp.getNanos(); // max 999-999-999
243
+ final int millis = nanos / 1000000;
244
+ nanos = nanos % 1000000;
237
245
 
238
- DateTime dateTime = new DateTime(year, month, day, hours, minutes, seconds, 0, defaultZone);
246
+ DateTime dateTime = new DateTime(year, month, day, hours, minutes, seconds, millis, defaultZone);
239
247
  return RubyTime.newTime(context.runtime, dateTime, nanos);
240
248
  }
241
249
 
@@ -260,6 +268,15 @@ public abstract class DateTimeUtils {
260
268
  return newDate(context, year, month, day, ISOChronology.getInstance(zone));
261
269
  }
262
270
 
271
+ @SuppressWarnings("deprecation")
272
+ public static IRubyObject newDate(final ThreadContext context, final Date date) {
273
+ final int year = date.getYear() + 1900;
274
+ final int month = date.getMonth() + 1;
275
+ final int day = date.getDate();
276
+
277
+ return newDate(context, year, month, day, CHRONO_ITALY_UTC);
278
+ }
279
+
263
280
  // @Deprecated
264
281
  public static Timestamp convertToTimestamp(final RubyFloat value) {
265
282
  final Timestamp timestamp = new Timestamp(value.getLongValue() * 1000); // millis
@@ -332,9 +349,7 @@ public abstract class DateTimeUtils {
332
349
 
333
350
  if ( bcEra ) year = -1 * year; // no + 1 since we use GJChronology
334
351
 
335
- DateTime dateTime = new DateTime(year, month, day, 0, 0, 0, GJChronology.getInstance(defaultZone));
336
- final Ruby runtime = context.runtime;
337
- return runtime.getClass("Date").newInstance(context, Java.getInstance(runtime, dateTime), Block.NULL_BLOCK);
352
+ return newDate(context, year, month, day, GJChronology.getInstance(defaultZone));
338
353
  }
339
354
 
340
355
  public static IRubyObject parseTime(final ThreadContext context, final CharSequence str, final DateTimeZone defaultZone)
@@ -559,7 +574,7 @@ public abstract class DateTimeUtils {
559
574
  }
560
575
 
561
576
  private static IRubyObject newDate(final ThreadContext context, final int year, final int month, final int day,
562
- final ISOChronology chronology) {
577
+ final Chronology chronology) {
563
578
  // NOTE: JRuby really needs a native date.rb until than its a bit costly going from ...
564
579
  // java.sql.Date -> allocating a DateTime proxy, help a bit by shooting at the internals
565
580
  //
@@ -618,14 +633,21 @@ public abstract class DateTimeUtils {
618
633
  */
619
634
  public static String timestampTimeToString(final ThreadContext context,
620
635
  final IRubyObject value, DateTimeZone zone, boolean withZone) {
621
- RubyTime timeValue = toTime(context, value);
622
- DateTime dt = timeValue.getDateTime();
636
+ DateTime dt;
637
+ int usec = 0;
638
+ if (value instanceof RubyDateTime) {
639
+ dt = ((RubyDateTime) value).getDateTime();
640
+ } else {
641
+ RubyTime timeValue = toTime(context, value);
642
+ dt = timeValue.getDateTime();
643
+ usec = (int) timeValue.getUSec();
644
+ }
623
645
 
624
646
  StringBuilder sb = new StringBuilder(36);
625
647
 
626
648
  int year = dt.getYear();
627
649
  if (year <= 0) {
628
- year--;
650
+ if (!(value instanceof RubyDateTime)) year--;
629
651
  } else if (zone != null) {
630
652
  dt = dateTimeInZone(dt, zone);
631
653
  year = dt.getYear();
@@ -645,7 +667,7 @@ public abstract class DateTimeUtils {
645
667
  if (year < 0) sb.append(" BC");
646
668
  sb.append(' ');
647
669
 
648
- appendTime(sb, chrono, millis, (int) timeValue.getUSec(), withZone);
670
+ appendTime(sb, chrono, millis, usec, withZone);
649
671
 
650
672
  return sb.toString();
651
673
  }
metadata CHANGED
@@ -1,21 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-jdbc-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 60.0.rc1
4
+ version: '60.1'
5
5
  platform: java
6
6
  authors:
7
7
  - Nick Sieger, Ola Bini, Karol Bucek and JRuby contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-28 00:00:00.000000000 Z
11
+ date: 2019-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 6.0.0.rc1
18
+ version: 6.0.0
19
19
  name: activerecord
20
20
  prerelease: false
21
21
  type: :runtime
@@ -23,13 +23,10 @@ dependencies:
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 6.0.0.rc1
26
+ version: 6.0.0
27
27
  description: 'AR-JDBC is a database adapter for Rails'' ActiveRecord component designed
28
28
  to be used with JRuby built upon Java''s JDBC API for database access. Provides
29
- (ActiveRecord) built-in adapters: MySQL, PostgreSQL and SQLite3 as well as adapters
30
- for popular databases such as Oracle, SQLServer, DB2, FireBird and even Java (embed)
31
- databases: Derby, HSQLDB and H2. It allows to connect to virtually any JDBC-compliant
32
- database with your JRuby on Rails application.'
29
+ (ActiveRecord) built-in adapters: MySQL, PostgreSQL, SQLite3, and SQLServer.'
33
30
  email:
34
31
  - nick@nicksieger.com
35
32
  - ola.bini@gmail.com
@@ -60,12 +57,10 @@ files:
60
57
  - lib/active_record/connection_adapters/jdbc_adapter.rb
61
58
  - lib/active_record/connection_adapters/jndi_adapter.rb
62
59
  - lib/active_record/connection_adapters/mariadb_adapter.rb
63
- - lib/active_record/connection_adapters/mssql_adapter.rb
64
60
  - lib/active_record/connection_adapters/mysql2_adapter.rb
65
61
  - lib/active_record/connection_adapters/mysql_adapter.rb
66
62
  - lib/active_record/connection_adapters/postgresql_adapter.rb
67
63
  - lib/active_record/connection_adapters/sqlite3_adapter.rb
68
- - lib/active_record/connection_adapters/sqlserver_adapter.rb
69
64
  - lib/activerecord-jdbc-adapter.rb
70
65
  - lib/arel/visitors/compat.rb
71
66
  - lib/arel/visitors/db2.rb
@@ -124,15 +119,6 @@ files:
124
119
  - lib/arjdbc/jdbc/serialized_attributes_helper.rb
125
120
  - lib/arjdbc/jdbc/type_cast.rb
126
121
  - lib/arjdbc/jdbc/type_converter.rb
127
- - lib/arjdbc/mssql.rb
128
- - lib/arjdbc/mssql/adapter.rb
129
- - lib/arjdbc/mssql/column.rb
130
- - lib/arjdbc/mssql/connection_methods.rb
131
- - lib/arjdbc/mssql/explain_support.rb
132
- - lib/arjdbc/mssql/limit_helpers.rb
133
- - lib/arjdbc/mssql/lock_methods.rb
134
- - lib/arjdbc/mssql/types.rb
135
- - lib/arjdbc/mssql/utils.rb
136
122
  - lib/arjdbc/mysql.rb
137
123
  - lib/arjdbc/mysql/adapter.rb
138
124
  - lib/arjdbc/mysql/connection_methods.rb
@@ -240,12 +226,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
240
226
  version: '0'
241
227
  required_rubygems_version: !ruby/object:Gem::Requirement
242
228
  requirements:
243
- - - ">"
229
+ - - ">="
244
230
  - !ruby/object:Gem::Version
245
- version: 1.3.1
231
+ version: '0'
246
232
  requirements: []
247
233
  rubyforge_project:
248
- rubygems_version: 2.7.9
234
+ rubygems_version: 2.7.10
249
235
  signing_key:
250
236
  specification_version: 4
251
237
  summary: JDBC adapter for ActiveRecord, for use within JRuby on Rails.
@@ -1 +0,0 @@
1
- require 'arjdbc/mssql'
@@ -1 +0,0 @@
1
- require 'arjdbc/mssql'
data/lib/arjdbc/mssql.rb DELETED
@@ -1,7 +0,0 @@
1
- require 'arjdbc'
2
- require 'arjdbc/mssql/adapter'
3
- require 'arjdbc/mssql/connection_methods'
4
- module ArJdbc
5
- MsSQL = MSSQL # compatibility with 1.2
6
- end
7
- ArJdbc.warn_unsupported_adapter 'mssql', [4, 2] # warns on AR >= 4.2
@@ -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