Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

TiDB Ecto

livebooks/tidb/tidb_ecto.livemd

TiDB Ecto

Mix.install([
  {:ecto, "~> 3.12"},
  {:ecto_sql, "~> 3.12"},
  {:myxql, "~> 0.7"},
  {:jason, "~> 1.4"},
  {:kino, "~> 0.14"}
])

Connect to DB

defmodule Repo do
  use Ecto.Repo,
    otp_app: :my_notebook,
    adapter: Ecto.Adapters.MyXQL
end
hostname_input = Kino.Input.text("HOSTNAME")
username_input = Kino.Input.text("USERNAME")
password_input = Kino.Input.password("PASSWORD")
database_input = Kino.Input.text("DATABASE")

Kino.Layout.grid(
  [hostname_input, username_input, password_input, database_input],
  columns: 4
)
opts = [
  hostname: Kino.Input.read(hostname_input),
  port: 4000,
  username: Kino.Input.read(username_input),
  password: Kino.Input.read(password_input),
  database: Kino.Input.read(database_input),
  ssl: [cacertfile: "/etc/ssl/certs/ca-certificates.crt"]
]

Kino.start_child({Repo, opts})

Migration

defmodule Migrations.CreateTeamMemberTable do
  use Ecto.Migration

  def change do
    create table(:team_member) do
      add(:name, :string)
      add(:age, :integer)
      add(:weight, :float)
      add(:has_license, :boolean)
      add(:hash, :binary)
      add(:embedding, :vector, size: 4)
      add(:languages, :json)
      add(:skil_level, :json)
      add(:salary, :decimal)
      add(:date_of_birth, :date)
      add(:starting_time_of_work, :time)

      timestamps()
    end

    flush()

    execute """
    ALTER TABLE team_member SET TIFLASH REPLICA 1
    """

    flush()

    execute """
    ALTER TABLE team_member
    ADD VECTOR INDEX idx_team_member_embedding
    ((VEC_COSINE_DISTANCE(embedding)));
    """
  end
end
Ecto.Migrator.up(Repo, 1, Migrations.CreateTeamMemberTable)

Execute query

defmodule TeamMember do
  use Ecto.Schema
  import Ecto.Changeset

  schema "team_member" do
    field(:name, :string)
    field(:age, :integer)
    field(:weight, :float)
    field(:has_license, :boolean)
    field(:hash, :binary)
    field(:embedding, :string)
    field(:languages, {:array, :string})
    field(:skil_level, {:map, :integer})
    field(:salary, :decimal)
    field(:date_of_birth, :date)
    field(:starting_time_of_work, :time)

    timestamps()
  end

  def changeset(team_member, attrs) do
    team_member
    |> cast(attrs, [
      :name,
      :age,
      :weight,
      :has_license,
      :hash,
      :embedding,
      :languages,
      :skil_level,
      :salary,
      :date_of_birth,
      :starting_time_of_work
    ])
    |> validate_required([:name])
  end
end

Insert

%TeamMember{}
|> TeamMember.changeset(%{
  name: "Alice",
  age: 20,
  weight: 60.0,
  has_license: true,
  hash: <<0b11111111>>,
  embedding: "[0.0, 0.1, 0.2, 0.3]",
  languages: ["Japanese", "English"],
  skil_level: %{frontend: 5, backend: 3},
  salary: 1000_000,
  date_of_birth: ~D[2000-01-01],
  starting_time_of_work: ~T[08:30:00.0010]
})
|> Repo.insert()
now =
  NaiveDateTime.utc_now()
  |> NaiveDateTime.truncate(:second)

entries =
  [
    %{name: "Bob", age: 20, embedding: "[0.0, 0.1, 0.2, 0.4]"},
    %{name: "John", age: 30, embedding: "[0.1, 0.1, 0.3, 0.4]"},
    %{name: "Ryo", age: 39, embedding: "[0.3, 0.4, 0.2, 0.1]"}
  ]
  |> Enum.map(fn attr ->
    Map.merge(attr, %{
      inserted_at: now,
      updated_at: now
    })
  end)

Repo.insert_all(TeamMember, entries)

Select

team_members = Repo.all(TeamMember)
Kino.DataTable.new(team_members)
import Ecto.Query
Repo.all(
  from(tm in TeamMember,
    select: %{
      member_name: tm.name,
      member_age: tm.age,
      distance: fragment(
        "VEC_COSINE_DISTANCE(?, ?) as distance", tm.embedding, "[0.0, 0.1, 0.2, 0.3]"
      )
    },
    where: tm.age < 25,
    order_by: fragment("distance")
  )
)

Update

TeamMember
|> Repo.get_by!(name: "Bob")
|> TeamMember.changeset(%{
  age: 21,
  weight: 62.0,
  embedding: "[0.4, 0.3, 0.2, 0.1]"
})
|> Repo.update()

Delete

TeamMember
|> Repo.get_by!(name: "Alice")
|> Repo.delete()