khibino / haskell-relational-record

This repository includes a joined query generator based on typefull relational algebra, and mapping tools between SQL values list and Haskell record type.
233 stars 36 forks source link

Add TH for deriving tables using specific Haskell record and field names #21

Closed k0001 closed 9 years ago

k0001 commented 9 years ago

This feature allows the user to have more control about the names that are generated so that they can be made more friendly to either qualified imports or unqualified imports as desired. It also allows the SQL column names to be different from the Haskell field names, thus allowing for more flexibility as well.

No camel-case or similar conversion of the names specified by the user is done.

Example usage:

defineTableNamedRecord'
  defaultConfig
  "public"
  "user_profiles"
  "UserProfile"
  [("id", "userProfile_id", [t| Int32 |]),
   ("name", "userProfile_name", [t| String |]),
   ("birth_date", "userProfile_birthDate", [t| Day |])]
  [toConName "Eq", toConName "Show"]

Expands into:

data UserProfile
  = UserProfile {userProfile_id :: !Int32,
                 userProfile_name :: !String,
                 userProfile_birthDate :: !Day}
  deriving (Eq, Show)

columnOffsetsUserProfile :: GHC.Arr.Array Int Int
columnOffsetsUserProfile
  = ((GHC.Arr.listArray (0 :: Int, 3))
     $ (scanl
          (+)
          (0 :: Int)
          [Database.Record.Persistable.runPersistableRecordWidth
             (Database.Record.Persistable.persistableWidth ::
                Database.Record.Persistable.PersistableRecordWidth Int32),
           Database.Record.Persistable.runPersistableRecordWidth
             (Database.Record.Persistable.persistableWidth ::
                Database.Record.Persistable.PersistableRecordWidth String),
           Database.Record.Persistable.runPersistableRecordWidth
             (Database.Record.Persistable.persistableWidth ::
                Database.Record.Persistable.PersistableRecordWidth Day)]))

instance Database.Record.Persistable.PersistableWidth UserProfile where
  Database.Record.Persistable.persistableWidth
    = (Database.Record.Persistable.unsafePersistableRecordWidth
       $ (columnOffsetsUserProfile GHC.Arr.! 3))

instance Database.Relational.Query.Pure.ProductConstructor
 (Int32 -> String -> Day -> UserProfile) where
  Database.Relational.Query.Pure.productConstructor = UserProfile

instance Database.Relational.Query.Table.TableDerivable UserProfile where
  Database.Relational.Query.Table.derivedTable
    = Database.Relational.Query.Table.table
        "PUBLIC.user_profiles" ["id", "name", "birth_date"]

tableOfUserProfile ::
  Database.Relational.Query.Table.Table UserProfile
tableOfUserProfile = Database.Relational.Query.Table.derivedTable

userProfile ::
  Database.Relational.Query.Relation.Relation () UserProfile
userProfile = Database.Relational.Query.Relation.derivedRelation

insertUserProfile ::
  Database.Relational.Query.Type.Insert UserProfile
insertUserProfile
  = Database.Relational.Query.Type.derivedInsert
      Database.Relational.Query.Pi.id'

insertQueryUserProfile ::
  forall p_anE7.
  Database.Relational.Query.Relation.Relation p_anE7 UserProfile
  -> Database.Relational.Query.Type.InsertQuery p_anE7
insertQueryUserProfile
  = Database.Relational.Query.Type.derivedInsertQuery
      Database.Relational.Query.Pi.id'

userProfile_id' ::
  Database.Relational.Query.Pi.Unsafe.Pi UserProfile Int32
userProfile_id'
  = Database.Relational.Query.Pi.Unsafe.definePi
      (columnOffsetsUserProfile array-0.5.1.0:Data.Array.Base.! 0)

userProfile_name' ::
  Database.Relational.Query.Pi.Unsafe.Pi UserProfile String
userProfile_name'
  = Database.Relational.Query.Pi.Unsafe.definePi
      (columnOffsetsUserProfile array-0.5.1.0:Data.Array.Base.! 1)

userProfile_birthDate' ::
  Database.Relational.Query.Pi.Unsafe.Pi UserProfile Day
userProfile_birthDate'
  = Database.Relational.Query.Pi.Unsafe.definePi
      (columnOffsetsUserProfile array-0.5.1.0:Data.Array.Base.! 2)

fromSqlOfUserProfile ::
  Database.Record.FromSql.RecordFromSql Database.HDBC.SqlValue.SqlValue UserProfile
fromSqlOfUserProfile
  = ((((pure UserProfile) <*> Database.Record.FromSql.recordFromSql)
      <*> Database.Record.FromSql.recordFromSql)
     <*> Database.Record.FromSql.recordFromSql)

toSqlOfUserProfile ::
  Database.Record.ToSql.RecordToSql Database.HDBC.SqlValue.SqlValue UserProfile
toSqlOfUserProfile
  = Database.Record.ToSql.wrapToSql
      (\ (UserProfile f1_anE8 f2_anE9 f3_anEa)
         -> ((Database.Record.ToSql.putRecord f1_anE8)
             >>
               ((Database.Record.ToSql.putRecord f2_anE9)
                >>
                  ((Database.Record.ToSql.putRecord f3_anEa)
                   >>
                     (Database.Record.ToSql.putEmpty ghc-prim-0.4.0.0:GHC.Tuple.())))))

instance Database.Record.FromSql.FromSql Database.HDBC.SqlValue.SqlValue UserProfile where
  Database.Record.FromSql.recordFromSql = fromSqlOfUserProfile

instance Database.Record.ToSql.ToSql Database.HDBC.SqlValue.SqlValue UserProfile where
  Database.Record.ToSql.recordToSql = toSqlOfUserProfile
k0001 commented 9 years ago

Hmm... nevermind this pull-request, I think it was a bit premature, as it still doesn't let me keep bindings as organized as I desired.

In case somebody finds it useful, I switched to using this TH internally in my project:

defineTable
  :: String  -- ^ Haskell record name and fields prefix.
  -> String  -- ^ SQL schema name.
  -> String  -- ^ SQL table name.
  -> [(String, String, TypeQ)]
             -- ^ SQL column name, Haskell record field name, Haskell type.
  -> Q [Dec]
defineTable nRecord nSchema nTable cols = do
    recD <- defineRecordType
       (toConName nRecord)
       [(toVarName ("_" ++ nRecord ++ "_" ++ f ++ "_"), t) | (_,f,t) <- cols]
       (toConName <$> ["Eq", "Show", "Data", "Typeable", "Generic"])
    rconD <- defineProductConstructorInstance
       (toTypeCon (toConName nRecord))
       (toDataCon (toConName nRecord))
       [t | (_,_,t) <- cols]
    tableD <- defineTableTypes
       (varNameWithPrefix nRecord "table")
       (varNameWithPrefix nRecord "rel")
       (varNameWithPrefix nRecord "insert")
       (varNameWithPrefix nRecord "insertQuery")
       (toTypeCon (toConName nRecord))
       (mconcat ["\"", nSchema , "\".\"", nTable, "\""])
       [c | (c,_,_) <- cols]
    colD <- defineColumns
       (toConName nRecord)
       [((toVarName ("_" ++ nRecord ++ "_" ++ f), t), Nothing) | (_,f,t) <- cols]
    sqlvD <- makeRecordPersistableWithSqlType
       [t|SqlValue|]
       (varNameWithPrefix nRecord "fromSql",
        varNameWithPrefix nRecord "toSql")
       (toTypeCon (toConName nRecord), toDataCon (toConName nRecord))
       (length cols)
    return $ recD ++ rconD ++ tableD ++ colD ++ sqlvD