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.AutoMigration
|
||||||
Orville.SQLite.Execution
|
Orville.SQLite.Execution
|
||||||
Orville.SQLite.FieldDefinition
|
Orville.SQLite.FieldDefinition
|
||||||
|
Orville.SQLite.Internal
|
||||||
Orville.SQLite.Monad
|
Orville.SQLite.Monad
|
||||||
|
Orville.SQLite.Raw
|
||||||
Orville.SQLite.RawSql
|
Orville.SQLite.RawSql
|
||||||
Orville.SQLite.SqlMarshaller
|
Orville.SQLite.SqlMarshaller
|
||||||
Orville.SQLite.SqlType
|
Orville.SQLite.SqlType
|
||||||
@@ -42,6 +44,7 @@ test-suite spec
|
|||||||
Test.AutoMigration
|
Test.AutoMigration
|
||||||
Test.EntityOperations
|
Test.EntityOperations
|
||||||
Test.FieldDefinition
|
Test.FieldDefinition
|
||||||
|
Test.Raw
|
||||||
Test.SqlMarshaller
|
Test.SqlMarshaller
|
||||||
hs-source-dirs: test
|
hs-source-dirs: test
|
||||||
build-depends:
|
build-depends:
|
||||||
|
|||||||
@@ -13,12 +13,15 @@ module Orville.SQLite
|
|||||||
, module Orville.SQLite.AutoMigration
|
, module Orville.SQLite.AutoMigration
|
||||||
-- * Execution
|
-- * Execution
|
||||||
, module Orville.SQLite.Execution
|
, module Orville.SQLite.Execution
|
||||||
|
-- * Raw
|
||||||
|
, module Orville.SQLite.Raw
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Orville.SQLite.AutoMigration
|
import Orville.SQLite.AutoMigration
|
||||||
import Orville.SQLite.Execution
|
import Orville.SQLite.Execution
|
||||||
import Orville.SQLite.FieldDefinition
|
import Orville.SQLite.FieldDefinition
|
||||||
import Orville.SQLite.Monad
|
import Orville.SQLite.Monad
|
||||||
|
import Orville.SQLite.Raw
|
||||||
import Orville.SQLite.SqlMarshaller
|
import Orville.SQLite.SqlMarshaller
|
||||||
import Orville.SQLite.SqlType
|
import Orville.SQLite.SqlType
|
||||||
import Orville.SQLite.TableDefinition
|
import Orville.SQLite.TableDefinition
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import Control.Monad.Reader (ask)
|
|||||||
import Data.List (intercalate)
|
import Data.List (intercalate)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Database.SQLite3 as SQLite3
|
import qualified Database.SQLite3 as SQLite3
|
||||||
import Database.SQLite3.Direct (columnCount)
|
|
||||||
import Orville.SQLite.FieldDefinition (fieldColumnName, fieldToSqlValue)
|
import Orville.SQLite.FieldDefinition (fieldColumnName, fieldToSqlValue)
|
||||||
|
import Orville.SQLite.Internal (getRowData)
|
||||||
import Orville.SQLite.Monad (OrvilleM)
|
import Orville.SQLite.Monad (OrvilleM)
|
||||||
import Orville.SQLite.SqlMarshaller (
|
import Orville.SQLite.SqlMarshaller (
|
||||||
marshallerDecodeRow,
|
marshallerDecodeRow,
|
||||||
@@ -148,22 +148,4 @@ deleteEntity tableDef key = do
|
|||||||
_ <- SQLite3.step stmt
|
_ <- SQLite3.step stmt
|
||||||
SQLite3.finalize 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.AutoMigration as AutoMigration
|
||||||
import qualified Test.EntityOperations as EntityOperations
|
import qualified Test.EntityOperations as EntityOperations
|
||||||
import qualified Test.FieldDefinition as FieldDefinition
|
import qualified Test.FieldDefinition as FieldDefinition
|
||||||
|
import qualified Test.Raw as Raw
|
||||||
import qualified Test.SqlMarshaller as SqlMarshaller
|
import qualified Test.SqlMarshaller as SqlMarshaller
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
@@ -15,3 +16,4 @@ main = hspec $ do
|
|||||||
describe "SqlMarshaller" SqlMarshaller.sqlMarshallerTests
|
describe "SqlMarshaller" SqlMarshaller.sqlMarshallerTests
|
||||||
describe "EntityOperations" EntityOperations.entityOperationsTests
|
describe "EntityOperations" EntityOperations.entityOperationsTests
|
||||||
describe "AutoMigration" AutoMigration.autoMigrationTests
|
describe "AutoMigration" AutoMigration.autoMigrationTests
|
||||||
|
describe "Raw" Raw.rawTests
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ autoMigrationTests = do
|
|||||||
pure ()
|
pure ()
|
||||||
closeConnection db
|
closeConnection db
|
||||||
|
|
||||||
it "is idempotent — running twice does not error" $ do
|
it "is idempotent -- running twice does not error" $ do
|
||||||
db <- openConnection ":memory:"
|
db <- openConnection ":memory:"
|
||||||
withConnection db $ do
|
withConnection db $ do
|
||||||
autoMigrateSchema defaultOptions [schemaTable personTable []]
|
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