@@ -1029,8 +1029,15 @@ function resolveColumns(sql: string, db: DatabaseHandle): string[] {
10291029 const cols = stmt . getColumnNames ( ) ;
10301030 stmt . free ( ) ;
10311031 return cols ;
1032- } catch {
1033- return [ ] ;
1032+ } catch ( e ) {
1033+ // Re-throw table-not-found errors as RAError for better messaging
1034+ const msg = ( e as Error ) . message || String ( e ) ;
1035+ if ( / n o s u c h t a b l e / i. test ( msg ) ) {
1036+ const match = msg . match ( / n o s u c h t a b l e : \s * ( \S + ) / i) ;
1037+ throw new RAError ( match ? `Table '${ match [ 1 ] } ' does not exist` : msg ) ;
1038+ }
1039+ // For other errors (e.g. ambiguous column), let them propagate
1040+ throw new RAError ( msg ) ;
10341041 }
10351042}
10361043function nodeToSQL ( node : RANode , db ?: DatabaseHandle ) : string {
@@ -1148,23 +1155,88 @@ function nodeToSQL(node: RANode, db?: DatabaseHandle): string {
11481155 }
11491156}
11501157
1158+ /**
1159+ * Rewrite table references in an AST node using a name mapping.
1160+ * Used to handle variable reassignment (A <- ...; A <- ...) by pointing
1161+ * references to the correct versioned CTE name.
1162+ */
1163+ function rewriteTableRefs ( node : RANode , nameMap : Record < string , string > ) : RANode {
1164+ switch ( node . type ) {
1165+ case "table" :
1166+ return nameMap [ node . name ] ? { type : "table" , name : nameMap [ node . name ] } : node ;
1167+ case "selection" :
1168+ return { ...node , relation : rewriteTableRefs ( node . relation , nameMap ) } ;
1169+ case "projection" :
1170+ return { ...node , relation : rewriteTableRefs ( node . relation , nameMap ) } ;
1171+ case "rename" :
1172+ return { ...node , relation : rewriteTableRefs ( node . relation , nameMap ) } ;
1173+ case "group" :
1174+ return { ...node , relation : rewriteTableRefs ( node . relation , nameMap ) } ;
1175+ case "sort" :
1176+ return { ...node , relation : rewriteTableRefs ( node . relation , nameMap ) } ;
1177+ case "distinct" :
1178+ return { ...node , relation : rewriteTableRefs ( node . relation , nameMap ) } ;
1179+ case "crossProduct" :
1180+ case "naturalJoin" :
1181+ case "union" :
1182+ case "intersect" :
1183+ case "difference" :
1184+ case "division" :
1185+ case "leftSemiJoin" :
1186+ case "rightSemiJoin" :
1187+ case "antiJoin" :
1188+ return { ...node , left : rewriteTableRefs ( node . left , nameMap ) , right : rewriteTableRefs ( node . right , nameMap ) } ;
1189+ case "thetaJoin" :
1190+ case "leftJoin" :
1191+ case "rightJoin" :
1192+ case "fullJoin" :
1193+ return { ...node , left : rewriteTableRefs ( node . left , nameMap ) , right : rewriteTableRefs ( node . right , nameMap ) } ;
1194+ default :
1195+ return node ;
1196+ }
1197+ }
1198+
11511199function programToSQL ( program : RAProgram , db ?: DatabaseHandle ) : string {
11521200 if ( program . assignments . length === 0 ) {
11531201 return nodeToSQL ( program . result , db ) ;
11541202 }
11551203
1156- // Use CTEs (WITH clauses) for assignments
1157- const ctes = program . assignments . map ( a => {
1158- const sql = nodeToSQL ( a . expr , db ) ;
1159- // Wrap non-table expressions in SELECT * FROM (...) for CTE compatibility
1204+ // Use CTEs (WITH clauses) for assignments.
1205+ // Handle reassignment (A <- ..., A <- ...) by versioning CTE names
1206+ // and rewriting references in subsequent expressions.
1207+ const ctes : string [ ] = [ ] ;
1208+ // Maps variable name -> current CTE name (may be versioned like A_v2)
1209+ const nameMap : Record < string , string > = { } ;
1210+ // Track how many times each name has been assigned
1211+ const assignCount : Record < string , number > = { } ;
1212+
1213+ for ( const a of program . assignments ) {
1214+ if ( a . expr . type === "table" && a . expr . name === a . name && ! nameMap [ a . name ] ) {
1215+ // Self-referential assignment (A <- A) where A is a real table — skip, it's a no-op
1216+ continue ;
1217+ }
1218+
1219+ // Rewrite the expression: replace table references with their current CTE aliases
1220+ const rewrittenExpr = rewriteTableRefs ( a . expr , nameMap ) ;
1221+ const sql = nodeToSQL ( rewrittenExpr , db ) ;
11601222 const wrappedSQL = / ^ \w + $ / . test ( sql ) ? `SELECT * FROM ${ sql } ` : sql ;
1161- return `${ a . name } AS (${ wrappedSQL } )` ;
1162- } ) ;
11631223
1164- const resultSQL = nodeToSQL ( program . result , db ) ;
1165- // Wrap bare table reference in SELECT for the final expression
1224+ // Determine the CTE name for this assignment
1225+ assignCount [ a . name ] = ( assignCount [ a . name ] || 0 ) + 1 ;
1226+ const cteName = assignCount [ a . name ] > 1 ? `${ a . name } _v${ assignCount [ a . name ] } ` : a . name ;
1227+ nameMap [ a . name ] = cteName ;
1228+
1229+ ctes . push ( `${ cteName } AS (${ wrappedSQL } )` ) ;
1230+ }
1231+
1232+ // Rewrite the result expression with final name mappings
1233+ const rewrittenResult = rewriteTableRefs ( program . result , nameMap ) ;
1234+ const resultSQL = nodeToSQL ( rewrittenResult , db ) ;
11661235 const wrappedResult = / ^ \w + $ / . test ( resultSQL ) ? `SELECT * FROM ${ resultSQL } ` : resultSQL ;
11671236
1237+ if ( ctes . length === 0 ) {
1238+ return wrappedResult ;
1239+ }
11681240 return `WITH ${ ctes . join ( ", " ) } ${ wrappedResult } ` ;
11691241}
11701242
0 commit comments