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

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