Copyright | (c) Kristof Bastiaensen 2020 |
---|---|
License | BSD-3 |
Maintainer | [email protected] |
Stability | unstable |
Portability | ghc |
Safe Haskell | Safe-Inferred |
Language | Haskell2010 |
Database.MySQL.Hasqlator
Description
This module provides a lower level SQL combinator language. Hasqlator works on existing databases, and doesn't do schema creation or database migration.
example schema
Here is an example schema for a library loan system, that will be used in the following examples:
CREATE TABLE borrowers ( id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, date_of_birth DATETIME NOT NULL ); CREATE TABLE authors ( id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, birth_year INT ); CREATE TABLE books ( id INT PRIMARY KEY, title VARCHAR(255) NOT NULL, author_id INT, published_year INT, age_rating INT, FOREIGN KEY (author_id) REFERENCES authors(id) ); CREATE TABLE loans ( id INT PRIMARY KEY, book_id INT, borrower_id INT, loan_date DATE NOT NULL, return_date DATE, FOREIGN KEY (book_id) REFERENCES books(id) );
inserting data
You can insert data into the database using the insertValues
function. This can insert any haskell datatype into the database.
You have to create an Insertor
first, which maps a haskell datatype
to sql rows. There are several ways to do this:
extractor function.
Proved an extractor function for each field, match it to the sql field
using into
, and compose them using monoid append (<>
):
data Author = Author { name , birth_year } insertValues "authors" (nameinto
"name" <> birth_yearinto
"birth_year") [Author "George Orwell" 1903, Author "Aldous Huxley" 1894]
extractor lenses
Similarly, you can use a lens to match the fields, using lensInto
insertValues "authors" (_1 `lensInto` "name" <> _2 `lensInto` "birth_year") [("J.K. Rowling", 1965), ("Leo Tolstoy", 1828), ("Harper Lee", 1926)]
Data types
The insertData function uses Generics to match the input fields to the given sql columns. It works with any product type, including data declarations and records. It uses the position of the field. You can use skipInsert instead of the table name to skip fields you don't care about.
data Borrower = Borrower { name :: Text , date_of_birth :: Day } insertValues "borrowers" (insertData ("name", "date_of_birth")) [Borrower "John Doe" (fromJulian 1985 6 15), Borrower "Jane Smith" (fromJulian 1990 11 23), Borrower "Emily Johnson" (fromJulian 2000 8 30), Borrower "Michael Brown" (fromJulian 1982 2 10), Borrower "Sarah Davis" (fromJulian 1995 03 12)]
Expressions
Insert values can have arbitrary SQL queries, by using the exprInto
function. Here I create a subquery, from the query which fetches
author_id from an author name.
data Book = Book { title :: String , author :: String , publishedYear :: Int , ageRating :: Int } getBookAuthorId : Book -> Query Int getBookAuthorId Book{author=a} = select (sel "id") $ from "authors" <> where_ ["name" =. arg a] insertValues (title `into` "title" <> (subQuery . getBookAuthorId `exprInto` "author_id") <> publishedYear `into` "published_year" <> ageRating `into` "age_rating") [ Book "1984" "George Orwell" 1903 16 , Book "Brave New World" "Aldous Huxley" 1932 14, , Book "Harry Potter and the Philosopher\'s Stone" "J.K. Rowling" 1997 7 , Book "War and Peace" "Leo Tolstoy" 1869 18 ]
Querying
Queries are done using the select
function. It takes a Selector
,
which tells how to match SQL fields with haskell values, and a
QueryClauses
, which contains the rest of the query (the select
statement is emitted by the select function).
For example, a query that gives back all authors:
getAllAuthors :: Query Author getAllAuthors = select (Author $ sel "name" * sel "birth_year" ) $ from "author"
Here the selector is composed using the sel
function for individual
columns, and and applicative functor to turn it into the desired
haskell datastructure.
The query body can be composed using Monoid mappend
(<>
). For
example, here is a more complicated expression to get all books loaned
by a specific borrower:
@ booksLoaned :: Borrower -> Query Book booksLoaned borrower = select (Book $
SELECT books.title, loans.loan_date, loans.return_date FROM loans JOIN books ON loans.book_id = books.id JOIN borrowers ON loans.borrower_id = borrowers.id WHERE borrowers.name = 'Jane Smith';
Insert Loans
Finally we can insert the loans in the table.
data Loan = Loan { bookTitle :: Text , bookAuthor :: Text , borrowerName :: Text , loanDate :: Day , return_date :: Day } getLoanBookId :: Loan -> Query Int getLoanBookId Loan{bookAuthor=bookAuthor, bookTitle=bookTitle} = select (sel "id") $ from "books" <> leftJoin ["authors"] ["books.author_id" =. "authors.id"] <> where_ ["books.title" =. arg bookTitle, "authors.name" =. arg bookAuthor] getLoanBorrower_id :: Loan -> Query Int getLoanBorrower_id Loan{borrowerName = borrowerName} = select (sel "id") $ from "borrowers"
INSERT INTO loans (id, book_id, borrower_id, loan_date, return_date) VALUES (1, 1, 1, '2024-09-15', '2024-09-30'), -- John Doe borrowed '1984' and returned it (2, 3, 2, '2024-10-01', NULL), -- Jane Smith borrowed 'Brave New World' and hasn't returned it yet (3, 4, 3, '2024-10-05', NULL), -- Emily Johnson borrowed 'Harry Potter and the Philosopher's Stone' (4, 6, 4, '2024-09-20', '2024-09-27'), -- Michael Brown borrowed 'To Kill a Mockingbird' and returned it (5, 5, 5, '2024-10-08', NULL); -- Sarah Davis borrowed 'War and Peace' recen
Synopsis
- data Query a
- data Command
- select :: Selector a -> QueryClauses -> Query a
- unionDistinct :: Query a -> Query a -> Query a
- unionAll :: Query a -> Query a -> Query a
- mergeSelect :: Query b -> (a -> b -> c) -> Selector a -> Query c
- replaceSelect :: Selector a -> Query b -> Query a
- data QueryClauses
- from :: QueryBuilder -> QueryClauses
- innerJoin :: [QueryBuilder] -> [QueryBuilder] -> QueryClauses
- leftJoin :: [QueryBuilder] -> [QueryBuilder] -> QueryClauses
- rightJoin :: [QueryBuilder] -> [QueryBuilder] -> QueryClauses
- outerJoin :: [QueryBuilder] -> [QueryBuilder] -> QueryClauses
- emptyJoins :: QueryClauses
- where_ :: [QueryBuilder] -> QueryClauses
- emptyWhere :: QueryClauses
- groupBy_ :: [QueryBuilder] -> QueryClauses
- having :: [QueryBuilder] -> QueryClauses
- emptyHaving :: QueryClauses
- data QueryOrdering
- orderBy :: [QueryOrdering] -> QueryClauses
- limit :: Int -> QueryClauses
- limitOffset :: Int -> Int -> QueryClauses
- data Selector a
- as :: QueryBuilder -> QueryBuilder -> QueryBuilder
- forUpdate :: [QueryBuilder] -> WaitLock -> QueryClauses
- forShare :: [QueryBuilder] -> WaitLock -> QueryClauses
- shareMode :: QueryClauses
- data WaitLock
- sel :: FromSql a => QueryBuilder -> Selector a
- intSel :: (Show a, Bounded a, Integral a) => QueryBuilder -> Selector a
- integerSel :: QueryBuilder -> Selector Integer
- doubleSel :: QueryBuilder -> Selector Double
- floatSel :: QueryBuilder -> Selector Float
- scientificSel :: QueryBuilder -> Selector Scientific
- localTimeSel :: QueryBuilder -> Selector LocalTime
- timeOfDaySel :: QueryBuilder -> Selector TimeOfDay
- diffTimeSel :: QueryBuilder -> Selector DiffTime
- daySel :: QueryBuilder -> Selector Day
- byteStringSel :: QueryBuilder -> Selector ByteString
- textSel :: QueryBuilder -> Selector Text
- rawValues :: [QueryBuilder] -> Selector [MySQLValue]
- rawValues_ :: [QueryBuilder] -> Selector ()
- subQuery :: ToQueryBuilder a => a -> QueryBuilder
- arg :: ToSql a => a -> QueryBuilder
- fun :: Text -> [QueryBuilder] -> QueryBuilder
- op :: Text -> QueryBuilder -> QueryBuilder -> QueryBuilder
- isNull :: QueryBuilder -> QueryBuilder
- isNotNull :: QueryBuilder -> QueryBuilder
- (>.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (<.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (>=.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (<=.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (+.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (-.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (*.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (/.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (=.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (++.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (/=.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (&&.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- (||.) :: QueryBuilder -> QueryBuilder -> QueryBuilder
- abs_ :: QueryBuilder -> QueryBuilder
- negate_ :: QueryBuilder -> QueryBuilder
- signum_ :: QueryBuilder -> QueryBuilder
- sum_ :: QueryBuilder -> QueryBuilder
- rawSql :: Text -> QueryBuilder
- substr :: QueryBuilder -> QueryBuilder -> QueryBuilder -> QueryBuilder
- in_ :: QueryBuilder -> [QueryBuilder] -> QueryBuilder
- false_ :: QueryBuilder
- true_ :: QueryBuilder
- notIn_ :: QueryBuilder -> [QueryBuilder] -> QueryBuilder
- values :: QueryBuilder -> QueryBuilder
- data Insertor a
- insertValues :: QueryBuilder -> Insertor a -> [a] -> Command
- insertUpdateValues :: QueryBuilder -> Insertor a -> [(QueryBuilder, QueryBuilder)] -> [a] -> Command
- insertSelect :: QueryBuilder -> [QueryBuilder] -> [QueryBuilder] -> QueryClauses -> Command
- insertData :: (Generic a, Generic b, InsertGeneric (Rep a ()) (Rep b ())) => a -> Insertor b
- skipInsert :: Insertor a
- into :: ToSql b => (a -> b) -> Text -> Insertor a
- exprInto :: (a -> QueryBuilder) -> Text -> Insertor a
- type Getter s a = (a -> Const a a) -> s -> Const a s
- lensInto :: ToSql b => Getter a b -> Text -> Insertor a
- insertOne :: ToSql a => Text -> Insertor a
- class ToSql a
- insertLess :: Insertor a -> [Text] -> Insertor a
- update :: [QueryBuilder] -> [(QueryBuilder, QueryBuilder)] -> QueryClauses -> Command
- delete :: QueryBuilder -> QueryClauses -> Command
- renderStmt :: ToQueryBuilder a => a -> ByteString
- renderPreparedStmt :: ToQueryBuilder a => a -> (ByteString, [MySQLValue])
- data SQLError
- data QueryBuilder
- class ToQueryBuilder a where
- toQueryBuilder :: a -> QueryBuilder
- class FromSql a
- executeQuery :: MySQLConn -> Query a -> IO [a]
- executeCommand :: MySQLConn -> Command -> IO OK
Querying
`Query a` represents a query returning values of type a
.
Instances
ToQueryBuilder (Query a) Source # | |
Defined in Database.MySQL.Hasqlator Methods toQueryBuilder :: Query a -> QueryBuilder Source # |
A command is a database query that doesn't return a value, but is executed for the side effect (inserting, updating, deleteing).
Instances
ToQueryBuilder Command Source # | |
Defined in Database.MySQL.Hasqlator Methods toQueryBuilder :: Command -> QueryBuilder Source # |
mergeSelect :: Query b -> (a -> b -> c) -> Selector a -> Query c Source #
Merge a new Selector
in a query.
Query Clauses
data QueryClauses Source #
Instances
Monoid QueryClauses Source # | |
Defined in Database.MySQL.Hasqlator Methods mempty :: QueryClauses # mappend :: QueryClauses -> QueryClauses -> QueryClauses # mconcat :: [QueryClauses] -> QueryClauses # | |
Semigroup QueryClauses Source # | |
Defined in Database.MySQL.Hasqlator Methods (<>) :: QueryClauses -> QueryClauses -> QueryClauses # sconcat :: NonEmpty QueryClauses -> QueryClauses # stimes :: Integral b => b -> QueryClauses -> QueryClauses # |
from :: QueryBuilder -> QueryClauses Source #
FROM table
Arguments
:: [QueryBuilder] | tables |
-> [QueryBuilder] | on expressions, joined by AND |
-> QueryClauses |
INNER JOIN table1, ... ON cond1, cond2, ...
Arguments
:: [QueryBuilder] | tables |
-> [QueryBuilder] | on expressions, joined by AND |
-> QueryClauses |
LEFT JOIN
Arguments
:: [QueryBuilder] | tables |
-> [QueryBuilder] | on expressions, joined by AND |
-> QueryClauses |
RIGHT JOIN
Arguments
:: [QueryBuilder] | tables |
-> [QueryBuilder] | on expressions, joined by AND |
-> QueryClauses |
OUTER JOIN
emptyJoins :: QueryClauses Source #
remove all existing joins
where_ :: [QueryBuilder] -> QueryClauses Source #
WHERE expression1, expression2, ...
emptyWhere :: QueryClauses Source #
remove all existing where expressions
groupBy_ :: [QueryBuilder] -> QueryClauses Source #
GROUP BY e1, e2, ...
having :: [QueryBuilder] -> QueryClauses Source #
HAVING e1, e2, ...
emptyHaving :: QueryClauses Source #
remove having expression
data QueryOrdering Source #
Constructors
Asc QueryBuilder | |
Desc QueryBuilder |
Instances
ToQueryBuilder QueryOrdering Source # | |
Defined in Database.MySQL.Hasqlator Methods |
orderBy :: [QueryOrdering] -> QueryClauses Source #
ORDER BY e1, e2, ...
limit :: Int -> QueryClauses Source #
LIMIT n
Selectors
Selectors contain the target fields or expressions in a SQL
SELECT statement, and perform the conversion to haskell. Selectors
are instances of Applicative
, so they can return the desired
haskell type.
as :: QueryBuilder -> QueryBuilder -> QueryBuilder Source #
combinator for aliasing columns.
forUpdate :: [QueryBuilder] -> WaitLock -> QueryClauses Source #
forShare :: [QueryBuilder] -> WaitLock -> QueryClauses Source #
polymorphic selector
sel :: FromSql a => QueryBuilder -> Selector a Source #
The polymorphic selector. The return type is determined by type inference.
specialised selectors
The following are specialised versions of sel
. Using these
may make refactoring easier, for example accidently swapping
and sel
"age"
would not give a type error,
while sel
"name"intSel "age"
and textSel "name"
most likely would.
integerSel :: QueryBuilder -> Selector Integer Source #
Un unbounded integer field, either a bounded integer (TINYINT, etc...) or DECIMAL in the database. Will throw a type error if the stored value is actually fractional.
WARNING: this function could potentially create huge integers with DECIMAL, if the exponent is large, even fillup the space and crash your program! Only use this on trusted inputs, or use Scientific instead.
scientificSel :: QueryBuilder -> Selector Scientific Source #
A DECIMAL or NUMERIC field.
localTimeSel :: QueryBuilder -> Selector LocalTime Source #
a DATETIME or a TIMESTAMP field.
timeOfDaySel :: QueryBuilder -> Selector TimeOfDay Source #
A TIME field taken as a specific time.
diffTimeSel :: QueryBuilder -> Selector DiffTime Source #
a TIME field taken as a time duration.
byteStringSel :: QueryBuilder -> Selector ByteString Source #
A binary BLOB field.
other selectors
rawValues :: [QueryBuilder] -> Selector [MySQLValue] Source #
Read the columns directly as a MySQLValue
type without conversion.
rawValues_ :: [QueryBuilder] -> Selector () Source #
Ignore the content of the given columns
Expressions
subQuery :: ToQueryBuilder a => a -> QueryBuilder Source #
(subquery)
arg :: ToSql a => a -> QueryBuilder Source #
fun :: Text -> [QueryBuilder] -> QueryBuilder Source #
op :: Text -> QueryBuilder -> QueryBuilder -> QueryBuilder Source #
isNull :: QueryBuilder -> QueryBuilder Source #
IS NULL
isNotNull :: QueryBuilder -> QueryBuilder Source #
IS NOT NULL expression
(>.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infix 4 Source #
(<.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infix 4 Source #
(>=.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infix 4 Source #
(<=.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infix 4 Source #
(+.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infixl 6 Source #
(-.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infixl 6 Source #
(*.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infixl 7 Source #
(/.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infixl 7 Source #
(=.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infix 4 Source #
(++.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infixr 5 Source #
(/=.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infix 4 Source #
(&&.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infixr 3 Source #
(||.) :: QueryBuilder -> QueryBuilder -> QueryBuilder infixr 3 Source #
abs_ :: QueryBuilder -> QueryBuilder Source #
negate_ :: QueryBuilder -> QueryBuilder Source #
signum_ :: QueryBuilder -> QueryBuilder Source #
sum_ :: QueryBuilder -> QueryBuilder Source #
rawSql :: Text -> QueryBuilder Source #
substr :: QueryBuilder -> QueryBuilder -> QueryBuilder -> QueryBuilder Source #
in_ :: QueryBuilder -> [QueryBuilder] -> QueryBuilder Source #
false_ :: QueryBuilder Source #
False
true_ :: QueryBuilder Source #
True
notIn_ :: QueryBuilder -> [QueryBuilder] -> QueryBuilder Source #
values :: QueryBuilder -> QueryBuilder Source #
VALUES
Insertion
An
provides a mapping of parts of values of type
Insertor
aa
to columns in the database. Insertors can be combined using <>
.
insertValues :: QueryBuilder -> Insertor a -> [a] -> Command Source #
insert values using the given insertor.
insertUpdateValues :: QueryBuilder -> Insertor a -> [(QueryBuilder, QueryBuilder)] -> [a] -> Command Source #
INSERT UPDATE
insertSelect :: QueryBuilder -> [QueryBuilder] -> [QueryBuilder] -> QueryClauses -> Command Source #
insertData :: (Generic a, Generic b, InsertGeneric (Rep a ()) (Rep b ())) => a -> Insertor b Source #
insertData
inserts a tuple or other product type into the given
fields. It uses generics to match the input to the fields. For
example:
insert "Person" (insertData ("name", "age")) [Person "Bart Simpson" 10, Person "Lisa Simpson" 8]
skipInsert :: Insertor a Source #
skipInsert is mempty specialized to an Insertor. It can be used to skip fields when using insertData.
into :: ToSql b => (a -> b) -> Text -> Insertor a Source #
into
uses the given accessor function to map the part to a
field. For example:
insertValues "Person" (fst `into` "name" <> snd `into` "age") [("Bart Simpson", 10), ("Lisa Simpson", 8)]
exprInto :: (a -> QueryBuilder) -> Text -> Insertor a Source #
insert an SQL expression. Takes a function that generates the SQL expression from the input.
type Getter s a = (a -> Const a a) -> s -> Const a s Source #
A Getter type compatible with the lens library
lensInto :: ToSql b => Getter a b -> Text -> Insertor a Source #
lensInto
uses a lens to map the part to a field. For example:
insertValues "Person" (_1 `lensInto` "name" <> _2 `lensInto` "age") [("Bart Simpson", 10), ("Lisa Simpson", 8)]
Minimal complete definition
toSqlValue
Instances
Updates
update :: [QueryBuilder] -> [(QueryBuilder, QueryBuilder)] -> QueryClauses -> Command Source #
Deletes
delete :: QueryBuilder -> QueryClauses -> Command Source #
DELETE
Rendering Queries
renderStmt :: ToQueryBuilder a => a -> ByteString Source #
renderPreparedStmt :: ToQueryBuilder a => a -> (ByteString, [MySQLValue]) Source #
Instances
Exception SQLError Source # | |
Defined in Database.MySQL.Hasqlator Methods toException :: SQLError -> SomeException # fromException :: SomeException -> Maybe SQLError # displayException :: SQLError -> String # | |
Show SQLError Source # | |
data QueryBuilder Source #
Instances
IsString QueryBuilder Source # | |
Defined in Database.MySQL.Hasqlator Methods fromString :: String -> QueryBuilder # | |
Monoid QueryBuilder Source # | |
Defined in Database.MySQL.Hasqlator Methods mempty :: QueryBuilder # mappend :: QueryBuilder -> QueryBuilder -> QueryBuilder # mconcat :: [QueryBuilder] -> QueryBuilder # | |
Semigroup QueryBuilder Source # | |
Defined in Database.MySQL.Hasqlator Methods (<>) :: QueryBuilder -> QueryBuilder -> QueryBuilder # sconcat :: NonEmpty QueryBuilder -> QueryBuilder # stimes :: Integral b => b -> QueryBuilder -> QueryBuilder # |
class ToQueryBuilder a where Source #
Methods
toQueryBuilder :: a -> QueryBuilder Source #
Instances
ToQueryBuilder Command Source # | |
Defined in Database.MySQL.Hasqlator Methods toQueryBuilder :: Command -> QueryBuilder Source # | |
ToQueryBuilder QueryOrdering Source # | |
Defined in Database.MySQL.Hasqlator Methods | |
ToQueryBuilder (Query a) Source # | |
Defined in Database.MySQL.Hasqlator Methods toQueryBuilder :: Query a -> QueryBuilder Source # |
Minimal complete definition
fromSql