MySQL Node Odd ER_PARSE_ERROR 5 out of 38,000 records

Hi,
Odd error here and pulling my hair out, I'm processing a CSV file with some 37,800 plus records
All get inserted into MySQL except 5. The csv will change over time, modified and added too. I download from source once a week and insert records into DB.

There are 5 records in the current csv that produce an error "ER_PARSE_ERROR", same records every time.
I extracted those records and created just a csv of those 5, all 5 fail (expected).

First thought is must be something in the data, however if I dump out the msg.topic via node warn or a catch node. Nothing jumps out and if I run the SQL in the terminal monitor of MySQL (MySQL running in linux) the SQL is fine and inserts as expected.

CSV

partnumber,name,brand,shortdescription,lastmodified
534-202,"Throttle Position Kit for Electric Choke Carburetors",Holley,"Running an electric choke Holley carburetor and having trouble figuring out how to get that new electronic overdrive transmission to work? Holley has a solution for you.","2021-06-19 09:03:14"
84891,"Chevy Flat Top Pro-Billet Distributor",MSD,"Need blower/intake clearance? The Flat top is your answer. Must be used with an MSD 6, 7 or 8-series ignition.","2021-06-19 09:03:18"
C6TZ-96101767-OR,"Scott Drake 1966-77 Bronco Outer Rocker Panel - Weld Thru Primer","Scott Drake","1966-77 Bronco Outer Rocker Panel ? Weld Thru Primer","2021-05-04 14:02:44"
USA-BLUEKIT,"Scott Drake Bluetooth® Interface for Custom Autosound CD Controller Radios","Scott Drake","Bluetooth? Interface for Custom Autosound CD Controller Radios","2021-05-05 09:03:38"

Produces SQl (1 example)

START TRANSACTION;INSERT INTO vehicle_partnumbers (partnumber,name,ShortDescription,lastmodified) VALUES ('84891','Chevy Flat Top Pro-Billet Distributor','Need blower/intake clearance? The Flat top is your answer. Must be used with an MSD 6, 7 or 8-series ignition.','2021-06-19 09:03:18') ON DUPLICATE KEY UPDATE name='Chevy Flat Top Pro-Billet Distributor', ShortDescription='Need blower/intake clearance? The Flat top is your answer. Must be used with an MSD 6, 7 or 8-series ignition.', lastmodified='2021-06-19 09:03:18',partnumber_id=LAST_INSERT_ID(partnumber_id);SET @last_id_in_vehicle_partnumbers = LAST_INSERT_ID();INSERT INTO vehicle_brand (brand,lastmodified) VALUES ('MSD','2021-06-19 09:03:18') ON DUPLICATE KEY UPDATE lastmodified='2021-06-19 09:03:18', brand_id=LAST_INSERT_ID(brand_id);SET @last_id_in_vehicle_brand = LAST_INSERT_ID();INSERT IGNORE INTO partnumber_vehicle_brand_link (partnumber_id,brand_id) VALUES(@last_id_in_vehicle_partnumbers,@last_id_in_vehicle_brand);COMMIT;

Fails in MySql node but works directly in MySql.

Function creating SQL, remember some 37k plus records work, it's just the same 5 that don't

const mysql = global.get("mysql");
//load payload into data object
const data = msg.payload;


msg.topic ='';

//start transaction;
let insert = "START TRANSACTION;";

//insert part number
insert += "INSERT INTO vehicle_partnumbers (partnumber,name,ShortDescription,lastmodified) VALUES (" + mysql.escape(data.partnumber) + "," + mysql.escape(data.name) + ",";
insert += mysql.escape(data.shortdescription) + "," + mysql.escape(data.lastmodified) + ") ";
insert += "ON DUPLICATE KEY UPDATE name=" + mysql.escape(data.name) + ", ShortDescription=" + mysql.escape(data.shortdescription) + ", lastmodified=" + mysql.escape(data.lastmodified) + ",";
insert += "partnumber_id=LAST_INSERT_ID(partnumber_id);";

insert += "SET @last_id_in_vehicle_partnumbers = LAST_INSERT_ID();";

//check if we have brand with a value.
if ( data.hasOwnProperty('brand' ) && data.brand != null ){ 
    
    //insert Brand
    insert += "INSERT INTO vehicle_brand (brand,lastmodified) VALUES (" + mysql.escape(data.brand) + "," + mysql.escape(data.lastmodified) + ") ";
    insert += "ON DUPLICATE KEY UPDATE lastmodified=" + mysql.escape(data.lastmodified) + ", brand_id=LAST_INSERT_ID(brand_id);";
    insert += "SET @last_id_in_vehicle_brand = LAST_INSERT_ID();";
    insert += "INSERT IGNORE INTO partnumber_vehicle_brand_link (partnumber_id,brand_id) VALUES(@last_id_in_vehicle_partnumbers,@last_id_in_vehicle_brand);";
}
//commit transaction
insert += "COMMIT;";
msg.topic = insert;
//node.warn(msg.topic);
return msg;

I was able to get a little bit more information out if the catch node and dumping to the sys log

 18 Oct 16:25:27 - [info] [debug:80e9217117a93199] 
node-red_1  | {
node-red_1  |   topic: "START TRANSACTION;INSERT INTO vehicle_partnumbers (partnumber,name,ShortDescription,lastmodified) VALUES ('C6TZ-96101767-OR','Scott Drake 1966-77 Bronco Outer Rocker Panel - Weld Thru Primer','1966-77 Bronco Outer Rocker Panel ? Weld Thru Primer','2021-05-04 14:02:44') ON DUPLICATE KEY UPDATE name='Scott Drake 1966-77 Bronco Outer Rocker Panel - Weld Thru Primer', ShortDescription='1966-77 Bronco Outer Rocker Panel ? Weld Thru Primer', lastmodified='2021-05-04 14:02:44',partnumber_id=LAST_INSERT_ID(partnumber_id);SET @last_id_in_vehicle_partnumbers = LAST_INSERT_ID();INSERT INTO vehicle_brand (brand,lastmodified) VALUES ('Scott Drake','2021-05-04 14:02:44') ON DUPLICATE KEY UPDATE lastmodified='2021-05-04 14:02:44', brand_id=LAST_INSERT_ID(brand_id);SET @last_id_in_vehicle_brand = LAST_INSERT_ID();INSERT IGNORE INTO partnumber_vehicle_brand_link (partnumber_id,brand_id) VALUES(@last_id_in_vehicle_partnumbers,@last_id_in_vehicle_brand);COMMIT;",
node-red_1  |   filename: '/data/holley/information/csvimport/errord.csv',
node-red_1  |   payload: {
node-red_1  |     partnumber: 'C6TZ-96101767-OR',
node-red_1  |     name: 'Scott Drake 1966-77 Bronco Outer Rocker Panel - Weld Thru Primer',
node-red_1  |     brand: 'Scott Drake',
node-red_1  |     shortdescription: '1966-77 Bronco Outer Rocker Panel ? Weld Thru Primer',
node-red_1  |     lastmodified: '2021-05-04 14:02:44'
node-red_1  |   },
node-red_1  |   _msgid: '5339ee0faf71ee93',
node-red_1  |   columns: 'partnumber,name,brand,shortdescription,lastmodified',
node-red_1  |   parts: { index: 3, ch: '\n', type: 'string', id: '729cd342f1d2c3a1' },
node-red_1  |   error: {
node-red_1  |     message: "Error: ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'C6TZ-96101767-OR', `name` = 'Scott Drake 1966-77 Bronco Outer Rocker Panel - Wel' at line 1",
node-red_1  |     source: {
node-red_1  |       id: '6e7ecd02169bb67b',
node-red_1  |       type: 'mysql',
node-red_1  |       name: 'Bulk Insert',
node-red_1  |       count: 1
node-red_1  |     },
node-red_1  |     stack: "Error: ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'C6TZ-96101767-OR', `name` = 'Scott Drake 1966-77 Bronco Outer Rocker Panel - Wel' at line 1\n" +
node-red_1  |       '    at Query.Sequence._packetToError (/usr/src/node-red/node_modules/mysql/lib/protocol/sequences/Sequence.js:47:14)\n' +
node-red_1  |       '    at Query.ErrorPacket (/usr/src/node-red/node_modules/mysql/lib/protocol/sequences/Query.js:79:18)\n' +
node-red_1  |       '    at Protocol._parsePacket (/usr/src/node-red/node_modules/mysql/lib/protocol/Protocol.js:291:23)\n' +
node-red_1  |       '    at Parser._parsePacket (/usr/src/node-red/node_modules/mysql/lib/protocol/Parser.js:433:10)\n' +
node-red_1  |       '    at Parser.write (/usr/src/node-red/node_modules/mysql/lib/protocol/Parser.js:43:10)\n' +
node-red_1  |       '    at Protocol.write (/usr/src/node-red/node_modules/mysql/lib/protocol/Protocol.js:38:16)\n' +
node-red_1  |       '    at Socket.<anonymous> (/usr/src/node-red/node_modules/mysql/lib/Connection.js:88:28)\n' +
node-red_1  |       '    at Socket.<anonymous> (/usr/src/node-red/node_modules/mysql/lib/Connection.js:526:10)\n' +
node-red_1  |       '    at Socket.emit (events.js:375:28)\n' +
node-red_1  |       '    at addChunk (internal/streams/readable.js:290:12)\n' +
node-red_1  |       '    --------------------\n' +
node-red_1  |       '    at Pool.query (/usr/src/node-red/node_modules/mysql/lib/Pool.js:199:23)\n' +
node-red_1  |       '    at MysqlDBNodeIn._inputCallback (/usr/src/node-red/node_modules/node-red-node-mysql/68-mysql.js:131:46)\n' +
node-red_1  |       '    at /usr/src/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:203:26\n' +
node-red_1  |       '    at Object.trigger (/usr/src/node-red/node_modules/@node-red/util/lib/hooks.js:149:13)\n' +
node-red_1  |       '    at MysqlDBNodeIn.Node._emitInput (/usr/src/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:195:11)\n' +
node-red_1  |       '    at MysqlDBNodeIn.Node.emit (/usr/src/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:179:25)\n' +
node-red_1  |       '    at MysqlDBNodeIn.Node.receive (/usr/src/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:478:10)\n' +
node-red_1  |       '    at Immediate.<anonymous> (/usr/src/node-red/node_modules/@node-red/runtime/lib/flows/Flow.js:679:52)\n' +
node-red_1  |       '    at processImmediate (internal/timers.js:464:21)'
node-red_1  |   }
node-red_1  | }

Is "Scott Drake 1966-77 Bronco Outer Rocker Panel - Weld Thru Primer" longer than the definition of the field ShortDescription?

No I doubled check them

CREATE TABLE vehicle_partnumbers (
    partnumber_id int(11) UNSIGNED UNIQUE NOT NULL  AUTO_INCREMENT,
    partnumber varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci PRIMARY KEY,
    `name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
    ShortDescription varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
    lastmodified DATETIME
);
ALTER TABLE vehicle_partnumbers ADD UNIQUE INDEX partnumber (partnumber);
DROP TABLE IF EXISTS vehicle_brand;
CREATE TABLE vehicle_brand (
    brand_id int(11) UNSIGNED UNIQUE NOT NULL PRIMARY KEY AUTO_INCREMENT,
    brand varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
    lastmodified DATETIME
);
ALTER TABLE vehicle_brand ADD UNIQUE INDEX brand (brand);

To add onto @jbudd’s question, would take one of the data rows and change - one at a time - the value to a minimum size. I.e. Chang each text string to ‘xxx’ but only one at a time and see what happens.

Also are you certain there is not an unprintable character in the data somewhere? Check by completely retyping the start of the csv (don't copy/paste anything) and see if it still fails.

It's the question mark inside the short description,

this fails

534-202,"Throttle Position Kit for Electric Choke Carburetors",Holley,"Running an electric choke Holley carburetor and having trouble figuring out how to get that new electronic overdrive transmission to work? Holley has a solution for you.","2021-06-19 09:03:14"

This works

534-202,"Throttle Position Kit for Electric Choke Carburetors",Holley,"Running an electric choke Holley carburetor and having trouble figuring out how to get that new electronic overdrive transmission to work Holley has a solution for you.","2021-06-19 09:03:14"

Quoted text but assume the question mark is being picked up by Node Red MySql node as a prepared statement and expect the value in the payload.

Fixed: Leaving for someone else who as the same issue.

Clearing the msg.payload cleared the issue.

The csv row was in the payload and I had it still in the msg object for debugging.

Assuming the question mark was being treated as prepared statement and it was trying to replace the ? with the payload. setting msg.payload = ''; fixed the issue.

Thanks for the input, I couldn't see the forest for the trees

harry

1 Like

Do single rather than double quotes fix it?

They were single quotes

fyi. loading mysql node globally to get access to mysql escape.

const mysql = global.get("mysql");

mysql.esacpe adds the single quotes for you.

VALUES (" + mysql.escape(data.partnumber) + "," + mysql.escape(data.name) + "

Produces

VALUES ('USA-BLUEKIT','Scott Drake Bluetooth&reg; Interface for Custom Autosound CD Controller Radios','Bluetooth? Interface for Custom Autosound CD Controller Radios','2021-05-05 09:03:38')

Note the single quotes added.

Harry

edit
Oh you meant in the js
VALUES (' + mysql.escape(data.partnumber) + ',' + mysql.escape(data.name) + '

Isn't the better (and safer) solution to actually use prepared statements?

Ideally yes, but I have not been able to figure out how to use them in node-red when chaining/making one long sql call as in this example. Some of the csv's we import have 750K rows and makes it more efficient to make one call rather than a series of many.

I'm always willing to learn if someone can demonstrate how to do the above with prepared statements.

One of the reasons I import msql node js is so that I can escape the values safely.

harry

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.