From ae265c8f70f654f4ee4c5cc42310b433a3989f2a Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 6 Mar 2025 18:41:43 +0000 Subject: [PATCH 1/7] Fix retrieval of temporary table column information --- .../connection_adapters/sqlserver/schema_statements.rb | 7 ++++++- lib/active_record/connection_adapters/sqlserver/utils.rb | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 5f1e0f6dd..eb4e21f3c 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -631,6 +631,11 @@ def column_type(ci:) end def column_definitions_sql(database, identifier) + # puts "column_definitions_sql: identifier=#{identifier}" + # puts "column_definitions_sql: database=#{database}" + + # database = "TEMPDB" if identifier.temporary_table? + object_name = prepared_statements ? "@0" : quote(identifier.object) schema_name = if identifier.schema.blank? "schema_name()" @@ -691,7 +696,7 @@ def column_definitions_sql(database, identifier) AND k.unique_index_id = ic.index_id AND c.column_id = ic.column_id WHERE - o.name = #{object_name} + o.Object_ID = Object_ID(#{object_name}) AND s.name = #{schema_name} ORDER BY c.column_id diff --git a/lib/active_record/connection_adapters/sqlserver/utils.rb b/lib/active_record/connection_adapters/sqlserver/utils.rb index baf237495..77ec1d734 100644 --- a/lib/active_record/connection_adapters/sqlserver/utils.rb +++ b/lib/active_record/connection_adapters/sqlserver/utils.rb @@ -81,6 +81,10 @@ def hash parts.hash end + def temporary_table? + object.start_with?("#") + end + protected def parse_raw_name From f2ab1396c8cf01148dafa8dc750aa2f90f47c9d8 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Thu, 6 Mar 2025 19:16:01 +0000 Subject: [PATCH 2/7] Fix object ID --- .../sqlserver/database_statements.rb | 3 +-- .../sqlserver/schema_statements.rb | 13 ++++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/active_record/connection_adapters/sqlserver/database_statements.rb b/lib/active_record/connection_adapters/sqlserver/database_statements.rb index d5dc267ea..27180e74a 100644 --- a/lib/active_record/connection_adapters/sqlserver/database_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/database_statements.rb @@ -43,8 +43,7 @@ def cast_result(raw_result) # Returns the affected rows from results. def affected_rows(raw_result) - column_name = lowercase_schema_reflection ? "affectedrows" : "AffectedRows" - raw_result&.first&.fetch(column_name, nil) + raw_result&.first&.fetch(lowercase_schema_reflection_string("AffectedRows"), nil) end # Returns the affected rows from results or handle. diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index eb4e21f3c..ed8370c7c 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -643,6 +643,12 @@ def column_definitions_sql(database, identifier) prepared_statements ? "@1" : quote(identifier.schema) end + object_id_arg = if identifier.schema.present? + "CONCAT(@1,''.'',@0)" + else + "@0" + end + %{ SELECT #{lowercase_schema_reflection_sql("o.name")} AS [table_name], @@ -696,7 +702,8 @@ def column_definitions_sql(database, identifier) AND k.unique_index_id = ic.index_id AND c.column_id = ic.column_id WHERE - o.Object_ID = Object_ID(#{object_name}) + o.name = #{object_name} + /* o.Object_ID = Object_ID(#{object_id_arg}) */ AND s.name = #{schema_name} ORDER BY c.column_id @@ -767,6 +774,10 @@ def lowercase_schema_reflection_sql(node) lowercase_schema_reflection ? "LOWER(#{node})" : node end + def lowercase_schema_reflection_string(str) + lowercase_schema_reflection ? str.downcase : str + end + # === SQLServer Specific (View Reflection) ====================== # def view_table_name(table_name) From 90133dffbbd81f9ac0a826a486801b7ab1cc6cca Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Fri, 7 Mar 2025 11:25:25 +0000 Subject: [PATCH 3/7] Update schema_statements.rb --- .../sqlserver/schema_statements.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index ed8370c7c..b2084c517 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -636,18 +636,19 @@ def column_definitions_sql(database, identifier) # database = "TEMPDB" if identifier.temporary_table? - object_name = prepared_statements ? "@0" : quote(identifier.object) - schema_name = if identifier.schema.blank? - "schema_name()" + schema_name = "schema_name()" + + if prepared_statements + object_name = "@0" + schema_name = "@1" if identifier.schema.present? else - prepared_statements ? "@1" : quote(identifier.schema) + object_name = quote(identifier.object) + schema_name = quote(identifier.schema) if identifier.schema.present? end - object_id_arg = if identifier.schema.present? - "CONCAT(@1,''.'',@0)" - else - "@0" - end + object_id_arg = identifier.schema.present? ? "CONCAT(#{schema_name},'.',#{object_name})" : object_name + + %{ SELECT @@ -702,8 +703,7 @@ def column_definitions_sql(database, identifier) AND k.unique_index_id = ic.index_id AND c.column_id = ic.column_id WHERE - o.name = #{object_name} - /* o.Object_ID = Object_ID(#{object_id_arg}) */ + o.Object_ID = Object_ID(#{object_id_arg}) AND s.name = #{schema_name} ORDER BY c.column_id From bcb11c572dae699ca5b5136800fe298444607334 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Fri, 7 Mar 2025 11:48:22 +0000 Subject: [PATCH 4/7] WIP --- .../sqlserver/schema_statements.rb | 9 +++- .../connection_adapters/sqlserver/utils.rb | 4 ++ test/cases/temporary_table_test_sqlserver.rb | 50 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 test/cases/temporary_table_test_sqlserver.rb diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index b2084c517..ee4632b31 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -634,7 +634,9 @@ def column_definitions_sql(database, identifier) # puts "column_definitions_sql: identifier=#{identifier}" # puts "column_definitions_sql: database=#{database}" - # database = "TEMPDB" if identifier.temporary_table? + + + schema_name = "schema_name()" @@ -648,7 +650,10 @@ def column_definitions_sql(database, identifier) 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 %{ SELECT diff --git a/lib/active_record/connection_adapters/sqlserver/utils.rb b/lib/active_record/connection_adapters/sqlserver/utils.rb index 77ec1d734..8f2380b35 100644 --- a/lib/active_record/connection_adapters/sqlserver/utils.rb +++ b/lib/active_record/connection_adapters/sqlserver/utils.rb @@ -85,6 +85,10 @@ def temporary_table? object.start_with?("#") end + # def temporary_database + # "TEMPDB" + # end + protected def parse_raw_name diff --git a/test/cases/temporary_table_test_sqlserver.rb b/test/cases/temporary_table_test_sqlserver.rb new file mode 100644 index 000000000..496be9f49 --- /dev/null +++ b/test/cases/temporary_table_test_sqlserver.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "cases/helper_sqlserver" + +class TemporaryTableSQLServer < ActiveRecord::TestCase + # setup do + # @connection = ActiveRecord::Base.lease_connection + # @connection.create_table(:barcodes, primary_key: "code", id: :uuid, force: true) + # end + # + # # teardown do + # # @connection.drop_table(:barcodes, if_exists: true) + # # end + # + def test_insert_into_temporary_table + ActiveSupport::Notifications.subscribe('sql.active_record') do |_name, _start, _finish, _id, payload| + puts payload[:sql] + end + + + ActiveRecord::Base.with_connection do |conn| + temp_table = "#temp_users" + # connection.exec_query("IF OBJECT_ID('tempdb..#{temp_table}') IS NOT NULL DROP TABLE #{temp_table}") + + puts "Creating table" + conn.exec_query("CREATE TABLE #{temp_table} (id INT IDENTITY(1,1), name NVARCHAR(100))") + + puts "Selecting table" + result = conn.exec_query("SELECT * FROM #{temp_table}") + puts "Result: #{result.to_a}" + + puts "Inserting into table" + + # ❌ This raises "Table doesn’t exist" error + conn.exec_query("INSERT INTO #{temp_table} (name) VALUES ('John')") + + + + # ✅ Workaround: Only runs if the table still exists in this session + # conn.exec_query("IF OBJECT_ID('tempdb..#{temp_table}') IS NOT NULL INSERT INTO #{temp_table} (name) VALUES ('John')") + + # ✅ Workaround: raw_connection works without issue + # conn.raw_connection.execute("IF OBJECT_ID('tempdb..#{temp_table}') IS NOT NULL INSERT INTO #{temp_table} (name) VALUES ('John')") + + puts "Selecting table again" + result = conn.exec_query("SELECT * FROM #{temp_table}") + puts "Result: #{result.to_a}" + end + end +end From 80cbfd443e4f756368cec2a12c3622df7493dae8 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Fri, 7 Mar 2025 13:58:38 +0000 Subject: [PATCH 5/7] Cleanup --- .../sqlserver/schema_statements.rb | 9 +--- test/cases/temporary_table_test_sqlserver.rb | 43 +++---------------- 2 files changed, 7 insertions(+), 45 deletions(-) diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index ee4632b31..1baca5407 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -631,13 +631,6 @@ def column_type(ci:) end def column_definitions_sql(database, identifier) - # puts "column_definitions_sql: identifier=#{identifier}" - # puts "column_definitions_sql: database=#{database}" - - - - - schema_name = "schema_name()" if prepared_statements @@ -730,7 +723,7 @@ def remove_check_constraints(table_name, column_name) end def remove_default_constraint(table_name, column_name) - # If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case. + # If there are foreign keys in this table, we could still get back a 2D array, so flatten just in case. execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row| row["constraint_type"] == "DEFAULT on column #{column_name}" end.each do |row| diff --git a/test/cases/temporary_table_test_sqlserver.rb b/test/cases/temporary_table_test_sqlserver.rb index 496be9f49..0ab808a70 100644 --- a/test/cases/temporary_table_test_sqlserver.rb +++ b/test/cases/temporary_table_test_sqlserver.rb @@ -3,48 +3,17 @@ require "cases/helper_sqlserver" class TemporaryTableSQLServer < ActiveRecord::TestCase - # setup do - # @connection = ActiveRecord::Base.lease_connection - # @connection.create_table(:barcodes, primary_key: "code", id: :uuid, force: true) - # end - # - # # teardown do - # # @connection.drop_table(:barcodes, if_exists: true) - # # end - # def test_insert_into_temporary_table - ActiveSupport::Notifications.subscribe('sql.active_record') do |_name, _start, _finish, _id, payload| - puts payload[:sql] - end - - ActiveRecord::Base.with_connection do |conn| - temp_table = "#temp_users" - # connection.exec_query("IF OBJECT_ID('tempdb..#{temp_table}') IS NOT NULL DROP TABLE #{temp_table}") - - puts "Creating table" - conn.exec_query("CREATE TABLE #{temp_table} (id INT IDENTITY(1,1), name NVARCHAR(100))") - - puts "Selecting table" - result = conn.exec_query("SELECT * FROM #{temp_table}") - puts "Result: #{result.to_a}" - - puts "Inserting into table" - - # ❌ This raises "Table doesn’t exist" error - conn.exec_query("INSERT INTO #{temp_table} (name) VALUES ('John')") - - + conn.exec_query("CREATE TABLE #temp_users (id INT IDENTITY(1,1), name NVARCHAR(100))") - # ✅ Workaround: Only runs if the table still exists in this session - # conn.exec_query("IF OBJECT_ID('tempdb..#{temp_table}') IS NOT NULL INSERT INTO #{temp_table} (name) VALUES ('John')") + result = conn.exec_query("SELECT * FROM #temp_users") + assert_equal 0, result.count - # ✅ Workaround: raw_connection works without issue - # conn.raw_connection.execute("IF OBJECT_ID('tempdb..#{temp_table}') IS NOT NULL INSERT INTO #{temp_table} (name) VALUES ('John')") + conn.exec_query("INSERT INTO #temp_users (name) VALUES ('John'), ('Doe')") - puts "Selecting table again" - result = conn.exec_query("SELECT * FROM #{temp_table}") - puts "Result: #{result.to_a}" + result = conn.exec_query("SELECT * FROM #temp_users") + assert_equal 2, result.count end end end From 2f0b190449b65fab3124b0e440729a4bcb53ad6b Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Fri, 7 Mar 2025 14:02:19 +0000 Subject: [PATCH 6/7] Removed some changes --- .../connection_adapters/sqlserver/database_statements.rb | 3 ++- .../connection_adapters/sqlserver/schema_statements.rb | 4 ---- lib/active_record/connection_adapters/sqlserver/utils.rb | 4 ---- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/active_record/connection_adapters/sqlserver/database_statements.rb b/lib/active_record/connection_adapters/sqlserver/database_statements.rb index 27180e74a..d5dc267ea 100644 --- a/lib/active_record/connection_adapters/sqlserver/database_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/database_statements.rb @@ -43,7 +43,8 @@ def cast_result(raw_result) # Returns the affected rows from results. def affected_rows(raw_result) - raw_result&.first&.fetch(lowercase_schema_reflection_string("AffectedRows"), nil) + column_name = lowercase_schema_reflection ? "affectedrows" : "AffectedRows" + raw_result&.first&.fetch(column_name, nil) end # Returns the affected rows from results or handle. diff --git a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb index 1baca5407..140cc89e1 100644 --- a/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +++ b/lib/active_record/connection_adapters/sqlserver/schema_statements.rb @@ -772,10 +772,6 @@ def lowercase_schema_reflection_sql(node) lowercase_schema_reflection ? "LOWER(#{node})" : node end - def lowercase_schema_reflection_string(str) - lowercase_schema_reflection ? str.downcase : str - end - # === SQLServer Specific (View Reflection) ====================== # def view_table_name(table_name) diff --git a/lib/active_record/connection_adapters/sqlserver/utils.rb b/lib/active_record/connection_adapters/sqlserver/utils.rb index 8f2380b35..77ec1d734 100644 --- a/lib/active_record/connection_adapters/sqlserver/utils.rb +++ b/lib/active_record/connection_adapters/sqlserver/utils.rb @@ -85,10 +85,6 @@ def temporary_table? object.start_with?("#") end - # def temporary_database - # "TEMPDB" - # end - protected def parse_raw_name From 9858fe367dff24bbee87ec19850348a60dcfa627 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Fri, 7 Mar 2025 14:11:58 +0000 Subject: [PATCH 7/7] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c8b5e367..47847fbb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +#### Fixed + +- [#1308](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1308) Fix retrieval of temporary table's column information. + #### Added - [#1301](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/1301) Add support for INDEX INCLUDE.