Skip to content

Commit

Permalink
Add mix phx.new --database cockroach
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismccord committed May 16, 2024
1 parent ee75ad7 commit 80fbb4e
Show file tree
Hide file tree
Showing 7 changed files with 354 additions and 119 deletions.
1 change: 1 addition & 0 deletions guides/ecto.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Most web applications today need some form of data validation and persistence. I
Phoenix uses Ecto to provide builtin support to the following databases:

* PostgreSQL (via [`postgrex`](https://github.com/elixir-ecto/postgrex))
* CockroachDB (via [`postgrex`](https://github.com/elixir-ecto/postgrex))
* MySQL (via [`myxql`](https://github.com/elixir-ecto/myxql))
* MSSQL (via [`tds`](https://github.com/livehelpnow/tds))
* ETS (via [`etso`](https://github.com/evadne/etso))
Expand Down
3 changes: 2 additions & 1 deletion installer/lib/mix/tasks/phx.new.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defmodule Mix.Tasks.Phx.New do
* `--database` - specify the database adapter for Ecto. One of:
* `postgres` - via https://github.com/elixir-ecto/postgrex
* `cockroach` - via https://github.com/elixir-ecto/postgrex
* `mysql` - via https://github.com/elixir-ecto/myxql
* `mssql` - via https://github.com/livehelpnow/tds
* `sqlite3` - via https://github.com/elixir-sqlite/ecto_sqlite3
Expand Down Expand Up @@ -53,7 +54,7 @@ defmodule Mix.Tasks.Phx.New do
* `--no-html` - do not generate HTML views
* `--no-live` - comment out LiveView socket setup in your Endpoint
* `--no-live` - comment out LiveView socket setup in your Endpoint
and assets/js/app.js. Automatically disabled if --no-html is given
* `--no-mailer` - do not generate Swoosh mailer files
Expand Down
70 changes: 69 additions & 1 deletion installer/lib/phx_new/generator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,74 @@ defmodule Phx.New.Generator do
{:ecto_sqlite3, Ecto.Adapters.SQLite3, fs_db_config(app, module)}
end

defp get_ecto_adapter("cockroach", app, module) do
{:postgrex, Ecto.Adapters.Postgres,
case_insensitive_field_type: :"STRING COLLATE \"en-US-u-ks-level2\"",
dev: [
username: "root",
password: nil,
hostname: "localhost",
port: 26257,
database: "#{app}_dev",
stacktrace: true,
migration_lock: false,
show_sensitive_data_on_connection_error: true,
pool_size: 10
],
test: [
username: "root",
password: nil,
hostname: "localhost",
port: 26257,
database: {:literal, ~s|"#{app}_test\#{System.get_env("MIX_TEST_PARTITION")}"|},
migration_lock: false,
pool: Ecto.Adapters.SQL.Sandbox,
pool_size: {:literal, ~s|System.schedulers_online() * 2|}
],
test_setup_all: "Ecto.Adapters.SQL.Sandbox.mode(#{inspect(module)}.Repo, :manual)",
test_setup: """
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(module)}.Repo, shared: not tags[:async])
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)\
""",
prod_variables: ~S"""
database_url =
System.get_env("DATABASE_URL") ||
raise \"""
environment variable DATABASE_URL is missing.
For example: postgres://USER:PASS@HOST/DATABASE
\"""
database_cert = System.get_env("DATABASE_CERT") ||
raise \"""
environment variable DATABASE_CERT is missing.
You must set this to the certificate file contents of the CockroachDB cluster.
\"""
db_conf =
Regex.named_captures(
~r/^postgresql:\/\/(?<username>[^:]+):(?<password>[^@]+)@(?<hostname>[^:]+):(?<port>\d+)\/(?<database>[^\?]+)/,
database_url
)
""",
prod_config: ~S"""
username: db_conf["username"],
password: db_conf["password"],
hostname: db_conf["hostname"],
port: db_conf["port"],
database: db_conf["database"],
migration_lock: false,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
show_sensitive_data_on_connection_error: true,
ssl: true,
ssl_opts: [
server_name_indication: ~c"#{db_conf["hostname"]}",
customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)],
cacerts: for({:Certificate, der, _} <- :public_key.pem_decode(database_cert), do: der)
]
"""}
end

defp get_ecto_adapter(db, _app, _mod) do
Mix.raise("Unknown database #{inspect(db)}")
end
Expand Down Expand Up @@ -394,7 +462,7 @@ defmodule Phx.New.Generator do

defp adapter_generators(adapter_config) do
adapter_config
|> Keyword.take([:binary_id, :migration, :sample_binary_id])
|> Keyword.take([:binary_id, :migration, :sample_binary_id, :case_insensitive_field_type])
|> Enum.filter(fn {_, value} -> not is_nil(value) end)
end

Expand Down
56 changes: 56 additions & 0 deletions installer/test/phx_new_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,62 @@ defmodule Mix.Tasks.Phx.NewTest do
end)
end

test "new with cockroach adapter" do
in_tmp("new with cockroach adapter", fn ->
project_path = Path.join(File.cwd!(), "custom_path")
Mix.Tasks.Phx.New.run([project_path, "--database", "cockroach"])

assert_file("custom_path/mix.exs", ":postgrex")

assert_file("custom_path/config/config.exs", fn file ->
assert file =~ "ecto_repos: [CustomPath.Repo]"
assert file =~ """
generators: [
timestamp_type: :utc_datetime,
case_insensitive_field_type: :"STRING COLLATE \\"en-US-u-ks-level2\\""
]
"""
end)

assert_file("custom_path/config/dev.exs", [
~r/username: "root"/,
~r/password: nil/,
~r/hostname: "localhost"/,
~r/port: 26257/,
~r/migration_lock: false/,
])

assert_file("custom_path/config/test.exs", [
~r/username: "root"/,
~r/password: nil/,
~r/hostname: "localhost"/,
~r/port: 26257/,
~r/migration_lock: false/,
])

assert_file("custom_path/config/runtime.exs", fn file ->
assert file =~ ~s|database_cert =|
assert file =~ ~s|db_conf =|
assert file =~ ~s|migration_lock: false|
assert file =~ ~s|username: db_conf["username"]|
assert file =~ ~s|password: db_conf["password"]|
assert file =~ ~s|hostname: db_conf["hostname"]|
assert file =~ ~s|database: db_conf["database"]|
assert file =~ ~s|ssl_opts:|
assert file =~ ~s| ssl: true|
refute file =~ ~s|url: database_url|
end)
assert_file("custom_path/lib/custom_path/repo.ex", "Ecto.Adapters.Postgres")

assert_file("custom_path/test/support/conn_case.ex", "DataCase.setup_sandbox(tags)")

assert_file(
"custom_path/test/support/data_case.ex",
"Ecto.Adapters.SQL.Sandbox.start_owner"
)
end)
end

test "new with mysql adapter" do
in_tmp("new with mysql adapter", fn ->
project_path = Path.join(File.cwd!(), "custom_path")
Expand Down
21 changes: 18 additions & 3 deletions lib/mix/tasks/phx.gen.auth/migration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,34 @@ defmodule Mix.Tasks.Phx.Gen.Auth.Migration do
end

defp extensions(Ecto.Adapters.Postgres) do
["execute \"CREATE EXTENSION IF NOT EXISTS citext\", \"\""]
if case_insensitive_field_type() == :citext do
["execute \"CREATE EXTENSION IF NOT EXISTS citext\", \"\""]
else
[]
end
end

defp extensions(_), do: []

defp case_insensitive_field_type do
case Application.get_env(Mix.Phoenix.otp_app(), :generators, [])[:case_insensitive_field_type] do
type when type in [nil, :citext] -> :citext
custom_type -> custom_type
end
end

defp column_definitions(ecto_adapter) do
for field <- ~w(email token)a,
into: %{},
do: {field, column_definition(field, ecto_adapter)}
end

defp column_definition(:email, Ecto.Adapters.Postgres), do: "add :email, :citext, null: false"
defp column_definition(:email, Ecto.Adapters.SQLite3), do: "add :email, :string, null: false, collate: :nocase"
defp column_definition(:email, Ecto.Adapters.Postgres),
do: "add :email, #{inspect(case_insensitive_field_type())}, null: false"

defp column_definition(:email, Ecto.Adapters.SQLite3),
do: "add :email, :string, null: false, collate: :nocase"

defp column_definition(:email, _), do: "add :email, :string, null: false, size: 160"

defp column_definition(:token, Ecto.Adapters.Postgres), do: "add :token, :binary, null: false"
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/phx.gen.live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</:action>
<:action :let={{id, <%= schema.singular %>}}>
<.link
phx-click={JS.push("delete", value: %{id: <%= schema.singular %>.id}) |> hide("##{id}")}
phx-click={JS.push("delete", value: %{id: to_string(<%= schema.singular %>.id)}) |> hide("##{id}")}
data-confirm="Are you sure?"
>
Delete
Expand Down
Loading

0 comments on commit 80fbb4e

Please sign in to comment.