feat: add raw SQL execution helpers (execute, executeWith, query_, queryWith)
Build and Test / build-and-test (push) Successful in 4m46s
Build and Test / build-and-test (push) Successful in 4m46s
This commit is contained in:
@@ -19,7 +19,9 @@ library
|
||||
Orville.SQLite.AutoMigration
|
||||
Orville.SQLite.Execution
|
||||
Orville.SQLite.FieldDefinition
|
||||
Orville.SQLite.Internal
|
||||
Orville.SQLite.Monad
|
||||
Orville.SQLite.Raw
|
||||
Orville.SQLite.RawSql
|
||||
Orville.SQLite.SqlMarshaller
|
||||
Orville.SQLite.SqlType
|
||||
@@ -42,6 +44,7 @@ test-suite spec
|
||||
Test.AutoMigration
|
||||
Test.EntityOperations
|
||||
Test.FieldDefinition
|
||||
Test.Raw
|
||||
Test.SqlMarshaller
|
||||
hs-source-dirs: test
|
||||
build-depends:
|
||||
|
||||
@@ -13,12 +13,15 @@ module Orville.SQLite
|
||||
, module Orville.SQLite.AutoMigration
|
||||
-- * Execution
|
||||
, module Orville.SQLite.Execution
|
||||
-- * Raw
|
||||
, module Orville.SQLite.Raw
|
||||
) where
|
||||
|
||||
import Orville.SQLite.AutoMigration
|
||||
import Orville.SQLite.Execution
|
||||
import Orville.SQLite.FieldDefinition
|
||||
import Orville.SQLite.Monad
|
||||
import Orville.SQLite.Raw
|
||||
import Orville.SQLite.SqlMarshaller
|
||||
import Orville.SQLite.SqlType
|
||||
import Orville.SQLite.TableDefinition
|
||||
|
||||
@@ -15,8 +15,8 @@ import Control.Monad.Reader (ask)
|
||||
import Data.List (intercalate)
|
||||
import qualified Data.Text as T
|
||||
import qualified Database.SQLite3 as SQLite3
|
||||
import Database.SQLite3.Direct (columnCount)
|
||||
import Orville.SQLite.FieldDefinition (fieldColumnName, fieldToSqlValue)
|
||||
import Orville.SQLite.Internal (getRowData)
|
||||
import Orville.SQLite.Monad (OrvilleM)
|
||||
import Orville.SQLite.SqlMarshaller (
|
||||
marshallerDecodeRow,
|
||||
@@ -148,22 +148,4 @@ deleteEntity tableDef key = do
|
||||
_ <- SQLite3.step stmt
|
||||
SQLite3.finalize stmt
|
||||
|
||||
getRowData ::
|
||||
SQLite3.Statement ->
|
||||
[String] ->
|
||||
IO [(String, SQLite3.SQLData)]
|
||||
getRowData stmt cols = do
|
||||
colCount <- columnCount stmt
|
||||
let count :: Int = fromIntegral colCount
|
||||
indexes = take count [0 :: SQLite3.ColumnIndex ..]
|
||||
mapM
|
||||
( \i -> do
|
||||
let idx :: Int = fromIntegral i
|
||||
colName =
|
||||
if idx < length cols
|
||||
then cols !! idx
|
||||
else ""
|
||||
sqlVal <- SQLite3.column stmt i
|
||||
pure (colName, sqlVal)
|
||||
)
|
||||
indexes
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
module Orville.SQLite.Internal (
|
||||
getRowData,
|
||||
) where
|
||||
|
||||
import qualified Database.SQLite3 as SQLite3
|
||||
import Database.SQLite3.Direct (columnCount)
|
||||
|
||||
getRowData ::
|
||||
SQLite3.Statement ->
|
||||
[String] ->
|
||||
IO [(String, SQLite3.SQLData)]
|
||||
getRowData stmt cols = do
|
||||
colCount <- columnCount stmt
|
||||
let count :: Int = fromIntegral colCount
|
||||
indexes = take count [0 :: SQLite3.ColumnIndex ..]
|
||||
mapM
|
||||
( \i -> do
|
||||
let idx :: Int = fromIntegral i
|
||||
colName =
|
||||
if idx < length cols
|
||||
then cols !! idx
|
||||
else ""
|
||||
sqlVal <- SQLite3.column stmt i
|
||||
pure (colName, sqlVal)
|
||||
)
|
||||
indexes
|
||||
@@ -0,0 +1,71 @@
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Orville.SQLite.Raw (
|
||||
execute,
|
||||
executeWith,
|
||||
query_,
|
||||
queryWith,
|
||||
) where
|
||||
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Control.Monad.Reader (ask)
|
||||
import qualified Data.Text as T
|
||||
import qualified Database.SQLite3 as SQLite3
|
||||
|
||||
import Orville.SQLite.Internal (getRowData)
|
||||
import Orville.SQLite.Monad (OrvilleM)
|
||||
|
||||
-- | Execute a raw SQL statement with no parameters.
|
||||
execute :: T.Text -> OrvilleM ()
|
||||
execute sql = do
|
||||
db <- ask
|
||||
liftIO $ do
|
||||
stmt <- SQLite3.prepare db sql
|
||||
_ <- SQLite3.step stmt
|
||||
SQLite3.finalize stmt
|
||||
|
||||
-- | Execute a raw SQL statement with bound parameters.
|
||||
executeWith :: T.Text -> [SQLite3.SQLData] -> OrvilleM ()
|
||||
executeWith sql params = do
|
||||
db <- ask
|
||||
liftIO $ do
|
||||
stmt <- SQLite3.prepare db sql
|
||||
SQLite3.bind stmt params
|
||||
_ <- SQLite3.step stmt
|
||||
SQLite3.finalize stmt
|
||||
|
||||
-- | Execute a raw SQL query with no parameters and collect all rows.
|
||||
query_ :: T.Text -> OrvilleM [[(String, SQLite3.SQLData)]]
|
||||
query_ sql = do
|
||||
db <- ask
|
||||
liftIO $ do
|
||||
stmt <- SQLite3.prepare db sql
|
||||
let loop acc = do
|
||||
stepResult <- SQLite3.step stmt
|
||||
case stepResult of
|
||||
SQLite3.Done -> do
|
||||
SQLite3.finalize stmt
|
||||
pure (reverse acc)
|
||||
SQLite3.Row -> do
|
||||
rowData <- getRowData stmt []
|
||||
loop (rowData : acc)
|
||||
loop []
|
||||
|
||||
-- | Execute a raw SQL query with bound parameters and collect all rows.
|
||||
queryWith :: T.Text -> [SQLite3.SQLData] -> OrvilleM [[(String, SQLite3.SQLData)]]
|
||||
queryWith sql params = do
|
||||
db <- ask
|
||||
liftIO $ do
|
||||
stmt <- SQLite3.prepare db sql
|
||||
SQLite3.bind stmt params
|
||||
let loop acc = do
|
||||
stepResult <- SQLite3.step stmt
|
||||
case stepResult of
|
||||
SQLite3.Done -> do
|
||||
SQLite3.finalize stmt
|
||||
pure (reverse acc)
|
||||
SQLite3.Row -> do
|
||||
rowData <- getRowData stmt []
|
||||
loop (rowData : acc)
|
||||
loop []
|
||||
@@ -7,6 +7,7 @@ import Test.Hspec
|
||||
import qualified Test.AutoMigration as AutoMigration
|
||||
import qualified Test.EntityOperations as EntityOperations
|
||||
import qualified Test.FieldDefinition as FieldDefinition
|
||||
import qualified Test.Raw as Raw
|
||||
import qualified Test.SqlMarshaller as SqlMarshaller
|
||||
|
||||
main :: IO ()
|
||||
@@ -15,3 +16,4 @@ main = hspec $ do
|
||||
describe "SqlMarshaller" SqlMarshaller.sqlMarshallerTests
|
||||
describe "EntityOperations" EntityOperations.entityOperationsTests
|
||||
describe "AutoMigration" AutoMigration.autoMigrationTests
|
||||
describe "Raw" Raw.rawTests
|
||||
|
||||
@@ -23,7 +23,7 @@ autoMigrationTests = do
|
||||
pure ()
|
||||
closeConnection db
|
||||
|
||||
it "is idempotent — running twice does not error" $ do
|
||||
it "is idempotent -- running twice does not error" $ do
|
||||
db <- openConnection ":memory:"
|
||||
withConnection db $ do
|
||||
autoMigrateSchema defaultOptions [schemaTable personTable []]
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Test.Raw where
|
||||
|
||||
import qualified Database.SQLite3 as SQLite3
|
||||
import Test.Hspec
|
||||
|
||||
import Orville.SQLite
|
||||
|
||||
rawTests :: Spec
|
||||
rawTests = do
|
||||
describe "execute" $ do
|
||||
it "runs a CREATE TABLE and INSERT statement" $ do
|
||||
db <- openConnection ":memory:"
|
||||
runOrvilleM db $ execute "CREATE TABLE raw_test (id INTEGER, name TEXT)"
|
||||
runOrvilleM db $ execute "INSERT INTO raw_test VALUES (1, 'hello')"
|
||||
rows <- runOrvilleM db $ query_ "SELECT * FROM raw_test"
|
||||
length rows `shouldBe` 1
|
||||
closeConnection db
|
||||
|
||||
describe "executeWith" $ do
|
||||
it "runs parameterized INSERT" $ do
|
||||
db <- openConnection ":memory:"
|
||||
runOrvilleM db $ execute "CREATE TABLE raw_test2 (id INTEGER, name TEXT)"
|
||||
runOrvilleM db $ executeWith "INSERT INTO raw_test2 VALUES (?, ?)" [SQLite3.SQLInteger 42, SQLite3.SQLText "world"]
|
||||
rows <- runOrvilleM db $ query_ "SELECT * FROM raw_test2"
|
||||
length rows `shouldBe` 1
|
||||
closeConnection db
|
||||
|
||||
describe "query_" $ do
|
||||
it "returns rows from a SELECT" $ do
|
||||
db <- openConnection ":memory:"
|
||||
runOrvilleM db $ execute "CREATE TABLE raw_test3 (a INTEGER)"
|
||||
runOrvilleM db $ execute "INSERT INTO raw_test3 VALUES (1)"
|
||||
runOrvilleM db $ execute "INSERT INTO raw_test3 VALUES (2)"
|
||||
rows <- runOrvilleM db $ query_ "SELECT * FROM raw_test3 ORDER BY a"
|
||||
length rows `shouldBe` 2
|
||||
closeConnection db
|
||||
|
||||
describe "queryWith" $ do
|
||||
it "returns rows matching parameters" $ do
|
||||
db <- openConnection ":memory:"
|
||||
runOrvilleM db $ execute "CREATE TABLE raw_test4 (name TEXT)"
|
||||
runOrvilleM db $ executeWith "INSERT INTO raw_test4 VALUES (?)" [SQLite3.SQLText "alpha"]
|
||||
runOrvilleM db $ executeWith "INSERT INTO raw_test4 VALUES (?)" [SQLite3.SQLText "beta"]
|
||||
rows <- runOrvilleM db $ queryWith "SELECT * FROM raw_test4 WHERE name = ?" [SQLite3.SQLText "beta"]
|
||||
length rows `shouldBe` 1
|
||||
closeConnection db
|
||||
Reference in New Issue
Block a user