-
Couldn't load subscription status.
- Fork 54
Implement values #174
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
Implement values #174
Changes from 10 commits
ff6b2e1
bbd1dbb
cafc720
40733b8
2b01d45
e851684
2054a79
3183b70
b86ecbb
99cec9a
c5cc3c5
7cb8351
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| defmodule Ecto.Integration.ValuesTest do | ||
| use Ecto.Integration.Case, async: true | ||
|
|
||
| import Ecto.Query, only: [from: 2, with_cte: 3] | ||
|
|
||
| alias Ecto.Integration.Comment | ||
| alias Ecto.Integration.Post | ||
| alias Ecto.Integration.TestRepo | ||
|
|
||
| test "values works with datetime" do | ||
| TestRepo.insert!(%Post{inserted_at: ~N[2000-01-01 00:01:00]}) | ||
| TestRepo.insert!(%Post{inserted_at: ~N[2000-01-01 00:02:00]}) | ||
| TestRepo.insert!(%Post{inserted_at: ~N[2000-01-01 00:03:00]}) | ||
|
|
||
| params = [ | ||
| %{id: 1, date: ~N[2000-01-01 00:00:00]}, | ||
| %{id: 2, date: ~N[2000-01-01 00:01:00]}, | ||
| %{id: 3, date: ~N[2000-01-01 00:02:00]}, | ||
| %{id: 4, date: ~N[2000-01-01 00:03:00]} | ||
| ] | ||
|
|
||
| types = %{id: :integer, date: :naive_datetime} | ||
|
|
||
| results = | ||
| from(params in values(params, types), | ||
| left_join: p in Post, | ||
| on: p.inserted_at <= params.date, | ||
| group_by: params.id, | ||
| select: %{id: params.id, count: count(p.id)}, | ||
| order_by: count(p.id) | ||
| ) | ||
| |> TestRepo.all() | ||
|
|
||
| assert results == [ | ||
| %{count: 0, id: 1}, | ||
| %{count: 1, id: 2}, | ||
| %{count: 2, id: 3}, | ||
| %{count: 3, id: 4} | ||
| ] | ||
| end | ||
|
|
||
| test "join to values works" do | ||
| TestRepo.insert!(%Post{id: 1}) | ||
| TestRepo.insert!(%Comment{post_id: 1, text: "short"}) | ||
| TestRepo.insert!(%Comment{post_id: 1, text: "much longer text"}) | ||
|
|
||
| params = [%{id: 1, post_id: 1, n: 0}, %{id: 2, post_id: 1, n: 10}] | ||
| types = %{id: :integer, post_id: :integer, n: :integer} | ||
|
|
||
| results = | ||
| from(p in Post, | ||
| right_join: params in values(params, types), | ||
| on: params.post_id == p.id, | ||
| left_join: c in Comment, | ||
| on: c.post_id == p.id and fragment("LENGTH(?)", c.text) > params.n, | ||
| group_by: params.id, | ||
| select: {params.id, count(c.id)} | ||
| ) | ||
| |> TestRepo.all() | ||
|
|
||
| assert [{1, 2}, {2, 1}] = results | ||
| end | ||
|
|
||
| test "values can be used together with CTE" do | ||
| TestRepo.insert!(%Post{id: 1, visits: 42}) | ||
| TestRepo.insert!(%Comment{post_id: 1, text: "short"}) | ||
| TestRepo.insert!(%Comment{post_id: 1, text: "much longer text"}) | ||
|
|
||
| params = [%{id: 1, post_id: 1, n: 0}, %{id: 2, post_id: 1, n: 10}] | ||
| types = %{id: :integer, post_id: :integer, n: :integer} | ||
|
|
||
| cte_query = from(p in Post, select: %{id: p.id, visits: coalesce(p.visits, 0)}) | ||
|
|
||
| q = Post |> with_cte("xxx", as: ^cte_query) | ||
|
|
||
| results = | ||
| from(p in q, | ||
| right_join: params in values(params, types), | ||
| on: params.post_id == p.id, | ||
| left_join: c in Comment, | ||
| on: c.post_id == p.id and fragment("LENGTH(?)", c.text) > params.n, | ||
| left_join: cte in "xxx", | ||
| on: cte.id == p.id, | ||
| group_by: params.id, | ||
| select: {params.id, count(c.id), cte.visits} | ||
| ) | ||
| |> TestRepo.all() | ||
|
|
||
| assert [{1, 2, 42}, {2, 1, 42}] = results | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1037,12 +1037,6 @@ defmodule Ecto.Adapters.SQLite3.Connection do | |
| message: "join hints are not supported by SQLite3" | ||
| end | ||
|
|
||
| defp assert_valid_join(%JoinExpr{source: {:values, _, _}}, query) do | ||
| raise Ecto.QueryError, | ||
| query: query, | ||
| message: "SQLite3 adapter does not support values lists" | ||
| end | ||
|
|
||
| defp assert_valid_join(_join_expr, _query), do: :ok | ||
|
|
||
| defp join_on(:cross, true, _sources, _query), do: [] | ||
|
|
@@ -1368,8 +1362,8 @@ defmodule Ecto.Adapters.SQLite3.Connection do | |
| |> parens_for_select | ||
| end | ||
|
|
||
| defp expr({:values, _, _}, _, _query) do | ||
| raise ArgumentError, "SQLite3 adapter does not support values lists" | ||
| defp expr({:values, _, [types, idx, num_rows]}, _, _query) do | ||
| [?(, values_list(types, idx + 1, num_rows), ?)] | ||
| end | ||
|
|
||
| defp expr({:identifier, _, [literal]}, _sources, _query) do | ||
|
|
@@ -1560,6 +1554,30 @@ defmodule Ecto.Adapters.SQLite3.Connection do | |
| message: "unsupported expression #{inspect(expr)}" | ||
| end | ||
|
|
||
| defp values_list(types, idx, num_rows) do | ||
| rows = :lists.seq(1, num_rows, 1) | ||
| col_names = Enum.map_join(types, ", ", &elem(&1, 0)) | ||
| table_name = "temp_#{:rand.uniform(100_000)}" | ||
|
|
||
| [ | ||
| "WITH #{table_name}(", | ||
| col_names, | ||
| ") AS (VALUES ", | ||
| intersperse_reduce(rows, ?,, idx, fn _, idx -> | ||
| {value, idx} = values_expr(types, idx) | ||
| {[?(, value, ?)], idx} | ||
| end) | ||
| |> elem(0), | ||
| ") SELECT * FROM #{table_name}" | ||
| ] | ||
| end | ||
|
|
||
| defp values_expr(types, idx) do | ||
| intersperse_reduce(types, ?,, idx, fn {_field, type}, idx -> | ||
| {[?$, Integer.to_string(idx), ?:, ?: | column_type(type, nil)], idx + 1} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added the type casting as in the postgres version. Without it we were completely ignoring the types that the user supplied, so I think it makes more sense to just always cast. |
||
| end) | ||
| end | ||
|
|
||
| def interval(_, "microsecond", _sources) do | ||
| raise ArgumentError, | ||
| "SQLite does not support microsecond precision in datetime intervals" | ||
|
|
@@ -1618,6 +1636,9 @@ defmodule Ecto.Adapters.SQLite3.Connection do | |
| {:fragment, _, _} -> | ||
| {nil, as_prefix ++ [?f | Integer.to_string(pos)], nil} | ||
|
|
||
| {:values, _, _} -> | ||
| {nil, as_prefix ++ [?v | Integer.to_string(pos)], nil} | ||
|
|
||
| {table, schema, prefix} -> | ||
| name = as_prefix ++ [create_alias(table) | Integer.to_string(pos)] | ||
| {quote_table(prefix, table), name, schema} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can shove this into the
test/directory under theconnection/directory asvalues_testThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inside
test/ecto/adapters/sqlite3/connection?If I do that I get
** (Exqlite.Error) no such table: posts. So there is some setup missing there.Also, I didn't see any other similar tests in that directory, tests that actually operate on a Repo. Only in
integration_test/