@@ -237,8 +237,6 @@ embedEntityDefsMap existingEnts rawEnts =
237237 (embedEntityMap, noCycleEnts)
238238 where
239239 noCycleEnts = entsWithEmbeds
240- -- every EntityDef could reference each-other (as an EmbedRef)
241- -- let Haskell tie the knot
242240 embedEntityMap = constructEmbedEntityMap entsWithEmbeds
243241 entsWithEmbeds = fmap setEmbedEntity (rawEnts <> map unbindEntityDef existingEnts)
244242 setEmbedEntity ubEnt =
@@ -773,7 +771,13 @@ mkPersistWith mps preexistingEntities ents' = do
773771 $ predefs
774772 entityMap =
775773 constructEntityMap allEnts
776- ents <- filterM shouldGenerateCode allEnts
774+ preexistingSet =
775+ Set. fromList $ map getEntityHaskellName preexistingEntities
776+ newEnts =
777+ filter
778+ (\ e -> getUnboundEntityNameHS e `Set.notMember` preexistingSet)
779+ allEnts
780+ ents <- filterM shouldGenerateCode newEnts
777781 requireExtensions
778782 [ [TypeFamilies ], [GADTs , ExistentialQuantification ]
779783 , [DerivingStrategies ], [GeneralizedNewtypeDeriving ], [StandaloneDeriving ]
@@ -1037,9 +1041,15 @@ dataTypeDec mps entityMap entDef = do
10371041 cols :: [VarBangType ]
10381042 cols = do
10391043 fieldDef <- getUnboundFieldDefs entDef
1040- let recordName = fieldDefToRecordName mps entDef fieldDef
1041- strictness = if unboundFieldStrict fieldDef then isStrict else notStrict
1042- fieldIdType = maybeIdType mps entityMap fieldDef Nothing Nothing
1044+ let
1045+ recordName =
1046+ fieldDefToRecordName mps entDef fieldDef
1047+ strictness =
1048+ if unboundFieldStrict fieldDef
1049+ then isStrict
1050+ else notStrict
1051+ fieldIdType =
1052+ maybeIdType mps entityMap fieldDef Nothing Nothing
10431053 pure (recordName, strictness, fieldIdType)
10441054
10451055 constrs
@@ -1097,6 +1107,45 @@ mkUnique mps entityMap entDef (UniqueDef constr _ fields attrs) =
10971107 , " on the end of the line that defines your uniqueness "
10981108 , " constraint in order to disable this check. ***" ]
10991109
1110+ -- | This function renders a Template Haskell 'Type' for an 'UnboundFieldDef'.
1111+ -- It takes care to respect the 'mpsGeneric' setting to render an Id faithfully,
1112+ -- and it also ensures that the generated Haskell type is 'Maybe' if the
1113+ -- database column has that attribute.
1114+ --
1115+ -- For a database schema with @'mpsGeneric' = False@, this is simple - it uses
1116+ -- the @ModelNameId@ type directly. This resolves just fine.
1117+ --
1118+ -- If 'mpsGeneric' is @True@, then we have to do something a bit more
1119+ -- complicated. We can't refer to a @ModelNameId@ directly, because that @Id@
1120+ -- alias hides the backend type variable. Instead, we need to refer to:
1121+ --
1122+ -- > Key (ModelNameGeneric backend)
1123+ --
1124+ -- This means that the client code will need both the term @ModelNameId@ in
1125+ -- scope, as well as the @ModelNameGeneric@ constructor, despite the fact that
1126+ -- the @ModelNameId@ is the only term explicitly used (and imported).
1127+ --
1128+ -- However, we're not guaranteed to have @ModelName@ in scope - we've only
1129+ -- referenced @ModelNameId@ in code, and so code generation *should* work even
1130+ -- without this. Consider an explicit-style import:
1131+ --
1132+ -- @
1133+ -- import Model.Foo (FooId)
1134+ --
1135+ -- mkPersistWith sqlSettings $(discoverEntities) [persistLowerCase|
1136+ -- Bar
1137+ -- foo FooId
1138+ -- |]
1139+ -- @
1140+ --
1141+ -- This looks like it ought to work, but it would fail with @mpsGeneric@ being
1142+ -- enabled. One hacky work-around is to perform a @'lookupTypeName' :: String ->
1143+ -- Q (Maybe Name)@ on the @"ModelNameId"@ type string. If the @Id@ is
1144+ -- a reference in the 'EntityMap' and @lookupTypeName@ returns @'Just' name@,
1145+ -- then that 'Name' contains the fully qualified information needed to use the
1146+ -- 'Name' without importing it at the client-site. Then we can perform a bit of
1147+ -- surgery on the 'Name' to strip the @Id@ suffix, turn it into a 'Type', and
1148+ -- apply the 'Key' constructor.
11001149maybeIdType
11011150 :: MkPersistSettings
11021151 -> EntityMap
@@ -1113,25 +1162,90 @@ maybeIdType mps entityMap fieldDef mbackend mnull =
11131162 True
11141163 _ ->
11151164 maybeNullable fieldDef
1116- idType = fromMaybe (ftToType $ unboundFieldType fieldDef) $ do
1117- typ <- extractForeignRef entityMap fieldDef
1118- pure $
1119- ConT ''Key
1120- `AppT ` genericDataType mps typ (VarT $ fromMaybe backendName mbackend)
1165+ idType =
1166+ fromMaybe (ftToType $ unboundFieldType fieldDef) $ do
1167+ typ <- extractForeignRef entityMap fieldDef
1168+ guard ((mpsGeneric mps))
1169+ pure $
1170+ ConT ''Key
1171+ `AppT ` genericDataType mps typ (VarT $ fromMaybe backendName mbackend)
1172+
1173+ -- TODO: if we keep mpsGeneric, this needs to check 'mpsGeneric' and then
1174+ -- append Generic to the model name, probably
1175+ _removeIdFromTypeSuffix :: Name -> Type
1176+ _removeIdFromTypeSuffix oldName@ (Name (OccName nm) nameFlavor) =
1177+ case stripSuffix " Id" (T. pack nm) of
1178+ Nothing ->
1179+ ConT oldName
1180+ Just name ->
1181+ ConT ''Key
1182+ `AppT ` do
1183+ ConT $ Name (OccName (T. unpack name)) nameFlavor
1184+
1185+ -- | TODO: if we keep mpsGeneric, let's incorporate this behavior here, so
1186+ -- end users don't need to import the constructor type as well as the id type
1187+ --
1188+ -- Returns 'Nothing' if the given text does not appear to be a table reference.
1189+ -- In that case, do the usual thing for generating a type name.
1190+ --
1191+ -- Returns a @Just typ@ if the text appears to be a model name, and if the
1192+ -- @ModelId@ type is in scope. The 'Type' is a fully qualified reference to
1193+ -- @'Key' ModelName@ such that end users won't have to import it directly.
1194+ _lookupReferencedTable :: EntityMap -> Text -> Q (Maybe Type )
1195+ _lookupReferencedTable em fieldTypeText = do
1196+ let
1197+ mmodelIdString = do
1198+ fieldTypeNoId <- stripSuffix " Id" fieldTypeText
1199+ _ <- M. lookup (EntityNameHS fieldTypeNoId) em
1200+ pure (T. unpack fieldTypeText)
1201+ case mmodelIdString of
1202+ Nothing ->
1203+ pure Nothing
1204+ Just modelIdString -> do
1205+ mIdName <- lookupTypeName modelIdString
1206+ pure $ fmap _removeIdFromTypeSuffix mIdName
1207+
1208+ _fieldNameEndsWithId :: UnboundFieldDef -> Maybe String
1209+ _fieldNameEndsWithId ufd = go (unboundFieldType ufd)
1210+ where
1211+ go = \ case
1212+ FTTypeCon mmodule name -> do
1213+ a <- stripSuffix " Id" name
1214+ pure $
1215+ T. unpack $ mconcat
1216+ [ case mmodule of
1217+ Nothing ->
1218+ " "
1219+ Just m ->
1220+ mconcat [m, " ." ]
1221+ , a
1222+ , " Id"
1223+ ]
1224+ _ ->
1225+ Nothing
11211226
11221227backendDataType :: MkPersistSettings -> Type
11231228backendDataType mps
11241229 | mpsGeneric mps = backendT
11251230 | otherwise = mpsBackend mps
11261231
1232+ -- | TODO:
1233+ --
1234+ -- if we keep mpsGeneric
1235+ -- then
1236+ -- let's make this fully qualify the generic name
1237+ -- else
1238+ -- let's delete it
11271239genericDataType
11281240 :: MkPersistSettings
11291241 -> EntityNameHS
11301242 -> Type -- ^ backend
11311243 -> Type
11321244genericDataType mps name backend
1133- | mpsGeneric mps = ConT (mkEntityNameHSGenericName name) `AppT ` backend
1134- | otherwise = ConT $ mkEntityNameHSName name
1245+ | mpsGeneric mps =
1246+ ConT (mkEntityNameHSGenericName name) `AppT ` backend
1247+ | otherwise =
1248+ ConT $ mkEntityNameHSName name
11351249
11361250degen :: [Clause ] -> [Clause ]
11371251degen [] =
@@ -2429,8 +2543,10 @@ mkField mps entityMap et fieldDef = do
24292543 con =
24302544 ForallC
24312545 []
2432- [mkEqualP (VarT $ mkName " typ" ) $ maybeIdType mps entityMap fieldDef Nothing Nothing ]
2546+ [mkEqualP (VarT $ mkName " typ" ) fieldT ]
24332547 $ NormalC name []
2548+ fieldT =
2549+ maybeIdType mps entityMap fieldDef Nothing Nothing
24342550 bod <- mkLookupEntityField et (unboundFieldNameHS fieldDef)
24352551 let cla = normalClause
24362552 [ConP name [] ]
@@ -2679,7 +2795,6 @@ mkSymbolToFieldInstances mps entityMap (fixEntityDef -> ed) = do
26792795 symbolToField = $(entityFieldConstr)
26802796 |]
26812797
2682-
26832798-- | Pass in a list of lists of extensions, where any of the given
26842799-- extensions will satisfy it. For example, you might need either GADTs or
26852800-- ExistentialQuantification, so you'd write:
0 commit comments