{"record":{"id":"c7ea44792c6e2d9b","repo":"mybatis/mybatis-3","slug":"line-missing-end-of-line-terminator","errorCode":null,"errorMessage":"Line missing end-of-line terminator ({}) => {}","messagePattern":"Line missing end-of-line terminator \\((.+?)\\) => (.+?)","errorType":"exception","errorClass":"RuntimeSqlException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/jdbc/ScriptRunner.java","lineNumber":209,"sourceCode":"      }\n    } catch (Throwable t) {\n      throw new RuntimeSqlException(\"Could not commit transaction. Cause: \" + t, t);\n    }\n  }\n\n  private void rollbackConnection() {\n    try {\n      if (!connection.getAutoCommit()) {\n        connection.rollback();\n      }\n    } catch (Throwable t) {\n      // ignore\n    }\n  }\n\n  private void checkForMissingLineTerminator(StringBuilder command) {\n    if (command != null && command.toString().trim().length() > 0) {\n      throw new RuntimeSqlException(\"Line missing end-of-line terminator (\" + delimiter + \") => \" + command);\n    }\n  }\n\n  private void handleLine(StringBuilder command, String line) throws SQLException {\n    String trimmedLine = line.trim();\n    if (lineIsComment(trimmedLine)) {\n      Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine);\n      if (matcher.find()) {\n        delimiter = matcher.group(5);\n      }\n      println(trimmedLine);\n    } else if (commandReadyToExecute(trimmedLine)) {\n      command.append(line, 0, line.lastIndexOf(delimiter));\n      command.append(LINE_SEPARATOR);\n      println(command);\n      executeStatement(command.toString());\n      command.setLength(0);\n    } else if (trimmedLine.length() > 0) {","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/jdbc/ScriptRunner.java#L191-L227","documentation":"ScriptRunner throws RuntimeSqlException from checkForMissingLineTerminator() when, after consuming the whole script, leftover non-whitespace text remains in the command buffer. This means the final SQL command was never terminated with the configured delimiter (default ';'), so it was never executed. The offending partial command text is included in the message.","triggerScenarios":"A .sql script whose last statement lacks a trailing ';' (e.g. ends with the last line of an INSERT with no terminator), or a custom delimiter (setDelimiter) that does not match what the script uses on its final statement; also sendFullScript=false with a last line that has no delimiter.","commonSituations":"Hand-edited migration files where the final semicolon was accidentally deleted; scripts generated by tools that omit the last terminator; changing the delimiter for stored procedures (DELIMITER $$) but forgetting the final $$.","solutions":["Add the delimiter (';' by default) at the end of the last statement in the script.","If the script uses a different terminator, ensure the final statement ends with that exact delimiter string.","If you deliberately want the whole script sent at once, call runner.setSendFullScript(true) so line-by-line delimiter parsing is bypassed."],"exampleFix":"-- before (end of script.sql)\nINSERT INTO users(id, name) VALUES (3, 'carl')\n\n-- after\nINSERT INTO users(id, name) VALUES (3, 'carl');","handlingStrategy":"validation","validationCode":"// Before running, check the last non-whitespace chars of the script end with the delimiter\nString sql = Files.readString(path);\nString trimmed = sql.trim();\nString delim = \";\"; // match ScriptRunner delimiter\nif (!trimmed.endsWith(delim)) {\n  throw new IllegalArgumentException(\"Script must end with '\" + delim + \"': \" + path);\n}","typeGuard":null,"tryCatchPattern":"try {\n  runner.runScript(reader);\n} catch (RuntimeSqlException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"missing end-of-line terminator\")) {\n    // append delimiter and retry once with corrected script\n  }\n  throw e;\n}","preventionTips":["Add a lint step in CI that rejects .sql files not ending with the statement delimiter.","Keep delimiter usage consistent when using DELIMITER directives for procedures.","Consider setSendFullScript(true) for single-statement-test scripts where delimiter parsing is unnecessary."],"tags":["mybatis","sql","script-runner","parsing"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}