123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- 'use strict';
- 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;
|