module Database where
import qualified Data.Map
import Data.Maybe (fromJust, isNothing)
import Data.List (elemIndex, elemIndices)
import Data.Sort(sortBy)
import qualified ExpressionParser as Exp
import qualified Text.Parsec.Error
import qualified Funcs as F
sampleCommands = do
print "======================================="
let my_db = newDatabase "my_db 1" Data.Map.empty
let my_db_1 = addNewTable "table 1" my_db
let my_db = my_db_1
let my_db_1 = addNewTable "table 2" my_db
let my_db = my_db_1
let contains = containsTable "table 1" my_db
let my_db_1 = addColumn "Name" STRING my_db "table 1"
let my_db = my_db_1
let my_db_1 = addColumn "Phone_number" INT my_db "table 1"
let my_db = addColumn "BoolCol" BOOL my_db_1 "table 1"
let my_db_1 = insert ["Name", "Phone_number", "BoolCol"] ["Martha", "12345", "False"] [STRING, INT, BOOL] my_db "table 1"
let my_db = my_db_1
let my_db_1 = insert ["BoolCol", "Name", "Phone_number"] ["True", "Stewart", "78910"] [BOOL, STRING, INT] my_db "table 1"
let m = F.parseWithWSEof (Exp.valueExpr []) "BoolCol"
let n = F.parseWithWSEof (Exp.valueExpr []) "Phone_number > 0"
let o = F.parseWithWSEof (Exp.valueExpr []) "Phone_number = 78910"
let p = F.parseWithWSEof (Exp.valueExpr []) "-1 * Phone_number"
let x = delete o my_db_1 "table 1"
print("Y=-------------------->")
let x = find n my_db_1 "table 1"
let y = orderBy p x
print y
let y = select [("BoolCol", "IsFemale"), ("Phone_number", "home_number")] $ orderBy p x
let x = find o my_db_1 "table 1"
return 0
data Column = Column {
cName :: String,
cDatatype :: Datatype,
cValues :: [String]
} deriving (Show, Eq)
newColumn :: String -> Datatype -> [String] -> Maybe Column
newColumn name datatype values = Just Column {
cName = name,
cDatatype = datatype,
cValues = values
}
data Table = Table {
tName :: String,
tColNameList :: [String],
tColumns :: Data.Map.Map String Column,
tPKeys :: [Int]
} deriving (Show, Eq)
newTable :: String -> ([String], Data.Map.Map String Column) -> [Int] -> Maybe Table
newTable name (colNameList, columns) pKeys= Just Table {
tName = name,
tColNameList = colNameList,
tColumns = columns,
tPKeys = pKeys
}
data Database = Database {
dName :: String,
dTables :: Data.Map.Map String Table
} deriving (Show, Eq)
newDatabase :: String -> Data.Map.Map String Table -> Maybe Database
newDatabase name tables = Just Database {
dName = name,
dTables = tables
}
data Datatype = INT | STRING | BOOL deriving (Show, Eq)
containsTable :: String -> Maybe Database -> Bool
containsTable tableName database = not (isNothing $ getTable tableName database)
containsColumn :: String -> Maybe Database -> String -> Bool
containsColumn columnName database tableName = elem columnName (tColNameList $ fromJust $ getTable tableName database)
getTable :: String -> Maybe Database -> Maybe Table
getTable tableName database
| isNothing database = Nothing
| otherwise = Data.Map.lookup tableName $ dTables $ fromJust database
count :: Maybe Table -> Int
count table
| isNothing table || Data.Map.size (tColumns $ fromJust table) == 0 = 0
| otherwise = let firstCol = snd $ Data.Map.elemAt 0 (tColumns $ fromJust table)
in length (cValues firstCol)
getColumn :: String -> Maybe Table -> Maybe Column
getColumn columnName table
| isNothing table = Nothing
| otherwise = Data.Map.lookup columnName (tColumns $ fromJust table)
addNewTable :: String -> Maybe Database -> Maybe Database
addNewTable tableName database
| isNothing database = Nothing
| otherwise = Just Database {
dName = dName $ fromJust database,
dTables = Data.Map.insert tableName (fromJust $ newTable tableName ([],Data.Map.empty) []) (dTables $ fromJust database)
}
addColumnToTable :: String -> Datatype -> Table -> Table
addColumnToTable columnName datatype table = fromJust $ newTable (tName table)
((tColNameList table) ++ [columnName], Data.Map.insert columnName (fromJust $ newColumn columnName datatype []) (tColumns table)) (tPKeys table)
addColumn :: String -> Datatype -> Maybe Database -> String -> Maybe Database
addColumn columnName datatype database tableName
| isNothing database || not (containsTable tableName database) = database
| otherwise = newDatabase (dName $ fromJust database)
(Data.Map.adjust (addColumnToTable columnName datatype) tableName $ dTables $ fromJust database)
insertOne :: String -> Datatype -> Maybe Database -> String -> String -> Maybe Database
insertOne value datatype database tableName columnName
| isNothing database || not (containsTable tableName database) || not (containsColumn columnName database tableName)
= database
| datatype /= cDatatype (fromJust $ getColumn columnName $ getTable tableName database )= database
| otherwise = newDatabase (dName $ fromJust database) (Data.Map.adjust f tableName $ dTables $ fromJust database)
where f = (\table -> fromJust $ newTable (tName table) (tColNameList table, (Data.Map.adjust g columnName $ tColumns table)) (tPKeys table))
g = (\column -> fromJust $ newColumn (cName column) (cDatatype column) ((cValues column) ++ [value]))
isValidDatatype :: Maybe Table -> [String] -> [Datatype] -> Bool
isValidDatatype t colList typeList
| isNothing t || (colList == [] && typeList == [] )= True
| colList == [] || typeList == [] = False
| otherwise =
let (dType:dTypes) = typeList
(col:cols) = colList
in (dType == cDatatype (fromJust $ getColumn col t)) && (isValidDatatype t cols dTypes)
addPrimaryKeyToTable :: Table -> Table
addPrimaryKeyToTable table = fromJust $ newTable (tName table) (tColNameList table, tColumns table) (let pKeys = tPKeys table in if pKeys == [] then [0] else pKeys ++ [maximum pKeys + 1])
addPrimaryKey :: Maybe Database -> String -> Maybe Database
addPrimaryKey db tableName = newDatabase (dName $ fromJust db)
(Data.Map.adjust addPrimaryKeyToTable tableName $ dTables $ fromJust db)
insert :: [String] -> [String] -> [Datatype] -> Maybe Database -> String -> Maybe Database
insert columnNames values datatypes db tableName
| length values /= length datatypes || not (isValidDatatype (getTable tableName db) columnNames datatypes) || length columnNames /= length values = db
| length values == 0 = addPrimaryKey db tableName
| otherwise = do
let v = head values; vs = tail values
let d = head datatypes; ds = tail datatypes
let col = head columnNames; cols = tail columnNames
let new_db = insertOne v d db tableName col
insert cols vs ds new_db tableName
insertDefault :: [String] -> Maybe Database -> String -> Maybe Database
insertDefault values db tableName
| otherwise =
let colNameList = tColNameList $ fromJust table
in insert colNameList values (map (\name -> cDatatype $ fromJust $ getColumn name table) colNameList) db tableName
where table = getTable tableName db
colNameList = if isNothing table then [] else tColNameList $ fromJust table
getColList table db= Data.Map.elems (tColumns $ fromJust $ getTable table db)
findEntryAtIndex :: String -> Maybe Database -> Int -> [(Int, String, Datatype, String)]
findEntryAtIndex tableName db index = [ cell | col <- getColList tableName db,
let cell = ((tPKeys $ fromJust $ getTable tableName db) !! index, cName col, cDatatype col, (cValues col) !! index)]
litValue :: String -> Datatype -> Exp.ValueExpr
litValue value datatype
| datatype == INT = Exp.NumLit (read value :: Integer)
| datatype == BOOL = if value == "True" then Exp.BoolLit True else Exp.BoolLit False
| datatype == STRING = Exp.StringLit value
find :: Either Text.Parsec.Error.ParseError Exp.ValueExpr -> Maybe Database -> String -> [[(Int, String, Datatype, String)]]
find (Right condition) db tableName
| isNothing db || not (containsTable tableName db) = []
| otherwise =
let table = getTable tableName db
nEntries = count table
columns = Data.Map.elems $ tColumns $ fromJust table
indices = [n | n <- [0..nEntries-1], let map = Data.Map.fromList [(colName, value) | col <- columns, let colName = cName col, let value = litValue ((cValues col) !! n) (cDatatype col)], Exp.evaluateExpr2 map condition]
in [findEntryAtIndex tableName db n | n <- indices]
deleteFromList :: Int -> [a] -> [a]
deleteFromList index xs = (take index xs) ++ reverse(take (length xs - index - 1) (reverse xs))
deleteEntryAtIndex :: Int -> Maybe Database -> String -> Maybe Database
deleteEntryAtIndex index db tableName
| isNothing db = Nothing
| otherwise = do
let my_db = fromJust db
newDatabase (dName my_db) (Data.Map.adjust f tableName $ dTables my_db)
where f = \table -> (fromJust $ newTable (tName table) (tColNameList table, (Data.Map.fromList new_cols)) (deleteFromList index $ tPKeys table))
cols = Data.Map.elems $ tColumns $ fromJust $ getTable tableName db
new_cols = [(cName col, fromJust $ newColumn (cName col) (cDatatype col) (deleteFromList index $ cValues col)) | col <- cols]
deleteEntryAtIndices :: [Int] -> Maybe Database -> String -> Maybe Database
deleteEntryAtIndices indexList db tableName
| isNothing db || indexList == [] || not (containsTable tableName db) = db
| otherwise =
let (index:indices) = indexList
newDB = deleteEntryAtIndex index db tableName
in deleteEntryAtIndices [index-1 | index <- indices] newDB tableName
delete :: Either Text.Parsec.Error.ParseError Exp.ValueExpr -> Maybe Database -> String -> Maybe Database
delete (Right condition) db tableName
| isNothing db || not (containsTable tableName db) = db
| otherwise =
let table = getTable tableName db
nEntries = count table
columns = Data.Map.elems $ tColumns $ fromJust table
indices = [n | n <- [0..nEntries-1], let map = Data.Map.fromList [(colName, value) | col <- columns, let colName = cName col, let value = litValue ((cValues col) !! n) (cDatatype col)], Exp.evaluateExpr2 map condition]
in deleteEntryAtIndices indices db tableName
orderBy :: Either Text.Parsec.Error.ParseError Exp.ValueExpr -> [[(Int, String, Datatype, String)]] -> [[(Int, String, Datatype, String)]]
orderBy (Right expr) entryList
| length entryList == 0 = []
| otherwise =
sortBy (\entry1 entry2->
let map1 = Data.Map.fromList [(colName, litValue (value) colDatatype) | (pKey, colName, colDatatype, value) <- entry1]
map2 = Data.Map.fromList [(colName, litValue (value) colDatatype) | (pKey, colName, colDatatype, value) <- entry2]
val1 = Exp.evaluateExpr map1 expr
val2 = Exp.evaluateExpr map2 expr
in compare val1 val2) entryList
select :: [(String, String)] -> [[(Int, String, Datatype, String)]] -> [[(Int, String, Datatype, String)]]
select colAlias entryList
| length entryList == 0 = []
| length colAlias == 0 = entryList
| otherwise =
let alias = Data.Map.fromList colAlias
in [newEntry | entry <- entryList,
let newEntry = [(pKey, fromJust newName, datatype, value) | (pKey, name, datatype, value) <- entry, let newName = Data.Map.lookup name alias, not (isNothing newName)]]