Skip to content

Commit 7a18e66

Browse files
committed
first commit
0 parents  commit 7a18e66

26 files changed

+1494
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.stack-work/
2+
*~

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Changelog for `sql2er`
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to the
7+
[Haskell Package Versioning Policy](https://pvp.haskell.org/).
8+
9+
## Unreleased
10+
11+
## 0.1.0.0 - YYYY-MM-DD

Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM alpine:3.20
2+
3+
COPY sql2er-exe /app/sql2er
4+
5+
RUN apk add libc6-compat gmp-dev
6+
7+
WORKDIR /app
8+
9+
ENTRYPOINT [ "./sql2er" ]

LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2024 tushar
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
[![Contributors][contributors-shield]][contributors-url]
2+
[![Forks][forks-shield]][forks-url]
3+
[![Stargazers][stars-shield]][stars-url]
4+
[![Issues][issues-shield]][issues-url]
5+
[![MIT License][license-shield]][license-url]
6+
[![LinkedIn][linkedin-shield]][linkedin-url]
7+
8+
<!-- PROJECT LOGO -->
9+
<br />
10+
<div align="center">
11+
<a href="https://github.com/tusharad/sql2er">
12+
<img src="example/logo.jpeg" alt="Logo" width="180" height="180">
13+
</a>
14+
15+
<h3 align="center">SQL 2 ER</h3>
16+
17+
<p align="center">
18+
SQL2ER is a command line tool that takes SQL script as input and generates an ER diagram. The parser is written to adopt PostgreSQL syntax.
19+
<a href="https://github.com/tusharad/sql2er/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
20+
·
21+
<a href="https://github.com/tusharad/sql2er/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
22+
</p>
23+
</div>
24+
25+
### Example
26+
27+
test.sql
28+
29+
```sql
30+
create table department (dep_id serial primary key, dep_name varchar(30), createAt timestamptz default now());
31+
create table employee ( employee_id serial primary key, employee_name varchar(30), employee_age int,
32+
dep_id int references department (dep_id) on delete cascade, createAt timestamptz default now());
33+
create table tasks (task_id int, task_name text);
34+
```
35+
36+
```bash
37+
./sql2er-exe test.sql -o erd.svg
38+
```
39+
40+
#### Output
41+
42+
<img src="example/erd.svg" alt="Logo" width="100%" height="580">
43+
44+
## Getting Started
45+
46+
Here are 2 ways to use this tool
47+
48+
1. Download the binary from release and simply run it.
49+
50+
```bash
51+
./sql2er-exe test.sql -o erd.svg
52+
```
53+
54+
2. Build from source:
55+
56+
You can download [stack](https://docs.haskellstack.org/en/stable/) via [GHCup](https://www.haskell.org/ghcup/).
57+
58+
then simply build and run using below command from the root directory of the project.
59+
60+
```
61+
stack run -- test.sql -o erd.svg
62+
```
63+
64+
### Built With
65+
66+
[![Haskell][Haskell]][Haskell-url]
67+
68+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
69+
70+
<!-- ROADMAP -->
71+
## Roadmap
72+
73+
- [x] Add Changelog
74+
- [ ] Add Additional Examples
75+
- [ ] Add more parsing functions
76+
- [ ] Add more documentation
77+
78+
See the [open issues](https://github.com/tusharad/sql2er/issues) for a full list of proposed features (and known issues).
79+
80+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
81+
82+
### Limitations:
83+
- The parser is not smart enough to find syntactical errors in the SQL. It will only extract neccecary information for it to generate an ERD.
84+
- ForeignKeyConstraint can only handle a single column and not a list of column.
85+
- The tool is mainly tested and is following postgres version 17 documentation.
86+
- Not able to parse (to ignore) `Create function` statements. If it came across `create function` statement, it will take rest and stop parsing.
87+
88+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
89+
90+
### does not support:
91+
- detach
92+
- using ...
93+
- tablespace
94+
- not valid
95+
- validate
96+
- begin/commit
97+
98+
<!-- ACKNOWLEDGMENTS -->
99+
## Acknowledgments
100+
101+
- This tool is inspired by [sqldiagram](https://github.com/RadhiFadlillah/sqldiagram) but the tool was created for MySQL and the parser was weak.
102+
103+
<p align="right">(<a href="#readme-top">back to top</a>)</p>
104+
105+
<!-- MARKDOWN LINKS & IMAGES -->
106+
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
107+
[contributors-shield]: https://img.shields.io/github/contributors/tusharad/sql2er.svg?style=for-the-badge
108+
[contributors-url]: https://github.com/tusharad/sql2er/graphs/contributors
109+
[forks-shield]: https://img.shields.io/github/forks/tusharad/sql2er.svg?style=for-the-badge
110+
[forks-url]: https://github.com/tusharad/sql2er/network/members
111+
[stars-shield]: https://img.shields.io/github/stars/tusharad/sql2er.svg?style=for-the-badge
112+
[stars-url]: https://github.com/tusharad/sql2er/stargazers
113+
[issues-shield]: https://img.shields.io/github/issues/tusharad/sql2er.svg?style=for-the-badge
114+
[issues-url]: https://github.com/tusharad/sql2er/issues
115+
[license-shield]: https://img.shields.io/github/license/tusharad/sql2er.svg?style=for-the-badge
116+
[license-url]: https://github.com/tusharad/sql2er/blob/main/LICENSE.txt
117+
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
118+
[linkedin-url]: https://linkedin.com/in/tushar-adhatrao
119+
[Haskell]: https://img.shields.io/badge/Haskell-5e5086?style=for-the-badge&logo=haskell&logoColor=white
120+
[Haskell-url]: https://www.haskell.org/

Setup.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain

app/Main.hs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
3+
module Main (main) where
4+
5+
import Control.Exception
6+
import Data.List (find)
7+
import qualified Data.Text as T
8+
import qualified Data.Text.IO as T
9+
import Sql2er.Common.Types
10+
import Sql2er.Parser (parseSqlScript)
11+
import System.Exit (exitFailure)
12+
import Text.Megaparsec
13+
import Sql2er.ERDiagram (renderErDiagram)
14+
import Sql2er.CmdArgs (Options(..), parseCmdArgs)
15+
16+
-- This function will find all the create table statements,
17+
-- alter or drop the tables based on statements.
18+
postParsingSetup :: [Statement] -> [Table] -> [Table]
19+
postParsingSetup [] r = r
20+
postParsingSetup (statement : statements) resTable = do
21+
case statement of
22+
AlterStatement alterStatement -> do
23+
let alteredTableName = alterTableName alterStatement
24+
let actions = action alterStatement
25+
case find (\x -> alteredTableName == tableName x) resTable of
26+
Nothing -> postParsingSetup statements resTable -- Table name of alter statement not found
27+
Just alteringTable -> do
28+
let res = applyAlteration alteringTable actions
29+
newList = removeElem alteringTable resTable
30+
postParsingSetup statements (res : newList)
31+
DropStatement dropStatement -> postParsingSetup statements (dropTables (tableNames dropStatement) resTable)
32+
CreateStatement s -> postParsingSetup statements (s : resTable)
33+
CreateTypeStatement _ -> postParsingSetup statements resTable
34+
EOF -> postParsingSetup statements resTable
35+
where
36+
dropTables :: [TableName] -> [Table] -> [Table]
37+
dropTables tNames = filter (\t -> tableName t `notElem` tNames)
38+
39+
applyAlterAction :: Table -> AlterTableAction -> Table
40+
applyAlterAction alteringTable act = do
41+
case act of
42+
RenameColumn oldCol newCol -> do
43+
case find (\x -> oldCol == columnName x) (columns alteringTable) of
44+
Nothing -> alteringTable
45+
Just col -> do
46+
let listWithoutElem = removeElem col (columns alteringTable)
47+
listWithNewElem = (col {columnName = newCol}) : listWithoutElem
48+
alteringTable {columns = listWithNewElem}
49+
AddColumn col -> alteringTable {columns = col : columns alteringTable}
50+
DropColumn col _ -> do
51+
case find (\x -> col == columnName x) (columns alteringTable) of
52+
Nothing -> alteringTable
53+
Just col0 -> alteringTable {columns = removeElem col0 (columns alteringTable)}
54+
RenameTable newTableName -> alteringTable {tableName = newTableName}
55+
SetSchema schemaName -> alteringTable {tableName = schemaName <> "." <> tableName alteringTable}
56+
AlterColumnSetType colName sqlType mDefaultVal -> do
57+
case find (\x -> colName == columnName x) (columns alteringTable) of
58+
Nothing -> alteringTable
59+
Just col -> do
60+
let listWithoutElem = removeElem col (columns alteringTable)
61+
let listWithNewElem =
62+
case mDefaultVal of
63+
Nothing -> (col {columnType = sqlType}) : listWithoutElem
64+
Just dVal -> do
65+
let newCConstraints =
66+
removeElemP
67+
( \x ->
68+
case x of
69+
(Default _) -> True
70+
_ -> False
71+
)
72+
(cConstraints col)
73+
( col
74+
{ columnType = sqlType
75+
, cConstraints = Default dVal : newCConstraints
76+
}
77+
)
78+
: listWithoutElem
79+
alteringTable {columns = listWithNewElem}
80+
AlterColumnSetDefault colName dVal -> do
81+
case find (\x -> colName == columnName x) (columns alteringTable) of
82+
Nothing -> alteringTable
83+
Just col -> do
84+
let listWithoutElem = removeElem col (columns alteringTable)
85+
let newCConstraints =
86+
removeElemP
87+
( \x ->
88+
case x of
89+
(Default _) -> True
90+
_ -> False
91+
)
92+
(cConstraints col)
93+
let listWithNewElem = col {cConstraints = Default dVal : newCConstraints} : listWithoutElem
94+
alteringTable {columns = listWithNewElem}
95+
AlterColunmnDropDefault colName -> do
96+
case find (\x -> colName == columnName x) (columns alteringTable) of
97+
Nothing -> alteringTable
98+
Just col -> do
99+
let listWithoutElem = removeElem col (columns alteringTable)
100+
let newCConstraints =
101+
removeElemP
102+
( \x ->
103+
case x of
104+
(Default _) -> True
105+
_ -> False
106+
)
107+
(cConstraints col)
108+
alteringTable {columns = (col {cConstraints = newCConstraints}) : listWithoutElem}
109+
AlterColumnSetNotNull colName -> do
110+
case find (\x -> colName == columnName x) (columns alteringTable) of
111+
Nothing -> alteringTable
112+
Just col -> do
113+
let listWithoutElem = removeElem col (columns alteringTable)
114+
alteringTable
115+
{ columns = (col {cConstraints = NotNull : (cConstraints col)}) : listWithoutElem
116+
}
117+
AlterColumnDropNotNull colName -> do
118+
case find (\x -> colName == columnName x) (columns alteringTable) of
119+
Nothing -> alteringTable
120+
Just col -> do
121+
let listWithoutElem = removeElem col (columns alteringTable)
122+
let newCConstraints =
123+
removeElemP
124+
( \x ->
125+
case x of
126+
NotNull -> True
127+
_ -> False
128+
)
129+
(cConstraints col)
130+
alteringTable
131+
{ columns = (col {cConstraints = newCConstraints}) : listWithoutElem
132+
}
133+
AddTableConstraint tConstraint ->
134+
alteringTable
135+
{ tableConstraints = tConstraint : tableConstraints alteringTable
136+
}
137+
DropTableConstriant _ -> alteringTable -- Cannot implement since we are not tracking constraint name
138+
RenameConstraint _ _ -> alteringTable -- Cannot implement since we are not tracking constraint name
139+
applyAlteration :: Table -> [AlterTableAction] -> Table
140+
applyAlteration = foldl applyAlterAction
141+
142+
removeElemP :: (a -> Bool) -> [a] -> [a]
143+
removeElemP p lst = helper lst []
144+
where
145+
helper [] res = res
146+
helper (x : xs) res = if p x then res ++ xs else helper xs (x : res)
147+
148+
removeElem :: Eq a => a -> [a] -> [a]
149+
removeElem ele lst = helper lst []
150+
where
151+
helper [] res = res
152+
helper (x : xs) res = if ele == x then res ++ xs else helper xs (x : res)
153+
154+
main :: IO ()
155+
main = do
156+
opts <- parseCmdArgs
157+
content <- T.toLower
158+
<$> T.readFile (sqlFile opts)
159+
`catch` ( \e -> do
160+
print (e :: SomeException)
161+
exitFailure
162+
)
163+
case parse parseSqlScript "sql parser" content of
164+
Left _ -> putStrLn "parsing failed :("
165+
Right r -> do
166+
let tableList = postParsingSetup r []
167+
renderErDiagram
168+
(outputFile opts)
169+
(width opts)
170+
(height opts)
171+
tableList

example/erd.svg

Lines changed: 3 additions & 0 deletions
Loading

example/logo.jpeg

163 KB
Loading

example/test.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
create table department (dep_id serial primary key, dep_name varchar(30), createAt timestamptz default now());
2+
create table employee ( employee_id serial primary key, employee_name varchar(30), employee_age int,
3+
dep_id int references department (dep_id) on delete cascade, createAt timestamptz default now());
4+
create table tasks (task_id int, task_name text);

0 commit comments

Comments
 (0)