@@ -1128,4 +1128,287 @@ describe("SQLite execution", () => {
11281128 const res = execRA ( "π_{name, city}(Person)" ) ;
11291129 expect ( res [ 0 ] . columns ) . toEqual ( [ "name" , "city" ] ) ;
11301130 } ) ;
1131+
1132+ it ( "ρ_{old→new}(R)" , ( ) => {
1133+ const res = execRA ( "ρ_{name→fullName}(Person)" ) ;
1134+ expect ( res [ 0 ] . columns ) . toContain ( "fullName" ) ;
1135+ expect ( res [ 0 ] . columns ) . not . toContain ( "name" ) ;
1136+ } ) ;
1137+
1138+ it ( "σ{cond}(R) without underscore" , ( ) => {
1139+ const res = execRA ( "σ{age > 20}(Person)" ) ;
1140+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1141+ } ) ;
1142+
1143+ it ( "⋈{cond} theta join with curly braces" , ( ) => {
1144+ const res = execRA ( "Person ⋈{age > credits} Course" ) ;
1145+ expect ( res [ 0 ] . values . length ) . toBeGreaterThan ( 0 ) ;
1146+ } ) ;
1147+
1148+ // ── Selection edge cases ──
1149+
1150+ it ( "σ with OR condition" , ( ) => {
1151+ const res = execRA ( "σ[city = 'Stockholm' or city = 'York'](Person)" ) ;
1152+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ; // Alice and Bob
1153+ } ) ;
1154+
1155+ it ( "σ with NOT condition" , ( ) => {
1156+ const res = execRA ( "σ[not age > 20](Person)" ) ;
1157+ expect ( res [ 0 ] . values . length ) . toBe ( 1 ) ; // Only Bob (19)
1158+ expect ( res [ 0 ] . values [ 0 ] ) . toContain ( "Bob" ) ;
1159+ } ) ;
1160+
1161+ it ( "σ with nested OR and AND" , ( ) => {
1162+ const res = execRA ( "σ[age > 20 or (name = 'Bob' and city = 'York')](Person)" ) ;
1163+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ; // Alice, Bob, Carol
1164+ } ) ;
1165+
1166+ it ( "σ with all comparison operators" , ( ) => {
1167+ expect ( execRA ( "σ[age = 25](Person)" ) [ 0 ] . values . length ) . toBe ( 1 ) ;
1168+ expect ( execRA ( "σ[age <> 25](Person)" ) [ 0 ] . values . length ) . toBe ( 2 ) ;
1169+ expect ( execRA ( "σ[age != 25](Person)" ) [ 0 ] . values . length ) . toBe ( 2 ) ;
1170+ expect ( execRA ( "σ[age < 25](Person)" ) [ 0 ] . values . length ) . toBe ( 1 ) ;
1171+ expect ( execRA ( "σ[age > 25](Person)" ) [ 0 ] . values . length ) . toBe ( 1 ) ;
1172+ expect ( execRA ( "σ[age <= 25](Person)" ) [ 0 ] . values . length ) . toBe ( 2 ) ;
1173+ expect ( execRA ( "σ[age >= 25](Person)" ) [ 0 ] . values . length ) . toBe ( 2 ) ;
1174+ } ) ;
1175+
1176+ it ( "σ with table.column references" , ( ) => {
1177+ const res = execRA ( "σ[Person.age > 20](Person)" ) ;
1178+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1179+ } ) ;
1180+
1181+ // ── Rename edge cases ──
1182+
1183+ it ( "ρ with multiple rename mappings" , ( ) => {
1184+ const res = execRA ( "ρ[name→fullName, age→years](Person)" ) ;
1185+ expect ( res [ 0 ] . columns ) . toContain ( "fullName" ) ;
1186+ expect ( res [ 0 ] . columns ) . toContain ( "years" ) ;
1187+ expect ( res [ 0 ] . columns ) . not . toContain ( "name" ) ;
1188+ expect ( res [ 0 ] . columns ) . not . toContain ( "age" ) ;
1189+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ;
1190+ } ) ;
1191+
1192+ it ( "ρ with ASCII arrow" , ( ) => {
1193+ const res = execRA ( "rho[name->fullName](Person)" ) ;
1194+ expect ( res [ 0 ] . columns ) . toContain ( "fullName" ) ;
1195+ } ) ;
1196+
1197+ // ── Outer joins ──
1198+
1199+ it ( "rightjoin" , ( ) => {
1200+ const res = execRA ( "Student rightjoin[age > hasDisability] Person" ) ;
1201+ expect ( res [ 0 ] . values . length ) . toBeGreaterThan ( 0 ) ;
1202+ } ) ;
1203+
1204+ it ( "fulljoin" , ( ) => {
1205+ const res = execRA ( "Person fulljoin[age > credits] Course" ) ;
1206+ expect ( res [ 0 ] . values . length ) . toBeGreaterThan ( 0 ) ;
1207+ } ) ;
1208+
1209+ it ( "⟕ left outer join with Unicode" , ( ) => {
1210+ const res = execRA ( "Person ⟕[age > credits] Course" ) ;
1211+ expect ( res [ 0 ] . values . length ) . toBeGreaterThan ( 0 ) ;
1212+ } ) ;
1213+
1214+ it ( "left join preserves non-matching rows" , ( ) => {
1215+ // Carol (age 30) has no Student row — left join should keep her with NULLs
1216+ const res = execRA ( "Person leftjoin[Person.id = Student.id] Student" ) ;
1217+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ; // All 3 Person rows
1218+ } ) ;
1219+
1220+ // ── Semi-join edge cases ──
1221+
1222+ it ( "⋊ right semi-join" , ( ) => {
1223+ const res = execRA ( "Student ⋊ Person" ) ;
1224+ // Student ids 1,2 both exist in Person — all Student rows match
1225+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1226+ } ) ;
1227+
1228+ it ( "rightsemijoin keyword" , ( ) => {
1229+ const res = execRA ( "Student rightsemijoin Person" ) ;
1230+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1231+ } ) ;
1232+
1233+ it ( "leftsemijoin keyword" , ( ) => {
1234+ const res = execRA ( "Person leftsemijoin Student" ) ;
1235+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1236+ } ) ;
1237+
1238+ it ( "antijoin keyword" , ( ) => {
1239+ const res = execRA ( "Person antijoin Student" ) ;
1240+ expect ( res [ 0 ] . values . length ) . toBe ( 1 ) ;
1241+ expect ( res [ 0 ] . values [ 0 ] ) . toContain ( "Carol" ) ;
1242+ } ) ;
1243+
1244+ // ── Sort edge cases ──
1245+
1246+ it ( "τ with multiple sort columns" , ( ) => {
1247+ const res = execRA ( "τ[city, age DESC](Person)" ) ;
1248+ // Bristol(30), Stockholm(25), York(19)
1249+ expect ( res [ 0 ] . values [ 0 ] ) . toContain ( "Carol" ) ; // Bristol
1250+ expect ( res [ 0 ] . values [ 1 ] ) . toContain ( "Alice" ) ; // Stockholm
1251+ expect ( res [ 0 ] . values [ 2 ] ) . toContain ( "Bob" ) ; // York
1252+ } ) ;
1253+
1254+ it ( "sort keyword" , ( ) => {
1255+ const res = execRA ( "sort[name](Person)" ) ;
1256+ expect ( res [ 0 ] . values [ 0 ] ) . toContain ( "Alice" ) ;
1257+ expect ( res [ 0 ] . values [ 2 ] ) . toContain ( "Carol" ) ;
1258+ } ) ;
1259+
1260+ // ── Aggregation edge cases ──
1261+
1262+ it ( "γ with SUM" , ( ) => {
1263+ const res = execRA ( "γ[city; SUM(age) AS totalAge](Person)" ) ;
1264+ expect ( res [ 0 ] . columns ) . toContain ( "totalAge" ) ;
1265+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ;
1266+ } ) ;
1267+
1268+ it ( "γ with AVG" , ( ) => {
1269+ const res = execRA ( "γ[city; AVG(age) AS avgAge](Person)" ) ;
1270+ expect ( res [ 0 ] . columns ) . toContain ( "avgAge" ) ;
1271+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ;
1272+ } ) ;
1273+
1274+ it ( "γ with MIN and MAX" , ( ) => {
1275+ const res = execRA ( "γ[city; MIN(age) AS youngest, MAX(age) AS oldest](Person)" ) ;
1276+ expect ( res [ 0 ] . columns ) . toContain ( "youngest" ) ;
1277+ expect ( res [ 0 ] . columns ) . toContain ( "oldest" ) ;
1278+ } ) ;
1279+
1280+ it ( "γ COUNT without alias" , ( ) => {
1281+ const res = execRA ( "γ[city; COUNT(id)](Person)" ) ;
1282+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ;
1283+ } ) ;
1284+
1285+ it ( "gamma keyword" , ( ) => {
1286+ const res = execRA ( "gamma[city; COUNT(id) AS cnt](Person)" ) ;
1287+ expect ( res [ 0 ] . columns ) . toContain ( "cnt" ) ;
1288+ } ) ;
1289+
1290+ // ── Distinct edge cases ──
1291+
1292+ it ( "delta keyword" , ( ) => {
1293+ const res = execRA ( "delta(Person)" ) ;
1294+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ;
1295+ } ) ;
1296+
1297+ it ( "distinct keyword" , ( ) => {
1298+ const res = execRA ( "distinct(Person)" ) ;
1299+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ;
1300+ } ) ;
1301+
1302+ it ( "δ without parens" , ( ) => {
1303+ const res = execRA ( "δ Person" ) ;
1304+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ;
1305+ } ) ;
1306+
1307+ // ── Keyword variants for operators ──
1308+
1309+ it ( "select keyword" , ( ) => {
1310+ const res = execRA ( "select[age > 20](Person)" ) ;
1311+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1312+ } ) ;
1313+
1314+ it ( "project keyword" , ( ) => {
1315+ const res = execRA ( "project[name, city](Person)" ) ;
1316+ expect ( res [ 0 ] . columns ) . toEqual ( [ "name" , "city" ] ) ;
1317+ } ) ;
1318+
1319+ it ( "cross keyword" , ( ) => {
1320+ const res = execRA ( "Person cross Course" ) ;
1321+ expect ( res [ 0 ] . values . length ) . toBe ( 6 ) ;
1322+ } ) ;
1323+
1324+ it ( "join keyword with condition" , ( ) => {
1325+ const res = execRA ( "Person join[age > credits] Course" ) ;
1326+ expect ( res [ 0 ] . values . length ) . toBeGreaterThan ( 0 ) ;
1327+ } ) ;
1328+
1329+ it ( "|X| as natural join" , ( ) => {
1330+ const res = execRA ( "Person |X| Student" ) ;
1331+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1332+ } ) ;
1333+
1334+ it ( "|><| as natural join" , ( ) => {
1335+ const res = execRA ( "Person |><| Student" ) ;
1336+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1337+ } ) ;
1338+
1339+ it ( "intersect keyword" , ( ) => {
1340+ const res = execRA ( "π[name](Person) intersect π[name](Teacher)" ) ;
1341+ expect ( res . length === 0 || res [ 0 ] . values . length === 0 ) . toBe ( true ) ;
1342+ } ) ;
1343+
1344+ it ( "divide keyword" , ( ) => {
1345+ const res = execRA ( "π[id, course_id](Enrollment) divide π[course_id](Course)" ) ;
1346+ expect ( res [ 0 ] . values . length ) . toBe ( 1 ) ;
1347+ expect ( res [ 0 ] . values [ 0 ] ) . toContain ( 1 ) ;
1348+ } ) ;
1349+
1350+ // ── Implicit subscripts edge cases ──
1351+
1352+ it ( "ρ old→new (R) implicit" , ( ) => {
1353+ const res = execRA ( "ρ name→fullName (Person)" ) ;
1354+ expect ( res [ 0 ] . columns ) . toContain ( "fullName" ) ;
1355+ } ) ;
1356+
1357+ it ( "τ col (R) implicit" , ( ) => {
1358+ const res = execRA ( "τ name (Person)" ) ;
1359+ expect ( res [ 0 ] . values [ 0 ] ) . toContain ( "Alice" ) ;
1360+ } ) ;
1361+
1362+ it ( "σ compound implicit with AND" , ( ) => {
1363+ const res = execRA ( "σ age > 20 and city = 'Stockholm' (Person)" ) ;
1364+ expect ( res [ 0 ] . values . length ) . toBe ( 1 ) ;
1365+ expect ( res [ 0 ] . values [ 0 ] ) . toContain ( "Alice" ) ;
1366+ } ) ;
1367+
1368+ it ( "nested implicit subscripts" , ( ) => {
1369+ const res = execRA ( "π name (σ age > 20 (Person))" ) ;
1370+ expect ( res [ 0 ] . columns ) . toEqual ( [ "name" ] ) ;
1371+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1372+ } ) ;
1373+
1374+ // ── Parenthesis-free edge cases ──
1375+
1376+ it ( "triple chain without parens" , ( ) => {
1377+ const res = execRA ( "π[name] σ[age > 20] δ Person" ) ;
1378+ expect ( res [ 0 ] . columns ) . toEqual ( [ "name" ] ) ;
1379+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1380+ } ) ;
1381+
1382+ it ( "π[cols] over union with parens" , ( ) => {
1383+ const res = execRA ( "π[name] (π[name](Person) ∪ π[name](Teacher))" ) ;
1384+ expect ( res [ 0 ] . values . length ) . toBe ( 5 ) ;
1385+ } ) ;
1386+
1387+ // ── Assignment edge cases ──
1388+
1389+ it ( "assignment with <- ASCII arrow" , ( ) => {
1390+ const res = execRA ( "A <- σ[age > 20](Person)\nπ[name](A)" ) ;
1391+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1392+ } ) ;
1393+
1394+ it ( "semicolons as statement separators" , ( ) => {
1395+ const res = execRA ( "A ← Person; π[name](A)" ) ;
1396+ expect ( res [ 0 ] . values . length ) . toBe ( 3 ) ;
1397+ } ) ;
1398+
1399+ it ( "comments in multi-line input" , ( ) => {
1400+ const res = execRA ( "-- Get adults\nA ← σ[age > 20](Person)\n-- Project names\nπ[name](A)" ) ;
1401+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1402+ } ) ;
1403+
1404+ it ( "implicit return from last assignment" , ( ) => {
1405+ const res = execRA ( "A ← σ[age > 20](Person)" ) ;
1406+ expect ( res [ 0 ] . values . length ) . toBe ( 2 ) ;
1407+ } ) ;
1408+
1409+ it ( "complex pipeline with reassignment" , ( ) => {
1410+ const res = execRA ( "X ← Person ⋈ Student\nX ← σ[age > 20](X)\nX ← π[name](X)" ) ;
1411+ expect ( res [ 0 ] . values . length ) . toBe ( 1 ) ;
1412+ expect ( res [ 0 ] . values [ 0 ] ) . toContain ( "Alice" ) ;
1413+ } ) ;
11311414} ) ;
0 commit comments