Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ You are able to change the location of the model file output in the configuratio

* module_name -> This is the name of the module that the models will be placed under
* destination -> The output location for the generated models. If this is not provided then the models will go in the directory that you ran plsm in
* server -> this is the name of the server that you are connecting to. It can be a DNS name or an IP Address. This needs to be filled in as there are no defaults
* port -> The port that the database server is listening on. This needs to be provided as there may not be a default for your server
* database_name -> the name of the database that you are connecting to. This is required.
* server -> this is the name of the server that you are connecting to. It can be a DNS name or an IP Address. This needs to be filled in for PostgreSQL and MySQL as there are no defaults
* port -> The port that the database server is listening on. This needs to be provided except for SQLite as there may not be a default for your server
* database_name -> the name of the database that you are connecting to. This is required except for SQLite
* username -> The username that is used to connect. Make sure that there is sufficient privileges to be able to connect, query tables as well as query information schemas on the database. The schema information is used to find the index/keys on each table
* password -> This is necessary as there is no default nor is there any handling of a blank password currently.
* filename -> Database file full path. This is required for SQLite
* type -> This dictates which database vendor you are using. We currently support PostgreSQL and MySQL. If no value is entered then it will default to MySQL. Accepted values: `:mysql` or `:postgres`. **Do note that this is an atom and not a string**


Expand All @@ -44,6 +45,7 @@ You are able to change the location of the model file output in the configuratio

* MySQL
* PostgreSQL
* SQLite

We may add support to other databases based on demand. Please reach out and if you want a specific database supported. Please feel free to contribute commits that add different database vendor support!

Expand Down
1 change: 1 addition & 0 deletions lib/common/configs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule Plsm.Common.Configs do
database_name: Application.get_env(:plsm, :database_name, ""),
username: Application.get_env(:plsm, :username, ""),
password: Application.get_env(:plsm, :password, ""),
filename: Application.get_env(:plsm, :filename, ""),
type: Application.get_env(:plsm, :type, :mysql)
}

Expand Down
2 changes: 1 addition & 1 deletion lib/common/structs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Plsm.Configs do
end

defmodule Plsm.Configs.Database do
defstruct server: "", port: "", database_name: "", username: "", password: "", type: :mysql
defstruct server: "", port: "", database_name: "", username: "", password: "", filename: "", type: :mysql
end

defmodule Plsm.Configs.Project do
Expand Down
11 changes: 7 additions & 4 deletions lib/config/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ defmodule Plsm.Config.Config do
|> append_next_item()
|> append_config_item_string("password", "password")
|> append_next_item()
|> append_config_item_string("filename", "filename")
|> append_next_item()
|> append_config_item_atom("type", "mysql")
end

Expand Down Expand Up @@ -65,12 +67,13 @@ defmodule Plsm.Config.Config do
# Plsm configs are used to drive the extraction process. Below are what each field means:
# * module_name -> This is the name of the module that the models will be placed under
# * destination -> The output location for the generated models
# * server -> this is the name of the server that you are connecting to. It can be a DNS name or an IP Address. This needs to be filled in as there are no defaults
# * port -> The port that the database server is listening on. This needs to be provided as there may not be a default for your server
# * server -> this is the name of the server that you are connecting to. It can be a DNS name or an IP Address. This needs to be filled in for PostgreSQL and MySQL as there are no defaults
# * port -> The port that the database server is listening on. This needs to be provided for PostgreSQL and MySQL as there may not be a default for your server
# * database_name -> the name of the database that you are connecting to. This is required.
# * username -> The username that is used to connect. Make sure that there is sufficient privileges to be able to connect, query tables as well as query information schemas on the database. The schema information is used to find the index/keys on each table
# * password -> This is necessary as there is no default nor is there any handling of a blank password currently.
# * type -> This dictates which database vendor you are using. We currently support PostgreSQL and MySQL. If no value is entered then it will default to MySQL. Do note that this is an atom and not a string
# * password -> This is necessary for PostgreSQL and MySQL as there is no default nor is there any handling of a blank password currently.
# * filename -> Database file full path. This is required for SQLite
# * type -> This dictates which database vendor you are using. We currently support PostgreSQL, MySQL and SQLite. If no value is entered then it will default to MySQL. Do note that this is an atom and not a string
"""
end

Expand Down
4 changes: 4 additions & 0 deletions lib/database/common.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ defmodule Plsm.Database.Common do
IO.puts("Using PostgreSQL...")
Plsm.Database.create(%Plsm.Database.PostgreSQL{}, configs)

:sqlite ->
IO.puts("Using SQLite...")
Plsm.Database.create(%Plsm.Database.SQLite{}, configs)

_ ->
IO.puts("Using default database MySql...")
Plsm.Database.create(%Plsm.Database.MySql{}, configs)
Expand Down
87 changes: 87 additions & 0 deletions lib/database/sqlite.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
defmodule Plsm.Database.SQLite do
defstruct filename: "filename.sqlite",
connection: nil
end

defimpl Plsm.Database, for: Plsm.Database.SQLite do
@doc """
Create a SQLite database struct. We pass in the configs in order to
properly open the SQLite database
"""
@spec create(Plsm.Database.SQLite, Plsm.Configs) :: Plsm.Database.SQLite
def create(_db, configs) do
%Plsm.Database.SQLite{
filename: configs.database.filename,
}
end

@spec connect(Plsm.Database.SQLite) :: Plsm.Database.SQLite
def connect(db) do
{_, conn} = Exqlite.Sqlite3.open(db.filename)

%Plsm.Database.SQLite{
connection: conn,
filename: db.filename,
}
end

@spec get_tables(Plsm.Database.SQLite) :: [Plsm.Database.TableHeader]
def get_tables(db) do
{_, statement} = Exqlite.Sqlite3.prepare(db.connection, "
SELECT
name
FROM
sqlite_schema
WHERE
type = 'table'
AND name NOT LIKE 'sqlite_%'"
)

{_, result} = Exqlite.Sqlite3.fetch_all(db.connection, statement)

result
|> List.flatten()
|> Enum.map(fn x -> %Plsm.Database.TableHeader{database: db, name: x} end)
end

@spec get_columns(Plsm.Database.SQLite, Plsm.Database.Table) :: [Plsm.Database.Column]
def get_columns(db, table) do
{_, statement} = Exqlite.Sqlite3.prepare(db.connection, "PRAGMA table_info(#{table.name})")
{_, result} = Exqlite.Sqlite3.fetch_all(db.connection, statement)

result
|> Enum.map(&to_column/1)
end

defp to_column(row) do
{_, name} = Enum.fetch(row, 1)
type = Enum.fetch(row, 2) |> get_type
{_, pk} = Enum.fetch(row, 5)
primary_key? = pk == 1
%Plsm.Database.Column{name: name, type: type, primary_key: primary_key?}
end

defp get_type(start_type) do
{_, type} = start_type
downcase = String.downcase(type)

cond do
# Determines types using the rules under paragraph 3.1 in SQLite docs:
# https://www.sqlite.org/datatype3.html
String.contains?(downcase, "int") -> :integer
String.contains?(downcase, "char") -> :string
String.contains?(downcase, "clob") -> :string
String.contains?(downcase, "text") -> :string
String.contains?(downcase, "real") -> :float
String.contains?(downcase, "floa") -> :float
String.contains?(downcase, "doub") -> :float

# Useful use cases
String.starts_with?(downcase, "boolean") -> :boolean
String.starts_with?(downcase, "text_datetime") -> :date
String.starts_with?(downcase, "int_datetime") -> :date

true -> :none
end
end
end
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ defmodule Plsm.Mixfile do
end

def application do
[applications: [:postgrex, :myxql]]
[applications: [:postgrex, :myxql, :exqlite]]
end

defp deps do
[
{:ex_doc, "~> 0.23.0", only: :dev, runtime: false},
{:myxql, "~> 0.4.4"},
{:postgrex, "~> 0.15"},
{:exqlite, "~> 0.8.4"},
{:ecto_sql, "~> 3.5.3", only: :test},
{:mock, "~> 0.2.0", only: :test}
]
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"},
"ecto": {:hex, :ecto, "3.5.5", "48219a991bb86daba6e38a1e64f8cea540cded58950ff38fbc8163e062281a07", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "98dd0e5e1de7f45beca6130d13116eae675db59adfa055fb79612406acf6f6f1"},
"ecto_sql": {:hex, :ecto_sql, "3.5.3", "1964df0305538364b97cc4661a2bd2b6c89d803e66e5655e4e55ff1571943efd", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.5.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2f53592432ce17d3978feb8f43e8dc0705e288b0890caf06d449785f018061c"},
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
"ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"},
"exqlite": {:hex, :exqlite, "0.8.4", "6cbfcd15d1307727615c8ce33589a0498e34c1020a3401bba4dd9ae723e99cc6", [:make, :mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "791fee489e0529b24218d49f39e3a9ed64fd2d26a2110a3fab3e26d8c8524b44"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
Expand Down
26 changes: 25 additions & 1 deletion test/plsm_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,31 @@ defmodule PlsmTest do
test "schema files are generated and can compile" do
Mix.Tasks.Plsm.run([])

assert :ok == IEx.Helpers.recompile()
assert :ok == IEx.Helpers.recompile([force: true])
end
end

describe "plsm task using sqlite" do
setup do
Application.put_env(:plsm, :filename, File.cwd! <> "/test/support/schemas/sqlite/test.sqlite")
Application.put_env(:plsm, :type, :sqlite)
Application.put_env(:plsm, :module_name, "PlsmTest")
Application.put_env(:plsm, :destination, @schema_dir)

:ok
end

test "schema files are generated and can compile" do
Mix.Tasks.Plsm.run([])

assert :ok == IEx.Helpers.recompile([force: true])

assert File.exists?(@schema_dir <> "test_entity_one.ex")
assert File.exists?(@schema_dir <> "test_entity_two.ex")

File.ls!("#{@schema_dir}")
|> Enum.filter(fn file -> !String.starts_with?(file, ".") end)
|> Enum.each(fn file -> File.rm!(@schema_dir <> file) end)
end
end
end
Binary file added test/support/schemas/sqlite/test.sqlite
Binary file not shown.