git.iko.soy/rowtype-yoga/purescript-yoga-postgres
on main help sign in
commit77c18476d5f8a63145d9333b2e3f7d22867609e3 authorMark <mark.eibes@gmail.com> dateFri 13 Feb 2026, 14:37 (8 months ago) parent44673e3
2 files changed+63
src/Yoga/Postgres/Schema.purs+33 test/Schema.purs+30
src/Yoga/Postgres/Schema.purs+33
@@ -9,6 +9,7 @@ import Data.Symbol (class IsSymbol, reflectSymbol)
99import Data.Tuple.Nested (type (/\))
1010import Prim.RowList as RL
1111import Prim.RowList (class RowToList)
12+import Prim.Symbol (class Cons, class Append) as Symbol
1213import Type.Proxy (Proxy(..))
1314
1415-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -309,3 +310,35 @@ instance
309310 let tableName = reflectSymbol (Proxy :: Proxy name)
310311 let conditions = whereClauseRL (Proxy :: Proxy whereRL) 1
311312 "DELETE FROM " <> tableName <> " WHERE " <> intercalate " AND " conditions
313+
314+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
315+-- Builder-style query API
316+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
317+
318+newtype Q :: Symbol -> Row Type -> Type
319+newtype Q name cols = Q String
320+
321+from :: forall name cols. Proxy (Table name cols) -> Q name cols
322+from _ = Q ""
323+
324+selectAll :: forall name cols. IsSymbol name => Q name cols -> Q name cols
325+selectAll _ = Q ("SELECT * FROM " <> reflectSymbol (Proxy :: Proxy name))
326+
327+select
328+ :: forall @sel name cols
329+ . IsSymbol name
330+ => IsSymbol sel
331+ => Q name cols
332+ -> Q name cols
333+select _ = Q ("SELECT " <> reflectSymbol (Proxy :: Proxy sel) <> " FROM " <> reflectSymbol (Proxy :: Proxy name))
334+
335+where_
336+ :: forall @whr whrRL name cols
337+ . RowToList whr whrRL
338+ => WhereClauseRL whrRL
339+ => Q name cols
340+ -> Q name cols
341+where_ (Q base) = Q (base <> " WHERE " <> intercalate " AND " (whereClauseRL (Proxy :: Proxy whrRL) 1))
342+
343+toSQL :: forall name cols. Q name cols -> String
344+toSQL (Q s) = s
test/Schema.purs+30
@@ -8,6 +8,7 @@ import Data.Tuple.Nested (type (/\))
88import Prim.Boolean (True)
99import Test.Spec (Spec, describe, it)
1010import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
11+import Type.Proxy (Proxy(..))
1112import Yoga.Postgres.Schema
1213
1314-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -80,6 +81,25 @@ updateSQL = updateSQLFor @UsersTable @(name :: String) @(id :: Int)
8081deleteSQL :: String
8182deleteSQL = deleteSQLFor @UsersTable @(id :: Int)
8283
84+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
85+-- Phase 8: Builder-style query API
86+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
87+
88+usersTable :: Proxy UsersTable
89+usersTable = Proxy
90+
91+builderSelectAll :: String
92+builderSelectAll = from usersTable # selectAll # toSQL
93+
94+builderSelectCols :: String
95+builderSelectCols = from usersTable # select @"name, email" # toSQL
96+
97+builderSelectWhere :: String
98+builderSelectWhere = from usersTable # selectAll # where_ @(id :: Int) # toSQL
99+
100+builderSelectColsWhere :: String
101+builderSelectColsWhere = from usersTable # select @"name" # where_ @(age :: Int) # toSQL
102+
83103-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
84104-- Spec
85105-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -123,3 +143,13 @@ spec = do
123143 describe "DELETE SQL" do
124144 it "generates DELETE with WHERE" do
125145 deleteSQL `shouldEqual` "DELETE FROM users WHERE id = $1"
146+
147+ describe "Builder API" do
148+ it "builds SELECT *" do
149+ builderSelectAll `shouldEqual` "SELECT * FROM users"
150+ it "builds SELECT with columns" do
151+ builderSelectCols `shouldEqual` "SELECT name, email FROM users"
152+ it "builds SELECT * with WHERE" do
153+ builderSelectWhere `shouldEqual` "SELECT * FROM users WHERE id = $1"
154+ it "builds SELECT columns with WHERE" do
155+ builderSelectColsWhere `shouldEqual` "SELECT name FROM users WHERE age = $1"