Skip to content

Not in scope error when nesting table per class inheritance #994

Closed

Description

@tysonzero

To encode sum types / subtyping I am attempting to use the table per class strategy, similar to the shared primary keys strategy.

However I am running into a very strange not in scope error when modeling nested inheritance:

The following works and seems to give me what I want (if a bit hacky):

Vehicle

Car
    vehicle VehicleId sql=id
    Primary vehicle

Bike
    vehicle VehicleId sql=id
    Primary vehicle

However it breaks when I append the following to the bottom:

Coupe
    car CarId sql=id
    Primary car

Sedan
    car CarId sql=id
    Primary car

With the following error:

    • Not in scope: type constructor or class ‘VehicleId’
    • In the quasi-quotation:
        [persistLowerCase|

This is with persistent-2.9.2 and persistent-template-2.6.0.

Activity

  1. changed the title [-]Not in scope error when nesting table er class inheritance[/-] [+]Not in scope error when nesting table per class inheritance[/+] on Dec 19, 2019
  2. parsonsmatt commented on Dec 19, 2019

    @parsonsmatt
    Collaborator

    There's a simpler case:

      Top
    
      Middle
        top     TopId sql=id
        Primary top
    
      Bottom
        middle  MiddleId sql=id
        Primary middle
    

    which fails with a similar "TopId not in scope" error. If I -ddump-splices, I get this output:

    Output is kinda large, sorry

    λ ~/Projects/persistent/persistent-test/ master* stack ghci src/PersistentTestModelsInherit.hs
    
    Using configuration for persistent-test:lib to load /home/matt/Projects/persistent/persistent-
    test/src/PersistentTestModelsInherit.hs
    persistent-test> configure (lib)
    Configuring persistent-test-2.0.3.0...
    persistent-test> initial-build-steps (lib)
    Configuring GHCi with the following packages: persistent-test
    GHCi, version 8.6.5: http://www.haskell.org/ghc/  :? for help
    Loaded GHCi configuration from /home/matt/.dotfiles/haskell/ghci.symlink
    [1 of 2] Compiling Init             ( /home/matt/Projects/persistent/persistent-test/src/Init.
    hs, interpreted )
    [2 of 2] Compiling PersistentTestModels ( /home/matt/Projects/persistent/persistent-test/src/P
    ersistentTestModelsInherit.hs, interpreted )
    
    /home/matt/Projects/persistent/persistent-test/src/PersistentTestModelsInherit.hs:13:135: erro
    r:
        • Not in scope: type constructor or class ‘TopId’
        • In the quasi-quotation:
            [persistUpperCase|
      Top
    
      Middle
        top     TopId sql=id
        Primary top
    
      Bottom
        middle  MiddleId sql=id
        Primary middle
    |]
       |
    13 | share [mkPersist persistSettings,  mkMigrate "testMigrate", mkDeleteCascade persistSettin
    gs, mkSave "_ignoredSave"] [persistUpperCase|
       |
                                                 ^...
    Failed, one module loaded.
    Loaded GHCi configuration from /tmp/haskell-stack-ghci/46874742/ghci-script
    λ> :set -ddump-splices
    λ> :r
    [2 of 2] Compiling PersistentTestModels ( /home/matt/Projects/persistent/persistent-test/src/P
    ersistentTestModelsInherit.hs, interpreted )
    /home/matt/Projects/persistent/persistent-test/src/PersistentTestModelsInherit.hs:(13,135)-(23
    ,2): Splicing expression
        template-haskell-2.14.0.0:Language.Haskell.TH.Quote.quoteExp
          persistUpperCase
          "\n\
          \  Top\n\
          \\n\
          \  Middle\n\
          \    top     TopId sql=id\n\
          \    Primary top\n\
          \\n\
          \  Bottom\n\
          \    middle  MiddleId sql=id\n\
          \    Primary middle\n"
      ======>
        [((((((((((EntityDef (HaskellName (Data.Text.pack "Top")))
                    (DBName (Data.Text.pack "Top")))
                   ((((((((FieldDef (HaskellName (Data.Text.pack "Id")))
                            (DBName (Data.Text.pack "id")))
                           ((FTTypeCon Nothing) (Data.Text.pack "TopId")))
                          SqlInt64)
                         [])
                        True)
                       ((ForeignRef (HaskellName (Data.Text.pack "Top")))
                          ((FTTypeCon (Just (Data.Text.pack "Data.Int")))
                             (Data.Text.pack "Int64"))))
                      Nothing))
                  [])
                 [])
                [])
               [])
              [])
             (Data.Map.Internal.fromList []))
            False)
           Nothing
           {entityFields = [],
            entityId = (((((((FieldDef (HaskellName (Data.Text.pack "Id")))
                               (DBName (Data.Text.pack "id")))
                              ((FTTypeCon Nothing) (Data.Text.pack "TopId")))
                             (sqlType (Data.Proxy.Proxy :: Data.Proxy.Proxy Int64)))
                            [])
                           True)
                          ((ForeignRef (HaskellName (Data.Text.pack "Top")))
                             ((FTTypeCon (Just (Data.Text.pack "Data.Int")))
                                (Data.Text.pack "Int64"))))
                         Nothing},
         ((((((((((EntityDef (HaskellName (Data.Text.pack "Middle")))
                    (DBName (Data.Text.pack "Middle")))
                   ((((((((FieldDef (HaskellName (Data.Text.pack "Id")))
                            (DBName (Data.Text.pack "id")))
                           ((FTTypeCon Nothing) (Data.Text.pack "MiddleId")))
                          (SqlOther (Data.Text.pack "Primary Key")))
                         [])
                        True)
                       (CompositeRef
                          ((CompositeDef
                              [(((((((FieldDef (HaskellName (Data.Text.pack "top")))
                                       (DBName (Data.Text.pack "id")))
                                      ((FTTypeCon Nothing) (Data.Text.pack "TopId")))
                                     (SqlOther (Data.Text.pack "SqlType unset for top")))
                                    [Data.Text.pack "sql=id"])
                                   True)
                                  NoReference)
                                 Nothing])
                             [])))
                      Nothing))
                  [])
                 [(((((((FieldDef (HaskellName (Data.Text.pack "top")))
                          (DBName (Data.Text.pack "id")))
                         ((FTTypeCon Nothing) (Data.Text.pack "TopId")))
                        (SqlOther (Data.Text.pack "SqlType unset for top")))
                       [Data.Text.pack "sql=id"])
                      True)
                     ((ForeignRef (HaskellName (Data.Text.pack "Top")))
                        ((FTTypeCon (Just (Data.Text.pack "Data.Int")))
                           (Data.Text.pack "Int64"))))
                    Nothing])
                [])
               [])
              [])
             (Data.Map.Internal.fromList []))
            False)
           Nothing
           {entityFields = [(((((((FieldDef
                                     (HaskellName (Data.Text.pack "top")))
                                    (DBName (Data.Text.pack "id")))
                                   ((FTTypeCon Nothing) (Data.Text.pack "TopId")))
                                  (sqlType (Data.Proxy.Proxy :: Data.Proxy.Proxy Int64)))
                                 [Data.Text.pack "sql=id"])
                                True)
                               ((ForeignRef (HaskellName (Data.Text.pack "Top")))
                                  ((FTTypeCon (Just (Data.Text.pack "Data.Int")))
                                     (Data.Text.pack "Int64"))))
                              Nothing],
            entityId = (((((((FieldDef (HaskellName (Data.Text.pack "Id")))
                               (DBName (Data.Text.pack "id")))
                              ((FTTypeCon Nothing) (Data.Text.pack "MiddleId")))
                             (SqlOther (Data.Text.pack "Composite Reference")))
                            [])
                           True)
                          (CompositeRef
                             ((CompositeDef
                                 [(((((((FieldDef (HaskellName (Data.Text.pack "top")))
                                          (DBName (Data.Text.pack "id")))
                                         ((FTTypeCon Nothing) (Data.Text.pack "TopId")))
                                        (SqlOther (Data.Text.pack "SqlType unset for top")))
                                       [Data.Text.pack "sql=id"])
                                      True)
                                     NoReference)
                                    Nothing])
                                [])))
                         Nothing},
         ((((((((((EntityDef (HaskellName (Data.Text.pack "Bottom")))
                    (DBName (Data.Text.pack "Bottom")))
                   ((((((((FieldDef (HaskellName (Data.Text.pack "Id")))
                            (DBName (Data.Text.pack "id")))
                           ((FTTypeCon Nothing) (Data.Text.pack "BottomId")))
                          (SqlOther (Data.Text.pack "Primary Key")))
                         [])
                        True)
                       (CompositeRef
                          ((CompositeDef
                              [(((((((FieldDef (HaskellName (Data.Text.pack "middle")))
                                       (DBName (Data.Text.pack "id")))
                                      ((FTTypeCon Nothing) (Data.Text.pack "MiddleId")))
                                     (SqlOther (Data.Text.pack "SqlType unset for middle")))
                                    [Data.Text.pack "sql=id"])
                                   True)
                                  NoReference)
                                 Nothing])
                             [])))
                      Nothing))
                  [])
                 [(((((((FieldDef (HaskellName (Data.Text.pack "middle")))
                          (DBName (Data.Text.pack "id")))
                         ((FTTypeCon Nothing) (Data.Text.pack "MiddleId")))
                        (SqlOther (Data.Text.pack "SqlType unset for middle")))
                       [Data.Text.pack "sql=id"])
                      True)
                     ((ForeignRef (HaskellName (Data.Text.pack "Middle")))
                        ((FTTypeCon (Just (Data.Text.pack "Data.Int")))
                           (Data.Text.pack "Int64"))))
                    Nothing])
                [])
               [])
              [])
             (Data.Map.Internal.fromList []))
            False)
           Nothing
           {entityFields = [(((((((FieldDef
                                     (HaskellName (Data.Text.pack "middle")))
                                    (DBName (Data.Text.pack "id")))
                                   ((FTTypeCon Nothing) (Data.Text.pack "MiddleId")))
                                  (sqlType (Data.Proxy.Proxy :: Data.Proxy.Proxy TopId)))
                                 [Data.Text.pack "sql=id"])
                                True)
                               ((ForeignRef (HaskellName (Data.Text.pack "Middle")))
                                  ((FTTypeCon (Just (Data.Text.pack "Data.Int")))
                                     (Data.Text.pack "Int64"))))
                              Nothing],
            entityId = (((((((FieldDef (HaskellName (Data.Text.pack "Id")))
                               (DBName (Data.Text.pack "id")))
                              ((FTTypeCon Nothing) (Data.Text.pack "BottomId")))
                             (SqlOther (Data.Text.pack "Composite Reference")))
                            [])
                           True)
                          (CompositeRef
                             ((CompositeDef
                                 [(((((((FieldDef (HaskellName (Data.Text.pack "middle")))
                                          (DBName (Data.Text.pack "id")))
                                         ((FTTypeCon Nothing) (Data.Text.pack "MiddleId")))
                                        (SqlOther (Data.Text.pack "SqlType unset for middle")))
                                       [Data.Text.pack "sql=id"])
                                      True)
                                     NoReference)
                                    Nothing])
                                [])))
                         Nothing}]
    

    The problem is in the Bottom { entityFields = ... } entry.

    -- Bottom:
           {entityFields = [(((((((FieldDef
                                     (HaskellName (Data.Text.pack "middle")))
                                    (DBName (Data.Text.pack "id")))
                                   ((FTTypeCon Nothing) (Data.Text.pack "MiddleId")))
                                                                        -- vvvvvvvvvvvv
                                  (sqlType (Data.Proxy.Proxy :: Data.Proxy.Proxy TopId)))
                                                                        -- ^^^^^^^^^^^^
                                 [Data.Text.pack "sql=id"])
                                True)
                               ((ForeignRef (HaskellName (Data.Text.pack "Middle")))
                                  ((FTTypeCon (Just (Data.Text.pack "Data.Int")))
                                     (Data.Text.pack "Int64"))))
                              Nothing],
    
    -- Middle:
           {entityFields = [(((((((FieldDef
                                     (HaskellName (Data.Text.pack "top")))
                                    (DBName (Data.Text.pack "id")))
                                   ((FTTypeCon Nothing) (Data.Text.pack "TopId")))
                                                                        -- vvvvvvvvvvvv
                                  (sqlType (Data.Proxy.Proxy :: Data.Proxy.Proxy Int64)))
                                                                        -- ^^^^^^^^^^^^
                                 [Data.Text.pack "sql=id"])
                                True)
                               ((ForeignRef (HaskellName (Data.Text.pack "Top")))
                                  ((FTTypeCon (Just (Data.Text.pack "Data.Int")))
                                     (Data.Text.pack "Int64"))))
                              Nothing],
    

    TopId is being referenced directly here for some reason, instead of a Proxy Int64 and it'll be set later by the TH machinery. So that's not right!

    The ForeignRef stuff is fine, it's just the FieldDef that is wrong.

    Cleaning up the Middle, we get:

        { entityFields = 
            [ FieldDef 
                (HaskellName "top") 
                (DBName "id") 
                ((FTTypeCon Nothing) "TopId") 
                (sqlType (Data.Proxy.Proxy :: Data.Proxy.Proxy Int64)) 
                [Data.Text.pack "sql=id"] 
                True 
                (ForeignRef (HaskellName "Top") ((FTTypeCon (Just "Data.Int")) "Int64")) 
                Nothing
            ]
        }

    While Bottom has this:

        { entityFields = 
            [ FieldDef 
                (HaskellName "middle") 
                (DBName "id") 
                (FTTypeCon Nothing "MiddleId") 
                (sqlType (Data.Proxy.Proxy :: Data.Proxy.Proxy TopId)) 
                [Data.Text.pack "sql=id"] 
                True 
                (ForeignRef (HaskellName "Middle") ((FTTypeCon (Just "Data.Int")) "Int64")) 
                Nothing
            ]

    So the problem is very specifically when the QuasiQuoter is looking up the field type. Something is properly setting the Proxy :: Proxy (TopId -> Int64) in the .Quasi module, but I can't really tell what.

  3. tysonzero commented on Feb 11, 2020

    @tysonzero
    Author

    @parsonsmatt

    Any thoughts on where to go from here? I'm happy to help, although I admittedly know very little about Template Haskell and quasi quotes.

    We are trying to move toward table per class inheritance due to the significant issues with our current approximation of it (lots of joins, extra error possibilities, significantly more key-passing noise). This approach is currently the only way to implement table per class inheritance in persistent.

  4. parsonsmatt commented on Feb 14, 2020

    @parsonsmatt
    Collaborator

    #1042 is a conglomerate issue to track failures of key/id lookups

  5. added this to the 2.13 milestone on May 4, 2021
  6. linked a pull request that will close this issueQuasiQuoter Improvements #1256on May 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions