@@ -1,3780 +0,0 @@
1−module Yoga.Postgres.Schema where
2−
3−import Prelude
4−
5−import Control.Monad.Except (except, runExcept)
6−import Data.Array (intercalate, mapWithIndex, foldl)
7−import Data.Array as Array
8−import Data.Date (Date, day, month, year)
9−import Data.DateTime (DateTime(..))
10−import Data.Either (Either(..), note)
11−import Data.Enum (fromEnum, toEnum)
12−import Data.Int as Int
13−import Data.JSDate as JSDate
14−import Data.List.Types (NonEmptyList)
15−import Data.Map as Map
16−import Data.Maybe (Maybe(..), fromMaybe)
17−import Data.Newtype (class Newtype)
18−import Data.Nullable (toNullable)
19−import Data.Number as Number
20−import Data.Reflectable (class Reflectable, reflectType)
21−import Data.String.CodeUnits as SCU
22−import Data.String.Common as Str
23−import Data.String.Pattern (Pattern(..))
24−import Data.String.Regex (Regex, regex, replace') as Regex
25−import Data.String.Regex.Flags (global) as Regex
26−import Data.Symbol (class IsSymbol, reflectSymbol)
27−import Data.Time (Time(..))
28−import Data.Traversable (traverse)
29−import Data.UUID (UUID)
30−import Data.UUID as UUID
31−import Effect.Aff (Aff, throwError)
32−import Effect.Exception (error) as Exception
33−import Foreign (Foreign, ForeignError(..))
34−import JS.BigInt (BigInt)
35−import Prim.Boolean (True, False)
36−import Prim.Row (class Cons, class Lacks, class Union, class Nub) as Row
37−import Prim.RowList (class RowToList)
38−import Prim.RowList as RL
39−import Prim.Symbol (class Cons, class Append) as Symbol
40−import Prim.TypeError (class Fail, Beside, Text, Quote)
41−import Record (get) as Record
42−import Type.Proxy (Proxy(..))
43−import Type.RowList (class ListToRow)
44−import Unsafe.Coerce (unsafeCoerce)
45−import Yoga.JSON (class ReadForeign, class WriteForeign, readImpl, writeImpl)
46−import Yoga.Postgres as PG
47−
48−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
49−-- Core phantom types
50−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
51−
52−data Table :: Symbol -> Row Type -> Type
53−data Table name columns = Table
54−
55−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
56−-- Constraint wrappers (used with # operator from Type.Function)
57−-- Int # PrimaryKey # AutoIncrement = AutoIncrement (PrimaryKey Int)
58−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
59−
60−data PrimaryKey :: Type -> Type
61−data PrimaryKey a
62−
63−data AutoIncrement :: Type -> Type
64−data AutoIncrement a
65−
66−data Unique :: Type -> Type
67−data Unique a
68−
69−data Default :: forall k. k -> Type -> Type
70−data Default val a
71−data DefaultExpr :: Symbol -> Type -> Type
72−data DefaultExpr expression a
73−
74−
75−data References
76−
77−data ForeignKey :: Symbol -> Type -> Symbol -> Type -> Type
78−data ForeignKey table references col a
79−
80−-- Internal: used by LEFT JOIN to mark columns as nullable
81−data Nullable :: Type -> Type
82−data Nullable a
83−
84−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
85−-- JSONB type wrapper
86−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
87−
88−newtype Jsonb = Jsonb Foreign
89−
90−derive instance Newtype Jsonb _
91−
92−instance ReadForeign Jsonb where
93− readImpl = pure <<< Jsonb
94−
95−instance WriteForeign Jsonb where
96− writeImpl (Jsonb value) = value
97−
98−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
99−-- PGUUID: newtype over Data.UUID.UUID for Postgres integration
100−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
101−
102−newtype PGUUID = PGUUID UUID
103−
104−derive instance Newtype PGUUID _
105−derive newtype instance Eq PGUUID
106−derive newtype instance Ord PGUUID
107−derive newtype instance Show PGUUID
108−
109−instance ReadForeign PGUUID where
110− readImpl f = do
111− s :: String <- readImpl f
112− pure (unsafeCoerce s)
113−
114−instance WriteForeign PGUUID where
115− writeImpl (PGUUID uuid) = writeImpl (UUID.toString uuid)
116−
117−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
118−-- PGDate / PGTime: newtypes for DATE and TIME columns
119−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
120−
121−newtype PGDate = PGDate Date
122−
123−derive instance Newtype PGDate _
124−derive instance Eq PGDate
125−derive instance Ord PGDate
126−instance Show PGDate where
127− show (PGDate d) = "(PGDate " <> show d <> ")"
128−
129−instance ReadForeign PGDate where
130− readImpl f =
131− except $ note (pure (ForeignError "Invalid date")) (JSDate.toDate jsDate <#> PGDate)
132− where
133− jsDate :: JSDate.JSDate
134− jsDate = unsafeCoerce f
135−
136−instance WriteForeign PGDate where
137− writeImpl (PGDate date) =
138− writeImpl
139− ( show (fromEnum (year date))
140− <> "-"
141− <> pad2 (fromEnum (month date))
142− <> "-"
143− <> pad2 (fromEnum (day date))
144− )
145− where
146− pad2 n = if n < 10 then "0" <> show n else show n
147−
148−newtype TimestampTZ = TimestampTZ DateTime
149−
150−derive instance Newtype TimestampTZ _
151−derive instance Eq TimestampTZ
152−derive instance Ord TimestampTZ
153−instance Show TimestampTZ where
154− show (TimestampTZ dt) = "(TimestampTZ " <> show dt <> ")"
155−
156−instance ReadForeign TimestampTZ where
157− readImpl f =
158− except $ note (pure (ForeignError "Invalid timestamp")) (JSDate.toDateTime jsDate <#> TimestampTZ)
159− where
160− jsDate :: JSDate.JSDate
161− jsDate = unsafeCoerce f
162−
163−newtype PGTime = PGTime Time
164−
165−derive instance Newtype PGTime _
166−derive instance Eq PGTime
167−derive instance Ord PGTime
168−instance Show PGTime where
169− show (PGTime t) = "(PGTime " <> show t <> ")"
170−
171−instance ReadForeign PGTime where
172− readImpl f = do
173− s :: String <- readImpl f
174− except $ note (pure (ForeignError ("Invalid time: " <> s))) (parseTime s)
175− where
176− parseTime str = do
177− let parts = Str.split (Pattern ":") str
178− case parts of
179− [ hStr, mStr, sStr ] -> do
180− h <- Int.fromString hStr >>= toEnum
181− mi <- Int.fromString mStr >>= toEnum
182− let secParts = Str.split (Pattern ".") sStr
183− s <- Int.fromString (fromMaybe sStr (Array.head secParts)) >>= toEnum
184− let fraction = fromMaybe "" (Array.index secParts 1)
185− let milliseconds = case SCU.length fraction of
186− 0 -> "0"
187− 1 -> fraction <> "00"
188− 2 -> fraction <> "0"
189− _ -> SCU.take 3 fraction
190− let ms = fromMaybe bottom (Int.fromString milliseconds >>= toEnum)
191− pure (PGTime (Time h mi s ms))
192− _ -> Nothing
193−
194−instance WriteForeign PGTime where
195− writeImpl (PGTime (Time hour minute second millisecond)) =
196− writeImpl
197− ( pad2 (fromEnum hour)
198− <> ":"
199− <> pad2 (fromEnum minute)
200− <> ":"
201− <> pad2 (fromEnum second)
202− <> "."
203− <> pad3 (fromEnum millisecond)
204− )
205− where
206− pad2 n = if n < 10 then "0" <> show n else show n
207− pad3 n
208− | n < 10 = "00" <> show n
209− | n < 100 = "0" <> show n
210− | otherwise = show n
211−
212−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
213−-- Point: geometric point "(x,y)"
214−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
215−
216−newtype Point = Point { x :: Number, y :: Number }
217−
218−derive instance Newtype Point _
219−derive instance Eq Point
220−
221−instance Show Point where
222− show (Point { x, y }) = "(Point " <> show x <> " " <> show y <> ")"
223−
224−instance ReadForeign Point where
225− readImpl f = do
226− s :: String <- readImpl f
227− except $ note (pure (ForeignError ("Invalid point: " <> s))) (parsePoint s)
228− where
229− parsePoint str = do
230− let inner = SCU.drop 1 (SCU.dropRight 1 str)
231− let parts = Str.split (Pattern ",") inner
232− case parts of
233− [ xStr, yStr ] -> do
234− x <- Number.fromString xStr
235− y <- Number.fromString yStr
236− pure (Point { x, y })
237− _ -> Nothing
238−
239−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
240−-- TSVector / TSQuery: full-text search types
241−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
242−
243−newtype TSVector = TSVector String
244−
245−derive instance Newtype TSVector _
246−derive newtype instance Eq TSVector
247−derive newtype instance Ord TSVector
248−derive newtype instance Show TSVector
249−
250−instance ReadForeign TSVector where
251− readImpl f = do
252− s :: String <- readImpl f
253− pure (TSVector s)
254−
255−newtype TSQuery = TSQuery String
256−
257−derive instance Newtype TSQuery _
258−derive newtype instance Eq TSQuery
259−derive newtype instance Ord TSQuery
260−derive newtype instance Show TSQuery
261−
262−instance ReadForeign TSQuery where
263− readImpl f = do
264− s :: String <- readImpl f
265− pure (TSQuery s)
266−
267−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
268−-- Nullability: inferred from Maybe
269−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
270−
271−class IsNullable a where
272− isNullable :: Proxy a -> Boolean
273−
274−instance IsNullable (Maybe a) where
275− isNullable _ = true
276−else instance IsNullable a where
277− isNullable _ = false
278−
279−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
280−-- ExtractType: recursively unwrap constraint wrappers to get base type
281−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
282−
283−class ExtractType :: Type -> Type -> Constraint
284−class ExtractType wrapped typ | wrapped -> typ
285−
286−instance ExtractType a typ => ExtractType (PrimaryKey a) typ
287−else instance ExtractType a typ => ExtractType (AutoIncrement a) typ
288−else instance ExtractType a typ => ExtractType (Unique a) typ
289−else instance ExtractType a typ => ExtractType (Default val a) typ
290−else instance ExtractType a typ => ExtractType (DefaultExpr expression a) typ
291−else instance ExtractType a typ => ExtractType (ForeignKey t r c a) typ
292−else instance ExtractType a typ => ExtractType (Nullable a) (Maybe typ)
293−else instance ExtractType a a
294−
295−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
296−-- PureScript type -> Postgres type name
297−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
298−
299−class PGTypeName a where
300− pgTypeName :: Proxy a -> String
301−
302−instance PGTypeName Int where
303− pgTypeName _ = "INTEGER"
304−
305−instance PGTypeName String where
306− pgTypeName _ = "TEXT"
307−
308−instance PGTypeName Boolean where
309− pgTypeName _ = "BOOLEAN"
310−
311−instance PGTypeName Number where
312− pgTypeName _ = "DOUBLE PRECISION"
313−
314−instance PGTypeName DateTime where
315− pgTypeName _ = "TIMESTAMPTZ"
316−
317−instance PGTypeName BigInt where
318− pgTypeName _ = "BIGINT"
319−
320−instance PGTypeName PGUUID where
321− pgTypeName _ = "UUID"
322−
323−instance PGTypeName TimestampTZ where
324− pgTypeName _ = "TIMESTAMPTZ"
325−
326−instance PGTypeName PGDate where
327− pgTypeName _ = "DATE"
328−
329−instance PGTypeName PGTime where
330− pgTypeName _ = "TIME"
331−
332−instance PGTypeName Point where
333− pgTypeName _ = "POINT"
334−
335−instance PGTypeName TSVector where
336− pgTypeName _ = "TSVECTOR"
337−
338−instance PGTypeName TSQuery where
339− pgTypeName _ = "TSQUERY"
340−
341−instance PGTypeName Jsonb where
342− pgTypeName _ = "JSONB"
343−
344−instance PGTypeName a => PGTypeName (Array a) where
345− pgTypeName _ = pgTypeName (Proxy :: Proxy a) <> "[]"
346−
347−instance PGTypeName a => PGTypeName (Maybe a) where
348− pgTypeName _ = pgTypeName (Proxy :: Proxy a)
349−
350−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
351−-- Constraint -> DDL fragment
352−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
353−
354−class RenderDefaultValue val typ where
355− renderDefaultValue :: Proxy val -> Proxy typ -> String
356−
357−instance Reflectable sym String => RenderDefaultValue sym String where
358− renderDefaultValue _ _ = "DEFAULT '" <> reflectType (Proxy :: Proxy sym) <> "'"
359−
360−instance Reflectable val Int => RenderDefaultValue val Int where
361− renderDefaultValue _ _ = "DEFAULT " <> show (reflectType (Proxy :: Proxy val))
362−
363−instance RenderDefaultValue True Boolean where
364− renderDefaultValue _ _ = "DEFAULT true"
365−
366−instance RenderDefaultValue False Boolean where
367− renderDefaultValue _ _ = "DEFAULT false"
368−
369−instance RenderDefaultValue "true" Boolean where
370− renderDefaultValue _ _ = "DEFAULT true"
371−
372−instance RenderDefaultValue "false" Boolean where
373− renderDefaultValue _ _ = "DEFAULT false"
374−
375−instance RenderDefaultValue "now()" TimestampTZ where
376− renderDefaultValue _ _ = "DEFAULT now()"
377−
378−instance RenderDefaultValue "gen_random_uuid()" PGUUID where
379− renderDefaultValue _ _ = "DEFAULT gen_random_uuid()"
380−
381−
382−class RenderConstraint a where
383− renderConstraint :: Proxy a -> String
384−
385−instance RenderConstraint a => RenderConstraint (PrimaryKey a) where
386− renderConstraint _ = joinConstraints "PRIMARY KEY" (renderConstraint (Proxy :: Proxy a))
387−
388−else instance RenderConstraint a => RenderConstraint (AutoIncrement a) where
389− renderConstraint _ = joinConstraints "GENERATED ALWAYS AS IDENTITY" (renderConstraint (Proxy :: Proxy a))
390−
391−else instance RenderConstraint a => RenderConstraint (Unique a) where
392− renderConstraint _ = joinConstraints "UNIQUE" (renderConstraint (Proxy :: Proxy a))
393−
394−else instance (RenderDefaultValue val a, RenderConstraint a) => RenderConstraint (Default val a) where
395− renderConstraint _ = joinConstraints (renderDefaultValue (Proxy :: Proxy val) (Proxy :: Proxy a)) (renderConstraint (Proxy :: Proxy a))
396−
397−else instance (IsSymbol expression, RenderConstraint a) => RenderConstraint (DefaultExpr expression a) where
398− renderConstraint _ = joinConstraints
399− ("DEFAULT " <> reflectSymbol (Proxy :: Proxy expression))
400− (renderConstraint (Proxy :: Proxy a))
401−else instance (IsSymbol table, IsSymbol col, RenderConstraint a) => RenderConstraint (ForeignKey table References col a) where
402− renderConstraint _ = joinConstraints
403− ("REFERENCES " <> reflectSymbol (Proxy :: Proxy table) <> "(" <> reflectSymbol (Proxy :: Proxy col) <> ")")
404− (renderConstraint (Proxy :: Proxy a))
405−
406−else instance RenderConstraint a => RenderConstraint (Nullable a) where
407− renderConstraint _ = renderConstraint (Proxy :: Proxy a)
408−
409−else instance RenderConstraint a where
410− renderConstraint _ = ""
411−
412−joinConstraints :: String -> String -> String
413−joinConstraints a b = case a, b of
414− "", s -> s
415− s, "" -> s
416− s1, s2 -> s1 <> " " <> s2
417−
418−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
419−-- DDL generation
420−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
421−
422−class CreateTableDDL a where
423− createTableDDL :: String
424−
425−class RenderColumnsRL :: RL.RowList Type -> Constraint
426−class RenderColumnsRL rl where
427− renderColumnsRL :: Proxy rl -> Array String
428−
429−instance RenderColumnsRL RL.Nil where
430− renderColumnsRL _ = []
431−
432−instance
433− ( IsSymbol name
434− , ExtractType entry typ
435− , PGTypeName typ
436− , IsNullable typ
437− , RenderConstraint entry
438− , RenderColumnsRL tail
439− ) =>
440− RenderColumnsRL (RL.Cons name entry tail) where
441− renderColumnsRL _ = do
442− let colName = reflectSymbol (Proxy :: Proxy name)
443− let colType = pgTypeName (Proxy :: Proxy typ)
444− let nullable = isNullable (Proxy :: Proxy typ)
445− let notNull = if nullable then "" else " NOT NULL"
446− let constraints = renderConstraint (Proxy :: Proxy entry)
447− let constraintsSuffix = if constraints == "" then "" else " " <> constraints
448− [ colName <> " " <> colType <> notNull <> constraintsSuffix ] <> renderColumnsRL (Proxy :: Proxy tail)
449−
450−instance
451− ( IsSymbol name
452− , RowToList cols rl
453− , RenderColumnsRL rl
454− ) =>
455− CreateTableDDL (Table name cols) where
456− createTableDDL = do
457− let tableName = reflectSymbol (Proxy :: Proxy name)
458− let columns = renderColumnsRL (Proxy :: Proxy rl)
459− "CREATE TABLE " <> tableName <> " (" <> intercalate ", " columns <> ")"
460−
461−createTableDDLIfNotExists :: forall @a. CreateTableDDL a => String
462−createTableDDLIfNotExists = do
463− let ddl = createTableDDL @a
464− "CREATE TABLE IF NOT EXISTS" <> SCU.drop 12 ddl
465−
466−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
467−-- INSERT SQL generation
468−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
469−
470−class IsAutoGenerated a where
471− isAutoGenerated :: Proxy a -> Boolean
472−
473−instance IsAutoGenerated (AutoIncrement a) where
474− isAutoGenerated _ = true
475−else instance IsAutoGenerated (Default v a) where
476− isAutoGenerated _ = true
477−else instance IsAutoGenerated (DefaultExpr expression a) where
478− isAutoGenerated _ = true
479−else instance IsAutoGenerated a => IsAutoGenerated (PrimaryKey a) where
480− isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a)
481−else instance IsAutoGenerated a => IsAutoGenerated (Unique a) where
482− isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a)
483−else instance IsAutoGenerated a => IsAutoGenerated (ForeignKey t r c a) where
484− isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a)
485−else instance IsAutoGenerated a => IsAutoGenerated (Nullable a) where
486− isAutoGenerated _ = isAutoGenerated (Proxy :: Proxy a)
487−else instance IsAutoGenerated a where
488− isAutoGenerated _ = false
489−
490−class InsertColumnsRL :: RL.RowList Type -> Constraint
491−class InsertColumnsRL rl where
492− insertColumnsRL :: Proxy rl -> Array String
493−
494−instance InsertColumnsRL RL.Nil where
495− insertColumnsRL _ = []
496−
497−instance
498− ( IsSymbol name
499− , IsAutoGenerated entry
500− , InsertColumnsRL tail
501− ) =>
502− InsertColumnsRL (RL.Cons name entry tail) where
503− insertColumnsRL _ =
504− let
505− rest = insertColumnsRL (Proxy :: Proxy tail)
506− in
507− if isAutoGenerated (Proxy :: Proxy entry) then rest
508− else [ reflectSymbol (Proxy :: Proxy name) ] <> rest
509−
510−class InsertSQLFor a where
511− insertSQLFor :: String
512−
513−instance
514− ( IsSymbol name
515− , RowToList cols rl
516− , InsertColumnsRL rl
517− ) =>
518− InsertSQLFor (Table name cols) where
519− insertSQLFor = do
520− let tableName = reflectSymbol (Proxy :: Proxy name)
521− let cols = insertColumnsRL (Proxy :: Proxy rl)
522− if Array.null cols then
523− "INSERT INTO " <> tableName <> " DEFAULT VALUES RETURNING *"
524− else do
525− let placeholders = cols # mapWithIndex \i _ -> "$" <> show (i + 1)
526− "INSERT INTO " <> tableName
527− <> " ("
528− <> intercalate ", " cols
529− <> ")"
530− <> " VALUES ("
531− <> intercalate ", " placeholders
532− <> ")"
533− <> " RETURNING *"
534−
535−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
536−-- SELECT SQL generation
537−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
538−
539−class SelectAllSQLFor a where
540− selectAllSQLFor :: String
541−
542−instance
543− ( IsSymbol name
544− ) =>
545− SelectAllSQLFor (Table name cols) where
546− selectAllSQLFor = "SELECT * FROM " <> reflectSymbol (Proxy :: Proxy name)
547−
548−class WhereClauseRL :: RL.RowList Type -> Constraint
549−class WhereClauseRL rl where
550− whereClauseRL :: Proxy rl -> Int -> Array String
551−
552−instance WhereClauseRL RL.Nil where
553− whereClauseRL _ _ = []
554−
555−instance (IsSymbol name, WhereClauseRL tail) => WhereClauseRL (RL.Cons name typ tail) where
556− whereClauseRL _ idx =
557− [ reflectSymbol (Proxy :: Proxy name) <> " = $" <> show idx ]
558− <> whereClauseRL (Proxy :: Proxy tail) (idx + 1)
559−
560−class SelectWhereSQLFor a whereRow where
561− selectWhereSQLFor :: String
562−
563−instance
564− ( IsSymbol name
565− , RowToList whereRow whereRL
566− , WhereClauseRL whereRL
567− ) =>
568− SelectWhereSQLFor (Table name cols) whereRow where
569− selectWhereSQLFor = do
570− let tableName = reflectSymbol (Proxy :: Proxy name)
571− let conditions = whereClauseRL (Proxy :: Proxy whereRL) 1
572− "SELECT * FROM " <> tableName <> " WHERE " <> intercalate " AND " conditions
573−
574−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
575−-- UPDATE SQL generation
576−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
577−
578−class ColumnCountRL :: RL.RowList Type -> Constraint
579−class ColumnCountRL rl where
580− columnCountRL :: Proxy rl -> Int
581−
582−instance ColumnCountRL RL.Nil where
583− columnCountRL _ = 0
584−
585−instance ColumnCountRL tail => ColumnCountRL (RL.Cons name typ tail) where
586− columnCountRL _ = 1 + columnCountRL (Proxy :: Proxy tail)
587−
588−class UpdateSQLFor table setRow whereRow where
589− updateSQLFor :: String
590−
591−instance
592− ( IsSymbol name
593− , RowToList setRow setRL
594− , RowToList whereRow whereRL
595− , WhereClauseRL setRL
596− , WhereClauseRL whereRL
597− , ColumnCountRL setRL
598− ) =>
599− UpdateSQLFor (Table name cols) setRow whereRow where
600− updateSQLFor = do
601− let tableName = reflectSymbol (Proxy :: Proxy name)
602− let setClauses = whereClauseRL (Proxy :: Proxy setRL) 1
603− let setCount = columnCountRL (Proxy :: Proxy setRL)
604− let whereClauses = whereClauseRL (Proxy :: Proxy whereRL) (setCount + 1)
605− "UPDATE " <> tableName
606− <> " SET "
607− <> intercalate ", " setClauses
608− <> " WHERE "
609− <> intercalate " AND " whereClauses
610−
611−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
612−-- DELETE SQL generation
613−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
614−
615−class DeleteSQLFor table whereRow where
616− deleteSQLFor :: String
617−
618−instance
619− ( IsSymbol name
620− , RowToList whereRow whereRL
621− , WhereClauseRL whereRL
622− ) =>
623− DeleteSQLFor (Table name cols) whereRow where
624− deleteSQLFor = do
625− let tableName = reflectSymbol (Proxy :: Proxy name)
626− let conditions = whereClauseRL (Proxy :: Proxy whereRL) 1
627− "DELETE FROM " <> tableName <> " WHERE " <> intercalate " AND " conditions
628−
629−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
630−-- Utility type classes
631−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
632−
633−class UnwrapMaybe :: Type -> Type -> Constraint
634−class UnwrapMaybe a b | a -> b
635−
636−instance UnwrapMaybe (Maybe a) a
637−else instance UnwrapMaybe a a
638−
639−-- Skip leading spaces
640−class SkipSpaces :: Symbol -> Symbol -> Constraint
641−class SkipSpaces sym result | sym -> result
642−
643−instance SkipSpaces "" ""
644−else instance
645− ( Symbol.Cons head tail sym
646− , SkipSpacesGo head tail result
647− ) =>
648− SkipSpaces sym result
649−
650−class SkipSpacesGo :: Symbol -> Symbol -> Symbol -> Constraint
651−class SkipSpacesGo head tail result | head tail -> result
652−
653−instance SkipSpaces tail result => SkipSpacesGo " " tail result
654−else instance Symbol.Cons head tail result => SkipSpacesGo head tail result
655−
656−-- Skip a string literal: consume chars until closing single quote
657−class SkipStringLiteral :: Symbol -> Symbol -> Constraint
658−class SkipStringLiteral sym rest | sym -> rest
659−
660−instance Fail (Text "Unclosed string literal") => SkipStringLiteral "" rest
661−else instance
662− ( Symbol.Cons h t sym
663− , SkipStringLiteralGo h t rest
664− ) =>
665− SkipStringLiteral sym rest
666−
667−class SkipStringLiteralGo :: Symbol -> Symbol -> Symbol -> Constraint
668−class SkipStringLiteralGo head tail rest | head tail -> rest
669−
670−-- Closing quote: done
671−instance SkipStringLiteralGo "'" tail tail
672−-- End of string without closing quote
673−else instance Fail (Text "Unclosed string literal") => SkipStringLiteralGo h "" rest
674−-- Any other char: skip and continue
675−else instance
676− ( Symbol.Cons nextH nextT tail
677− , SkipStringLiteralGo nextH nextT rest
678− ) =>
679− SkipStringLiteralGo h tail rest
680−
681−-- Extract the first word from a Symbol (up to space or end)
682−class ExtractWord :: Symbol -> Symbol -> Symbol -> Constraint
683−class ExtractWord sym word rest | sym -> word rest
684−
685−instance ExtractWord "" "" ""
686−else instance
687− ( Symbol.Cons h t sym
688− , ExtractWordGo h t "" word rest
689− ) =>
690− ExtractWord sym word rest
691−
692−class ExtractWordGo :: Symbol -> Symbol -> Symbol -> Symbol -> Symbol -> Constraint
693−class ExtractWordGo head tail acc word rest | head tail acc -> word rest
694−
695−-- Stop on space
696−instance (SkipSpaces tail rest) => ExtractWordGo " " tail acc acc rest
697−-- Stop on comma (keep comma in rest)
698−else instance Symbol.Cons "," tail rest => ExtractWordGo "," tail acc acc rest
699−-- End of string
700−else instance Symbol.Append acc h word => ExtractWordGo h "" acc word ""
701−-- Regular char: accumulate
702−else instance
703− ( Symbol.Append acc h acc'
704− , Symbol.Cons nextH nextT tail
705− , ExtractWordGo nextH nextT acc' word rest
706− ) =>
707− ExtractWordGo h tail acc word rest
708−
709−-- StripColumns: (name :: String # Unique, ...) -> (name :: String, ...)
710−class StripColumnsRL :: RL.RowList Type -> RL.RowList Type -> Constraint
711−class StripColumnsRL rl out | rl -> out
712−
713−instance StripColumnsRL RL.Nil RL.Nil
714−instance (ExtractType entry typ, StripColumnsRL tail out') => StripColumnsRL (RL.Cons name entry tail) (RL.Cons name typ out')
715−
716−class StripColumns :: Row Type -> Row Type -> Constraint
717−class StripColumns cols result | cols -> result
718−
719−instance (RowToList cols rl, StripColumnsRL rl outRL, ListToRow outRL result) => StripColumns cols result
720−
721−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
722−-- SingleTable: extract name and cols from a single-entry tables row
723−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
724−
725−class SingleTable :: Row (Row Type) -> Symbol -> Row Type -> Constraint
726−class SingleTable tables name cols | tables -> name cols
727−
728−instance
729− ( RowToList tables (RL.Cons name cols RL.Nil)
730− , IsSymbol name
731− ) =>
732− SingleTable tables name cols
733−
734−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
735−-- SplitOnDot: "users.id" -> ("users", "id"); "name" -> unqualified
736−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
737−
738−class SplitOnDot :: Symbol -> Boolean -> Symbol -> Symbol -> Constraint
739−class SplitOnDot sym hasDot table col | sym -> hasDot table col
740−
741−instance
742− ( Symbol.Cons h t sym
743− , SplitOnDotGo h t "" hasDot table col
744− ) =>
745− SplitOnDot sym hasDot table col
746−
747−class SplitOnDotGo :: Symbol -> Symbol -> Symbol -> Boolean -> Symbol -> Symbol -> Constraint
748−class SplitOnDotGo head tail acc hasDot table col | head tail acc -> hasDot table col
749−
750−-- Found dot: acc is table, rest is column
751−instance SplitOnDotGo "." tail acc True acc tail
752−
753−-- End of string without dot: unqualified
754−else instance
755− ( Symbol.Append acc h col
756− ) =>
757− SplitOnDotGo h "" acc False "" col
758−
759−-- Regular char: accumulate
760−else instance
761− ( Symbol.Append acc h acc'
762− , Symbol.Cons nextH nextT tail
763− , SplitOnDotGo nextH nextT acc' hasDot table col
764− ) =>
765− SplitOnDotGo h tail acc hasDot table col
766−
767−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
768−-- ResolveColumn: look up a column (qualified or unqualified) in tables
769−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
770−
771−class ResolveColumn :: Symbol -> Row (Row Type) -> Type -> Constraint
772−class ResolveColumn word tables typ | word tables -> typ
773−
774−instance
775− ( SplitOnDot word hasDot table col
776− , ResolveColumnBranch hasDot table col tables typ
777− ) =>
778− ResolveColumn word tables typ
779−
780−class ResolveColumnBranch :: Boolean -> Symbol -> Symbol -> Row (Row Type) -> Type -> Constraint
781−class ResolveColumnBranch hasDot table col tables typ | hasDot table col tables -> typ
782−
783−-- Qualified: has dot
784−instance
785− ( Row.Cons table tableCols restTables tables
786− , Row.Cons col typ restCols tableCols
787− ) =>
788− ResolveColumnBranch True table col tables typ
789−
790−-- Unqualified: no dot
791−else instance
792− ( RowToList tables tablesRL
793− , FindUnqualifiedColumn col tablesRL typ
794− ) =>
795− ResolveColumnBranch False table col tables typ
796−
797−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
798−-- FindUnqualifiedColumn: search all tables for a column name
799−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
800−
801−class FindUnqualifiedColumn :: Symbol -> RL.RowList (Row Type) -> Type -> Constraint
802−class FindUnqualifiedColumn col tablesRL typ | col tablesRL -> typ
803−
804−instance
805− ( Fail (Beside (Text "Column ") (Beside (Quote col) (Text " not found in any table")))
806− ) =>
807− FindUnqualifiedColumn col RL.Nil typ
808−
809−instance
810− ( RowToList tableCols colsRL
811− , HasColumnRL col colsRL found
812− , FindUnqualifiedColumnDecide found col tableName tableCols tailTables typ
813− ) =>
814− FindUnqualifiedColumn col (RL.Cons tableName tableCols tailTables) typ
815−
816−class HasColumnRL :: Symbol -> RL.RowList Type -> Boolean -> Constraint
817−class HasColumnRL col rl found | col rl -> found
818−
819−instance HasColumnRL col RL.Nil False
820−instance HasColumnRL col (RL.Cons col typ tail) True
821−else instance HasColumnRL col tail found => HasColumnRL col (RL.Cons name typ tail) found
822−
823−class FindUnqualifiedColumnDecide :: Boolean -> Symbol -> Symbol -> Row Type -> RL.RowList (Row Type) -> Type -> Constraint
824−class FindUnqualifiedColumnDecide found col tableName tableCols restTables typ | found col tableName tableCols restTables -> typ
825−
826−-- Found in this table: verify it's not in remaining tables
827−instance
828− ( Row.Cons col typ restCols tableCols
829− , AssertNotInRemainingTables col restTables
830− ) =>
831− FindUnqualifiedColumnDecide True col tableName tableCols restTables typ
832−
833−-- Not found in this table: keep searching
834−instance
835− FindUnqualifiedColumn col restTables typ =>
836− FindUnqualifiedColumnDecide False col tableName tableCols restTables typ
837−
838−class AssertNotInRemainingTables :: Symbol -> RL.RowList (Row Type) -> Constraint
839−class AssertNotInRemainingTables col tablesRL
840−
841−instance AssertNotInRemainingTables col RL.Nil
842−instance
843− ( RowToList tableCols colsRL
844− , HasColumnRL col colsRL found
845− , AssertNotAmbiguous found col tableName
846− , AssertNotInRemainingTables col tail
847− ) =>
848− AssertNotInRemainingTables col (RL.Cons tableName tableCols tail)
849−
850−class AssertNotAmbiguous :: Boolean -> Symbol -> Symbol -> Constraint
851−class AssertNotAmbiguous found col tableName
852−
853−instance AssertNotAmbiguous False col tableName
854−instance
855− ( Fail (Beside (Text "Column ") (Beside (Quote col) (Text " is ambiguous - qualify with table name")))
856− ) =>
857− AssertNotAmbiguous True col tableName
858−
859−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
860−-- ParseSelect: parse "users.name, posts.title AS t" against tables
861−-- Result labels: column name (after dot) or explicit AS alias
862−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
863−
864−class ParseSelect :: Symbol -> Row (Row Type) -> Row Type -> Constraint
865−class ParseSelect sym tables result | sym tables -> result
866−
867−instance ParseSelect "" tables ()
868−else instance
869− ( Symbol.Cons h t sym
870− , ParseSelectGo h t "" tables RL.Nil outRL
871− , ListToRow outRL result
872− ) =>
873− ParseSelect sym tables result
874−
875−class ParseSelectGo :: Symbol -> Symbol -> Symbol -> Row (Row Type) -> RL.RowList Type -> RL.RowList Type -> Constraint
876−class ParseSelectGo head tail acc tables accRL outRL | head tail acc tables accRL -> outRL
877−
878−-- Leading/double comma: no column name before comma
879−instance
880− Fail (Text "Unexpected comma in SELECT clause (missing column name)") =>
881− ParseSelectGo "," tail "" tables accRL outRL
882−
883−-- Comma: emit column, continue
884−else instance
885− ( ResolveColumn acc tables entry
886− , ExtractType entry typ
887− , SplitOnDot acc _hasDot _table colName
888− , SkipSpaces tail rest
889− , ParseSelectContinue rest tables (RL.Cons colName typ accRL) outRL
890− ) =>
891− ParseSelectGo "," tail acc tables accRL outRL
892−
893−-- Space: column reference done, check for AS or comma
894−else instance
895− ( SkipSpaces tail rest
896− , ParseSelectAfterCol acc rest tables accRL outRL
897− ) =>
898− ParseSelectGo " " tail acc tables accRL outRL
899−
900−-- Open paren: aggregate function call
901−else instance
902− ( ExtractUntilParen tail args afterParen
903− , ResolveAggregateArg args tables argType
904− , AggregateReturnType acc argType returnType
905− , SkipSpaces afterParen rest
906− , ParseAfterAggregate rest tables returnType accRL outRL
907− ) =>
908− ParseSelectGo "(" tail acc tables accRL outRL
909−
910−-- End of string: emit final column
911−else instance
912− ( Symbol.Append acc h acc'
913− , ResolveColumn acc' tables entry
914− , ExtractType entry typ
915− , SplitOnDot acc' _hasDot _table colName
916− ) =>
917− ParseSelectGo h "" acc tables accRL (RL.Cons colName typ accRL)
918−
919−-- Regular char (including dot): accumulate
920−else instance
921− ( Symbol.Append acc h acc'
922− , Symbol.Cons nextH nextT tail
923− , ParseSelectGo nextH nextT acc' tables accRL outRL
924− ) =>
925− ParseSelectGo h tail acc tables accRL outRL
926−
927−-- After column name + space: AS alias, comma, or end
928−class ParseSelectAfterCol :: Symbol -> Symbol -> Row (Row Type) -> RL.RowList Type -> RL.RowList Type -> Constraint
929−class ParseSelectAfterCol colRef rest tables accRL outRL | colRef rest tables accRL -> outRL
930−
931−-- End: emit column with default label (column name after dot)
932−instance
933− ( ResolveColumn colRef tables entry
934− , ExtractType entry typ
935− , SplitOnDot colRef _hasDot _table colName
936− ) =>
937− ParseSelectAfterCol colRef "" tables accRL (RL.Cons colName typ accRL)
938−
939−-- Non-empty: branch on first char
940−else instance
941− ( Symbol.Cons h t rest
942− , ParseSelectAfterColByHead h t colRef tables accRL outRL
943− ) =>
944− ParseSelectAfterCol colRef rest tables accRL outRL
945−
946−class ParseSelectAfterColByHead :: Symbol -> Symbol -> Symbol -> Row (Row Type) -> RL.RowList Type -> RL.RowList Type -> Constraint
947−class ParseSelectAfterColByHead head tail colRef tables accRL outRL | head tail colRef tables accRL -> outRL
948−
949−-- Comma: emit column with default label, continue
950−instance
951− ( ResolveColumn colRef tables entry
952− , ExtractType entry typ
953− , SplitOnDot colRef _hasDot _table colName
954− , SkipSpaces tail rest
955− , ParseSelectContinue rest tables (RL.Cons colName typ accRL) outRL
956− ) =>
957− ParseSelectAfterColByHead "," tail colRef tables accRL outRL
958−
959−-- Otherwise (AS ...): extract word
960−else instance
961− ( Symbol.Append h t rest
962− , ExtractWord rest keyword afterKeyword
963− , ParseSelectHandleAS keyword afterKeyword colRef tables accRL outRL
964− ) =>
965− ParseSelectAfterColByHead h t colRef tables accRL outRL
966−
967−class ParseSelectHandleAS :: Symbol -> Symbol -> Symbol -> Row (Row Type) -> RL.RowList Type -> RL.RowList Type -> Constraint
968−class ParseSelectHandleAS keyword afterKeyword colRef tables accRL outRL | keyword afterKeyword colRef tables accRL -> outRL
969−
970−instance
971− ( ExtractWord afterKeyword alias afterAlias
972− , ResolveColumn colRef tables entry
973− , ExtractType entry typ
974− , SkipSpaces afterAlias rest
975− , ParseSelectExpectEnd rest tables (RL.Cons alias typ accRL) outRL
976− ) =>
977− ParseSelectHandleAS "AS" afterKeyword colRef tables accRL outRL
978−
979−else instance
980− ( ExtractWord afterKeyword alias afterAlias
981− , ResolveColumn colRef tables entry
982− , ExtractType entry typ
983− , SkipSpaces afterAlias rest
984− , ParseSelectExpectEnd rest tables (RL.Cons alias typ accRL) outRL
985− ) =>
986− ParseSelectHandleAS "as" afterKeyword colRef tables accRL outRL
987−
988−-- Bare alias (no AS keyword): "name n" means "name AS n"
989−else instance
990− ( ResolveColumn colRef tables entry
991− , ExtractType entry typ
992− , SkipSpaces afterKeyword rest
993− , ParseSelectExpectEnd rest tables (RL.Cons keyword typ accRL) outRL
994− ) =>
995− ParseSelectHandleAS keyword afterKeyword colRef tables accRL outRL
996−
997−class ParseSelectExpectEnd :: Symbol -> Row (Row Type) -> RL.RowList Type -> RL.RowList Type -> Constraint
998−class ParseSelectExpectEnd sym tables accRL outRL | sym tables accRL -> outRL
999−
1000−instance ParseSelectExpectEnd "" tables accRL accRL
1001−else instance
1002− ( Symbol.Cons h t sym
1003− , ParseSelectExpectEndByHead h t tables accRL outRL
1004− ) =>
1005− ParseSelectExpectEnd sym tables accRL outRL
1006−
1007−class ParseSelectExpectEndByHead :: Symbol -> Symbol -> Row (Row Type) -> RL.RowList Type -> RL.RowList Type -> Constraint
1008−class ParseSelectExpectEndByHead head tail tables accRL outRL | head tail tables accRL -> outRL
1009−
1010−instance
1011− ( SkipSpaces tail rest
1012− , ParseSelectContinue rest tables accRL outRL
1013− ) =>
1014− ParseSelectExpectEndByHead "," tail tables accRL outRL
1015−
1016−class ParseSelectContinue :: Symbol -> Row (Row Type) -> RL.RowList Type -> RL.RowList Type -> Constraint
1017−class ParseSelectContinue sym tables accRL outRL | sym tables accRL -> outRL
1018−
1019−instance Fail (Text "Trailing comma in SELECT clause") => ParseSelectContinue "" tables accRL outRL
1020−else instance
1021− ( Symbol.Cons h t sym
1022− , ParseSelectGo h t "" tables accRL outRL
1023− ) =>
1024− ParseSelectContinue sym tables accRL outRL
1025−
1026−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1027−-- Aggregate functions in SELECT: COUNT(*), SUM(col), etc.
1028−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1029−
1030−-- Extract characters until closing paren
1031−class ExtractUntilParen :: Symbol -> Symbol -> Symbol -> Constraint
1032−class ExtractUntilParen tail args afterParen | tail -> args afterParen
1033−
1034−instance
1035− Fail (Text "Unclosed parenthesis in SELECT clause") =>
1036− ExtractUntilParen "" args afterParen
1037−
1038−else instance
1039− ( Symbol.Cons h t tail
1040− , ExtractUntilParenGo h t "" Z args afterParen
1041− ) =>
1042− ExtractUntilParen tail args afterParen
1043−
1044−-- Peano depth counter for nested parentheses
1045−data Z
1046−
1047−data S :: Type -> Type
1048−data S n
1049−
1050−class ExtractUntilParenGo :: Symbol -> Symbol -> Symbol -> Type -> Symbol -> Symbol -> Constraint
1051−class ExtractUntilParenGo head tail acc depth args afterParen | head tail acc depth -> args afterParen
1052−
1053−-- ) at depth zero: done
1054−instance ExtractUntilParenGo ")" tail acc Z acc tail
1055−
1056−-- ) at depth > 0: decrement depth, keep going
1057−else instance
1058− ( Symbol.Append acc ")" acc'
1059− , Symbol.Cons nextH nextT tail
1060− , ExtractUntilParenGo nextH nextT acc' n args afterParen
1061− ) =>
1062− ExtractUntilParenGo ")" tail acc (S n) args afterParen
1063−
1064−-- ( : increment depth, keep going
1065−else instance
1066− ( Symbol.Append acc "(" acc'
1067− , Symbol.Cons nextH nextT tail
1068− , ExtractUntilParenGo nextH nextT acc' (S depth) args afterParen
1069− ) =>
1070− ExtractUntilParenGo "(" tail acc depth args afterParen
1071−
1072−-- Unclosed paren at end of input
1073−else instance
1074− Fail (Text "Unclosed parenthesis in SELECT clause") =>
1075− ExtractUntilParenGo h "" acc depth args afterParen
1076−
1077−-- Any other char: accumulate
1078−else instance
1079− ( Symbol.Append acc h acc'
1080− , Symbol.Cons nextH nextT tail
1081− , ExtractUntilParenGo nextH nextT acc' depth args afterParen
1082− ) =>
1083− ExtractUntilParenGo h tail acc depth args afterParen
1084−
1085−-- Resolve aggregate argument: "*" -> Star, column ref -> unwrapped type
1086−data Star
1087−
1088−class ResolveAggregateArg :: Symbol -> Row (Row Type) -> Type -> Constraint
1089−class ResolveAggregateArg args tables argType | args tables -> argType
1090−
1091−instance ResolveAggregateArg "*" tables Star
1092−else instance ResolveAggregateArg "" tables Star
1093−else instance
1094− ( SkipSpaces args trimmedFront
1095− , ExtractIdent trimmedFront col rest
1096− , ResolveAggregateArgDispatch col rest tables argType
1097− ) =>
1098− ResolveAggregateArg args tables argType
1099−
1100−-- ExtractIdent: like ExtractWord but also stops at "(" so we can detect nested function calls
1101−class ExtractIdent :: Symbol -> Symbol -> Symbol -> Constraint
1102−class ExtractIdent sym word rest | sym -> word rest
1103−
1104−instance ExtractIdent "" "" ""
1105−else instance
1106− ( Symbol.Cons h t sym
1107− , ExtractIdentGo h t "" word rest
1108− ) =>
1109− ExtractIdent sym word rest
1110−
1111−class ExtractIdentGo :: Symbol -> Symbol -> Symbol -> Symbol -> Symbol -> Constraint
1112−class ExtractIdentGo head tail acc word rest | head tail acc -> word rest
1113−
1114−instance (SkipSpaces tail rest) => ExtractIdentGo " " tail acc acc rest
1115−else instance Symbol.Cons "," tail rest => ExtractIdentGo "," tail acc acc rest
1116−else instance Symbol.Append "(" tail rest => ExtractIdentGo "(" tail acc acc rest
1117−else instance Symbol.Append acc h word => ExtractIdentGo h "" acc word ""
1118−else instance
1119− ( Symbol.Append acc h acc'
1120− , Symbol.Cons nextH nextT tail
1121− , ExtractIdentGo nextH nextT acc' word rest
1122− ) =>
1123− ExtractIdentGo h tail acc word rest
1124−
1125−-- After extracting the identifier, check if followed by "(" (nested function call) or not (column ref)
1126−class ResolveAggregateArgDispatch :: Symbol -> Symbol -> Row (Row Type) -> Type -> Constraint
1127−class ResolveAggregateArgDispatch col rest tables argType | col rest tables -> argType
1128−
1129−instance
1130− ResolveAggregateArgCol col tables argType =>
1131− ResolveAggregateArgDispatch col "" tables argType
1132−
1133−else instance
1134− ( Symbol.Cons h t rest
1135− , ResolveAggregateArgDispatchByHead h t col tables argType
1136− ) =>
1137− ResolveAggregateArgDispatch col rest tables argType
1138−
1139−class ResolveAggregateArgDispatchByHead :: Symbol -> Symbol -> Symbol -> Row (Row Type) -> Type -> Constraint
1140−class ResolveAggregateArgDispatchByHead head tail col tables argType | head tail col tables -> argType
1141−
1142−-- Nested function call: col(...)
1143−instance
1144− ( ExtractUntilParen tail innerArgs _afterParen
1145− , ResolveAggregateArg innerArgs tables innerArgType
1146− , AggregateReturnType col innerArgType argType
1147− ) =>
1148− ResolveAggregateArgDispatchByHead "(" tail col tables argType
1149−
1150−-- Not a function call: column reference
1151−else instance
1152− ResolveAggregateArgCol col tables argType =>
1153− ResolveAggregateArgDispatchByHead h t col tables argType
1154−
1155−class ResolveAggregateArgCol :: Symbol -> Row (Row Type) -> Type -> Constraint
1156−class ResolveAggregateArgCol col tables argType | col tables -> argType
1157−
1158−instance ResolveAggregateArgCol "*" tables Star
1159−else instance
1160− ( Symbol.Cons h _t col
1161− , ResolveAggregateArgColByHead h col tables argType
1162− ) =>
1163− ResolveAggregateArgCol col tables argType
1164−
1165−class ResolveAggregateArgColByHead :: Symbol -> Symbol -> Row (Row Type) -> Type -> Constraint
1166−class ResolveAggregateArgColByHead head col tables argType | head col tables -> argType
1167−
1168−-- Numeric literals: treat as Star (return type determined by function)
1169−instance ResolveAggregateArgColByHead "0" col tables Star
1170−else instance ResolveAggregateArgColByHead "1" col tables Star
1171−else instance ResolveAggregateArgColByHead "2" col tables Star
1172−else instance ResolveAggregateArgColByHead "3" col tables Star
1173−else instance ResolveAggregateArgColByHead "4" col tables Star
1174−else instance ResolveAggregateArgColByHead "5" col tables Star
1175−else instance ResolveAggregateArgColByHead "6" col tables Star
1176−else instance ResolveAggregateArgColByHead "7" col tables Star
1177−else instance ResolveAggregateArgColByHead "8" col tables Star
1178−else instance ResolveAggregateArgColByHead "9" col tables Star
1179−-- Column reference
1180−else instance
1181− ( ResolveColumn col tables entry
1182− , ExtractType entry typ
1183− , UnwrapMaybe typ unwrapped
1184− ) =>
1185− ResolveAggregateArgColByHead head col tables unwrapped
1186−
1187−-- Map (funcName, argType) -> returnType
1188−-- Dispatches on first character to avoid linear search
1189−class AggregateReturnType :: Symbol -> Type -> Type -> Constraint
1190−class AggregateReturnType funcName argType returnType | funcName argType -> returnType
1191−
1192−instance
1193− ( Symbol.Cons head rest funcName
1194− , AggregateReturnTypeByHead head funcName argType returnType
1195− ) =>
1196− AggregateReturnType funcName argType returnType
1197−
1198−class AggregateReturnTypeByHead :: Symbol -> Symbol -> Type -> Type -> Constraint
1199−class AggregateReturnTypeByHead head funcName argType returnType | head funcName argType -> returnType
1200−
1201−instance AggregateReturnTypeA funcName argType returnType => AggregateReturnTypeByHead "A" funcName argType returnType
1202−else instance AggregateReturnTypeA funcName argType returnType => AggregateReturnTypeByHead "a" funcName argType returnType
1203−else instance AggregateReturnTypeC funcName argType returnType => AggregateReturnTypeByHead "C" funcName argType returnType
1204−else instance AggregateReturnTypeC funcName argType returnType => AggregateReturnTypeByHead "c" funcName argType returnType
1205−else instance AggregateReturnTypeD funcName argType returnType => AggregateReturnTypeByHead "D" funcName argType returnType
1206−else instance AggregateReturnTypeD funcName argType returnType => AggregateReturnTypeByHead "d" funcName argType returnType
1207−else instance AggregateReturnTypeF funcName argType returnType => AggregateReturnTypeByHead "F" funcName argType returnType
1208−else instance AggregateReturnTypeF funcName argType returnType => AggregateReturnTypeByHead "f" funcName argType returnType
1209−else instance AggregateReturnTypeL funcName argType returnType => AggregateReturnTypeByHead "L" funcName argType returnType
1210−else instance AggregateReturnTypeL funcName argType returnType => AggregateReturnTypeByHead "l" funcName argType returnType
1211−else instance AggregateReturnTypeM funcName argType returnType => AggregateReturnTypeByHead "M" funcName argType returnType
1212−else instance AggregateReturnTypeM funcName argType returnType => AggregateReturnTypeByHead "m" funcName argType returnType
1213−else instance AggregateReturnTypeN funcName argType returnType => AggregateReturnTypeByHead "N" funcName argType returnType
1214−else instance AggregateReturnTypeN funcName argType returnType => AggregateReturnTypeByHead "n" funcName argType returnType
1215−else instance AggregateReturnTypeR funcName argType returnType => AggregateReturnTypeByHead "R" funcName argType returnType
1216−else instance AggregateReturnTypeR funcName argType returnType => AggregateReturnTypeByHead "r" funcName argType returnType
1217−else instance AggregateReturnTypeS funcName argType returnType => AggregateReturnTypeByHead "S" funcName argType returnType
1218−else instance AggregateReturnTypeS funcName argType returnType => AggregateReturnTypeByHead "s" funcName argType returnType
1219−else instance
1220− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1221− AggregateReturnTypeByHead head funcName argType returnType
1222−
1223−-- A: AVG, ARRAY_AGG
1224−class AggregateReturnTypeA :: Symbol -> Type -> Type -> Constraint
1225−class AggregateReturnTypeA funcName argType returnType | funcName argType -> returnType
1226−
1227−instance AggregateReturnTypeA "AVG" argType Number
1228−else instance AggregateReturnTypeA "avg" argType Number
1229−else instance AggregateReturnTypeA "ARRAY_AGG" argType (Array argType)
1230−else instance AggregateReturnTypeA "array_agg" argType (Array argType)
1231−else instance
1232− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1233− AggregateReturnTypeA funcName argType returnType
1234−
1235−-- C: COUNT, COALESCE
1236−class AggregateReturnTypeC :: Symbol -> Type -> Type -> Constraint
1237−class AggregateReturnTypeC funcName argType returnType | funcName argType -> returnType
1238−
1239−instance AggregateReturnTypeC "COUNT" argType BigInt
1240−else instance AggregateReturnTypeC "count" argType BigInt
1241−else instance AggregateReturnTypeC "COALESCE" argType argType
1242−else instance AggregateReturnTypeC "coalesce" argType argType
1243−else instance
1244− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1245− AggregateReturnTypeC funcName argType returnType
1246−
1247−-- D: DENSE_RANK
1248−class AggregateReturnTypeD :: Symbol -> Type -> Type -> Constraint
1249−class AggregateReturnTypeD funcName argType returnType | funcName argType -> returnType
1250−
1251−instance AggregateReturnTypeD "DENSE_RANK" argType Int
1252−else instance AggregateReturnTypeD "dense_rank" argType Int
1253−else instance
1254− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1255− AggregateReturnTypeD funcName argType returnType
1256−
1257−-- F: FIRST_VALUE
1258−class AggregateReturnTypeF :: Symbol -> Type -> Type -> Constraint
1259−class AggregateReturnTypeF funcName argType returnType | funcName argType -> returnType
1260−
1261−instance AggregateReturnTypeF "FIRST_VALUE" argType argType
1262−else instance AggregateReturnTypeF "first_value" argType argType
1263−else instance
1264− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1265− AggregateReturnTypeF funcName argType returnType
1266−
1267−-- L: LAG, LEAD, LAST_VALUE
1268−class AggregateReturnTypeL :: Symbol -> Type -> Type -> Constraint
1269−class AggregateReturnTypeL funcName argType returnType | funcName argType -> returnType
1270−
1271−instance AggregateReturnTypeL "LAG" argType argType
1272−else instance AggregateReturnTypeL "lag" argType argType
1273−else instance AggregateReturnTypeL "LEAD" argType argType
1274−else instance AggregateReturnTypeL "lead" argType argType
1275−else instance AggregateReturnTypeL "LAST_VALUE" argType argType
1276−else instance AggregateReturnTypeL "last_value" argType argType
1277−else instance
1278− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1279− AggregateReturnTypeL funcName argType returnType
1280−
1281−-- M: MIN, MAX
1282−class AggregateReturnTypeM :: Symbol -> Type -> Type -> Constraint
1283−class AggregateReturnTypeM funcName argType returnType | funcName argType -> returnType
1284−
1285−instance AggregateReturnTypeM "MIN" argType argType
1286−else instance AggregateReturnTypeM "min" argType argType
1287−else instance AggregateReturnTypeM "MAX" argType argType
1288−else instance AggregateReturnTypeM "max" argType argType
1289−else instance
1290− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1291− AggregateReturnTypeM funcName argType returnType
1292−
1293−-- N: NTILE, NTH_VALUE
1294−class AggregateReturnTypeN :: Symbol -> Type -> Type -> Constraint
1295−class AggregateReturnTypeN funcName argType returnType | funcName argType -> returnType
1296−
1297−instance AggregateReturnTypeN "NTILE" argType Int
1298−else instance AggregateReturnTypeN "ntile" argType Int
1299−else instance AggregateReturnTypeN "NTH_VALUE" argType argType
1300−else instance AggregateReturnTypeN "nth_value" argType argType
1301−else instance
1302− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1303− AggregateReturnTypeN funcName argType returnType
1304−
1305−-- R: ROW_NUMBER, RANK
1306−class AggregateReturnTypeR :: Symbol -> Type -> Type -> Constraint
1307−class AggregateReturnTypeR funcName argType returnType | funcName argType -> returnType
1308−
1309−instance AggregateReturnTypeR "ROW_NUMBER" argType Int
1310−else instance AggregateReturnTypeR "row_number" argType Int
1311−else instance AggregateReturnTypeR "RANK" argType Int
1312−else instance AggregateReturnTypeR "rank" argType Int
1313−else instance
1314− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1315− AggregateReturnTypeR funcName argType returnType
1316−
1317−-- S: SUM, STRING_AGG
1318−class AggregateReturnTypeS :: Symbol -> Type -> Type -> Constraint
1319−class AggregateReturnTypeS funcName argType returnType | funcName argType -> returnType
1320−
1321−instance AggregateReturnTypeS "SUM" argType argType
1322−else instance AggregateReturnTypeS "sum" argType argType
1323−else instance AggregateReturnTypeS "STRING_AGG" argType String
1324−else instance AggregateReturnTypeS "string_agg" argType String
1325−else instance
1326− Fail (Beside (Text "Unknown function: ") (Quote funcName)) =>
1327− AggregateReturnTypeS funcName argType returnType
1328−
1329−-- After aggregate ): require AS alias, then continue
1330−class ParseAfterAggregate :: Symbol -> Row (Row Type) -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
1331−class ParseAfterAggregate rest tables returnType accRL outRL | rest tables returnType accRL -> outRL
1332−
1333−-- End of string without alias
1334−instance
1335− Fail (Text "Aggregate function requires AS alias (e.g. COUNT(*) AS cnt)") =>
1336− ParseAfterAggregate "" tables returnType accRL outRL
1337−
1338−else instance
1339− ( Symbol.Cons h t rest
1340− , ParseAfterAggregateByHead h t tables returnType accRL outRL
1341− ) =>
1342− ParseAfterAggregate rest tables returnType accRL outRL
1343−
1344−class ParseAfterAggregateByHead :: Symbol -> Symbol -> Row (Row Type) -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
1345−class ParseAfterAggregateByHead head tail tables returnType accRL outRL | head tail tables returnType accRL -> outRL
1346−
1347−-- Comma without alias
1348−instance
1349− Fail (Text "Aggregate function requires AS alias (e.g. COUNT(*) AS cnt)") =>
1350− ParseAfterAggregateByHead "," tail tables returnType accRL outRL
1351−
1352−-- Otherwise: extract keyword and dispatch (OVER or AS)
1353−else instance
1354− ( Symbol.Append h t rest
1355− , ExtractWord rest keyword afterKeyword
1356− , ParseAfterAggregateKeyword keyword afterKeyword tables returnType accRL outRL
1357− ) =>
1358− ParseAfterAggregateByHead h t tables returnType accRL outRL
1359−
1360−class ParseAfterAggregateKeyword :: Symbol -> Symbol -> Row (Row Type) -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
1361−class ParseAfterAggregateKeyword keyword afterKeyword tables returnType accRL outRL | keyword afterKeyword tables returnType accRL -> outRL
1362−
1363−-- OVER: parse the over clause, then continue to AS
1364−instance
1365− ( SkipSpaces afterKeyword rest
1366− , ParseOverClause rest tables returnType accRL outRL
1367− ) =>
1368− ParseAfterAggregateKeyword "OVER" afterKeyword tables returnType accRL outRL
1369−
1370−else instance
1371− ( SkipSpaces afterKeyword rest
1372− , ParseOverClause rest tables returnType accRL outRL
1373− ) =>
1374− ParseAfterAggregateKeyword "over" afterKeyword tables returnType accRL outRL
1375−
1376−-- AS: extract alias
1377−else instance
1378− ( ExtractWord afterKeyword alias afterAlias
1379− , SkipSpaces afterAlias rest
1380− , ParseSelectExpectEnd rest tables (RL.Cons alias returnType accRL) outRL
1381− ) =>
1382− ParseAfterAggregateKeyword "AS" afterKeyword tables returnType accRL outRL
1383−
1384−else instance
1385− ( ExtractWord afterKeyword alias afterAlias
1386− , SkipSpaces afterAlias rest
1387− , ParseSelectExpectEnd rest tables (RL.Cons alias returnType accRL) outRL
1388− ) =>
1389− ParseAfterAggregateKeyword "as" afterKeyword tables returnType accRL outRL
1390−
1391−else instance
1392− Fail (Text "Aggregate function requires AS alias (e.g. COUNT(*) AS cnt)") =>
1393− ParseAfterAggregateKeyword keyword afterKeyword tables returnType accRL outRL
1394−
1395−-- Parse OVER (...) clause: expect (, skip until ), then continue to AS alias
1396−class ParseOverClause :: Symbol -> Row (Row Type) -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
1397−class ParseOverClause rest tables returnType accRL outRL | rest tables returnType accRL -> outRL
1398−
1399−instance
1400− Fail (Text "Expected ( after OVER") =>
1401− ParseOverClause "" tables returnType accRL outRL
1402−
1403−else instance
1404− ( Symbol.Cons h t rest
1405− , ParseOverClauseByHead h t tables returnType accRL outRL
1406− ) =>
1407− ParseOverClause rest tables returnType accRL outRL
1408−
1409−class ParseOverClauseByHead :: Symbol -> Symbol -> Row (Row Type) -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
1410−class ParseOverClauseByHead head tail tables returnType accRL outRL | head tail tables returnType accRL -> outRL
1411−
1412−-- Open paren: extract until close paren, validate content, continue to AS
1413−instance
1414− ( ExtractUntilParen tail overContent afterParen
1415− , ValidateOverContent overContent tables
1416− , SkipSpaces afterParen rest
1417− , ParseAfterAggregate rest tables returnType accRL outRL
1418− ) =>
1419− ParseOverClauseByHead "(" tail tables returnType accRL outRL
1420−
1421−else instance
1422− Fail (Text "Expected ( after OVER") =>
1423− ParseOverClauseByHead h t tables returnType accRL outRL
1424−
1425−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1426−-- ValidateOverContent: validates PARTITION BY / ORDER BY inside OVER(...)
1427−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1428−
1429−class ValidateOverContent :: Symbol -> Row (Row Type) -> Constraint
1430−class ValidateOverContent content tables
1431−
1432−instance ValidateOverContent "" tables
1433−
1434−else instance
1435− ( SkipSpaces content content'
1436− , ValidateOverContentTrimmed content' tables
1437− ) =>
1438− ValidateOverContent content tables
1439−
1440−class ValidateOverContentTrimmed :: Symbol -> Row (Row Type) -> Constraint
1441−class ValidateOverContentTrimmed content tables
1442−
1443−instance ValidateOverContentTrimmed "" tables
1444−
1445−else instance
1446− ( ExtractWord content keyword rest
1447− , ValidateOverKeyword keyword rest tables
1448− ) =>
1449− ValidateOverContentTrimmed content tables
1450−
1451−class ValidateOverKeyword :: Symbol -> Symbol -> Row (Row Type) -> Constraint
1452−class ValidateOverKeyword keyword rest tables
1453−
1454−instance
1455− ( ExtractWord rest byWord afterBy
1456− , AssertIsBy byWord
1457− , ValidatePartitionByColumns afterBy tables
1458− ) =>
1459− ValidateOverKeyword "PARTITION" rest tables
1460−
1461−else instance
1462− ( ExtractWord rest byWord afterBy
1463− , AssertIsBy byWord
1464− , ValidatePartitionByColumns afterBy tables
1465− ) =>
1466− ValidateOverKeyword "partition" rest tables
1467−
1468−else instance
1469− ( ExtractWord rest byWord afterBy
1470− , AssertIsBy byWord
1471− , ValidateOrderBy afterBy tables
1472− ) =>
1473− ValidateOverKeyword "ORDER" rest tables
1474−
1475−else instance
1476− ( ExtractWord rest byWord afterBy
1477− , AssertIsBy byWord
1478− , ValidateOrderBy afterBy tables
1479− ) =>
1480− ValidateOverKeyword "order" rest tables
1481−
1482−else instance
1483− Fail (Text "Expected PARTITION BY or ORDER BY inside OVER clause") =>
1484− ValidateOverKeyword keyword rest tables
1485−
1486−class AssertIsBy :: Symbol -> Constraint
1487−class AssertIsBy sym
1488−
1489−instance AssertIsBy "BY"
1490−else instance AssertIsBy "by"
1491−else instance Fail (Text "Expected BY after PARTITION or ORDER") => AssertIsBy sym
1492−
1493−-- ValidatePartitionByColumns: column list that terminates at ORDER keyword
1494−class ValidatePartitionByColumns :: Symbol -> Row (Row Type) -> Constraint
1495−class ValidatePartitionByColumns sym tables
1496−
1497−instance ValidatePartitionByColumns "" tables
1498−
1499−else instance
1500− ( SkipSpaces sym sym'
1501− , ValidatePartitionByColumnsTrimmed sym' tables
1502− ) =>
1503− ValidatePartitionByColumns sym tables
1504−
1505−class ValidatePartitionByColumnsTrimmed :: Symbol -> Row (Row Type) -> Constraint
1506−class ValidatePartitionByColumnsTrimmed sym tables
1507−
1508−instance ValidatePartitionByColumnsTrimmed "" tables
1509−
1510−else instance
1511− ( Symbol.Cons h t sym
1512− , ValidatePartitionByColumnsGo h t "" tables
1513− ) =>
1514− ValidatePartitionByColumnsTrimmed sym tables
1515−
1516−class ValidatePartitionByColumnsGo :: Symbol -> Symbol -> Symbol -> Row (Row Type) -> Constraint
1517−class ValidatePartitionByColumnsGo head tail acc tables
1518−
1519−-- Comma: flush column, continue
1520−instance
1521− ( ResolveColumn acc tables typ
1522− , SkipSpaces tail rest
1523− , ValidatePartitionByColumnsTrimmed rest tables
1524− ) =>
1525− ValidatePartitionByColumnsGo "," tail acc tables
1526−
1527−-- Space: flush word, check if ORDER keyword or column
1528−else instance
1529− ( SkipSpaces tail rest
1530− , FlushPartitionWord acc rest tables
1531− ) =>
1532− ValidatePartitionByColumnsGo " " tail acc tables
1533−
1534−-- End of string: flush final column
1535−else instance
1536− ( Symbol.Append acc h acc'
1537− , ResolveColumn acc' tables typ
1538− ) =>
1539− ValidatePartitionByColumnsGo h "" acc tables
1540−
1541−-- Regular char: accumulate
1542−else instance
1543− ( Symbol.Append acc h acc'
1544− , Symbol.Cons nextH nextT tail
1545− , ValidatePartitionByColumnsGo nextH nextT acc' tables
1546− ) =>
1547− ValidatePartitionByColumnsGo h tail acc tables
1548−
1549−class FlushPartitionWord :: Symbol -> Symbol -> Row (Row Type) -> Constraint
1550−class FlushPartitionWord word rest tables
1551−
1552−instance
1553− ( ExtractWord rest byWord afterBy
1554− , AssertIsBy byWord
1555− , ValidateOrderBy afterBy tables
1556− ) =>
1557− FlushPartitionWord "ORDER" rest tables
1558−
1559−else instance
1560− ( ExtractWord rest byWord afterBy
1561− , AssertIsBy byWord
1562− , ValidateOrderBy afterBy tables
1563− ) =>
1564− FlushPartitionWord "order" rest tables
1565−
1566−else instance
1567− ( ResolveColumn word tables typ
1568− , ValidatePartitionByColumnsTrimmed rest tables
1569− ) =>
1570− FlushPartitionWord word rest tables
1571−
1572−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1573−-- ParseWhere: parse "id = $id AND age > $age" -> params
1574−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1575−
1576−data NoType
1577−
1578−class ParseWhere :: Symbol -> Row (Row Type) -> Row Type -> Constraint
1579−class ParseWhere sym tables params | sym tables -> params
1580−
1581−instance Fail (Text "Empty WHERE clause") => ParseWhere "" tables params
1582−else instance
1583− ( Symbol.Cons h t sym
1584− , ParseWhereGo h t "" NoType tables RL.Nil outRL
1585− , ListToRow outRL params
1586− ) =>
1587− ParseWhere sym tables params
1588−
1589−class ParseWhereGo :: Symbol -> Symbol -> Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> RL.RowList Type -> Constraint
1590−class ParseWhereGo head tail acc currentType tables paramsIn paramsOut | head tail acc currentType tables paramsIn -> paramsOut
1591−
1592−-- Space: flush word, continue
1593−instance
1594− ( FlushWhereWord acc currentType tables paramsIn currentType' paramsOut'
1595− , SkipSpaces tail rest
1596− , ParseWhereContinue rest currentType' tables paramsOut' paramsOut
1597− ) =>
1598− ParseWhereGo " " tail acc currentType tables paramsIn paramsOut
1599−
1600−-- Operators
1601−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo "=" tail acc currentType tables paramsIn paramsOut
1602−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo ">" tail acc currentType tables paramsIn paramsOut
1603−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo "<" tail acc currentType tables paramsIn paramsOut
1604−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo "!" tail acc currentType tables paramsIn paramsOut
1605−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo "(" tail acc currentType tables paramsIn paramsOut
1606−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo ")" tail acc currentType tables paramsIn paramsOut
1607−-- String literal: flush word, skip until closing quote
1608−else instance
1609− ( FlushWhereWord acc currentType tables paramsIn currentType' paramsOut'
1610− , SkipStringLiteral tail rest
1611− , ParseWhereContinue rest currentType' tables paramsOut' paramsOut
1612− ) =>
1613− ParseWhereGo "'" tail acc currentType tables paramsIn paramsOut
1614−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo "@" tail acc currentType tables paramsIn paramsOut
1615−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo "?" tail acc currentType tables paramsIn paramsOut
1616−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo ":" tail acc currentType tables paramsIn paramsOut
1617−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo "~" tail acc currentType tables paramsIn paramsOut
1618−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo "#" tail acc currentType tables paramsIn paramsOut
1619−else instance (FlushWhereWord acc currentType tables paramsIn currentType' paramsOut', ParseWhereContinue tail currentType' tables paramsOut' paramsOut) => ParseWhereGo "," tail acc currentType tables paramsIn paramsOut
1620−
1621−-- End of string: flush final word
1622−else instance
1623− ( Symbol.Append acc h acc'
1624− , FlushWhereWord acc' currentType tables paramsIn _ct paramsOut
1625− ) =>
1626− ParseWhereGo h "" acc currentType tables paramsIn paramsOut
1627−
1628−-- Regular char (including dot): accumulate
1629−else instance
1630− ( Symbol.Append acc h acc'
1631− , Symbol.Cons nextH nextT tail
1632− , ParseWhereGo nextH nextT acc' currentType tables paramsIn paramsOut
1633− ) =>
1634− ParseWhereGo h tail acc currentType tables paramsIn paramsOut
1635−
1636−class ParseWhereContinue :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> RL.RowList Type -> Constraint
1637−class ParseWhereContinue sym currentType tables paramsIn paramsOut | sym currentType tables paramsIn -> paramsOut
1638−
1639−instance ParseWhereContinue "" currentType tables paramsIn paramsIn
1640−else instance
1641− ( Symbol.Cons h t sym
1642− , ParseWhereGo h t "" currentType tables paramsIn paramsOut
1643− ) =>
1644− ParseWhereContinue sym currentType tables paramsIn paramsOut
1645−
1646−-- Flush a word in WHERE context
1647−-- Dispatches on first character to avoid linear search through all keywords
1648−class FlushWhereWord :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1649−class FlushWhereWord word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1650−
1651−instance FlushWhereWord "" currentType tables paramsIn currentType paramsIn
1652−-- Most common SQL keywords matched directly for fast resolution
1653−else instance FlushWhereWord "AND" currentType tables paramsIn currentType paramsIn
1654−else instance FlushWhereWord "and" currentType tables paramsIn currentType paramsIn
1655−else instance FlushWhereWord "OR" currentType tables paramsIn currentType paramsIn
1656−else instance FlushWhereWord "or" currentType tables paramsIn currentType paramsIn
1657−else instance FlushWhereWord "NOT" currentType tables paramsIn currentType paramsIn
1658−else instance FlushWhereWord "not" currentType tables paramsIn currentType paramsIn
1659−else instance FlushWhereWord "IS" currentType tables paramsIn currentType paramsIn
1660−else instance FlushWhereWord "is" currentType tables paramsIn currentType paramsIn
1661−else instance FlushWhereWord "NULL" currentType tables paramsIn currentType paramsIn
1662−else instance FlushWhereWord "null" currentType tables paramsIn currentType paramsIn
1663−-- Everything else: dispatch on first character
1664−else instance
1665− ( Symbol.Cons head rest word
1666− , FlushWhereWordByHead head word currentType tables paramsIn currentTypeOut paramsOut
1667− ) =>
1668− FlushWhereWord word currentType tables paramsIn currentTypeOut paramsOut
1669−
1670−class FlushWhereWordByHead :: Symbol -> Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1671−class FlushWhereWordByHead head word currentType tables paramsIn currentTypeOut paramsOut | head word currentType tables paramsIn -> currentTypeOut paramsOut
1672−
1673−-- $param: emit with currentType
1674−instance
1675− ( Symbol.Cons "$" paramName word
1676− ) =>
1677− FlushWhereWordByHead "$" word currentType tables paramsIn currentType (RL.Cons paramName currentType paramsIn)
1678−
1679−-- Digit: number literal, pass through
1680−else instance FlushWhereWordByHead "0" word currentType tables paramsIn currentType paramsIn
1681−else instance FlushWhereWordByHead "1" word currentType tables paramsIn currentType paramsIn
1682−else instance FlushWhereWordByHead "2" word currentType tables paramsIn currentType paramsIn
1683−else instance FlushWhereWordByHead "3" word currentType tables paramsIn currentType paramsIn
1684−else instance FlushWhereWordByHead "4" word currentType tables paramsIn currentType paramsIn
1685−else instance FlushWhereWordByHead "5" word currentType tables paramsIn currentType paramsIn
1686−else instance FlushWhereWordByHead "6" word currentType tables paramsIn currentType paramsIn
1687−else instance FlushWhereWordByHead "7" word currentType tables paramsIn currentType paramsIn
1688−else instance FlushWhereWordByHead "8" word currentType tables paramsIn currentType paramsIn
1689−else instance FlushWhereWordByHead "9" word currentType tables paramsIn currentType paramsIn
1690−else instance FlushWhereWordByHead "*" word currentType tables paramsIn currentType paramsIn
1691−
1692−-- Letters with keywords: dispatch to per-letter helpers
1693−else instance FlushWhereWordA word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "A" word currentType tables paramsIn currentTypeOut paramsOut
1694−else instance FlushWhereWordA word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "a" word currentType tables paramsIn currentTypeOut paramsOut
1695−else instance FlushWhereWordB word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "B" word currentType tables paramsIn currentTypeOut paramsOut
1696−else instance FlushWhereWordB word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "b" word currentType tables paramsIn currentTypeOut paramsOut
1697−else instance FlushWhereWordC word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "C" word currentType tables paramsIn currentTypeOut paramsOut
1698−else instance FlushWhereWordC word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "c" word currentType tables paramsIn currentTypeOut paramsOut
1699−else instance FlushWhereWordD word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "D" word currentType tables paramsIn currentTypeOut paramsOut
1700−else instance FlushWhereWordD word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "d" word currentType tables paramsIn currentTypeOut paramsOut
1701−else instance FlushWhereWordE word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "E" word currentType tables paramsIn currentTypeOut paramsOut
1702−else instance FlushWhereWordE word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "e" word currentType tables paramsIn currentTypeOut paramsOut
1703−else instance FlushWhereWordF word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "F" word currentType tables paramsIn currentTypeOut paramsOut
1704−else instance FlushWhereWordF word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "f" word currentType tables paramsIn currentTypeOut paramsOut
1705−else instance FlushWhereWordI word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "I" word currentType tables paramsIn currentTypeOut paramsOut
1706−else instance FlushWhereWordI word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "i" word currentType tables paramsIn currentTypeOut paramsOut
1707−else instance FlushWhereWordJ word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "J" word currentType tables paramsIn currentTypeOut paramsOut
1708−else instance FlushWhereWordJ word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "j" word currentType tables paramsIn currentTypeOut paramsOut
1709−else instance FlushWhereWordL word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "L" word currentType tables paramsIn currentTypeOut paramsOut
1710−else instance FlushWhereWordL word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "l" word currentType tables paramsIn currentTypeOut paramsOut
1711−else instance FlushWhereWordM word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "M" word currentType tables paramsIn currentTypeOut paramsOut
1712−else instance FlushWhereWordM word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "m" word currentType tables paramsIn currentTypeOut paramsOut
1713−else instance FlushWhereWordN word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "N" word currentType tables paramsIn currentTypeOut paramsOut
1714−else instance FlushWhereWordN word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "n" word currentType tables paramsIn currentTypeOut paramsOut
1715−else instance FlushWhereWordS word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "S" word currentType tables paramsIn currentTypeOut paramsOut
1716−else instance FlushWhereWordS word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "s" word currentType tables paramsIn currentTypeOut paramsOut
1717−else instance FlushWhereWordT word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "T" word currentType tables paramsIn currentTypeOut paramsOut
1718−else instance FlushWhereWordT word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "t" word currentType tables paramsIn currentTypeOut paramsOut
1719−else instance FlushWhereWordU word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "U" word currentType tables paramsIn currentTypeOut paramsOut
1720−else instance FlushWhereWordU word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "u" word currentType tables paramsIn currentTypeOut paramsOut
1721−else instance FlushWhereWordV word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "V" word currentType tables paramsIn currentTypeOut paramsOut
1722−else instance FlushWhereWordV word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "v" word currentType tables paramsIn currentTypeOut paramsOut
1723−else instance FlushWhereWordP word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "P" word currentType tables paramsIn currentTypeOut paramsOut
1724−else instance FlushWhereWordP word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "p" word currentType tables paramsIn currentTypeOut paramsOut
1725−else instance FlushWhereWordR word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "R" word currentType tables paramsIn currentTypeOut paramsOut
1726−else instance FlushWhereWordR word currentType tables paramsIn currentTypeOut paramsOut => FlushWhereWordByHead "r" word currentType tables paramsIn currentTypeOut paramsOut
1727−
1728−-- Catch-all: column reference (letters without keywords like g, h, q, w, x, y, z)
1729−else instance
1730− ( ResolveColumn word tables entry
1731− , ExtractType entry typ
1732− , UnwrapMaybe typ unwrapped
1733− ) =>
1734− FlushWhereWordByHead head word currentType tables paramsIn unwrapped paramsIn
1735−
1736−-- Per-letter keyword helpers
1737−
1738−class FlushWhereWordA :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1739−class FlushWhereWordA word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1740−
1741−instance FlushWhereWordA "ABS" currentType tables paramsIn currentType paramsIn
1742−else instance FlushWhereWordA "abs" currentType tables paramsIn currentType paramsIn
1743−else instance FlushWhereWordA "ANY" currentType tables paramsIn (Array currentType) paramsIn
1744−else instance FlushWhereWordA "any" currentType tables paramsIn (Array currentType) paramsIn
1745−else instance FlushWhereWordA "ALL" currentType tables paramsIn (Array currentType) paramsIn
1746−else instance FlushWhereWordA "all" currentType tables paramsIn (Array currentType) paramsIn
1747−else instance FlushWhereWordA "AS" currentType tables paramsIn currentType paramsIn
1748−else instance FlushWhereWordA "as" currentType tables paramsIn currentType paramsIn
1749−else instance FlushWhereWordA "AVG" currentType tables paramsIn Number paramsIn
1750−else instance FlushWhereWordA "avg" currentType tables paramsIn Number paramsIn
1751−else instance FlushWhereWordA "ARRAY_AGG" currentType tables paramsIn currentType paramsIn
1752−else instance FlushWhereWordA "array_agg" currentType tables paramsIn currentType paramsIn
1753−else instance
1754− ( ResolveColumn word tables entry
1755− , ExtractType entry typ
1756− , UnwrapMaybe typ unwrapped
1757− ) =>
1758− FlushWhereWordA word currentType tables paramsIn unwrapped paramsIn
1759−
1760−class FlushWhereWordB :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1761−class FlushWhereWordB word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1762−
1763−instance FlushWhereWordB "BETWEEN" currentType tables paramsIn currentType paramsIn
1764−else instance FlushWhereWordB "between" currentType tables paramsIn currentType paramsIn
1765−else instance FlushWhereWordB "bigint" currentType tables paramsIn currentType paramsIn
1766−else instance FlushWhereWordB "boolean" currentType tables paramsIn currentType paramsIn
1767−else instance
1768− ( ResolveColumn word tables entry
1769− , ExtractType entry typ
1770− , UnwrapMaybe typ unwrapped
1771− ) =>
1772− FlushWhereWordB word currentType tables paramsIn unwrapped paramsIn
1773−
1774−class FlushWhereWordC :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1775−class FlushWhereWordC word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1776−
1777−instance FlushWhereWordC "CAST" currentType tables paramsIn currentType paramsIn
1778−else instance FlushWhereWordC "cast" currentType tables paramsIn currentType paramsIn
1779−else instance FlushWhereWordC "CEIL" currentType tables paramsIn currentType paramsIn
1780−else instance FlushWhereWordC "ceil" currentType tables paramsIn currentType paramsIn
1781−else instance FlushWhereWordC "COALESCE" currentType tables paramsIn currentType paramsIn
1782−else instance FlushWhereWordC "coalesce" currentType tables paramsIn currentType paramsIn
1783−else instance FlushWhereWordC "CONCAT" currentType tables paramsIn currentType paramsIn
1784−else instance FlushWhereWordC "concat" currentType tables paramsIn currentType paramsIn
1785−else instance FlushWhereWordC "COUNT" currentType tables paramsIn Int paramsIn
1786−else instance FlushWhereWordC "count" currentType tables paramsIn Int paramsIn
1787−else instance FlushWhereWordC "CURRENT_DATE" currentType tables paramsIn currentType paramsIn
1788−else instance FlushWhereWordC "current_date" currentType tables paramsIn currentType paramsIn
1789−else instance FlushWhereWordC "CURRENT_TIMESTAMP" currentType tables paramsIn currentType paramsIn
1790−else instance FlushWhereWordC "current_timestamp" currentType tables paramsIn currentType paramsIn
1791−else instance
1792− ( ResolveColumn word tables entry
1793− , ExtractType entry typ
1794− , UnwrapMaybe typ unwrapped
1795− ) =>
1796− FlushWhereWordC word currentType tables paramsIn unwrapped paramsIn
1797−
1798−class FlushWhereWordD :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1799−class FlushWhereWordD word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1800−
1801−instance FlushWhereWordD "DATE" currentType tables paramsIn currentType paramsIn
1802−else instance FlushWhereWordD "date" currentType tables paramsIn currentType paramsIn
1803−else instance
1804− ( ResolveColumn word tables entry
1805− , ExtractType entry typ
1806− , UnwrapMaybe typ unwrapped
1807− ) =>
1808− FlushWhereWordD word currentType tables paramsIn unwrapped paramsIn
1809−
1810−class FlushWhereWordE :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1811−class FlushWhereWordE word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1812−
1813−instance FlushWhereWordE "EXISTS" currentType tables paramsIn currentType paramsIn
1814−else instance FlushWhereWordE "exists" currentType tables paramsIn currentType paramsIn
1815−else instance
1816− ( ResolveColumn word tables entry
1817− , ExtractType entry typ
1818− , UnwrapMaybe typ unwrapped
1819− ) =>
1820− FlushWhereWordE word currentType tables paramsIn unwrapped paramsIn
1821−
1822−class FlushWhereWordF :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1823−class FlushWhereWordF word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1824−
1825−instance FlushWhereWordF "FALSE" currentType tables paramsIn currentType paramsIn
1826−else instance FlushWhereWordF "false" currentType tables paramsIn currentType paramsIn
1827−else instance FlushWhereWordF "FLOOR" currentType tables paramsIn currentType paramsIn
1828−else instance FlushWhereWordF "floor" currentType tables paramsIn currentType paramsIn
1829−else instance
1830− ( ResolveColumn word tables entry
1831− , ExtractType entry typ
1832− , UnwrapMaybe typ unwrapped
1833− ) =>
1834− FlushWhereWordF word currentType tables paramsIn unwrapped paramsIn
1835−
1836−class FlushWhereWordI :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1837−class FlushWhereWordI word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1838−
1839−instance FlushWhereWordI "IN" currentType tables paramsIn (Array currentType) paramsIn
1840−else instance FlushWhereWordI "in" currentType tables paramsIn (Array currentType) paramsIn
1841−else instance FlushWhereWordI "ILIKE" currentType tables paramsIn currentType paramsIn
1842−else instance FlushWhereWordI "ilike" currentType tables paramsIn currentType paramsIn
1843−else instance FlushWhereWordI "integer" currentType tables paramsIn currentType paramsIn
1844−else instance FlushWhereWordI "int" currentType tables paramsIn currentType paramsIn
1845−else instance
1846− ( ResolveColumn word tables entry
1847− , ExtractType entry typ
1848− , UnwrapMaybe typ unwrapped
1849− ) =>
1850− FlushWhereWordI word currentType tables paramsIn unwrapped paramsIn
1851−
1852−class FlushWhereWordJ :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1853−class FlushWhereWordJ word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1854−
1855−instance FlushWhereWordJ "jsonb" currentType tables paramsIn currentType paramsIn
1856−else instance FlushWhereWordJ "json" currentType tables paramsIn currentType paramsIn
1857−else instance
1858− ( ResolveColumn word tables entry
1859− , ExtractType entry typ
1860− , UnwrapMaybe typ unwrapped
1861− ) =>
1862− FlushWhereWordJ word currentType tables paramsIn unwrapped paramsIn
1863−
1864−class FlushWhereWordL :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1865−class FlushWhereWordL word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1866−
1867−instance FlushWhereWordL "LEAST" currentType tables paramsIn currentType paramsIn
1868−else instance FlushWhereWordL "least" currentType tables paramsIn currentType paramsIn
1869−else instance FlushWhereWordL "LENGTH" currentType tables paramsIn Int paramsIn
1870−else instance FlushWhereWordL "length" currentType tables paramsIn Int paramsIn
1871−else instance FlushWhereWordL "LIKE" currentType tables paramsIn currentType paramsIn
1872−else instance FlushWhereWordL "like" currentType tables paramsIn currentType paramsIn
1873−else instance FlushWhereWordL "LOWER" currentType tables paramsIn String paramsIn
1874−else instance FlushWhereWordL "lower" currentType tables paramsIn String paramsIn
1875−else instance
1876− ( ResolveColumn word tables entry
1877− , ExtractType entry typ
1878− , UnwrapMaybe typ unwrapped
1879− ) =>
1880− FlushWhereWordL word currentType tables paramsIn unwrapped paramsIn
1881−
1882−class FlushWhereWordM :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1883−class FlushWhereWordM word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1884−
1885−instance FlushWhereWordM "MIN" currentType tables paramsIn currentType paramsIn
1886−else instance FlushWhereWordM "min" currentType tables paramsIn currentType paramsIn
1887−else instance FlushWhereWordM "MAX" currentType tables paramsIn currentType paramsIn
1888−else instance FlushWhereWordM "max" currentType tables paramsIn currentType paramsIn
1889−else instance
1890− ( ResolveColumn word tables entry
1891− , ExtractType entry typ
1892− , UnwrapMaybe typ unwrapped
1893− ) =>
1894− FlushWhereWordM word currentType tables paramsIn unwrapped paramsIn
1895−
1896−class FlushWhereWordN :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1897−class FlushWhereWordN word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1898−
1899−instance FlushWhereWordN "NOT" currentType tables paramsIn currentType paramsIn -- unreachable (caught by FlushWhereWord directly) but included for self-containedness
1900−else instance FlushWhereWordN "not" currentType tables paramsIn currentType paramsIn
1901−else instance FlushWhereWordN "NOW" currentType tables paramsIn currentType paramsIn
1902−else instance FlushWhereWordN "now" currentType tables paramsIn currentType paramsIn
1903−else instance FlushWhereWordN "NULL" currentType tables paramsIn currentType paramsIn
1904−else instance FlushWhereWordN "null" currentType tables paramsIn currentType paramsIn
1905−else instance FlushWhereWordN "NULLIF" currentType tables paramsIn currentType paramsIn
1906−else instance FlushWhereWordN "nullif" currentType tables paramsIn currentType paramsIn
1907−else instance
1908− ( ResolveColumn word tables entry
1909− , ExtractType entry typ
1910− , UnwrapMaybe typ unwrapped
1911− ) =>
1912− FlushWhereWordN word currentType tables paramsIn unwrapped paramsIn
1913−
1914−class FlushWhereWordS :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1915−class FlushWhereWordS word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1916−
1917−instance FlushWhereWordS "SMALLINT" currentType tables paramsIn currentType paramsIn
1918−else instance FlushWhereWordS "smallint" currentType tables paramsIn currentType paramsIn
1919−else instance FlushWhereWordS "SUM" currentType tables paramsIn currentType paramsIn
1920−else instance FlushWhereWordS "sum" currentType tables paramsIn currentType paramsIn
1921−else instance FlushWhereWordS "STRING_AGG" currentType tables paramsIn String paramsIn
1922−else instance FlushWhereWordS "string_agg" currentType tables paramsIn String paramsIn
1923−else instance
1924− ( ResolveColumn word tables entry
1925− , ExtractType entry typ
1926− , UnwrapMaybe typ unwrapped
1927− ) =>
1928− FlushWhereWordS word currentType tables paramsIn unwrapped paramsIn
1929−
1930−class FlushWhereWordT :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1931−class FlushWhereWordT word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1932−
1933−instance FlushWhereWordT "TRIM" currentType tables paramsIn String paramsIn
1934−else instance FlushWhereWordT "trim" currentType tables paramsIn String paramsIn
1935−else instance FlushWhereWordT "TRUE" currentType tables paramsIn currentType paramsIn
1936−else instance FlushWhereWordT "true" currentType tables paramsIn currentType paramsIn
1937−else instance FlushWhereWordT "text" currentType tables paramsIn currentType paramsIn
1938−else instance FlushWhereWordT "timestamptz" currentType tables paramsIn currentType paramsIn
1939−else instance FlushWhereWordT "timestamp" currentType tables paramsIn currentType paramsIn
1940−else instance FlushWhereWordT "TIME" currentType tables paramsIn currentType paramsIn
1941−else instance FlushWhereWordT "time" currentType tables paramsIn currentType paramsIn
1942−else instance FlushWhereWordT "tsvector" currentType tables paramsIn currentType paramsIn
1943−else instance FlushWhereWordT "TSVECTOR" currentType tables paramsIn currentType paramsIn
1944−else instance FlushWhereWordT "tsquery" currentType tables paramsIn currentType paramsIn
1945−else instance FlushWhereWordT "TSQUERY" currentType tables paramsIn currentType paramsIn
1946−else instance FlushWhereWordT "to_tsvector" currentType tables paramsIn String paramsIn
1947−else instance FlushWhereWordT "TO_TSVECTOR" currentType tables paramsIn String paramsIn
1948−else instance FlushWhereWordT "to_tsquery" currentType tables paramsIn String paramsIn
1949−else instance FlushWhereWordT "TO_TSQUERY" currentType tables paramsIn String paramsIn
1950−else instance
1951− ( ResolveColumn word tables entry
1952− , ExtractType entry typ
1953− , UnwrapMaybe typ unwrapped
1954− ) =>
1955− FlushWhereWordT word currentType tables paramsIn unwrapped paramsIn
1956−
1957−class FlushWhereWordU :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1958−class FlushWhereWordU word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1959−
1960−instance FlushWhereWordU "UPPER" currentType tables paramsIn String paramsIn
1961−else instance FlushWhereWordU "upper" currentType tables paramsIn String paramsIn
1962−else instance FlushWhereWordU "uuid" currentType tables paramsIn currentType paramsIn
1963−else instance
1964− ( ResolveColumn word tables entry
1965− , ExtractType entry typ
1966− , UnwrapMaybe typ unwrapped
1967− ) =>
1968− FlushWhereWordU word currentType tables paramsIn unwrapped paramsIn
1969−
1970−class FlushWhereWordV :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1971−class FlushWhereWordV word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1972−
1973−instance FlushWhereWordV "varchar" currentType tables paramsIn currentType paramsIn
1974−else instance
1975− ( ResolveColumn word tables entry
1976− , ExtractType entry typ
1977− , UnwrapMaybe typ unwrapped
1978− ) =>
1979− FlushWhereWordV word currentType tables paramsIn unwrapped paramsIn
1980−
1981−class FlushWhereWordP :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1982−class FlushWhereWordP word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1983−
1984−instance FlushWhereWordP "point" currentType tables paramsIn currentType paramsIn
1985−else instance FlushWhereWordP "POINT" currentType tables paramsIn currentType paramsIn
1986−else instance FlushWhereWordP "plainto_tsquery" currentType tables paramsIn currentType paramsIn
1987−else instance FlushWhereWordP "PLAINTO_TSQUERY" currentType tables paramsIn currentType paramsIn
1988−else instance
1989− ( ResolveColumn word tables entry
1990− , ExtractType entry typ
1991− , UnwrapMaybe typ unwrapped
1992− ) =>
1993− FlushWhereWordP word currentType tables paramsIn unwrapped paramsIn
1994−
1995−class FlushWhereWordR :: Symbol -> Type -> Row (Row Type) -> RL.RowList Type -> Type -> RL.RowList Type -> Constraint
1996−class FlushWhereWordR word currentType tables paramsIn currentTypeOut paramsOut | word currentType tables paramsIn -> currentTypeOut paramsOut
1997−
1998−instance FlushWhereWordR "real" currentType tables paramsIn currentType paramsIn
1999−else instance FlushWhereWordR "REAL" currentType tables paramsIn currentType paramsIn
2000−else instance
2001− ( ResolveColumn word tables entry
2002− , ExtractType entry typ
2003− , UnwrapMaybe typ unwrapped
2004− ) =>
2005− FlushWhereWordR word currentType tables paramsIn unwrapped paramsIn
2006−
2007−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2008−-- ValidateColumnList: comma-separated column references
2009−-- Used by GROUP BY, DISTINCT ON, ON CONFLICT target
2010−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2011−
2012−class ValidateColumnList :: Symbol -> Row (Row Type) -> Constraint
2013−class ValidateColumnList sym tables
2014−
2015−instance Fail (Text "Empty column list") => ValidateColumnList "" tables
2016−else instance
2017− ( Symbol.Cons h t sym
2018− , ValidateColumnListGo h t "" tables
2019− ) =>
2020− ValidateColumnList sym tables
2021−
2022−class ValidateColumnListContinue :: Symbol -> Row (Row Type) -> Constraint
2023−class ValidateColumnListContinue sym tables
2024−
2025−instance ValidateColumnListContinue "" tables
2026−else instance
2027− ( Symbol.Cons h t sym
2028− , ValidateColumnListGo h t "" tables
2029− ) =>
2030− ValidateColumnListContinue sym tables
2031−
2032−class ValidateColumnListGo :: Symbol -> Symbol -> Symbol -> Row (Row Type) -> Constraint
2033−class ValidateColumnListGo head tail acc tables
2034−
2035−instance
2036− ( ResolveColumn acc tables typ
2037− , SkipSpaces tail rest
2038− , ValidateColumnListContinue rest tables
2039− ) =>
2040− ValidateColumnListGo "," tail acc tables
2041−
2042−else instance
2043− ( SkipSpaces tail rest
2044− , ResolveColumn acc tables typ
2045− , ValidateColumnListContinue rest tables
2046− ) =>
2047− ValidateColumnListGo " " tail acc tables
2048−
2049−else instance
2050− ( Symbol.Append acc h acc'
2051− , ResolveColumn acc' tables typ
2052− ) =>
2053− ValidateColumnListGo h "" acc tables
2054−
2055−else instance
2056− ( Symbol.Append acc h acc'
2057− , Symbol.Cons nextH nextT tail
2058− , ValidateColumnListGo nextH nextT acc' tables
2059− ) =>
2060− ValidateColumnListGo h tail acc tables
2061−
2062−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2063−-- ValidateOrderBy: validate ORDER BY with qualified column support
2064−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2065−
2066−class ValidateOrderBy :: Symbol -> Row (Row Type) -> Constraint
2067−class ValidateOrderBy sym tables
2068−
2069−instance ValidateOrderBy "" tables
2070−else instance
2071− ( Symbol.Cons h t sym
2072− , ValidateOrderByGo h t "" tables
2073− ) =>
2074− ValidateOrderBy sym tables
2075−
2076−class ValidateOrderByGo :: Symbol -> Symbol -> Symbol -> Row (Row Type) -> Constraint
2077−class ValidateOrderByGo head tail acc tables
2078−
2079−-- Leading/double comma: no column before comma
2080−instance
2081− Fail (Text "Unexpected comma in ORDER BY clause (missing column name)") =>
2082− ValidateOrderByGo "," tail "" tables
2083−
2084−-- Comma: flush column, continue (non-empty acc)
2085−else instance
2086− ( FlushOrderByWord acc tables
2087− , SkipSpaces tail rest
2088− , ValidateOrderByContinueNonEmpty rest tables
2089− ) =>
2090− ValidateOrderByGo "," tail acc tables
2091−
2092−-- Space: flush column, skip modifiers
2093−else instance
2094− ( SkipSpaces tail rest
2095− , FlushOrderByThenSkip acc rest tables
2096− ) =>
2097− ValidateOrderByGo " " tail acc tables
2098−
2099−-- End of string: flush final column
2100−else instance
2101− ( Symbol.Append acc h acc'
2102− , FlushOrderByFinalWord acc' tables
2103− ) =>
2104− ValidateOrderByGo h "" acc tables
2105−
2106−-- Regular char (including dot): accumulate
2107−else instance
2108− ( Symbol.Append acc h acc'
2109− , Symbol.Cons nextH nextT tail
2110− , ValidateOrderByGo nextH nextT acc' tables
2111− ) =>
2112− ValidateOrderByGo h tail acc tables
2113−
2114−-- After comma: reject empty (trailing comma)
2115−class ValidateOrderByContinueNonEmpty :: Symbol -> Row (Row Type) -> Constraint
2116−class ValidateOrderByContinueNonEmpty sym tables
2117−
2118−instance Fail (Text "Trailing comma in ORDER BY clause") => ValidateOrderByContinueNonEmpty "" tables
2119−else instance
2120− ( Symbol.Cons h t sym
2121− , ValidateOrderByGo h t "" tables
2122− ) =>
2123− ValidateOrderByContinueNonEmpty sym tables
2124−
2125−-- End of string: reject bare modifiers (ASC/DESC without column)
2126−class FlushOrderByFinalWord :: Symbol -> Row (Row Type) -> Constraint
2127−class FlushOrderByFinalWord word tables
2128−
2129−instance Fail (Text "ORDER BY requires at least one column") => FlushOrderByFinalWord "" tables
2130−else instance Fail (Text "ORDER BY requires a column name before ASC/DESC") => FlushOrderByFinalWord "ASC" tables
2131−else instance Fail (Text "ORDER BY requires a column name before ASC/DESC") => FlushOrderByFinalWord "asc" tables
2132−else instance Fail (Text "ORDER BY requires a column name before ASC/DESC") => FlushOrderByFinalWord "DESC" tables
2133−else instance Fail (Text "ORDER BY requires a column name before ASC/DESC") => FlushOrderByFinalWord "desc" tables
2134−else instance Fail (Text "ORDER BY requires a column name before ASC/DESC") => FlushOrderByFinalWord "NULLS" tables
2135−else instance Fail (Text "ORDER BY requires a column name before ASC/DESC") => FlushOrderByFinalWord "FIRST" tables
2136−else instance Fail (Text "ORDER BY requires a column name before ASC/DESC") => FlushOrderByFinalWord "LAST" tables
2137−else instance ResolveColumn word tables typ => FlushOrderByFinalWord word tables
2138−
2139−class FlushOrderByWord :: Symbol -> Row (Row Type) -> Constraint
2140−class FlushOrderByWord word tables
2141−
2142−instance FlushOrderByWord "" tables
2143−else instance FlushOrderByWord "ASC" tables
2144−else instance FlushOrderByWord "asc" tables
2145−else instance FlushOrderByWord "DESC" tables
2146−else instance FlushOrderByWord "desc" tables
2147−else instance FlushOrderByWord "NULLS" tables
2148−else instance FlushOrderByWord "FIRST" tables
2149−else instance FlushOrderByWord "LAST" tables
2150−else instance ResolveColumn word tables typ => FlushOrderByWord word tables
2151−
2152−class FlushOrderByThenSkip :: Symbol -> Symbol -> Row (Row Type) -> Constraint
2153−class FlushOrderByThenSkip colName rest tables
2154−
2155−instance FlushOrderByWord colName tables => FlushOrderByThenSkip colName "" tables
2156−else instance
2157− ( FlushOrderByWord colName tables
2158− , Symbol.Cons h t rest
2159− , FlushOrderByThenSkipByHead h t tables
2160− ) =>
2161− FlushOrderByThenSkip colName rest tables
2162−
2163−class FlushOrderByThenSkipByHead :: Symbol -> Symbol -> Row (Row Type) -> Constraint
2164−class FlushOrderByThenSkipByHead head tail tables
2165−
2166−-- Comma: continue with next column
2167−instance
2168− ( SkipSpaces tail rest
2169− , ValidateOrderBy rest tables
2170− ) =>
2171− FlushOrderByThenSkipByHead "," tail tables
2172−
2173−-- Modifier word: consume it, continue
2174−else instance
2175− ( Symbol.Append h t rest
2176− , ExtractWord rest word afterWord
2177− , FlushOrderByWord word tables
2178− , SkipSpaces afterWord rest'
2179− , ValidateOrderByContinue rest' tables
2180− ) =>
2181− FlushOrderByThenSkipByHead h t tables
2182−
2183−class ValidateOrderByContinue :: Symbol -> Row (Row Type) -> Constraint
2184−class ValidateOrderByContinue sym tables
2185−
2186−instance ValidateOrderByContinue "" tables
2187−else instance
2188− ( Symbol.Cons h t sym
2189− , ValidateOrderByContinueByHead h t tables
2190− ) =>
2191− ValidateOrderByContinue sym tables
2192−
2193−class ValidateOrderByContinueByHead :: Symbol -> Symbol -> Row (Row Type) -> Constraint
2194−class ValidateOrderByContinueByHead head tail tables
2195−
2196−instance
2197− ( SkipSpaces tail rest
2198− , ValidateOrderBy rest tables
2199− ) =>
2200− ValidateOrderByContinueByHead "," tail tables
2201−
2202−else instance
2203− ( Symbol.Append h t rest
2204− , ExtractWord rest word afterWord
2205− , FlushOrderByWord word tables
2206− , SkipSpaces afterWord rest'
2207− , ValidateOrderByContinue rest' tables
2208− ) =>
2209− ValidateOrderByContinueByHead h t tables
2210−
2211−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2212−-- RowListHas: check if a label exists in a RowList
2213−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2214−
2215−class RowListHas :: Symbol -> RL.RowList Type -> Boolean -> Constraint
2216−class RowListHas label rl has | label rl -> has
2217−
2218−instance RowListHas label RL.Nil False
2219−instance RowListHas label (RL.Cons label typ rest) True
2220−else instance RowListHas label rest has => RowListHas label (RL.Cons other typ rest) has
2221−
2222−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2223−-- CheckDistinctOrderBy: when DISTINCT is in stage, ORDER BY
2224−-- columns must appear in the SELECT result list
2225−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2226−
2227−class CheckDistinctOrderBy :: Row Type -> Symbol -> Row Type -> Constraint
2228−class CheckDistinctOrderBy stage cols result
2229−
2230−instance
2231− ( RowToList stage stageRL
2232− , RowListHas "distinct" stageRL isDistinct
2233− , CheckDistinctOrderByBranch isDistinct cols result
2234− ) =>
2235− CheckDistinctOrderBy stage cols result
2236−
2237−class CheckDistinctOrderByBranch :: Boolean -> Symbol -> Row Type -> Constraint
2238−class CheckDistinctOrderByBranch isDistinct cols result
2239−
2240−instance OrderByColumnsInResult cols result => CheckDistinctOrderByBranch True cols result
2241−instance CheckDistinctOrderByBranch False cols result
2242−
2243−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2244−-- OrderByColumnsInResult: validate ORDER BY column refs exist in result row
2245−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2246−
2247−class OrderByColumnsInResult :: Symbol -> Row Type -> Constraint
2248−class OrderByColumnsInResult sym result
2249−
2250−instance OrderByColumnsInResult "" result
2251−else instance
2252− ( Symbol.Cons h t sym
2253− , OrderByColumnsInResultGo h t "" result
2254− ) =>
2255− OrderByColumnsInResult sym result
2256−
2257−class OrderByColumnsInResultGo :: Symbol -> Symbol -> Symbol -> Row Type -> Constraint
2258−class OrderByColumnsInResultGo head tail acc result
2259−
2260−-- Comma: flush column, continue
2261−instance
2262− ( FlushOrderByWordInResult acc result
2263− , SkipSpaces tail rest
2264− , OrderByColumnsInResult rest result
2265− ) =>
2266− OrderByColumnsInResultGo "," tail acc result
2267−
2268−-- Space: flush word, skip modifiers
2269−else instance
2270− ( SkipSpaces tail rest
2271− , FlushOrderByThenSkipInResult acc rest result
2272− ) =>
2273− OrderByColumnsInResultGo " " tail acc result
2274−
2275−-- End of string: flush final column
2276−else instance
2277− ( Symbol.Append acc h acc'
2278− , FlushOrderByWordInResult acc' result
2279− ) =>
2280− OrderByColumnsInResultGo h "" acc result
2281−
2282−-- Regular char: accumulate
2283−else instance
2284− ( Symbol.Append acc h acc'
2285− , Symbol.Cons nextH nextT tail
2286− , OrderByColumnsInResultGo nextH nextT acc' result
2287− ) =>
2288− OrderByColumnsInResultGo h tail acc result
2289−
2290−class FlushOrderByWordInResult :: Symbol -> Row Type -> Constraint
2291−class FlushOrderByWordInResult word result
2292−
2293−instance FlushOrderByWordInResult "" result
2294−else instance FlushOrderByWordInResult "ASC" result
2295−else instance FlushOrderByWordInResult "asc" result
2296−else instance FlushOrderByWordInResult "DESC" result
2297−else instance FlushOrderByWordInResult "desc" result
2298−else instance FlushOrderByWordInResult "NULLS" result
2299−else instance FlushOrderByWordInResult "FIRST" result
2300−else instance FlushOrderByWordInResult "LAST" result
2301−else instance CheckColumnInResult word result => FlushOrderByWordInResult word result
2302−
2303−class CheckColumnInResult :: Symbol -> Row Type -> Constraint
2304−class CheckColumnInResult col result
2305−
2306−instance
2307− ( RowToList result rl
2308− , CheckColumnInResultRL col rl
2309− ) =>
2310− CheckColumnInResult col result
2311−
2312−class CheckColumnInResultRL :: Symbol -> RL.RowList Type -> Constraint
2313−class CheckColumnInResultRL col rl
2314−
2315−instance
2316− Fail (Beside (Text "ORDER BY column ") (Beside (Quote col) (Text " must appear in the SELECT list when using DISTINCT"))) =>
2317− CheckColumnInResultRL col RL.Nil
2318−
2319−instance CheckColumnInResultRL col (RL.Cons col typ tail)
2320−
2321−else instance CheckColumnInResultRL col tail => CheckColumnInResultRL col (RL.Cons name typ tail)
2322−
2323−class FlushOrderByThenSkipInResult :: Symbol -> Symbol -> Row Type -> Constraint
2324−class FlushOrderByThenSkipInResult colName rest result
2325−
2326−instance FlushOrderByWordInResult colName result => FlushOrderByThenSkipInResult colName "" result
2327−else instance
2328− ( FlushOrderByWordInResult colName result
2329− , Symbol.Cons h t rest
2330− , FlushOrderByThenSkipByHeadInResult h t result
2331− ) =>
2332− FlushOrderByThenSkipInResult colName rest result
2333−
2334−class FlushOrderByThenSkipByHeadInResult :: Symbol -> Symbol -> Row Type -> Constraint
2335−class FlushOrderByThenSkipByHeadInResult head tail result
2336−
2337−-- Comma: continue with next column
2338−instance
2339− ( SkipSpaces tail rest
2340− , OrderByColumnsInResult rest result
2341− ) =>
2342− FlushOrderByThenSkipByHeadInResult "," tail result
2343−
2344−-- Modifier word: consume it, continue
2345−else instance
2346− ( Symbol.Append h t rest
2347− , ExtractWord rest word afterWord
2348− , FlushOrderByWordInResult word result
2349− , SkipSpaces afterWord rest'
2350− , OrderByColumnsInResultContinue rest' result
2351− ) =>
2352− FlushOrderByThenSkipByHeadInResult h t result
2353−
2354−class OrderByColumnsInResultContinue :: Symbol -> Row Type -> Constraint
2355−class OrderByColumnsInResultContinue sym result
2356−
2357−instance OrderByColumnsInResultContinue "" result
2358−else instance
2359− ( Symbol.Cons h t sym
2360− , OrderByColumnsInResultContinueByHead h t result
2361− ) =>
2362− OrderByColumnsInResultContinue sym result
2363−
2364−class OrderByColumnsInResultContinueByHead :: Symbol -> Symbol -> Row Type -> Constraint
2365−class OrderByColumnsInResultContinueByHead head tail result
2366−
2367−instance
2368− ( SkipSpaces tail rest
2369− , OrderByColumnsInResult rest result
2370− ) =>
2371− OrderByColumnsInResultContinueByHead "," tail result
2372−
2373−else instance
2374− ( Symbol.Append h t rest
2375− , ExtractWord rest word afterWord
2376− , FlushOrderByWordInResult word result
2377− , SkipSpaces afterWord rest'
2378− , OrderByColumnsInResultContinue rest' result
2379− ) =>
2380− OrderByColumnsInResultContinueByHead h t result
2381−
2382−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2383−-- ValidateColumns: comma-separated column names (for ON CONFLICT target)
2384−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2385−
2386−class ValidateColumns :: Symbol -> Row Type -> Constraint
2387−class ValidateColumns sym cols
2388−
2389−instance ValidateColumns "" cols
2390−else instance
2391− ( Symbol.Cons h t sym
2392− , ValidateColumnsGo h t "" cols
2393− ) =>
2394− ValidateColumns sym cols
2395−
2396−class ValidateColumnsGo :: Symbol -> Symbol -> Symbol -> Row Type -> Constraint
2397−class ValidateColumnsGo head tail acc cols
2398−
2399−-- Comma: flush name, continue
2400−instance
2401− ( FlushColumnWord acc cols
2402− , SkipSpaces tail rest
2403− , ValidateColumns rest cols
2404− ) =>
2405− ValidateColumnsGo "," tail acc cols
2406−
2407−-- Space: flush name, skip to comma or end
2408−else instance
2409− ( SkipSpaces tail rest
2410− , ValidateAfterName acc rest cols
2411− ) =>
2412− ValidateColumnsGo " " tail acc cols
2413−
2414−-- End of string: flush final name
2415−else instance
2416− ( Symbol.Append acc h acc'
2417− , FlushColumnWord acc' cols
2418− ) =>
2419− ValidateColumnsGo h "" acc cols
2420−
2421−-- Regular char: accumulate
2422−else instance
2423− ( Symbol.Append acc h acc'
2424− , Symbol.Cons nextH nextT tail
2425− , ValidateColumnsGo nextH nextT acc' cols
2426− ) =>
2427− ValidateColumnsGo h tail acc cols
2428−
2429−class FlushColumnWord :: Symbol -> Row Type -> Constraint
2430−class FlushColumnWord word cols
2431−
2432−instance FlushColumnWord "" cols
2433−else instance FlushColumnWord "$" cols
2434−else instance FlushColumnWord "AND" cols
2435−else instance FlushColumnWord "OR" cols
2436−else instance FlushColumnWord "NOT" cols
2437−else instance FlushColumnWord "IS" cols
2438−else instance FlushColumnWord "NULL" cols
2439−else instance FlushColumnWord "LIKE" cols
2440−else instance FlushColumnWord "ILIKE" cols
2441−else instance FlushColumnWord "IN" cols
2442−else instance FlushColumnWord "TRUE" cols
2443−else instance FlushColumnWord "FALSE" cols
2444−else instance FlushColumnWord "BETWEEN" cols
2445−else instance FlushColumnWord "ANY" cols
2446−else instance FlushColumnWord "ALL" cols
2447−else instance
2448− ( Symbol.Cons head rest word
2449− , FlushColumnWordByHead head word cols
2450− ) =>
2451− FlushColumnWord word cols
2452−
2453−class FlushColumnWordByHead :: Symbol -> Symbol -> Row Type -> Constraint
2454−class FlushColumnWordByHead head word cols
2455−
2456−instance FlushColumnWordByHead "$" word cols
2457−else instance FlushColumnWordByHead "0" word cols
2458−else instance FlushColumnWordByHead "1" word cols
2459−else instance FlushColumnWordByHead "2" word cols
2460−else instance FlushColumnWordByHead "3" word cols
2461−else instance FlushColumnWordByHead "4" word cols
2462−else instance FlushColumnWordByHead "5" word cols
2463−else instance FlushColumnWordByHead "6" word cols
2464−else instance FlushColumnWordByHead "7" word cols
2465−else instance FlushColumnWordByHead "8" word cols
2466−else instance FlushColumnWordByHead "9" word cols
2467−else instance Row.Cons word typ rest cols => FlushColumnWordByHead head word cols
2468−else instance
2469− Fail (Beside (Beside (Text "Column ") (Quote word)) (Text " does not exist in the table")) =>
2470− FlushColumnWordByHead head word cols
2471−
2472−class ValidateAfterName :: Symbol -> Symbol -> Row Type -> Constraint
2473−class ValidateAfterName acc rest cols
2474−
2475−instance FlushColumnWord acc cols => ValidateAfterName acc "" cols
2476−else instance
2477− ( FlushColumnWord acc cols
2478− , Symbol.Cons h t rest
2479− , ValidateAfterNameByHead h t cols
2480− ) =>
2481− ValidateAfterName acc rest cols
2482−
2483−class ValidateAfterNameByHead :: Symbol -> Symbol -> Row Type -> Constraint
2484−class ValidateAfterNameByHead head tail cols
2485−
2486−instance
2487− ( SkipSpaces tail rest
2488− , ValidateColumns rest cols
2489− ) =>
2490− ValidateAfterNameByHead "," tail cols
2491−
2492−else instance
2493− ( Symbol.Append h t rest
2494− , ExtractWord rest word afterWord
2495− , HandleAfterColumnWord word afterWord cols
2496− ) =>
2497− ValidateAfterNameByHead h t cols
2498−
2499−class HandleAfterColumnWord :: Symbol -> Symbol -> Row Type -> Constraint
2500−class HandleAfterColumnWord word rest cols
2501−
2502−instance SkipAlias rest cols => HandleAfterColumnWord "AS" rest cols
2503−else instance SkipAlias rest cols => HandleAfterColumnWord "as" rest cols
2504−
2505−class SkipAlias :: Symbol -> Row Type -> Constraint
2506−class SkipAlias sym cols
2507−
2508−instance SkipAlias "" cols
2509−else instance
2510− ( ExtractWord sym _alias afterAlias
2511− , SkipSpaces afterAlias rest
2512− , ExpectCommaOrEnd rest cols
2513− ) =>
2514− SkipAlias sym cols
2515−
2516−class ExpectCommaOrEnd :: Symbol -> Row Type -> Constraint
2517−class ExpectCommaOrEnd sym cols
2518−
2519−instance ExpectCommaOrEnd "" cols
2520−else instance
2521− ( Symbol.Cons h t sym
2522− , ExpectCommaOrEndByHead h t cols
2523− ) =>
2524− ExpectCommaOrEnd sym cols
2525−
2526−class ExpectCommaOrEndByHead :: Symbol -> Symbol -> Row Type -> Constraint
2527−class ExpectCommaOrEndByHead head tail cols
2528−
2529−instance
2530− ( SkipSpaces tail rest
2531− , ValidateColumns rest cols
2532− ) =>
2533− ExpectCommaOrEndByHead "," tail cols
2534−
2535−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2536−-- ValidateJoinCondition: validate ON clause column references
2537−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2538−
2539−class ValidateJoinCondition :: Symbol -> Row (Row Type) -> Constraint
2540−class ValidateJoinCondition sym tables
2541−
2542−instance ValidateJoinCondition "" tables
2543−else instance
2544− ( Symbol.Cons h t sym
2545− , ValidateJoinCondGo h t "" tables
2546− ) =>
2547− ValidateJoinCondition sym tables
2548−
2549−class ValidateJoinCondGo :: Symbol -> Symbol -> Symbol -> Row (Row Type) -> Constraint
2550−class ValidateJoinCondGo head tail acc tables
2551−
2552−-- Space: flush word, continue
2553−instance
2554− ( FlushJoinWord acc tables
2555− , SkipSpaces tail rest
2556− , ValidateJoinCondition rest tables
2557− ) =>
2558− ValidateJoinCondGo " " tail acc tables
2559−
2560−-- Operators
2561−else instance (FlushJoinWord acc tables, ValidateJoinCondition tail tables) => ValidateJoinCondGo "=" tail acc tables
2562−else instance (FlushJoinWord acc tables, ValidateJoinCondition tail tables) => ValidateJoinCondGo ">" tail acc tables
2563−else instance (FlushJoinWord acc tables, ValidateJoinCondition tail tables) => ValidateJoinCondGo "<" tail acc tables
2564−else instance (FlushJoinWord acc tables, ValidateJoinCondition tail tables) => ValidateJoinCondGo "!" tail acc tables
2565−else instance (FlushJoinWord acc tables, ValidateJoinCondition tail tables) => ValidateJoinCondGo "(" tail acc tables
2566−else instance (FlushJoinWord acc tables, ValidateJoinCondition tail tables) => ValidateJoinCondGo ")" tail acc tables
2567−
2568−-- String literal: skip content between quotes
2569−else instance
2570− ( FlushJoinWord acc tables
2571− , SkipStringLiteral tail rest
2572− , ValidateJoinCondition rest tables
2573− ) =>
2574− ValidateJoinCondGo "'" tail acc tables
2575−
2576−-- End of string: flush final word
2577−else instance
2578− ( Symbol.Append acc h acc'
2579− , FlushJoinWord acc' tables
2580− ) =>
2581− ValidateJoinCondGo h "" acc tables
2582−
2583−-- Regular char (including dot): accumulate
2584−else instance
2585− ( Symbol.Append acc h acc'
2586− , Symbol.Cons nextH nextT tail
2587− , ValidateJoinCondGo nextH nextT acc' tables
2588− ) =>
2589− ValidateJoinCondGo h tail acc tables
2590−
2591−class FlushJoinWord :: Symbol -> Row (Row Type) -> Constraint
2592−class FlushJoinWord word tables
2593−
2594−instance FlushJoinWord "" tables
2595−else instance FlushJoinWord "AND" tables
2596−else instance FlushJoinWord "OR" tables
2597−else instance FlushJoinWord "NOT" tables
2598−else instance FlushJoinWord "IS" tables
2599−else instance FlushJoinWord "NULL" tables
2600−else instance FlushJoinWord "TRUE" tables
2601−else instance FlushJoinWord "FALSE" tables
2602−else instance
2603− ( Symbol.Cons head rest word
2604− , FlushJoinWordByHead head word tables
2605− ) =>
2606− FlushJoinWord word tables
2607−
2608−class FlushJoinWordByHead :: Symbol -> Symbol -> Row (Row Type) -> Constraint
2609−class FlushJoinWordByHead head word tables
2610−
2611−instance FlushJoinWordByHead "$" word tables
2612−else instance FlushJoinWordByHead "0" word tables
2613−else instance FlushJoinWordByHead "1" word tables
2614−else instance FlushJoinWordByHead "2" word tables
2615−else instance FlushJoinWordByHead "3" word tables
2616−else instance FlushJoinWordByHead "4" word tables
2617−else instance FlushJoinWordByHead "5" word tables
2618−else instance FlushJoinWordByHead "6" word tables
2619−else instance FlushJoinWordByHead "7" word tables
2620−else instance FlushJoinWordByHead "8" word tables
2621−else instance FlushJoinWordByHead "9" word tables
2622−else instance ResolveColumn word tables typ => FlushJoinWordByHead head word tables
2623−
2624−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2625−-- ON CONFLICT
2626−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2627−
2628−class ParseConflictAction :: Symbol -> Row Type -> Constraint
2629−class ParseConflictAction sym cols
2630−
2631−instance
2632− ( ExtractWord sym w1 rest1
2633− , ExpectKeyword w1 "DO"
2634− , ExtractWord rest1 w2 rest2
2635− , ExpectKeyword w2 "UPDATE"
2636− , ExtractWord rest2 w3 rest3
2637− , ExpectKeyword w3 "SET"
2638− , ParseAssignments rest3 cols
2639− ) =>
2640− ParseConflictAction sym cols
2641−
2642−class ExpectKeyword :: Symbol -> Symbol -> Constraint
2643−class ExpectKeyword actual expected
2644−
2645−instance ExpectKeyword a a
2646−else instance
2647− Fail (Beside (Beside (Text "Expected keyword ") (Quote expected)) (Beside (Text " but got ") (Quote actual))) =>
2648− ExpectKeyword actual expected
2649−
2650−class ParseAssignments :: Symbol -> Row Type -> Constraint
2651−class ParseAssignments sym cols
2652−
2653−instance ParseAssignments "" cols
2654−else instance
2655− ( ExtractWord sym colName rest1
2656− , Row.Cons colName colType colRest cols
2657− , SkipSpaces rest1 rest2
2658− , ExpectChar rest2 "=" rest3
2659− , SkipSpaces rest3 rest4
2660− , SkipExpression rest4 rest5
2661− , ParseAssignmentsContinue rest5 cols
2662− ) =>
2663− ParseAssignments sym cols
2664−
2665−-- | Skip a SQL expression on the RHS of an assignment.
2666−-- | Handles balanced parentheses so that e.g. now() and
2667−-- | COALESCE(EXCLUDED.value, '') are consumed as a single expression.
2668−-- | Stops at an unparenthesised comma or end-of-string.
2669−class SkipExpression :: Symbol -> Symbol -> Constraint
2670−class SkipExpression sym rest | sym -> rest
2671−
2672−instance SkipExpression "" ""
2673−else instance
2674− ( Symbol.Cons h t sym
2675− , SkipExpressionGo h t rest
2676− ) =>
2677− SkipExpression sym rest
2678−
2679−class SkipExpressionGo :: Symbol -> Symbol -> Symbol -> Constraint
2680−class SkipExpressionGo head tail rest | head tail -> rest
2681−
2682−-- Comma at depth 0 → stop, keep comma in rest
2683−instance Symbol.Cons "," tail rest => SkipExpressionGo "," tail rest
2684−-- Open paren → enter nested expression
2685−else instance
2686− ( SkipNested tail rest1
2687− , SkipExpression rest1 rest
2688− ) =>
2689− SkipExpressionGo "(" tail rest
2690−-- Space → skip and continue (expressions can have spaces, e.g. "EXCLUDED.col")
2691−else instance (SkipSpaces tail rest1, SkipExpressionAfterSpace rest1 rest) => SkipExpressionGo " " tail rest
2692−-- End of string after this char
2693−else instance SkipExpressionGo h "" ""
2694−-- Regular char → continue
2695−else instance (Symbol.Cons h2 t2 tail, SkipExpressionGo h2 t2 rest) => SkipExpressionGo h tail rest
2696−
2697−-- | After a space in an expression, check if we've hit a comma or a new assignment (word = ...).
2698−-- | If the next word is a column name followed by '=', this is a new assignment, so stop.
2699−-- | Otherwise, continue the expression.
2700−class SkipExpressionAfterSpace :: Symbol -> Symbol -> Constraint
2701−class SkipExpressionAfterSpace sym rest | sym -> rest
2702−
2703−instance SkipExpressionAfterSpace "" ""
2704−else instance
2705− ( Symbol.Cons h t sym
2706− , SkipExpressionAfterSpaceGo h t sym rest
2707− ) =>
2708− SkipExpressionAfterSpace sym rest
2709−
2710−class SkipExpressionAfterSpaceGo :: Symbol -> Symbol -> Symbol -> Symbol -> Constraint
2711−class SkipExpressionAfterSpaceGo head tail sym rest | head tail sym -> rest
2712−
2713−-- Comma → stop
2714−instance Symbol.Cons "," tail rest => SkipExpressionAfterSpaceGo "," tail sym rest
2715−-- Otherwise continue the expression
2716−else instance (SkipExpressionGo h tail rest) => SkipExpressionAfterSpaceGo h tail sym rest
2717−
2718−-- | Skip a parenthesised group (after the opening paren has been consumed).
2719−-- | Handles nesting.
2720−class SkipNested :: Symbol -> Symbol -> Constraint
2721−class SkipNested sym rest | sym -> rest
2722−
2723−instance
2724− ( Symbol.Cons h t sym
2725− , SkipNestedGo h t rest
2726− ) =>
2727− SkipNested sym rest
2728−
2729−class SkipNestedGo :: Symbol -> Symbol -> Symbol -> Constraint
2730−class SkipNestedGo head tail rest | head tail -> rest
2731−
2732−-- Close paren → done
2733−instance SkipNestedGo ")" tail tail
2734−-- Open paren → recurse
2735−else instance (SkipNested tail rest1, SkipNested rest1 rest) => SkipNestedGo "(" tail rest
2736−-- Any other char → continue
2737−else instance (SkipNested tail rest) => SkipNestedGo h tail rest
2738−
2739−class ParseAssignmentsContinue :: Symbol -> Row Type -> Constraint
2740−class ParseAssignmentsContinue sym cols
2741−
2742−instance ParseAssignmentsContinue "" cols
2743−else instance
2744− ( Symbol.Cons h t sym
2745− , ParseAssignmentsContinueByHead h t cols
2746− ) =>
2747− ParseAssignmentsContinue sym cols
2748−
2749−class ParseAssignmentsContinueByHead :: Symbol -> Symbol -> Row Type -> Constraint
2750−class ParseAssignmentsContinueByHead head tail cols
2751−
2752−instance
2753− ( SkipSpaces tail rest
2754− , ParseAssignments rest cols
2755− ) =>
2756− ParseAssignmentsContinueByHead "," tail cols
2757−
2758−class ExpectChar :: Symbol -> Symbol -> Symbol -> Constraint
2759−class ExpectChar sym char rest | sym char -> rest
2760−
2761−instance
2762− ( Symbol.Cons h t sym
2763− , ExpectCharMatch h t char rest
2764− ) =>
2765− ExpectChar sym char rest
2766−
2767−class ExpectCharMatch :: Symbol -> Symbol -> Symbol -> Symbol -> Constraint
2768−class ExpectCharMatch head tail expected rest | head tail expected -> rest
2769−
2770−instance ExpectCharMatch c tail c tail
2771−else instance
2772− Fail (Beside (Beside (Text "Expected '") (Quote expected)) (Beside (Text "' but got '") (Quote head))) =>
2773− ExpectCharMatch head tail expected rest
2774−
2775−class ValidateExcludedRef :: Symbol -> Symbol -> Constraint
2776−class ValidateExcludedRef ref colName
2777−
2778−instance
2779− ( Symbol.Append "EXCLUDED." colName expected
2780− , MatchSymbol ref expected
2781− ) =>
2782− ValidateExcludedRef ref colName
2783−
2784−class MatchSymbol :: Symbol -> Symbol -> Constraint
2785−class MatchSymbol a b
2786−
2787−instance MatchSymbol a a
2788−else instance
2789− Fail (Beside (Beside (Text "Expected ") (Quote expected)) (Beside (Text " but got ") (Quote actual))) =>
2790− MatchSymbol actual expected
2791−
2792−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2793−-- Type-level IsAutoGenerated (Boolean kind)
2794−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2795−
2796−class IsAutoGeneratedTC :: Type -> Boolean -> Constraint
2797−class IsAutoGeneratedTC constraints result | constraints -> result
2798−
2799−instance IsAutoGeneratedTC (AutoIncrement a) True
2800−else instance IsAutoGeneratedTC (Default s a) True
2801−else instance IsAutoGeneratedTC (DefaultExpr expression a) True
2802−else instance IsAutoGeneratedTC a result => IsAutoGeneratedTC (PrimaryKey a) result
2803−else instance IsAutoGeneratedTC a result => IsAutoGeneratedTC (Unique a) result
2804−else instance IsAutoGeneratedTC a result => IsAutoGeneratedTC (ForeignKey t r c a) result
2805−else instance IsAutoGeneratedTC a result => IsAutoGeneratedTC (Nullable a) result
2806−else instance IsAutoGeneratedTC a False
2807−
2808−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2809−-- InsertableColumnsRL: filter out auto-generated columns
2810−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2811−
2812−class InsertableColumnsRL :: RL.RowList Type -> RL.RowList Type -> Constraint
2813−class InsertableColumnsRL tableRL outRL | tableRL -> outRL
2814−
2815−instance InsertableColumnsRL RL.Nil RL.Nil
2816−instance
2817− ( IsAutoGeneratedTC entry isAuto
2818− , ExtractType entry typ
2819− , InsertableColumnDecide isAuto name typ tail outRL
2820− ) =>
2821− InsertableColumnsRL (RL.Cons name entry tail) outRL
2822−
2823−class InsertableColumnDecide :: Boolean -> Symbol -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
2824−class InsertableColumnDecide isAuto name typ tail outRL | isAuto name typ tail -> outRL
2825−
2826−instance InsertableColumnsRL tail outRL => InsertableColumnDecide True name typ tail outRL
2827−instance InsertableColumnsRL tail outRL => InsertableColumnDecide False name typ tail (RL.Cons name typ outRL)
2828−
2829−-- Split insertable columns into required (non-Maybe) and optional (Maybe)
2830−class RequiredColumnsRL :: RL.RowList Type -> RL.RowList Type -> Constraint
2831−class RequiredColumnsRL rl out | rl -> out
2832−
2833−instance RequiredColumnsRL RL.Nil RL.Nil
2834−instance RequiredColumnsRL tail out => RequiredColumnsRL (RL.Cons name (Maybe a) tail) out
2835−else instance RequiredColumnsRL tail out => RequiredColumnsRL (RL.Cons name typ tail) (RL.Cons name typ out)
2836−
2837−-- Generate column names from a RowList
2838−class ColumnNamesRL :: RL.RowList Type -> Constraint
2839−class ColumnNamesRL rl where
2840− columnNamesRL :: Proxy rl -> Array String
2841−
2842−instance ColumnNamesRL RL.Nil where
2843− columnNamesRL _ = []
2844−
2845−instance (IsSymbol name, ColumnNamesRL tail) => ColumnNamesRL (RL.Cons name typ tail) where
2846− columnNamesRL _ = [ reflectSymbol (Proxy :: Proxy name) ] <> columnNamesRL (Proxy :: Proxy tail)
2847−
2848−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2849−-- RecordValuesRL: extract record values in RowList order
2850−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2851−
2852−class FieldToPGValue :: Type -> Constraint
2853−class FieldToPGValue a where
2854− fieldToPGValue :: a -> PG.PGValue
2855−
2856−instance FieldToPGValue a => FieldToPGValue (Maybe a) where
2857− fieldToPGValue = case _ of
2858− Nothing -> unsafeCoerce (toNullable Nothing)
2859− Just value -> fieldToPGValue value
2860−else instance FieldToPGValue DateTime where
2861− fieldToPGValue = JSDate.fromDateTime >>> unsafeCoerce
2862−else instance FieldToPGValue TimestampTZ where
2863− fieldToPGValue (TimestampTZ dt) = JSDate.fromDateTime dt # unsafeCoerce
2864−else instance FieldToPGValue PGDate where
2865− fieldToPGValue = writeImpl >>> unsafeCoerce
2866−else instance FieldToPGValue PGUUID where
2867− fieldToPGValue = unsafeCoerce
2868−else instance WriteForeign a => FieldToPGValue a where
2869− fieldToPGValue = writeImpl >>> unsafeCoerce
2870−
2871−class RecordValuesRL :: RL.RowList Type -> Row Type -> Constraint
2872−class RecordValuesRL rl row where
2873− recordValuesRL :: Proxy rl -> { | row } -> Array PG.PGValue
2874−
2875−instance RecordValuesRL RL.Nil row where
2876− recordValuesRL _ _ = []
2877−
2878−instance
2879− ( IsSymbol name
2880− , Row.Cons name typ rest row
2881− , FieldToPGValue typ
2882− , RecordValuesRL tail row
2883− ) =>
2884− RecordValuesRL (RL.Cons name typ tail) row where
2885− recordValuesRL _ rec =
2886− [ fieldToPGValue (Record.get (Proxy :: Proxy name) rec) ]
2887− <> recordValuesRL (Proxy :: Proxy tail) rec
2888−
2889−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2890−-- MakeNullableRL: wrap column types in Maybe for LEFT JOIN
2891−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2892−
2893−class MakeNullableRL :: RL.RowList Type -> RL.RowList Type -> Constraint
2894−class MakeNullableRL rl out | rl -> out
2895−
2896−instance MakeNullableRL RL.Nil RL.Nil
2897−instance
2898− ( ExtractType entry typ
2899− , MakeNullableDecide typ name entry tail out
2900− ) =>
2901− MakeNullableRL (RL.Cons name entry tail) out
2902−
2903−class MakeNullableDecide :: Type -> Symbol -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
2904−class MakeNullableDecide typ name entry tail out | typ name entry tail -> out
2905−
2906−-- Already nullable: keep as-is
2907−instance MakeNullableRL tail out' => MakeNullableDecide (Maybe a) name entry tail (RL.Cons name entry out')
2908−-- Not nullable: wrap in Nullable
2909−else instance MakeNullableRL tail out' => MakeNullableDecide typ name entry tail (RL.Cons name (Nullable entry) out')
2910−
2911−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2912−-- Query builder: unified Q type
2913−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2914−
2915−newtype Q :: Row (Row Type) -> Row Type -> Row Type -> Row Type -> Type
2916−newtype Q tables result params stage = Q { sql :: String, values :: Array PG.PGValue }
2917−
2918−class HasClause :: Symbol -> Row Type -> Constraint
2919−class HasClause label row
2920−
2921−instance Row.Cons label Unit rest row => HasClause label row
2922−
2923−class HasAnyDML :: Row Type -> Constraint
2924−class HasAnyDML stage
2925−
2926−instance (RL.RowToList stage rl, HasAnyDMLRL rl) => HasAnyDML stage
2927−
2928−class HasAnyDMLRL :: RL.RowList Type -> Constraint
2929−class HasAnyDMLRL rl
2930−
2931−instance HasAnyDMLRL (RL.Cons "select" Unit rest)
2932−else instance HasAnyDMLRL (RL.Cons "set" Unit rest)
2933−else instance HasAnyDMLRL (RL.Cons "delete" Unit rest)
2934−else instance HasAnyDMLRL rest => HasAnyDMLRL (RL.Cons label typ rest)
2935−else instance Fail (Text "WHERE requires a preceding SELECT, UPDATE (set), or DELETE") => HasAnyDMLRL RL.Nil
2936−
2937−class HasMutation :: Row Type -> Constraint
2938−class HasMutation stage
2939−
2940−instance (RL.RowToList stage rl, HasMutationRL rl) => HasMutation stage
2941−
2942−class HasMutationRL :: RL.RowList Type -> Constraint
2943−class HasMutationRL rl
2944−
2945−instance HasMutationRL (RL.Cons "insert" Unit rest)
2946−else instance HasMutationRL (RL.Cons "set" Unit rest)
2947−else instance HasMutationRL (RL.Cons "delete" Unit rest)
2948−else instance HasMutationRL rest => HasMutationRL (RL.Cons label typ rest)
2949−else instance Fail (Text "RETURNING requires a preceding INSERT, UPDATE (set), or DELETE") => HasMutationRL RL.Nil
2950−
2951−toSQL :: forall tables result params stage. Q tables result params stage -> String
2952−toSQL (Q q) = q.sql
2953−
2954−from :: forall name cols tables. IsSymbol name => Row.Cons name cols () tables => Proxy (Table name cols) -> Q tables () () ()
2955−from _ = Q { sql: reflectSymbol (Proxy :: Proxy name), values: [] }
2956−
2957−fromAs :: forall @alias name cols tables. IsSymbol name => IsSymbol alias => Row.Cons alias cols () tables => Proxy (Table name cols) -> Q tables () () ()
2958−fromAs _ = Q { sql: reflectSymbol (Proxy :: Proxy name) <> " " <> reflectSymbol (Proxy :: Proxy alias), values: [] }
2959−
2960−into :: forall name cols tables. IsSymbol name => Row.Cons name cols () tables => Proxy (Table name cols) -> Q tables () () (into :: Unit)
2961−into _ = Q { sql: reflectSymbol (Proxy :: Proxy name), values: [] }
2962−
2963−update :: forall name cols tables. IsSymbol name => Row.Cons name cols () tables => Proxy (Table name cols) -> Q tables () () (update :: Unit)
2964−update _ = Q { sql: reflectSymbol (Proxy :: Proxy name), values: [] }
2965−
2966−selectAll
2967− :: forall tables name cols result r p stage stage'
2968− . SingleTable tables name cols
2969− => IsSymbol name
2970− => StripColumns cols result
2971− => Row.Lacks "select" stage
2972− => Row.Lacks "insert" stage
2973− => Row.Lacks "into" stage
2974− => Row.Lacks "set" stage
2975− => Row.Lacks "delete" stage
2976− => Row.Lacks "where" stage
2977− => Row.Lacks "orderBy" stage
2978− => Row.Lacks "limit" stage
2979− => Row.Lacks "offset" stage
2980− => Row.Cons "select" Unit stage stage'
2981− => Q tables r p stage
2982− -> Q tables result p stage'
2983−selectAll (Q q) = Q (q { sql = "SELECT * FROM " <> q.sql })
2984−
2985−select
2986− :: forall @sel tables result r p stage stage'
2987− . IsSymbol sel
2988− => ParseSelect sel tables result
2989− => Row.Lacks "select" stage
2990− => Row.Lacks "insert" stage
2991− => Row.Lacks "into" stage
2992− => Row.Lacks "set" stage
2993− => Row.Lacks "delete" stage
2994− => Row.Lacks "where" stage
2995− => Row.Lacks "orderBy" stage
2996− => Row.Lacks "limit" stage
2997− => Row.Lacks "offset" stage
2998− => Row.Cons "select" Unit stage stage'
2999− => Q tables r p stage
3000− -> Q tables result p stage'
3001−select (Q q) = Q (q { sql = "SELECT " <> reflectSymbol (Proxy :: Proxy sel) <> " FROM " <> q.sql })
3002−
3003−selectDistinct
3004− :: forall @sel tables result r p stage stage' stage''
3005− . IsSymbol sel
3006− => ParseSelect sel tables result
3007− => Row.Lacks "select" stage
3008− => Row.Lacks "insert" stage
3009− => Row.Lacks "into" stage
3010− => Row.Lacks "set" stage
3011− => Row.Lacks "delete" stage
3012− => Row.Lacks "where" stage
3013− => Row.Lacks "orderBy" stage
3014− => Row.Lacks "limit" stage
3015− => Row.Lacks "offset" stage
3016− => Row.Cons "select" Unit stage stage'
3017− => Row.Cons "distinct" Unit stage' stage''
3018− => Q tables r p stage
3019− -> Q tables result p stage''
3020−selectDistinct (Q q) = Q (q { sql = "SELECT DISTINCT " <> reflectSymbol (Proxy :: Proxy sel) <> " FROM " <> q.sql })
3021−
3022−selectDistinctOn
3023− :: forall @on @sel tables result r p stage stage'
3024− . IsSymbol on
3025− => IsSymbol sel
3026− => ValidateColumnList on tables
3027− => ParseSelect sel tables result
3028− => Row.Lacks "select" stage
3029− => Row.Lacks "insert" stage
3030− => Row.Lacks "into" stage
3031− => Row.Lacks "set" stage
3032− => Row.Lacks "delete" stage
3033− => Row.Lacks "where" stage
3034− => Row.Lacks "orderBy" stage
3035− => Row.Lacks "limit" stage
3036− => Row.Lacks "offset" stage
3037− => Row.Cons "select" Unit stage stage'
3038− => Q tables r p stage
3039− -> Q tables result p stage'
3040−selectDistinctOn (Q q) = Q (q { sql = "SELECT DISTINCT ON (" <> reflectSymbol (Proxy :: Proxy on) <> ") " <> reflectSymbol (Proxy :: Proxy sel) <> " FROM " <> q.sql })
3041−
3042−where_
3043− :: forall @whr tables result params p stage stage'
3044− . IsSymbol whr
3045− => ParseWhere whr tables params
3046− => HasAnyDML stage
3047− => Row.Lacks "where" stage
3048− => Row.Lacks "insert" stage
3049− => Row.Lacks "groupBy" stage
3050− => Row.Lacks "having" stage
3051− => Row.Lacks "orderBy" stage
3052− => Row.Lacks "limit" stage
3053− => Row.Lacks "offset" stage
3054− => Row.Cons "where" Unit stage stage'
3055− => Q tables result p stage
3056− -> Q tables result params stage'
3057−where_ (Q q) = Q (q { sql = q.sql <> " WHERE " <> reflectSymbol (Proxy :: Proxy whr) })
3058−
3059−orderBy
3060− :: forall @cols tables result params stage stage'
3061− . IsSymbol cols
3062− => ValidateOrderBy cols tables
3063− => CheckDistinctOrderBy stage cols result
3064− => HasClause "select" stage
3065− => Row.Lacks "orderBy" stage
3066− => Row.Lacks "limit" stage
3067− => Row.Lacks "offset" stage
3068− => Row.Cons "orderBy" Unit stage stage'
3069− => Q tables result params stage
3070− -> Q tables result params stage'
3071−orderBy (Q q) = Q (q { sql = q.sql <> " ORDER BY " <> reflectSymbol (Proxy :: Proxy cols) })
3072−
3073−groupBy
3074− :: forall @cols tables result params stage stage'
3075− . IsSymbol cols
3076− => ValidateColumnList cols tables
3077− => HasClause "select" stage
3078− => Row.Lacks "groupBy" stage
3079− => Row.Lacks "having" stage
3080− => Row.Lacks "orderBy" stage
3081− => Row.Lacks "limit" stage
3082− => Row.Lacks "offset" stage
3083− => Row.Cons "groupBy" Unit stage stage'
3084− => Q tables result params stage
3085− -> Q tables result params stage'
3086−groupBy (Q q) = Q (q { sql = q.sql <> " GROUP BY " <> reflectSymbol (Proxy :: Proxy cols) })
3087−
3088−having
3089− :: forall @cond tables result params havingParams allParams stage stage'
3090− . IsSymbol cond
3091− => ParseWhere cond tables havingParams
3092− => Row.Union params havingParams allParams
3093− => Row.Nub allParams allParams
3094− => HasClause "groupBy" stage
3095− => Row.Lacks "having" stage
3096− => Row.Lacks "orderBy" stage
3097− => Row.Lacks "limit" stage
3098− => Row.Lacks "offset" stage
3099− => Row.Cons "having" Unit stage stage'
3100− => Q tables result params stage
3101− -> Q tables result allParams stage'
3102−having (Q q) = Q (q { sql = q.sql <> " HAVING " <> reflectSymbol (Proxy :: Proxy cond) })
3103−
3104−class IsLimitParam :: Symbol -> Symbol -> Boolean -> Constraint
3105−class IsLimitParam head tail isParam | head tail -> isParam
3106−
3107−instance IsLimitParam "$" tail True
3108−else instance IsLimitParam head tail False
3109−
3110−class ParseLimitOffsetParams :: Boolean -> Symbol -> Row Type -> Row Type -> Constraint
3111−class ParseLimitOffsetParams isParam name params params' | isParam name params -> params'
3112−
3113−instance
3114− ( Row.Lacks name params
3115− , Row.Cons name Int params params'
3116− ) =>
3117− ParseLimitOffsetParams True name params params'
3118−
3119−instance ParseLimitOffsetParams False name params params
3120−
3121−-- Validate that a Symbol is a non-negative integer (all digits, non-empty)
3122−class ValidateNumericLiteral :: Symbol -> Constraint
3123−class ValidateNumericLiteral sym
3124−
3125−instance
3126− ( Symbol.Cons h t sym
3127− , ValidateAllDigits h t
3128− ) =>
3129− ValidateNumericLiteral sym
3130−
3131−class ValidateAllDigits :: Symbol -> Symbol -> Constraint
3132−class ValidateAllDigits head tail
3133−
3134−instance ValidateAllDigits "0" ""
3135−else instance ValidateAllDigits "1" ""
3136−else instance ValidateAllDigits "2" ""
3137−else instance ValidateAllDigits "3" ""
3138−else instance ValidateAllDigits "4" ""
3139−else instance ValidateAllDigits "5" ""
3140−else instance ValidateAllDigits "6" ""
3141−else instance ValidateAllDigits "7" ""
3142−else instance ValidateAllDigits "8" ""
3143−else instance ValidateAllDigits "9" ""
3144−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "0" tail
3145−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "1" tail
3146−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "2" tail
3147−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "3" tail
3148−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "4" tail
3149−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "5" tail
3150−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "6" tail
3151−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "7" tail
3152−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "8" tail
3153−else instance (Symbol.Cons h2 t2 tail, ValidateAllDigits h2 t2) => ValidateAllDigits "9" tail
3154−else instance
3155− Fail (Beside (Text "LIMIT/OFFSET must be a non-negative integer or $parameter, got: ") (Quote sym)) =>
3156− ValidateAllDigits head tail
3157−
3158−class ParseLimitOffset :: Symbol -> Row Type -> Row Type -> Constraint
3159−class ParseLimitOffset sym params params' | sym params -> params'
3160−
3161−instance
3162− ( Symbol.Cons head tail sym
3163− , IsLimitParam head tail isParam
3164− , ParseLimitOffsetBranch isParam sym tail params params'
3165− ) =>
3166− ParseLimitOffset sym params params'
3167−
3168−class ParseLimitOffsetBranch :: Boolean -> Symbol -> Symbol -> Row Type -> Row Type -> Constraint
3169−class ParseLimitOffsetBranch isParam sym paramName params params' | isParam sym paramName params -> params'
3170−
3171−instance
3172− ( Row.Lacks paramName params
3173− , Row.Cons paramName Int params params'
3174− ) =>
3175− ParseLimitOffsetBranch True sym paramName params params'
3176−
3177−instance
3178− ValidateNumericLiteral sym =>
3179− ParseLimitOffsetBranch False sym paramName params params
3180−
3181−limit
3182− :: forall @sym tables result params params' stage stage'
3183− . ParseLimitOffset sym params params'
3184− => IsSymbol sym
3185− => HasClause "select" stage
3186− => Row.Lacks "limit" stage
3187− => Row.Cons "limit" Unit stage stage'
3188− => Q tables result params stage
3189− -> Q tables result params' stage'
3190−limit (Q q) = Q (q { sql = q.sql <> " LIMIT " <> reflectSymbol (Proxy :: Proxy sym) })
3191−
3192−offset
3193− :: forall @sym tables result params params' stage stage'
3194− . ParseLimitOffset sym params params'
3195− => IsSymbol sym
3196− => HasClause "select" stage
3197− => Row.Lacks "offset" stage
3198− => Row.Cons "offset" Unit stage stage'
3199− => Q tables result params stage
3200− -> Q tables result params' stage'
3201−offset (Q q) = Q (q { sql = q.sql <> " OFFSET " <> reflectSymbol (Proxy :: Proxy sym) })
3202−
3203−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3204−-- INSERT builder
3205−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3206−
3207−insert
3208− :: forall tables name cols colsRL insertableRL insertable requiredRL required
3209− optionalProvided missing userRow userRowRL stage stage'
3210− . SingleTable tables name cols
3211− => RowToList cols colsRL
3212− => InsertableColumnsRL colsRL insertableRL
3213− => ListToRow insertableRL insertable
3214− => RequiredColumnsRL insertableRL requiredRL
3215− => ListToRow requiredRL required
3216− => Row.Union required optionalProvided userRow
3217− => Row.Union userRow missing insertable
3218− => RowToList userRow userRowRL
3219− => ColumnNamesRL userRowRL
3220− => RecordValuesRL userRowRL userRow
3221− => IsSymbol name
3222− => Row.Lacks "select" stage
3223− => Row.Lacks "insert" stage
3224− => Row.Lacks "set" stage
3225− => Row.Lacks "delete" stage
3226− => Row.Cons "insert" Unit stage stage'
3227− => { | userRow }
3228− -> Q tables () () stage
3229− -> Q tables () () stage'
3230−insert rec _ = Q { sql, values }
3231− where
3232− tableName = reflectSymbol (Proxy :: Proxy name)
3233− colNames = columnNamesRL (Proxy :: Proxy userRowRL)
3234− placeholders = colNames # mapWithIndex \i _ -> "$" <> show (i + 1)
3235− sql =
3236− if Array.null colNames then
3237− "INSERT INTO " <> tableName <> " DEFAULT VALUES"
3238− else
3239− "INSERT INTO " <> tableName
3240− <> " ("
3241− <> intercalate ", " colNames
3242− <> ")"
3243− <> " VALUES ("
3244− <> intercalate ", " placeholders
3245− <> ")"
3246− values = recordValuesRL (Proxy :: Proxy userRowRL) rec
3247−
3248−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3249−-- RETURNING clause
3250−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3251−
3252−returning
3253− :: forall @sel tables name cols result p stage stage'
3254− . SingleTable tables name cols
3255− => IsSymbol sel
3256− => ParseSelect sel tables result
3257− => HasMutation stage
3258− => Row.Lacks "returning" stage
3259− => Row.Lacks "select" stage
3260− => Row.Cons "returning" Unit stage stage'
3261− => Q tables () p stage
3262− -> Q tables result p stage'
3263−returning (Q q) = Q (q { sql = q.sql <> " RETURNING " <> reflectSymbol (Proxy :: Proxy sel) })
3264−
3265−returningAll
3266− :: forall tables name cols result p stage stage'
3267− . SingleTable tables name cols
3268− => StripColumns cols result
3269− => HasMutation stage
3270− => Row.Lacks "returning" stage
3271− => Row.Lacks "select" stage
3272− => Row.Cons "returning" Unit stage stage'
3273− => Q tables () p stage
3274− -> Q tables result p stage'
3275−returningAll (Q q) = Q (q { sql = q.sql <> " RETURNING *" })
3276−
3277−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3278−-- UPDATE builder (SET)
3279−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3280−
3281−class ValidateSetColumnsRL :: RL.RowList Type -> Row Type -> Constraint
3282−class ValidateSetColumnsRL rl cols
3283−
3284−instance ValidateSetColumnsRL RL.Nil cols
3285−instance
3286− ( Row.Cons name entry rest cols
3287− , ExtractType entry typ
3288− , ValidateSetColumnsRL tail cols
3289− ) =>
3290− ValidateSetColumnsRL (RL.Cons name typ tail) cols
3291−
3292−class SetClauseRL :: RL.RowList Type -> Constraint
3293−class SetClauseRL rl where
3294− setClauseRL :: Proxy rl -> Int -> Array String
3295−
3296−instance SetClauseRL RL.Nil where
3297− setClauseRL _ _ = []
3298−
3299−instance (IsSymbol name, SetClauseRL tail) => SetClauseRL (RL.Cons name typ tail) where
3300− setClauseRL _ idx =
3301− [ reflectSymbol (Proxy :: Proxy name) <> " = $" <> show idx ]
3302− <> setClauseRL (Proxy :: Proxy tail) (idx + 1)
3303−
3304−set
3305− :: forall tables name cols setRow setRL stage stage'
3306− . SingleTable tables name cols
3307− => RowToList setRow setRL
3308− => IsSymbol name
3309− => ValidateSetColumnsRL setRL cols
3310− => SetClauseRL setRL
3311− => RecordValuesRL setRL setRow
3312− => HasClause "update" stage
3313− => Row.Lacks "select" stage
3314− => Row.Lacks "insert" stage
3315− => Row.Lacks "into" stage
3316− => Row.Lacks "set" stage
3317− => Row.Lacks "delete" stage
3318− => Row.Cons "set" Unit stage stage'
3319− => { | setRow }
3320− -> Q tables () () stage
3321− -> Q tables () () stage'
3322−set rec _ = Q { sql, values }
3323− where
3324− tableName = reflectSymbol (Proxy :: Proxy name)
3325− setClauses = setClauseRL (Proxy :: Proxy setRL) 1
3326− sql = "UPDATE " <> tableName <> " SET " <> intercalate ", " setClauses
3327− values = recordValuesRL (Proxy :: Proxy setRL) rec
3328−
3329−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3330−-- DELETE builder
3331−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3332−
3333−delete
3334− :: forall tables name cols r p stage stage'
3335− . SingleTable tables name cols
3336− => IsSymbol name
3337− => Row.Lacks "select" stage
3338− => Row.Lacks "insert" stage
3339− => Row.Lacks "into" stage
3340− => Row.Lacks "set" stage
3341− => Row.Lacks "delete" stage
3342− => Row.Cons "delete" Unit stage stage'
3343− => Q tables r p stage
3344− -> Q tables () () stage'
3345−delete _ = Q { sql: "DELETE FROM " <> reflectSymbol (Proxy :: Proxy name), values: [] }
3346−
3347−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3348−-- ON CONFLICT
3349−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3350−
3351−onConflict
3352− :: forall @target @action tables name cols result params stage stage'
3353− . SingleTable tables name cols
3354− => IsSymbol target
3355− => IsSymbol action
3356− => ValidateColumns target cols
3357− => ParseConflictAction action cols
3358− => HasClause "insert" stage
3359− => Row.Lacks "conflict" stage
3360− => Row.Cons "conflict" Unit stage stage'
3361− => Q tables result params stage
3362− -> Q tables result params stage'
3363−onConflict (Q q) = Q
3364− ( q
3365− { sql = q.sql
3366− <> " ON CONFLICT ("
3367− <> reflectSymbol (Proxy :: Proxy target)
3368− <> ") "
3369− <> reflectSymbol (Proxy :: Proxy action)
3370− }
3371− )
3372−
3373−onConflictDoNothing
3374− :: forall @target tables name cols result params stage stage'
3375− . SingleTable tables name cols
3376− => IsSymbol target
3377− => ValidateColumns target cols
3378− => HasClause "insert" stage
3379− => Row.Lacks "conflict" stage
3380− => Row.Cons "conflict" Unit stage stage'
3381− => Q tables result params stage
3382− -> Q tables result params stage'
3383−onConflictDoNothing (Q q) = Q
3384− ( q
3385− { sql = q.sql
3386− <> " ON CONFLICT ("
3387− <> reflectSymbol (Proxy :: Proxy target)
3388− <> ") DO NOTHING"
3389− }
3390− )
3391−
3392−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3393−-- JOIN builders
3394−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3395−
3396−innerJoin
3397− :: forall @cond name cols tables tables' r p stage
3398− . IsSymbol name
3399− => IsSymbol cond
3400− => Row.Lacks name tables
3401− => Row.Cons name cols tables tables'
3402− => ValidateJoinCondition cond tables'
3403− => Row.Lacks "select" stage
3404− => Row.Lacks "insert" stage
3405− => Row.Lacks "set" stage
3406− => Row.Lacks "delete" stage
3407− => Proxy (Table name cols)
3408− -> Q tables r p stage
3409− -> Q tables' () () (join :: Unit)
3410−innerJoin _ (Q q) = Q
3411− { sql: q.sql
3412− <> " INNER JOIN "
3413− <> reflectSymbol (Proxy :: Proxy name)
3414− <> " ON "
3415− <> reflectSymbol (Proxy :: Proxy cond)
3416− , values: q.values
3417− }
3418−
3419−leftJoin
3420− :: forall @cond name cols colsRL nullableColsRL nullableCols tables tables' r p stage
3421− . IsSymbol name
3422− => IsSymbol cond
3423− => RowToList cols colsRL
3424− => MakeNullableRL colsRL nullableColsRL
3425− => ListToRow nullableColsRL nullableCols
3426− => Row.Lacks name tables
3427− => Row.Cons name nullableCols tables tables'
3428− => ValidateJoinCondition cond tables'
3429− => Row.Lacks "select" stage
3430− => Row.Lacks "insert" stage
3431− => Row.Lacks "set" stage
3432− => Row.Lacks "delete" stage
3433− => Proxy (Table name cols)
3434− -> Q tables r p stage
3435− -> Q tables' () () (join :: Unit)
3436−leftJoin _ (Q q) = Q
3437− { sql: q.sql
3438− <> " LEFT JOIN "
3439− <> reflectSymbol (Proxy :: Proxy name)
3440− <> " ON "
3441− <> reflectSymbol (Proxy :: Proxy cond)
3442− , values: q.values
3443− }
3444−
3445−innerJoinAs
3446− :: forall @alias @cond name cols tables tables' r p stage
3447− . IsSymbol name
3448− => IsSymbol alias
3449− => IsSymbol cond
3450− => Row.Lacks alias tables
3451− => Row.Cons alias cols tables tables'
3452− => ValidateJoinCondition cond tables'
3453− => Row.Lacks "select" stage
3454− => Row.Lacks "insert" stage
3455− => Row.Lacks "set" stage
3456− => Row.Lacks "delete" stage
3457− => Proxy (Table name cols)
3458− -> Q tables r p stage
3459− -> Q tables' () () (join :: Unit)
3460−innerJoinAs _ (Q q) = Q
3461− { sql: q.sql
3462− <> " INNER JOIN "
3463− <> reflectSymbol (Proxy :: Proxy name)
3464− <> " "
3465− <> reflectSymbol (Proxy :: Proxy alias)
3466− <> " ON "
3467− <> reflectSymbol (Proxy :: Proxy cond)
3468− , values: q.values
3469− }
3470−
3471−leftJoinAs
3472− :: forall @alias @cond name cols colsRL nullableColsRL nullableCols tables tables' r p stage
3473− . IsSymbol name
3474− => IsSymbol alias
3475− => IsSymbol cond
3476− => RowToList cols colsRL
3477− => MakeNullableRL colsRL nullableColsRL
3478− => ListToRow nullableColsRL nullableCols
3479− => Row.Lacks alias tables
3480− => Row.Cons alias nullableCols tables tables'
3481− => ValidateJoinCondition cond tables'
3482− => Row.Lacks "select" stage
3483− => Row.Lacks "insert" stage
3484− => Row.Lacks "set" stage
3485− => Row.Lacks "delete" stage
3486− => Proxy (Table name cols)
3487− -> Q tables r p stage
3488− -> Q tables' () () (join :: Unit)
3489−leftJoinAs _ (Q q) = Q
3490− { sql: q.sql
3491− <> " LEFT JOIN "
3492− <> reflectSymbol (Proxy :: Proxy name)
3493− <> " "
3494− <> reflectSymbol (Proxy :: Proxy alias)
3495− <> " ON "
3496− <> reflectSymbol (Proxy :: Proxy cond)
3497− , values: q.values
3498− }
3499−
3500−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3501−-- Set operations: UNION, INTERSECT, EXCEPT
3502−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3503−
3504−type SetOpStage =
3505− ( select :: Unit
3506− , "where" :: Unit
3507− , groupBy :: Unit
3508− , having :: Unit
3509− , insert :: Unit
3510− , "set" :: Unit
3511− , "delete" :: Unit
3512− )
3513−
3514−union
3515− :: forall tables1 tables2 result params1 params2 params stage1 stage2
3516− . HasClause "select" stage1
3517− => HasClause "select" stage2
3518− => Row.Union params1 params2 params
3519− => Row.Nub params params
3520− => Q tables1 result params1 stage1
3521− -> Q tables2 result params2 stage2
3522− -> Q tables1 result params SetOpStage
3523−union (Q q1) (Q q2) = Q { sql: "(" <> q1.sql <> ") UNION (" <> q2.sql <> ")", values: q1.values <> q2.values }
3524−
3525−unionAll
3526− :: forall tables1 tables2 result params1 params2 params stage1 stage2
3527− . HasClause "select" stage1
3528− => HasClause "select" stage2
3529− => Row.Union params1 params2 params
3530− => Row.Nub params params
3531− => Q tables1 result params1 stage1
3532− -> Q tables2 result params2 stage2
3533− -> Q tables1 result params SetOpStage
3534−unionAll (Q q1) (Q q2) = Q { sql: "(" <> q1.sql <> ") UNION ALL (" <> q2.sql <> ")", values: q1.values <> q2.values }
3535−
3536−intersect
3537− :: forall tables1 tables2 result params1 params2 params stage1 stage2
3538− . HasClause "select" stage1
3539− => HasClause "select" stage2
3540− => Row.Union params1 params2 params
3541− => Row.Nub params params
3542− => Q tables1 result params1 stage1
3543− -> Q tables2 result params2 stage2
3544− -> Q tables1 result params SetOpStage
3545−intersect (Q q1) (Q q2) = Q { sql: "(" <> q1.sql <> ") INTERSECT (" <> q2.sql <> ")", values: q1.values <> q2.values }
3546−
3547−intersectAll
3548− :: forall tables1 tables2 result params1 params2 params stage1 stage2
3549− . HasClause "select" stage1
3550− => HasClause "select" stage2
3551− => Row.Union params1 params2 params
3552− => Row.Nub params params
3553− => Q tables1 result params1 stage1
3554− -> Q tables2 result params2 stage2
3555− -> Q tables1 result params SetOpStage
3556−intersectAll (Q q1) (Q q2) = Q { sql: "(" <> q1.sql <> ") INTERSECT ALL (" <> q2.sql <> ")", values: q1.values <> q2.values }
3557−
3558−except_
3559− :: forall tables1 tables2 result params1 params2 params stage1 stage2
3560− . HasClause "select" stage1
3561− => HasClause "select" stage2
3562− => Row.Union params1 params2 params
3563− => Row.Nub params params
3564− => Q tables1 result params1 stage1
3565− -> Q tables2 result params2 stage2
3566− -> Q tables1 result params SetOpStage
3567−except_ (Q q1) (Q q2) = Q { sql: "(" <> q1.sql <> ") EXCEPT (" <> q2.sql <> ")", values: q1.values <> q2.values }
3568−
3569−exceptAll
3570− :: forall tables1 tables2 result params1 params2 params stage1 stage2
3571− . HasClause "select" stage1
3572− => HasClause "select" stage2
3573− => Row.Union params1 params2 params
3574− => Row.Nub params params
3575− => Q tables1 result params1 stage1
3576− -> Q tables2 result params2 stage2
3577− -> Q tables1 result params SetOpStage
3578−exceptAll (Q q1) (Q q2) = Q { sql: "(" <> q1.sql <> ") EXCEPT ALL (" <> q2.sql <> ")", values: q1.values <> q2.values }
3579−
3580−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3581−-- CTE (WITH clause) support
3582−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3583−
3584−newtype WithCTEs :: Row (Row Type) -> Row Type -> Type
3585−newtype WithCTEs cteTables cteParams = WithCTEs
3586− { sql :: String, values :: Array PG.PGValue }
3587−
3588−-- Wrap a complete SELECT query as a named CTE
3589−cte
3590− :: forall @name tables result params stage cteTables
3591− . IsSymbol name
3592− => HasClause "select" stage
3593− => Row.Cons name result () cteTables
3594− => Q tables result params stage
3595− -> WithCTEs cteTables params
3596−cte (Q q) = WithCTEs
3597− { sql: reflectSymbol (Proxy :: Proxy name) <> " AS (" <> q.sql <> ")"
3598− , values: q.values
3599− }
3600−
3601−-- Add another named CTE to an accumulator
3602−andCTE
3603− :: forall @name tables result params stage cteTables cteParams cteTables' cteParams'
3604− . IsSymbol name
3605− => HasClause "select" stage
3606− => Row.Lacks name cteTables
3607− => Row.Cons name result cteTables cteTables'
3608− => Row.Union cteParams params cteParams'
3609− => Row.Nub cteParams' cteParams'
3610− => Q tables result params stage
3611− -> WithCTEs cteTables cteParams
3612− -> WithCTEs cteTables' cteParams'
3613−andCTE (Q q) (WithCTEs w) = WithCTEs
3614− { sql: w.sql <> ", " <> reflectSymbol (Proxy :: Proxy name) <> " AS (" <> q.sql <> ")"
3615− , values: w.values <> q.values
3616− }
3617−
3618−-- Start main query FROM a named CTE; cteTables becomes available as tables
3619−fromCTE
3620− :: forall @name result restTables cteTables cteParams
3621− . IsSymbol name
3622− => Row.Cons name result restTables cteTables
3623− => WithCTEs cteTables cteParams
3624− -> Q cteTables () () ()
3625−fromCTE _ = Q { sql: reflectSymbol (Proxy :: Proxy name), values: [] }
3626−
3627−-- Finalize: prepend WITH clause and merge params
3628−with_
3629− :: forall cteTables cteParams mainParams allParams result stage
3630− . Row.Union cteParams mainParams allParams
3631− => Row.Nub allParams allParams
3632− => WithCTEs cteTables cteParams
3633− -> Q cteTables result mainParams stage
3634− -> Q cteTables result allParams stage
3635−with_ (WithCTEs w) (Q q) = Q
3636− { sql: "WITH " <> w.sql <> " " <> q.sql
3637− , values: w.values <> q.values
3638− }
3639−
3640−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3641−-- Query execution
3642−-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3643−
3644−class ParamsToArray :: RL.RowList Type -> Row Type -> Constraint
3645−class ParamsToArray rl row where
3646− paramsToArray :: Proxy rl -> { | row } -> Array { name :: String, value :: Foreign }
3647−
3648−instance ParamsToArray RL.Nil row where
3649− paramsToArray _ _ = []
3650−
3651−instance
3652− ( IsSymbol name
3653− , Row.Cons name typ rest row
3654− , Row.Lacks name rest
3655− , FieldToPGValue typ
3656− , ParamsToArray tail row
3657− ) =>
3658− ParamsToArray (RL.Cons name typ tail) row where
3659− paramsToArray _ rec =
3660− [ { name: reflectSymbol (Proxy :: Proxy name)
3661− , value: unsafeCoerce (fieldToPGValue (Record.get (Proxy :: Proxy name) rec))
3662− }
3663− ]
3664− <> paramsToArray (Proxy :: Proxy tail) rec
3665−
3666−namedParamRegex :: Regex.Regex
3667−namedParamRegex = case Regex.regex "\\$[a-zA-Z_][a-zA-Z0-9_]*(::[a-zA-Z_]+)?" Regex.global of
3668− Right r -> r
3669− Left _ -> unsafeCoerce unit
3670−
3671−stripCast :: String -> String
3672−stripCast s = case SCU.indexOf (Pattern "::") s of
3673− Nothing -> s
3674− Just i -> SCU.take i s
3675−
3676−replaceNamedParams :: Int -> Array { name :: String, value :: Foreign } -> String -> { sql :: String, values :: Array PG.PGValue }
3677−replaceNamedParams offset entries sql = do
3678− let indexed = entries # mapWithIndex \i e -> { idx: offset + i + 1, name: e.name, value: e.value }
3679− let replacements = indexed # foldl (\m e -> Map.insert ("$" <> e.name) ("$" <> show e.idx) m) Map.empty
3680− let
3681− sql' = Regex.replace' namedParamRegex
3682− ( \match _ -> case Map.lookup (stripCast match) replacements of
3683− Nothing -> match
3684− Just v -> v
3685− )
3686− sql
3687− { sql: sql', values: map (\e -> unsafeCoerce e.value) indexed }
3688−
3689−runQuery
3690− :: forall tables result params paramsRL stage
3691− . RowToList params paramsRL
3692− => ParamsToArray paramsRL params
3693− => ReadForeign { | result }
3694− => PG.Connection
3695− -> { | params }
3696− -> Q tables result params stage
3697− -> Aff (Array { | result })
3698−runQuery conn params (Q q) = do
3699− let entries = paramsToArray (Proxy :: Proxy paramsRL) params
3700− let { sql, values } = replaceNamedParams (Array.length q.values) entries q.sql
3701− let allValues = q.values <> values
3702− result <- PG.query (PG.SQL sql) allValues conn
3703− case decodeRows result.rows of
3704− Left errs -> throwError (Exception.error ("Row decode failed: " <> show errs))
3705− Right rows -> pure rows
3706−
3707−runQueryOne
3708− :: forall tables result params paramsRL stage
3709− . RowToList params paramsRL
3710− => ParamsToArray paramsRL params
3711− => ReadForeign { | result }
3712− => PG.Connection
3713− -> { | params }
3714− -> Q tables result params stage
3715− -> Aff (Maybe { | result })
3716−runQueryOne conn params (Q q) = do
3717− let entries = paramsToArray (Proxy :: Proxy paramsRL) params
3718− let { sql, values } = replaceNamedParams (Array.length q.values) entries q.sql
3719− let allValues = q.values <> values
3720− result <- PG.queryOne (PG.SQL sql) allValues conn
3721− case result of
3722− Nothing -> pure Nothing
3723− Just row -> case decodeRow row of
3724− Left errs -> throwError (Exception.error ("Row decode failed: " <> show errs))
3725− Right a -> pure (Just a)
3726−
3727−runExecute
3728− :: forall tables params paramsRL stage
3729− . RowToList params paramsRL
3730− => ParamsToArray paramsRL params
3731− => PG.Connection
3732− -> { | params }
3733− -> Q tables () params stage
3734− -> Aff Int
3735−runExecute conn params (Q q) = do
3736− let entries = paramsToArray (Proxy :: Proxy paramsRL) params
3737− let { sql, values } = replaceNamedParams (Array.length q.values) entries q.sql
3738− let allValues = q.values <> values
3739− PG.execute (PG.SQL sql) allValues conn
3740−
3741−-- Transaction variants
3742−
3743−runQueryTx
3744− :: forall tables result params paramsRL stage
3745− . RowToList params paramsRL
3746− => ParamsToArray paramsRL params
3747− => ReadForeign { | result }
3748− => PG.Transaction
3749− -> { | params }
3750− -> Q tables result params stage
3751− -> Aff (Array { | result })
3752−runQueryTx txn params (Q q) = do
3753− let entries = paramsToArray (Proxy :: Proxy paramsRL) params
3754− let { sql, values } = replaceNamedParams (Array.length q.values) entries q.sql
3755− let allValues = q.values <> values
3756− result <- PG.txQuery (PG.SQL sql) allValues txn
3757− case decodeRows result.rows of
3758− Left errs -> throwError (Exception.error ("Row decode failed: " <> show errs))
3759− Right rows -> pure rows
3760−
3761−runExecuteTx
3762− :: forall tables params paramsRL stage
3763− . RowToList params paramsRL
3764− => ParamsToArray paramsRL params
3765− => PG.Transaction
3766− -> { | params }
3767− -> Q tables () params stage
3768− -> Aff Int
3769−runExecuteTx txn params (Q q) = do
3770− let entries = paramsToArray (Proxy :: Proxy paramsRL) params
3771− let { sql, values } = replaceNamedParams (Array.length q.values) entries q.sql
3772− let allValues = q.values <> values
3773− PG.txExecute (PG.SQL sql) allValues txn
3774−
3775−
3776−decodeRows :: forall a. ReadForeign a => Array Foreign -> Either (NonEmptyList ForeignError) (Array a)
3777−decodeRows = traverse (readImpl >>> runExcept)
3778−
3779−decodeRow :: forall a. ReadForeign a => Foreign -> Either (NonEmptyList ForeignError) a
3780−decodeRow = readImpl >>> runExcept