├── .gitignore ├── LICENSE ├── MODULE.md ├── README.org ├── bower.json ├── package.json ├── schema.sql ├── src └── Database │ ├── AnyDB.js │ ├── AnyDB.purs │ └── AnyDB │ ├── Pool.js │ ├── Pool.purs │ ├── SqlValue.js │ ├── SqlValue.purs │ ├── Transaction.js │ └── Transaction.purs └── test ├── Main.purs ├── Postgres.purs ├── Shared.purs └── Sqlite3.purs /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | output/ 4 | README.html 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 - 2015 Erik Post 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MODULE.md: -------------------------------------------------------------------------------- 1 | # Module Documentation 2 | 3 | ## Module Database.AnyDB 4 | 5 | #### `Query` 6 | 7 | ``` purescript 8 | newtype Query a 9 | = Query String 10 | ``` 11 | 12 | 13 | #### `Connection` 14 | 15 | ``` purescript 16 | data Connection :: * 17 | ``` 18 | 19 | 20 | #### `DB` 21 | 22 | ``` purescript 23 | data DB :: ! 24 | ``` 25 | 26 | 27 | #### `ConnectionString` 28 | 29 | ``` purescript 30 | type ConnectionString = String 31 | ``` 32 | 33 | 34 | #### `ConnectionInfo` 35 | 36 | ``` purescript 37 | type ConnectionInfo = { password :: String, user :: String, port :: Number, db :: String, host :: String } 38 | ``` 39 | 40 | 41 | #### `mkConnectionString` 42 | 43 | ``` purescript 44 | mkConnectionString :: ConnectionInfo -> ConnectionString 45 | ``` 46 | 47 | 48 | #### `connect` 49 | 50 | ``` purescript 51 | connect :: forall eff. ConnectionInfo -> Aff (db :: DB | eff) Connection 52 | ``` 53 | 54 | Makes a connection to the database. 55 | 56 | #### `execute` 57 | 58 | ``` purescript 59 | execute :: forall eff a. Query a -> [SqlValue] -> Connection -> Aff (db :: DB | eff) Unit 60 | ``` 61 | 62 | Runs a query and returns nothing. 63 | 64 | #### `execute_` 65 | 66 | ``` purescript 67 | execute_ :: forall eff a. Query a -> Connection -> Aff (db :: DB | eff) Unit 68 | ``` 69 | 70 | Runs a query and returns nothing 71 | 72 | #### `query` 73 | 74 | ``` purescript 75 | query :: forall eff a. (IsForeign a) => Query a -> [SqlValue] -> Connection -> Aff (db :: DB | eff) [F a] 76 | ``` 77 | 78 | Runs a query and returns all results. 79 | 80 | #### `query_` 81 | 82 | ``` purescript 83 | query_ :: forall eff a. (IsForeign a) => Query a -> Connection -> Aff (db :: DB | eff) [a] 84 | ``` 85 | 86 | Just like `query` but does not make any param replacement 87 | 88 | #### `queryOne` 89 | 90 | ``` purescript 91 | queryOne :: forall eff a. (IsForeign a) => Query a -> [SqlValue] -> Connection -> Aff (db :: DB | eff) (Maybe a) 92 | ``` 93 | 94 | Runs a query and returns the first row, if any 95 | 96 | #### `queryOne_` 97 | 98 | ``` purescript 99 | queryOne_ :: forall eff a. (IsForeign a) => Query a -> Connection -> Aff (db :: DB | eff) (Maybe a) 100 | ``` 101 | 102 | Just like `queryOne` but does not make any param replacement 103 | 104 | #### `queryValue` 105 | 106 | ``` purescript 107 | queryValue :: forall eff a. (IsForeign a) => Query a -> [SqlValue] -> Connection -> Aff (db :: DB | eff) (Maybe a) 108 | ``` 109 | 110 | Runs a query and returns a single value, if any. 111 | 112 | #### `queryValue_` 113 | 114 | ``` purescript 115 | queryValue_ :: forall eff a. (IsForeign a) => Query a -> Connection -> Aff (db :: DB | eff) (Maybe a) 116 | ``` 117 | 118 | Just like `queryValue` but does not make any param replacement 119 | 120 | #### `withConnection` 121 | 122 | ``` purescript 123 | withConnection :: forall eff a. ConnectionInfo -> (Connection -> Aff (db :: DB | eff) a) -> Aff (db :: DB | eff) a 124 | ``` 125 | 126 | Connects to the database, calls the provided function with the connection 127 | and returns the results. 128 | 129 | #### `close` 130 | 131 | ``` purescript 132 | close :: forall eff. Connection -> Eff (db :: DB | eff) Unit 133 | ``` 134 | 135 | 136 | 137 | ## Module Database.AnyDB.Pool 138 | 139 | 140 | #### `Pool` 141 | 142 | ``` purescript 143 | data Pool :: * 144 | ``` 145 | 146 | 147 | #### `createPool` 148 | 149 | ``` purescript 150 | createPool :: forall eff. ConnectionInfo -> Eff (db :: DB | eff) Pool 151 | ``` 152 | 153 | Create a connection pool. 154 | 155 | #### `createPoolFromString` 156 | 157 | ``` purescript 158 | createPoolFromString :: forall eff. ConnectionString -> Eff (db :: DB | eff) Pool 159 | ``` 160 | 161 | Create a connection pool. Remember to call `closePool`. 162 | 163 | #### `closePool` 164 | 165 | ``` purescript 166 | closePool :: forall eff. Pool -> Eff (db :: DB | eff) Unit 167 | ``` 168 | 169 | Close the connection pool. 170 | 171 | #### `withPool` 172 | 173 | ``` purescript 174 | withPool :: forall eff a. Pool -> (Connection -> Aff (db :: DB | eff) a) -> Aff (db :: DB | eff) a 175 | ``` 176 | 177 | Run a database action with a connection from the specified `Pool`. 178 | 179 | 180 | ## Module Database.AnyDB.SqlValue 181 | 182 | #### `SqlValue` 183 | 184 | ``` purescript 185 | data SqlValue :: * 186 | ``` 187 | 188 | 189 | #### `IsSqlValue` 190 | 191 | ``` purescript 192 | class IsSqlValue a where 193 | toSql :: a -> SqlValue 194 | ``` 195 | 196 | 197 | #### `isSqlValueString` 198 | 199 | ``` purescript 200 | instance isSqlValueString :: IsSqlValue String 201 | ``` 202 | 203 | 204 | #### `isSqlValueNumber` 205 | 206 | ``` purescript 207 | instance isSqlValueNumber :: IsSqlValue Number 208 | ``` 209 | 210 | 211 | #### `isSqlValueInt` 212 | 213 | ``` purescript 214 | instance isSqlValueInt :: IsSqlValue Int 215 | ``` 216 | 217 | 218 | #### `isSqlValueMaybe` 219 | 220 | ``` purescript 221 | instance isSqlValueMaybe :: (IsSqlValue a) => IsSqlValue (Maybe a) 222 | ``` 223 | 224 | 225 | 226 | ## Module Database.AnyDB.Transaction 227 | 228 | #### `Transaction` 229 | 230 | ``` purescript 231 | data Transaction :: * 232 | ``` 233 | 234 | 235 | #### `withTransaction` 236 | 237 | ``` purescript 238 | withTransaction :: forall eff a. (Connection -> Aff (db :: DB | eff) a) -> Connection -> Aff (db :: DB | eff) a 239 | ``` 240 | 241 | 242 | 243 | ## Module Database.AnyDB.Util 244 | 245 | #### `finally` 246 | 247 | ``` purescript 248 | finally :: forall e a. Aff e a -> Aff e Unit -> Aff e a 249 | ``` 250 | 251 | Compute `aff1`, followed by `aff2` regardless of whether `aff1` terminated successfully. 252 | 253 | 254 | 255 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+title: PureScript any-db bindings 2 | 3 | * Caveat 4 | 5 | This is alpha quality software. 6 | 7 | * About 8 | 9 | Bindings for [[https://github.com/grncdr/node-any-db][node-any-db]] forked from [[https://github.com/epost/purescript-node-postgres][purescript-node-postgres]], with a slightly more fine-grained API. 10 | 11 | * Installation 12 | 13 | I'll assume you have [[http://www.purescript.org/][PureScript]] and [[http://www.postgresql.org/][PostgreSQL]] installed. You'll also need [[https://github.com/bodil/pulp][purescript-pulp]] and the postgres version of [[https://github.com/grncdr/node-any-db][node-any-db]]: 14 | 15 | #+begin_src bash 16 | npm install any-db-postgres any-db-transaction 17 | npm install pulp 18 | #+end_src 19 | 20 | Clone the project: 21 | 22 | #+begin_src bash 23 | git clone https://github.com/epost/purescript-node-postgres 24 | cd purescript-node-postgres 25 | #+end_src 26 | 27 | Create a Postgres database and run the tests: 28 | 29 | #+begin_src bash 30 | psql --command="create database test" 31 | cat schema.sql | psql --username=testuser test 32 | #+end_src 33 | 34 | * Building and running 35 | 36 | Let's run some tests: 37 | 38 | #+begin_src bash 39 | pulp test 40 | #+end_src 41 | 42 | * Usage 43 | 44 | See [[file:./MODULE.md][Module documentation]]. 45 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-any-db", 3 | "version": "0.1.0", 4 | "moduleType": [ 5 | "node" 6 | ], 7 | "authors": [ 8 | { 9 | "name": "Erik Post", 10 | "email": "erik@shinsetsu.nl", 11 | "homepage": "http://www.shinsetsu.nl" 12 | }, 13 | { 14 | "name": "Antti Holvikari" 15 | } 16 | ], 17 | "license": "MIT", 18 | "keywords": [ 19 | "purescript", 20 | "database", 21 | "sql", 22 | "postgres" 23 | ], 24 | "ignore": [ 25 | "**/.*", 26 | "node_modules", 27 | "bower_components", 28 | "output" 29 | ], 30 | "dependencies": { 31 | "purescript-aff": "^3.1.0", 32 | "purescript-arrays": "^4.1.0", 33 | "purescript-control": "^3.3.0", 34 | "purescript-datetime": "^3.2.0", 35 | "purescript-either": "^3.0.0", 36 | "purescript-foldable-traversable": "^3.3.0", 37 | "purescript-foreign": "^4.0.1", 38 | "purescript-integers": "^3.0.0", 39 | "purescript-node-buffer": "^3.0.0", 40 | "purescript-transformers": "^3.4.0", 41 | "purescript-foreign-generic": "^4.1.0" 42 | }, 43 | "devDependencies": { 44 | "purescript-console": "^3.0.0", 45 | "purescript-spec": "^1.0.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "any-db-sqlite3": "^2.1.4", 4 | "any-db": "^2.1.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | -- psql --username=testuser test 2 | 3 | CREATE TABLE artist ( 4 | name text NOT NULL, 5 | year int NOT NULL 6 | ); 7 | -------------------------------------------------------------------------------- /src/Database/AnyDB.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // module Database.AnyDB 4 | /* eslint-env node*/ 5 | /* eslint "no-underscore-dangle": 0 */ 6 | 7 | exports.connect_ = function connect_(conString) { 8 | return function(success, error) { 9 | var anyDB = require("any-db"); 10 | anyDB.createConnection(conString, function(err, con) { 11 | if (err) { 12 | error(err); 13 | } else { 14 | success(con); 15 | } 16 | }); 17 | }; 18 | }; 19 | 20 | exports.runQuery_ = function runQuery_(queryStr) { 21 | return function(con) { 22 | return function(success, error) { 23 | con.query(queryStr, function(err, result) { 24 | if (err) { 25 | error(err); 26 | } else { 27 | success(result.rows); 28 | } 29 | }); 30 | }; 31 | }; 32 | }; 33 | 34 | exports.runQuery = function runQuery(queryStr) { 35 | return function(params) { 36 | return function(con) { 37 | return function(success, error) { 38 | con.query(queryStr, params, function(err, result) { 39 | if (err) {return error(err); } 40 | success(result.rows); 41 | }); 42 | }; 43 | }; 44 | }; 45 | }; 46 | 47 | exports.runQueryValue_ = function runQueryValue_(queryStr) { 48 | return function(con) { 49 | return function(success, error) { 50 | con.query(queryStr, function(err, result) { 51 | if (err) {return error(err); } 52 | success(result.rows.length > 0 ? result.rows[0][result.fields[0].name] : undefined); 53 | }); 54 | }; 55 | }; 56 | }; 57 | 58 | exports.runQueryValue = function runQueryValue(queryStr) { 59 | return function(params) { 60 | return function(con) { 61 | return function(success, error) { 62 | con.query(queryStr, params, function(err, result) { 63 | if (err) {return error(err); } 64 | success(result.rows.length > 0 ? result.rows[0][result.fields[0].name] : undefined); 65 | }); 66 | }; 67 | }; 68 | }; 69 | }; 70 | 71 | exports.close = function close(con) { 72 | return function() { 73 | con.end(); 74 | }; 75 | }; 76 | -------------------------------------------------------------------------------- /src/Database/AnyDB.purs: -------------------------------------------------------------------------------- 1 | module Database.AnyDB 2 | ( Query(..) 3 | , Connection() 4 | , DB() 5 | , ConnectionInfo(..) 6 | , ConnectionString() 7 | , PgConnectionInfo() 8 | , Sqlite3ConnectionInfo() 9 | , mkConnectionString 10 | , connect 11 | , close 12 | , execute, execute_ 13 | , query, query_ 14 | , queryValue, queryValue_ 15 | , queryOne, queryOne_ 16 | , withConnection 17 | ) where 18 | 19 | import Prelude 20 | import Control.Monad.Eff (kind Effect, Eff) 21 | import Data.Either (either) 22 | import Data.Array ((!!)) 23 | import Data.Foreign (Foreign, ForeignError) 24 | import Data.Foreign.Class (class Decode, decode) 25 | import Data.List.Types (NonEmptyList) 26 | import Data.Maybe (Maybe(..), maybe) 27 | import Data.Newtype (unwrap) 28 | import Data.NonEmpty (head) 29 | import Control.Monad.Aff (Aff, finally) 30 | import Control.Monad.Eff.Class (liftEff) 31 | import Control.Monad.Eff.Exception (error) 32 | import Control.Monad.Error.Class (throwError) 33 | import Control.Monad.Except (runExcept) 34 | import Data.Traversable (sequence) 35 | 36 | import Database.AnyDB.SqlValue (SqlValue) 37 | 38 | newtype Query a = Query String 39 | 40 | instance eqQuery :: Eq (Query a) where 41 | eq (Query a) (Query b) = a == b 42 | instance showQuery :: Show (Query a) where 43 | show (Query n) = n 44 | 45 | foreign import data Connection :: Type 46 | 47 | foreign import data DB :: Effect 48 | 49 | type ConnectionString = String 50 | 51 | type PgConnectionInfo = 52 | { host :: String 53 | , db :: String 54 | , port :: Int 55 | , user :: String 56 | , password :: String 57 | } 58 | 59 | type Sqlite3ConnectionInfo = 60 | { filename :: String 61 | , memory :: Boolean} 62 | 63 | data ConnectionInfo = Postgres PgConnectionInfo 64 | | Sqlite3 Sqlite3ConnectionInfo 65 | 66 | mkConnectionString :: ConnectionInfo -> ConnectionString 67 | mkConnectionString (Postgres ci) = "postgres://" 68 | <> ci.user <> ":" 69 | <> ci.password <> "@" 70 | <> ci.host <> ":" 71 | <> show ci.port <> "/" 72 | <> ci.db 73 | mkConnectionString (Sqlite3 ci) = "sqlite3://" <> if (ci.memory) then ":memory:" else ci.filename 74 | 75 | -- | Makes a connection to the database. 76 | connect :: forall eff. ConnectionInfo -> Aff (db :: DB | eff) Connection 77 | connect = connect_ <<< mkConnectionString 78 | 79 | -- | Runs a query and returns nothing. 80 | execute :: forall eff a. Query a -> Array SqlValue -> Connection -> Aff (db :: DB | eff) Unit 81 | execute (Query sql) params con = void $ runQuery sql params con 82 | 83 | -- | Runs a query and returns nothing 84 | execute_ :: forall eff a. Query a -> Connection -> Aff (db :: DB | eff) Unit 85 | execute_ (Query sql) con = void $ runQuery_ sql con 86 | 87 | -- | Runs a query and returns all results. 88 | query :: forall eff a. (Decode a) => 89 | Query a -> Array SqlValue -> Connection -> Aff (db :: DB | eff) (Array a) 90 | query (Query sql) params con = do 91 | rows <- runQuery sql params con 92 | either liftError pure $ runExcept (sequence $ decode <$> rows) 93 | 94 | -- | Just like `query` but does not make any param replacement 95 | query_ :: forall eff a. (Decode a) => Query a -> Connection -> Aff (db :: DB | eff) (Array a) 96 | query_ (Query sql) con = do 97 | rows <- runQuery_ sql con 98 | either liftError pure $ runExcept (sequence $ decode <$> rows) 99 | 100 | -- | Runs a query and returns the first row, if any 101 | queryOne :: forall eff a. (Decode a) => 102 | Query a -> Array SqlValue -> Connection -> Aff (db :: DB | eff) (Maybe a) 103 | queryOne (Query sql) params con = do 104 | rows <- runQuery sql params con 105 | maybe (pure Nothing) ((either liftError (pure <<< Just)) <<< runExcept) $ decode <$> (rows !! 0) 106 | 107 | -- | Just like `queryOne` but does not make any param replacement 108 | queryOne_ :: forall eff a. (Decode a) => Query a -> Connection -> Aff (db :: DB | eff) (Maybe a) 109 | queryOne_ (Query sql) con = do 110 | rows <- runQuery_ sql con 111 | maybe (pure Nothing) ((either liftError (pure <<< Just)) <<< runExcept) $ decode <$> (rows !! 0) 112 | 113 | -- | Runs a query and returns a single value, if any. 114 | queryValue :: forall eff a. (Decode a) => 115 | Query a -> Array SqlValue -> Connection -> Aff (db :: DB | eff) (Maybe a) 116 | queryValue (Query sql) params con = do 117 | val <- runQueryValue sql params con 118 | pure $ either (const Nothing) Just $ runExcept (decode val) 119 | 120 | -- | Just like `queryValue` but does not make any param replacement 121 | queryValue_ :: forall eff a. (Decode a) => Query a -> Connection -> Aff (db :: DB | eff) (Maybe a) 122 | queryValue_ (Query sql) con = do 123 | val <- runQueryValue_ sql con 124 | either liftError (pure <<< Just) $ runExcept $ decode val 125 | 126 | -- | Connects to the database, calls the provided function with the connection 127 | -- | and returns the results. 128 | withConnection :: forall eff a. 129 | ConnectionInfo 130 | -> (Connection -> Aff (db :: DB | eff) a) 131 | -> Aff (db :: DB | eff) a 132 | withConnection info p = do 133 | con <- connect info 134 | finally (p con) $ liftEff (close con) 135 | 136 | liftError :: forall e a. NonEmptyList ForeignError -> Aff e a 137 | liftError = throwError <<< error <<< show <<< head <<< unwrap 138 | 139 | foreign import connect_ :: forall eff. ConnectionString -> Aff (db :: DB | eff) Connection 140 | 141 | foreign import runQuery_ :: forall eff. String -> Connection -> Aff (db :: DB | eff) (Array Foreign) 142 | 143 | foreign import runQuery :: forall eff. String -> Array SqlValue -> Connection -> Aff (db :: DB | eff) (Array Foreign) 144 | 145 | foreign import runQueryValue_ :: forall eff. String -> Connection -> Aff (db :: DB | eff) Foreign 146 | 147 | foreign import runQueryValue :: forall eff. String -> Array SqlValue -> Connection -> Aff (db :: DB | eff) Foreign 148 | 149 | foreign import close :: forall eff. Connection -> Eff (db :: DB | eff) Unit 150 | -------------------------------------------------------------------------------- /src/Database/AnyDB/Pool.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // module Database.AnyDB.Pool 4 | /* eslint-env node*/ 5 | /* eslint "no-underscore-dangle": 0 */ 6 | 7 | exports.createPoolFromString = function createPoolFromString(conString) { 8 | return function() { 9 | var anyDB = require("any-db"); 10 | return anyDB.createPool(conString); 11 | }; 12 | }; 13 | 14 | exports.closePool = function closePool(pool) { 15 | return function() { 16 | return pool.close(); 17 | }; 18 | }; 19 | 20 | exports.withPool = function withPool(pool) { 21 | return function(connectionToDbAction) { 22 | return function(handleDbResult, handleDbActionError) { 23 | 24 | pool._pool.acquire(function(err, con) { 25 | 26 | function release(connection) { 27 | pool._reset(connection, function(err2) { 28 | if (err2) {return pool.destroy(connection); } 29 | pool._pool.release(connection); 30 | }); 31 | } 32 | 33 | function handleDbResultThenReleaseConnection(err2, succ) { 34 | handleDbResult(err2, succ); 35 | release(con); 36 | } 37 | 38 | function handleDbActionErrorThenReleaseConnection(err2, succ) { 39 | handleDbActionError(err2, succ); 40 | release(con); 41 | } 42 | 43 | if (err) { 44 | console.log("withPool$prime: error acquiring connection: ", err); 45 | handleDbActionError(err); 46 | } else { 47 | var dbAction = connectionToDbAction(con); 48 | dbAction(handleDbResultThenReleaseConnection, 49 | handleDbActionErrorThenReleaseConnection); 50 | } 51 | }); 52 | }; 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /src/Database/AnyDB/Pool.purs: -------------------------------------------------------------------------------- 1 | -- See https://github.com/grncdr/node-any-db-pool 2 | module Database.AnyDB.Pool 3 | ( Pool() 4 | , withPool 5 | , createPool 6 | , createPoolFromString 7 | , closePool 8 | ) where 9 | 10 | import Prelude (Unit, ($)) 11 | import Control.Monad.Eff (Eff) 12 | import Control.Monad.Aff (Aff) 13 | import Database.AnyDB (Connection, ConnectionInfo, ConnectionString, DB, mkConnectionString) 14 | 15 | foreign import data Pool :: Type 16 | 17 | -- | Create a connection pool. 18 | createPool :: forall eff. ConnectionInfo -> Eff (db :: DB | eff) Pool 19 | createPool ci = createPoolFromString $ mkConnectionString ci 20 | 21 | -- | Create a connection pool. Remember to call `closePool`. 22 | foreign import createPoolFromString :: forall eff. ConnectionString -> Eff (db :: DB | eff) Pool 23 | 24 | -- | Close the connection pool. 25 | foreign import closePool :: forall eff. Pool -> Eff (db :: DB | eff) Unit 26 | 27 | -- | Run a database action with a connection from the specified `Pool`. 28 | foreign import withPool :: forall eff a. Pool 29 | -> (Connection -> Aff (db :: DB | eff) a) 30 | -> Aff (db :: DB | eff) a 31 | -------------------------------------------------------------------------------- /src/Database/AnyDB/SqlValue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // module Database.AnyDB.SqlValue 4 | /* eslint-env node*/ 5 | 6 | exports.unsafeToSqlValue = function unsafeToSqlValue(x) { 7 | return x; 8 | }; 9 | 10 | exports.nullSqlValue = null; 11 | -------------------------------------------------------------------------------- /src/Database/AnyDB/SqlValue.purs: -------------------------------------------------------------------------------- 1 | module Database.AnyDB.SqlValue 2 | ( SqlValue() 3 | , class IsSqlValue 4 | , toSql 5 | ) where 6 | 7 | import Prelude 8 | import Data.Int 9 | import Data.Maybe 10 | import Data.Date (Date()) 11 | import Node.Buffer (Buffer()) 12 | 13 | foreign import data SqlValue :: Type 14 | 15 | class IsSqlValue a where 16 | toSql :: a -> SqlValue 17 | 18 | instance isSqlValueString :: IsSqlValue String where 19 | toSql = unsafeToSqlValue 20 | 21 | instance isSqlValueNumber :: IsSqlValue Number where 22 | toSql = unsafeToSqlValue 23 | 24 | instance isSqlValueInt :: IsSqlValue Int where 25 | toSql = unsafeToSqlValue <<< toNumber 26 | 27 | instance isSqlValueBuffer :: IsSqlValue Buffer where 28 | toSql = unsafeToSqlValue 29 | 30 | instance isSqlValueMaybe :: (IsSqlValue a) => IsSqlValue (Maybe a) where 31 | toSql Nothing = nullSqlValue 32 | toSql (Just x) = toSql x 33 | 34 | instance isSqlValueDate :: IsSqlValue Date where 35 | toSql = unsafeToSqlValue 36 | 37 | foreign import unsafeToSqlValue :: forall a. a -> SqlValue 38 | 39 | foreign import nullSqlValue :: SqlValue 40 | -------------------------------------------------------------------------------- /src/Database/AnyDB/Transaction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // module Database.AnyDB.Transaction 4 | /* eslint-env node*/ 5 | 6 | exports.beginTransaction = function beginTransaction(con) { 7 | return function(success, error) { 8 | var begin = require("any-db-transaction"); 9 | begin(con, function(err, tx) { 10 | if (err) { 11 | error(err); 12 | } else { 13 | success(tx, error); 14 | } 15 | }); 16 | }; 17 | }; 18 | 19 | exports.commitTransaction = function commitTransaction(tx) { 20 | return function(success, error) { 21 | tx.commit(success, error); 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/Database/AnyDB/Transaction.purs: -------------------------------------------------------------------------------- 1 | module Database.AnyDB.Transaction 2 | ( Transaction () 3 | , withTransaction 4 | ) where 5 | 6 | import Prelude (Unit, bind, pure, discard) 7 | import Control.Monad.Aff (Aff) 8 | import Database.AnyDB (Connection, DB) 9 | 10 | foreign import data Transaction :: Type 11 | 12 | withTransaction :: forall eff a. 13 | (Connection -> Aff (db :: DB | eff) a) 14 | -> Connection 15 | -> Aff (db :: DB | eff) a 16 | withTransaction p con = do 17 | tx <- beginTransaction con 18 | res <- p con 19 | commitTransaction tx 20 | pure res 21 | 22 | foreign import beginTransaction :: forall eff. Connection -> Aff (db :: DB | eff) Transaction 23 | 24 | foreign import commitTransaction :: forall eff. Transaction -> Aff (db :: DB | eff) Unit 25 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude (Unit) 4 | import Control.Monad.Aff.AVar (AVAR) 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Console (CONSOLE) 7 | import Control.Monad.Eff.Timer (TIMER) 8 | import Node.Process (PROCESS) 9 | 10 | import Database.AnyDB (DB) 11 | 12 | import Test.Sqlite3 as SL 13 | --import Test.Postgres as PG 14 | 15 | import Test.Spec.Runner (run) 16 | import Test.Spec.Reporter.Console (consoleReporter) 17 | 18 | main :: Eff (avar :: AVAR, timer :: TIMER, console :: CONSOLE, db :: DB, process :: PROCESS) Unit 19 | main = run [consoleReporter] do 20 | SL.main 21 | --PG.main 22 | -------------------------------------------------------------------------------- /test/Postgres.purs: -------------------------------------------------------------------------------- 1 | module Test.Postgres where 2 | 3 | import Prelude 4 | import Database.AnyDB 5 | import Database.AnyDB.SqlValue 6 | import Database.AnyDB.Pool 7 | import Database.AnyDB.Transaction 8 | import Control.Monad.Eff.Console 9 | --import Debug.Trace 10 | import Control.Monad.Eff 11 | import Control.Monad.Eff.Class 12 | import Control.Monad.Cont.Trans 13 | import Data.Array 14 | import Data.Foldable 15 | import Data.Either 16 | import Data.Maybe 17 | import Data.Foreign 18 | import Data.Foreign.Class 19 | --import Data.Foreign.Index 20 | import Control.Monad.Aff 21 | import Test.Shared 22 | 23 | main = runAff (log <<< show) (const $ log "All ok") $ do 24 | liftEff <<< log $ "connecting to " <> mkConnectionString connectionInfo <> "..." 25 | exampleUsingWithConnection 26 | exampleLowLevel 27 | 28 | res <- attempt exampleError 29 | liftEff $ either (const $ log "got an error, like we should") (const $ log "FAIL") res 30 | 31 | exampleQueries 32 | exampleUsingWithPool 33 | exampleUsingWithTransaction 34 | 35 | connectionInfo = Postgres { host: "localhost" 36 | , db: "test" 37 | , port: 5432 38 | , user: "testuser" 39 | , password: "test" } 40 | 41 | exampleUsingWithConnection :: forall eff. Aff (console :: CONSOLE, db :: DB | eff) Unit 42 | exampleUsingWithConnection = withConnection connectionInfo $ \c -> do 43 | execute_ (Query "delete from artist") c 44 | execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c 45 | execute_ (Query "insert into artist values ('Deep Purple', 1968)") c 46 | year <- queryValue_ (Query "insert into artist values ('Fairport Convention', 1967) returning year" :: Query Number) c 47 | liftEff $ log (show year) 48 | artists <- query_ (Query "select * from artist" :: Query Artist) c 49 | liftEff $ printRows artists 50 | 51 | exampleLowLevel :: forall eff. Aff (console :: CONSOLE, db :: DB | eff) Unit 52 | exampleLowLevel = do 53 | con <- connect connectionInfo 54 | artists <- query_ (Query "select * from artist order by name desc" :: Query Artist) con 55 | liftEff $ printRows artists 56 | liftEff $ close con 57 | 58 | exampleError :: forall eff. Aff (db :: DB | eff) (Maybe Artist) 59 | exampleError = withConnection connectionInfo $ \c -> do 60 | execute_ (Query "delete from artist") c 61 | execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c 62 | queryOne_ (Query "select year from artist") c 63 | 64 | exampleQueries :: forall eff. Aff (console :: CONSOLE, db :: DB | eff) Unit 65 | exampleQueries = withConnection connectionInfo $ \c -> do 66 | liftEff $ log "Example queries with params:" 67 | execute_ (Query "delete from artist") c 68 | execute_ (Query "insert into artist values ('Led Zeppelin', 1968)") c 69 | execute_ (Query "insert into artist values ('Deep Purple', 1968)") c 70 | execute_ (Query "insert into artist values ('Toto', 1977)") c 71 | artists <- query (Query "select * from artist where name = $1" :: Query Artist) [toSql "Toto"] c 72 | liftEff $ printRows artists 73 | 74 | exampleUsingWithPool :: forall eff. Aff (console :: CONSOLE, db :: DB | eff) Unit 75 | exampleUsingWithPool = do 76 | pool <- liftEff $ createPool connectionInfo 77 | withPool pool $ \c -> do 78 | artists <- query_ (Query "select * from artist" :: Query Artist) c 79 | liftEff $ printRows artists 80 | liftEff $ closePool pool 81 | 82 | exampleUsingWithTransaction :: forall eff. Aff (console :: CONSOLE, db :: DB | eff) Unit 83 | exampleUsingWithTransaction = 84 | withConnection connectionInfo $ withTransaction \con -> do 85 | artists <- query_ (Query "select * from artist" :: Query Artist) con 86 | liftEff $ printRows artists 87 | 88 | printRows :: forall a eff. (Show a) => Array a -> Eff (console :: CONSOLE | eff) Unit 89 | printRows rows = log $ "result:\n" <> foldMap stringify rows 90 | where stringify = show >>> flip (<>) "\n" 91 | -------------------------------------------------------------------------------- /test/Shared.purs: -------------------------------------------------------------------------------- 1 | module Test.Shared where 2 | 3 | import Prelude 4 | import Data.Foreign.Class (class Decode) 5 | import Data.Foreign.Generic (defaultOptions, genericDecode) 6 | import Data.Generic.Rep (class Generic) 7 | 8 | data Artist = Artist 9 | { name :: String 10 | , year :: Number 11 | } 12 | 13 | instance artistShow :: Show Artist where 14 | show (Artist p) = "Artist (" <> p.name <> ", " <> show p.year <> ")" 15 | 16 | derive instance artistGeneric :: Generic Artist _ 17 | 18 | instance artistDecode :: Decode Artist where 19 | decode = genericDecode $ defaultOptions { unwrapSingleConstructors = true } 20 | 21 | instance artistEq :: Eq Artist where 22 | eq (Artist {name: n1, year: y1}) (Artist {name: n2, year: y2}) = n1 == n2 && y1 == y2 23 | -------------------------------------------------------------------------------- /test/Sqlite3.purs: -------------------------------------------------------------------------------- 1 | module Test.Sqlite3 where 2 | 3 | import Prelude (Unit, bind, discard) 4 | import Test.Shared (Artist(..)) 5 | import Database.AnyDB (ConnectionInfo(..), DB, Query(..), execute_, query_, withConnection) 6 | 7 | import Test.Spec (Spec, describe, it) 8 | import Test.Spec.Assertions (shouldEqual) 9 | 10 | connectionInfo :: ConnectionInfo 11 | connectionInfo = Sqlite3 { filename: "test" 12 | , memory: true } 13 | 14 | main :: forall r. Spec (db :: DB | r) Unit 15 | main = do 16 | describe "integration test Sqlite3 + Photobooth type" do 17 | it "should make a db, drop it, make it again, insert an Artist row, and get it back out" do 18 | withConnection connectionInfo \conn -> do 19 | execute_ (Query "CREATE TABLE artist (name CHAR(20), year INTEGER)") conn 20 | execute_ (Query "DROP TABLE artist") conn 21 | execute_ (Query "CREATE TABLE artist (name CHAR(20), year INTEGER)") conn 22 | execute_ (Query "INSERT INTO artist VALUES ('Led Zeppelin', '1968')") conn 23 | result <- query_ (Query "SELECT * from artist":: Query Artist) conn 24 | result `shouldEqual` [Artist {name: "Led Zeppelin", year: 1968.0 }] 25 | --------------------------------------------------------------------------------