packages feed

dataframe-lazy 1.1.0.0 → 1.1.0.2

raw patch · 6 files changed

+151/−16 lines, 6 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- DataFrame.Typed.Lazy: join :: forall (left :: [Type]) (right :: [Type]). JoinType -> Text -> Text -> TypedLazyDataFrame left -> TypedLazyDataFrame right -> TypedLazyDataFrame left
+ DataFrame.Lazy.Internal.DataFrame: scanCsvStreamingWith :: CsvReader -> Schema -> Text -> LazyDataFrame
+ DataFrame.Lazy.Internal.LogicalPlan: CsvSourceStreaming :: FilePath -> Char -> CsvReader -> DataSource
+ DataFrame.Typed.Lazy: fullOuterJoin :: forall (key :: Symbol) (left :: [Type]) (right :: [Type]). (KnownSymbol key, AssertAllPresent '[key] left, AssertAllPresent '[key] right, AssertKeyTypesMatch '[key] left right) => TypedLazyDataFrame left -> TypedLazyDataFrame right -> TypedLazyDataFrame (FullOuterJoinSchema '[key] left right)
+ DataFrame.Typed.Lazy: innerJoin :: forall (key :: Symbol) (left :: [Type]) (right :: [Type]). (KnownSymbol key, AssertAllPresent '[key] left, AssertAllPresent '[key] right, AssertKeyTypesMatch '[key] left right) => TypedLazyDataFrame left -> TypedLazyDataFrame right -> TypedLazyDataFrame (InnerJoinSchema '[key] left right)
+ DataFrame.Typed.Lazy: leftJoin :: forall (key :: Symbol) (left :: [Type]) (right :: [Type]). (KnownSymbol key, AssertAllPresent '[key] left, AssertAllPresent '[key] right, AssertKeyTypesMatch '[key] left right) => TypedLazyDataFrame left -> TypedLazyDataFrame right -> TypedLazyDataFrame (LeftJoinSchema '[key] left right)
+ DataFrame.Typed.Lazy: rightJoin :: forall (key :: Symbol) (left :: [Type]) (right :: [Type]). (KnownSymbol key, AssertAllPresent '[key] left, AssertAllPresent '[key] right, AssertKeyTypesMatch '[key] left right) => TypedLazyDataFrame left -> TypedLazyDataFrame right -> TypedLazyDataFrame (RightJoinSchema '[key] left right)

Files

dataframe-lazy.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               dataframe-lazy-version:            1.1.0.0+version:            1.1.0.2 synopsis:           Lazy query engine for the dataframe ecosystem. description:     The lazy/streaming query engine: relational-algebra plans, optimizer,
src/DataFrame/Lazy/Internal/DataFrame.hs view
@@ -73,6 +73,13 @@         , batchSize = 1_000_000         } +scanCsvStreamingWith :: CsvReader -> Schema -> T.Text -> LazyDataFrame+scanCsvStreamingWith reader schema path =+    LazyDataFrame+        { plan = Scan (CsvSourceStreaming (T.unpack path) ',' reader) schema+        , batchSize = 1_000_000+        }+ -- | Scan a character-separated file with the default attoparsec reader. scanSeparated :: Char -> Schema -> T.Text -> LazyDataFrame scanSeparated = scanSeparatedWith readCsvWithSchema
src/DataFrame/Lazy/Internal/Executor.hs view
@@ -43,8 +43,9 @@ import Control.Concurrent.STM (atomically) import Control.Concurrent.STM.TBQueue (newTBQueueIO, readTBQueue, writeTBQueue) import Control.Exception (evaluate)-import Control.Monad (filterM, forM, forM_, when)+import Control.Monad (filterM, forM, forM_, when, unless) import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as C8 import Data.IORef import Data.Int (Int16, Int32, Int64, Int8) import qualified Data.Map as M@@ -75,6 +76,7 @@ import System.Directory (doesDirectoryExist, removeFile) import System.FilePath ((</>)) import System.FilePath.Glob (glob)+import System.IO (IOMode (ReadMode), hIsEOF, withFile) import System.IO.Temp (emptySystemTempFile) import Type.Reflection (typeRep) @@ -141,6 +143,7 @@ -} isBounded :: PhysicalPlan -> Bool isBounded (PhysicalScan (CsvSource{}) _) = True+isBounded (PhysicalScan (CsvSourceStreaming{}) _) = False isBounded (PhysicalScan (ParquetSource _) _) = True isBounded (PhysicalProject _ c) = isBounded c isBounded (PhysicalFilter _ c) = isBounded c@@ -187,6 +190,8 @@ -- Scan ----------------------------------------------------------------------- buildStream (PhysicalScan (CsvSource path sep reader) cfg) =     executeCsvScan path sep reader cfg+buildStream (PhysicalScan (CsvSourceStreaming path _sep reader) cfg) =+    executeCsvScanStreaming path reader cfg buildStream (PhysicalScan (ParquetSource path) cfg) =     executeParquetScan path cfg buildStream (PhysicalSpill child path) = do@@ -610,6 +615,47 @@                      in return (Just df')         ) +executeCsvScanStreaming :: FilePath -> CsvReader -> ScanConfig -> IO Stream+executeCsvScanStreaming path reader cfg = do+    let schema = scanSchema cfg+        batchSz = scanBatchSize cfg+        windowBytes = 64 * 1024 * 1024 :: Int+    queue <- newTBQueueIO 8+    _ <- forkIO $+        withFile path ReadMode $ \h -> do+            header <- C8.hGetLine h+            let feed bytes =+                    unless (BS.null bytes) $ do+                        p <- emptySystemTempFile "lazy_csv_win_.csv"+                        BS.writeFile p (header <> BS.singleton nl <> bytes)+                        df <- reader schema p+                        removeFile p+                        forM_ (sliceIntoBatches batchSz df) $ \b ->+                            atomically (writeTBQueue queue (Just b))+                loop leftover = do+                    eof <- hIsEOF h+                    if eof+                        then feed leftover >> atomically (writeTBQueue queue Nothing)+                        else do+                            chunk <- BS.hGetSome h windowBytes+                            let buf = leftover <> chunk+                            case BS.elemIndexEnd nl buf of+                                Nothing -> loop buf+                                Just i -> feed (BS.take i buf) >> loop (BS.drop (i + 1) buf)+            loop BS.empty+    return . Stream $ do+        mb <- atomically (readTBQueue queue)+        case mb of+            Nothing -> atomically (writeTBQueue queue Nothing) >> return Nothing+            Just df ->+                let df' = case scanPushdownPredicate cfg of+                        Nothing -> df+                        Just p -> Sub.filterWhere p df+                 in return (Just df')+  where+    nl :: Word8+    nl = 0x0A+ -- | Slice a 'DataFrame' into row-bounded batches of at most @n@ rows. sliceIntoBatches :: Int -> D.DataFrame -> [D.DataFrame] sliceIntoBatches n df =@@ -656,15 +702,26 @@  {- | Route join to the existing Operations.Join implementation. When the left and right key names differ, rename the right key before joining.++'Join.join' retains its first 'DataFrame' argument and makes the second one+optional, so the lazy left sub-query ('leftDf') must be passed first for LEFT+and RIGHT joins to retain the side the caller means. INNER and FULL_OUTER are+symmetric in which rows survive, and 'Operations.Join' orders their output+columns with the renamed right frame first, so they keep the @rightDf leftDf@+order. (Passing @rightDf@ first for LEFT/RIGHT was the source of a silent+left/right inversion: a left join dropped the left side's unmatched rows.) -} performJoin ::     Join.JoinType -> T.Text -> T.Text -> D.DataFrame -> D.DataFrame -> D.DataFrame performJoin jt leftKey rightKey leftDf rightDf =-    if leftKey == rightKey-        then Join.join jt [leftKey] rightDf leftDf-        else-            let rightRenamed = Core.rename rightKey leftKey rightDf-             in Join.join jt [leftKey] rightRenamed leftDf+    case jt of+        Join.LEFT -> Join.join jt [leftKey] leftDf rightRenamed+        Join.RIGHT -> Join.join jt [leftKey] leftDf rightRenamed+        _ -> Join.join jt [leftKey] rightRenamed leftDf+  where+    rightRenamed+        | leftKey == rightKey = rightDf+        | otherwise = Core.rename rightKey leftKey rightDf  -- --------------------------------------------------------------------------- -- Sort order conversion
src/DataFrame/Lazy/Internal/LogicalPlan.hs view
@@ -13,11 +13,14 @@ data DataSource     = -- | path, separator, CSV reader (e.g. attoparsec or SIMD)       CsvSource FilePath Char CsvReader+    | CsvSourceStreaming FilePath Char CsvReader     | ParquetSource FilePath  instance Show DataSource where     show (CsvSource path sep _) =         "CsvSource " ++ show path ++ " " ++ show sep ++ " <reader>"+    show (CsvSourceStreaming path sep _) =+        "CsvSourceStreaming " ++ show path ++ " " ++ show sep ++ " <reader>"     show (ParquetSource path) = "ParquetSource " ++ show path  -- | Sort direction used in Sort nodes and the public API.
src/DataFrame/Lazy/Internal/Optimizer.hs view
@@ -180,6 +180,14 @@     PhysicalScan         (CsvSource path sep reader)         (ScanConfig batchSz sep schema Nothing)+toPhysical batchSz (Filter p (Scan (CsvSourceStreaming path sep reader) schema)) =+    PhysicalScan+        (CsvSourceStreaming path sep reader)+        (ScanConfig batchSz sep schema (Just p))+toPhysical batchSz (Scan (CsvSourceStreaming path sep reader) schema) =+    PhysicalScan+        (CsvSourceStreaming path sep reader)+        (ScanConfig batchSz sep schema Nothing) toPhysical batchSz (Filter p (Scan (ParquetSource path) schema)) =     PhysicalScan         (ParquetSource path)
src/DataFrame/Typed/Lazy.hs view
@@ -58,7 +58,10 @@     aggregate,      -- * Joins-    join,+    innerJoin,+    leftJoin,+    rightJoin,+    fullOuterJoin,      -- * Sort     sortBy,@@ -84,7 +87,7 @@ import DataFrame.Lazy.Internal.DataFrame (LazyDataFrame) import qualified DataFrame.Lazy.Internal.DataFrame as L import DataFrame.Lazy.Internal.LogicalPlan (SortOrder (..))-import DataFrame.Operations.Join (JoinType)+import DataFrame.Operations.Join (JoinType (..)) import DataFrame.Typed.Expr import DataFrame.Typed.Freeze (unsafeFreeze) import DataFrame.Typed.Schema@@ -174,16 +177,73 @@ aggregate tagg (TLG (keys, ldf)) =     TLD (L.groupBy keys (aggToNamedExprs tagg) ldf) --- | Join two lazy queries on a shared key column.-join ::+-- | Typed inner join on a single key column present in both schemas.+innerJoin ::+    forall (key :: Symbol) left right.+    ( KnownSymbol key+    , AssertAllPresent '[key] left+    , AssertAllPresent '[key] right+    , AssertKeyTypesMatch '[key] left right+    ) =>+    TypedLazyDataFrame left ->+    TypedLazyDataFrame right ->+    TypedLazyDataFrame (InnerJoinSchema '[key] left right)+innerJoin = joinOn @key INNER++-- | Typed left join. The right table's non-key columns become @Maybe@.+leftJoin ::+    forall (key :: Symbol) left right.+    ( KnownSymbol key+    , AssertAllPresent '[key] left+    , AssertAllPresent '[key] right+    , AssertKeyTypesMatch '[key] left right+    ) =>+    TypedLazyDataFrame left ->+    TypedLazyDataFrame right ->+    TypedLazyDataFrame (LeftJoinSchema '[key] left right)+leftJoin = joinOn @key LEFT++-- | Typed right join. The left table's non-key columns become @Maybe@.+rightJoin ::+    forall (key :: Symbol) left right.+    ( KnownSymbol key+    , AssertAllPresent '[key] left+    , AssertAllPresent '[key] right+    , AssertKeyTypesMatch '[key] left right+    ) =>+    TypedLazyDataFrame left ->+    TypedLazyDataFrame right ->+    TypedLazyDataFrame (RightJoinSchema '[key] left right)+rightJoin = joinOn @key RIGHT++-- | Typed full outer join. Non-key columns from both tables become @Maybe@.+fullOuterJoin ::+    forall (key :: Symbol) left right.+    ( KnownSymbol key+    , AssertAllPresent '[key] left+    , AssertAllPresent '[key] right+    , AssertKeyTypesMatch '[key] left right+    ) =>+    TypedLazyDataFrame left ->+    TypedLazyDataFrame right ->+    TypedLazyDataFrame (FullOuterJoinSchema '[key] left right)+fullOuterJoin = joinOn @key FULL_OUTER++{- | Runtime delegation shared by the typed joins. The lazy backend joins on a+single key whose name is the same in both schemas; the result schema is+computed by the caller's join-specific type family.+-}+joinOn ::+    forall (key :: Symbol) left right out.+    (KnownSymbol key) =>     JoinType ->-    T.Text ->-    T.Text ->     TypedLazyDataFrame left ->     TypedLazyDataFrame right ->-    TypedLazyDataFrame left -- TODO: compute join result schema-join jt leftKey rightKey (TLD left) (TLD right) =-    TLD (L.join jt leftKey rightKey left right)+    TypedLazyDataFrame out+joinOn jt (TLD left) (TLD right) =+    TLD (L.join jt keyName keyName left right)+  where+    keyName = T.pack (symbolVal (Proxy @key))  -- | Sort the result by column name and direction. sortBy ::