Skip to content

Commit 25f2ce4

Browse files
authored
feat(bindings/haskell): init haskell binding (#2463)
* feat(bindings/haskell): init haskell binding Signed-off-by: silver-ymz <[email protected]> * add license Signed-off-by: silver-ymz <[email protected]> * add safety explanation Signed-off-by: silver-ymz <[email protected]> * change Cargo.toml Signed-off-by: silver-ymz <[email protected]> * change Cargo.toml Signed-off-by: silver-ymz <[email protected]> --------- Signed-off-by: silver-ymz <[email protected]>
1 parent d2abd75 commit 25f2ce4

File tree

13 files changed

+669
-0
lines changed

13 files changed

+669
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ members = [
3030
"bindings/python",
3131
"bindings/ruby",
3232
"bindings/java",
33+
"bindings/haskell",
3334

3435
"bin/oli",
3536
"bin/oay",

bindings/haskell/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist-newstyle
2+
.envrc

bindings/haskell/Cargo.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
[package]
19+
name = "opendal-hs"
20+
version = "0.1.0"
21+
22+
authors.workspace = true
23+
edition.workspace = true
24+
homepage.workspace = true
25+
license.workspace = true
26+
repository.workspace = true
27+
rust-version.workspace = true
28+
29+
[lib]
30+
crate-type = ["cdylib"]
31+
doc = false
32+
33+
[dependencies]
34+
opendal.workspace = true

bindings/haskell/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# OpenDAL Haskell Binding (WIP)
2+
3+
## Example
4+
5+
```haskell
6+
import OpenDAL
7+
import qualified Data.HashMap.Strict as HashMap
8+
9+
main :: IO ()
10+
main = do
11+
Right op <- operator "memory" HashMap.empty
12+
_ <- write op "key1" "value1"
13+
_ <- write op "key2" "value2"
14+
value1 <- read op "key1"
15+
value2 <- read op "key2"
16+
value1 @?= "value1"
17+
value2 @?= "value2"
18+
```
19+
20+
## Build
21+
22+
1. Build OpenDAL Haskell Interface
23+
24+
```bash
25+
cargo build --package opendal-hs
26+
```
27+
28+
2. Build Haskell binding
29+
30+
If you don't want to install `libopendal_hs`, you need to specify library path manually by `LIBRARY_PATH=${OPENDAL_ROOT}/target/debug`.
31+
32+
```bash
33+
LIBRARY_PATH=... cabal build
34+
```

bindings/haskell/cabal.project.local

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- Licensed to the Apache Software Foundation (ASF) under one
2+
-- or more contributor license agreements. See the NOTICE file
3+
-- distributed with this work for additional information
4+
-- regarding copyright ownership. The ASF licenses this file
5+
-- to you under the Apache License, Version 2.0 (the
6+
-- "License"); you may not use this file except in compliance
7+
-- with the License. You may obtain a copy of the License at
8+
--
9+
-- http://www.apache.org/licenses/LICENSE-2.0
10+
--
11+
-- Unless required by applicable law or agreed to in writing,
12+
-- software distributed under the License is distributed on an
13+
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
-- KIND, either express or implied. See the License for the
15+
-- specific language governing permissions and limitations
16+
-- under the License.
17+
18+
tests: True
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
-- Licensed to the Apache Software Foundation (ASF) under one
2+
-- or more contributor license agreements. See the NOTICE file
3+
-- distributed with this work for additional information
4+
-- regarding copyright ownership. The ASF licenses this file
5+
-- to you under the Apache License, Version 2.0 (the
6+
-- "License"); you may not use this file except in compliance
7+
-- with the License. You may obtain a copy of the License at
8+
--
9+
-- http://www.apache.org/licenses/LICENSE-2.0
10+
--
11+
-- Unless required by applicable law or agreed to in writing,
12+
-- software distributed under the License is distributed on an
13+
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
-- KIND, either express or implied. See the License for the
15+
-- specific language governing permissions and limitations
16+
-- under the License.
17+
18+
module OpenDAL (
19+
Operator,
20+
createOp,
21+
readOp,
22+
writeOp,
23+
) where
24+
25+
import Data.ByteString (ByteString)
26+
import qualified Data.ByteString as BS
27+
import Data.HashMap.Strict (HashMap)
28+
import qualified Data.HashMap.Strict as HashMap
29+
import Foreign
30+
import Foreign.C.String
31+
import OpenDAL.FFI
32+
33+
newtype Operator = Operator (Ptr RawOperator)
34+
35+
byteSliceToByteString :: ByteSlice -> IO ByteString
36+
byteSliceToByteString (ByteSlice bsDataPtr len) = BS.packCStringLen (bsDataPtr, fromIntegral len)
37+
38+
-- | Create a new Operator.
39+
createOp :: String -> HashMap String String -> IO (Either String Operator)
40+
createOp scheme hashMap = do
41+
let keysAndValues = HashMap.toList hashMap
42+
withCString scheme $ \cScheme ->
43+
withMany withCString (map fst keysAndValues) $ \cKeys ->
44+
withMany withCString (map snd keysAndValues) $ \cValues ->
45+
allocaArray (length keysAndValues) $ \cKeysPtr ->
46+
allocaArray (length keysAndValues) $ \cValuesPtr ->
47+
alloca $ \ffiResultPtr -> do
48+
pokeArray cKeysPtr cKeys
49+
pokeArray cValuesPtr cValues
50+
c_via_map_ffi cScheme cKeysPtr cValuesPtr (fromIntegral $ length keysAndValues) ffiResultPtr
51+
ffiResult <- peek ffiResultPtr
52+
if success ffiResult
53+
then do
54+
let op = Operator (castPtr $ dataPtr ffiResult)
55+
return $ Right op
56+
else do
57+
errMsg <- peekCString (errorMessage ffiResult)
58+
return $ Left errMsg
59+
60+
readOp :: Operator -> String -> IO (Either String ByteString)
61+
readOp (Operator op) path = (flip ($)) op $ \opptr ->
62+
withCString path $ \cPath ->
63+
alloca $ \ffiResultPtr -> do
64+
c_blocking_read opptr cPath ffiResultPtr
65+
ffiResult <- peek ffiResultPtr
66+
if success ffiResult
67+
then do
68+
byteslice <- peek (castPtr $ dataPtr ffiResult)
69+
byte <- byteSliceToByteString byteslice
70+
c_free_byteslice (bsData byteslice) (bsLen byteslice)
71+
return $ Right byte
72+
else do
73+
errMsg <- peekCString (errorMessage ffiResult)
74+
return $ Left errMsg
75+
76+
writeOp :: Operator -> String -> ByteString -> IO (Either String ())
77+
writeOp (Operator op) path byte = (flip ($)) op $ \opptr ->
78+
withCString path $ \cPath ->
79+
BS.useAsCStringLen byte $ \(cByte, len) ->
80+
alloca $ \ffiResultPtr -> do
81+
c_blocking_write opptr cPath cByte (fromIntegral len) ffiResultPtr
82+
ffiResult <- peek ffiResultPtr
83+
if success ffiResult
84+
then return $ Right ()
85+
else do
86+
errMsg <- peekCString (errorMessage ffiResult)
87+
return $ Left errMsg
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
-- Licensed to the Apache Software Foundation (ASF) under one
2+
-- or more contributor license agreements. See the NOTICE file
3+
-- distributed with this work for additional information
4+
-- regarding copyright ownership. The ASF licenses this file
5+
-- to you under the Apache License, Version 2.0 (the
6+
-- "License"); you may not use this file except in compliance
7+
-- with the License. You may obtain a copy of the License at
8+
--
9+
-- http://www.apache.org/licenses/LICENSE-2.0
10+
--
11+
-- Unless required by applicable law or agreed to in writing,
12+
-- software distributed under the License is distributed on an
13+
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
-- KIND, either express or implied. See the License for the
15+
-- specific language governing permissions and limitations
16+
-- under the License.
17+
{-# LANGUAGE ForeignFunctionInterface #-}
18+
19+
module OpenDAL.FFI where
20+
21+
import Foreign
22+
import Foreign.C.String
23+
import Foreign.C.Types
24+
25+
data RawOperator
26+
27+
data FFIResult = FFIResult
28+
{ success :: Bool
29+
, dataPtr :: Ptr ()
30+
, errorMessage :: CString
31+
}
32+
deriving (Show)
33+
34+
instance Storable FFIResult where
35+
sizeOf _ = sizeOf (undefined :: CSize) + sizeOf (undefined :: Ptr ()) + sizeOf (undefined :: CString)
36+
alignment _ = alignment (undefined :: CIntPtr)
37+
peek ptr = do
38+
s <- ((/= (0 :: CSize)) <$> peekByteOff ptr successOffset)
39+
d <- peekByteOff ptr dataPtrOffset
40+
errMsg <- peekByteOff ptr errorMessageOffset
41+
return $ FFIResult s d errMsg
42+
where
43+
successOffset = 0
44+
dataPtrOffset = sizeOf (undefined :: CSize)
45+
errorMessageOffset = dataPtrOffset + sizeOf (undefined :: Ptr ())
46+
poke ptr (FFIResult s d errMsg) = do
47+
pokeByteOff ptr successOffset (fromBool s :: CSize)
48+
pokeByteOff ptr dataPtrOffset d
49+
pokeByteOff ptr errorMessageOffset errMsg
50+
where
51+
successOffset = 0
52+
dataPtrOffset = sizeOf (undefined :: CSize)
53+
errorMessageOffset = dataPtrOffset + sizeOf (undefined :: Ptr ())
54+
55+
data ByteSlice = ByteSlice
56+
{ bsData :: Ptr CChar
57+
, bsLen :: CSize
58+
}
59+
60+
instance Storable ByteSlice where
61+
sizeOf _ = sizeOf (undefined :: Ptr CChar) + sizeOf (undefined :: CSize)
62+
alignment _ = alignment (undefined :: Ptr CChar)
63+
peek ptr = do
64+
bsDataPtr <- peekByteOff ptr dataOffset
65+
len <- peekByteOff ptr lenOffset
66+
return $ ByteSlice bsDataPtr len
67+
where
68+
dataOffset = 0
69+
lenOffset = sizeOf (undefined :: Ptr ())
70+
poke ptr (ByteSlice bsDataPtr len) = do
71+
pokeByteOff ptr dataOffset bsDataPtr
72+
pokeByteOff ptr lenOffset len
73+
where
74+
dataOffset = 0
75+
lenOffset = sizeOf (undefined :: Ptr ())
76+
77+
foreign import ccall "via_map_ffi"
78+
c_via_map_ffi ::
79+
CString -> Ptr CString -> Ptr CString -> CSize -> Ptr FFIResult -> IO ()
80+
foreign import ccall "&free_operator" c_free_operator :: FunPtr (Ptr RawOperator -> IO ())
81+
foreign import ccall "free_byteslice" c_free_byteslice :: Ptr CChar -> CSize -> IO ()
82+
foreign import ccall "blocking_read" c_blocking_read :: Ptr RawOperator -> CString -> Ptr FFIResult -> IO ()
83+
foreign import ccall "blocking_write" c_blocking_write :: Ptr RawOperator -> CString -> Ptr CChar -> CSize -> Ptr FFIResult -> IO ()

bindings/haskell/opendal-hs.cabal

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
cabal-version: 2.0
2+
3+
-- Licensed to the Apache Software Foundation (ASF) under one
4+
-- or more contributor license agreements. See the NOTICE file
5+
-- distributed with this work for additional information
6+
-- regarding copyright ownership. The ASF licenses this file
7+
-- to you under the Apache License, Version 2.0 (the
8+
-- "License"); you may not use this file except in compliance
9+
-- with the License. You may obtain a copy of the License at
10+
--
11+
-- http://www.apache.org/licenses/LICENSE-2.0
12+
--
13+
-- Unless required by applicable law or agreed to in writing,
14+
-- software distributed under the License is distributed on an
15+
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
-- KIND, either express or implied. See the License for the
17+
-- specific language governing permissions and limitations
18+
-- under the License.
19+
20+
name: opendal-hs
21+
version: 0.1.0.0
22+
license: Apache-2.0
23+
synopsis: OpenDAL Haskell Binding
24+
description:
25+
OpenDAL Haskell Binding. Open Data Access Layer: Access data freely, painlessly, and efficiently
26+
27+
category: Storage, Binding
28+
build-type: Simple
29+
30+
source-repository head
31+
type: git
32+
location: https://github.com/apache/incubator-opendal
33+
34+
library
35+
exposed-modules:
36+
OpenDAL
37+
other-modules:
38+
OpenDAL.FFI
39+
hs-source-dirs: haskell-src
40+
default-language: Haskell2010
41+
extra-libraries: opendal_hs
42+
ghc-options: -Wall
43+
build-depends:
44+
base >=4.10.0.0 && <5,
45+
unordered-containers >=0.2.0.0,
46+
bytestring >=0.11.0.0
47+
48+
test-suite opendal-hs-test
49+
type: exitcode-stdio-1.0
50+
main-is: Spec.hs
51+
other-modules: BasicTest
52+
hs-source-dirs: test
53+
default-language: Haskell2010
54+
other-extensions: OverloadedStrings
55+
ghc-options: -Wall
56+
build-depends:
57+
base,
58+
unordered-containers,
59+
opendal-hs,
60+
tasty,
61+
tasty-hunit

0 commit comments

Comments
 (0)