├── Setup.hs ├── jar └── COPY_DERBY_JAR_HERE ├── README.md ├── java └── DB.java ├── eta-jdbc.cabal └── src ├── Main.hs └── Jdbc.hs /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /jar/COPY_DERBY_JAR_HERE: -------------------------------------------------------------------------------- 1 | Copy derby.jar from Apache Derby to this folder, before running. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eta JDBC example 2 | 3 | Simple example of how to use JDBC via FFI form [Eta](http://eta-lang.org). 4 | 5 | To use, download Apache Derby and copy derby.jar to jar/ folder before running and change the path 6 | to the toursdb. 7 | -------------------------------------------------------------------------------- /java/DB.java: -------------------------------------------------------------------------------- 1 | import java.sql.*; 2 | 3 | public class DB { 4 | 5 | public static Connection connect(String url, String user, String pass) { 6 | try { 7 | DriverManager.registerDriver((Driver)Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()); 8 | return DriverManager.getConnection(url, user, pass); 9 | } catch(Throwable t) { 10 | throw new RuntimeException(t); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /eta-jdbc.cabal: -------------------------------------------------------------------------------- 1 | name: eta-jdbc 2 | version: 0.1.0.0 3 | -- synopsis: 4 | description: Simple example of how to use JDBC from Eta language 5 | -- license: 6 | license-file: LICENSE 7 | author: Tatu Tarvainen 8 | -- maintainer: 9 | -- copyright: 10 | -- category: 11 | build-type: Simple 12 | -- extra-source-files: 13 | cabal-version: >=1.10 14 | 15 | executable eta-jdbc 16 | main-is: Main.hs 17 | other-modules: Jdbc 18 | -- other-extensions: 19 | build-depends: base >=4.8 && <4.9 20 | hs-source-dirs: src 21 | default-language: Haskell2010 22 | java-sources: jar/derby.jar java/DB.java -------------------------------------------------------------------------------- /src/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module Main where 3 | 4 | import Java 5 | import Jdbc 6 | 7 | -- Connect to Apache Derby embedded by path 8 | connectDB :: String -> IO Jdbc.Connection 9 | connectDB path = Jdbc.connect ("jdbc:derby:" ++ path) "" "" 10 | 11 | -- Define a record type that holds our result row 12 | data CitiesInCountry = CitiesInCountry { country :: String, numberOfCities :: Int} deriving Show 13 | 14 | -- Define how this result is read from a JDBC ResultSet 15 | instance ResultRow CitiesInCountry where 16 | parseResultRow rs = do 17 | country <- getString rs "country" 18 | cities <- getInt rs "cities" 19 | return $ CitiesInCountry { country = country, numberOfCities = cities } 20 | 21 | -- Print out a country and its city count 22 | showCountry (CitiesInCountry { country = c, numberOfCities = n }) = do 23 | putStrLn $ "Country " ++ c ++ " has " ++ (show n) ++ " cities." 24 | 25 | 26 | main = do 27 | con <- connectDB "/Users/tatuta/Downloads/db-derby-10.13.1.1-bin/demo/databases/toursdb" 28 | countries <- query con 29 | "SELECT COUNT(*) as cities, country FROM cities WHERE country IN (SELECT country FROM countries WHERE region = ?) GROUP BY country" 30 | [stringParam "Asia"] 31 | mapM_ showCountry countries 32 | let sum = foldl (+) 0 (map (\(CitiesInCountry {numberOfCities = num }) -> num) countries) 33 | putStrLn $ "Total cities in list: " ++ (show sum) 34 | -------------------------------------------------------------------------------- /src/Jdbc.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE MagicHash, TypeFamilies, DataKinds #-} 2 | module Jdbc where 3 | 4 | import Java 5 | 6 | -- Define the Java classes we need from java.sql package: 7 | -- Connection, PreparedStatement and ResultSet 8 | 9 | data {-# CLASS "java.sql.Connection" #-} Connection = Connection (Object# Connection) 10 | deriving (Class, Show, Eq) 11 | 12 | data {-# CLASS "java.sql.PreparedStatement" #-} PreparedStatement = PreparedStatement (Object# PreparedStatement) 13 | deriving (Class, Show) 14 | 15 | data {-# CLASS "java.sql.ResultSet" #-} ResultSet = ResultSet (Object# ResultSet) 16 | deriving (Class, Show) 17 | 18 | -- Import Java methods we need 19 | 20 | foreign import java unsafe "@static DB.connect" 21 | connect :: String -> String -> String -> IO Connection 22 | 23 | foreign import java unsafe "@interface prepareStatement" 24 | prepareStatement :: Connection -> String -> IO PreparedStatement 25 | 26 | foreign import java unsafe "@interface setInt" 27 | setIntParameter :: PreparedStatement -> Int -> Int -> IO () 28 | foreign import java unsafe "@interface setString" 29 | setStringParameter :: PreparedStatement -> Int -> String -> IO () 30 | foreign import java unsafe "@interface setBoolean" 31 | setBoolParameter :: PreparedStatement -> Int -> Bool -> IO () 32 | 33 | foreign import java unsafe "@interface executeQuery" 34 | executeQuery :: PreparedStatement -> IO ResultSet 35 | 36 | foreign import java unsafe "@interface next" 37 | next :: ResultSet -> IO Bool 38 | 39 | foreign import java unsafe "@interface getInt" 40 | getInt :: ResultSet -> String -> IO Int 41 | 42 | foreign import java unsafe "@interface getString" 43 | getString :: ResultSet -> String -> IO String 44 | 45 | foreign import java unsafe "@interface close" 46 | closeResultSet :: ResultSet -> IO () 47 | foreign import java unsafe "@interface close" 48 | closePreparedStatement :: PreparedStatement -> IO () 49 | 50 | -- Define different types of query parameters and helpers 51 | data QueryParameter = StringQueryParameter String | IntQueryParameter Int | BoolQueryParameter Bool 52 | 53 | intParam = IntQueryParameter 54 | stringParam = StringQueryParameter 55 | boolParam = BoolQueryParameter 56 | 57 | 58 | setPreparedStatementParams :: PreparedStatement -> Int -> [QueryParameter] -> IO PreparedStatement 59 | setPreparedStatementParams ps i [] = do return ps 60 | setPreparedStatementParams ps i (param:params) = do 61 | case param of 62 | (IntQueryParameter p) -> setIntParameter ps i p 63 | (StringQueryParameter p) -> setStringParameter ps i p 64 | (BoolQueryParameter p) -> setBoolParameter ps i p 65 | setPreparedStatementParams ps (i + 1) params 66 | 67 | -- Define an interface how a row in the ResultSet is turned into a value 68 | class ResultRow a where 69 | parseResultRow :: ResultSet -> IO a 70 | 71 | -- Read the resultset into a list of values 72 | parseRows :: ResultRow a => ResultSet -> [a] -> IO [a] 73 | parseRows rs acc = do 74 | more <- next rs 75 | if more 76 | then do row <- parseResultRow rs 77 | parseRows rs (row:acc) 78 | else return (reverse acc) 79 | 80 | -- Execute a SQL query: Takes a connection, a SQL string and list of parameters 81 | -- Returns a list of result values 82 | query :: ResultRow a => Connection -> String -> [QueryParameter] -> IO [a] 83 | query c q params = do 84 | ps <- prepareStatement c q 85 | ps <- setPreparedStatementParams ps 1 params 86 | rs <- executeQuery ps 87 | result <- parseRows rs [] 88 | closeResultSet rs 89 | closePreparedStatement ps 90 | return result 91 | --------------------------------------------------------------------------------