diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 984a6268d..6ff557351 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ jobs: test: name: Run test suite runs-on: ubuntu-latest + timeout-minutes: 10 env: COMPOSE_FILE: docker-compose.ci.yml diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 957b5ec9b..9a503424d 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -9,6 +9,6 @@ services: build: context: . dockerfile: Dockerfile.ci - command: wait-for sqlserver:1433 -- bundle exec rake test + command: wait-for sqlserver:1433 -- bundle exec rake test TESTOPTS="-v" depends_on: - "sqlserver" diff --git a/lib/active_record/connection_adapters/sqlserver/database_statements.rb b/lib/active_record/connection_adapters/sqlserver/database_statements.rb index b9f0f078f..d31033abf 100644 --- a/lib/active_record/connection_adapters/sqlserver/database_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/database_statements.rb @@ -14,6 +14,8 @@ def write_query?(sql) # :nodoc: end def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true) + # puts "raw_execute: #{sql}" if $AIDO + log(sql, name, async: async) do |notification_payload| with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn| result = if id_insert_table_name = query_requires_identity_insert?(sql) @@ -312,8 +314,11 @@ def sql_for_insert(sql, pk, binds, returning) # === SQLServer Specific ======================================== # def set_identity_insert(table_name, conn, enable) + # puts "set_identity_insert: #{table_name} #{enable}" + internal_raw_execute("SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}", conn , perform_do: true) rescue Exception + puts "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}" raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}" end @@ -404,12 +409,37 @@ def exclude_output_inserted_id_sql_type(pk, exclude_output_inserted) end def query_requires_identity_insert?(sql) + # puts "query_requires_identity_insert?: #{sql}" if $AIDO + return false unless insert_sql?(sql) raw_table_name = get_raw_table_name(sql) + + # binding.pry + # if $AIDO + # + # puts "xxxx" + # return true + # end + + + + id_column = identity_columns(raw_table_name).first - id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? SQLServer::Utils.extract_identifiers(raw_table_name).quoted : false + + if id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i + + # puts "xxx: raw_table_name=#{raw_table_name}, id_column=#{id_column.name}" if $AIDO + # puts "xxx: quoted=#{SQLServer::Utils.extract_identifiers(raw_table_name).quoted}" if $AIDO + + SQLServer::Utils.extract_identifiers(raw_table_name).quoted + else + false + + end + + # id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? SQLServer::Utils.extract_identifiers(raw_table_name).quoted : false end def insert_sql?(sql) @@ -453,6 +483,8 @@ def finish_statement_handle(handle) # Getting around this by raising an exception ourselves while PR # https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released. def internal_raw_execute(sql, conn, perform_do: false) + # puts "internal_raw_execute: #{sql}" if $AIDO + result = conn.execute(sql).tap do |_result| raise TinyTds::Error, "failed to execute statement" if _result.is_a?(FalseClass) end diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 828dcbff2..6ccf31755 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -571,6 +571,7 @@ def column_definitions(table_name) end def column_definitions_sql(database, identifier) + database = "TEMPDB" if identifier.temporary_table? schema_name = "schema_name()" if prepared_statements @@ -581,12 +582,8 @@ def column_definitions_sql(database, identifier) schema_name = quote(identifier.schema) if identifier.schema.present? end - object_id_arg = identifier.schema.present? ? "CONCAT(#{schema_name},'.',#{object_name})" : object_name - - if identifier.temporary_table? - database = "TEMPDB" - object_id_arg = "CONCAT('#{database}','..',#{object_name})" - end + object_id_arg = identifier.schema.present? ? "CONCAT('.',#{schema_name},'.',#{object_name})" : "CONCAT('..',#{object_name})" + object_id_arg = "CONCAT('#{database}',#{object_id_arg})" %{ SELECT diff --git a/test/cases/temp_test_sqlserver.rb b/test/cases/temp_test_sqlserver.rb new file mode 100644 index 000000000..9664bd934 --- /dev/null +++ b/test/cases/temp_test_sqlserver.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "cases/helper_sqlserver" +require "models/dog" +require "models/other_dog" + +class TempTestSQLServer < ActiveRecord::TestCase + # it "assert true" do + # assert true + # end + + it "insert from one schema to another using raw SQL" do + arunit_connection = Dog.lease_connection + arunit2_connection = OtherDog.lease_connection + + arunit_database = arunit_connection.pool.db_config.database + arunit2_database = arunit2_connection.pool.db_config.database + + sql = <<~SQL + INSERT INTO #{arunit2_database}.dbo.dogs(id) SELECT id FROM #{arunit_database}.dbo.dogs (NOLOCK) + SQL + + #ActiveSupport::Notifications.subscribe('sql.active_record') do |_name, _start, _finish, _id, payload| + #puts payload[:sql] + #end + + OtherDog.destroy_all + + assert Dog.count, 1 + assert OtherDog.count, 0 + + + arunit_connection.transaction do + + # binding.pry + puts "*** isolation level=#{arunit_connection.user_options_isolation_level}" + assert_nothing_raised do + arunit_connection.execute(sql) + end + + end + + assert Dog.count, 1 + assert OtherDog.count, 1 + end +end