From f441f3be728d0afc055599cf70d5d5b94fa6e8c9 Mon Sep 17 00:00:00 2001 From: supermomonga <377137+supermomonga@users.noreply.github.com> Date: Wed, 5 Aug 2026 12:06:45 +0900 Subject: [PATCH 1/2] Fix FTS structure dump round trips --- .../tasks/turso_database_tasks.rb | 1 + test/test_rails_database_tasks.rb | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/active_record/tasks/turso_database_tasks.rb b/lib/active_record/tasks/turso_database_tasks.rb index 5e6f322..e351ff8 100644 --- a/lib/active_record/tasks/turso_database_tasks.rb +++ b/lib/active_record/tasks/turso_database_tasks.rb @@ -65,6 +65,7 @@ def structure_dump(filename, extra_flags) SELECT name FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_%' + AND name NOT LIKE '__turso_internal_%' AND sql IS NOT NULL ORDER BY name SQL diff --git a/test/test_rails_database_tasks.rb b/test/test_rails_database_tasks.rb index 5ebb961..88a1ff4 100644 --- a/test/test_rails_database_tasks.rb +++ b/test/test_rails_database_tasks.rb @@ -66,6 +66,37 @@ def test_structure_dump_and_load_round_trip FileUtils.rm_f(dump) end + def test_structure_dump_and_load_round_trip_with_fts_index + dump = File.join(ActiveRecordTursoTest::TMP, "fts_structure.sql") + + ActiveRecord::Base.establish_connection(ActiveRecordTursoTest.base_config) + connection = ActiveRecord::Base.connection + connection.create_table(:messages) { |table| table.text :content, null: false } + connection.add_fts_index(:messages, :content, tokenizer: :ngram) + ActiveRecord::Base.connection_pool.disconnect! + + task = build_task + task.structure_dump(dump, nil) + + sql = File.read(dump) + assert_match(/CREATE INDEX .* USING fts \(content\)/, sql) + assert_match(/tokenizer = 'ngram'/, sql) + refute_match(/__turso_internal_/, sql) + + task.drop + task.structure_load(dump, nil) + + ActiveRecord::Base.establish_connection(ActiveRecordTursoTest.base_config) + connection = ActiveRecord::Base.connection + connection.execute("INSERT INTO messages (content) VALUES ('日本語の部分一致検索')") + + assert_equal 1, connection.select_value(<<~SQL) + SELECT COUNT(*) FROM messages WHERE fts_match(content, '部分一致') + SQL + ensure + FileUtils.rm_f(dump) + end + def test_charset task = build_task assert_equal "UTF-8", task.charset From 6da26663c3dfd2e66d39a0ae74fac132f2447430 Mon Sep 17 00:00:00 2001 From: supermomonga <377137+supermomonga@users.noreply.github.com> Date: Wed, 5 Aug 2026 12:28:22 +0900 Subject: [PATCH 2/2] Fix pooled connection reuse across threads --- .../turso_adapter/connection_management.rb | 10 ++++++++ lib/turso/ar/connection.rb | 9 +++++++ .../integration/connection_management_test.rb | 25 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/lib/active_record/connection_adapters/turso_adapter/connection_management.rb b/lib/active_record/connection_adapters/turso_adapter/connection_management.rb index c1ed6f6..ab7621b 100644 --- a/lib/active_record/connection_adapters/turso_adapter/connection_management.rb +++ b/lib/active_record/connection_adapters/turso_adapter/connection_management.rb @@ -6,6 +6,7 @@ class TursoAdapter < SQLite3Adapter module ConnectionManagement def self.included(base) base.extend(ClassMethods) + base.set_callback :checkout, :before, :reconnect_for_execution_context end module ClassMethods @@ -80,6 +81,15 @@ def configure_connection query_timeout = @config[:query_timeout] || 30_000 @raw_connection.query_timeout = query_timeout end + + private + + def reconnect_for_execution_context + return unless @raw_connection + return if @raw_connection.owned_by_current_execution_context? + + reconnect! + end end end end diff --git a/lib/turso/ar/connection.rb b/lib/turso/ar/connection.rb index c9d93a4..b069c48 100644 --- a/lib/turso/ar/connection.rb +++ b/lib/turso/ar/connection.rb @@ -16,6 +16,7 @@ class Connection def initialize(config) @config = config + @owner = owner_token db_opts = { busy_timeout: config[:busy_timeout] || config[:timeout] || DEFAULT_BUSY_TIMEOUT_MS, query_timeout: config[:query_timeout] || DEFAULT_QUERY_TIMEOUT_MS @@ -38,6 +39,10 @@ def open? !@db.closed? end + def owned_by_current_execution_context? + @owner == owner_token + end + def disconnect! @db.close unless @db.closed? end @@ -57,6 +62,10 @@ def execute_batch(sql) private + def owner_token + [Thread.current.object_id, Fiber.current.object_id] + end + def normalize_binds(binds) binds.map do |value| case value diff --git a/test/integration/connection_management_test.rb b/test/integration/connection_management_test.rb index 401f5fc..40ce6d5 100644 --- a/test/integration/connection_management_test.rb +++ b/test/integration/connection_management_test.rb @@ -74,6 +74,31 @@ def test_pool_connections_are_independent assert_equal 3, ConnectionRecord.count end + def test_checked_in_connection_can_be_reused_by_another_thread + ActiveRecord::Base.establish_connection(ActiveRecordTursoTest.base_config.merge(pool: 1)) + pool = ActiveRecord::Base.connection_pool + + original_connection = pool.checkout + original_connection.execute("INSERT INTO connection_records (name) VALUES ('main-thread')") + original_adapter_id = original_connection.object_id + original_raw_connection_id = original_connection.raw_connection.object_id + pool.checkin(original_connection) + + result = Thread.new do + pool.with_connection do |connection| + [ + connection.object_id, + connection.raw_connection.object_id, + connection.query_value("SELECT name FROM connection_records") + ] + end + end.value + + assert_equal original_adapter_id, result[0] + refute_equal original_raw_connection_id, result[1] + assert_equal "main-thread", result[2] + end + def test_reconnect_restores_foreign_keys_pragma conn = ActiveRecord::Base.connection conn.reconnect!