Readers, thank you all for your patience as I haven’t posted for several weeks.

Last time I wrote about setting up Ecto from scratch and I promised to continue with this topic. However, this post will be another topice though it is Ecto related. I’m going to go through a series of troubleshooting experiments in order to learn more about a problem I’ve run into in one of my private projects.

In this post I’m using

Elixir 1.2.0

Ecto 1.1.1

timex 1.0.0

timex_ecto 0.7.0

The Models

In my private project I have a Phoenix application with a model that represents an object that is available during specific date/time ranges. We’ll call this Object for now. There is a second model, AvailableDateRange which represents a single range of dates. An Object has many AvailableDateRange s.

I’m using Timex to describe the date/times.

Here’s a stripped down version of these structures:

defmodule Project . Object do use Project . Web , :model require Logger schema " object" do field :title , :string field :description , :string belongs_to :user , User has_many :available_date_ranges , Project . AvailableDateRange , on_replace: :delete timestamps end end defmodule Project . AvailableDateRange do use Project . Web , :model use Timex . Ecto . Timestamps schema " available_date_ranges" do field :start , Timex . Ecto . DateTimeWithTimezone field :end , Timex . Ecto . DateTimeWithTimezone belongs_to :object , Project . Object timestamps end end

The Query

In my project I have a query that looks like this:

def available_for ( query \\ Project . Object , s , e ) do s = parse_date ( s ) e = parse_date ( e ) from object in query , join: a in assoc ( object , :available_date_ranges ), where: a . start > ^ s end defp parse_date ( date ) do { :ok , timex } = Timex . DateFormat . parse ( date , " {M}/{D}/{YYYY}" ) timex end

The function available_for/3 is a composable query. I can pass any query that returns Object records int available_for/3 and it builds a new query that limits the original query to those Object records that are available for a specific date. The function is unfinished. It checks only the starting date and not the ending date. I haven’t finished this because it isn’t working.

The Problem

When I try to use this query I get this error:

** (exit) an exception was raised: ** (FunctionClauseError) no function clause matching in :lists.zip/2 (stdlib) lists.erl:386: :lists.zip([{ {2016, 1, 5}, {0, 0, 0, 0}}, "UTC"], []) (postgrex) lib/postgrex/extensions/binary.ex:297: Postgrex.Extensions.Binary.encode_record/3 (postgrex) lib/postgrex/protocol.ex:299: anonymous fn/2 in Postgrex.Protocol.encode_params/1 (elixir) lib/enum.ex:1043: anonymous fn/3 in Enum.map/2 (elixir) lib/enum.ex:1387: Enum."-reduce/3-lists^foldl/2-0-"/3 (elixir) lib/enum.ex:1043: Enum.map/2 (postgrex) lib/postgrex/protocol.ex:294: Postgrex.Protocol.encode_params/1 (postgrex) lib/postgrex/protocol.ex:264: Postgrex.Protocol.send_params/2 (postgrex) lib/postgrex/protocol.ex:136: Postgrex.Protocol.message/3 (postgrex) lib/postgrex/connection.ex:417: Postgrex.Connection.new_data/2 (postgrex) lib/postgrex/connection.ex:292: Postgrex.Connection.handle_info/2 (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4 (stdlib) gen_server.erl:681: :gen_server.handle_msg/5 (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

This error happens deep inside Postgrex, the Postgres adapter for Ecto. I can understand that the zip function doesn’t match on lists of different sizes but I don’t know why this happening. In the rest of the post we will investigate what is going wrong and how we can make this work.

Experiments

I will approach this investigation as a series of experiments, each experiment building on the previous. I’ll start with a very simple Ecto program and will build up toward something like the example from my private project until I can reproduce this same error.

The goals of these are experiments are to

Build up an understanding of what’s going on with my query.

Build a minimal reproduction case that I can debug or send off to the Ecto developers for further investigation.

Experiment 1 - Setup a basic Ecto application

First I need a basic Elixir application with Ecto. I covered exactly this in the last post so I won’t go over it in detail here. But here are the commands I’m running so you can follow along.

First, create a new supervised mix project. EE is “Ecto Experiments”

$ mix new --sup ee

Next, I put everything under source control.

Install ecto with postgrex and start the Ecto application. I can do this in mix.exs.

@@ -14,7 +14,7 @@ defmodule EE.Mixfile do # # Type "mix help compile.app" for more information def application do - [applications: [:logger]] + [applications: [:logger, :postgrex, :ecto]] end # Dependencies can be Hex packages: @@ -27,6 +27,9 @@ defmodule EE.Mixfile do # # Type "mix help deps" for more examples and options defp deps do - [] + [ + {:postgrex, ">= 0.0.0"}, + {:ecto, "~> 1.0"}, + ] end end

I then configured the database by adding the following to config/config.exs:

config :ee , Ee . Repo , adapter: Ecto . Adapters . Postgres , database: " ecto_experiments" , username: " postgres" , password: " postgres"

Then, I created a Repo in lib/ee/repo.ex:

defmodule Ee . Repo do use Ecto . Repo , otp_app: :ee end

Then I added the Repo as a worker in my child specification:

index ae71ede..7524c78 100644 --- a/lib/ee.ex +++ b/lib/ee.ex @@ -8,7 +8,7 @@ defmodule Ee do children = [ # Define workers and child supervisors to be supervised - # worker(Ee.Worker, [arg1, arg2, arg3]), + worker(Ee.Repo, []) ] # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html

and tested the whole thing with

$ mix ecto.create The database for Ee.Repo has been created.

I committed all the changes. The next step is to build a model. We’ll start with this as lib/ee/object.ex:

defmodule EE . Object do use Ecto . Schema schema " object" do field :title , :string field :description , :string # has_many :available_date_ranges, Project.AvailableDateRange, on_replace: :delete timestamps end end

I’ve stripped this down slightly from the example I showed from my private project.

I’ve removed Project.Web since we are not using Phoenix. Instead we use Ecto.Schema .

since we are not using Phoenix. Instead we . I’ve removed logger.

I’ve removed belongs_to :user, User since we haven’t built a User model.

since we haven’t built a model. I’ve commented out the has_many relationship. We’ll add it back when we add AvailableDateRange .

Here’s the associated migration:

defmodule EE . Repo . Migrations . AddObject do use Ecto . Migration def change do create table ( :object ) do add :title , :string add :description , :string timestamps end end end

and I can run it like this:

$ mix ecto.migrate 14:53:37.623 [ info] == Running Ee.Repo.Migrations.AddObject.change/0 forward 14:53:37.623 [ info] create table object 14:53:37.628 [ info] == Migrated in 0.0s

We now have a basic setup so I committed everything. And I added a git tag called experiment/setup to mark this point for later reference.

Experiment 2 - Tests

Next, let’s write a test and any additional required code to create an object and insert it into the database.

In the last experiment we configured the database for the dev environment. We’ll need to add a configuration for the test environment. We’ll also have to set things up so that we can maintain separate dev and test configs.

Mix generated support for this but commented out. In config/config.exs we have:

# It is also possible to import configuration files, relative to this # directory. For example, you can emulate configuration per environment # by uncommenting the line below and defining dev.exs, test.exs and such. # Configuration from the imported file will override the ones defined # here (which is why it is important to import them last). # # import_config "#{Mix.env}.exs"

We’ll uncomment this import and move our existing config to config/dev.exs:

# config/dev.exs use Mix . Config config :ee , EE . Repo , adapter: Ecto . Adapters . Postgres , database: " ecto_experiments" , username: " postgres" , password: " postgres"

Then we create a new file config/test.exs:

# config/test.exs use Mix . Config config :ee , EE . Repo , adapter: Ecto . Adapters . Postgres , database: " ecto_experiments_test" , username: " postgres" , password: " postgres" , pool: Ecto . Adapters . SQL . Sandbox

The difference between the dev and test configs are:

database: We have separate databases for each config.

We have separate databases for each config. pool: For test we use Ecto.Adapters.SQL.Sandbox. This uses a special Sandbox pool that allows us to use test transactions.

For Ecto testing we want to run each test in its own database transaction. We can do this by adding the following to test/test_helper.exs:

Mix . Task . run " ecto.create" , [ " --quiet" ] Mix . Task . run " ecto.migrate" , [ " --quiet" ] Ecto . Adapters . SQL . begin_test_transaction ( Ee . Repo )

At this point the initial test runs which means our config is good:

$ mix test 14:58:37.285 [ debug] BEGIN [] OK query = 0.3ms 14:58:37.286 [ debug] SAVEPOINT ecto_sandbox [] OK query = 0.3ms . Finished in 0.1 seconds ( 0.1s on load, 0.00s on tests ) 1 test , 0 failures Randomized with seed 323271

Now that we have Ecto setup for our tests we can write a real test.

test " we can create and insert object records" do Ee . Repo . insert! % Ee . Object { title: " Test Object" , description: " And object for the test" } end

And this simple test passes.

I’ll commit and add a tag called experiment/simple_test.

Experiment 3 - Simple Query

In this experiment we’ll add a simple query and wrap it in a function. I’ll start by writing a test that defines the interface I want:

test " we can insert object records and query them back" do title = " Queryable title" object = Ee . Repo . insert! % Ee . Object { title: title , description: " And object for the test" } found = Ee . Object . find_by_title ( title ) |> Ee . Repo . all assert Enum . count ( found ) == 1 assert List . first ( found ) . id == object . id end

This test

Inserts an object with a special title Uses Ee.Object.find_by_title/1 to query the same object. Asserts that it got the object it expected.

To make this test pass we just need to implement find_by_title/1 . The query is simply:

def find_by_title ( title ) do from object in Ee . Object , where: object . title == ^ title end

We query from all Ee.Object records and select those where the title matches our specified title . With this implementation the test passes.

There are no surprises here but we’re building slowly toward our goal.

I’ll commit and add a tag experiment/simple_query.

Let’s take another step toward our goal and introduce a DateTime field and query. We’ll continue to take things slow and will test out using a DateTime field in Object before trying an association.

To add the field we’ll need a new migration:

defmodule Ee . Repo . Migrations . AddDateTimeToObject do use Ecto . Migration def change do alter table ( :object ) do add :date , :datetime end end end

and to update the Object struct:

--- a/lib/ee/object.ex +++ b/lib/ee/object.ex @@ -5,6 +5,7 @@ defmodule Ee.Object do schema "object" do field :title, :string field :description, :string + field :date, Ecto.DateTime # has_many :available_date_ranges, Project.AvailableDateRange, on_replace: :delete

With this let’s try inserting an object with a filled in date.

test " we can create and insert object records with the date field" do Ee . Repo . insert! % Ee . Object { title: " Test Object" , description: " And object for the test" , date: Ecto . DateTime . from_erl ({ { 2016 , 1 , 6 }, { 3 , 59 , 5 }}) } end

And this test passes.

Now, let’s try to query the object by date. Here’s the test:

test " we can insert object records and query them back by date" do date = { { 2016 , 1 , 6 }, { 8 , 0 , 0 }} object = Ee . Repo . insert! % Ee . Object { title: " Test Object" , description: " And object for the test" , date: Ecto . DateTime . from_erl ( date ) } found = Ee . Object . find_by_date ( date ) |> Ee . Repo . all assert Enum . count ( found ) == 1 assert List . first ( found ) . id == object . id end

And here’s a working implementation of find_by_date:

def find_by_date ( date ) do from object in Ee . Object , where: object . date == ^ date end

This test passes and the experiment is a success. I commit and add a tag experiment/date-query.

Experiment 4 - Timex Query

Now, I don’t actually want to use Ecto.DateTime to store dates. I want to use the timex and timex_ecto libraries for their improved handling of timezones.

Let’s start by setting up Timex in our application. I’ll follow the README.

First I’ll update mix.exs to include the timex and timex_ecto dependencies and start the tzdata app:

--- a/mix.exs +++ b/mix.exs @@ -14,7 +14,7 @@ defmodule Ee.Mixfile do # # Type "mix help compile.app" for more information def application do - [applications: [:logger, :postgrex, :ecto], + [applications: [:logger, :postgrex, :ecto, :tzdata, :timex], mod: {Ee, []}] end @@ -31,6 +31,8 @@ defmodule Ee.Mixfile do [ {:postgrex, ">= 0.0.0"}, {:ecto, "~> 1.0"}, + {:timex, "~> 1.0"}, + {:timex_ecto, "~> 0.7"} ] end end

I want to use the Timex.Ecto.DateTimeWithTimezone type which is only supported with Postgres and requires some extra setup. We have to run some custom SQL which we can do in a migration like this:

defmodule Ee . Repo . Migrations . AddTimexDatetimetz do use Ecto . Migration def up do execute """ CREATE TYPE datetimetz AS ( dt timestamptz, tz varchar ); """ end def down do execute " DROP TYPE IF EXISTS datetimetz;" end end

When bringing up this migration we execute custom SQL to create a new type called “datetimetz”. This SQL comes from the timex_ecto documentation.

Now that timex is setup we can add a field to Object . I’ve simply done this:

--- a/lib/ee/object.ex +++ b/lib/ee/object.ex @@ -6,6 +6,7 @@ defmodule Ee.Object do field :title, :string field :description, :string field :date, Ecto.DateTime + field :timex, Timex.Ecto.DateTimeWithTimezone # has_many :available_date_ranges, Project.AvailableDateRange, on_replace: :delete

I’ve named the field timex so that it is clear which kind of date field it is in these examples.

And of course to add this field we need another migration:

defmodule Ee . Repo . Migrations . AddTimexToObject do use Ecto . Migration def change do alter table ( :object ) do add :timex , :datetimetz end end end

At this point my tests start to fail.

$ mix test 11:04:58.985 [debug] BEGIN [] OK query=0.2ms 11:04:58.985 [debug] SAVEPOINT ecto_sandbox [] OK query=0.2ms . 11:04:59.063 [debug] INSERT INTO "object" ("inserted_at", "updated_at", "date", "description", "timex", "title") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [{ {2016, 1, 6}, {19, 4, 59, 0}}, { {2016, 1, 6}, {19, 4, 59, 0}}, { {2016, 1, 6}, {8, 0, 0, 0}}, "And object for the test", nil, "Test Object"] OK query=0.9ms 11:04:59.074 [debug] INSERT INTO "object" ("inserted_at", "updated_at", "date", "description", "timex", "title") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [{ {2016, 1, 6}, {19, 4, 59, 0}}, { {2016, 1, 6}, {19, 4, 59, 0}}, { {2016, 1, 6}, {3, 59, 5, 0}}, "And object for the test", nil, "Test Object"] OK query=5.0ms 11:04:59.076 [debug] INSERT INTO "object" ("inserted_at", "updated_at", "date", "description", "timex", "title") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [{ {2016, 1, 6}, {19, 4, 59, 0}}, { {2016, 1, 6}, {19, 4, 59, 0}}, nil, "And object for the test", nil, "Queryable title"] OK query=1.0ms 1) test we can insert object records and query them back by date (EeTest) test/ee_test.exs:39 ** (CaseClauseError) no case clause matching: {:ok, {:error, %ArgumentError{message: "no extension found for oid `131165`"}, [{Postgrex.Types, :fetch!, 2, [file: 'lib/postgrex/types.ex', line: 296]}, {Postgrex.Types, :decoder, 2, [file: 'lib/postgrex/types.ex', line: 220]}, {Enum, :"-map/2-lists^map/1-0-", 2, [file: 'lib/enum.ex', line: 1088]}, {Enum, :"-map/2-lists^map/1-0-", 2, [file: 'lib/enum.ex', line: 1088]}, {Postgrex.Protocol, :decoders, 2, [file: 'lib/postgrex/protocol.ex', line: 545]}, {Postgrex.Protocol, :describe_fields, 4, [file: 'lib/postgrex/protocol.ex', line: 368]}, {Postgrex.Protocol, :query, 4, [file: 'lib/postgrex/protocol.ex', line: 80]}, {Postgrex.Connection, :query, 6, [file: 'lib/postgrex/connection.ex', line: 315]}, {Postgrex.Connection, :query, 4, [file: 'lib/postgrex/connection.ex', line: 101]}, {Ecto.Adapters.Postgres.Connection, :query, 4, [file: 'lib/ecto/adapters/postgres/connection.ex', line: 31]}, {:timer, :tc, 3, [file: 'timer.erl', line: 197]}, {Ecto.Adapters.SQL, :query, 7, [file: 'lib/ecto/adapters/sql.ex', line: 262]}, {Ecto.Pool, :do_run, 4, [file: 'lib/ecto/pool.ex', line: 159]}, {Ecto.Adapters.SQL, :query, 6, [file: 'lib/ecto/adapters/sql.ex', line: 247]}, {Ecto.Adapters.SQL, :query, 5, [file: 'lib/ecto/adapters/sql.ex', line: 225]}, {Ecto.Adapters.SQL, :query!, 5, [file: 'lib/ecto/adapters/sql.ex', line: 183]}, {Ecto.Adapters.SQL, :execute, 6, [file: 'lib/ecto/adapters/sql.ex', line: 481]}, {Ecto.Repo.Queryable, :execute, 5, [file: 'lib/ecto/repo/queryable.ex', line: 95]}, {Ecto.Repo.Queryable, :all, 4, [file: 'lib/ecto/repo/queryable.ex', line: 15]}, {EeTest, :"test we can insert object records and query them back by date", 1, [file: 'test/ee_test.exs', line: 49]}]}, %{}, [], ""} stacktrace: (postgrex) lib/postgrex/protocol.ex:80: Postgrex.Protocol.query/4 (postgrex) lib/postgrex/connection.ex:315: Postgrex.Connection.query/6 (postgrex) lib/postgrex/connection.ex:101: Postgrex.Connection.query/4 (ecto) lib/ecto/adapters/postgres/connection.ex:31: Ecto.Adapters.Postgres.Connection.query/4 (stdlib) timer.erl:197: :timer.tc/3 (ecto) lib/ecto/adapters/sql.ex:262: Ecto.Adapters.SQL.query/7 (ecto) lib/ecto/pool.ex:159: Ecto.Pool.do_run/4 (ecto) lib/ecto/adapters/sql.ex:247: Ecto.Adapters.SQL.query/6 (ecto) lib/ecto/adapters/sql.ex:225: Ecto.Adapters.SQL.query/5 (ecto) lib/ecto/adapters/sql.ex:183: Ecto.Adapters.SQL.query!/5 (ecto) lib/ecto/adapters/sql.ex:481: Ecto.Adapters.SQL.execute/6 (ecto) lib/ecto/repo/queryable.ex:95: Ecto.Repo.Queryable.execute/5 (ecto) lib/ecto/repo/queryable.ex:15: Ecto.Repo.Queryable.all/4 test/ee_test.exs:49 . 2) test we can insert object records and query them back (EeTest) test/ee_test.exs:16 ** (CaseClauseError) no case clause matching: {:ok, {:error, %ArgumentError{message: "no extension found for oid `131165`"}, [{Postgrex.Types, :fetch!, 2, [file: 'lib/postgrex/types.ex', line: 296]}, {Postgrex.Types, :decoder, 2, [file: 'lib/postgrex/types.ex', line: 220]}, {Enum, :"-map/2-lists^map/1-0-", 2, [file: 'lib/enum.ex', line: 1088]}, {Enum, :"-map/2-lists^map/1-0-", 2, [file: 'lib/enum.ex', line: 1088]}, {Postgrex.Protocol, :decoders, 2, [file: 'lib/postgrex/protocol.ex', line: 545]}, {Postgrex.Protocol, :describe_fields, 4, [file: 'lib/postgrex/protocol.ex', line: 368]}, {Postgrex.Protocol, :query, 4, [file: 'lib/postgrex/protocol.ex', line: 80]}, {Postgrex.Connection, :query, 6, [file: 'lib/postgrex/connection.ex', line: 315]}, {Postgrex.Connection, :query, 4, [file: 'lib/postgrex/connection.ex', line: 101]}, {Ecto.Adapters.Postgres.Connection, :query, 4, [file: 'lib/ecto/adapters/postgres/connection.ex', line: 31]}, {:timer, :tc, 3, [file: 'timer.erl', line: 197]}, {Ecto.Adapters.SQL, :query, 7, [file: 'lib/ecto/adapters/sql.ex', line: 262]}, {Ecto.Pool, :do_run, 4, [file: 'lib/ecto/pool.ex', line: 159]}, {Ecto.Adapters.SQL, :query, 6, [file: 'lib/ecto/adapters/sql.ex', line: 247]}, {Ecto.Adapters.SQL, :query, 5, [file: 'lib/ecto/adapters/sql.ex', line: 225]}, {Ecto.Adapters.SQL, :query!, 5, [file: 'lib/ecto/adapters/sql.ex', line: 183]}, {Ecto.Adapters.SQL, :execute, 6, [file: 'lib/ecto/adapters/sql.ex', line: 481]}, {Ecto.Repo.Queryable, :execute, 5, [file: 'lib/ecto/repo/queryable.ex', line: 95]}, {Ecto.Repo.Queryable, :all, 4, [file: 'lib/ecto/repo/queryable.ex', line: 15]}, {EeTest, :"test we can insert object records and query them back", 1, [file: 'test/ee_test.exs', line: 25]}]}, %{}, [], ""} stacktrace: (postgrex) lib/postgrex/protocol.ex:80: Postgrex.Protocol.query/4 (postgrex) lib/postgrex/connection.ex:315: Postgrex.Connection.query/6 (postgrex) lib/postgrex/connection.ex:101: Postgrex.Connection.query/4 (ecto) lib/ecto/adapters/postgres/connection.ex:31: Ecto.Adapters.Postgres.Connection.query/4 (stdlib) timer.erl:197: :timer.tc/3 (ecto) lib/ecto/adapters/sql.ex:262: Ecto.Adapters.SQL.query/7 (ecto) lib/ecto/pool.ex:159: Ecto.Pool.do_run/4 (ecto) lib/ecto/adapters/sql.ex:247: Ecto.Adapters.SQL.query/6 (ecto) lib/ecto/adapters/sql.ex:225: Ecto.Adapters.SQL.query/5 (ecto) lib/ecto/adapters/sql.ex:183: Ecto.Adapters.SQL.query!/5 (ecto) lib/ecto/adapters/sql.ex:481: Ecto.Adapters.SQL.execute/6 (ecto) lib/ecto/repo/queryable.ex:95: Ecto.Repo.Queryable.execute/5 (ecto) lib/ecto/repo/queryable.ex:15: Ecto.Repo.Queryable.all/4 test/ee_test.exs:25 3) test we can create and insert object records (EeTest) test/ee_test.exs:9 ** (ArgumentError) repo Ee.Repo is not started, please ensure it is part of your supervision tree stacktrace: (ecto) lib/ecto/adapters/sql.ex:253: Ecto.Adapters.SQL.query/6 (ecto) lib/ecto/adapters/sql.ex:225: Ecto.Adapters.SQL.query/5 (ecto) lib/ecto/adapters/sql.ex:487: Ecto.Adapters.SQL.model/6 (ecto) lib/ecto/repo/schema.ex:297: Ecto.Repo.Schema.apply/5 (ecto) lib/ecto/repo/schema.ex:81: anonymous fn/11 in Ecto.Repo.Schema.do_insert/4 (ecto) lib/ecto/repo/schema.ex:14: Ecto.Repo.Schema.insert!/4 test/ee_test.exs:10 Finished in 0.5 seconds (0.4s on load, 0.02s on tests) 5 tests, 3 failures Randomized with seed 53554

And they fail intermittently. Actually, I discovered that they fail the first time I run them and then they don’t fail again until I drop the database with:

$ MIX_ENV = test mix ecto.drop

I can make my results more consistent by making this change to the test helper:

--- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,5 +1,6 @@ ExUnit.start() +Mix.Task.run "ecto.drop", ["--quiet"] Mix.Task.run "ecto.create", ["--quiet"] Mix.Task.run "ecto.migrate", ["--quiet"] Ecto.Adapters.SQL.begin_test_transaction(Ee.Repo)

This gives me a failure every time I run the tests but the number of failures varies. To try to understand what’s going wrong I comment out all of the tests except the initial test:

test " the truth" do assert 1 + 1 == 2 end

This passes every time I run it. Continuing with baby steps, I uncomment this test:

test " we can create and insert object records" do

The two uncommented tests pass every time I run them. Next, I uncomment another insertion test:

test " we can create and insert object records with the date field" do

The three tests pass every time I run them. Next, I uncomment the first query test:

test " we can insert object records and query them back" do

And this fails. So there is some problem with query?

Experiment 5 - Query Tests

To isolate the problem, I comment out all the tests except “we can insert object records and query them back” and this single test fails every time I run it:

$ mix test 11:13:18.923 [debug] BEGIN [] OK query=0.2ms 11:13:18.924 [debug] SAVEPOINT ecto_sandbox [] OK query=0.4ms 11:13:18.984 [debug] INSERT INTO "object" ("inserted_at", "updated_at", "date", "description", "timex", "title") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [{ {2016, 1, 6}, {19, 13, 18, 0}}, { {2016, 1, 6}, {19, 13, 18, 0}}, nil, "And object for the test", nil, "Queryable title"] OK query=0.9ms 1) test we can insert object records and query them back (EeTest) test/ee_test.exs:16 ** (CaseClauseError) no case clause matching: {:ok, {:error, %ArgumentError{message: "no extension found for oid `131645`"}, [{Postgrex.Types, :fetch!, 2, [file: 'lib/postgrex/types.ex', line: 296]}, {Postgrex.Types, :decoder, 2, [file: 'lib/postgrex/types.ex', line: 220]}, {Enum, :"-map/2-lists^map/1-0-", 2, [file: 'lib/enum.ex', line: 1088]}, {Enum, :"-map/2-lists^map/1-0-", 2, [file: 'lib/enum.ex', line: 1088]}, {Postgrex.Protocol, :decoders, 2, [file: 'lib/postgrex/protocol.ex', line: 545]}, {Postgrex.Protocol, :describe_fields, 4, [file: 'lib/postgrex/protocol.ex', line: 368]}, {Postgrex.Protocol, :query, 4, [file: 'lib/postgrex/protocol.ex', line: 80]}, {Postgrex.Connection, :query, 6, [file: 'lib/postgrex/connection.ex', line: 315]}, {Postgrex.Connection, :query, 4, [file: 'lib/postgrex/connection.ex', line: 101]}, {Ecto.Adapters.Postgres.Connection, :query, 4, [file: 'lib/ecto/adapters/postgres/connection.ex', line: 31]}, {:timer, :tc, 3, [file: 'timer.erl', line: 197]}, {Ecto.Adapters.SQL, :query, 7, [file: 'lib/ecto/adapters/sql.ex', line: 262]}, {Ecto.Pool, :do_run, 4, [file: 'lib/ecto/pool.ex', line: 159]}, {Ecto.Adapters.SQL, :query, 6, [file: 'lib/ecto/adapters/sql.ex', line: 247]}, {Ecto.Adapters.SQL, :query, 5, [file: 'lib/ecto/adapters/sql.ex', line: 225]}, {Ecto.Adapters.SQL, :query!, 5, [file: 'lib/ecto/adapters/sql.ex', line: 183]}, {Ecto.Adapters.SQL, :execute, 6, [file: 'lib/ecto/adapters/sql.ex', line: 481]}, {Ecto.Repo.Queryable, :execute, 5, [file: 'lib/ecto/repo/queryable.ex', line: 95]}, {Ecto.Repo.Queryable, :all, 4, [file: 'lib/ecto/repo/queryable.ex', line: 15]}, {EeTest, :"test we can insert object records and query them back", 1, [file: 'test/ee_test.exs', line: 25]}]}, %{}, [], ""} stacktrace: (postgrex) lib/postgrex/protocol.ex:80: Postgrex.Protocol.query/4 (postgrex) lib/postgrex/connection.ex:315: Postgrex.Connection.query/6 (postgrex) lib/postgrex/connection.ex:101: Postgrex.Connection.query/4 (ecto) lib/ecto/adapters/postgres/connection.ex:31: Ecto.Adapters.Postgres.Connection.query/4 (stdlib) timer.erl:197: :timer.tc/3 (ecto) lib/ecto/adapters/sql.ex:262: Ecto.Adapters.SQL.query/7 (ecto) lib/ecto/pool.ex:159: Ecto.Pool.do_run/4 (ecto) lib/ecto/adapters/sql.ex:247: Ecto.Adapters.SQL.query/6 (ecto) lib/ecto/adapters/sql.ex:225: Ecto.Adapters.SQL.query/5 (ecto) lib/ecto/adapters/sql.ex:183: Ecto.Adapters.SQL.query!/5 (ecto) lib/ecto/adapters/sql.ex:481: Ecto.Adapters.SQL.execute/6 (ecto) lib/ecto/repo/queryable.ex:95: Ecto.Repo.Queryable.execute/5 (ecto) lib/ecto/repo/queryable.ex:15: Ecto.Repo.Queryable.all/4 test/ee_test.exs:25

What does this error mean? It sounds like some extension isn’t enabled in the database but which extension?

Debugging this head on hasn’t been fruitful. The oid 131645 doesn’t seem to have a specific meaning that I can find. It even seems to change every time I run the tests.

I’m going to try a less direct approach. First, I remove the mix ecto.drop call that I added to test_helper. Then I run the test again and see it pass. This suggests that there is some data being carried over in the database that is making the test pass! I should be able to look in the database to find it.

I”m using PG Commander to look at the database.

There are no objects left in the object table.

table. There is a datetimetz user defined type.

I’m sort of thinking that the datetimetz type isn’t getting setup when the tests are running.

Experiment 6 - Run commands from the shell

At this point we’re into real investigation and it’s time to get a little more scientific about these experiments. Based on my thoughts above, I have a hypothesis: Something isn’t working right with the Mix.Task.run calls. Perhaps they don’t finish by the time the test starts.

Based on this hypothesis I ran the following experiment.

First, I commented out all the Mix.Task calls from the test helper:

--- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,6 +1,6 @@ ExUnit.start() -Mix.Task.run "ecto.drop", ["--quiet"] -Mix.Task.run "ecto.create", ["--quiet"] -Mix.Task.run "ecto.migrate", ["--quiet"] +# Mix.Task.run "ecto.drop", ["--quiet"] +# Mix.Task.run "ecto.create", ["--quiet"] +# Mix.Task.run "ecto.migrate", ["--quiet"] Ecto.Adapters.SQL.begin_test_transaction(Ee.Repo)

Second, I ran this sequence of shell commands

$ mix ecto.drop --quiet ; mix ecto.create --quiet ; mix ecto.migrate --quiet ; mix test

This sequence of commands passes consistently. So I think this points to some problem with the commands running properly.

Next steps

Unfortunately, this post is getting a little too long so I am going to have to stop here and continue this investigation in a second post.

In this post we reviewed setting up Ecto and then worked through a system of experiments in order to driver and investigation of a problem that I’m having in another project. I hope you enjoyed exploring this system of problem isolation with me.

Next week I’ll continue in this line of investigation and will pick up where we just left off.