@@ -982,9 +982,10 @@ else instance
982982class ParseOverClauseByHead :: Symbol -> Symbol -> Row (Row Type) -> Type -> RL.RowList Type -> RL.RowList Type -> Constraint
983983class ParseOverClauseByHead head tail tables returnType accRL outRL | head tail tables returnType accRL -> outRL
984984
985−-- Open paren: extract until close paren, skip content, continue to AS
985+-- Open paren: extract until close paren, validate content, continue to AS
986986instance
987− ( ExtractUntilParen tail _overContent afterParen
987+ ( ExtractUntilParen tail overContent afterParen
988+ , ValidateOverContent overContent tables
988989 , SkipSpaces afterParen rest
989990 , ParseAfterAggregate rest tables returnType accRL outRL
990991 ) =>
@@ -994,6 +995,153 @@ else instance
994995 Fail (Text "Expected ( after OVER") =>
995996 ParseOverClauseByHead h t tables returnType accRL outRL
996997
998+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
999+-- ValidateOverContent: validates PARTITION BY / ORDER BY inside OVER(...)
1000+-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1001+
1002+class ValidateOverContent :: Symbol -> Row (Row Type) -> Constraint
1003+class ValidateOverContent content tables
1004+
1005+instance ValidateOverContent "" tables
1006+
1007+else instance
1008+ ( SkipSpaces content content'
1009+ , ValidateOverContentTrimmed content' tables
1010+ ) =>
1011+ ValidateOverContent content tables
1012+
1013+class ValidateOverContentTrimmed :: Symbol -> Row (Row Type) -> Constraint
1014+class ValidateOverContentTrimmed content tables
1015+
1016+instance ValidateOverContentTrimmed "" tables
1017+
1018+else instance
1019+ ( ExtractWord content keyword rest
1020+ , ValidateOverKeyword keyword rest tables
1021+ ) =>
1022+ ValidateOverContentTrimmed content tables
1023+
1024+class ValidateOverKeyword :: Symbol -> Symbol -> Row (Row Type) -> Constraint
1025+class ValidateOverKeyword keyword rest tables
1026+
1027+instance
1028+ ( ExtractWord rest byWord afterBy
1029+ , AssertIsBy byWord
1030+ , ValidatePartitionByColumns afterBy tables
1031+ ) =>
1032+ ValidateOverKeyword "PARTITION" rest tables
1033+
1034+else instance
1035+ ( ExtractWord rest byWord afterBy
1036+ , AssertIsBy byWord
1037+ , ValidatePartitionByColumns afterBy tables
1038+ ) =>
1039+ ValidateOverKeyword "partition" rest tables
1040+
1041+else instance
1042+ ( ExtractWord rest byWord afterBy
1043+ , AssertIsBy byWord
1044+ , ValidateOrderBy afterBy tables
1045+ ) =>
1046+ ValidateOverKeyword "ORDER" rest tables
1047+
1048+else instance
1049+ ( ExtractWord rest byWord afterBy
1050+ , AssertIsBy byWord
1051+ , ValidateOrderBy afterBy tables
1052+ ) =>
1053+ ValidateOverKeyword "order" rest tables
1054+
1055+else instance
1056+ Fail (Text "Expected PARTITION BY or ORDER BY inside OVER clause") =>
1057+ ValidateOverKeyword keyword rest tables
1058+
1059+class AssertIsBy :: Symbol -> Constraint
1060+class AssertIsBy sym
1061+
1062+instance AssertIsBy "BY"
1063+else instance AssertIsBy "by"
1064+else instance Fail (Text "Expected BY after PARTITION or ORDER") => AssertIsBy sym
1065+
1066+-- ValidatePartitionByColumns: column list that terminates at ORDER keyword
1067+class ValidatePartitionByColumns :: Symbol -> Row (Row Type) -> Constraint
1068+class ValidatePartitionByColumns sym tables
1069+
1070+instance ValidatePartitionByColumns "" tables
1071+
1072+else instance
1073+ ( SkipSpaces sym sym'
1074+ , ValidatePartitionByColumnsTrimmed sym' tables
1075+ ) =>
1076+ ValidatePartitionByColumns sym tables
1077+
1078+class ValidatePartitionByColumnsTrimmed :: Symbol -> Row (Row Type) -> Constraint
1079+class ValidatePartitionByColumnsTrimmed sym tables
1080+
1081+instance ValidatePartitionByColumnsTrimmed "" tables
1082+
1083+else instance
1084+ ( Symbol.Cons h t sym
1085+ , ValidatePartitionByColumnsGo h t "" tables
1086+ ) =>
1087+ ValidatePartitionByColumnsTrimmed sym tables
1088+
1089+class ValidatePartitionByColumnsGo :: Symbol -> Symbol -> Symbol -> Row (Row Type) -> Constraint
1090+class ValidatePartitionByColumnsGo head tail acc tables
1091+
1092+-- Comma: flush column, continue
1093+instance
1094+ ( ResolveColumn acc tables typ
1095+ , SkipSpaces tail rest
1096+ , ValidatePartitionByColumnsTrimmed rest tables
1097+ ) =>
1098+ ValidatePartitionByColumnsGo "," tail acc tables
1099+
1100+-- Space: flush word, check if ORDER keyword or column
1101+else instance
1102+ ( SkipSpaces tail rest
1103+ , FlushPartitionWord acc rest tables
1104+ ) =>
1105+ ValidatePartitionByColumnsGo " " tail acc tables
1106+
1107+-- End of string: flush final column
1108+else instance
1109+ ( Symbol.Append acc h acc'
1110+ , ResolveColumn acc' tables typ
1111+ ) =>
1112+ ValidatePartitionByColumnsGo h "" acc tables
1113+
1114+-- Regular char: accumulate
1115+else instance
1116+ ( Symbol.Append acc h acc'
1117+ , Symbol.Cons nextH nextT tail
1118+ , ValidatePartitionByColumnsGo nextH nextT acc' tables
1119+ ) =>
1120+ ValidatePartitionByColumnsGo h tail acc tables
1121+
1122+class FlushPartitionWord :: Symbol -> Symbol -> Row (Row Type) -> Constraint
1123+class FlushPartitionWord word rest tables
1124+
1125+instance
1126+ ( ExtractWord rest byWord afterBy
1127+ , AssertIsBy byWord
1128+ , ValidateOrderBy afterBy tables
1129+ ) =>
1130+ FlushPartitionWord "ORDER" rest tables
1131+
1132+else instance
1133+ ( ExtractWord rest byWord afterBy
1134+ , AssertIsBy byWord
1135+ , ValidateOrderBy afterBy tables
1136+ ) =>
1137+ FlushPartitionWord "order" rest tables
1138+
1139+else instance
1140+ ( ResolveColumn word tables typ
1141+ , ValidatePartitionByColumnsTrimmed rest tables
1142+ ) =>
1143+ FlushPartitionWord word rest tables
1144+
9971145-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9981146-- ParseWhere: parse "id = $id AND age > $age" -> params
9991147-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━