215 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| const Types = require('../constants/types.js');
 | |
| const Charsets = require('../constants/charsets.js');
 | |
| const helpers = require('../helpers');
 | |
| const genFunc = require('generate-function');
 | |
| const parserCache = require('./parser_cache.js');
 | |
| 
 | |
| const typeNames = [];
 | |
| for (const t in Types) {
 | |
|   typeNames[Types[t]] = t;
 | |
| }
 | |
| 
 | |
| function readCodeFor(type, charset, encodingExpr, config, options) {
 | |
|   const supportBigNumbers = Boolean(
 | |
|     options.supportBigNumbers || config.supportBigNumbers
 | |
|   );
 | |
|   const bigNumberStrings = Boolean(
 | |
|     options.bigNumberStrings || config.bigNumberStrings
 | |
|   );
 | |
|   const timezone = options.timezone || config.timezone;
 | |
|   const dateStrings = options.dateStrings || config.dateStrings;
 | |
| 
 | |
|   switch (type) {
 | |
|     case Types.TINY:
 | |
|     case Types.SHORT:
 | |
|     case Types.LONG:
 | |
|     case Types.INT24:
 | |
|     case Types.YEAR:
 | |
|       return 'packet.parseLengthCodedIntNoBigCheck()';
 | |
|     case Types.LONGLONG:
 | |
|       if (supportBigNumbers && bigNumberStrings) {
 | |
|         return 'packet.parseLengthCodedIntString()';
 | |
|       }
 | |
|       return `packet.parseLengthCodedInt(${supportBigNumbers})`;
 | |
|     case Types.FLOAT:
 | |
|     case Types.DOUBLE:
 | |
|       return 'packet.parseLengthCodedFloat()';
 | |
|     case Types.NULL:
 | |
|       return 'packet.readLengthCodedNumber()';
 | |
|     case Types.DECIMAL:
 | |
|     case Types.NEWDECIMAL:
 | |
|       if (config.decimalNumbers) {
 | |
|         return 'packet.parseLengthCodedFloat()';
 | |
|       }
 | |
|       return 'packet.readLengthCodedString("ascii")';
 | |
|     case Types.DATE:
 | |
|       if (helpers.typeMatch(type, dateStrings, Types)) {
 | |
|         return 'packet.readLengthCodedString("ascii")';
 | |
|       }
 | |
|       return `packet.parseDate(${helpers.srcEscape(timezone)})`;
 | |
|     case Types.DATETIME:
 | |
|     case Types.TIMESTAMP:
 | |
|       if (helpers.typeMatch(type, dateStrings, Types)) {
 | |
|         return 'packet.readLengthCodedString("ascii")';
 | |
|       }
 | |
|       return `packet.parseDateTime(${helpers.srcEscape(timezone)})`;
 | |
|     case Types.TIME:
 | |
|       return 'packet.readLengthCodedString("ascii")';
 | |
|     case Types.GEOMETRY:
 | |
|       return 'packet.parseGeometryValue()';
 | |
|     case Types.VECTOR:
 | |
|       return 'packet.parseVector()';
 | |
|     case Types.JSON:
 | |
|       // Since for JSON columns mysql always returns charset 63 (BINARY),
 | |
|       // we have to handle it according to JSON specs and use "utf8",
 | |
|       // see https://github.com/sidorares/node-mysql2/issues/409
 | |
|       return config.jsonStrings
 | |
|         ? 'packet.readLengthCodedString("utf8")'
 | |
|         : 'JSON.parse(packet.readLengthCodedString("utf8"))';
 | |
|     default:
 | |
|       if (charset === Charsets.BINARY) {
 | |
|         return 'packet.readLengthCodedBuffer()';
 | |
|       }
 | |
|       return `packet.readLengthCodedString(${encodingExpr})`;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function compile(fields, options, config) {
 | |
|   // use global typeCast if current query doesn't specify one
 | |
|   if (
 | |
|     typeof config.typeCast === 'function' &&
 | |
|     typeof options.typeCast !== 'function'
 | |
|   ) {
 | |
|     options.typeCast = config.typeCast;
 | |
|   }
 | |
| 
 | |
|   function wrap(field, _this) {
 | |
|     return {
 | |
|       type: typeNames[field.columnType],
 | |
|       length: field.columnLength,
 | |
|       db: field.schema,
 | |
|       table: field.table,
 | |
|       name: field.name,
 | |
|       string: function (encoding = field.encoding) {
 | |
|         if (field.columnType === Types.JSON && encoding === field.encoding) {
 | |
|           // Since for JSON columns mysql always returns charset 63 (BINARY),
 | |
|           // we have to handle it according to JSON specs and use "utf8",
 | |
|           // see https://github.com/sidorares/node-mysql2/issues/1661
 | |
|           console.warn(
 | |
|             `typeCast: JSON column "${field.name}" is interpreted as BINARY by default, recommended to manually set utf8 encoding: \`field.string("utf8")\``
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         return _this.packet.readLengthCodedString(encoding);
 | |
|       },
 | |
|       buffer: function () {
 | |
|         return _this.packet.readLengthCodedBuffer();
 | |
|       },
 | |
|       geometry: function () {
 | |
|         return _this.packet.parseGeometryValue();
 | |
|       },
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   const parserFn = genFunc();
 | |
| 
 | |
|   parserFn('(function () {')('return class TextRow {');
 | |
| 
 | |
|   // constructor method
 | |
|   parserFn('constructor(fields) {');
 | |
|   // node-mysql typeCast compatibility wrapper
 | |
|   // see https://github.com/mysqljs/mysql/blob/96fdd0566b654436624e2375c7b6604b1f50f825/lib/protocol/packets/Field.js
 | |
|   if (typeof options.typeCast === 'function') {
 | |
|     parserFn('const _this = this;');
 | |
|     parserFn('for(let i=0; i<fields.length; ++i) {');
 | |
|     parserFn('this[`wrap${i}`] = wrap(fields[i], _this);');
 | |
|     parserFn('}');
 | |
|   }
 | |
|   parserFn('}');
 | |
| 
 | |
|   // next method
 | |
|   parserFn('next(packet, fields, options) {');
 | |
|   parserFn('this.packet = packet;');
 | |
|   if (options.rowsAsArray) {
 | |
|     parserFn(`const result = new Array(${fields.length});`);
 | |
|   } else {
 | |
|     parserFn('const result = {};');
 | |
|   }
 | |
| 
 | |
|   const resultTables = {};
 | |
|   let resultTablesArray = [];
 | |
| 
 | |
|   if (options.nestTables === true) {
 | |
|     for (let i = 0; i < fields.length; i++) {
 | |
|       resultTables[fields[i].table] = 1;
 | |
|     }
 | |
|     resultTablesArray = Object.keys(resultTables);
 | |
|     for (let i = 0; i < resultTablesArray.length; i++) {
 | |
|       parserFn(`result[${helpers.fieldEscape(resultTablesArray[i])}] = {};`);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   let lvalue = '';
 | |
|   let fieldName = '';
 | |
|   let tableName = '';
 | |
|   for (let i = 0; i < fields.length; i++) {
 | |
|     fieldName = helpers.fieldEscape(fields[i].name);
 | |
|     // parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
 | |
| 
 | |
|     if (typeof options.nestTables === 'string') {
 | |
|       lvalue = `result[${helpers.fieldEscape(fields[i].table + options.nestTables + fields[i].name)}]`;
 | |
|     } else if (options.nestTables === true) {
 | |
|       tableName = helpers.fieldEscape(fields[i].table);
 | |
| 
 | |
|       parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`);
 | |
|       lvalue = `result[${tableName}][${fieldName}]`;
 | |
|     } else if (options.rowsAsArray) {
 | |
|       lvalue = `result[${i.toString(10)}]`;
 | |
|     } else {
 | |
|       lvalue = `result[${fieldName}]`;
 | |
|     }
 | |
|     if (options.typeCast === false) {
 | |
|       parserFn(`${lvalue} = packet.readLengthCodedBuffer();`);
 | |
|     } else {
 | |
|       const encodingExpr = `fields[${i}].encoding`;
 | |
|       const readCode = readCodeFor(
 | |
|         fields[i].columnType,
 | |
|         fields[i].characterSet,
 | |
|         encodingExpr,
 | |
|         config,
 | |
|         options
 | |
|       );
 | |
|       if (typeof options.typeCast === 'function') {
 | |
|         parserFn(
 | |
|           `${lvalue} = options.typeCast(this.wrap${i}, function() { return ${readCode} });`
 | |
|         );
 | |
|       } else {
 | |
|         parserFn(`${lvalue} = ${readCode};`);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   parserFn('return result;');
 | |
|   parserFn('}');
 | |
|   parserFn('};')('})()');
 | |
| 
 | |
|   if (config.debug) {
 | |
|     helpers.printDebugWithCode(
 | |
|       'Compiled text protocol row parser',
 | |
|       parserFn.toString()
 | |
|     );
 | |
|   }
 | |
|   if (typeof options.typeCast === 'function') {
 | |
|     return parserFn.toFunction({ wrap });
 | |
|   }
 | |
|   return parserFn.toFunction();
 | |
| }
 | |
| 
 | |
| function getTextParser(fields, options, config) {
 | |
|   return parserCache.getParser('text', fields, options, config, compile);
 | |
| }
 | |
| 
 | |
| module.exports = getTextParser;
 |