Spaces:
Paused
Paused
| ; | |
| const Packet = require('../packets/packet'); | |
| const StringParser = require('../parsers/string'); | |
| const CharsetToEncoding = require('../constants/charset_encodings.js'); | |
| const fields = ['catalog', 'schema', 'table', 'orgTable', 'name', 'orgName']; | |
| // creating JS string is relatively expensive (compared to | |
| // reading few bytes from buffer) because all string properties | |
| // except for name are unlikely to be used we postpone | |
| // string conversion until property access | |
| // | |
| // TODO: watch for integration benchmarks (one with real network buffer) | |
| // there could be bad side effect as keeping reference to a buffer makes it | |
| // sit in the memory longer (usually until final .query() callback) | |
| // Latest v8 perform much better in regard to bufferer -> string conversion, | |
| // at some point of time this optimisation might become unnecessary | |
| // see https://github.com/sidorares/node-mysql2/pull/137 | |
| // | |
| class ColumnDefinition { | |
| constructor(packet, clientEncoding) { | |
| this._buf = packet.buffer; | |
| this._clientEncoding = clientEncoding; | |
| this._catalogLength = packet.readLengthCodedNumber(); | |
| this._catalogStart = packet.offset; | |
| packet.offset += this._catalogLength; | |
| this._schemaLength = packet.readLengthCodedNumber(); | |
| this._schemaStart = packet.offset; | |
| packet.offset += this._schemaLength; | |
| this._tableLength = packet.readLengthCodedNumber(); | |
| this._tableStart = packet.offset; | |
| packet.offset += this._tableLength; | |
| this._orgTableLength = packet.readLengthCodedNumber(); | |
| this._orgTableStart = packet.offset; | |
| packet.offset += this._orgTableLength; | |
| // name is always used, don't make it lazy | |
| const _nameLength = packet.readLengthCodedNumber(); | |
| const _nameStart = packet.offset; | |
| packet.offset += _nameLength; | |
| this._orgNameLength = packet.readLengthCodedNumber(); | |
| this._orgNameStart = packet.offset; | |
| packet.offset += this._orgNameLength; | |
| packet.skip(1); // length of the following fields (always 0x0c) | |
| this.characterSet = packet.readInt16(); | |
| this.encoding = CharsetToEncoding[this.characterSet]; | |
| this.name = StringParser.decode( | |
| this._buf, | |
| this.encoding === 'binary' ? this._clientEncoding : this.encoding, | |
| _nameStart, | |
| _nameStart + _nameLength | |
| ); | |
| this.columnLength = packet.readInt32(); | |
| this.columnType = packet.readInt8(); | |
| this.type = this.columnType; | |
| this.flags = packet.readInt16(); | |
| this.decimals = packet.readInt8(); | |
| } | |
| inspect() { | |
| return { | |
| catalog: this.catalog, | |
| schema: this.schema, | |
| name: this.name, | |
| orgName: this.orgName, | |
| table: this.table, | |
| orgTable: this.orgTable, | |
| characterSet: this.characterSet, | |
| encoding: this.encoding, | |
| columnLength: this.columnLength, | |
| type: this.columnType, | |
| flags: this.flags, | |
| decimals: this.decimals | |
| }; | |
| } | |
| [Symbol.for('nodejs.util.inspect.custom')](depth, inspectOptions, inspect) { | |
| const Types = require('../constants/types.js'); | |
| const typeNames = []; | |
| for (const t in Types) { | |
| typeNames[Types[t]] = t; | |
| } | |
| const fiedFlags = require('../constants/field_flags.js'); | |
| const flagNames = []; | |
| // TODO: respect inspectOptions.showHidden | |
| //const inspectFlags = inspectOptions.showHidden ? this.flags : this.flags & ~fiedFlags.PRI_KEY; | |
| const inspectFlags = this.flags; | |
| for (const f in fiedFlags) { | |
| if (inspectFlags & fiedFlags[f]) { | |
| if (f === 'PRI_KEY') { | |
| flagNames.push('PRIMARY KEY'); | |
| } else if (f === 'NOT_NULL') { | |
| flagNames.push('NOT NULL'); | |
| } else if (f === 'BINARY') { | |
| // ignore flag for now | |
| } else if (f === 'MULTIPLE_KEY') { | |
| // not sure if that should be part of inspection. | |
| // in the schema usually this is part of index definition | |
| // example: UNIQUE KEY `my_uniq_id` (`id_box_elements`,`id_router`) | |
| // note that only first column has MULTIPLE_KEY flag set in this case | |
| // so there is no good way of knowing that this is part of index just | |
| // by looking at indifidual field flags | |
| } else if (f === 'NO_DEFAULT_VALUE') { | |
| // almost the same as NOT_NULL? | |
| } else if (f === 'BLOB') { | |
| // included in the type | |
| } else if (f === 'UNSIGNED') { | |
| // this should be first after type | |
| } else if (f === 'TIMESTAMP') { | |
| // timestamp flag is redundant for inspection - already included in type | |
| } else if (f === 'ON_UPDATE_NOW') { | |
| flagNames.push('ON UPDATE CURRENT_TIMESTAMP'); | |
| } else { | |
| flagNames.push(f); | |
| } | |
| } | |
| } | |
| if (depth > 1) { | |
| return inspect({ | |
| ...this.inspect(), | |
| typeName: typeNames[this.columnType], | |
| flags: flagNames, | |
| }); | |
| } | |
| const isUnsigned = this.flags & fiedFlags.UNSIGNED; | |
| let typeName = typeNames[this.columnType]; | |
| if (typeName === 'BLOB') { | |
| // TODO: check for non-utf8mb4 encoding | |
| if (this.columnLength === 4294967295) { | |
| typeName = 'LONGTEXT'; | |
| } else if (this.columnLength === 67108860) { | |
| typeName = 'MEDIUMTEXT'; | |
| } else if (this.columnLength === 262140) { | |
| typeName = 'TEXT'; | |
| } else if (this.columnLength === 1020) { // 255*4 | |
| typeName = 'TINYTEXT'; | |
| } else { | |
| typeName = `BLOB(${this.columnLength})`; | |
| } | |
| } else if (typeName === 'VAR_STRING') { | |
| // TODO: check for non-utf8mb4 encoding | |
| typeName = `VARCHAR(${Math.ceil(this.columnLength/4)})`; | |
| } else if (typeName === 'TINY') { | |
| if ( | |
| (this.columnLength === 3 && isUnsigned) || | |
| (this.columnLength === 4 && !isUnsigned) ) { | |
| typeName = 'TINYINT'; | |
| } else { | |
| typeName = `TINYINT(${this.columnLength})`; | |
| } | |
| } else if (typeName === 'LONGLONG') { | |
| if (this.columnLength === 20) { | |
| typeName = 'BIGINT'; | |
| } else { | |
| typeName = `BIGINT(${this.columnLength})`; | |
| } | |
| } else if (typeName === 'SHORT') { | |
| if (isUnsigned && this.columnLength === 5) { | |
| typeName = 'SMALLINT'; | |
| } else if (!isUnsigned && this.columnLength === 6) { | |
| typeName = 'SMALLINT'; | |
| } else { | |
| typeName = `SMALLINT(${this.columnLength})`; | |
| } | |
| } else if (typeName === 'LONG') { | |
| if (isUnsigned && this.columnLength === 10) { | |
| typeName = 'INT'; | |
| } else if (!isUnsigned && this.columnLength === 11) { | |
| typeName = 'INT'; | |
| } else { | |
| typeName = `INT(${this.columnLength})`; | |
| } | |
| } else if (typeName === 'INT24') { | |
| if (isUnsigned && this.columnLength === 8) { | |
| typeName = 'MEDIUMINT'; | |
| } else if (!isUnsigned && this.columnLength === 9) { | |
| typeName = 'MEDIUMINT'; | |
| } else { | |
| typeName = `MEDIUMINT(${this.columnLength})`; | |
| } | |
| } else if (typeName === 'DOUBLE') { | |
| // DOUBLE without modifiers is reported as DOUBLE(22, 31) | |
| if (this.columnLength === 22 && this.decimals === 31) { | |
| typeName = 'DOUBLE'; | |
| } else { | |
| typeName = `DOUBLE(${this.columnLength},${this.decimals})`; | |
| } | |
| } else if (typeName === 'FLOAT') { | |
| // FLOAT without modifiers is reported as FLOAT(12, 31) | |
| if (this.columnLength === 12 && this.decimals === 31) { | |
| typeName = 'FLOAT'; | |
| } else { | |
| typeName = `FLOAT(${this.columnLength},${this.decimals})`; | |
| } | |
| } else if (typeName === 'NEWDECIMAL') { | |
| if (this.columnLength === 11 && this.decimals === 0) { | |
| typeName = 'DECIMAL'; | |
| } else if (this.decimals === 0) { | |
| // not sure why, but DECIMAL(13) is reported as DECIMAL(14, 0) | |
| // and DECIMAL(13, 9) is reported as NEWDECIMAL(15, 9) | |
| if (isUnsigned) { | |
| typeName = `DECIMAL(${this.columnLength})`; | |
| } else { | |
| typeName = `DECIMAL(${this.columnLength - 1})`; | |
| } | |
| } else { | |
| typeName = `DECIMAL(${this.columnLength - 2},${this.decimals})`; | |
| } | |
| } else { | |
| typeName = `${typeNames[this.columnType]}(${this.columnLength})`; | |
| } | |
| if (isUnsigned) { | |
| typeName += ' UNSIGNED'; | |
| } | |
| // TODO respect colors option | |
| return `\`${this.name}\` ${[typeName, ...flagNames].join(' ')}`; | |
| } | |
| static toPacket(column, sequenceId) { | |
| let length = 17; // = 4 padding + 1 + 12 for the rest | |
| fields.forEach(field => { | |
| length += Packet.lengthCodedStringLength( | |
| column[field], | |
| CharsetToEncoding[column.characterSet] | |
| ); | |
| }); | |
| const buffer = Buffer.allocUnsafe(length); | |
| const packet = new Packet(sequenceId, buffer, 0, length); | |
| function writeField(name) { | |
| packet.writeLengthCodedString( | |
| column[name], | |
| CharsetToEncoding[column.characterSet] | |
| ); | |
| } | |
| packet.offset = 4; | |
| fields.forEach(writeField); | |
| packet.writeInt8(0x0c); | |
| packet.writeInt16(column.characterSet); | |
| packet.writeInt32(column.columnLength); | |
| packet.writeInt8(column.columnType); | |
| packet.writeInt16(column.flags); | |
| packet.writeInt8(column.decimals); | |
| packet.writeInt16(0); // filler | |
| return packet; | |
| } | |
| // node-mysql compatibility: alias "db" to "schema" | |
| get db() { | |
| return this.schema; | |
| } | |
| } | |
| const addString = function(name) { | |
| Object.defineProperty(ColumnDefinition.prototype, name, { | |
| get: function() { | |
| const start = this[`_${name}Start`]; | |
| const end = start + this[`_${name}Length`]; | |
| const val = StringParser.decode( | |
| this._buf, | |
| this.encoding === 'binary' ? this._clientEncoding : this.encoding, | |
| start, | |
| end | |
| ); | |
| Object.defineProperty(this, name, { | |
| value: val, | |
| writable: false, | |
| configurable: false, | |
| enumerable: false | |
| }); | |
| return val; | |
| } | |
| }); | |
| }; | |
| addString('catalog'); | |
| addString('schema'); | |
| addString('table'); | |
| addString('orgTable'); | |
| addString('orgName'); | |
| module.exports = ColumnDefinition; | |