git.iko.soy/rowtype-yoga/purescript-yoga-postgres
on main help sign in
commit9a2f17f0dcaa11324b067a3d00e753b3e98233c7 authorMark <mark.eibes@gmail.com> dateFri 13 Feb 2026, 14:04 (8 months ago) parent18fc598
2 files changed+71−10
src/Yoga/Postgres/Schema.purs+59−1 test/Schema.purs+12−9
src/Yoga/Postgres/Schema.purs+59−1
@@ -2,7 +2,7 @@ module Yoga.Postgres.Schema where
22
33import Prelude
44
5−import Data.Array (intercalate)
5+import Data.Array (intercalate, length, mapWithIndex)
66
77import Data.Maybe (Maybe)
88import Data.Symbol (class IsSymbol, reflectSymbol)
@@ -139,3 +139,61 @@ instance
139139 let tableName = reflectSymbol (Proxy :: Proxy name)
140140 let columns = renderColumnsRL (Proxy :: Proxy rl)
141141 "CREATE TABLE " <> tableName <> " (" <> intercalate ", " columns <> ")"
142+
143+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
144+-- INSERT SQL generation
145+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
146+
147+class IsAutoGenerated a where
148+ isAutoGenerated :: Proxy a -> Boolean
149+
150+instance IsAutoGenerated AutoIncrement where
151+ isAutoGenerated _ = true
152+else instance IsAutoGenerated (Default a) where
153+ isAutoGenerated _ = true
154+else instance (IsAutoGenerated a, IsAutoGenerated b) => IsAutoGenerated (a /\ b) where
155+ isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a) || isAutoGenerated (Proxy :: Proxy b)
156+else instance IsAutoGenerated a where
157+ isAutoGenerated _ = false
158+
159+class InsertColumnsRL :: RL.RowList Type -> Constraint
160+class InsertColumnsRL rl where
161+ insertColumnsRL :: Proxy rl -> Array String
162+
163+instance InsertColumnsRL RL.Nil where
164+ insertColumnsRL _ = []
165+
166+instance
167+ ( IsSymbol name
168+ , IsAutoGenerated constraints
169+ , InsertColumnsRL tail
170+ ) =>
171+ InsertColumnsRL (RL.Cons name (Column typ constraints) tail) where
172+ insertColumnsRL _ =
173+ let
174+ rest = insertColumnsRL (Proxy :: Proxy tail)
175+ in
176+ if isAutoGenerated (Proxy :: Proxy constraints) then rest
177+ else [ reflectSymbol (Proxy :: Proxy name) ] <> rest
178+
179+class InsertSQLFor a where
180+ insertSQLFor :: String
181+
182+instance
183+ ( IsSymbol name
184+ , RowToList cols rl
185+ , InsertColumnsRL rl
186+ ) =>
187+ InsertSQLFor (Table name cols) where
188+ insertSQLFor = do
189+ let tableName = reflectSymbol (Proxy :: Proxy name)
190+ let cols = insertColumnsRL (Proxy :: Proxy rl)
191+ let placeholders = cols # mapWithIndex \i _ -> "$" <> show (i + 1)
192+ "INSERT INTO " <> tableName
193+ <> " ("
194+ <> intercalate ", " cols
195+ <> ")"
196+ <> " VALUES ("
197+ <> intercalate ", " placeholders
198+ <> ")"
199+ <> " RETURNING *"
test/Schema.purs+12−9
@@ -14,7 +14,7 @@ import Yoga.Postgres.Schema
1414-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1515
1616type UsersTable = Table "users"
17− ( id :: Column Int PrimaryKey
17+ ( id :: Column Int (PrimaryKey /\ AutoIncrement)
1818 , name :: Column String None
1919 , email :: Column String Unique
2020 , age :: Column (Maybe Int) None
@@ -34,13 +34,9 @@ ddl = createTableDDL @UsersTable
3434-- Insert should require non-PK, non-default columns
3535-- PK columns (id) are omitted
3636-- Maybe columns (age) become optional
37−-- Returns all columns via RETURNING *
3837
39−-- insertResult :: PG.Connection -> Aff (Either String { id :: Int, name :: String, email :: String, age :: Maybe Int })
40−-- insertResult conn = insert @UsersTable { name: "Alice", email: "alice@example.com", age: Nothing } conn
41−
42−-- insertWithoutOptional :: PG.Connection -> Aff (Either String { id :: Int, name :: String, email :: String, age :: Maybe Int })
43−-- insertWithoutOptional conn = insert @UsersTable { name: "Bob", email: "bob@example.com" } conn
38+insertSQL :: String
39+insertSQL = insertSQLFor @UsersTable
4440
4541-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4642-- Phase 4: Type-safe SELECT
@@ -95,7 +91,14 @@ spec = do
9591 describe "CREATE TABLE DDL" do
9692 it "generates correct DDL for UsersTable" do
9793 ddl `shouldSatisfy` contains (Pattern "CREATE TABLE users")
98− ddl `shouldSatisfy` contains (Pattern "id INTEGER NOT NULL PRIMARY KEY")
94+ ddl `shouldSatisfy` contains (Pattern "id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY")
9995 ddl `shouldSatisfy` contains (Pattern "name TEXT NOT NULL")
10096 ddl `shouldSatisfy` contains (Pattern "email TEXT NOT NULL UNIQUE")
101− ddl `shouldSatisfy` contains (Pattern "age INTEGER")
97+ ddl `shouldSatisfy` contains (Pattern "age INTEGER,")
98+
99+ describe "INSERT SQL" do
100+ it "generates INSERT skipping PrimaryKey columns" do
101+ insertSQL `shouldSatisfy` contains (Pattern "INSERT INTO users")
102+ insertSQL `shouldSatisfy` contains (Pattern "(age, email, name)")
103+ insertSQL `shouldSatisfy` contains (Pattern "VALUES ($1, $2, $3)")
104+ insertSQL `shouldSatisfy` contains (Pattern "RETURNING *")