module Positron ( model , Column , Property(..) , (//) , Query(..) , Condition , (.==) , SetValue , (?=) , whose , Parameter(..) , orderBy , OrderBy(..) , onConflict , returning , module Positron.Alias , module Positron.Query , mkPositron , Positron , ConnConf , defaultConnConf , setDBHost , setDBPort , setDBName , setDBUser , setDBPassword , connect , close , withDatabase , PositronError(..) , textShow -- re-export data types , NonEmpty(..) , Int16 , Int32 , Int64 , Scientific , Float , Double , Word16 , Word32 , Word64 , ByteString , Text , UUID(..) ) where import Positron.Import -- local modules import Positron.Alias import Positron.Driver import Positron.Query import Positron.Types import Positron.UUID import Positron.Unsafe import Positron.Util mkPositron :: String -> Q [Dec] mkPositron namespace = do -- record field names that cannot be captured connField <- newName "conn" connType <- [t| Connection |] lockField <- newName "lock" lockType <- [t| MVar () |] let rec fieldName fieldType = (fieldName, Bang NoSourceUnpackedness SourceStrict, fieldType) recs = [ rec connField connType , rec lockField lockType ] dataDec = DataD [] (mkName $ "Positron" <> namespace) [] Nothing [RecC dataName recs] [] pairs <- runIO readPrepared createQueries <- fold . reverse . map buildCreateQuery <$> getCurrentTableMap instanceDec <- [d| instance Positron $(return $ ConT dataName) where pConn = $(return $ VarE connField) pLock = $(return $ VarE lockField) pPrepareds = const pairs pCreateQueries = const createQueries pMake conn = do lock <- newMVar () return $ $(return $ ConE dataName) conn lock |] return $ dataDec : instanceDec where dataName = mkName $ "Positron" <> namespace buildCreateQuery (tableName, columnPairs) = let snakeTableName = snake (decap tableName) columns = map snd columnPairs primaryKeys = map (snake . acn) $ filter acp columns indexedKeys = map (snake . acn) $ filter aci columns foreignKeys = map formatForeignKey $ mapMaybe (\ c -> fmap (\ x -> (acn c, x)) (acf c)) columns in fold [ "CREATE TABLE IF NOT EXISTS " , snakeTableName , " (\n " , fold $ for columns $ \AC{..} -> fold [ snake acn, " ", show act , if acUnique then " UNIQUE" else "" , if acnl then " NULL" else " NOT NULL" , ",\n " ] , "PRIMARY KEY (" , intercalate ", " primaryKeys , ")" , if foreignKeys /= [] then foldMap (",\n " ++) foreignKeys else mempty , "\n);\n" , if indexedKeys /= [] then fold $ for indexedKeys $ \columnName -> fold [ "CREATE INDEX IF NOT EXISTS ix_" , snakeTableName, "_", columnName , " ON ", snakeTableName, " (", columnName, ");\n" ] else mempty ] formatForeignKey (columnName, (targetTableName, targetColumnName)) = fold [ "FOREIGN KEY(", snake columnName, ") REFERENCES " , targetTableName, " (", snake targetColumnName, ")" ] model :: String -> [Column] -> Q [Dec] model tableName plainColumns = do columns <- mapM (analyze tableName) plainColumns thisModuleStr <- show <$> thisModule addTable thisModuleStr (tableName, [(acn, a) | a@AC{..} <- columns]) let recs = for columns $ \ac@AC{..} -> ( mkName acFullName , Bang (if acnl then NoSourceUnpackedness else SourceUnpack) SourceStrict , columnTypeCon ac ) condDecs <- fmap fold <$> forM columns $ \ AC{..} -> let condName = mkName $ acFullName ++ "EqParam" in do defAST <- [| Condition acn Parameter |] return [ SigD condName $ ConT ''Condition , ValD (VarP condName) (NormalB defAST) [] ] return $ DataD [] dataName [] Nothing [RecC dataName recs] [DerivClause Nothing [ConT ''Eq, ConT ''Show]] : condDecs where dataName = mkName tableName analyze :: String -> Column -> Q AnalyzedColumn analyze tableName (Column n t pk idx nl unique) = case t of Psmallint -> ret DBsmallint Pinteger -> ret DBinteger Pbigint -> ret DBbigint Pdecimal -> ret DBdecimal Pnumeric -> ret DBnumeric Preal -> ret DBreal Pdouble -> ret DBdouble Psmallserial -> ret DBsmallserial Pserial -> ret DBserial Pbigserial -> ret DBbigserial Pvarchar len -> ret $ DBvarchar len Ptext -> ret DBtext Puuid -> ret DBuuid Pforeignkey s -> do let (tn, dottedColName) = break (== '.') s cn = tail dottedColName thisModuleStr <- show <$> thisModule lookupColumn thisModuleStr tn cn >>= \case Nothing -> fail $ fold ["Column \"", cn, "\" of Table \"", tn, "\" not found"] Just AC{..} -> return $ acBase (plain act) (Just (tn, cn)) where ret dt = return (acBase dt Nothing) acBase = AC n fullName pk idx nl unique plain = \case DBsmallserial -> DBsmallint DBserial -> DBinteger DBbigserial -> DBbigint x -> x fullName = decap tableName ++ cap n