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,62 +1,12 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            ArJdbc::ConnectionMethods.module_eval do
         | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
              # uses the (open-source) jTDS driver.
         | 
| 7 | 
            -
              # If you'd like to use the "official" MS's SQL-JDBC driver, it's preferable
         | 
| 8 | 
            -
              # to use the {#sqlserver_connection} method (set `adapter: sqlserver`).
         | 
| 9 | 
            -
              def mssql_connection(config)
         | 
| 10 | 
            -
                # NOTE: this detection ain't perfect and is only meant as a temporary hack
         | 
| 11 | 
            -
                # users will get a deprecation eventually to use `adapter: sqlserver` ...
         | 
| 12 | 
            -
                if config[:driver] =~ /SQLServerDriver$/ || config[:url] =~ /^jdbc:sqlserver:/
         | 
| 13 | 
            -
                  return sqlserver_connection(config)
         | 
| 14 | 
            -
                end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                config = config.deep_dup
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                config[:adapter_spec] ||= ::ArJdbc::MSSQL
         | 
| 19 | 
            -
                config[:adapter_class] = ActiveRecord::ConnectionAdapters::MSSQLAdapter unless config.key?(:adapter_class)
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                return jndi_connection(config) if jndi_config?(config)
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                begin
         | 
| 24 | 
            -
                  require 'jdbc/jtds'
         | 
| 25 | 
            -
                  # NOTE: the adapter has only support for working with the
         | 
| 26 | 
            -
                  # open-source jTDS driver (won't work with MS's driver) !
         | 
| 27 | 
            -
                  ::Jdbc::JTDS.load_driver(:require) if defined?(::Jdbc::JTDS.load_driver)
         | 
| 28 | 
            -
                rescue LoadError => e # assuming driver.jar is on the class-path
         | 
| 29 | 
            -
                  raise e unless e.message.to_s.index('no such file to load')
         | 
| 30 | 
            -
                end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                config[:host] ||= 'localhost'
         | 
| 33 | 
            -
                config[:port] ||= 1433
         | 
| 34 | 
            -
                config[:driver] ||= defined?(::Jdbc::JTDS.driver_name) ? ::Jdbc::JTDS.driver_name : 'net.sourceforge.jtds.jdbc.Driver'
         | 
| 35 | 
            -
                config[:connection_alive_sql] ||= 'SELECT 1'
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                config[:url] ||= begin
         | 
| 38 | 
            -
                  url = ''.dup
         | 
| 39 | 
            -
                  url << "jdbc:jtds:sqlserver://#{config[:host]}:#{config[:port]}/#{config[:database]}"
         | 
| 40 | 
            -
                  # Instance is often a preferrable alternative to port when dynamic ports are used.
         | 
| 41 | 
            -
                  # If instance is specified then port is essentially ignored.
         | 
| 42 | 
            -
                  url << ";instance=#{config[:instance]}" if config[:instance]
         | 
| 43 | 
            -
                  # This will enable windows domain-based authentication and will require the JTDS native libraries be available.
         | 
| 44 | 
            -
                  url << ";domain=#{config[:domain]}" if config[:domain]
         | 
| 45 | 
            -
                  # AppName is shown in sql server as additional information against the connection.
         | 
| 46 | 
            -
                  url << ";appname=#{config[:appname]}" if config[:appname]
         | 
| 47 | 
            -
                  url
         | 
| 48 | 
            -
                end
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                unless config[:domain]
         | 
| 51 | 
            -
                  config[:username] ||= 'sa'
         | 
| 52 | 
            -
                  config[:password] ||= ''
         | 
| 53 | 
            -
                end
         | 
| 54 | 
            -
                jdbc_connection(config)
         | 
| 4 | 
            +
              def mssql_adapter_class
         | 
| 5 | 
            +
                ConnectionAdapters::MSSQLAdapter
         | 
| 55 6 | 
             
              end
         | 
| 56 | 
            -
              alias_method :jdbcmssql_connection, :mssql_connection
         | 
| 57 7 |  | 
| 58 | 
            -
              #  | 
| 59 | 
            -
              def  | 
| 8 | 
            +
              # NOTE: Assumes SQLServer SQL-JDBC driver on the class-path.
         | 
| 9 | 
            +
              def mssql_connection(config)
         | 
| 60 10 | 
             
                config = config.deep_dup
         | 
| 61 11 |  | 
| 62 12 | 
             
                config[:adapter_spec] ||= ::ArJdbc::MSSQL
         | 
| @@ -89,6 +39,8 @@ ArJdbc::ConnectionMethods.module_eval do | |
| 89 39 |  | 
| 90 40 | 
             
                jdbc_connection(config)
         | 
| 91 41 | 
             
              end
         | 
| 92 | 
            -
              alias_method :jdbcsqlserver_connection, :sqlserver_connection
         | 
| 93 42 |  | 
| 43 | 
            +
              alias_method :jdbcmssql_connection, :mssql_connection
         | 
| 44 | 
            +
              alias_method :sqlserver_connection, :mssql_connection
         | 
| 45 | 
            +
              alias_method :jdbcsqlserver_connection, :mssql_connection
         | 
| 94 46 | 
             
            end
         | 
| @@ -4,23 +4,22 @@ module ActiveRecord | |
| 4 4 | 
             
              module ConnectionAdapters
         | 
| 5 5 | 
             
                module MSSQL
         | 
| 6 6 | 
             
                  module DatabaseStatements
         | 
| 7 | 
            -
             | 
| 8 7 | 
             
                    def exec_proc(proc_name, *variables)
         | 
| 9 | 
            -
                      vars =
         | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 8 | 
            +
                      vars = 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 | 
            +
             | 
| 15 14 | 
             
                      sql = "EXEC #{proc_name} #{vars}".strip
         | 
| 16 15 | 
             
                      log(sql, 'Execute Procedure') do
         | 
| 17 | 
            -
                        result =  | 
| 18 | 
            -
             | 
| 16 | 
            +
                        result = internal_execute(sql)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        result.map do |row|
         | 
| 19 19 | 
             
                          row = row.is_a?(Hash) ? row.with_indifferent_access : row
         | 
| 20 20 | 
             
                          yield(row) if block_given?
         | 
| 21 21 | 
             
                          row
         | 
| 22 22 | 
             
                        end
         | 
| 23 | 
            -
                        result
         | 
| 24 23 | 
             
                      end
         | 
| 25 24 | 
             
                    end
         | 
| 26 25 | 
             
                    alias_method :execute_procedure, :exec_proc # AR-SQLServer-Adapter naming
         | 
| @@ -34,47 +33,22 @@ module ActiveRecord | |
| 34 33 | 
             
                      !READ_QUERY.match?(sql)
         | 
| 35 34 | 
             
                    end
         | 
| 36 35 |  | 
| 37 | 
            -
                     | 
| 38 | 
            -
             | 
| 39 | 
            -
                      if insert_sql?(sql)
         | 
| 40 | 
            -
                        table_name_for_identity_insert = identity_insert_table_name(sql)
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                        if table_name_for_identity_insert
         | 
| 43 | 
            -
                          with_identity_insert_enabled(table_name_for_identity_insert) do
         | 
| 44 | 
            -
                            super
         | 
| 45 | 
            -
                          end
         | 
| 46 | 
            -
                        else
         | 
| 47 | 
            -
                          super
         | 
| 48 | 
            -
                        end
         | 
| 49 | 
            -
                      else
         | 
| 50 | 
            -
                        super
         | 
| 51 | 
            -
                      end
         | 
| 52 | 
            -
                    end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                    def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
         | 
| 55 | 
            -
                      table_name_for_identity_insert = identity_insert_table_name(sql)
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                      if table_name_for_identity_insert
         | 
| 58 | 
            -
                        with_identity_insert_enabled(table_name_for_identity_insert) do
         | 
| 59 | 
            -
                          super
         | 
| 60 | 
            -
                        end
         | 
| 61 | 
            -
                      else
         | 
| 62 | 
            -
                        super
         | 
| 63 | 
            -
                      end
         | 
| 64 | 
            -
                    end
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                    # Not a rails method, own method to test different isolation
         | 
| 67 | 
            -
                    # levels supported by the mssql adapter.
         | 
| 36 | 
            +
                    # Internal method to test different isolation levels supported by this
         | 
| 37 | 
            +
                    # mssql adapter. NOTE: not a active record method
         | 
| 68 38 | 
             
                    def supports_transaction_isolation_level?(level)
         | 
| 69 | 
            -
                       | 
| 39 | 
            +
                      raw_jdbc_connection.supports_transaction_isolation?(level)
         | 
| 70 40 | 
             
                    end
         | 
| 71 41 |  | 
| 42 | 
            +
                    # Internal method to test different isolation levels supported by this
         | 
| 43 | 
            +
                    # mssql adapter. Not a active record method
         | 
| 72 44 | 
             
                    def transaction_isolation=(value)
         | 
| 73 | 
            -
                       | 
| 45 | 
            +
                      raw_jdbc_connection.set_transaction_isolation(value)
         | 
| 74 46 | 
             
                    end
         | 
| 75 47 |  | 
| 48 | 
            +
                    # Internal method to test different isolation levels supported by this
         | 
| 49 | 
            +
                    # mssql adapter. Not a active record method
         | 
| 76 50 | 
             
                    def transaction_isolation
         | 
| 77 | 
            -
                       | 
| 51 | 
            +
                      raw_jdbc_connection.get_transaction_isolation
         | 
| 78 52 | 
             
                    end
         | 
| 79 53 |  | 
| 80 54 | 
             
                    def insert_fixtures_set(fixture_set, tables_to_delete = [])
         | 
| @@ -116,8 +90,163 @@ module ActiveRecord | |
| 116 90 | 
             
                      end
         | 
| 117 91 | 
             
                    end
         | 
| 118 92 |  | 
| 93 | 
            +
                    def internal_exec_query(sql, name = 'SQL', binds = [], prepare: false, async: false)
         | 
| 94 | 
            +
                      sql = transform_query(sql)
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                      check_if_write_query(sql)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                      mark_transaction_written_if_write(sql)
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                      # binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                      # puts "internal----->sql: #{sql}, binds: #{binds}"
         | 
| 103 | 
            +
                      if without_prepared_statement?(binds)
         | 
| 104 | 
            +
                        log(sql, name) do
         | 
| 105 | 
            +
                          with_raw_connection do |conn|
         | 
| 106 | 
            +
                            result = conditional_indentity_insert(sql) do
         | 
| 107 | 
            +
                              conn.execute_query(sql)
         | 
| 108 | 
            +
                            end
         | 
| 109 | 
            +
                            verified!
         | 
| 110 | 
            +
                            result
         | 
| 111 | 
            +
                          end
         | 
| 112 | 
            +
                        end
         | 
| 113 | 
            +
                      else
         | 
| 114 | 
            +
                        log(sql, name, binds) do
         | 
| 115 | 
            +
                          with_raw_connection do |conn|
         | 
| 116 | 
            +
                            # this is different from normal AR that always caches
         | 
| 117 | 
            +
                            cached_statement = fetch_cached_statement(sql) if prepare && @jdbc_statement_cache_enabled
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                            result = conditional_indentity_insert(sql) do
         | 
| 120 | 
            +
                              conn.execute_prepared_query(sql, binds, cached_statement)
         | 
| 121 | 
            +
                            end
         | 
| 122 | 
            +
                            verified!
         | 
| 123 | 
            +
                            result
         | 
| 124 | 
            +
                          end
         | 
| 125 | 
            +
                        end
         | 
| 126 | 
            +
                      end
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                    def exec_update(sql, name = nil, binds = [])
         | 
| 130 | 
            +
                      sql = transform_query(sql)
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                      check_if_write_query(sql)
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                      mark_transaction_written_if_write(sql)
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                      # puts "exec_update----->sql: #{sql}, binds: #{binds}"
         | 
| 137 | 
            +
                      if without_prepared_statement?(binds)
         | 
| 138 | 
            +
                        log(sql, name) do
         | 
| 139 | 
            +
                          with_raw_connection do |conn|
         | 
| 140 | 
            +
                            result = conn.execute_update(sql)
         | 
| 141 | 
            +
                            verified!
         | 
| 142 | 
            +
                            result
         | 
| 143 | 
            +
                          end
         | 
| 144 | 
            +
                        end
         | 
| 145 | 
            +
                      else
         | 
| 146 | 
            +
                        log(sql, name, binds) do
         | 
| 147 | 
            +
                          with_raw_connection do |conn|
         | 
| 148 | 
            +
                            result = conn.execute_prepared_update(sql, binds)
         | 
| 149 | 
            +
                            verified!
         | 
| 150 | 
            +
                            result
         | 
| 151 | 
            +
                          end
         | 
| 152 | 
            +
                        end
         | 
| 153 | 
            +
                      end
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
                    alias :exec_delete :exec_update
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
         | 
| 158 | 
            +
                      sql = transform_query(sql)
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                      check_if_write_query(sql)
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                      mark_transaction_written_if_write(sql)
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                      # puts "exec_insert----->sql: #{sql}, binds: #{binds}"
         | 
| 165 | 
            +
                      if without_prepared_statement?(binds)
         | 
| 166 | 
            +
                        log(sql, name) do
         | 
| 167 | 
            +
                          with_raw_connection do |conn|
         | 
| 168 | 
            +
                            result = conditional_indentity_insert(sql) do
         | 
| 169 | 
            +
                              conn.execute_insert_pk(sql, pk)
         | 
| 170 | 
            +
                            end
         | 
| 171 | 
            +
                            verified!
         | 
| 172 | 
            +
                            result
         | 
| 173 | 
            +
                          end
         | 
| 174 | 
            +
                        end
         | 
| 175 | 
            +
                      else
         | 
| 176 | 
            +
                        log(sql, name, binds) do
         | 
| 177 | 
            +
                          with_raw_connection do |conn|
         | 
| 178 | 
            +
                            result = conditional_indentity_insert(sql) do
         | 
| 179 | 
            +
                              conn.execute_insert_pk(sql, binds, pk)
         | 
| 180 | 
            +
                            end
         | 
| 181 | 
            +
                            verified!
         | 
| 182 | 
            +
                            result
         | 
| 183 | 
            +
                          end
         | 
| 184 | 
            +
                        end
         | 
| 185 | 
            +
                      end
         | 
| 186 | 
            +
                    end
         | 
| 187 | 
            +
             | 
| 119 188 | 
             
                    private
         | 
| 120 189 |  | 
| 190 | 
            +
                    def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
         | 
| 191 | 
            +
                      # puts "raw_execute----->sql: #{sql}"
         | 
| 192 | 
            +
                      log(sql, name, async: async) do
         | 
| 193 | 
            +
                        with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
         | 
| 194 | 
            +
                          result = conditional_indentity_insert(sql) { conn.execute(sql) }
         | 
| 195 | 
            +
                          verified!
         | 
| 196 | 
            +
                          result
         | 
| 197 | 
            +
                        end
         | 
| 198 | 
            +
                      end
         | 
| 199 | 
            +
                    end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                    def sql_for_insert(sql, pk, binds, returning) # :nodoc:
         | 
| 202 | 
            +
                      return [sql, binds]
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                      # TODO: Add/Implement and support for insert returning values when
         | 
| 205 | 
            +
                      # upgrading to rails 7.2
         | 
| 206 | 
            +
                      return [sql, binds] unless supports_insert_returning?
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                      if pk.nil?
         | 
| 209 | 
            +
                        # Extract the table from the insert sql. Yuck.
         | 
| 210 | 
            +
                        table_name = identity_insert_table_name(sql)
         | 
| 211 | 
            +
                        pk = primary_key(table_name) if table_name
         | 
| 212 | 
            +
                      end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                      returning_columns = returning || Array(pk)
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                      returning_columns_stmt = returning_columns.map do |column|
         | 
| 217 | 
            +
                        "INSERTED.#{quote_column_name(column)}"
         | 
| 218 | 
            +
                      end.join(', ')
         | 
| 219 | 
            +
             | 
| 220 | 
            +
             | 
| 221 | 
            +
                      return [sql, binds] unless returning_columns.any?
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                      index = sql.index(/VALUES\s\(\?/) || sql.index(/DEFAULT VALUES/)
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                      insert_into = sql[0..(index - 1)].strip
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                      values_list = sql[index..]
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                      sql = "#{insert_into} OUTPUT #{returning_columns_stmt} #{values_list}"
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                      [sql, binds]
         | 
| 232 | 
            +
                    end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                    def conditional_indentity_insert(sql, &block)
         | 
| 235 | 
            +
                      table_name_for_identity_insert = identity_insert_table_name(sql)
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                      if table_name_for_identity_insert
         | 
| 238 | 
            +
                        with_identity_insert_enabled(table_name_for_identity_insert) do
         | 
| 239 | 
            +
                          block.call
         | 
| 240 | 
            +
                        end
         | 
| 241 | 
            +
                      else
         | 
| 242 | 
            +
                        block.call
         | 
| 243 | 
            +
                      end
         | 
| 244 | 
            +
                    end
         | 
| 245 | 
            +
             | 
| 246 | 
            +
                    def raw_jdbc_connection
         | 
| 247 | 
            +
                      @raw_connection
         | 
| 248 | 
            +
                    end
         | 
| 249 | 
            +
             | 
| 121 250 | 
             
                    # It seems the truncate_tables is mostly used for testing
         | 
| 122 251 | 
             
                    # this a workaround to the fact that SQL Server truncate tables
         | 
| 123 252 | 
             
                    # referenced by a foreign key, it may not be required to reset
         | 
| @@ -151,24 +280,21 @@ module ActiveRecord | |
| 151 280 | 
             
                      end
         | 
| 152 281 | 
             
                    end
         | 
| 153 282 |  | 
| 154 | 
            -
                    def insert_sql?(sql)
         | 
| 155 | 
            -
                      !(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil? 
         | 
| 156 | 
            -
                    end
         | 
| 157 | 
            -
             | 
| 158 283 | 
             
                    def identity_insert_table_name(sql)
         | 
| 159 | 
            -
                       | 
| 284 | 
            +
                      return unless ArJdbc::MSSQL::Utils.insert_sql?(sql)
         | 
| 285 | 
            +
             | 
| 286 | 
            +
                      table_name = ArJdbc::MSSQL::Utils.get_table_name(sql)
         | 
| 287 | 
            +
             | 
| 160 288 | 
             
                      id_column = identity_column_name(table_name)
         | 
| 289 | 
            +
             | 
| 161 290 | 
             
                      if id_column && sql.strip =~ /INSERT INTO [^ ]+ ?\((.+?)\)/i
         | 
| 162 | 
            -
                        insert_columns = $1.split(/, */).map{|w| ArJdbc::MSSQL::Utils.unquote_column_name(w)}
         | 
| 291 | 
            +
                        insert_columns = $1.split(/, */).map { |w| ArJdbc::MSSQL::Utils.unquote_column_name(w) }
         | 
| 163 292 | 
             
                        return table_name if insert_columns.include?(id_column)
         | 
| 164 293 | 
             
                      end
         | 
| 165 294 | 
             
                    end
         | 
| 166 295 |  | 
| 167 296 | 
             
                    def identity_column_name(table_name)
         | 
| 168 | 
            -
                       | 
| 169 | 
            -
                        return column.name if column.identity?
         | 
| 170 | 
            -
                      end
         | 
| 171 | 
            -
                      nil
         | 
| 297 | 
            +
                      schema_cache.columns(table_name).find(&:identity?)&.name
         | 
| 172 298 | 
             
                    end
         | 
| 173 299 |  | 
| 174 300 | 
             
                    # Turns IDENTITY_INSERT ON for table during execution of the block
         | 
| @@ -183,29 +309,14 @@ module ActiveRecord | |
| 183 309 |  | 
| 184 310 | 
             
                    def set_identity_insert(table_name, enable = true)
         | 
| 185 311 | 
             
                      if enable
         | 
| 186 | 
            -
                         | 
| 312 | 
            +
                        internal_execute("SET IDENTITY_INSERT #{quote_table_name(table_name)} ON")
         | 
| 187 313 | 
             
                      else
         | 
| 188 | 
            -
                         | 
| 314 | 
            +
                        internal_execute("SET IDENTITY_INSERT #{quote_table_name(table_name)} OFF")
         | 
| 189 315 | 
             
                      end
         | 
| 190 316 | 
             
                    rescue Exception => e
         | 
| 191 317 | 
             
                      raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned" +
         | 
| 192 318 | 
             
                            " #{enable ? 'ON' : 'OFF'} for table #{table_name} due : #{e.inspect}"
         | 
| 193 319 | 
             
                    end
         | 
| 194 | 
            -
             | 
| 195 | 
            -
                    def get_table_name(sql, qualified = nil)
         | 
| 196 | 
            -
                      if sql =~ TABLE_NAME_INSERT_UPDATE
         | 
| 197 | 
            -
                        tn = $2 || $3
         | 
| 198 | 
            -
                        qualified ? tn : ArJdbc::MSSQL::Utils.unqualify_table_name(tn)
         | 
| 199 | 
            -
                      elsif sql =~ TABLE_NAME_FROM
         | 
| 200 | 
            -
                        qualified ? $1 : ArJdbc::MSSQL::Utils.unqualify_table_name($1)
         | 
| 201 | 
            -
                      else
         | 
| 202 | 
            -
                        nil
         | 
| 203 | 
            -
                      end
         | 
| 204 | 
            -
                    end
         | 
| 205 | 
            -
             | 
| 206 | 
            -
                    TABLE_NAME_INSERT_UPDATE = /^\s*(INSERT|EXEC sp_executesql N'INSERT)(?:\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
         | 
| 207 | 
            -
             | 
| 208 | 
            -
                    TABLE_NAME_FROM = /\bFROM\s+([^\(\)\s,]+)\s*/i
         | 
| 209 320 | 
             
                  end
         | 
| 210 321 | 
             
                end
         | 
| 211 322 | 
             
              end
         | 
| @@ -17,7 +17,7 @@ module ActiveRecord | |
| 17 17 | 
             
                      !DISABLED
         | 
| 18 18 | 
             
                    end
         | 
| 19 19 |  | 
| 20 | 
            -
                    def explain(arel, binds = [])
         | 
| 20 | 
            +
                    def explain(arel, binds = [], options = [])
         | 
| 21 21 | 
             
                      return if DISABLED
         | 
| 22 22 |  | 
| 23 23 | 
             
                      if arel.respond_to?(:to_sql)
         | 
| @@ -25,12 +25,15 @@ module ActiveRecord | |
| 25 25 | 
             
                      else
         | 
| 26 26 | 
             
                        raw_sql, raw_binds = arel, binds
         | 
| 27 27 | 
             
                      end
         | 
| 28 | 
            +
             | 
| 28 29 | 
             
                      # sql = to_sql(arel, binds)
         | 
| 29 30 | 
             
                      # result = with_showplan_on { exec_query(sql, 'EXPLAIN', binds) }
         | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 31 | 
            +
             | 
| 32 | 
            +
                      sql = interpolate_sql_statement(raw_sql, raw_binds)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      result = with_showplan_on do
         | 
| 35 | 
            +
                        exec_query(sql, 'EXPLAIN', [])
         | 
| 36 | 
            +
                      end
         | 
| 34 37 | 
             
                      PrinterTable.new(result).pp
         | 
| 35 38 | 
             
                    end
         | 
| 36 39 |  | 
| @@ -28,7 +28,7 @@ module ActiveRecord | |
| 28 28 | 
             
                          statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
         | 
| 29 29 | 
             
                        end
         | 
| 30 30 |  | 
| 31 | 
            -
                        if  | 
| 31 | 
            +
                        if use_foreign_keys?
         | 
| 32 32 | 
             
                          statements.concat(o.foreign_keys.map { |fk| accept fk })
         | 
| 33 33 | 
             
                        end
         | 
| 34 34 |  | 
| @@ -70,6 +70,10 @@ module ActiveRecord | |
| 70 70 | 
             
                    def uuid(*args, **options)
         | 
| 71 71 | 
             
                      args.each { |name| column(name, :uniqueidentifier, **options) }
         | 
| 72 72 | 
             
                    end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    def json(*names, **options)
         | 
| 75 | 
            +
                      names.each { |name| column(name, :text, **options) }
         | 
| 76 | 
            +
                    end
         | 
| 73 77 | 
             
                  end
         | 
| 74 78 |  | 
| 75 79 | 
             
                  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
         | 
| @@ -106,6 +110,12 @@ module ActiveRecord | |
| 106 110 |  | 
| 107 111 | 
             
                      super
         | 
| 108 112 | 
             
                    end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    private
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    def valid_column_definition_options
         | 
| 117 | 
            +
                      super + [:is_identity]
         | 
| 118 | 
            +
                    end
         | 
| 109 119 | 
             
                  end
         | 
| 110 120 |  | 
| 111 121 | 
             
                  class Table < ActiveRecord::ConnectionAdapters::Table
         | 
| @@ -18,6 +18,7 @@ module ActiveRecord | |
| 18 18 | 
             
                      string:         { name: 'nvarchar', limit: 4000 },
         | 
| 19 19 | 
             
                      text:           { name: 'nvarchar(max)' },
         | 
| 20 20 | 
             
                      binary:         { name: 'varbinary(max)' },
         | 
| 21 | 
            +
                      json:           { name: 'nvarchar(max)' },
         | 
| 21 22 | 
             
                      # Other types or SQL Server specific
         | 
| 22 23 | 
             
                      bigint:         { name: 'bigint' },
         | 
| 23 24 | 
             
                      smalldatetime:  { name: 'smalldatetime' },
         | 
| @@ -76,11 +77,11 @@ module ActiveRecord | |
| 76 77 | 
             
                    end
         | 
| 77 78 |  | 
| 78 79 | 
             
                    def primary_keys(table_name)
         | 
| 79 | 
            -
                       | 
| 80 | 
            +
                      valid_raw_connection.primary_keys(table_name)
         | 
| 80 81 | 
             
                    end
         | 
| 81 82 |  | 
| 82 83 | 
             
                    def foreign_keys(table_name)
         | 
| 83 | 
            -
                       | 
| 84 | 
            +
                      valid_raw_connection.foreign_keys(table_name)
         | 
| 84 85 | 
             
                    end
         | 
| 85 86 |  | 
| 86 87 | 
             
                    def charset
         | 
| @@ -151,16 +152,20 @@ module ActiveRecord | |
| 151 152 | 
             
                      # https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-table-transact-sql?view=sql-server-2017
         | 
| 152 153 | 
             
                      if options[:force] == :cascade
         | 
| 153 154 | 
             
                        execute_procedure(:sp_fkeys, pktable_name: table_name).each do |fkdata|
         | 
| 154 | 
            -
                           | 
| 155 | 
            -
                           | 
| 156 | 
            -
             | 
| 157 | 
            -
                           | 
| 158 | 
            -
                           | 
| 159 | 
            -
             | 
| 155 | 
            +
                          raw_fktable = fkdata['FKTABLE_NAME']
         | 
| 156 | 
            +
                          remove_foreign_key(raw_fktable, name: fkdata['FK_NAME'])
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                          fktable = quote_table_name(fkdata['FKTABLE_NAME'])
         | 
| 159 | 
            +
                          fkcolmn = quote_column_name(fkdata['FKCOLUMN_NAME'])
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                          pktable = quote_table_name(fkdata['PKTABLE_NAME'])
         | 
| 162 | 
            +
                          pkcolmn = quote_column_name(fkdata['PKCOLUMN_NAME'])
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                          execute("DELETE FROM #{fktable} WHERE #{fkcolmn} IN ( SELECT #{pkcolmn} FROM #{pktable} )")
         | 
| 160 165 | 
             
                        end
         | 
| 161 166 | 
             
                      end
         | 
| 162 167 |  | 
| 163 | 
            -
                      if options[:if_exists] &&  | 
| 168 | 
            +
                      if options[:if_exists] && mssql_version.major < '13'
         | 
| 164 169 | 
             
                        # this is for sql server 2012 and 2014
         | 
| 165 170 | 
             
                        execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
         | 
| 166 171 | 
             
                      else
         | 
| @@ -261,7 +266,10 @@ module ActiveRecord | |
| 261 266 | 
             
                        options[:precision] = 7
         | 
| 262 267 | 
             
                      end
         | 
| 263 268 |  | 
| 264 | 
            -
                       | 
| 269 | 
            +
                      # In SQL Server only the first column added should have the `ADD` keyword.
         | 
| 270 | 
            +
                      fragments = add_timestamps_for_alter(table_name, **options)
         | 
| 271 | 
            +
                      fragments[1..].each { |fragment| fragment.sub!('ADD ', '') }
         | 
| 272 | 
            +
                      execute("ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}")
         | 
| 265 273 | 
             
                    end
         | 
| 266 274 |  | 
| 267 275 | 
             
                    def add_column(table_name, column_name, type, **options)
         | 
| @@ -371,7 +379,7 @@ module ActiveRecord | |
| 371 379 | 
             
                      MSSQL::TableDefinition.new(self, name, **options)
         | 
| 372 380 | 
             
                    end
         | 
| 373 381 |  | 
| 374 | 
            -
                    def new_column_from_field(table_name, field)
         | 
| 382 | 
            +
                    def new_column_from_field(table_name, field, definitions)
         | 
| 375 383 | 
             
                      # NOTE: this method is used by the columns method in the abstract Class
         | 
| 376 384 | 
             
                      # to map column_definitions. It would be good if column_definitions is
         | 
| 377 385 | 
             
                      # implemented in ruby
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ActiveRecord
         | 
| 4 | 
            +
              module ConnectionAdapters
         | 
| 5 | 
            +
                module MSSQL
         | 
| 6 | 
            +
                  class Version
         | 
| 7 | 
            +
                    attr_reader :major
         | 
| 8 | 
            +
                    attr_reader :complete
         | 
| 9 | 
            +
                    attr_reader :level
         | 
| 10 | 
            +
                    attr_reader :edition
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    VERSION_YEAR = {
         | 
| 13 | 
            +
                      '8'  => '2000',
         | 
| 14 | 
            +
                      '9'  => '2005',
         | 
| 15 | 
            +
                      '10' => '2008',
         | 
| 16 | 
            +
                      '11' => '2012',
         | 
| 17 | 
            +
                      '12' => '2014',
         | 
| 18 | 
            +
                      '13' => '2016',
         | 
| 19 | 
            +
                      '14' => '2017',
         | 
| 20 | 
            +
                      '15' => '2019',
         | 
| 21 | 
            +
                      '16' => '2022'
         | 
| 22 | 
            +
                    }.freeze
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def initialize(version_array = [])
         | 
| 25 | 
            +
                      @complete, @major, @level, @edition = version_array
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def product_name
         | 
| 29 | 
            +
                      return system_name unless year
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      "#{system_name} #{year}"
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    def system_name
         | 
| 35 | 
            +
                      'Microsoft SQL Server'
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    def year
         | 
| 39 | 
            +
                      VERSION_YEAR[major]
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    def min_year
         | 
| 43 | 
            +
                      VERSION_YEAR[min_major]
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    def min_major
         | 
| 47 | 
            +
                      '13'
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    def support_message
         | 
| 51 | 
            +
                      "This adapter supports #{system_name} >= #{min_year}."
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
    
        data/lib/arjdbc/mssql/utils.rb
    CHANGED
    
    | @@ -27,32 +27,47 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 27 27 | 
             
            module ArJdbc
         | 
| 28 28 | 
             
              module MSSQL
         | 
| 29 29 | 
             
                module Utils
         | 
| 30 | 
            +
                  TABLE_NAME_INSERT_UPDATE = /^\s*(INSERT|EXEC sp_executesql N'INSERT)(?:\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
         | 
| 30 31 |  | 
| 31 | 
            -
                   | 
| 32 | 
            +
                  TABLE_NAME_FROM = /\bFROM\s+([^\(\)\s,]+)\s*/i
         | 
| 32 33 |  | 
| 33 | 
            -
                  def  | 
| 34 | 
            +
                  def self.insert_sql?(sql)
         | 
| 35 | 
            +
                    !(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def self.get_table_name(sql, qualified = nil)
         | 
| 39 | 
            +
                    if sql =~ TABLE_NAME_INSERT_UPDATE
         | 
| 40 | 
            +
                      tn = $2 || $3
         | 
| 41 | 
            +
                      qualified ? tn : unqualify_table_name(tn)
         | 
| 42 | 
            +
                    elsif sql =~ TABLE_NAME_FROM
         | 
| 43 | 
            +
                      qualified ? $1 : unqualify_table_name($1)
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def self.unquote_table_name(table_name)
         | 
| 34 48 | 
             
                    remove_identifier_delimiters(table_name)
         | 
| 35 49 | 
             
                  end
         | 
| 36 50 |  | 
| 37 | 
            -
                  def unquote_column_name(column_name)
         | 
| 51 | 
            +
                  def self.unquote_column_name(column_name)
         | 
| 38 52 | 
             
                    remove_identifier_delimiters(column_name)
         | 
| 39 53 | 
             
                  end
         | 
| 40 54 |  | 
| 41 | 
            -
                  def unquote_string(string)
         | 
| 55 | 
            +
                  def self.unquote_string(string)
         | 
| 42 56 | 
             
                    string.to_s.gsub("''", "'")
         | 
| 43 57 | 
             
                  end
         | 
| 44 58 |  | 
| 45 | 
            -
                  def unqualify_table_name(table_name)
         | 
| 59 | 
            +
                  def self.unqualify_table_name(table_name)
         | 
| 46 60 | 
             
                    return if table_name.blank?
         | 
| 61 | 
            +
             | 
| 47 62 | 
             
                    remove_identifier_delimiters(table_name.to_s.split('.').last)
         | 
| 48 63 | 
             
                  end
         | 
| 49 64 |  | 
| 50 | 
            -
                  def unqualify_table_schema(table_name)
         | 
| 65 | 
            +
                  def self.unqualify_table_schema(table_name)
         | 
| 51 66 | 
             
                    schema_name = table_name.to_s.split('.')[-2]
         | 
| 52 67 | 
             
                    schema_name.nil? ? nil : remove_identifier_delimiters(schema_name)
         | 
| 53 68 | 
             
                  end
         | 
| 54 69 |  | 
| 55 | 
            -
                  def unqualify_db_name(table_name)
         | 
| 70 | 
            +
                  def self.unqualify_db_name(table_name)
         | 
| 56 71 | 
             
                    table_names = table_name.to_s.split('.')
         | 
| 57 72 | 
             
                    table_names.length == 3 ? remove_identifier_delimiters(table_names.first) : nil
         | 
| 58 73 | 
             
                  end
         | 
| @@ -60,10 +75,9 @@ module ArJdbc | |
| 60 75 | 
             
                  # private
         | 
| 61 76 |  | 
| 62 77 | 
             
                  # See "Delimited Identifiers": http://msdn.microsoft.com/en-us/library/ms176027.aspx
         | 
| 63 | 
            -
                  def remove_identifier_delimiters(keyword)
         | 
| 78 | 
            +
                  def self.remove_identifier_delimiters(keyword)
         | 
| 64 79 | 
             
                    keyword.to_s.tr("\]\[\"", '')
         | 
| 65 80 | 
             
                  end
         | 
| 66 | 
            -
             | 
| 67 81 | 
             
                end
         | 
| 68 82 | 
             
              end
         | 
| 69 83 | 
             
            end
         |