Skip to content

blob encode uuids if stored as binary #173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

sbaildon
Copy link
Contributor

@sbaildon sbaildon commented Jul 3, 2025

Marking as a draft because I actually don't know if this is the correct solution

I'm using https://github.com/sloanelybutsurely/typeid-elixir which implements an Ecto.ParameterizedType that can be either a :string or :uuid type. As strings they look like "user_01jz8mgbxkevwagbhr8cdqhpqh"

I want to use ecto_sqlite3 with uuid_type: :binary for efficiency reasons

Storing the TypeID as :uuid will dump the suffix, "01jz8mgbxkevwagbhr8cdqhpqh", creating a binary from the base32 representation — but Ecto blows up in STRICT tables with BLOB columns because typeid_elixir is unaware exqlite needs to receive {:blob, value}, not only value.

** (Exqlite.Error) cannot store TEXT value in BLOB column users.id

I've added blob encoding to the dumper pipeline, but again, maybe this is not the correct solution.

@warmwaffles
Copy link
Member

Would you be able to provide a stripped down example of an app where this is occurring so I can fiddle with it?

@sbaildon
Copy link
Contributor Author

sbaildon commented Jul 3, 2025

Of course

# main.exs
Mix.install([
  {:typeid_elixir, "~> 1.1"},
  {:ecto, "~> 3.13"},
  {:ecto_sql, "~> 3.13"},
  {:ecto_sqlite3, "~> 0.21.0"}
])

Enum.each(
  [
    uuid_type: :binary,
    journal_mode: :wal,
  ],
  fn {k, v} -> Application.put_env(:ecto_sqlite3, k, v) end
)

Application.put_env(:myapp, Repo,
  migration_primary_key: [name: :id, type: :uuid, null: false],
  database: ":memory:",
  pool_size: 1
)

Application.put_env(:typeid_elixir, :default_type, :uuid)

defmodule Migration0 do
  use Ecto.Migration

  def change do
    create table("entities", options: "STRICT") do
      add(:field, :string)
    end
  end
end

defmodule Repo do
  use Ecto.Repo,
    otp_app: :myapp,
    adapter: Ecto.Adapters.SQLite3
end

defmodule Entity do
  use Ecto.Schema

  @primary_key {:id, TypeID, prefix: "entity", autogenerate: true}

  schema "entities" do
    field(:field, :string)
  end
end

defmodule Main do
  import Ecto.Query, warn: false

  def main do
    Repo.__adapter__().storage_down(Repo.config())
    :ok = Repo.__adapter__().storage_up(Repo.config())
    {:ok, _} = Repo.start_link([])
    Ecto.Migrator.run(Repo, [{0, Migration0}], :up, all: true, log_migrations_sql: :info)

    Repo.insert!(%Entity{
      field: "Post 1"
    })

    Repo.all(from(e in Entity))
    |> dbg()
  end
end

Main.main()
$ elixir main.exs

17:30:31.238 [info] == Running 0 Migration0.change/0 forward

17:30:31.240 [info] create table entities

17:30:31.241 [info] QUERY OK db=0.1ms
CREATE TABLE "entities" ("id" BLOB NOT NULL PRIMARY KEY, "field" TEXT) STRICT []

17:30:31.241 [info] == Migrated 0 in 0.0s

17:30:31.260 [debug] QUERY ERROR source="entities" db=0.0ms idle=6.4ms
INSERT INTO "entities" ("field","id") VALUES (?1,?2) ["Post 1", #TypeID<"entity_01jz96kc2nfzaanentmz3tsybr">]
** (Exqlite.Error) cannot store TEXT value in BLOB column entities.id
INSERT INTO "entities" ("field","id") VALUES (?1,?2)
    (ecto_sql 3.13.2) lib/ecto/adapters/sql.ex:1098: Ecto.Adapters.SQL.raise_sql_call_error/1
    (ecto 3.13.2) lib/ecto/repo/schema.ex:1000: Ecto.Repo.Schema.apply/4
    (ecto 3.13.2) lib/ecto/repo/schema.ex:500: anonymous fn/15 in Ecto.Repo.Schema.do_insert/4
    (ecto 3.13.2) lib/ecto/repo/schema.ex:381: Ecto.Repo.Schema.insert!/4
    main.exs:59: Main.main/0
    main.exs:68: (file)

@sbaildon
Copy link
Contributor Author

sbaildon commented Jul 3, 2025

If this line from typeid_elixir were to return {:ok, {:blob, TypeID.uuid_bytes(tid)}} everything works okay in the context of ecto_sqlite3. The binary needs to be wrapped in a {:blob, binary} tuple somewhere, but I'm not sure where

@warmwaffles
Copy link
Member

Thank you @sbaildon I'll take a look at this soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants