@@ -1073,6 +1073,163 @@ set rec _ = Q { sql, values }
10731073 sql = "UPDATE " <> tableName <> " SET " <> intercalate ", " setClauses
10741074 values = recordValuesRL (Proxy :: Proxy setRL) rec
10751075
1076+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1077+-- DELETE builder
1078+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1079+
1080+delete
1081+ :: forall name cols r p
1082+ . IsSymbol name
1083+ => Q name cols r p
1084+ -> Q name cols () ()
1085+delete _ = Q { sql: "DELETE FROM " <> reflectSymbol (Proxy :: Proxy name), values: [] }
1086+
1087+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1088+-- ORDER BY / LIMIT / OFFSET
1089+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1090+
1091+orderBy
1092+ :: forall @cols name tableCols result params
1093+ . IsSymbol cols
1094+ => ValidateOrderBy cols tableCols
1095+ => Q name tableCols result params
1096+ -> Q name tableCols result params
1097+orderBy (Q q) = Q (q { sql = q.sql <> " ORDER BY " <> reflectSymbol (Proxy :: Proxy cols) })
1098+
1099+limit
1100+ :: forall name cols result params
1101+ . Int
1102+ -> Q name cols result params
1103+ -> Q name cols result params
1104+limit n (Q q) = Q (q { sql = q.sql <> " LIMIT " <> show n })
1105+
1106+offset
1107+ :: forall name cols result params
1108+ . Int
1109+ -> Q name cols result params
1110+ -> Q name cols result params
1111+offset n (Q q) = Q (q { sql = q.sql <> " OFFSET " <> show n })
1112+
1113+-- Validate ORDER BY columns: "name, age DESC" → validate name, age exist
1114+class ValidateOrderBy :: Symbol -> Row Type -> Constraint
1115+class ValidateOrderBy sym cols
1116+
1117+instance ValidateOrderBy "" cols
1118+else instance
1119+ ( Symbol.Cons h t sym
1120+ , ValidateOrderByGo h t "" cols
1121+ ) =>
1122+ ValidateOrderBy sym cols
1123+
1124+class ValidateOrderByGo :: Symbol -> Symbol -> Symbol -> Row Type -> Constraint
1125+class ValidateOrderByGo head tail acc cols
1126+
1127+-- Comma: flush column, continue
1128+instance
1129+ ( FlushOrderByWord acc cols
1130+ , SkipSpaces tail rest
1131+ , ValidateOrderBy rest cols
1132+ ) =>
1133+ ValidateOrderByGo "," tail acc cols
1134+
1135+-- Space: flush column, skip modifiers (ASC/DESC/NULLS FIRST/NULLS LAST)
1136+else instance
1137+ ( SkipSpaces tail rest
1138+ , FlushOrderByThenSkipModifiers acc rest cols
1139+ ) =>
1140+ ValidateOrderByGo " " tail acc cols
1141+
1142+-- End of string: flush final column
1143+else instance
1144+ ( Symbol.Append acc h acc'
1145+ , FlushOrderByWord acc' cols
1146+ ) =>
1147+ ValidateOrderByGo h "" acc cols
1148+
1149+-- Regular char: accumulate
1150+else instance
1151+ ( Symbol.Append acc h acc'
1152+ , Symbol.Cons nextH nextT tail
1153+ , ValidateOrderByGo nextH nextT acc' cols
1154+ ) =>
1155+ ValidateOrderByGo h tail acc cols
1156+
1157+class FlushOrderByWord :: Symbol -> Row Type -> Constraint
1158+class FlushOrderByWord word cols
1159+
1160+instance FlushOrderByWord "" cols
1161+else instance FlushOrderByWord "ASC" cols
1162+else instance FlushOrderByWord "asc" cols
1163+else instance FlushOrderByWord "DESC" cols
1164+else instance FlushOrderByWord "desc" cols
1165+else instance FlushOrderByWord "NULLS" cols
1166+else instance FlushOrderByWord "FIRST" cols
1167+else instance FlushOrderByWord "LAST" cols
1168+else instance Row.Cons word (Column typ constraints) rest cols => FlushOrderByWord word cols
1169+
1170+class FlushOrderByThenSkipModifiers :: Symbol -> Symbol -> Row Type -> Constraint
1171+class FlushOrderByThenSkipModifiers colName rest cols
1172+
1173+-- End of input
1174+instance FlushOrderByWord colName cols => FlushOrderByThenSkipModifiers colName "" cols
1175+
1176+-- Non-empty: branch on first char
1177+else instance
1178+ ( FlushOrderByWord colName cols
1179+ , Symbol.Cons h t rest
1180+ , FlushOrderByThenSkipModifiersByHead h t cols
1181+ ) =>
1182+ FlushOrderByThenSkipModifiers colName rest cols
1183+
1184+class FlushOrderByThenSkipModifiersByHead :: Symbol -> Symbol -> Row Type -> Constraint
1185+class FlushOrderByThenSkipModifiersByHead head tail cols
1186+
1187+-- Comma: continue with next column
1188+instance
1189+ ( SkipSpaces tail rest
1190+ , ValidateOrderBy rest cols
1191+ ) =>
1192+ FlushOrderByThenSkipModifiersByHead "," tail cols
1193+
1194+-- Other: word is a modifier (ASC/DESC/etc), keep consuming
1195+else instance
1196+ ( Symbol.Append h t rest
1197+ , ExtractWord rest word afterWord
1198+ , FlushOrderByWord word cols
1199+ , SkipSpaces afterWord rest'
1200+ , ValidateOrderByContinue rest' cols
1201+ ) =>
1202+ FlushOrderByThenSkipModifiersByHead h t cols
1203+
1204+class ValidateOrderByContinue :: Symbol -> Row Type -> Constraint
1205+class ValidateOrderByContinue sym cols
1206+
1207+instance ValidateOrderByContinue "" cols
1208+else instance
1209+ ( Symbol.Cons h t sym
1210+ , ValidateOrderByContinueByHead h t cols
1211+ ) =>
1212+ ValidateOrderByContinue sym cols
1213+
1214+class ValidateOrderByContinueByHead :: Symbol -> Symbol -> Row Type -> Constraint
1215+class ValidateOrderByContinueByHead head tail cols
1216+
1217+instance
1218+ ( SkipSpaces tail rest
1219+ , ValidateOrderBy rest cols
1220+ ) =>
1221+ ValidateOrderByContinueByHead "," tail cols
1222+
1223+-- More modifiers (e.g. "NULLS FIRST" after "DESC")
1224+else instance
1225+ ( Symbol.Append h t rest
1226+ , ExtractWord rest word afterWord
1227+ , FlushOrderByWord word cols
1228+ , SkipSpaces afterWord rest'
1229+ , ValidateOrderByContinue rest' cols
1230+ ) =>
1231+ ValidateOrderByContinueByHead h t cols
1232+
10761233-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
10771234-- ON CONFLICT
10781235-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━