diff --git a/README.md b/README.md index a92b7a4..0166316 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,12 @@ The driver bundles Apple Silicon macOS and glibc Linux x86_64/aarch64 libraries. ## Connection modes +Turso Cloud's `turso://` URLs are also accepted, both as `database:`/`url:` +options and directly through `Sequel.connect('turso://...', auth_token: ...)`. +They are normalized to `https://` for the pinned native driver before opening +a connection. The same credential/query/fragment validation applies. A +`turso://` database URL is never treated as a local file path. + ```ruby # Memory: a single pooled connection. db = Sequel.connect(adapter: :libsql) @@ -127,6 +133,6 @@ bundle exec rake remote_local # real native HTTP client, local Hrana server `tmp/` and `private_docs/` are Git-ignored and excluded from gem packaging. `private_docs/CONTINUATION.md` is the local continuation checkpoint to read after agent compaction. -For an explicit test against a **disposable** hosted database, set `LIBSQL_TEST_URL` and `LIBSQL_TEST_AUTH_TOKEN` in your environment, then run `bundle exec rake remote`. It creates randomly named test tables and drops those tables afterward. Hosted authentication, TLS, and embedded replication have not been validated in this workspace. +For an explicit test against a **disposable** hosted database, set `LIBSQL_TEST_URL` and `LIBSQL_TEST_AUTH_TOKEN` in your environment, then run `bundle exec rake remote`. It creates randomly named test tables and drops those tables afterward. Hosted authentication and HTTPS remote operations have been validated with a `turso://` URL; embedded replication remains unverified. See [compatibility and verification](docs/compatibility.md) for the tested scope. See [architecture](docs/architecture.md), [compatibility](docs/compatibility.md), and [the local example](examples/local.rb). diff --git a/docs/compatibility.md b/docs/compatibility.md index 42c0a81..5dde0a0 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -9,12 +9,13 @@ As of 2026-10-31, Apple Silicon macOS, Ruby 4.0.5, `turso_libsql` 0.2.0, bundled - Sequel upstream at `75b425dd1c1f34908426dd70371d14e4afb9c933`: SQLite adapter plus integration run passes (901 tests, six upstream skips). The test sources are unmodified. Some additional groups are never defined for an external adapter: seven SQLite type-conversion cases gated on `adapter_scheme == :sqlite` now have local equivalents. See [the coverage review](testing-review.md). - Real native HTTP execution against the official Hrana v3 Python fixture covers prepared Unicode/blob values, affected counts, inserted IDs, constraints, transactions/savepoints, reconnect, schema metadata, empty results, NULL values, models using prepared statements, and INSERT/UPDATE/DELETE RETURNING. - The fixture wrapper only normalizes padded/unpadded base64 between Python and Rust. SQL and stream handling stay upstream. This is a protocol integration test, not a hosted Turso test. +- Hosted Turso remote mode was verified with a `turso://` URL normalized to HTTPS and a real database token, using Sequel 5.109.0. The warning-enabled `test/remote.rb` suite passed all five tests (24 assertions), covering CRUD, prepared bindings, Unicode/blobs, constraints, transactions/savepoints/rollback, reconnect persistence, schema metadata, models, and returning writes. Only randomly named test tables were created and dropped; credentials were not logged. Exact counts and commands are recorded in the local ignored continuation notes and test logs. ## Not verified -Hosted Turso credentials/TLS, live embedded replication, sync scheduling/read-your-writes behavior against a primary, encryption, Linux native execution, and Ruby 3.3/3.4 execution have not been tested here. The connection descriptor and sync API support replicas; that does not establish end-to-end replication correctness. +Live embedded replication, sync scheduling/read-your-writes behavior against a primary, encryption, Linux native execution, and Ruby 3.3/3.4 execution have not been tested here. The connection descriptor and sync API support replicas; that does not establish end-to-end replication correctness. Hosted remote checks do not cover token expiry/rotation, custom TLS settings, or production load behavior. ## Known limits diff --git a/lib/sequel/adapters/turso.rb b/lib/sequel/adapters/turso.rb new file mode 100644 index 0000000..af2a47d --- /dev/null +++ b/lib/sequel/adapters/turso.rb @@ -0,0 +1,6 @@ +# frozen-string-literal: true + +require_relative 'libsql' + +# Route Sequel.connect('turso://...') through the same adapter and pool. +Sequel.synchronize { Sequel::ADAPTER_MAP[:turso] = Sequel::LibSQL::Database } diff --git a/lib/sequel/libsql/configuration.rb b/lib/sequel/libsql/configuration.rb index ce624e4..27e691e 100644 --- a/lib/sequel/libsql/configuration.rb +++ b/lib/sequel/libsql/configuration.rb @@ -11,6 +11,9 @@ def initialize(db, options) @options = options path, url = location validate_url(url) + # Turso Cloud now supplies turso:// URLs. The pinned native driver + # predates that alias but supports the equivalent HTTPS endpoint. + url = url.sub(/\Aturso:/i, 'https:') if url @native_options = {:path => path, :url => url} add_credentials add_replication_options @@ -37,7 +40,7 @@ def location database = @options[:database] database = nil if database.to_s.empty? url = @options[:url] - if database && /\A(?:libsql|https?):\/\//i.match?(database.to_s) + if database && /\A(?:turso|libsql|https?):\/\//i.match?(database.to_s) raise Sequel::Error, 'specify the remote URL with database or url, not both' if url && url != database url = database database = nil @@ -54,8 +57,8 @@ def location def validate_url(url) if url uri = URI.parse(url.to_s) - unless %w[libsql https http].include?(uri.scheme) && uri.host && !uri.userinfo && !uri.fragment && !uri.query - raise Sequel::Error, 'libSQL URL must use libsql, https, or http with a host and no credentials, query, or fragment' + unless %w[turso libsql https http].include?(uri.scheme) && uri.host && !uri.userinfo && !uri.fragment && !uri.query + raise Sequel::Error, 'libSQL URL must use turso, libsql, https, or http with a host and no credentials, query, or fragment' end elsif @options[:auth_token] raise Sequel::Error, 'auth_token requires a remote URL' diff --git a/test/configuration_test.rb b/test/configuration_test.rb index b7e7c57..0892d7c 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -71,6 +71,27 @@ def test_uri_is_a_remote_url db.disconnect if db end + def test_turso_urls_use_https_without_becoming_local_files + remote = configuration(:database => 'turso://example.turso.io', :auth_token => 'test-only') + assert_equal 'https://example.turso.io', remote.native_options[:url] + assert_nil remote.native_options[:path] + assert remote.remote? + replica = configuration(:url => 'turso://example.turso.io', :path => 'replica.db') + assert replica.replica? + assert_equal 'https://example.turso.io', replica.native_options[:url] + assert_raises(Sequel::Error){configuration(:database => 'turso://user:secret@example.com')} + assert_raises(Sequel::Error){configuration(:database => 'turso://example.com?auth_token=secret')} + assert_raises(Sequel::Error){configuration(:database => 'turso://example.com#secret')} + end + + def test_turso_uri_selects_the_libsql_adapter + db = Sequel.connect('turso://example.turso.io', :test => false, :auth_token => 'test-only') + assert_instance_of Sequel::LibSQL::Database, db + assert_equal 'https://example.turso.io', configuration(db.opts).native_options[:url] + ensure + db.disconnect if db + end + def test_remote_local_only_options_are_rejected [:timeout, :case_sensitive_like, :synchronous, :temp_store, :auto_vacuum].each do |option| assert_raises(Sequel::Error){configuration(:url => 'libsql://example.com', option => 1)}