git.iko.soy/rowtype-yoga/purescript-yoga-postgres
on main help sign in
commit2d3a05b16abf92060ff0f33eccd4051c8f150f6b authorMark <mark.eibes@gmail.com> dateSat 14 Feb 2026, 00:31 (8 months ago) parent966c499
4 files changed+32−125
compile-fail-tests/WhereWithoutDML.purs+20 spago.lock−115 src/Yoga/Postgres/Schema.purs+10−4 test/EdgeCases.purs+2−6
compile-fail-tests/WhereWithoutDML.pursnew file+20
@@ -0,0 +1,20 @@
1+-- EXPECT: WHERE requires a preceding SELECT, UPDATE (set), or DELETE
2+module Test.CompileFail.WhereWithoutDML where
3+
4+import Prelude
5+import Data.Maybe (Maybe(..))
6+import Type.Function (type (#))
7+import Type.Proxy (Proxy(..))
8+import Yoga.Postgres.Schema
9+
10+type UsersTable = Table "users"
11+ ( id :: Int # PrimaryKey # AutoIncrement
12+ , name :: String
13+ , email :: String # Unique
14+ , age :: Maybe Int
15+ )
16+
17+usersTable :: Proxy UsersTable
18+usersTable = Proxy
19+
20+bad = from usersTable # where_ @"id = $id"
spago.lock−115
@@ -56,66 +56,6 @@
5656 {
5757 "yoga-sql-types": ">=0.1.0 <0.2.0"
5858 }
59− ],
60− "build_plan": [
61− "aff",
62− "arrays",
63− "bifunctors",
64− "catenable-lists",
65− "const",
66− "contravariant",
67− "control",
68− "datetime",
69− "distributive",
70− "effect",
71− "either",
72− "enums",
73− "exceptions",
74− "exists",
75− "foldable-traversable",
76− "foreign",
77− "foreign-object",
78− "free",
79− "functions",
80− "functors",
81− "gen",
82− "heterogeneous",
83− "identity",
84− "integers",
85− "invariant",
86− "js-bigints",
87− "js-date",
88− "js-promise",
89− "js-promise-aff",
90− "lazy",
91− "lists",
92− "maybe",
93− "newtype",
94− "nonempty",
95− "nullable",
96− "numbers",
97− "ordered-collections",
98− "orders",
99− "parallel",
100− "partial",
101− "prelude",
102− "profunctor",
103− "record",
104− "refs",
105− "safe-coerce",
106− "st",
107− "strings",
108− "tailrec",
109− "transformers",
110− "tuples",
111− "type-equality",
112− "typelevel-prelude",
113− "unfoldable",
114− "unsafe-coerce",
115− "variant",
116− "yoga-json",
117− "yoga-sql-types",
118− "yoga-tree"
11959 ]
12060 },
12161 "test": {
@@ -135,61 +75,6 @@
13575 {
13676 "yoga-test-docker": ">=0.1.0 <1.0.0"
13777 }
138− ],
139− "build_plan": [
140− "aff",
141− "ansi",
142− "arrays",
143− "avar",
144− "bifunctors",
145− "catenable-lists",
146− "console",
147− "const",
148− "contravariant",
149− "control",
150− "datetime",
151− "distributive",
152− "effect",
153− "either",
154− "enums",
155− "exceptions",
156− "exists",
157− "foldable-traversable",
158− "fork",
159− "free",
160− "functions",
161− "functors",
162− "gen",
163− "identity",
164− "integers",
165− "invariant",
166− "lazy",
167− "lists",
168− "maybe",
169− "mmorph",
170− "newtype",
171− "nonempty",
172− "now",
173− "numbers",
174− "ordered-collections",
175− "orders",
176− "parallel",
177− "partial",
178− "pipes",
179− "prelude",
180− "profunctor",
181− "refs",
182− "safe-coerce",
183− "spec",
184− "st",
185− "strings",
186− "tailrec",
187− "transformers",
188− "tuples",
189− "type-equality",
190− "unfoldable",
191− "unsafe-coerce",
192− "yoga-test-docker"
19378 ]
19479 }
19580 }
src/Yoga/Postgres/Schema.purs+10−4
@@ -1770,10 +1770,16 @@ instance Row.Cons label Unit rest row => HasClause label row
17701770class HasAnyDML :: Row Type -> Constraint
17711771class HasAnyDML stage
17721772
1773−instance Row.Cons "select" Unit rest stage => HasAnyDML stage
1774−else instance Row.Cons "set" Unit rest stage => HasAnyDML stage
1775−else instance Row.Cons "delete" Unit rest stage => HasAnyDML stage
1776−else instance Fail (Text "WHERE requires a preceding SELECT, UPDATE (set), or DELETE") => HasAnyDML stage
1773+instance (RL.RowToList stage rl, HasAnyDMLRL rl) => HasAnyDML stage
1774+
1775+class HasAnyDMLRL :: RL.RowList Type -> Constraint
1776+class HasAnyDMLRL rl
1777+
1778+instance HasAnyDMLRL (RL.Cons "select" Unit rest)
1779+else instance HasAnyDMLRL (RL.Cons "set" Unit rest)
1780+else instance HasAnyDMLRL (RL.Cons "delete" Unit rest)
1781+else instance HasAnyDMLRL rest => HasAnyDMLRL (RL.Cons label typ rest)
1782+else instance Fail (Text "WHERE requires a preceding SELECT, UPDATE (set), or DELETE") => HasAnyDMLRL RL.Nil
17771783
17781784toSQL :: forall tables result params stage. Q tables result params stage -> String
17791785toSQL (Q q) = q.sql
test/EdgeCases.purs+2−6
@@ -42,14 +42,10 @@ havingMergesWhereParams = from usersTable
4242 # having @"COUNT(*) > $minCount"
4343
4444-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
45−-- EDGE CASE 2: WHERE without any DML operation
46−-- from table # where_ compiles, producing invalid SQL like
47−-- "users WHERE id = $id" (missing SELECT/DELETE/UPDATE)
45+-- EDGE CASE 2: WHERE without any DML operation (FIXED)
46+-- from table # where_ now fails to compile (see WhereWithoutDML.purs)
4847-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4948
50−whereWithoutOperation :: Q _ _ (id :: Int) _
51−whereWithoutOperation = from usersTable # where_ @"id = $id"
52−
5349-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
5450-- EDGE CASE 3: GROUP BY doesn't enforce aggregation rules
5551-- Non-aggregated, non-grouped columns in SELECT should be