     query = SQL.delete("prefix", "model", [:x, :y], [])
     assert query == ~s{DELETE FROM "prefix"."model" WHERE "x" = ?1 AND "y" = ?2}
   end
+
+  # DDL
+
+  import Ecto.Migration, only: [table: 1, table: 2, index: 2, index: 3, references: 1,
+                                references: 2] #, constraint: 2, constraint: 3]
+
+  test "executing a string during migration" do
+    assert SQL.execute_ddl("example") == "example"
+  end
+
+  test "create table" do
+    create = {:create, table(:posts),
+               [{:add, :name, :string, [default: "Untitled", size: 20, null: false]},
+                {:add, :price, :numeric, [precision: 8, scale: 2, default: {:fragment, "expr"}]},
+                {:add, :on_hand, :integer, [default: 0, null: true]},
+                {:add, :is_active, :boolean, [default: true]}]}
+
+    assert SQL.execute_ddl(create) == """
+    CREATE TABLE "posts" ("name" TEXT DEFAULT 'Untitled' NOT NULL,
+    "price" NUMERIC DEFAULT (expr),
+    "on_hand" INTEGER DEFAULT 0,
+    "is_active" BOOLEAN DEFAULT 1)
+    """ |> remove_newlines
+  end
+
+  test "create table if not exists" do
+    create = {:create_if_not_exists, table(:posts),
+               [{:add, :id, :serial, [primary_key: true]},
+                {:add, :title, :string, []},
+                {:add, :price, :decimal, [precision: 10, scale: 2]},
+                {:add, :created_at, :datetime, []}]}
+    query = SQL.execute_ddl(create)
+    assert query == ~s{CREATE TABLE IF NOT EXISTS "posts" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "title" TEXT, "price" DECIMAL(10,2), "created_at" DATETIME)}
+  end
+
+  test "create table with prefix" do
+    create = {:create, table(:posts, prefix: :foo),
+               [{:add, :category_0, references(:categories), []}]}
+
+    assert SQL.execute_ddl(create) == """
+    CREATE TABLE "foo"."posts"
+    ("category_0" CONSTRAINT "posts_category_0_fkey" REFERENCES "foo"."categories"("id"))
+    """ |> remove_newlines
+  end
+
+  test "create table with references" do
+    create = {:create, table(:posts),
+               [{:add, :id, :serial, [primary_key: true]},
+                {:add, :category_0, references(:categories), []},
+                {:add, :category_1, references(:categories, name: :foo_bar), []},
+                {:add, :category_2, references(:categories, on_delete: :nothing), []},
+                {:add, :category_3, references(:categories, on_delete: :delete_all), [null: false]},
+                {:add, :category_4, references(:categories, on_delete: :nilify_all), []}]}
+
+    assert SQL.execute_ddl(create) == """
+    CREATE TABLE "posts" ("id" INTEGER PRIMARY KEY AUTOINCREMENT,
+    "category_0" CONSTRAINT "posts_category_0_fkey" REFERENCES "categories"("id"),
+    "category_1" CONSTRAINT "foo_bar" REFERENCES "categories"("id"),
+    "category_2" CONSTRAINT "posts_category_2_fkey" REFERENCES "categories"("id"),
+    "category_3" NOT NULL CONSTRAINT "posts_category_3_fkey" REFERENCES "categories"("id") ON DELETE CASCADE,
+    "category_4" CONSTRAINT "posts_category_4_fkey" REFERENCES "categories"("id") ON DELETE SET NULL)
+    """ |> remove_newlines
+  end
+
+  test "create table with references including prefixes" do
+    create = {:create, table(:posts, prefix: :foo),
+               [{:add, :id, :serial, [primary_key: true]},
+                {:add, :category_0, references(:categories, prefix: :foo), []},
+                {:add, :category_1, references(:categories, name: :foo_bar, prefix: :foo), []},
+                {:add, :category_2, references(:categories, on_delete: :nothing, prefix: :foo), []},
+                {:add, :category_3, references(:categories, on_delete: :delete_all, prefix: :foo), [null: false]},
+                {:add, :category_4, references(:categories, on_delete: :nilify_all, prefix: :foo), []}]}
+
+    assert SQL.execute_ddl(create) == ~s{CREATE TABLE "foo"."posts" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "category_0" CONSTRAINT "posts_category_0_fkey" REFERENCES "foo"."categories"("id"), "category_1" CONSTRAINT "foo_bar" REFERENCES "foo"."categories"("id"), "category_2" CONSTRAINT "posts_category_2_fkey" REFERENCES "foo"."categories"("id"), "category_3" NOT NULL CONSTRAINT "posts_category_3_fkey" REFERENCES "foo"."categories"("id") ON DELETE CASCADE, "category_4" CONSTRAINT "posts_category_4_fkey" REFERENCES "foo"."categories"("id") ON DELETE SET NULL)}
+  end
+
+  test "create table with options" do
+    create = {:create, table(:posts, options: "WITHOUT ROWID"),
+               [{:add, :id, :serial, [primary_key: true]},
+                {:add, :created_at, :datetime, []}]}
+    assert SQL.execute_ddl(create) ==
+           ~s|CREATE TABLE "posts" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "created_at" DATETIME) WITHOUT ROWID|
+  end
+
+  # Generates incorrect results with current build, but will be fixed shortly, I think.
+  # test "create table with composite key" do
+  #   create = {:create, table(:posts),
+  #              [{:add, :a, :integer, [primary_key: true]},
+  #               {:add, :b, :integer, [primary_key: true]},
+  #               {:add, :name, :string, []}]}
+  #
+  #   assert SQL.execute_ddl(create) == """
+  #   CREATE TABLE "posts" ("a" integer, "b" integer, "name" varchar(255), PRIMARY KEY ("a", "b"))
+  #   """ |> remove_newlines
+  # end
+
+  test "drop table" do
+    drop = {:drop, table(:posts)}
+    assert SQL.execute_ddl(drop) == ~s|DROP TABLE "posts"|
+  end
+
+  test "drop table if exists" do
+    assert SQL.execute_ddl({:drop_if_exists, %Table{name: "posts"}}) == ~s{DROP TABLE IF EXISTS "posts"}
+  end
+
+  test "drop table with prefix" do
+    drop = {:drop, table(:posts, prefix: :foo)}
+    assert SQL.execute_ddl(drop) == ~s|DROP TABLE "foo"."posts"|
+  end
+
+  test "alter table" do
+    alter = {:alter, table(:posts),
+               [{:add, :title, :string, [default: "Untitled", size: 100, null: false]},
+                {:add, :author_id, references(:author), []}]}
+    assert SQL.execute_ddl(alter) == """
+    ALTER TABLE "posts" ADD COLUMN "title" TEXT DEFAULT 'Untitled' NOT NULL;
+    ALTER TABLE "posts" ADD COLUMN "author_id" CONSTRAINT "posts_author_id_fkey" REFERENCES "author"("id")
+    """ |> remove_newlines
+  end
+
+  test "alter table with prefix" do
+    alter = {:alter, table(:posts, prefix: :foo),
+               [{:add, :title, :string, [default: "Untitled", size: 100, null: false]},
+                {:add, :author_id, references(:author, prefix: :foo), []}]}
+
+    assert SQL.execute_ddl(alter) == """
+    ALTER TABLE "foo"."posts" ADD COLUMN "title" TEXT DEFAULT 'Untitled' NOT NULL;
+    ALTER TABLE "foo"."posts" ADD COLUMN "author_id" CONSTRAINT "posts_author_id_fkey" REFERENCES "foo"."author"("id")
+    """ |> remove_newlines
+  end
+
+  test "alter column errors for :modify column" do
+    alter = {:alter, table(:posts), [{:modify, :price, :numeric, [precision: 8, scale: 2]}]}
+    assert_raise ArgumentError, "ALTER COLUMN not supported by SQLite", fn ->
+      SQL.execute_ddl(alter)
+    end
+  end
+
+  test "create index" do
+    create = {:create, index(:posts, [:category_id, :permalink])}
+    assert SQL.execute_ddl(create) ==
+           ~s|CREATE INDEX "posts_category_id_permalink_index" ON "posts" ("category_id", "permalink")|
+
+    # Incorrect results in current build, but I think this will be fixed soon.
+    # create = {:create, index(:posts, ["lower(permalink)"], name: "posts$main")}
+    # assert SQL.execute_ddl(create) ==
+    #        ~s|CREATE INDEX "posts$main" ON "posts" (lower(permalink))|
+  end
+
+  test "create index if not exists" do
+    create = {:create_if_not_exists, index(:posts, [:category_id, :permalink])}
+    query = SQL.execute_ddl(create)
+    assert query == ~s{CREATE INDEX IF NOT EXISTS "posts_category_id_permalink_index" ON "posts" ("category_id", "permalink")}
+  end
+
+  test "create index with prefix" do
+    create = {:create, index(:posts, [:category_id, :permalink], prefix: :foo)}
+    assert SQL.execute_ddl(create) ==
+           ~s|CREATE INDEX "posts_category_id_permalink_index" ON "foo"."posts" ("category_id", "permalink")|
+
+    # Incorrect results in current build, but I think this will be fixed soon.
+    # create = {:create, index(:posts, ["lower(permalink)"], name: "posts$main", prefix: :foo)}
+    # assert SQL.execute_ddl(create) ==
+    #        ~s|CREATE INDEX "posts$main" ON "foo"."posts" (lower(permalink))|
+  end
+
+  test "create unique index" do
+    create = {:create, index(:posts, [:permalink], unique: true)}
+    assert SQL.execute_ddl(create) ==
+           ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink")|
+  end
+
+  test "create unique index if not exists" do
+    create = {:create_if_not_exists, index(:posts, [:permalink], unique: true)}
+    query = SQL.execute_ddl(create)
+    assert query == ~s{CREATE UNIQUE INDEX IF NOT EXISTS "posts_permalink_index" ON "posts" ("permalink")}
+  end
+
+  # test "create unique index with condition" do  # restore in subsequent commit
+  #   create = {:create, index(:posts, [:permalink], unique: true, where: "public IS 1")}
+  #   assert SQL.execute_ddl(create) ==
+  #          ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") WHERE public IS 1|
+  #
+  #   create = {:create, index(:posts, [:permalink], unique: true, where: :public)}
+  #   assert SQL.execute_ddl(create) ==
+  #          ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") WHERE public|
+  # end
+
+  # test "create index concurrently" do  # restore in subsequent commit
+  #   create = {:create, index(:posts, [:permalink], concurrently: true)}
+  #   assert_raise ArgumentError, "mumble", fn ->
+  #     SQL.execute_ddl(create)
+  #   end
+  # end
+
+  # test "create unique index concurrently" do  # restore in subsequent commit
+  #   create = {:create, index(:posts, [:permalink], concurrently: true, unique: true)}
+  #   assert_raise ArgumentError, "mumble", fn ->
+  #     SQL.execute_ddl(create)
+  #   end
+  # end
+
+  # test "create an index using a different type" do  # restore in subsequent commit
+  #   create = {:create, index(:posts, [:permalink], using: :hash)}
+  #   assert_raise ArgumentError, "mumble", fn ->
+  #     SQL.execute_ddl(create)
+  #   end
+  # end
+
+  test "drop index" do
+    drop = {:drop, index(:posts, [:id], name: "posts$main")}
+    assert SQL.execute_ddl(drop) == ~s|DROP INDEX "posts$main"|
+  end
+
+  test "drop index with prefix" do
+    drop = {:drop, index(:posts, [:id], name: "posts$main", prefix: :foo)}
+    assert SQL.execute_ddl(drop) == ~s|DROP INDEX "posts$main"|
+  end
+
+  test "drop index if exists" do
+    drop = {:drop_if_exists, index(:posts, [:id], name: "posts$main")}
+    assert SQL.execute_ddl(drop) == ~s|DROP INDEX IF EXISTS "posts$main"|
+  end
+
+  # test "drop index concurrently" do   # restore in subsequent commit
+  #   drop = {:drop, index(:posts, [:id], name: "posts$main", concurrently: true)}
+  #   assert_raise ArgumentError, "mumble", fn ->
+  #     SQL.execute_ddl(drop)
+  #   end
+  # end
+
+  # test "create check constraint" do   # restore in subsequent commit
+  #   create = {:create, constraint(:products, "price_must_be_positive", check: "price > 0")}
+  #   assert_raise ArgumentError, "mumble", fn ->
+  #     SQL.execute_ddl(create)
+  #   end
+  #
+  #   create = {:create, constraint(:products, "price_must_be_positive", check: "price > 0", prefix: "foo")}
+  #   assert_raise ArgumentError, "mumble", fn ->
+  #     SQL.execute_ddl(create)
+  #   end
+  # end
+
+  # test "create exclusion constraint" do  #Q restore in subsequent commit
+  #   create = {:create, constraint(:products, "price_must_be_positive", exclude: ~s|gist (int4range("from", "to", '[]') WITH &&)|)}
+  #   assert_raise ArgumentError, "mumble", fn ->
+  #     SQL.execute_ddl(create)
+  #   end
+  # end
+
+  # test "drop constraint" do   # restore in subsequent commit
+  #   drop = {:drop, constraint(:products, "price_must_be_positive")}
+  #   assert_raise ArgumentError, "mumble", fn ->
+  #     SQL.execute_ddl(drop)
+  #   end
+  #
+  #   drop = {:drop, constraint(:products, "price_must_be_positive", prefix: "foo")}
+  #   assert_raise ArgumentError, "mumble", fn ->
+  #     SQL.execute_ddl(drop)
+  #   end
+  # end
+
+  test "rename table" do
+    rename = {:rename, table(:posts), table(:new_posts)}
+    assert SQL.execute_ddl(rename) == ~s|ALTER TABLE "posts" RENAME TO "new_posts"|
+  end
+
+  test "rename table with prefix" do
+    rename = {:rename, table(:posts, prefix: :foo), table(:new_posts, prefix: :foo)}
+    assert SQL.execute_ddl(rename) == ~s|ALTER TABLE "foo"."posts" RENAME TO "foo"."new_posts"|
+  end
+
+  test "rename column errors" do
+    rename = {:rename, table(:posts), :given_name, :first_name}
+    assert_raise ArgumentError, "RENAME COLUMN not supported by SQLite", fn ->
+      SQL.execute_ddl(rename)
+    end
+  end
+
+  test "rename column in prefixed table errors" do
+    rename = {:rename, table(:posts, prefix: :foo), :given_name, :first_name}
+    assert_raise ArgumentError, "RENAME COLUMN not supported by SQLite", fn ->
+      SQL.execute_ddl(rename)
+    end
+  end
+
+  test "drop column errors" do
+    alter = {:alter, table(:posts), [{:remove, :summary}]}
+    assert_raise ArgumentError, "DROP COLUMN not supported by SQLite", fn ->
+      SQL.execute_ddl(alter)
+    end
+  end
+
+  defp remove_newlines(string) do
+    string |> String.strip |> String.replace("\n", " ")
+  end
 end

defmodule Sqlite.Ecto.Test do
  use ExUnit.Case, async: true

  alias Sqlite.Ecto.Connection, as: SQL
  alias Ecto.Migration.Table

      SQL.all from(p in "posts", select: p) |> normalize()
    end
  end

  test "select" do
    query = Model |> select([r], {r.x, r.y}) |> normalize
