dataframe-persistent 0.1.0.0 → 0.2.0.0
raw patch · 5 files changed
+43/−27 lines, 5 filesdep ~dataframePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: dataframe
API changes (from Hackage documentation)
Files
- CHANGELOG.md +5/−0
- README.md +11/−7
- dataframe-persistent.cabal +4/−7
- src/DataFrame/IO/Persistent/TH.hs +13/−1
- tests/PersistentTests.hs +10/−12
CHANGELOG.md view
@@ -0,0 +1,5 @@+## 0.2.0.0+* Support generating dataframe expressions for a table so we don't have to type them out manually. Works the same as `deriveColumns`.++## 0.1.0.0+* Initial version supporting reading dataframes from SQL using Persistent.
README.md view
@@ -17,7 +17,7 @@ ```yaml dependencies:-- dataframe ^>= 0.3+- dataframe ^>= 0.4 - dataframe-persistent ^>= 0.1 - persistent >= 2.14 - persistent-sqlite >= 2.13 # or your preferred backend@@ -27,7 +27,7 @@ ```cabal build-depends:- dataframe ^>= 0.3,+ dataframe ^>= 0.4, dataframe-persistent ^>= 0.1, persistent >= 2.14, persistent-sqlite >= 2.13@@ -60,6 +60,8 @@ import DataFrame.IO.Persistent.TH import qualified Data.Vector as V +import DataFrame.Functions ((.<))+ -- Define your entities share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase| TestUser@@ -92,8 +94,9 @@ liftIO $ putStrLn $ "Active users: " ++ show (nRows activeUsersDF) -- Process with DataFrame operations- let youngUsers = DF.filter @Int "age" (< 30) allUsersDF- ages = V.toList $ DF.columnAsVector @Int "age" youngUsers+ -- Expressions are automaticaly generated.+ let youngUsers = DF.filterWhere (test_user_age .< 30) allUsersDF+ ages = V.toList $ DF.columnAsVector test_user_age youngUsers liftIO $ putStrLn $ "Young user ages: " ++ show ages -- Custom configuration@@ -111,6 +114,7 @@ - **Template Haskell support** for automatic instance generation - **Configurable loading** with batch size and column selection - **Column name cleaning** - removes table prefixes automatically (e.g., `test_user_name` → `name`)+- **Automatically generate typed expressions** - creates expressions in snake case prefixed by table name (e.g `test_user_name`). - **Type preservation** - maintains proper types for Text, Int, Bool, Day, etc. - **Empty DataFrame support** - preserves column structure even with no data - **Support for all Persistent backends** (SQLite, PostgreSQL, MySQL, etc.)@@ -141,9 +145,9 @@ ```haskell -- Extract specific column data-let names = V.toList $ DF.columnAsVector @Text "name" df- ages = V.toList $ DF.columnAsVector @Int "age" df- activeFlags = V.toList $ DF.columnAsVector @Bool "active" df+let names = V.toList $ DF.columnAsVector test_user_name df+ ages = V.toList $ DF.columnAsVector test_user_age df+ activeFlags = V.toList $ DF.columnAsVector test_user_active df ``` ## Examples
dataframe-persistent.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: dataframe-persistent-version: 0.1.0.0+version: 0.2.0.0 synopsis: Persistent database integration for the dataframe library @@ -37,7 +37,7 @@ build-depends: base >= 4 && <5, bytestring >= 0.11 && < 0.13, containers >= 0.6.7 && < 0.9,- dataframe ^>= 0.3,+ dataframe ^>= 0.4, persistent >= 2.14 && < 3, template-haskell >= 2.0 && < 3, text >= 2.0 && < 3,@@ -53,20 +53,17 @@ main-is: Main.hs other-modules: PersistentTests build-depends: base >= 4 && < 5,- dataframe ^>= 0.3,+ dataframe ^>= 0.4, dataframe-persistent,- -- directory >= 1.3.0.0 && < 2, HUnit ^>= 1.6, monad-logger >= 0.3 && < 0.4, persistent >= 2.14 && < 3, persistent-sqlite >= 2.13 && < 3,- -- persistent-template >= 2.12 && < 3, resourcet >= 0.1 && < 2, temporary >= 1.3 && < 2, text >= 2.0 && < 3, time >= 1.12 && < 2, transformers >= 0.5 && < 0.7,- vector ^>= 0.13,- -- unliftio-core >= 0.2 && < 0.3+ vector ^>= 0.13 hs-source-dirs: tests default-language: Haskell2010
src/DataFrame/IO/Persistent/TH.hs view
@@ -19,14 +19,18 @@ import Data.Text (Text) import qualified Data.Text as T import qualified Data.Vector as V+import DataFrame.Functions (col) import DataFrame.IO.Persistent import qualified DataFrame.Internal.Column as DFCol+import DataFrame.Internal.Expression import Database.Persist import Database.Persist.Sql (fromSqlKey) import Database.Persist.TH import Language.Haskell.TH import Language.Haskell.TH.Syntax (Lift, lift) +import Debug.Trace (trace)+ -- | Derive EntityToDataFrame instance using Template Haskell deriveEntityToDataFrame :: Name -> Q [Dec] deriveEntityToDataFrame entityName = do@@ -64,8 +68,16 @@ [] (AppT (ConT ''EntityToDataFrame) (ConT entityName)) [colNamesMethod, entityDataMethod]+ dataframeExprs <- forM fields $ \(raw, _, ty) -> do+ let nm = camelToSnake (nameBase raw)+ let colName = camelToSnake (drop (length entityNameStr) (nameBase raw))+ trace ((nm <> " :: Expr " <> (show ty))) pure ()+ let n = mkName nm+ sig <- sigD n [t|Expr $(pure ty)|]+ val <- valD (varP n) (normalB [|col $(lift colName)|]) []+ pure [sig, val] - return [instanceDec]+ return ([instanceDec] ++ concat dataframeExprs) _ -> fail $ "deriveEntityToDataFrame: " ++ show entityName ++ " must be a record type"
tests/PersistentTests.hs view
@@ -36,6 +36,8 @@ import Test.HUnit import UnliftIO.Resource (ResourceT) +import DataFrame.Functions ((.<))+ -- Define test entities share [mkPersist sqlSettings, mkMigrate "migrateAll"]@@ -107,7 +109,7 @@ assertBool "Has active column" ("active" `elem` cols) -- Check data values- let names = V.toList (DF.columnAsVector @Text "name" df)+ let names = V.toList (DF.columnAsVector test_user_name df) assertEqual "Names match" ["Alice", "Bob", "Charlie", "Diana"] names @@ -123,7 +125,7 @@ print cols assertBool "Has id column" ("id" `elem` cols) assertBool "Has name column" ("name" `elem` cols)- let names = V.toList (DF.columnAsVector @Text "name" (DF.take 5 df))+ let names = V.toList (DF.columnAsVector artist_name (DF.take 5 df)) assertEqual "Names match"@@ -142,7 +144,7 @@ assertEqual "Active users count" 3 (nRows df) -- Check all loaded users are active- let activeFlags = V.toList $ DF.columnAsVector @Bool "active" df+ let activeFlags = V.toList $ DF.columnAsVector test_user_active df assertBool "All active" (and activeFlags) -- Test custom configuration@@ -190,23 +192,19 @@ df <- fromPersistent @TestUser [] liftIO $ do -- Filter operation- let youngUsers = DF.filter @Int "age" (< 30) df+ let youngUsers = DF.filter test_user_age (< 30) df assertEqual "Young users count" 2 (nRows youngUsers) -- Sort operation- let sorted = DF.sortBy DF.Ascending ["age"] df- let ages = V.toList $ DF.columnAsVector @Int "age" sorted+ let sorted = DF.sortBy [DF.Asc "age"] df+ let ages = V.toList $ DF.columnAsVector test_user_age sorted assertEqual "Ages sorted" [25, 28, 30, 35] ages -- Derive column let withAgeGroup =- DF.derive+ DF.derive @Text "age_group"- ( F.ifThenElse- (F.col @Int "age" `F.lt` F.lit 30)- (F.lit @Text "young")- (F.lit @Text "adult")- )+ (F.ifThenElse (test_user_age .< 30) "young" "adult") df assertEqual "Has age_group column" 5 (length $ DF.columnNames withAgeGroup)