fix(gengo): INDEX ON ... TO (expr) evaluates filename at runtime
Prior behavior used exprToString() to serialize the TO expression back into a string, so a runtime-evaluated filename like `( Lower(cTable) + "_pk.ntx" )` ended up as the literal filename `Lower(cTable) + "_pk.ntx"` on disk. Visible in FiveSql2's PRIMARY KEY / UNIQUE DDL path: test_sql1999 was creating files with that literal name, which the test happened not to care about because the USE inside BEGIN SEQUENCE caught the failure. Fix: if the File expression contains any function call (detected by new containsCall walker), emit emitExpr + Pop2 + AsString — runtime evaluation path. Static filenames (`TO test.ntx`) still use the cheap exprToString branch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -617,8 +617,20 @@ func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) {
|
||||
g.indent++
|
||||
keyStr := exprToString(s.KeyExpr)
|
||||
g.writeln(fmt.Sprintf("_keyExpr := %q", keyStr))
|
||||
fileStr := exprToString(s.File)
|
||||
g.writeln(fmt.Sprintf("_file := %q", fileStr))
|
||||
|
||||
// File expression: if it contains a function call, evaluate at
|
||||
// runtime — Harbour `INDEX ON ... TO ( cExpr )` semantics. Prior
|
||||
// behavior was static exprToString which serialized calls like
|
||||
// `Lower(cTable) + "_pk.ntx"` into the literal filename string.
|
||||
// Detect via containsCall; preserve static path for simple
|
||||
// `test.ntx` style identifiers.
|
||||
if containsCall(s.File) {
|
||||
g.emitExpr(s.File)
|
||||
g.writeln("_file := t.Pop2().AsString()")
|
||||
} else {
|
||||
fileStr := exprToString(s.File)
|
||||
g.writeln(fmt.Sprintf("_file := %q", fileStr))
|
||||
}
|
||||
forExpr := `""`
|
||||
if s.ForCond != nil {
|
||||
forExpr = fmt.Sprintf("%q", exprToString(s.ForCond))
|
||||
@@ -1783,6 +1795,26 @@ func (g *Generator) emitIndexKeyExpr(expr ast.Expr) {
|
||||
}
|
||||
|
||||
// exprToString extracts a string representation from an AST expression.
|
||||
// containsCall reports whether an expression contains any function call.
|
||||
// Used to distinguish runtime-evaluated INDEX ON filenames
|
||||
// (`TO ( Lower(cTable) + "_pk.ntx" )`) from static ones (`TO test.ntx`).
|
||||
func containsCall(expr ast.Expr) bool {
|
||||
if expr == nil {
|
||||
return false
|
||||
}
|
||||
switch e := expr.(type) {
|
||||
case *ast.CallExpr:
|
||||
return true
|
||||
case *ast.BinaryExpr:
|
||||
return containsCall(e.Left) || containsCall(e.Right)
|
||||
case *ast.UnaryExpr:
|
||||
return containsCall(e.X)
|
||||
case *ast.SendExpr:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Used for INDEX ON key and filename, where idents are field/file names, not variables.
|
||||
func exprToString(expr ast.Expr) string {
|
||||
switch e := expr.(type) {
|
||||
|
||||
Reference in New Issue
Block a user