60 lines
		
	
	
		
			1.7 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
			
		
		
	
	
			60 lines
		
	
	
		
			1.7 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
defmodule Ecto.Integration.LockTest do
 | 
						|
  # We can keep this test async as long as it
 | 
						|
  # is the only one accessing the lock_test table.
 | 
						|
  use ExUnit.Case, async: true
 | 
						|
 | 
						|
  import Ecto.Query
 | 
						|
  alias Ecto.Integration.PoolRepo
 | 
						|
 | 
						|
  defmodule LockCounter do
 | 
						|
    use Ecto.Schema
 | 
						|
 | 
						|
    schema "lock_counters" do
 | 
						|
      field :count, :integer
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  setup do
 | 
						|
    PoolRepo.delete_all(LockCounter)
 | 
						|
    :ok
 | 
						|
  end
 | 
						|
 | 
						|
  test "lock for update" do
 | 
						|
    %{id: id} = PoolRepo.insert!(%LockCounter{count: 1})
 | 
						|
    pid = self()
 | 
						|
 | 
						|
    lock_for_update =
 | 
						|
      Application.get_env(:ecto_sql, :lock_for_update) ||
 | 
						|
      raise ":lock_for_update not set in :ecto application"
 | 
						|
 | 
						|
    # Here we are manually inserting the lock in the query
 | 
						|
    # to test multiple adapters. Never do this in actual
 | 
						|
    # application code: it is not safe and not public.
 | 
						|
    query = from(lc in LockCounter, where: lc.id == ^id)
 | 
						|
    query = %{query | lock: lock_for_update}
 | 
						|
 | 
						|
    {:ok, new_pid} =
 | 
						|
      Task.start_link fn ->
 | 
						|
        assert_receive :select_for_update, 5000
 | 
						|
 | 
						|
        PoolRepo.transaction(fn ->
 | 
						|
          [post] = PoolRepo.all(query) # this should block until the other trans. commit
 | 
						|
          post |> Ecto.Changeset.change(count: post.count + 1) |> PoolRepo.update!
 | 
						|
        end)
 | 
						|
 | 
						|
        send pid, :updated
 | 
						|
      end
 | 
						|
 | 
						|
    PoolRepo.transaction(fn ->
 | 
						|
      [post] = PoolRepo.all(query)       # select and lock the row
 | 
						|
      send new_pid, :select_for_update   # signal second process to begin a transaction
 | 
						|
      post |> Ecto.Changeset.change(count: post.count + 1) |> PoolRepo.update!
 | 
						|
    end)
 | 
						|
 | 
						|
    assert_receive :updated, 5000
 | 
						|
 | 
						|
    # Final count will be 3 if SELECT ... FOR UPDATE worked and 2 otherwise
 | 
						|
    assert [%LockCounter{count: 3}] = PoolRepo.all(LockCounter)
 | 
						|
  end
 | 
						|
end
 |