Skip to content

Commit

Permalink
Added WASM
Browse files Browse the repository at this point in the history
  • Loading branch information
tusharad committed Nov 13, 2024
1 parent 2213f60 commit 2258aeb
Show file tree
Hide file tree
Showing 10 changed files with 438 additions and 151 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,25 @@ CREATE TABLE tasks (

1. Install [Stack](https://docs.haskellstack.org/en/stable/) via [GHCup](https://www.haskell.org/ghcup/).
2. Clone the repository and navigate to the project root.
3. Build and run the project:
3. Build the binary for linux:

```bash
stack run -- test.sql -o erd.svg
stack build :sql2er-exe
cp $(stack path --local-install-root)/bin/sql2er-exe .
./sql2er-exe test.sql -o erd.svg
```

### Option 3: Build WASM

1. Install `wasm32-wasi-cabal` from [here](https://gitlab.haskell.org/ghc/ghc-wasm-meta)
2. Make sure to download the `9.8` `FLAVOUR`.

```bash
wasm32-wasi-cabal build sql2er-wasm
cp path/to/sql2er-wasm.wasm .
python3 -m http.server
```

---

## Built With
Expand Down
142 changes: 1 addition & 141 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,154 +3,14 @@
module Main (main) where

import Control.Exception
import Data.List (find)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Sql2er.Common.Types
import Sql2er.Parser (parseSqlScript)
import Sql2er.Parser (parseSqlScript, postParsingSetup)
import System.Exit (exitFailure)
import Text.Megaparsec
import ERDiagram (renderErDiagram)
import Sql2er.CmdArgs (Options(..), parseCmdArgs)

-- This function will find all the create table statements,
-- alter or drop the tables based on statements.
postParsingSetup :: [Statement] -> [Table] -> [Table]
postParsingSetup [] r = r
postParsingSetup (statement : statements) resTable = do
case statement of
AlterStatement alterStatement -> do
let alteredTableName = alterTableName alterStatement
let actions = action alterStatement
case find (\x -> alteredTableName == tableName x) resTable of
Nothing -> postParsingSetup statements resTable -- Table name of alter statement not found
Just alteringTable -> do
let res = applyAlteration alteringTable actions
newList = removeElem alteringTable resTable
postParsingSetup statements (res : newList)
DropStatement dropStatement -> postParsingSetup statements (dropTables (tableNames dropStatement) resTable)
CreateStatement s -> postParsingSetup statements (s : resTable)
CreateTypeStatement _ -> postParsingSetup statements resTable
EOF -> postParsingSetup statements resTable
where
dropTables :: [TableName] -> [Table] -> [Table]
dropTables tNames = filter (\t -> tableName t `notElem` tNames)

applyAlterAction :: Table -> AlterTableAction -> Table
applyAlterAction alteringTable act = do
case act of
RenameColumn oldCol newCol -> do
case find (\x -> oldCol == columnName x) (columns alteringTable) of
Nothing -> alteringTable
Just col -> do
let listWithoutElem = removeElem col (columns alteringTable)
listWithNewElem = (col {columnName = newCol}) : listWithoutElem
alteringTable {columns = listWithNewElem}
AddColumn col -> alteringTable {columns = col : columns alteringTable}
DropColumn col _ -> do
case find (\x -> col == columnName x) (columns alteringTable) of
Nothing -> alteringTable
Just col0 -> alteringTable {columns = removeElem col0 (columns alteringTable)}
RenameTable newTableName -> alteringTable {tableName = newTableName}
SetSchema schemaName -> alteringTable {tableName = schemaName <> "." <> tableName alteringTable}
AlterColumnSetType colName sqlType mDefaultVal -> do
case find (\x -> colName == columnName x) (columns alteringTable) of
Nothing -> alteringTable
Just col -> do
let listWithoutElem = removeElem col (columns alteringTable)
let listWithNewElem =
case mDefaultVal of
Nothing -> (col {columnType = sqlType}) : listWithoutElem
Just dVal -> do
let newCConstraints =
removeElemP
( \x ->
case x of
(Default _) -> True
_ -> False
)
(cConstraints col)
( col
{ columnType = sqlType
, cConstraints = Default dVal : newCConstraints
}
)
: listWithoutElem
alteringTable {columns = listWithNewElem}
AlterColumnSetDefault colName dVal -> do
case find (\x -> colName == columnName x) (columns alteringTable) of
Nothing -> alteringTable
Just col -> do
let listWithoutElem = removeElem col (columns alteringTable)
let newCConstraints =
removeElemP
( \x ->
case x of
(Default _) -> True
_ -> False
)
(cConstraints col)
let listWithNewElem = col {cConstraints = Default dVal : newCConstraints} : listWithoutElem
alteringTable {columns = listWithNewElem}
AlterColunmnDropDefault colName -> do
case find (\x -> colName == columnName x) (columns alteringTable) of
Nothing -> alteringTable
Just col -> do
let listWithoutElem = removeElem col (columns alteringTable)
let newCConstraints =
removeElemP
( \x ->
case x of
(Default _) -> True
_ -> False
)
(cConstraints col)
alteringTable {columns = (col {cConstraints = newCConstraints}) : listWithoutElem}
AlterColumnSetNotNull colName -> do
case find (\x -> colName == columnName x) (columns alteringTable) of
Nothing -> alteringTable
Just col -> do
let listWithoutElem = removeElem col (columns alteringTable)
alteringTable
{ columns = (col {cConstraints = NotNull : (cConstraints col)}) : listWithoutElem
}
AlterColumnDropNotNull colName -> do
case find (\x -> colName == columnName x) (columns alteringTable) of
Nothing -> alteringTable
Just col -> do
let listWithoutElem = removeElem col (columns alteringTable)
let newCConstraints =
removeElemP
( \x ->
case x of
NotNull -> True
_ -> False
)
(cConstraints col)
alteringTable
{ columns = (col {cConstraints = newCConstraints}) : listWithoutElem
}
AddTableConstraint tConstraint ->
alteringTable
{ tableConstraints = tConstraint : tableConstraints alteringTable
}
DropTableConstriant _ -> alteringTable -- Cannot implement since we are not tracking constraint name
RenameConstraint _ _ -> alteringTable -- Cannot implement since we are not tracking constraint name
applyAlteration :: Table -> [AlterTableAction] -> Table
applyAlteration = foldl applyAlterAction

removeElemP :: (a -> Bool) -> [a] -> [a]
removeElemP p lst = helper lst []
where
helper [] res = res
helper (x : xs) res = if p x then res ++ xs else helper xs (x : res)

removeElem :: Eq a => a -> [a] -> [a]
removeElem ele lst = helper lst []
where
helper [] res = res
helper (x : xs) res = if ele == x then res ++ xs else helper xs (x : res)

main :: IO ()
main = do
opts <- parseCmdArgs
Expand Down
183 changes: 183 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL to Mermaid ER Diagram</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f4f4f8;
}

header {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
text-align: center;
font-size: 1.5rem;
font-weight: 600;
}

main {
display: flex;
flex: 1;
}

.left-panel {
width: 40%;
padding: 20px;
background: #fff;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
}

.left-panel textarea {
width: 100%;
height: 80%;
padding: 10px;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 5px;
resize: none;
}

.left-panel button {
margin-top: 10px;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
}

.left-panel button:hover {
background-color: #45a049;
}

.right-panel {
flex: 1;
padding: 20px;
}

.right-panel pre {
background: #2d2d2d;
color: #f4f4f8;
padding: 20px;
border-radius: 5px;
font-size: 1rem;
overflow: auto;
height: 100%;
}
</style>
</head>

<body>
<header>SQL to Mermaid ER Diagram</header>
<main>
<div class="left-panel">
<h2>SQL Query</h2>
<textarea id="sqlInput" placeholder="Type your SQL query here..."></textarea>
<button id="runBtn">Run</button>
</div>

<div class="right-panel">
<h2>Mermaid Diagram</h2>
<pre class="mermaid" id="mermaidOutput">
erDiagram
customer {
string name
string custNumber
string sector
}
order {
int orderNumber
string deliveryAddress
}
customer ||--o{ order : places
</pre>
</div>
</main>

<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

mermaid.initialize({ startOnLoad: true });

document.getElementById('runBtn').addEventListener('click', () => {
const sqlQuery = document.getElementById('sqlInput').value;
const diagram = generate(sqlQuery);
const mermaidOutput = document.getElementById('mermaidOutput');
mermaidOutput.textContent = diagram;
mermaidOutput.removeAttribute("data-processed");
mermaid.run();
});

// Mock function to simulate SQL to Mermaid ER Diagram conversion
function generate(sqlQuery) {
// Replace this with the actual logic for SQL parsing and Mermaid ER generation
const res = echo(sqlQuery);
console.log("got res:", res);
return res;
}

import { WASI } from 'https://cdn.jsdelivr.net/npm/@bjorn3/browser_wasi_shim@0.3.0/+esm'

const wasi = new WASI([], [], []);
const wasm = await WebAssembly.compileStreaming(fetch("sql2er-wasm.wasm"));

let inst = await WebAssembly.instantiate(wasm, {
"wasi_snapshot_preview1": wasi.wasiImport,
});

wasi.initialize(inst);
inst.exports.hs_init(0, 0);

function bufferAt(pos, len) {
return new Uint8Array(inst.exports.memory.buffer, pos, len);
}

function cstringBufferAt(cstr) {
let b = new Uint8Array(inst.exports.memory.buffer, cstr);
let l = b.findIndex(i => i == 0, b);
return bufferAt(cstr, l);
}

function withCStrings(strs, op) {
const cstrs = strs.map(str => {
const s = new TextEncoder().encode(str);
const l = s.length + 1;
const p = inst.exports.callocBuffer(l);
const b = new bufferAt(p, l);
b.set(s);
return p;
});
const r = op(cstrs);
cstrs.forEach(inst.exports.freeBuffer);
return r;
}

function withCString(str, op) {
return withCStrings([str], strs => op(strs[0]));
}

function fromCString(cstr) {
const s = new TextDecoder("utf8").decode(cstringBufferAt(cstr));
return s;
}

function echo(str) {
return fromCString(withCString(str, cstr => inst.exports.hs_runWorker(cstr)));
}
</script>

</body>
</html>
2 changes: 1 addition & 1 deletion package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ executables:
ghc-options:
- -no-hs-main
- -optl-mexec-model=reactor
- "-optl-Wl,--export=hs_runWorker"
- "-optl-Wl,--export=hs_init,--export=hs_runWorker,--export=freeBuffer,--export=callocBuffer"
dependencies:
- sql2er

Expand Down
3 changes: 2 additions & 1 deletion sql2er.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ library
Sql2er.CmdArgs
Sql2er.Common.Types
Sql2er.Common.Utils
Sql2er.Mermaid
Sql2er.Parser
Sql2er.Parser.AlterTable
Sql2er.Parser.Common
Expand Down Expand Up @@ -74,7 +75,7 @@ executable sql2er-wasm
Paths_sql2er
hs-source-dirs:
wasm
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -no-hs-main -optl-mexec-model=reactor -optl-Wl,--export=hs_runWorker
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -no-hs-main -optl-mexec-model=reactor -optl-Wl,--export=hs_init,--export=hs_runWorker,--export=freeBuffer,--export=callocBuffer
build-depends:
base >=4.7 && <5
, megaparsec
Expand Down
Loading

0 comments on commit 2258aeb

Please sign in to comment.