packages feed

selda 0.1.3.3 → 0.1.4.0

raw patch · 9 files changed

+77/−12 lines, 9 files

Files

ChangeLog.md view
@@ -1,5 +1,10 @@ # Revision history for selda +## 0.1.4.0 -- 2017-05-04++* Add uniqueness constraints and foreign keys.++ ## 0.1.3.3 -- 2017-05-04  * Fix cache invalidation race when using transactions.
selda.cabal view
@@ -1,5 +1,5 @@ name:                selda-version:             0.1.3.3+version:             0.1.4.0 synopsis:            Type-safe, high-level EDSL for interacting with relational databases. description:         This package provides an EDSL for writing portable, type-safe, high-level                      database code. Its feature set includes querying and modifying databases,@@ -62,6 +62,7 @@     Database.Selda.SqlType     Database.Selda.Table     Database.Selda.Table.Compile+    Database.Selda.Table.Foreign     Database.Selda.Transform     Database.Selda.Types   other-extensions:
src/Database/Selda.hs view
@@ -100,6 +100,7 @@   , table, tableWithSelectors, selectors   , required, optional   , primary, autoPrimary+  , fk, unique     -- * Creating and dropping tables   , createTable, tryCreateTable   , dropTable, tryDropTable@@ -125,6 +126,7 @@ import Database.Selda.SqlType import Database.Selda.Table import Database.Selda.Table.Compile+import Database.Selda.Table.Foreign import Database.Selda.Types import Database.Selda.Unsafe import Control.Exception (throw)
src/Database/Selda/Generic.hs view
@@ -260,12 +260,13 @@         { colName = mkColName . pack $ selName ((M1 undefined) :: M1 S c a b)         , colType = colType ci         , colAttrs = colAttrs ci+        , colFKs = colFKs ci         }   gMkDummy = M1 <$> gMkDummy  instance (Traits a, SqlType a) => GRelation (K1 i a) where   gToRel (K1 x) = x-  gTblCols _    = [ColInfo "" (sqlType (Proxy :: Proxy a)) optReq]+  gTblCols _    = [ColInfo "" (sqlType (Proxy :: Proxy a)) optReq []]     where       optReq         | isMaybeType (Proxy :: Proxy a) = [Optional]
src/Database/Selda/Selectors.hs view
@@ -38,8 +38,9 @@ with = foldl' upd  -- | A column selector. Column selectors can be used together with the '!' and---   '!=' operators to get and set values on inductive tuples.-newtype Selector t a = Selector Int+--   'with' functions to get and set values on inductive tuples, or to indicate+--   foreign keys.+data Selector t a = Selector Int  -- | The inductive tuple of selectors for a table of type @a@. type family Selectors t a where
src/Database/Selda/Table.hs view
@@ -44,6 +44,7 @@   { colName  :: !ColName   , colType  :: !Text   , colAttrs :: ![ColAttr]+  , colFKs   :: ![(Table (), ColName)]   }  newCol :: forall a. SqlType a => ColName -> ColSpec a@@ -51,6 +52,7 @@   { colName  = name   , colType  = sqlType (Proxy :: Proxy a)   , colAttrs = []+  , colFKs   = []   }]  -- | A table column specification.@@ -75,7 +77,7 @@ --   When adding elements, make sure that they are added in the order --   required by SQL syntax, as this list is only sorted before being --   pretty-printed.-data ColAttr = Primary | AutoIncrement | Required | Optional+data ColAttr = Primary | AutoIncrement | Required | Optional | Unique   deriving (Show, Eq, Ord)  -- | A non-nullable column with the given name.@@ -83,25 +85,31 @@ required = addAttr Required . newCol  -- | A nullable column with the given name.-optional :: SqlType a => ColName -> ColSpec (Maybe a)+optional :: NonNull a => ColName -> ColSpec (Maybe a) optional = addAttr Optional . newCol  -- | Marks the given column as the table's primary key. --   A table may only have one primary key; marking more than one key as---   primary will result in a run-time error.+--   primary will result in 'ValidationError' during validation. primary :: NonNull a => ColName -> ColSpec a-primary = addAttr Primary . required+primary = addAttr Primary . unique . required  -- | Automatically increment the given attribute if not specified during insert.---   Also adds the @PRIMARY KEY@ attribute on the column.+--   Also adds the @PRIMARY KEY@ and @UNIQUE@ attributes on the column. autoPrimary :: ColName -> ColSpec Int-autoPrimary n = ColSpec [c {colAttrs = [Primary, AutoIncrement, Required]}]+autoPrimary n = ColSpec [c {colAttrs = [Primary, AutoIncrement, Required, Unique]}]   where ColSpec [c] = newCol n :: ColSpec Int +-- | Add a uniqueness constraint to the given column.+--   Adding a uniqueness constraint to a column that is already implied to be+--   unique, such as a primary key, is a no-op.+unique :: SqlType a => ColSpec a -> ColSpec a+unique = addAttr Unique+ -- | Add an attribute to a column. Not for public consumption. addAttr :: SqlType a => ColAttr -> ColSpec a -> ColSpec a addAttr attr (ColSpec [ci]) = ColSpec [ci {colAttrs = attr : colAttrs ci}]-addAttr _ _                 = error "impossible: SqlType ColSpec with several columns"+addAttr _ _                 = error "impossible: ColSpec with several columns"  -- | An inductive tuple where each element is a column specification. type family ColSpecs a where@@ -154,6 +162,7 @@       , nulIdents       , emptyIdents       , emptyTableName+      , nonPkFks       ]     emptyTableName       | fromTableName name == "\"\"" = ["table name is empty"]@@ -172,6 +181,15 @@       ["duplicate column: " <> fromColName x | (x:_:_) <- soup $ map colName cis]     pkDupes =       ["multiple primary keys" | (Primary:_:_) <- soup $ concatMap colAttrs cis]+    nonPkFks =+      [ "column is used as a foreign key, but is not a primary key of its table: "+          <> fromTableName ftn <> "." <> fromColName fcn+      | ci <- cis+      , (Table ftn fcs, fcn) <- colFKs ci+      , fc <- fcs+      , colName fc == fcn+      , not (Unique `elem` colAttrs fc)+      ]      -- This should be impossible, but...     optionalRequiredMutex =
src/Database/Selda/Table/Compile.hs view
@@ -17,12 +17,26 @@ compileCreateTable customColType ifex tbl = mconcat   [ "CREATE TABLE ", ifNotExists ifex, fromTableName (tableName tbl), "("   , intercalate ", " (map (compileTableCol customColType) (tableCols tbl))+  , case allFKs of+      [] -> ""+      _  -> ", " <> intercalate ", " compFKs   , ")"   ]   where     ifNotExists Fail   = ""     ifNotExists Ignore = "IF NOT EXISTS "+    allFKs = [(colName ci, fk) | ci <- tableCols tbl, fk <- colFKs ci]+    compFKs = zipWith (uncurry compileFK) allFKs [0..] +-- | Compile a foreign key constraint.+compileFK :: ColName -> (Table (), ColName) -> Int -> Text+compileFK col (Table ftbl _, fcol) n = mconcat+  [ "CONSTRAINT ", fkName, " FOREIGN KEY (", fromColName col, ") "+  , "REFERENCES ", fromTableName ftbl, "(", fromColName fcol, ")"+  ]+  where+    fkName = fromColName $ addColPrefix col ("fk" <> pack (show n) <> "_")+ -- | Compile a table column. compileTableCol :: (Text -> [ColAttr] -> Maybe Text) -> ColInfo -> Text compileTableCol customColType ci = Text.unwords@@ -98,3 +112,4 @@ compileColAttr AutoIncrement = "AUTOINCREMENT" compileColAttr Required      = "NOT NULL" compileColAttr Optional      = "NULL"+compileColAttr Unique        = "UNIQUE"
+ src/Database/Selda/Table/Foreign.hs view
@@ -0,0 +1,18 @@+-- | Foreign key support.+module Database.Selda.Table.Foreign where+import Database.Selda.Selectors+import Database.Selda.Table++-- | Add a foreign key constraint to the given column, referencing+--   the column indicated by the given table and selector.+--   If the referenced column is not a primary key or has a+--   uniqueness constraint, a 'ValidationError' will be thrown+--   during validation.+fk :: ColSpec a -> (Table t, Selector t a) -> ColSpec a+fk (ColSpec [c]) (Table tn tcs, Selector i) =+    ColSpec [c {colFKs = thefk : colFKs c}]+  where+    thefk = (Table tn tcs, colName (tcs !! i))+fk _ _ =+  error "impossible: ColSpec with several columns"+
src/Database/Selda/Types.hs view
@@ -8,7 +8,7 @@   ( (:*:)(..), Head, Append (..), (:++:), ToDyn (..), Tup (..)   , first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth   , ColName, TableName-  , mkColName, mkTableName, addColSuffix+  , mkColName, mkTableName, addColSuffix, addColPrefix   , fromColName, fromTableName   ) where import Data.Dynamic@@ -31,6 +31,10 @@ -- | Name of a database table. newtype TableName = TableName Text   deriving (Ord, Eq, Show, IsString)++-- | Add a prefix to a column name.+addColPrefix :: ColName -> Text -> ColName+addColPrefix (ColName cn) s = ColName $ Data.Text.append s cn  -- | Add a suffix to a column name. addColSuffix :: ColName -> Text -> ColName