From 08fcd70a8c563aecb0b752a9b0952499744603d5 Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Thu, 30 Apr 2026 13:39:39 +0100 Subject: [PATCH] ML-154: add early platform detection for SQLite extensions --- ...on-in-runtime.exs-for-SQLite-extensions.md | 38 ++++++++ ...xtension_path-for-unsupported-platforms.md | 40 --------- config/runtime.exs | 2 + lib/music_library/repo.ex | 87 ++++++++++++++++--- 4 files changed, 113 insertions(+), 54 deletions(-) create mode 100644 backlog/tasks/ml-154 - Add-early-platform-detection-in-runtime.exs-for-SQLite-extensions.md delete mode 100644 backlog/tasks/ml-154 - Replace-raise-with-Logger.warning-in-Repo.extension_path-for-unsupported-platforms.md diff --git a/backlog/tasks/ml-154 - Add-early-platform-detection-in-runtime.exs-for-SQLite-extensions.md b/backlog/tasks/ml-154 - Add-early-platform-detection-in-runtime.exs-for-SQLite-extensions.md new file mode 100644 index 00000000..52e2ba7a --- /dev/null +++ b/backlog/tasks/ml-154 - Add-early-platform-detection-in-runtime.exs-for-SQLite-extensions.md @@ -0,0 +1,38 @@ +--- +id: ML-154 +title: Add early platform detection in runtime.exs for SQLite extensions +status: Done +assignee: [] +created_date: '2026-04-30 10:48' +updated_date: '2026-04-30 12:37' +labels: + - robustness + - sqlite +dependencies: [] +references: + - lib/music_library/repo.ex + - config/runtime.exs +priority: low +--- + +## Description + + +`MusicLibrary.Repo.extension_path/1` raises `"Unsupported OS or platform"` when run on an unrecognised OS/architecture combination. This is called at startup via `config/runtime.exs` to resolve paths for `unicode` and `vec0` SQLite extensions. + +Instead of silently degrading (which would cause confusing downstream failures), add early detection: + +1. Add `MusicLibrary.Repo.supported_platform?/0` that returns a boolean +2. In `config/runtime.exs`, check this BEFORE the `load_extensions` config block +3. If unsupported, raise with a helpful message listing supported platforms and the detected OS/arch + +The existing `raise` in `extension_path/1` stays as a defensive fallback (should not be reached if runtime.exs catches it first). + + +## Acceptance Criteria + +- [x] #1 #1 `Repo.supported_platform?/0` returns true/false based on OS/arch +- [x] #2 #2 `config/runtime.exs` checks `supported_platform?/0` before loading extensions and raises with a helpful message on unsupported platforms +- [x] #3 #3 Existing supported platforms (darwin-amd64, darwin-arm64, linux-amd64, linux-arm64) continue to load extensions correctly +- [x] #4 #4 `extension_path/1` raise stays as a defensive fallback + diff --git a/backlog/tasks/ml-154 - Replace-raise-with-Logger.warning-in-Repo.extension_path-for-unsupported-platforms.md b/backlog/tasks/ml-154 - Replace-raise-with-Logger.warning-in-Repo.extension_path-for-unsupported-platforms.md deleted file mode 100644 index cd2720f1..00000000 --- a/backlog/tasks/ml-154 - Replace-raise-with-Logger.warning-in-Repo.extension_path-for-unsupported-platforms.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: ML-154 -title: >- - Replace raise with Logger.warning in Repo.extension_path for unsupported - platforms -status: To Do -assignee: [] -created_date: '2026-04-30 10:48' -labels: - - robustness - - sqlite -dependencies: [] -references: - - lib/music_library/repo.ex - - config/runtime.exs -priority: low ---- - -## Description - - -`MusicLibrary.Repo.extension_path/1` (`lib/music_library/repo.ex`) derives the platform from `:erlang.system_info(:system_architecture)` and raises `"Unsupported OS or platform"` if the combination isn't recognised. - -This is called at startup in `config/runtime.exs` to load SQLite extensions (`unicode`, `vec0`). If deployed on an unexpected architecture, the app crashes before serving any traffic. - -Change the function to: -1. Log a warning with the detected OS/arch details -2. Return `nil` (or an `{:error, reason}` tuple) -3. Update `config/runtime.exs` and any other callers to handle the nil/error case gracefully — skipping extension loading but allowing the app to start - -Features that depend on the extensions (FTS5 unicode support, vector similarity search) will degrade but the app remains available. - - -## Acceptance Criteria - -- [ ] #1 `Repo.extension_path/1` logs a warning and returns nil instead of raising for unsupported platforms -- [ ] #2 `config/runtime.exs` handles a nil return by skipping extension loading for that extension -- [ ] #3 App can start on an unsupported platform without crashing -- [ ] #4 Existing supported platforms (darwin-amd64, darwin-arm64, linux-amd64, linux-arm64) continue to load extensions correctly - diff --git a/config/runtime.exs b/config/runtime.exs index 317db4c4..3f3e0c21 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -36,6 +36,8 @@ if default_timezone = System.get_env("DEFAULT_TIMEZONE") do config :music_library, :default_timezone, default_timezone end +MusicLibrary.Repo.ensure_supported_platform!() + config :music_library, MusicLibrary.Repo, auto_vacuum: :incremental, load_extensions: [ diff --git a/lib/music_library/repo.ex b/lib/music_library/repo.ex index 88f7e689..d488c287 100644 --- a/lib/music_library/repo.ex +++ b/lib/music_library/repo.ex @@ -1,25 +1,41 @@ defmodule MusicLibrary.Repo do + @moduledoc """ + Main application repository using SQLite3 with FTS5 unicode and vec0 extensions. + """ + use Ecto.Repo, otp_app: :music_library, adapter: Ecto.Adapters.SQLite3 + @doc """ + Returns `:ok` if the current OS/architecture has precompiled SQLite extensions + available (unicode, vec0). Raises `RuntimeError` with a diagnostic message + including the detected platform and supported alternatives. + """ + def ensure_supported_platform! do + case platform_string() do + {:ok, _platform} -> :ok + {:error, os, arch} -> raise_unsupported_platform(os, arch) + end + end + + @doc """ + Returns `true` if the current OS/architecture has precompiled SQLite extensions + available (unicode, vec0). + """ + def supported_platform? do + match?({:ok, _}, platform_string()) + end + + @doc """ + Returns the filesystem path for a named SQLite extension. + + Only macOS (Intel/Apple Silicon) and Linux (amd64/arm64) are supported. + """ def extension_path(name) do - [arch | _rest] = - :erlang.system_info(:system_architecture) - |> List.to_string() - |> String.split("-") - + platform = platform_string!() {_, os} = :os.type() - platform = - case {os, arch} do - {:darwin, "x86_64"} -> "darwin-amd64" - {:darwin, "aarch64"} -> "darwin-arm64" - {:linux, "x86_64"} -> "linux-amd64" - {:linux, "aarch64"} -> "linux-arm64" - _other -> raise "Unsupported OS or platform" - end - extension = case os do :darwin -> "dylib" @@ -34,6 +50,49 @@ defmodule MusicLibrary.Repo do ]) end + defp raise_unsupported_platform(os, arch) do + raise """ + Unsupported platform: #{os} / #{arch} + + This application ships precompiled SQLite extensions (unicode, vec0) for: + • darwin-amd64 (macOS Intel) + • darwin-arm64 (macOS Apple Silicon) + • linux-amd64 (Linux x86_64) + • linux-arm64 (Linux aarch64) + + Your platform (#{inspect(os)} / #{arch}) is not supported. The SQLite extensions + required for full-text search (unicode) and vector similarity search (vec0) + cannot be loaded on this architecture. + + To run on this platform, provide compatible builds of these extensions in + priv/sqlite_extensions/#{os}-#{arch}/. + """ + end + + defp platform_string! do + case platform_string() do + {:ok, platform} -> platform + {:error, _os, _arch} -> raise "Unsupported OS or platform" + end + end + + defp platform_string do + [arch | _rest] = + :erlang.system_info(:system_architecture) + |> List.to_string() + |> String.split("-") + + {_, os} = :os.type() + + case {os, arch} do + {:darwin, "x86_64"} -> {:ok, "darwin-amd64"} + {:darwin, "aarch64"} -> {:ok, "darwin-arm64"} + {:linux, "x86_64"} -> {:ok, "linux-amd64"} + {:linux, "aarch64"} -> {:ok, "linux-arm64"} + _ -> {:error, os, arch} + end + end + def vacuum, do: query("VACUUM") def optimize, do: query("PRAGMA optimize")