在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):morphismtech/squeal开源软件地址(OpenSource Url):https://github.com/morphismtech/squeal开源编程语言(OpenSource Language):Haskell 99.9%开源软件介绍(OpenSource Introduction):squealintroductionSqueal is a deep embedding of SQL into Haskell. By "deep embedding", I am abusing the term somewhat. What I mean is that Squeal embeds both SQL terms and SQL types into Haskell at the term and type levels respectively. This leads to a very high level of type-safety in Squeal. Squeal embeds not just the structured query language of SQL but also the
data manipulation language and the data definition language; that's Squeal expressions closely match their corresponding SQL expressions so that the SQL they actually generate is completely predictable. They are also highly composable and cover a large portion of SQL. features
installation
testingStart postgres on localhost port
contributingWe welcome contributors.
Please make pull requests on the usageLet's see an example! First, we need some language extensions because Squeal uses modern GHC features. >>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels -XFlexibleContexts
>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators -XGADTs We'll need some imports. >>> import Control.Monad.IO.Class (liftIO)
>>> import Data.Int (Int32)
>>> import Data.Text (Text)
>>> import Squeal.PostgreSQL We'll use generics to easily convert between Haskell and PostgreSQL values. >>> import qualified Generics.SOP as SOP
>>> import qualified GHC.Generics as GHC The first step is to define the schema of our database. This is where
we use >>> :{
type UsersColumns =
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "name" ::: 'NoDef :=> 'NotNull 'PGtext ]
type UsersConstraints = '[ "pk_users" ::: 'PrimaryKey '["id"] ]
type EmailsColumns =
'[ "id" ::: 'Def :=> 'NotNull 'PGint4
, "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
, "email" ::: 'NoDef :=> 'Null 'PGtext ]
type EmailsConstraints =
'[ "pk_emails" ::: 'PrimaryKey '["id"]
, "fk_user_id" ::: 'ForeignKey '["user_id"] "public" "users" '["id"] ]
type Schema =
'[ "users" ::: 'Table (UsersConstraints :=> UsersColumns)
, "emails" ::: 'Table (EmailsConstraints :=> EmailsColumns) ]
type DB = Public Schema
:} Notice the use of type operators.
Next, we'll write >>> :{
let
setup :: Definition (Public '[]) DB
setup =
createTable #users
( serial `as` #id :*
(text & notNullable) `as` #name )
( primaryKey #id `as` #pk_users ) >>>
createTable #emails
( serial `as` #id :*
(int & notNullable) `as` #user_id :*
(text & nullable) `as` #email )
( primaryKey #id `as` #pk_emails :*
foreignKey #user_id #users #id
(OnDelete Cascade) (OnUpdate Cascade) `as` #fk_user_id )
:} We can easily see the generated SQL is unsurprising looking. >>> printSQL setup CREATE TABLE "users" ("id" serial, "name" text NOT NULL, CONSTRAINT "pk_users" PRIMARY KEY ("id"));
CREATE TABLE "emails" ("id" serial, "user_id" int NOT NULL, "email" text NULL, CONSTRAINT "pk_emails" PRIMARY KEY ("id"), CONSTRAINT "fk_user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE); Notice that >>> :{
let
teardown :: Definition DB (Public '[])
teardown = dropTable #emails >>> dropTable #users
:}
>>> printSQL teardown DROP TABLE "emails";
DROP TABLE "users"; We'll need a Haskell type for >>> :set -XDerivingStrategies -XDeriveAnyClass
>>> :{
data User = User { userName :: Text, userEmail :: Maybe Text }
deriving stock (Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
:} Next, we'll write >>> :{
let
insertUser :: Statement DB User ()
insertUser = manipulation $ with (u `as` #u) e
where
u = insertInto #users
(Values_ (Default `as` #id :* Set (param @1) `as` #name))
OnConflictDoRaise (Returning_ (#id :* param @2 `as` #email))
e = insertInto_ #emails $ Select
(Default `as` #id :* Set (#u ! #id) `as` #user_id :* Set (#u ! #email) `as` #email)
(from (common #u))
:} >>> printSQL insertUser WITH "u" AS (INSERT INTO "users" ("id", "name") VALUES (DEFAULT, ($1 :: text)) RETURNING "id" AS "id", ($2 :: text) AS "email") INSERT INTO "emails" ("user_id", "email") SELECT "u"."id", "u"."email" FROM "u" AS "u" Next we write a >>> :{
let
getUsers :: Statement DB () User
getUsers = query $ select_
(#u ! #name `as` #userName :* #e ! #email `as` #userEmail)
( from (table (#users `as` #u)
& innerJoin (table (#emails `as` #e))
(#u ! #id .== #e ! #user_id)) )
:} >>> printSQL getUsers SELECT "u"."name" AS "userName", "e"."email" AS "userEmail" FROM "users" AS "u" INNER JOIN "emails" AS "e" ON ("u"."id" = "e"."user_id") Let's create some users to add to the database. >>> :{
let
users :: [User]
users =
[ User "Alice" (Just "[email protected]")
, User "Bob" Nothing
, User "Carole" (Just "[email protected]")
]
:} Now we can put together all the pieces into a program. The program
connects to the database, sets up the schema, inserts the user data
(using prepared statements as an optimization), queries the user
data and prints it out and finally closes the connection. We can thread
the changing schema information through by using the indexed >>> :{
let
session :: PQ DB DB IO ()
session = do
executePrepared_ insertUser users
usersResult <- execute getUsers
usersRows <- getRows usersResult
liftIO $ print usersRows
in
withConnection "host=localhost port=5432 dbname=exampledb user=postgres password=postgres" $
define setup
& pqThen session
& pqThen (define teardown)
:}
[User {userName = "Alice", userEmail = Just "[email protected]"},User {userName = "Bob", userEmail = Nothing},User {userName = "Carole", userEmail = Just "[email protected]"}] This should get you up and running with Squeal. Once you're writing more complicated queries and need a deeper understanding of Squeal's types and how everything fits together, check out the Core Concepts Handbook. |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论