packages feed

dataframe 0.5.0.0 → 0.5.0.1

raw patch · 4 files changed

+237/−38 lines, 4 filesdep ~granitePVP: minor bump suggested

API additions: PVP suggests at least a minor version bump

Dependency ranges changed: granite

API changes (from Hackage documentation)

+ DataFrame: ParquetReadOptions :: Maybe [Text] -> Maybe (Expr Bool) -> Maybe (Int, Int) -> ParquetReadOptions
+ DataFrame: [predicate] :: ParquetReadOptions -> Maybe (Expr Bool)
+ DataFrame: [rowRange] :: ParquetReadOptions -> Maybe (Int, Int)
+ DataFrame: [selectedColumns] :: ParquetReadOptions -> Maybe [Text]
+ DataFrame: data ParquetReadOptions
+ DataFrame: defaultParquetReadOptions :: ParquetReadOptions
+ DataFrame: readParquetFilesWithOpts :: ParquetReadOptions -> FilePath -> IO DataFrame
+ DataFrame: readParquetWithOpts :: ParquetReadOptions -> FilePath -> IO DataFrame
+ DataFrame.IO.Parquet: ParquetReadOptions :: Maybe [Text] -> Maybe (Expr Bool) -> Maybe (Int, Int) -> ParquetReadOptions
+ DataFrame.IO.Parquet: [predicate] :: ParquetReadOptions -> Maybe (Expr Bool)
+ DataFrame.IO.Parquet: [rowRange] :: ParquetReadOptions -> Maybe (Int, Int)
+ DataFrame.IO.Parquet: [selectedColumns] :: ParquetReadOptions -> Maybe [Text]
+ DataFrame.IO.Parquet: applyPredicate :: ParquetReadOptions -> DataFrame -> DataFrame
+ DataFrame.IO.Parquet: applyReadOptions :: ParquetReadOptions -> DataFrame -> DataFrame
+ DataFrame.IO.Parquet: applyRowRange :: ParquetReadOptions -> DataFrame -> DataFrame
+ DataFrame.IO.Parquet: applySelectedColumns :: ParquetReadOptions -> DataFrame -> DataFrame
+ DataFrame.IO.Parquet: data ParquetReadOptions
+ DataFrame.IO.Parquet: defaultParquetReadOptions :: ParquetReadOptions
+ DataFrame.IO.Parquet: instance GHC.Classes.Eq DataFrame.IO.Parquet.ParquetReadOptions
+ DataFrame.IO.Parquet: instance GHC.Show.Show DataFrame.IO.Parquet.ParquetReadOptions
+ DataFrame.IO.Parquet: readParquetFilesWithOpts :: ParquetReadOptions -> FilePath -> IO DataFrame
+ DataFrame.IO.Parquet: readParquetWithOpts :: ParquetReadOptions -> FilePath -> IO DataFrame

Files

dataframe.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               dataframe-version:            0.5.0.0+version:            0.5.0.1  synopsis: A fast, safe, and intuitive DataFrame library. @@ -101,7 +101,7 @@                       cassava >= 0.1 && < 1,                       containers >= 0.6.7 && < 0.9,                       directory >= 1.3.0.0 && < 2,-                      granite == 0.3.0.5,+                      granite ^>= 0.4,                       hashable >= 1.2 && < 2,                       process ^>= 1.6,                       snappy-hs ^>= 0.1,
src/DataFrame.hs view
@@ -105,6 +105,7 @@   * @D.readTsv :: FilePath -> IO DataFrame@   * @D.writeCsv :: FilePath -> DataFrame -> IO ()@   * @D.readParquet :: FilePath -> IO DataFrame@+  * @D.readParquetWithOpts :: ParquetReadOptions -> FilePath -> IO DataFrame@  __Exploration__ @@ -252,7 +253,14 @@     writeCsv,     writeSeparated,  )-import DataFrame.IO.Parquet as Parquet (readParquet, readParquetFiles)+import DataFrame.IO.Parquet as Parquet (+    ParquetReadOptions (..),+    defaultParquetReadOptions,+    readParquet,+    readParquetFiles,+    readParquetFilesWithOpts,+    readParquetWithOpts,+ ) import DataFrame.IO.Unstable.CSV as UnstableCSV (     fastReadCsvUnstable,     fastReadTsvUnstable,
src/DataFrame/IO/Parquet.hs view
@@ -6,6 +6,7 @@  module DataFrame.IO.Parquet where +import Control.Exception (throw) import Control.Monad import Data.Bits import qualified Data.ByteString as BSO@@ -14,15 +15,19 @@ import Data.Int import qualified Data.List as L import qualified Data.Map as M+import qualified Data.Set as S import qualified Data.Text as T import Data.Text.Encoding import Data.Time import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Data.Word+import DataFrame.Errors (DataFrameException (ColumnNotFoundException)) import qualified DataFrame.Internal.Column as DI import DataFrame.Internal.DataFrame (DataFrame)+import DataFrame.Internal.Expression (Expr, getColumns) import qualified DataFrame.Operations.Core as DI import DataFrame.Operations.Merge ()+import qualified DataFrame.Operations.Subset as DS import System.FilePath.Glob (glob)  import DataFrame.IO.Parquet.Dictionary@@ -35,6 +40,21 @@ import qualified Data.Vector.Unboxed as VU import System.FilePath ((</>)) +data ParquetReadOptions = ParquetReadOptions+    { selectedColumns :: Maybe [T.Text]+    , predicate :: Maybe (Expr Bool)+    , rowRange :: Maybe (Int, Int)+    }+    deriving (Eq, Show)++defaultParquetReadOptions :: ParquetReadOptions+defaultParquetReadOptions =+    ParquetReadOptions+        { selectedColumns = Nothing+        , predicate = Nothing+        , rowRange = Nothing+        }+ {- | Read a parquet file from path and load it into a dataframe.  ==== __Example__@@ -43,11 +63,39 @@ @ -} readParquet :: FilePath -> IO DataFrame-readParquet path = do+readParquet = readParquetWithOpts defaultParquetReadOptions++readParquetWithOpts :: ParquetReadOptions -> FilePath -> IO DataFrame+readParquetWithOpts opts path = do     fileMetadata <- readMetadataFromPath path     let columnPaths = getColumnPaths (drop 1 $ schema fileMetadata)     let columnNames = map fst columnPaths+    let leafNames = map (last . T.splitOn ".") columnNames+    let availableSelectedColumns = L.nub leafNames+    let predicateColumns = maybe [] (L.nub . getColumns) (predicate opts)+    let selectedColumnsForRead = case selectedColumns opts of+            Nothing -> Nothing+            Just selected -> Just (L.nub (selected ++ predicateColumns))+    let selectedColumnSet = S.fromList <$> selectedColumnsForRead+    let shouldReadColumn colName _ =+            case selectedColumnSet of+                Nothing -> True+                Just selected -> colName `S.member` selected +    case selectedColumnsForRead of+        Nothing -> pure ()+        Just requested ->+            let missing = requested L.\\ availableSelectedColumns+             in unless+                    (L.null missing)+                    ( throw+                        ( ColumnNotFoundException+                            (T.pack $ show missing)+                            "readParquetWithOpts"+                            availableSelectedColumns+                        )+                    )+     colMap <- newIORef (M.empty :: M.Map T.Text DI.Column)     lTypeMap <- newIORef (M.empty :: M.Map T.Text LogicalType) @@ -77,53 +125,59 @@                         then T.pack $ "col_" ++ show colIdx                         else T.pack $ last colPath -            let colDataPageOffset = columnDataPageOffset metadata-            let colDictionaryPageOffset = columnDictionaryPageOffset metadata-            let colStart =-                    if colDictionaryPageOffset > 0 && colDataPageOffset > colDictionaryPageOffset-                        then colDictionaryPageOffset-                        else colDataPageOffset-            let colLength = columnTotalCompressedSize metadata+            when (shouldReadColumn colName colPath) $ do+                let colDataPageOffset = columnDataPageOffset metadata+                let colDictionaryPageOffset = columnDictionaryPageOffset metadata+                let colStart =+                        if colDictionaryPageOffset > 0 && colDataPageOffset > colDictionaryPageOffset+                            then colDictionaryPageOffset+                            else colDataPageOffset+                let colLength = columnTotalCompressedSize metadata -            let columnBytes = BSO.take (fromIntegral colLength) (BSO.drop (fromIntegral colStart) contents)+                let columnBytes = BSO.take (fromIntegral colLength) (BSO.drop (fromIntegral colStart) contents) -            pages <- readAllPages (columnCodec metadata) columnBytes+                pages <- readAllPages (columnCodec metadata) columnBytes -            let maybeTypeLength =-                    if columnType metadata == PFIXED_LEN_BYTE_ARRAY-                        then getTypeLength colPath-                        else Nothing+                let maybeTypeLength =+                        if columnType metadata == PFIXED_LEN_BYTE_ARRAY+                            then getTypeLength colPath+                            else Nothing -            let primaryEncoding = maybe EPLAIN fst (L.uncons (columnEncodings metadata))+                let primaryEncoding = maybe EPLAIN fst (L.uncons (columnEncodings metadata)) -            let schemaTail = drop 1 (schema fileMetadata)-            let colPath = columnPathInSchema (columnMetaData colChunk)-            let (maxDef, maxRep) = levelsForPath schemaTail colPath-            let lType = logicalType (schemaTail !! colIdx)-            column <--                processColumnPages-                    (maxDef, maxRep)-                    pages-                    (columnType metadata)-                    primaryEncoding-                    maybeTypeLength-                    lType+                let schemaTail = drop 1 (schema fileMetadata)+                let (maxDef, maxRep) = levelsForPath schemaTail colPath+                let lType = logicalType (schemaTail !! colIdx)+                column <-+                    processColumnPages+                        (maxDef, maxRep)+                        pages+                        (columnType metadata)+                        primaryEncoding+                        maybeTypeLength+                        lType -            modifyIORef colMap (M.insertWith DI.concatColumnsEither colName column)-            modifyIORef lTypeMap (M.insert colName lType)+                modifyIORef colMap (M.insertWith DI.concatColumnsEither colName column)+                modifyIORef lTypeMap (M.insert colName lType)      finalColMap <- readIORef colMap     finalLTypeMap <- readIORef lTypeMap     let orderedColumns =             map-                ( \name -> (name, applyLogicalType (finalLTypeMap M.! name) $ finalColMap M.! name)+                ( \name ->+                    ( name+                    , applyLogicalType (finalLTypeMap M.! name) $ finalColMap M.! name+                    )                 )                 (filter (`M.member` finalColMap) columnNames) -    pure $ DI.fromNamedColumns orderedColumns+    pure $ applyReadOptions opts (DI.fromNamedColumns orderedColumns)  readParquetFiles :: FilePath -> IO DataFrame-readParquetFiles path = do+readParquetFiles = readParquetFilesWithOpts defaultParquetReadOptions++readParquetFilesWithOpts :: ParquetReadOptions -> FilePath -> IO DataFrame+readParquetFilesWithOpts opts path = do     isDir <- doesDirectoryExist path      let pat = if isDir then path </> "*" else path@@ -137,9 +191,28 @@             error $                 "readParquetFiles: no parquet files found for " ++ path         _ -> do-            dfs <- mapM readParquet files-            pure (mconcat dfs)+            let optsWithoutRowRange = opts{rowRange = Nothing}+            dfs <- mapM (readParquetWithOpts optsWithoutRowRange) files+            pure (applyRowRange opts (mconcat dfs)) +applyRowRange :: ParquetReadOptions -> DataFrame -> DataFrame+applyRowRange opts df =+    maybe df (`DS.range` df) (rowRange opts)++applySelectedColumns :: ParquetReadOptions -> DataFrame -> DataFrame+applySelectedColumns opts df =+    maybe df (`DS.select` df) (selectedColumns opts)++applyPredicate :: ParquetReadOptions -> DataFrame -> DataFrame+applyPredicate opts df =+    maybe df (`DS.filterWhere` df) (predicate opts)++applyReadOptions :: ParquetReadOptions -> DataFrame -> DataFrame+applyReadOptions opts =+    applyRowRange opts+        . applySelectedColumns opts+        . applyPredicate opts+ readMetadataFromPath :: FilePath -> IO FileMetadata readMetadataFromPath path = do     contents <- BSO.readFile path@@ -310,7 +383,7 @@                 L.foldl' (\l r -> fromRight (error "concat failed") (DI.concatColumns l r)) c cs  applyLogicalType :: LogicalType -> DI.Column -> DI.Column-applyLogicalType (TimestampType isUTC unit) col =+applyLogicalType (TimestampType _ unit) col =     fromRight col $         DI.mapColumn             (microsecondsToUTCTime . (* (1_000_000 `div` unitDivisor unit)))
tests/Parquet.hs view
@@ -3,6 +3,7 @@  module Parquet where +import Assertions (assertExpectException) import qualified DataFrame as D import qualified DataFrame.Functions as F @@ -186,6 +187,117 @@             (unsafePerformIO (D.readParquet "./tests/data/alltypes_dictionary.parquet"))         ) +selectedColumnsWithOpts :: Test+selectedColumnsWithOpts =+    TestCase+        ( assertEqual+            "selectedColumnsWithOpts"+            (D.select ["id", "bool_col"] allTypes)+            ( unsafePerformIO+                ( D.readParquetWithOpts+                    (D.defaultParquetReadOptions{D.selectedColumns = Just ["id", "bool_col"]})+                    "./tests/data/alltypes_plain.parquet"+                )+            )+        )++rowRangeWithOpts :: Test+rowRangeWithOpts =+    TestCase+        ( assertEqual+            "rowRangeWithOpts"+            (3, 11)+            ( unsafePerformIO+                ( D.dimensions+                    <$> D.readParquetWithOpts+                        (D.defaultParquetReadOptions{D.rowRange = Just (2, 5)})+                        "./tests/data/alltypes_plain.parquet"+                )+            )+        )++predicateWithOpts :: Test+predicateWithOpts =+    TestCase+        ( assertEqual+            "predicateWithOpts"+            (D.fromNamedColumns [("id", D.fromList [6 :: Int32, 7])])+            ( unsafePerformIO+                ( D.readParquetWithOpts+                    ( D.defaultParquetReadOptions+                        { D.selectedColumns = Just ["id"]+                        , D.predicate =+                            Just+                                ( F.geq+                                    (F.col @Int32 "id")+                                    (F.lit (6 :: Int32))+                                )+                        }+                    )+                    "./tests/data/alltypes_plain.parquet"+                )+            )+        )++predicateUsesNonSelectedColumnWithOpts :: Test+predicateUsesNonSelectedColumnWithOpts =+    TestCase+        ( assertEqual+            "predicateUsesNonSelectedColumnWithOpts"+            (D.fromNamedColumns [("bool_col", D.fromList [True, False])])+            ( unsafePerformIO+                ( D.readParquetWithOpts+                    ( D.defaultParquetReadOptions+                        { D.selectedColumns = Just ["bool_col"]+                        , D.predicate =+                            Just+                                ( F.geq+                                    (F.col @Int32 "id")+                                    (F.lit (6 :: Int32))+                                )+                        }+                    )+                    "./tests/data/alltypes_plain.parquet"+                )+            )+        )++predicateWithOptsAcrossFiles :: Test+predicateWithOptsAcrossFiles =+    TestCase+        ( assertEqual+            "predicateWithOptsAcrossFiles"+            (4, 1)+            ( unsafePerformIO+                ( D.dimensions+                    <$> D.readParquetFilesWithOpts+                        ( D.defaultParquetReadOptions+                            { D.selectedColumns = Just ["id"]+                            , D.predicate =+                                Just+                                    ( F.geq+                                        (F.col @Int32 "id")+                                        (F.lit (6 :: Int32))+                                    )+                            }+                        )+                        "./tests/data/alltypes_plain*.parquet"+                )+            )+        )++missingSelectedColumnWithOpts :: Test+missingSelectedColumnWithOpts =+    TestCase+        ( assertExpectException+            "missingSelectedColumnWithOpts"+            "Column not found"+            ( D.readParquetWithOpts+                (D.defaultParquetReadOptions{D.selectedColumns = Just ["does_not_exist"]})+                "./tests/data/alltypes_plain.parquet"+            )+        )+ transactions :: D.DataFrame transactions =     D.fromNamedColumns@@ -819,6 +931,12 @@     [ allTypesPlain     , allTypesPlainSnappy     , allTypesDictionary+    , selectedColumnsWithOpts+    , rowRangeWithOpts+    , predicateWithOpts+    , predicateUsesNonSelectedColumnWithOpts+    , predicateWithOptsAcrossFiles+    , missingSelectedColumnWithOpts     , mtCars     , allTypesTinyPagesLastFew     , allTypesTinyPagesDimensions