activerecord-jdbc-alt-adapter 50.3.0-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.
Files changed (198) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/.travis.yml +100 -0
  4. data/.yardopts +4 -0
  5. data/CONTRIBUTING.md +50 -0
  6. data/Gemfile +92 -0
  7. data/History.md +1191 -0
  8. data/LICENSE.txt +26 -0
  9. data/README.md +240 -0
  10. data/RUNNING_TESTS.md +127 -0
  11. data/Rakefile +336 -0
  12. data/Rakefile.jdbc +20 -0
  13. data/activerecord-jdbc-adapter.gemspec +55 -0
  14. data/activerecord-jdbc-alt-adapter.gemspec +56 -0
  15. data/lib/active_record/connection_adapters/as400_adapter.rb +2 -0
  16. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -0
  17. data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
  18. data/lib/active_record/connection_adapters/firebird_adapter.rb +1 -0
  19. data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
  20. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
  21. data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
  22. data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
  23. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  24. data/lib/active_record/connection_adapters/mariadb_adapter.rb +1 -0
  25. data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
  26. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
  30. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
  31. data/lib/activerecord-jdbc-adapter.rb +1 -0
  32. data/lib/arel/visitors/compat.rb +60 -0
  33. data/lib/arel/visitors/db2.rb +137 -0
  34. data/lib/arel/visitors/derby.rb +112 -0
  35. data/lib/arel/visitors/firebird.rb +79 -0
  36. data/lib/arel/visitors/h2.rb +25 -0
  37. data/lib/arel/visitors/hsqldb.rb +32 -0
  38. data/lib/arel/visitors/postgresql_jdbc.rb +6 -0
  39. data/lib/arel/visitors/sql_server.rb +225 -0
  40. data/lib/arel/visitors/sql_server/ng42.rb +294 -0
  41. data/lib/arel/visitors/sqlserver.rb +214 -0
  42. data/lib/arjdbc.rb +19 -0
  43. data/lib/arjdbc/abstract/connection_management.rb +35 -0
  44. data/lib/arjdbc/abstract/core.rb +74 -0
  45. data/lib/arjdbc/abstract/database_statements.rb +64 -0
  46. data/lib/arjdbc/abstract/statement_cache.rb +58 -0
  47. data/lib/arjdbc/abstract/transaction_support.rb +86 -0
  48. data/lib/arjdbc/db2.rb +4 -0
  49. data/lib/arjdbc/db2/adapter.rb +789 -0
  50. data/lib/arjdbc/db2/as400.rb +130 -0
  51. data/lib/arjdbc/db2/column.rb +167 -0
  52. data/lib/arjdbc/db2/connection_methods.rb +44 -0
  53. data/lib/arjdbc/derby.rb +3 -0
  54. data/lib/arjdbc/derby/active_record_patch.rb +13 -0
  55. data/lib/arjdbc/derby/adapter.rb +540 -0
  56. data/lib/arjdbc/derby/connection_methods.rb +20 -0
  57. data/lib/arjdbc/derby/schema_creation.rb +15 -0
  58. data/lib/arjdbc/discover.rb +104 -0
  59. data/lib/arjdbc/firebird.rb +4 -0
  60. data/lib/arjdbc/firebird/adapter.rb +434 -0
  61. data/lib/arjdbc/firebird/connection_methods.rb +23 -0
  62. data/lib/arjdbc/h2.rb +3 -0
  63. data/lib/arjdbc/h2/adapter.rb +303 -0
  64. data/lib/arjdbc/h2/connection_methods.rb +27 -0
  65. data/lib/arjdbc/hsqldb.rb +3 -0
  66. data/lib/arjdbc/hsqldb/adapter.rb +297 -0
  67. data/lib/arjdbc/hsqldb/connection_methods.rb +28 -0
  68. data/lib/arjdbc/hsqldb/explain_support.rb +35 -0
  69. data/lib/arjdbc/hsqldb/schema_creation.rb +11 -0
  70. data/lib/arjdbc/informix.rb +5 -0
  71. data/lib/arjdbc/informix/adapter.rb +162 -0
  72. data/lib/arjdbc/informix/connection_methods.rb +9 -0
  73. data/lib/arjdbc/jdbc.rb +59 -0
  74. data/lib/arjdbc/jdbc/adapter.rb +475 -0
  75. data/lib/arjdbc/jdbc/adapter_require.rb +46 -0
  76. data/lib/arjdbc/jdbc/base_ext.rb +15 -0
  77. data/lib/arjdbc/jdbc/callbacks.rb +53 -0
  78. data/lib/arjdbc/jdbc/column.rb +97 -0
  79. data/lib/arjdbc/jdbc/connection.rb +14 -0
  80. data/lib/arjdbc/jdbc/connection_methods.rb +37 -0
  81. data/lib/arjdbc/jdbc/error.rb +65 -0
  82. data/lib/arjdbc/jdbc/extension.rb +59 -0
  83. data/lib/arjdbc/jdbc/java.rb +13 -0
  84. data/lib/arjdbc/jdbc/railtie.rb +2 -0
  85. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -0
  86. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -0
  87. data/lib/arjdbc/jdbc/type_cast.rb +166 -0
  88. data/lib/arjdbc/jdbc/type_converter.rb +142 -0
  89. data/lib/arjdbc/mssql.rb +7 -0
  90. data/lib/arjdbc/mssql/adapter.rb +384 -0
  91. data/lib/arjdbc/mssql/column.rb +29 -0
  92. data/lib/arjdbc/mssql/connection_methods.rb +79 -0
  93. data/lib/arjdbc/mssql/database_statements.rb +134 -0
  94. data/lib/arjdbc/mssql/errors.rb +6 -0
  95. data/lib/arjdbc/mssql/explain_support.rb +129 -0
  96. data/lib/arjdbc/mssql/extensions.rb +36 -0
  97. data/lib/arjdbc/mssql/limit_helpers.rb +231 -0
  98. data/lib/arjdbc/mssql/lock_methods.rb +77 -0
  99. data/lib/arjdbc/mssql/old_adapter.rb +804 -0
  100. data/lib/arjdbc/mssql/old_column.rb +200 -0
  101. data/lib/arjdbc/mssql/quoting.rb +101 -0
  102. data/lib/arjdbc/mssql/schema_creation.rb +31 -0
  103. data/lib/arjdbc/mssql/schema_definitions.rb +74 -0
  104. data/lib/arjdbc/mssql/schema_statements.rb +329 -0
  105. data/lib/arjdbc/mssql/transaction.rb +69 -0
  106. data/lib/arjdbc/mssql/types.rb +52 -0
  107. data/lib/arjdbc/mssql/types/binary_types.rb +33 -0
  108. data/lib/arjdbc/mssql/types/date_and_time_types.rb +134 -0
  109. data/lib/arjdbc/mssql/types/deprecated_types.rb +40 -0
  110. data/lib/arjdbc/mssql/types/numeric_types.rb +71 -0
  111. data/lib/arjdbc/mssql/types/string_types.rb +56 -0
  112. data/lib/arjdbc/mssql/utils.rb +66 -0
  113. data/lib/arjdbc/mysql.rb +3 -0
  114. data/lib/arjdbc/mysql/adapter.rb +140 -0
  115. data/lib/arjdbc/mysql/connection_methods.rb +166 -0
  116. data/lib/arjdbc/oracle/adapter.rb +863 -0
  117. data/lib/arjdbc/postgresql.rb +3 -0
  118. data/lib/arjdbc/postgresql/adapter.rb +687 -0
  119. data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
  120. data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
  121. data/lib/arjdbc/postgresql/base/array_parser.rb +95 -0
  122. data/lib/arjdbc/postgresql/base/pgconn.rb +11 -0
  123. data/lib/arjdbc/postgresql/column.rb +51 -0
  124. data/lib/arjdbc/postgresql/connection_methods.rb +67 -0
  125. data/lib/arjdbc/postgresql/name.rb +24 -0
  126. data/lib/arjdbc/postgresql/oid_types.rb +266 -0
  127. data/lib/arjdbc/railtie.rb +11 -0
  128. data/lib/arjdbc/sqlite3.rb +3 -0
  129. data/lib/arjdbc/sqlite3/adapter.rb +678 -0
  130. data/lib/arjdbc/sqlite3/connection_methods.rb +59 -0
  131. data/lib/arjdbc/sybase.rb +2 -0
  132. data/lib/arjdbc/sybase/adapter.rb +47 -0
  133. data/lib/arjdbc/tasks.rb +13 -0
  134. data/lib/arjdbc/tasks/database_tasks.rb +31 -0
  135. data/lib/arjdbc/tasks/databases.rake +48 -0
  136. data/lib/arjdbc/tasks/db2_database_tasks.rb +104 -0
  137. data/lib/arjdbc/tasks/derby_database_tasks.rb +95 -0
  138. data/lib/arjdbc/tasks/h2_database_tasks.rb +31 -0
  139. data/lib/arjdbc/tasks/hsqldb_database_tasks.rb +70 -0
  140. data/lib/arjdbc/tasks/jdbc_database_tasks.rb +169 -0
  141. data/lib/arjdbc/tasks/mssql_database_tasks.rb +46 -0
  142. data/lib/arjdbc/util/quoted_cache.rb +60 -0
  143. data/lib/arjdbc/util/serialized_attributes.rb +98 -0
  144. data/lib/arjdbc/util/table_copier.rb +110 -0
  145. data/lib/arjdbc/version.rb +3 -0
  146. data/lib/generators/jdbc/USAGE +9 -0
  147. data/lib/generators/jdbc/jdbc_generator.rb +17 -0
  148. data/lib/jdbc_adapter.rb +2 -0
  149. data/lib/jdbc_adapter/rake_tasks.rb +4 -0
  150. data/lib/jdbc_adapter/version.rb +4 -0
  151. data/pom.xml +114 -0
  152. data/rails_generators/jdbc_generator.rb +15 -0
  153. data/rails_generators/templates/config/initializers/jdbc.rb +10 -0
  154. data/rails_generators/templates/lib/tasks/jdbc.rake +11 -0
  155. data/rakelib/01-tomcat.rake +51 -0
  156. data/rakelib/02-test.rake +132 -0
  157. data/rakelib/bundler_ext.rb +11 -0
  158. data/rakelib/db.rake +75 -0
  159. data/rakelib/rails.rake +223 -0
  160. data/src/java/arjdbc/ArJdbcModule.java +276 -0
  161. data/src/java/arjdbc/db2/DB2Module.java +76 -0
  162. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +126 -0
  163. data/src/java/arjdbc/derby/DerbyModule.java +178 -0
  164. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +152 -0
  165. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +174 -0
  166. data/src/java/arjdbc/h2/H2Module.java +50 -0
  167. data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +85 -0
  168. data/src/java/arjdbc/hsqldb/HSQLDBModule.java +73 -0
  169. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +75 -0
  170. data/src/java/arjdbc/jdbc/AdapterJavaService.java +43 -0
  171. data/src/java/arjdbc/jdbc/Callable.java +44 -0
  172. data/src/java/arjdbc/jdbc/ConnectionFactory.java +45 -0
  173. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +156 -0
  174. data/src/java/arjdbc/jdbc/DriverConnectionFactory.java +63 -0
  175. data/src/java/arjdbc/jdbc/DriverWrapper.java +119 -0
  176. data/src/java/arjdbc/jdbc/JdbcResult.java +130 -0
  177. data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +61 -0
  178. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3979 -0
  179. data/src/java/arjdbc/mssql/MSSQLModule.java +90 -0
  180. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +508 -0
  181. data/src/java/arjdbc/mysql/MySQLModule.java +152 -0
  182. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +294 -0
  183. data/src/java/arjdbc/oracle/OracleModule.java +80 -0
  184. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +455 -0
  185. data/src/java/arjdbc/postgresql/ByteaUtils.java +157 -0
  186. data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +52 -0
  187. data/src/java/arjdbc/postgresql/PostgreSQLModule.java +77 -0
  188. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +192 -0
  189. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +948 -0
  190. data/src/java/arjdbc/sqlite3/SQLite3Module.java +73 -0
  191. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +525 -0
  192. data/src/java/arjdbc/util/CallResultSet.java +826 -0
  193. data/src/java/arjdbc/util/DateTimeUtils.java +699 -0
  194. data/src/java/arjdbc/util/ObjectSupport.java +65 -0
  195. data/src/java/arjdbc/util/QuotingUtils.java +137 -0
  196. data/src/java/arjdbc/util/StringCache.java +63 -0
  197. data/src/java/arjdbc/util/StringHelper.java +145 -0
  198. metadata +269 -0
@@ -0,0 +1,29 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ # MSSQL specific extensions to column definitions in a table.
4
+ class MSSQLColumn < Column
5
+ def initialize(name, raw_default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
6
+ default = extract_default(raw_default)
7
+
8
+ super(name, default, sql_type_metadata, null, table_name, default_function, collation, comment: comment)
9
+ end
10
+
11
+ def extract_default(value)
12
+ # return nil if default does not match the patterns to avoid
13
+ # any unexpected errors.
14
+ return unless value =~ /^\(N?'(.*)'\)$/m || value =~ /^\(\(?(.*?)\)?\)$/
15
+
16
+ unquote_string(Regexp.last_match[1])
17
+ end
18
+
19
+ def unquote_string(string)
20
+ string.to_s.gsub("''", "'")
21
+ end
22
+
23
+ def identity?
24
+ sql_type.downcase.include? 'identity'
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,79 @@
1
+ ArJdbc::ConnectionMethods.module_eval do
2
+
3
+ # Default connection method for MS-SQL adapter (`adapter: mssql`),
4
+ # uses the (open-source) jTDS driver.
5
+ # If you'd like to use the "official" MS's SQL-JDBC driver, it's preferable
6
+ # to use the {#sqlserver_connection} method (set `adapter: sqlserver`).
7
+ def mssql_connection(config)
8
+ # NOTE: this detection ain't perfect and is only meant as a temporary hack
9
+ # users will get a deprecation eventually to use `adapter: sqlserver` ...
10
+ if config[:driver] =~ /SQLServerDriver$/ || config[:url] =~ /^jdbc:sqlserver:/
11
+ return sqlserver_connection(config)
12
+ end
13
+
14
+ config[:adapter_spec] ||= ::ArJdbc::MSSQL
15
+ config[:adapter_class] = ActiveRecord::ConnectionAdapters::MSSQLAdapter unless config.key?(:adapter_class)
16
+
17
+ return jndi_connection(config) if jndi_config?(config)
18
+
19
+ begin
20
+ require 'jdbc/jtds'
21
+ # NOTE: the adapter has only support for working with the
22
+ # open-source jTDS driver (won't work with MS's driver) !
23
+ ::Jdbc::JTDS.load_driver(:require) if defined?(::Jdbc::JTDS.load_driver)
24
+ rescue LoadError => e # assuming driver.jar is on the class-path
25
+ raise e unless e.message.to_s.index('no such file to load')
26
+ end
27
+
28
+ config[:host] ||= 'localhost'
29
+ config[:port] ||= 1433
30
+ config[:driver] ||= defined?(::Jdbc::JTDS.driver_name) ? ::Jdbc::JTDS.driver_name : 'net.sourceforge.jtds.jdbc.Driver'
31
+ config[:connection_alive_sql] ||= 'SELECT 1'
32
+
33
+ config[:url] ||= begin
34
+ url = "jdbc:jtds:sqlserver://#{config[:host]}:#{config[:port]}/#{config[:database]}"
35
+ # Instance is often a preferrable alternative to port when dynamic ports are used.
36
+ # If instance is specified then port is essentially ignored.
37
+ url << ";instance=#{config[:instance]}" if config[:instance]
38
+ # This will enable windows domain-based authentication and will require the JTDS native libraries be available.
39
+ url << ";domain=#{config[:domain]}" if config[:domain]
40
+ # AppName is shown in sql server as additional information against the connection.
41
+ url << ";appname=#{config[:appname]}" if config[:appname]
42
+ url
43
+ end
44
+
45
+ unless config[:domain]
46
+ config[:username] ||= 'sa'
47
+ config[:password] ||= ''
48
+ end
49
+ jdbc_connection(config)
50
+ end
51
+ alias_method :jdbcmssql_connection, :mssql_connection
52
+
53
+ # @note Assumes SQLServer SQL-JDBC driver on the class-path.
54
+ def sqlserver_connection(config)
55
+ config[:adapter_spec] ||= ::ArJdbc::MSSQL
56
+ config[:adapter_class] = ActiveRecord::ConnectionAdapters::MSSQLAdapter unless config.key?(:adapter_class)
57
+
58
+ return jndi_connection(config) if jndi_config?(config)
59
+
60
+ config[:host] ||= 'localhost'
61
+ config[:driver] ||= 'com.microsoft.sqlserver.jdbc.SQLServerDriver'
62
+ config[:connection_alive_sql] ||= 'SELECT 1'
63
+
64
+ config[:url] ||= begin
65
+ url = "jdbc:sqlserver://#{config[:host]}"
66
+ url << ( config[:port] ? ":#{config[:port]};" : ';' )
67
+ url << "databaseName=#{config[:database]};" if config[:database]
68
+ url << "instanceName=#{config[:instance]};" if config[:instance]
69
+ app = config[:appname] || config[:application]
70
+ url << "applicationName=#{app};" if app
71
+ isc = config[:integrated_security] # Win only - needs sqljdbc_auth.dll
72
+ url << "integratedSecurity=#{isc};" unless isc.nil?
73
+ url
74
+ end
75
+ jdbc_connection(config)
76
+ end
77
+ alias_method :jdbcsqlserver_connection, :sqlserver_connection
78
+
79
+ end
@@ -0,0 +1,134 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module MSSQL
4
+ module DatabaseStatements
5
+
6
+ def exec_proc(proc_name, *variables)
7
+ vars =
8
+ if variables.any? && variables.first.is_a?(Hash)
9
+ variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
10
+ else
11
+ variables.map { |v| quote(v) }
12
+ end.join(', ')
13
+ sql = "EXEC #{proc_name} #{vars}".strip
14
+ log(sql, 'Execute Procedure') do
15
+ result = @connection.execute_query_raw(sql)
16
+ result.map! do |row|
17
+ row = row.is_a?(Hash) ? row.with_indifferent_access : row
18
+ yield(row) if block_given?
19
+ row
20
+ end
21
+ result
22
+ end
23
+ end
24
+ alias_method :execute_procedure, :exec_proc # AR-SQLServer-Adapter naming
25
+
26
+ def execute(sql, name = nil)
27
+ # with identity insert on block
28
+ if insert_sql?(sql)
29
+ table_name_for_identity_insert = identity_insert_table_name(sql)
30
+
31
+ if table_name_for_identity_insert
32
+ with_identity_insert_enabled(table_name_for_identity_insert) do
33
+ super
34
+ end
35
+ else
36
+ super
37
+ end
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
44
+ table_name_for_identity_insert = identity_insert_table_name(sql)
45
+
46
+ if table_name_for_identity_insert
47
+ with_identity_insert_enabled(table_name_for_identity_insert) do
48
+ super
49
+ end
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ # Implements the truncate method.
56
+ def truncate(table_name, name = nil)
57
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
58
+ end
59
+
60
+ # Not a rails method, own method to test different isolation
61
+ # levels supported by the mssql adapter.
62
+ def supports_transaction_isolation_level?(level)
63
+ @connection.supports_transaction_isolation?(level)
64
+ end
65
+
66
+ def transaction_isolation=(value)
67
+ @connection.set_transaction_isolation(value)
68
+ end
69
+
70
+ def transaction_isolation
71
+ @connection.get_transaction_isolation
72
+ end
73
+
74
+ private
75
+
76
+ def insert_sql?(sql)
77
+ !(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
78
+ end
79
+
80
+ def identity_insert_table_name(sql)
81
+ table_name = get_table_name(sql)
82
+ id_column = identity_column_name(table_name)
83
+ if id_column && sql.strip =~ /INSERT INTO [^ ]+ ?\((.+?)\)/i
84
+ insert_columns = $1.split(/, */).map{|w| ArJdbc::MSSQL::Utils.unquote_column_name(w)}
85
+ return table_name if insert_columns.include?(id_column)
86
+ end
87
+ end
88
+
89
+ def identity_column_name(table_name)
90
+ for column in columns(table_name)
91
+ return column.name if column.identity?
92
+ end
93
+ nil
94
+ end
95
+
96
+ # Turns IDENTITY_INSERT ON for table during execution of the block
97
+ # N.B. This sets the state of IDENTITY_INSERT to OFF after the
98
+ # block has been executed without regard to its previous state
99
+ def with_identity_insert_enabled(table_name)
100
+ set_identity_insert(table_name, true)
101
+ yield
102
+ ensure
103
+ set_identity_insert(table_name, false)
104
+ end
105
+
106
+ def set_identity_insert(table_name, enable = true)
107
+ if enable
108
+ execute("SET IDENTITY_INSERT #{quote_table_name(table_name)} ON")
109
+ else
110
+ execute("SET IDENTITY_INSERT #{quote_table_name(table_name)} OFF")
111
+ end
112
+ rescue Exception => e
113
+ raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned" +
114
+ " #{enable ? 'ON' : 'OFF'} for table #{table_name} due : #{e.inspect}"
115
+ end
116
+
117
+ def get_table_name(sql, qualified = nil)
118
+ if sql =~ TABLE_NAME_INSERT_UPDATE
119
+ tn = $2 || $3
120
+ qualified ? tn : ArJdbc::MSSQL::Utils.unqualify_table_name(tn)
121
+ elsif sql =~ TABLE_NAME_FROM
122
+ qualified ? $1 : ArJdbc::MSSQL::Utils.unqualify_table_name($1)
123
+ else
124
+ nil
125
+ end
126
+ end
127
+
128
+ TABLE_NAME_INSERT_UPDATE = /^\s*(INSERT|EXEC sp_executesql N'INSERT)(?:\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
129
+
130
+ TABLE_NAME_FROM = /\bFROM\s+([^\(\)\s,]+)\s*/i
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,6 @@
1
+ module ActiveRecord
2
+ # Error raised when adapter determines the database could not acquire
3
+ # a necessary lock before timing out
4
+ class LockTimeout < StatementInvalid
5
+ end
6
+ end
@@ -0,0 +1,129 @@
1
+ require 'active_support/core_ext/string'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module MSSQL
6
+ # NOTE: the execution plan (explain) is a estimated only for prepared
7
+ # statements similar the jTDS used to provide. The mssql-jdbc driver
8
+ # does not supports explain from prepared statements.
9
+ # more in: https://github.com/Microsoft/mssql-jdbc/issues/778
10
+ #
11
+ module ExplainSupport
12
+ DISABLED = Java::JavaLang::Boolean.getBoolean('arjdbc.mssql.explain_support.disabled')
13
+
14
+ def supports_explain?
15
+ !DISABLED
16
+ end
17
+
18
+ def explain(arel, binds = [])
19
+ return if DISABLED
20
+
21
+ # sql = to_sql(arel, binds)
22
+ # result = with_showplan_on { exec_query(sql, 'EXPLAIN', binds) }
23
+ sql = interpolate_sql_statement(arel, binds)
24
+ result = with_showplan_on do
25
+ exec_query(sql, 'EXPLAIN', [])
26
+ end
27
+ PrinterTable.new(result).pp
28
+ end
29
+
30
+ protected
31
+
32
+ # converting the prepared statements to sql
33
+ def interpolate_sql_statement(arel, binds)
34
+ return arel if binds.empty?
35
+
36
+ sql = if arel.respond_to?(:to_sql)
37
+ arel.to_sql
38
+ else
39
+ arel
40
+ end
41
+
42
+ binds.each do |bind|
43
+ value = quote(bind.value_for_database)
44
+ sql.sub!('?', value)
45
+ end
46
+
47
+ sql
48
+ end
49
+
50
+ def with_showplan_on
51
+ set_showplan_option(true)
52
+ yield
53
+ ensure
54
+ set_showplan_option(false)
55
+ end
56
+
57
+ def set_showplan_option(enable = true)
58
+ option = 'SHOWPLAN_ALL'
59
+ execute "SET #{option} #{enable ? 'ON' : 'OFF'}"
60
+ rescue Exception => e
61
+ raise ActiveRecord::ActiveRecordError, "#{option} could not be turned" +
62
+ " #{enable ? 'ON' : 'OFF'} (check SHOWPLAN permissions) due : #{e.inspect}"
63
+ end
64
+
65
+ # @private
66
+ class PrinterTable
67
+
68
+ cattr_accessor :max_column_width, :cell_padding
69
+ self.max_column_width = 50
70
+ self.cell_padding = 1
71
+
72
+ attr_reader :result
73
+
74
+ def initialize(result)
75
+ @result = result
76
+ end
77
+
78
+ def pp
79
+ @widths = compute_column_widths
80
+ @separator = build_separator
81
+ pp = []
82
+ pp << @separator
83
+ pp << build_cells(result.columns)
84
+ pp << @separator
85
+ result.rows.each do |row|
86
+ pp << build_cells(row)
87
+ end
88
+ pp << @separator
89
+ pp.join("\n") << "\n"
90
+ end
91
+
92
+ private
93
+
94
+ def compute_column_widths
95
+ [].tap do |computed_widths|
96
+ result.columns.each_with_index do |column, i|
97
+ cells_in_column = [column] + result.rows.map { |r| cast_item(r[i]) }
98
+ computed_width = cells_in_column.map(&:length).max
99
+ final_width = computed_width > max_column_width ? max_column_width : computed_width
100
+ computed_widths << final_width
101
+ end
102
+ end
103
+ end
104
+
105
+ def build_separator
106
+ '+' << @widths.map {|w| '-' * (w + (cell_padding * 2))}.join('+') << '+'
107
+ end
108
+
109
+ def build_cells(items)
110
+ cells = []
111
+ items.each_with_index do |item, i|
112
+ cells << cast_item(item).ljust(@widths[i])
113
+ end
114
+ "| #{cells.join(' | ')} |"
115
+ end
116
+
117
+ def cast_item(item)
118
+ case item
119
+ when NilClass then 'NULL'
120
+ when Float then item.to_s.to(9)
121
+ else item.to_s.truncate(max_column_width)
122
+ end
123
+ end
124
+ end
125
+
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,36 @@
1
+ # This file contains extensions, overrides, and monkey patches to core parts
2
+ # of active record to allow SQL Server work properly.
3
+ #
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ module MSSQL
7
+ module AttributeMethods
8
+
9
+ private
10
+
11
+ # Overrides the original attributes_for_update merthod to reject
12
+ # primary keys because SQL Server does not allow updates
13
+ # of identity columns.
14
+ # NOTE: rails 4.1 used to reject primary keys but later changes broke
15
+ # this behaviour, even the current comments for that method says that
16
+ # it rejects primary key but it doesn't (maybe a rails bug?)
17
+ def attributes_for_update(attribute_names)
18
+ attribute_names.reject do |name|
19
+ # It seems is only required to check if column in identity or not.
20
+ # This allows to update rails custom primary keys
21
+ next true if readonly_attribute?(name)
22
+
23
+ column = self.class.columns_hash[name]
24
+ column && column.identity?
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ module ActiveRecord
33
+ class Base
34
+ include ActiveRecord::ConnectionAdapters::MSSQL::AttributeMethods
35
+ end
36
+ end
@@ -0,0 +1,231 @@
1
+ module ArJdbc
2
+ module MSSQL
3
+ module LimitHelpers
4
+
5
+ # @private
6
+ FIND_SELECT = /\b(SELECT(\s+DISTINCT)?)\b(.*)/mi
7
+ # @private
8
+ FIND_AGGREGATE_FUNCTION = /(AVG|COUNT|COUNT_BIG|MAX|MIN|SUM|STDDEV|STDEVP|VAR|VARP)\(/i
9
+
10
+ # @private
11
+ module SqlServerReplaceLimitOffset
12
+
13
+ GROUP_BY = 'GROUP BY'
14
+ ORDER_BY = 'ORDER BY'
15
+
16
+ module_function
17
+
18
+ def replace_limit_offset!(sql, limit, offset, order)
19
+ offset ||= 0
20
+
21
+ if match = FIND_SELECT.match(sql)
22
+ select, distinct, rest_of_query = match[1], match[2], match[3]
23
+ rest_of_query.strip!
24
+ end
25
+ rest_of_query[0] = '*' if rest_of_query[0...1] == '1' && rest_of_query !~ /1 AS/i
26
+ if rest_of_query[0...1] == '*'
27
+ from_table = Utils.get_table_name(rest_of_query, true)
28
+ rest_of_query = "#{from_table}.#{rest_of_query}"
29
+ end
30
+
31
+ # Ensure correct queries if the rest_of_query contains a 'GROUP BY'. Otherwise the following error occurs:
32
+ # ActiveRecord::StatementInvalid: ActiveRecord::JDBCError: Column 'users.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
33
+ # SELECT t.* FROM ( SELECT ROW_NUMBER() OVER(ORDER BY users.id) AS _row_num, [users].[lft], COUNT([users].[lft]) FROM [users] GROUP BY [users].[lft] HAVING COUNT([users].[lft]) > 1 ) AS t WHERE t._row_num BETWEEN 1 AND 1
34
+ if i = ( rest_of_query.rindex(GROUP_BY) || rest_of_query.rindex('group by') )
35
+ # Do not catch 'GROUP BY' statements from sub-selects, indicated
36
+ # by more closing than opening brackets after the last group by.
37
+ rest_after_last_group_by = rest_of_query[i..-1]
38
+ opening_brackets_count = rest_after_last_group_by.count('(')
39
+ closing_brackets_count = rest_after_last_group_by.count(')')
40
+
41
+ if opening_brackets_count == closing_brackets_count
42
+ order_start = order.strip[0, 8]; order_start.upcase!
43
+ if order_start == ORDER_BY && order.match(FIND_AGGREGATE_FUNCTION)
44
+ # do nothing
45
+ elsif order.count(',') == 0
46
+ order.gsub!(/ORDER +BY +([^\s]+)(\s+ASC|\s+DESC)?/i, 'ORDER BY MIN(\1)\2')
47
+ else
48
+ raise("can not handle multiple order conditions (#{order.inspect}) in #{sql.inspect}")
49
+ end
50
+ end
51
+ end
52
+
53
+ if distinct # select =~ /DISTINCT/i
54
+ order = order.gsub(/(\[[a-z0-9_]+\]|[a-z0-9_]+)\./, 't.')
55
+ new_sql = "SELECT t.* FROM "
56
+ new_sql << "( SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, t.* FROM (#{select} #{rest_of_query}) AS t ) AS t"
57
+ append_limit_row_num_clause(new_sql, limit, offset)
58
+ else
59
+ select_columns_before_from = rest_of_query.gsub(/FROM.*/, '').strip
60
+ only_one_column = !select_columns_before_from.include?(',')
61
+ only_one_id_column = only_one_column && (select_columns_before_from.ends_with?('.id') || select_columns_before_from.ends_with?('.[id]'))
62
+
63
+ if only_one_id_column
64
+ # If there's only one id column a subquery will be created which only contains this column
65
+ new_sql = "#{select} t.id FROM "
66
+ else
67
+ # All selected columns are used
68
+ new_sql = "#{select} t.* FROM "
69
+ end
70
+ new_sql << "( SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, #{rest_of_query} ) AS t"
71
+ append_limit_row_num_clause(new_sql, limit, offset)
72
+ end
73
+
74
+ sql.replace new_sql
75
+ end
76
+
77
+ def append_limit_row_num_clause(sql, limit, offset)
78
+ if limit
79
+ start_row = offset + 1; end_row = offset + limit.to_i
80
+ sql << " WHERE t._row_num BETWEEN #{start_row} AND #{end_row}"
81
+ else
82
+ sql << " WHERE t._row_num > #{offset}"
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ # @private
89
+ module SqlServer2000ReplaceLimitOffset
90
+
91
+ module_function
92
+
93
+ def replace_limit_offset!(sql, limit, offset, order)
94
+ if limit
95
+ offset ||= 0
96
+ start_row = offset + 1
97
+ end_row = offset + limit.to_i
98
+
99
+ if match = FIND_SELECT.match(sql)
100
+ select, distinct, rest_of_query = match[1], match[2], match[3]
101
+ end
102
+ #need the table name for avoiding amiguity
103
+ table_name = Utils.get_table_name(sql, true)
104
+ primary_key = get_primary_key(order, table_name)
105
+
106
+ #I am not sure this will cover all bases. but all the tests pass
107
+ if order[/ORDER/].nil?
108
+ new_order = "ORDER BY #{order}, [#{table_name}].[#{primary_key}]" if order.index("#{table_name}.#{primary_key}").nil?
109
+ else
110
+ new_order ||= order
111
+ end
112
+
113
+ if (start_row == 1) && (end_row ==1)
114
+ new_sql = "#{select} TOP 1 #{rest_of_query} #{new_order}"
115
+ sql.replace(new_sql)
116
+ else
117
+ # We are in deep trouble here. SQL Server does not have any kind of OFFSET build in.
118
+ # Only remaining solution is adding a where condition to be sure that the ID is not in SELECT TOP OFFSET FROM SAME_QUERY.
119
+ # To do so we need to extract each part of the query to insert our additional condition in the right place.
120
+ query_without_select = rest_of_query[/FROM/i=~ rest_of_query.. -1]
121
+ additional_condition = "#{table_name}.#{primary_key} NOT IN (#{select} TOP #{offset} #{table_name}.#{primary_key} #{query_without_select} #{new_order})"
122
+
123
+ # Extract the different parts of the query
124
+ having, group_by, where, from, selection = split_sql(rest_of_query, /having/i, /group by/i, /where/i, /from/i)
125
+
126
+ # Update the where part to add our additional condition
127
+ if where.blank?
128
+ where = "WHERE #{additional_condition}"
129
+ else
130
+ where = "#{where} AND #{additional_condition}"
131
+ end
132
+
133
+ # Replace the query to be our new customized query
134
+ sql.replace("#{select} TOP #{limit} #{selection} #{from} #{where} #{group_by} #{having} #{new_order}")
135
+ end
136
+ end
137
+ sql
138
+ end
139
+
140
+ # Split the rest_of_query into chunks based on regexs (applied from end of string to the beginning)
141
+ # The result is an array of regexs.size+1 elements (the last one being the remaining once everything was chopped away)
142
+ def split_sql(rest_of_query, *regexs)
143
+ results = Array.new
144
+
145
+ regexs.each do |regex|
146
+ if position = (regex =~ rest_of_query)
147
+ # Extract the matched string and chop the rest_of_query
148
+ matched = rest_of_query[position..-1]
149
+ rest_of_query = rest_of_query[0...position]
150
+ else
151
+ matched = nil
152
+ end
153
+
154
+ results << matched
155
+ end
156
+ results << rest_of_query
157
+
158
+ results
159
+ end
160
+
161
+ def get_primary_key(order, table_name) # table_name might be quoted
162
+ if order =~ /(\w*id\w*)/i
163
+ $1
164
+ else
165
+ unquoted_name = Utils.unquote_table_name(table_name)
166
+ model = descendants.find { |m| m.table_name == table_name || m.table_name == unquoted_name }
167
+ model ? model.primary_key : 'id'
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ if ActiveRecord::VERSION::MAJOR >= 3
174
+ def descendants; ::ActiveRecord::Base.descendants; end
175
+ else
176
+ def descendants; ::ActiveRecord::Base.send(:subclasses) end
177
+ end
178
+
179
+ end
180
+
181
+ private
182
+
183
+ if ::ActiveRecord::VERSION::MAJOR < 3
184
+
185
+ def setup_limit_offset!(version = nil)
186
+ if version.to_s == '2000' || sqlserver_2000?
187
+ extend SqlServer2000AddLimitOffset
188
+ else
189
+ extend SqlServerAddLimitOffset
190
+ end
191
+ end
192
+
193
+ else
194
+
195
+ def setup_limit_offset!(version = nil); end
196
+
197
+ end
198
+
199
+ # @private
200
+ module SqlServerAddLimitOffset
201
+
202
+ # @note Only needed with (non-AREL) ActiveRecord **2.3**.
203
+ # @see Arel::Visitors::SQLServer
204
+ def add_limit_offset!(sql, options)
205
+ if options[:limit]
206
+ order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
207
+ sql.sub!(/ ORDER BY.*$/i, '')
208
+ SqlServerReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
209
+ end
210
+ end
211
+
212
+ end if ::ActiveRecord::VERSION::MAJOR < 3
213
+
214
+ # @private
215
+ module SqlServer2000AddLimitOffset
216
+
217
+ # @note Only needed with (non-AREL) ActiveRecord **2.3**.
218
+ # @see Arel::Visitors::SQLServer
219
+ def add_limit_offset!(sql, options)
220
+ if options[:limit]
221
+ order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
222
+ sql.sub!(/ ORDER BY.*$/i, '')
223
+ SqlServer2000ReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
224
+ end
225
+ end
226
+
227
+ end if ::ActiveRecord::VERSION::MAJOR < 3
228
+
229
+ end
230
+ end
231
+ end