sparkle-0.7.3: src/Control/Distributed/Spark/SQL/Dataset.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StaticPointers #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fplugin=Language.Java.Inline.Plugin #-}
module Control.Distributed.Spark.SQL.Dataset where
import Control.Distributed.Closure
import Control.Distributed.Spark.Closure
import Control.Distributed.Spark.RDD (RDD)
import Control.Distributed.Spark.SQL.Column
import Control.Distributed.Spark.SQL.Encoder
import Control.Distributed.Spark.SQL.Row
import Control.Distributed.Spark.SQL.StructType
import Control.Distributed.Spark.SQL.SparkSession
import Data.Int
import Data.Text (Text)
import Data.Typeable
import Foreign.JNI
import Language.Java
import Language.Java.Inline (java)
import Prelude hiding (filter)
import Streaming (Stream, Of, effect)
import qualified Streaming.Prelude as S (filter, fold_, map, yield)
import System.IO.Unsafe (unsafeDupablePerformIO, unsafePerformIO)
newtype Dataset a = Dataset (J ('Class "org.apache.spark.sql.Dataset"))
deriving Coercible
type DataFrame = Dataset Row
javaRDD :: Dataset a -> IO (RDD a)
javaRDD df = [java| $df.javaRDD() |]
createDataset :: SparkSession -> Encoder a -> RDD a -> IO (Dataset a)
createDataset ss enc rdd = [java| $ss.createDataset($rdd.rdd(), $enc) |]
getEncoder :: forall a. Dataset a -> IO (Encoder a)
getEncoder ds = do
let klass = unsafeDupablePerformIO $ withLocalRef
(findClass $
referenceTypeName (SClass "org.apache.spark.sql.Dataset"))
newGlobalRef
fID = unsafePerformIO $
getFieldID klass
"org$apache$spark$sql$Dataset$$encoder"
(signature (sing :: Sing (Interp (Encoder a))))
(Encoder
. (unsafeCast :: JObject -> J ('Iface "org.apache.spark.sql.Encoder"))
)
<$> getObjectField ds fID
as :: Encoder b -> Dataset a -> IO (Dataset b)
as enc ds = [java| $ds.as($enc) |]
createDataFrame :: SparkSession -> RDD Row -> StructType -> IO DataFrame
createDataFrame ss rdd st = [java| $ss.createDataFrame($rdd, $st) |]
sparkSession :: Dataset a -> IO SparkSession
sparkSession ds = [java| $ds.sparkSession() |]
cache :: Dataset a -> IO (Dataset a)
cache ds = [java| $ds.cache() |]
unpersist :: Dataset a -> IO (Dataset a)
unpersist ds = [java| $ds.unpersist(false) |]
withColumnRenamed :: Text -> Text -> Dataset a -> IO (Dataset Row)
withColumnRenamed old newt df = do
jold <- reflect old
jnew <- reflect newt
[java| $df.withColumnRenamed($jold, $jnew) |]
toDF :: [Text] -> Dataset a -> IO (Dataset Row)
toDF cols df = do
jcols <- reflect cols
[java| $df.toDF($jcols) |]
selectDS :: Dataset a -> [Text] -> IO (Dataset Row)
selectDS _ [] = error "selectDS: not enough arguments."
selectDS df (c:cols) = do
jcol <- reflect c
jcols <- reflect cols
[java| $df.select($jcol, $jcols) |]
limit :: Int32 -> Dataset a -> IO (Dataset a)
limit n df = [java| $df.limit($n) |]
show :: Dataset a -> IO ()
show df = [java| { $df.show(); } |]
range
:: Int64
-> Int64
-> Int64
-> Int32
-> SparkSession
-> IO (Dataset Int64)
range start end step partitions ss =
[java| $ss.range($start, $end, $step, $partitions) |]
union :: Dataset a -> Dataset a -> IO (Dataset a)
union ds1 ds2 = [java| $ds1.union($ds2) |]
join :: Dataset a -> Dataset b -> IO (Dataset Row)
join d1 d2 = [java| $d1.join($d2) |]
joinOn :: Dataset a -> Dataset b -> Column -> IO DataFrame
joinOn d1 d2 colexpr = [java| $d1.join($d2, $colexpr) |]
crossJoin :: Dataset a -> Dataset b -> IO (Dataset Row)
crossJoin d1 d2 = [java| $d1.crossJoin($d2) |]
sample :: Bool -> Double -> Dataset a -> IO (Dataset Row)
sample withReplacement fraction d1 =
[java| $d1.sample($withReplacement, $fraction) |]
dropDuplicates :: [Text] -> Dataset a -> IO (Dataset Row)
dropDuplicates cols ds = do
jCols <- reflect cols
[java| $ds.dropDuplicates($jCols) |]
orderBy :: [Column] -> Dataset a -> IO (Dataset Row)
orderBy cols ds = do
jCols <- reflect cols
[java| $ds.orderBy($jCols) |]
except :: Dataset a -> Dataset a -> IO (Dataset Row)
except ds1 ds2 = [java| $ds1.except($ds2) |]
intersect :: Dataset a -> Dataset a -> IO (Dataset Row)
intersect ds1 ds2 = [java| $ds1.intersect($ds2) |]
columns :: Dataset a -> IO [Text]
columns df = [java| $df.columns() |] >>= reify
printSchema :: Dataset a -> IO ()
printSchema df = [java| { $df.printSchema(); } |]
distinct :: Dataset a -> IO (Dataset a)
distinct d = [java| $d.distinct() |]
repartition :: Int32 -> Dataset a -> IO (Dataset a)
repartition nbPart d = [java| $d.repartition($nbPart) |]
coalesce :: Int32 -> Dataset a -> IO (Dataset a)
coalesce nbPart ds = [java| $ds.coalesce($nbPart) |]
collectAsList :: forall a. Reify a => Dataset a -> IO [a]
collectAsList d =
[java| $d.collectAsList().toArray() |] >>= reify . jcast
where
jcast :: JObjectArray -> J ('Array (Interp a))
jcast = unsafeCast
newtype DataFrameReader = DataFrameReader (J ('Class "org.apache.spark.sql.DataFrameReader"))
deriving Coercible
newtype DataFrameWriter = DataFrameWriter (J ('Class "org.apache.spark.sql.DataFrameWriter"))
deriving Coercible
read :: SparkSession -> IO DataFrameReader
read ss = [java| $ss.read() |]
write :: Dataset a -> IO DataFrameWriter
write df = call df "write" []
readParquet :: [Text] -> DataFrameReader -> IO DataFrame
readParquet fps dfr = do
jfps <- reflect fps
call dfr "parquet" [coerce jfps]
writeParquet :: Text -> DataFrameWriter -> IO ()
writeParquet fp dfw = do
jfp <- reflect fp
call dfw "parquet" [coerce jfp]
formatReader :: Text -> DataFrameReader -> IO DataFrameReader
formatReader source dfr = do
jsource <- reflect source
[java| $dfr.format($jsource) |]
formatWriter :: Text -> DataFrameWriter -> IO DataFrameWriter
formatWriter source dfw = do
jsource <- reflect source
[java| $dfw.format($jsource) |]
optionReader :: Text -> Text -> DataFrameReader -> IO DataFrameReader
optionReader key value dfr = do
jkey <- reflect key
jv <- reflect value
[java| $dfr.option($jkey, $jv) |]
optionWriter
:: Text -> Text -> DataFrameWriter -> IO DataFrameWriter
optionWriter key value dfw = do
jkey <- reflect key
jv <- reflect value
[java| $dfw.option($jkey, $jv) |]
load :: Text -> DataFrameReader -> IO DataFrame
load path dfr = do
jpath <- reflect path
[java| $dfr.load($jpath) |]
save :: Text -> DataFrameWriter -> IO ()
save path dfw = do
jpath <- reflect path
[java| { $dfw.save($jpath); } |]
schema :: Dataset a -> IO StructType
schema df = [java| $df.schema() |]
select :: Dataset a -> [Column] -> IO DataFrame
select d1 colexprs = do
jCols <- reflect colexprs
[java| $d1.select($jCols) |]
whereDS :: Dataset a -> Column -> IO (Dataset a)
whereDS d1 colexpr = [java| $d1.where($colexpr) |]
count :: Dataset a -> IO Int64
count df = [java| $df.count() |]
col :: Dataset a -> Text -> IO Column
col d1 t = do
colName <- reflect t
[java| $d1.col($colName) |]
filter
:: ( Reflect (MapPartitionsFunction a a)
, Typeable a
)
=> Closure (a -> Bool)
-> Dataset a
-> IO (Dataset a)
filter clos ds = do
enc <- getEncoder ds
mapPartitions clos' enc ds
where clos' = closure (static S.filter) `cap` clos
filterByCol :: Column -> Dataset a -> IO (Dataset a)
filterByCol c ds = [java| $ds.filter($c) |]
map :: forall a b.
( Reflect (MapPartitionsFunction a b)
, Typeable b
, Typeable a
)
=> Closure (a -> b)
-> Encoder b
-> Dataset a
-> IO (Dataset b)
map clos enc ds = mapPartitions clos' enc ds
where
clos' :: Closure (Stream (Of a) IO () -> Stream (Of b) IO ())
clos' = closure (static S.map) `cap` clos
aggregate
:: ( Reflect (MapPartitionsFunction a b)
, Reflect (ReduceFunction b)
, Reify b
, Static (Serializable b)
, Typeable a
)
=> Closure (b -> a -> b)
-> Closure (b -> b -> b)
-> b
-> Dataset a
-> IO b
aggregate seqOp combOp zero =
aggregatePartitions seqOp' combOp
where
seqOp' = (closure $ static (\f z -> S.fold_ f z id))
`cap` seqOp
`cap` cpure closureDict zero
-- | Like 'aggregate', but exposing the underlying iterator
aggregatePartitions
:: forall a b.
( Reflect (MapPartitionsFunction a b)
, Reflect (ReduceFunction b)
, Reify b
, Typeable a
, Typeable b
)
=> Closure (Stream (Of a) IO () -> IO b)
-> Closure (b -> b -> b)
-> Dataset a -> IO b
aggregatePartitions seqOp combOp ds = do
enc <- kryo
reducePartitions seqOp enc ds
>>= slowReduce combOp
mapPartitions
:: Reflect (MapPartitionsFunction a b)
=> Closure (Stream (Of a) IO () -> Stream (Of b) IO ())
-> Encoder b
-> Dataset a
-> IO (Dataset b)
mapPartitions clos enc ds = do
f <- unsafeUngeneric <$> reflect (MapPartitionsFunction clos)
[java| $ds.mapPartitions($f, $enc) |]
-- | Like 'mapPartitions', but for a function that produces only a single
-- element partition
reducePartitions
:: ( Reflect (MapPartitionsFunction a b)
, Typeable a
, Typeable b
)
=> Closure (Stream (Of a) IO () -> IO b)
-> Encoder b
-> Dataset a
-> IO (Dataset b)
reducePartitions fun =
mapPartitions
(closure (static (\f it -> effect $ S.yield <$> f it)) `cap` fun)
reduce
:: forall a. (Static (Reify a), Static (Reflect a), Typeable a)
=> Closure (a -> a -> a)
-> Dataset a
-> IO a
reduce clos ds = do
f <- unsafeUngeneric <$> reflect (ReduceFunction clos)
[java| $ds.reduce($f) |]
>>= reify . jcast
where
jcast :: JObject -> J (Interp a)
jcast = unsafeCast
slowReduce
:: forall a. (Reflect (ReduceFunction a), Reify a)
=> Closure (a -> a -> a)
-> Dataset a
-> IO a
slowReduce clos ds = do
f <- unsafeUngeneric <$> reflect (ReduceFunction clos)
[java| $ds.reduce($f) |]
>>= reify . jcast
where
jcast :: JObject -> J (Interp a)
jcast = unsafeCast
groupBy :: forall a . Dataset a -> [Column] -> IO GroupedData
groupBy d1 colexprs = do
cols <- reflect colexprs
[java| $d1.groupBy($cols) |]
agg :: GroupedData -> [Column] -> IO (Dataset Row)
agg _ [] = error "agg: not enough arguments."
agg df (Column jcol : cols) = do
jcols <- reflect cols
[java| $df.agg($jcol, $jcols) |]