diff --git a/integration_test/myxql/storage_test.exs b/integration_test/myxql/storage_test.exs index 62315a55f..df9b5c45b 100644 --- a/integration_test/myxql/storage_test.exs +++ b/integration_test/myxql/storage_test.exs @@ -26,8 +26,11 @@ defmodule Ecto.Integration.StorageTest do run_mysql("DROP DATABASE #{params()[:database]};") end - def create_database do + def create_database(grant_privileges_to \\ nil) do run_mysql("CREATE DATABASE #{params()[:database]};") + if grant_privileges_to do + run_mysql("GRANT ALL PRIVILEGES ON #{params()[:database]}.* to #{grant_privileges_to}") + end end def create_posts do @@ -73,6 +76,20 @@ defmodule Ecto.Integration.StorageTest do drop_database() end + test "storage up with unprivileged user with access to the database" do + unprivileged_params = Keyword.merge(params(), + username: "unprivileged", + password: "pass" + ) + run_mysql("CREATE USER unprivileged IDENTIFIED BY 'pass'") + refute Ecto.Adapters.MyXQL.storage_up(unprivileged_params) == :ok + create_database("unprivileged") + assert Ecto.Adapters.MyXQL.storage_up(unprivileged_params) == {:error, :already_up} + after + run_mysql("DROP USER unprivileged") + drop_database() + end + test "structure dump and load" do create_database() create_posts() diff --git a/integration_test/pg/storage_test.exs b/integration_test/pg/storage_test.exs index cd79a3703..b19e2976f 100644 --- a/integration_test/pg/storage_test.exs +++ b/integration_test/pg/storage_test.exs @@ -27,8 +27,14 @@ defmodule Ecto.Integration.StorageTest do run_psql("DROP DATABASE #{params()[:database]};") end - def create_database do - run_psql("CREATE DATABASE #{params()[:database]};") + def create_database(owner \\ nil) do + query = "CREATE DATABASE #{params()[:database]}" + query = if owner do + query <> " OWNER #{owner};" + else + query <> ";" + end + run_psql(query) end def create_posts do @@ -74,6 +80,20 @@ defmodule Ecto.Integration.StorageTest do drop_database() end + test "storage up with unprivileged user with access to the database" do + unprivileged_params = Keyword.merge(params(), + username: "unprivileged", + password: "pass" + ) + run_psql("CREATE USER unprivileged WITH NOCREATEDB PASSWORD 'pass'") + refute Postgres.storage_up(unprivileged_params) == :ok + create_database("unprivileged") + assert Postgres.storage_up(unprivileged_params) == {:error, :already_up} + after + run_psql("DROP USER unprivileged") + drop_database() + end + test "structure dump and load" do create_database() create_posts() diff --git a/lib/ecto/adapters/myxql.ex b/lib/ecto/adapters/myxql.ex index b4b8df768..a12b470f1 100644 --- a/lib/ecto/adapters/myxql.ex +++ b/lib/ecto/adapters/myxql.ex @@ -159,19 +159,25 @@ defmodule Ecto.Adapters.MyXQL do opts = Keyword.delete(opts, :database) charset = opts[:charset] || "utf8mb4" - command = - ~s(CREATE DATABASE `#{database}` DEFAULT CHARACTER SET = #{charset}) - |> concat_if(opts[:collation], &"DEFAULT COLLATE = #{&1}") - - case run_query(command, opts) do - {:ok, _} -> - :ok - {:error, %{mysql: %{name: :ER_DB_CREATE_EXISTS}}} -> + check_existence_command = "SELECT TRUE FROM information_schema.schemata WHERE schema_name = '#{database}'" + case run_query(check_existence_command, opts) do + {:ok, %{num_rows: 1}} -> {:error, :already_up} - {:error, error} -> - {:error, Exception.message(error)} - {:exit, exit} -> - {:error, exit_to_exception(exit)} + _ -> + create_command = + ~s(CREATE DATABASE `#{database}` DEFAULT CHARACTER SET = #{charset}) + |> concat_if(opts[:collation], &"DEFAULT COLLATE = #{&1}") + + case run_query(create_command, opts) do + {:ok, _} -> + :ok + {:error, %{mysql: %{name: :ER_DB_CREATE_EXISTS}}} -> + {:error, :already_up} + {:error, error} -> + {:error, Exception.message(error)} + {:exit, exit} -> + {:error, exit_to_exception(exit)} + end end end diff --git a/lib/ecto/adapters/postgres.ex b/lib/ecto/adapters/postgres.ex index 0bc01b162..b702e566d 100644 --- a/lib/ecto/adapters/postgres.ex +++ b/lib/ecto/adapters/postgres.ex @@ -127,25 +127,37 @@ defmodule Ecto.Adapters.Postgres do @impl true def storage_up(opts) do - database = Keyword.fetch!(opts, :database) || raise ":database is nil in repository configuration" + database = + Keyword.fetch!(opts, :database) || raise ":database is nil in repository configuration" + encoding = if opts[:encoding] == :unspecified, do: nil, else: opts[:encoding] || "UTF8" maintenance_database = Keyword.get(opts, :maintenance_database, @default_maintenance_database) opts = Keyword.put(opts, :database, maintenance_database) - command = - ~s(CREATE DATABASE "#{database}") - |> concat_if(encoding, &"ENCODING '#{&1}'") - |> concat_if(opts[:template], &"TEMPLATE=#{&1}") - |> concat_if(opts[:lc_ctype], &"LC_CTYPE='#{&1}'") - |> concat_if(opts[:lc_collate], &"LC_COLLATE='#{&1}'") + check_existence_command = "SELECT FROM pg_database WHERE datname = '#{database}'" - case run_query(command, opts) do - {:ok, _} -> - :ok - {:error, %{postgres: %{code: :duplicate_database}}} -> + case run_query(check_existence_command, opts) do + {:ok, %{num_rows: 1}} -> {:error, :already_up} - {:error, error} -> - {:error, Exception.message(error)} + + _ -> + create_command = + ~s(CREATE DATABASE "#{database}") + |> concat_if(encoding, &"ENCODING '#{&1}'") + |> concat_if(opts[:template], &"TEMPLATE=#{&1}") + |> concat_if(opts[:lc_ctype], &"LC_CTYPE='#{&1}'") + |> concat_if(opts[:lc_collate], &"LC_COLLATE='#{&1}'") + + case run_query(create_command, opts) do + {:ok, _} -> + :ok + + {:error, %{postgres: %{code: :duplicate_database}}} -> + {:error, :already_up} + + {:error, error} -> + {:error, Exception.message(error)} + end end end