Quasi.hs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. {-# LANGUAGE CPP #-}
  2. {-# LANGUAGE PatternGuards #-}
  3. {-# LANGUAGE ViewPatterns #-}
  4. module Database.Persist.Quasi
  5. ( parse
  6. , PersistSettings (..)
  7. , upperCaseSettings
  8. , lowerCaseSettings
  9. , nullable
  10. #if TEST
  11. , Token (..)
  12. , tokenize
  13. , parseFieldType
  14. #endif
  15. ) where
  16. import Prelude hiding (lines)
  17. import Control.Arrow ((&&&))
  18. import Control.Monad (msum, mplus)
  19. import Data.Char
  20. import Data.List (find, foldl')
  21. import qualified Data.Map as M
  22. import Data.Maybe (mapMaybe, fromMaybe, maybeToList)
  23. import Data.Monoid (mappend)
  24. import Data.Text (Text)
  25. import qualified Data.Text as T
  26. import Database.Persist.Types
  27. data ParseState a = PSDone | PSFail String | PSSuccess a Text deriving Show
  28. parseFieldType :: Text -> Either String FieldType
  29. parseFieldType t0 =
  30. case parseApplyFT t0 of
  31. PSSuccess ft t'
  32. | T.all isSpace t' -> Right ft
  33. PSFail err -> Left $ "PSFail " ++ err
  34. other -> Left $ show other
  35. where
  36. parseApplyFT t =
  37. case goMany id t of
  38. PSSuccess (ft:fts) t' -> PSSuccess (foldl' FTApp ft fts) t'
  39. PSSuccess [] _ -> PSFail "empty"
  40. PSFail err -> PSFail err
  41. PSDone -> PSDone
  42. parseEnclosed :: Char -> (FieldType -> FieldType) -> Text -> ParseState FieldType
  43. parseEnclosed end ftMod t =
  44. let (a, b) = T.break (== end) t
  45. in case parseApplyFT a of
  46. PSSuccess ft t' -> case (T.dropWhile isSpace t', T.uncons b) of
  47. ("", Just (c, t'')) | c == end -> PSSuccess (ftMod ft) (t'' `Data.Monoid.mappend` t')
  48. (x, y) -> PSFail $ show (b, x, y)
  49. x -> PSFail $ show x
  50. parse1 t =
  51. case T.uncons t of
  52. Nothing -> PSDone
  53. Just (c, t')
  54. | isSpace c -> parse1 $ T.dropWhile isSpace t'
  55. | c == '(' -> parseEnclosed ')' id t'
  56. | c == '[' -> parseEnclosed ']' FTList t'
  57. | isUpper c ->
  58. let (a, b) = T.break (\x -> isSpace x || x `elem` ("()[]"::String)) t
  59. in PSSuccess (getCon a) b
  60. | otherwise -> PSFail $ show (c, t')
  61. getCon t =
  62. case T.breakOnEnd "." t of
  63. (_, "") -> FTTypeCon Nothing t
  64. ("", _) -> FTTypeCon Nothing t
  65. (a, b) -> FTTypeCon (Just $ T.init a) b
  66. goMany front t =
  67. case parse1 t of
  68. PSSuccess x t' -> goMany (front . (x:)) t'
  69. PSFail err -> PSFail err
  70. PSDone -> PSSuccess (front []) t
  71. -- _ ->
  72. data PersistSettings = PersistSettings
  73. { psToDBName :: !(Text -> Text)
  74. , psStrictFields :: !Bool
  75. -- ^ Whether fields are by default strict. Default value: @True@.
  76. --
  77. -- @since 1.2
  78. , psIdName :: !Text
  79. -- ^ The name of the id column. Default value: @id@
  80. -- The name of the id column can also be changed on a per-model basis
  81. -- <https://github.com/yesodweb/persistent/wiki/Persistent-entity-syntax>
  82. --
  83. -- @since 2.0
  84. }
  85. defaultPersistSettings, upperCaseSettings, lowerCaseSettings :: PersistSettings
  86. defaultPersistSettings = PersistSettings
  87. { psToDBName = id
  88. , psStrictFields = True
  89. , psIdName = "id"
  90. }
  91. upperCaseSettings = defaultPersistSettings
  92. lowerCaseSettings = defaultPersistSettings
  93. { psToDBName =
  94. let go c
  95. | isUpper c = T.pack ['_', toLower c]
  96. | otherwise = T.singleton c
  97. in T.dropWhile (== '_') . T.concatMap go
  98. }
  99. -- | Parses a quasi-quoted syntax into a list of entity definitions.
  100. parse :: PersistSettings -> Text -> [EntityDef]
  101. parse ps = parseLines ps
  102. . removeSpaces
  103. . filter (not . empty)
  104. . map tokenize
  105. . T.lines
  106. -- | A token used by the parser.
  107. data Token = Spaces !Int -- ^ @Spaces n@ are @n@ consecutive spaces.
  108. | Token Text -- ^ @Token tok@ is token @tok@ already unquoted.
  109. deriving (Show, Eq)
  110. -- | Tokenize a string.
  111. tokenize :: Text -> [Token]
  112. tokenize t
  113. | T.null t = []
  114. | "--" `T.isPrefixOf` t = [] -- Comment until the end of the line.
  115. | "#" `T.isPrefixOf` t = [] -- Also comment to the end of the line, needed for a CPP bug (#110)
  116. | T.head t == '"' = quotes (T.tail t) id
  117. | T.head t == '(' = parens 1 (T.tail t) id
  118. | isSpace (T.head t) =
  119. let (spaces, rest) = T.span isSpace t
  120. in Spaces (T.length spaces) : tokenize rest
  121. -- support mid-token quotes and parens
  122. | Just (beforeEquals, afterEquals) <- findMidToken t
  123. , not (T.any isSpace beforeEquals)
  124. , Token next : rest <- tokenize afterEquals =
  125. Token (T.concat [beforeEquals, "=", next]) : rest
  126. | otherwise =
  127. let (token, rest) = T.break isSpace t
  128. in Token token : tokenize rest
  129. where
  130. findMidToken t' =
  131. case T.break (== '=') t' of
  132. (x, T.drop 1 -> y)
  133. | "\"" `T.isPrefixOf` y || "(" `T.isPrefixOf` y -> Just (x, y)
  134. _ -> Nothing
  135. quotes t' front
  136. | T.null t' = error $ T.unpack $ T.concat $
  137. "Unterminated quoted string starting with " : front []
  138. | T.head t' == '"' = Token (T.concat $ front []) : tokenize (T.tail t')
  139. | T.head t' == '\\' && T.length t' > 1 =
  140. quotes (T.drop 2 t') (front . (T.take 1 (T.drop 1 t'):))
  141. | otherwise =
  142. let (x, y) = T.break (`elem` ['\\','\"']) t'
  143. in quotes y (front . (x:))
  144. parens count t' front
  145. | T.null t' = error $ T.unpack $ T.concat $
  146. "Unterminated parens string starting with " : front []
  147. | T.head t' == ')' =
  148. if count == (1 :: Int)
  149. then Token (T.concat $ front []) : tokenize (T.tail t')
  150. else parens (count - 1) (T.tail t') (front . (")":))
  151. | T.head t' == '(' =
  152. parens (count + 1) (T.tail t') (front . ("(":))
  153. | T.head t' == '\\' && T.length t' > 1 =
  154. parens count (T.drop 2 t') (front . (T.take 1 (T.drop 1 t'):))
  155. | otherwise =
  156. let (x, y) = T.break (`elem` ['\\','(',')']) t'
  157. in parens count y (front . (x:))
  158. -- | A string of tokens is empty when it has only spaces. There
  159. -- can't be two consecutive 'Spaces', so this takes /O(1)/ time.
  160. empty :: [Token] -> Bool
  161. empty [] = True
  162. empty [Spaces _] = True
  163. empty _ = False
  164. -- | A line. We don't care about spaces in the middle of the
  165. -- line. Also, we don't care about the ammount of indentation.
  166. data Line = Line { lineIndent :: Int
  167. , tokens :: [Text]
  168. }
  169. -- | Remove leading spaces and remove spaces in the middle of the
  170. -- tokens.
  171. removeSpaces :: [[Token]] -> [Line]
  172. removeSpaces =
  173. map toLine
  174. where
  175. toLine (Spaces i:rest) = toLine' i rest
  176. toLine xs = toLine' 0 xs
  177. toLine' i = Line i . mapMaybe fromToken
  178. fromToken (Token t) = Just t
  179. fromToken Spaces{} = Nothing
  180. -- | Divide lines into blocks and make entity definitions.
  181. parseLines :: PersistSettings -> [Line] -> [EntityDef]
  182. parseLines ps lines =
  183. fixForeignKeysAll $ toEnts lines
  184. where
  185. toEnts (Line indent (name:entattribs) : rest) =
  186. let (x, y) = span ((> indent) . lineIndent) rest
  187. in mkEntityDef ps name entattribs x : toEnts y
  188. toEnts (Line _ []:rest) = toEnts rest
  189. toEnts [] = []
  190. fixForeignKeysAll :: [UnboundEntityDef] -> [EntityDef]
  191. fixForeignKeysAll unEnts = map fixForeignKeys unEnts
  192. where
  193. ents = map unboundEntityDef unEnts
  194. entLookup = M.fromList $ map (\e -> (entityHaskell e, e)) ents
  195. fixForeignKeys :: UnboundEntityDef -> EntityDef
  196. fixForeignKeys (UnboundEntityDef foreigns ent) =
  197. ent { entityForeigns = map (fixForeignKey ent) foreigns }
  198. -- check the count and the sqltypes match and update the foreignFields with the names of the primary columns
  199. fixForeignKey :: EntityDef -> UnboundForeignDef -> ForeignDef
  200. fixForeignKey ent (UnboundForeignDef foreignFieldTexts fdef) =
  201. case M.lookup (foreignRefTableHaskell fdef) entLookup of
  202. Just pent -> case entityPrimary pent of
  203. Just pdef ->
  204. if length foreignFieldTexts /= length (compositeFields pdef)
  205. then lengthError pdef
  206. else let fds_ffs = zipWith (toForeignFields pent)
  207. foreignFieldTexts
  208. (compositeFields pdef)
  209. in fdef { foreignFields = map snd fds_ffs
  210. , foreignNullable = setNull $ map fst fds_ffs
  211. }
  212. Nothing ->
  213. error $ "no explicit primary key fdef="++show fdef++ " ent="++show ent
  214. Nothing ->
  215. error $ "could not find table " ++ show (foreignRefTableHaskell fdef)
  216. ++ " fdef=" ++ show fdef ++ " allnames="
  217. ++ show (map (unHaskellName . entityHaskell . unboundEntityDef) unEnts)
  218. ++ "\n\nents=" ++ show ents
  219. where
  220. setNull :: [FieldDef] -> Bool
  221. setNull [] = error "setNull: impossible!"
  222. setNull (fd:fds) = let nullSetting = isNull fd in
  223. if all ((nullSetting ==) . isNull) fds then nullSetting
  224. else error $ "foreign key columns must all be nullable or non-nullable"
  225. ++ show (map (unHaskellName . fieldHaskell) (fd:fds))
  226. isNull = (NotNullable /=) . nullable . fieldAttrs
  227. toForeignFields pent fieldText pfd =
  228. case chktypes fd haskellField (entityFields pent) pfh of
  229. Just err -> error err
  230. Nothing -> (fd, ((haskellField, fieldDB fd), (pfh, pfdb)))
  231. where
  232. fd = getFd (entityFields ent) haskellField
  233. haskellField = HaskellName fieldText
  234. (pfh, pfdb) = (fieldHaskell pfd, fieldDB pfd)
  235. chktypes :: FieldDef -> HaskellName -> [FieldDef] -> HaskellName -> Maybe String
  236. chktypes ffld _fkey pflds pkey =
  237. if fieldType ffld == fieldType pfld then Nothing
  238. else Just $ "fieldType mismatch: " ++ show (fieldType ffld) ++ ", " ++ show (fieldType pfld)
  239. where
  240. pfld = getFd pflds pkey
  241. entName = entityHaskell ent
  242. getFd [] t = error $ "foreign key constraint for: " ++ show (unHaskellName entName)
  243. ++ " unknown column: " ++ show t
  244. getFd (f:fs) t
  245. | fieldHaskell f == t = f
  246. | otherwise = getFd fs t
  247. lengthError pdef = error $ "found " ++ show (length foreignFieldTexts) ++ " fkeys and " ++ show (length (compositeFields pdef)) ++ " pkeys: fdef=" ++ show fdef ++ " pdef=" ++ show pdef
  248. data UnboundEntityDef = UnboundEntityDef
  249. { _unboundForeignDefs :: [UnboundForeignDef]
  250. , unboundEntityDef :: EntityDef
  251. }
  252. lookupKeyVal :: Text -> [Text] -> Maybe Text
  253. lookupKeyVal key = lookupPrefix $ key `mappend` "="
  254. lookupPrefix :: Text -> [Text] -> Maybe Text
  255. lookupPrefix prefix = msum . map (T.stripPrefix prefix)
  256. -- | Construct an entity definition.
  257. mkEntityDef :: PersistSettings
  258. -> Text -- ^ name
  259. -> [Attr] -- ^ entity attributes
  260. -> [Line] -- ^ indented lines
  261. -> UnboundEntityDef
  262. mkEntityDef ps name entattribs lines =
  263. UnboundEntityDef foreigns $
  264. EntityDef
  265. entName
  266. (DBName $ getDbName ps name' entattribs)
  267. -- idField is the user-specified Id
  268. -- otherwise useAutoIdField
  269. -- but, adjust it if the user specified a Primary
  270. (setComposite primaryComposite $ fromMaybe autoIdField idField)
  271. entattribs
  272. cols
  273. uniqs
  274. []
  275. derives
  276. extras
  277. isSum
  278. comments
  279. where
  280. comments = Nothing
  281. entName = HaskellName name'
  282. (isSum, name') =
  283. case T.uncons name of
  284. Just ('+', x) -> (True, x)
  285. _ -> (False, name)
  286. (attribs, extras) = splitExtras lines
  287. attribPrefix = flip lookupKeyVal entattribs
  288. idName | Just _ <- attribPrefix "id" = error "id= is deprecated, ad a field named 'Id' and use sql="
  289. | otherwise = Nothing
  290. (idField, primaryComposite, uniqs, foreigns) = foldl' (\(mid, mp, us, fs) attr ->
  291. let (i, p, u, f) = takeConstraint ps name' cols attr
  292. squish xs m = xs `mappend` maybeToList m
  293. in (just1 mid i, just1 mp p, squish us u, squish fs f)) (Nothing, Nothing, [],[]) attribs
  294. derives = concat $ mapMaybe takeDerives attribs
  295. cols :: [FieldDef]
  296. cols = mapMaybe (takeColsEx ps) attribs
  297. autoIdField = mkAutoIdField ps entName (DBName `fmap` idName) idSqlType
  298. idSqlType = maybe SqlInt64 (const $ SqlOther "Primary Key") primaryComposite
  299. setComposite Nothing fd = fd
  300. setComposite (Just c) fd = fd { fieldReference = CompositeRef c }
  301. just1 :: (Show x) => Maybe x -> Maybe x -> Maybe x
  302. just1 (Just x) (Just y) = error $ "expected only one of: "
  303. `mappend` show x `mappend` " " `mappend` show y
  304. just1 x y = x `mplus` y
  305. mkAutoIdField :: PersistSettings -> HaskellName -> Maybe DBName -> SqlType -> FieldDef
  306. mkAutoIdField ps entName idName idSqlType = FieldDef
  307. { fieldHaskell = HaskellName "Id"
  308. -- this should be modeled as a Maybe
  309. -- but that sucks for non-ID field
  310. -- TODO: use a sumtype FieldDef | IdFieldDef
  311. , fieldDB = fromMaybe (DBName $ psIdName ps) idName
  312. , fieldType = FTTypeCon Nothing $ keyConName $ unHaskellName entName
  313. , fieldSqlType = idSqlType
  314. -- the primary field is actually a reference to the entity
  315. , fieldReference = ForeignRef entName defaultReferenceTypeCon
  316. , fieldAttrs = []
  317. , fieldStrict = True
  318. , fieldComments = Nothing
  319. }
  320. defaultReferenceTypeCon :: FieldType
  321. defaultReferenceTypeCon = FTTypeCon (Just "Data.Int") "Int64"
  322. keyConName :: Text -> Text
  323. keyConName entName = entName `mappend` "Id"
  324. splitExtras :: [Line] -> ([[Text]], M.Map Text [[Text]])
  325. splitExtras [] = ([], M.empty)
  326. splitExtras (Line indent [name]:rest)
  327. | not (T.null name) && isUpper (T.head name) =
  328. let (children, rest') = span ((> indent) . lineIndent) rest
  329. (x, y) = splitExtras rest'
  330. in (x, M.insert name (map tokens children) y)
  331. splitExtras (Line _ ts:rest) =
  332. let (x, y) = splitExtras rest
  333. in (ts:x, y)
  334. takeColsEx :: PersistSettings -> [Text] -> Maybe FieldDef
  335. takeColsEx = takeCols (\ft perr -> error $ "Invalid field type " ++ show ft ++ " " ++ perr)
  336. takeCols :: (Text -> String -> Maybe FieldDef) -> PersistSettings -> [Text] -> Maybe FieldDef
  337. takeCols _ _ ("deriving":_) = Nothing
  338. takeCols onErr ps (n':typ:rest)
  339. | not (T.null n) && isLower (T.head n) =
  340. case parseFieldType typ of
  341. Left err -> onErr typ err
  342. Right ft -> Just FieldDef
  343. { fieldHaskell = HaskellName n
  344. , fieldDB = DBName $ getDbName ps n rest
  345. , fieldType = ft
  346. , fieldSqlType = SqlOther $ "SqlType unset for " `mappend` n
  347. , fieldAttrs = rest
  348. , fieldStrict = fromMaybe (psStrictFields ps) mstrict
  349. , fieldReference = NoReference
  350. , fieldComments = Nothing
  351. }
  352. where
  353. (mstrict, n)
  354. | Just x <- T.stripPrefix "!" n' = (Just True, x)
  355. | Just x <- T.stripPrefix "~" n' = (Just False, x)
  356. | otherwise = (Nothing, n')
  357. takeCols _ _ _ = Nothing
  358. getDbName :: PersistSettings -> Text -> [Text] -> Text
  359. getDbName ps n [] = psToDBName ps n
  360. getDbName ps n (a:as) = fromMaybe (getDbName ps n as) $ T.stripPrefix "sql=" a
  361. takeConstraint :: PersistSettings
  362. -> Text
  363. -> [FieldDef]
  364. -> [Text]
  365. -> (Maybe FieldDef, Maybe CompositeDef, Maybe UniqueDef, Maybe UnboundForeignDef)
  366. takeConstraint ps tableName defs (n:rest) | not (T.null n) && isUpper (T.head n) = takeConstraint'
  367. where
  368. takeConstraint'
  369. | n == "Unique" = (Nothing, Nothing, Just $ takeUniq ps tableName defs rest, Nothing)
  370. | n == "Foreign" = (Nothing, Nothing, Nothing, Just $ takeForeign ps tableName defs rest)
  371. | n == "Primary" = (Nothing, Just $ takeComposite defs rest, Nothing, Nothing)
  372. | n == "Id" = (Just $ takeId ps tableName (n:rest), Nothing, Nothing, Nothing)
  373. | otherwise = (Nothing, Nothing, Just $ takeUniq ps "" defs (n:rest), Nothing) -- retain compatibility with original unique constraint
  374. takeConstraint _ _ _ _ = (Nothing, Nothing, Nothing, Nothing)
  375. -- TODO: this is hacky (the double takeCols, the setFieldDef stuff, and setIdName.
  376. -- need to re-work takeCols function
  377. takeId :: PersistSettings -> Text -> [Text] -> FieldDef
  378. takeId ps tableName (n:rest) = fromMaybe (error "takeId: impossible!") $ setFieldDef $
  379. takeCols (\_ _ -> addDefaultIdType) ps (field:rest `mappend` setIdName)
  380. where
  381. field = case T.uncons n of
  382. Nothing -> error "takeId: empty field"
  383. Just (f, ield) -> toLower f `T.cons` ield
  384. addDefaultIdType = takeColsEx ps (field : keyCon : rest `mappend` setIdName)
  385. setFieldDef = fmap (\fd ->
  386. let refFieldType = if fieldType fd == FTTypeCon Nothing keyCon
  387. then defaultReferenceTypeCon
  388. else fieldType fd
  389. in fd { fieldReference = ForeignRef (HaskellName tableName) $ refFieldType
  390. })
  391. keyCon = keyConName tableName
  392. -- this will be ignored if there is already an existing sql=
  393. -- TODO: I think there is a ! ignore syntax that would screw this up
  394. setIdName = ["sql=" `mappend` psIdName ps]
  395. takeId _ tableName _ = error $ "empty Id field for " `mappend` show tableName
  396. takeComposite :: [FieldDef]
  397. -> [Text]
  398. -> CompositeDef
  399. takeComposite fields pkcols
  400. = CompositeDef
  401. (map (getDef fields) pkcols)
  402. attrs
  403. where
  404. (_, attrs) = break ("!" `T.isPrefixOf`) pkcols
  405. getDef [] t = error $ "Unknown column in primary key constraint: " ++ show t
  406. getDef (d:ds) t
  407. | fieldHaskell d == HaskellName t =
  408. if nullable (fieldAttrs d) /= NotNullable
  409. then error $ "primary key column cannot be nullable: " ++ show t
  410. else d
  411. | otherwise = getDef ds t
  412. -- Unique UppercaseConstraintName list of lowercasefields terminated
  413. -- by ! or sql= such that a unique constraint can look like:
  414. -- `UniqueTestNull fieldA fieldB sql=ConstraintNameInDatabase !force`
  415. -- Here using sql= sets the name of the constraint.
  416. takeUniq :: PersistSettings
  417. -> Text
  418. -> [FieldDef]
  419. -> [Text]
  420. -> UniqueDef
  421. takeUniq ps tableName defs (n:rest)
  422. | not (T.null n) && isUpper (T.head n)
  423. = UniqueDef
  424. (HaskellName n)
  425. dbName
  426. (map (HaskellName &&& getDBName defs) fields)
  427. attrs
  428. where
  429. isAttr a =
  430. "!" `T.isPrefixOf` a
  431. isSqlName a =
  432. "sql=" `T.isPrefixOf` a
  433. isNonField a =
  434. isAttr a
  435. || isSqlName a
  436. (fields, nonFields) =
  437. break isNonField rest
  438. attrs = filter isAttr nonFields
  439. usualDbName =
  440. DBName $ psToDBName ps (tableName `T.append` n)
  441. sqlName :: Maybe DBName
  442. sqlName =
  443. case find isSqlName nonFields of
  444. Nothing ->
  445. Nothing
  446. (Just t) ->
  447. case drop 1 $ T.splitOn "=" t of
  448. (x : _) -> Just (DBName x)
  449. _ -> Nothing
  450. dbName = fromMaybe usualDbName sqlName
  451. getDBName [] t =
  452. error $ "Unknown column in unique constraint: " ++ show t
  453. ++ " " ++ show defs ++ show n ++ " " ++ show attrs
  454. getDBName (d:ds) t
  455. | fieldHaskell d == HaskellName t = fieldDB d
  456. | otherwise = getDBName ds t
  457. takeUniq _ tableName _ xs =
  458. error $ "invalid unique constraint on table["
  459. ++ show tableName
  460. ++ "] expecting an uppercase constraint name xs="
  461. ++ show xs
  462. data UnboundForeignDef = UnboundForeignDef
  463. { _unboundFields :: [Text] -- ^ fields in other entity
  464. , _unboundForeignDef :: ForeignDef
  465. }
  466. takeForeign :: PersistSettings
  467. -> Text
  468. -> [FieldDef]
  469. -> [Text]
  470. -> UnboundForeignDef
  471. takeForeign ps tableName _defs (refTableName:n:rest)
  472. | not (T.null n) && isLower (T.head n)
  473. = UnboundForeignDef fields $ ForeignDef
  474. (HaskellName refTableName)
  475. (DBName $ psToDBName ps refTableName)
  476. (HaskellName n)
  477. (DBName $ psToDBName ps (tableName `T.append` n))
  478. []
  479. attrs
  480. False
  481. where
  482. (fields,attrs) = break ("!" `T.isPrefixOf`) rest
  483. takeForeign _ tableName _ xs = error $ "invalid foreign key constraint on table[" ++ show tableName ++ "] expecting a lower case constraint name xs=" ++ show xs
  484. takeDerives :: [Text] -> Maybe [Text]
  485. takeDerives ("deriving":rest) = Just rest
  486. takeDerives _ = Nothing
  487. nullable :: [Text] -> IsNullable
  488. nullable s
  489. | "Maybe" `elem` s = Nullable ByMaybeAttr
  490. | "nullable" `elem` s = Nullable ByNullableAttr
  491. | otherwise = NotNullable