Traceback Layers: Script Line vs SQL Line
Before anything else, confirm which layer threw the error. An Ignition system.db.runPrepUpdate failure produces a stacked traceback. Each layer reports its own line numbering, and mixing them up sends you looking in the wrong place.
| Traceback layer | Text in this failure | What its line number refers to |
|---|---|---|
| Jython script | File "event:actionPerformed", line 10 |
Line 10 of the button's actionPerformed script, which is the system.db.runPrepUpdate(...) call |
| Ignition client/gateway wrapper |
java.lang.Exception: Error executing system.db.runPrepUpdate(...) and GatewayException: SQL error for "UPDATE workorders ..."
|
No line number. It echoes the query, the argument list, the datasource (DB), and the transaction and flag arguments |
| JDBC driver / MySQL server | MySQLSyntaxErrorException: You have an error in your SQL syntax ... near '...' at line 1 |
Line 1 of the SQL text sent to the server. A single-line UPDATE string always reports line 1 |
"Line 1" does not point at the first line of the Python script. The SQL statement is one line, so every syntax error in it reports line 1. The Python layer already told you the call site (line 10). The fault sits inside the SQL string.
Check: Do not move on until you have isolated the innermost caused by entry. Here that is the MySQLSyntaxErrorException. Everything above it is wrapping.
The "near" Fragment and the Missing Separator
MySQL's syntax error message quotes the SQL text starting at the token where the parser gave up. The defect almost always sits immediately before the quoted fragment, not inside it. In this failure the fragment is:
near 'resolution = 'Reset Antennae and made sure cows could not get to it', timespent ' at line 1
The parameter values appear already substituted into the fragment. The MySQL JDBC driver builds the final statement text with the bound values inlined before sending it, so the error shows literal data rather than ?. That is normal and does not mean the parameters were mishandled.
Look at what precedes resolution in the original query:
... sitelocation = ?, problem = ? resolution = ?, timespent = ? ...
There is no comma between problem = ? and resolution = ?. In an UPDATE ... SET clause, each column = value assignment must be separated by a comma. The parser accepts problem = 'Bent Antennae' as a complete assignment. It then expects either a comma, WHERE, ORDER BY, LIMIT, or end of statement. Instead it finds a bare identifier resolution and fails at that point.
Check: Read the SQL string left to right and confirm exactly one comma between every pair of assignments and no comma before WHERE.
Corrected SET List
Insert the comma and leave everything else unchanged for the first test. That way the syntax fix is the only variable.
grp = event.source.parent.getComponent('Group')
clientname = grp.getComponent('Text Field').text
dateofwork = grp.getComponent('Text Field 1').text
progress = grp.getComponent('Numeric Text Field').intValue
sitelocation = grp.getComponent('Text Field 2').text
problem = grp.getComponent('Text Field 3').text
resolution = grp.getComponent('Text Area').text
timespent = event.source.parent.getComponent('Text Field 3').text
db = "DB"
updateQuery = """UPDATE workorders
SET clientname = ?,
dateofwork = ?,
progress = ?,
sitelocation = ?,
problem = ?,
resolution = ?,
timespent = ?
WHERE dateofwork = ?"""
rows = system.db.runPrepUpdate(updateQuery,
[clientname, dateofwork, progress, sitelocation, problem, resolution, timespent, dateofwork],
db)
print rows
Two changes beyond the comma, neither of which alters behavior:
- Cache the
Groupcontainer ingrp. This shortens each line and makes a wrong container reference easy to spot. - Put one assignment per line inside the triple-quoted string. A missing trailing comma then shows as a visually broken column. MySQL treats the newlines as whitespace. After this change, the error line number will point at the actual SQL line if a syntax error recurs.
Check: Run the button. A non-negative integer printed to the console (or the Designer output window) confirms the statement parsed and executed. A new MySQLSyntaxErrorException means a second defect exists. Read its near fragment the same way.
Placeholder and Argument Alignment
runPrepUpdate binds arguments to ? placeholders strictly by position. A syntax fix that adds or removes a placeholder shifts every value after it. Confirm the count and order before trusting any result.
| Position | Placeholder | Bound argument | Value in the failed call |
|---|---|---|---|
| 1 | clientname = ? |
clientname |
Sonerra |
| 2 | dateofwork = ? |
dateofwork |
6/4/2015 |
| 3 | progress = ? |
progress (intValue) |
75 |
| 4 | sitelocation = ? |
sitelocation |
Soaring Eagle |
| 5 | problem = ? |
problem |
Bent Antennae |
| 6 | resolution = ? |
resolution |
Reset Antennae and made sure cows could not get to it |
| 7 | timespent = ? |
timespent |
3 hour |
| 8 | WHERE dateofwork = ? |
dateofwork |
6/4/2015 |
Eight placeholders map to eight arguments, so alignment is correct. Note the component references. problem reads 'Text Field 3' inside Group, while timespent reads a different 'Text Field 3' directly under the parent container. The logged values differ ("Bent Antennae" vs "3 hour"), so these are two separate components with the same name in different containers. That works, but it is fragile. Rename one (for example txtTimeSpent) so a future copy-paste cannot silently read the wrong field.
Check: Count ? characters in the query and the length of the argument list. They must be equal before you proceed.
WHERE Clause Key and Date Handling
The statement now parses, but the WHERE clause decides which rows change. Settle it before this screen goes into use.
-
Non-unique key.
WHERE dateofwork = ?updates every work order with that date. Two jobs on 6/4/2015 would both be overwritten with the same client, site, problem, and resolution. Filter on the table's primary key (typically an auto-increment ID column; read the actual name from the table definition). Carry that ID in a hidden component or custom property when the record loads into the form. -
Updating the key you filter on. The query sets
dateofworkand filters ondateofworkusing the same value. It can never change a work order's date. If the operator edits the date field, theWHEREclause looks for the new date and misses the original row. Filtering on the primary key removes this problem. -
Date format vs column type.
dateofworkis passed as text inM/D/YYYYform. If the MySQL column isVARCHAR, the comparison is a plain string match. If the column isDATEorDATETIME, MySQL expectsYYYY-MM-DDordering. A6/4/2015string will not match stored dates reliably and can write a zero or invalid date. Check the column type withSHOW CREATE TABLE workorders. If it is a date type, use a Calendar or Popup Calendar component and pass its date value as the parameter so the driver binds a real date. -
Text vs numeric columns.
timespentcarries "3 hour", so that column must be a character type.progressis bound as an integer fromintValue, which suits anINTcolumn.
Check: The return value of runPrepUpdate is the affected-row count. For a single-record edit form it must be exactly 1.
Symptoms and Causes After the Syntax Fix
| Symptom | Likely cause | Where to look |
|---|---|---|
MySQLSyntaxErrorException ... near 'X' |
Missing comma, keyword, or quote immediately before X
|
SQL string, the token just ahead of the quoted fragment |
Python traceback line points at the runPrepUpdate call |
Error raised by the database, not by script logic | Innermost caused by entry |
| Return value 0 |
WHERE matched no row (wrong key, date format mismatch, edited key field) |
SELECT with the same WHERE and parameters |
| Return value greater than 1 | Filter column not unique | Switch the filter to the primary key |
| Error about parameter count or index |
? count differs from argument list length |
Placeholder/argument table above |
| Wrong data in a column | Argument order shifted, or a same-named component read from the wrong container | Print the argument list before the call |
Copy-Edit Pitfall and a Separator-Proof Query Builder
A second script that "looks almost identical" and works is the usual trap. Visual comparison skips punctuation. Diff the two SQL strings character by character, or avoid hand-typed separators entirely. Generate the SET list from a column list so commas are always correct and the argument order always matches:
grp = event.source.parent.getComponent('Group')
fields = [
("clientname", grp.getComponent('Text Field').text),
("dateofwork", grp.getComponent('Text Field 1').text),
("progress", grp.getComponent('Numeric Text Field').intValue),
("sitelocation", grp.getComponent('Text Field 2').text),
("problem", grp.getComponent('Text Field 3').text),
("resolution", grp.getComponent('Text Area').text),
("timespent", event.source.parent.getComponent('Text Field 3').text),
]
setClause = ", ".join(["%s = ?" % f[0] for f in fields])
args = [f[1] for f in fields]
keyValue = grp.getComponent('Text Field 1').text # replace with the primary key value
args.append(keyValue)
query = "UPDATE workorders SET " + setClause + " WHERE dateofwork = ?"
rows = system.db.runPrepUpdate(query, args, "DB")
print query
print rows
Column names come from a fixed list in the script, never from operator input. Only the values go through ? binding. Keep it that way so the query stays parameterized.
Check: The printed query shows six commas in the SET clause for seven columns and none before WHERE.
End-to-End Verification
- Test the SQL outside the script first. Paste the corrected statement into the Designer's Database Query Browser against the
DBdatasource with literal values substituted. Confirm it executes without a syntax error. - Run a
SELECTwith the sameWHEREclause and value. Confirm it returns exactly one row. If it returns zero or several rows, fix the key before running the update. - Trigger the button in a Designer preview or a client. Confirm the output console shows no traceback and the printed row count is 1.
- Re-run the
SELECT. Confirm every column holds the value typed into its intended component: client, date, progress 75, site, problem text inproblem, resolution text inresolution, and "3 hour" intimespent. - Edit one field only (for example progress) and repeat. Confirm row count 1 and that only that column changed. This proves the argument order and the key filter together.
FAQ
What happens if a comma is missing between two assignments in an UPDATE SET clause?
MySQL parses the first assignment, meets the next column name where it expects a comma or WHERE, and throws a MySQLSyntaxErrorException. The near '...' text begins at that second column name, so the missing comma sits just before the quoted fragment.
What happens if the number of ? placeholders does not match the argument list in system.db.runPrepUpdate?
The JDBC driver rejects the statement with a parameter count or index error, or the values bind one position off and land in the wrong columns. Count the ? characters and the list length, and keep them equal.
Why does the MySQL error say line 1 when my script error is on line 10?
Line 10 is the Jython line that calls runPrepUpdate. Line 1 is the line inside the SQL text, and a single-line query always reports line 1. Split the SQL across lines in a triple-quoted string to get a more precise line number.
What happens if runPrepUpdate returns 0 with no error?
The statement parsed and ran, but the WHERE clause matched nothing. Common causes are a date string like 6/4/2015 compared against a DATE column, or an edited key field. Run a SELECT with the same filter and switch to the primary key.