column_definition.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. 'use strict';
  2. const Packet = require('../packets/packet');
  3. const StringParser = require('../parsers/string');
  4. const CharsetToEncoding = require('../constants/charset_encodings.js');
  5. const fields = ['catalog', 'schema', 'table', 'orgTable', 'name', 'orgName'];
  6. // creating JS string is relatively expensive (compared to
  7. // reading few bytes from buffer) because all string properties
  8. // except for name are unlikely to be used we postpone
  9. // string conversion until property access
  10. //
  11. // TODO: watch for integration benchmarks (one with real network buffer)
  12. // there could be bad side effect as keeping reference to a buffer makes it
  13. // sit in the memory longer (usually until final .query() callback)
  14. // Latest v8 perform much better in regard to bufferer -> string conversion,
  15. // at some point of time this optimisation might become unnecessary
  16. // see https://github.com/sidorares/node-mysql2/pull/137
  17. //
  18. class ColumnDefinition {
  19. constructor(packet, clientEncoding) {
  20. this._buf = packet.buffer;
  21. this._clientEncoding = clientEncoding;
  22. this._catalogLength = packet.readLengthCodedNumber();
  23. this._catalogStart = packet.offset;
  24. packet.offset += this._catalogLength;
  25. this._schemaLength = packet.readLengthCodedNumber();
  26. this._schemaStart = packet.offset;
  27. packet.offset += this._schemaLength;
  28. this._tableLength = packet.readLengthCodedNumber();
  29. this._tableStart = packet.offset;
  30. packet.offset += this._tableLength;
  31. this._orgTableLength = packet.readLengthCodedNumber();
  32. this._orgTableStart = packet.offset;
  33. packet.offset += this._orgTableLength;
  34. // name is always used, don't make it lazy
  35. const _nameLength = packet.readLengthCodedNumber();
  36. const _nameStart = packet.offset;
  37. packet.offset += _nameLength;
  38. this._orgNameLength = packet.readLengthCodedNumber();
  39. this._orgNameStart = packet.offset;
  40. packet.offset += this._orgNameLength;
  41. packet.skip(1); // length of the following fields (always 0x0c)
  42. this.characterSet = packet.readInt16();
  43. this.encoding = CharsetToEncoding[this.characterSet];
  44. this.name = StringParser.decode(
  45. this._buf,
  46. this.encoding === 'binary' ? this._clientEncoding : this.encoding,
  47. _nameStart,
  48. _nameStart + _nameLength
  49. );
  50. this.columnLength = packet.readInt32();
  51. this.columnType = packet.readInt8();
  52. this.type = this.columnType;
  53. this.flags = packet.readInt16();
  54. this.decimals = packet.readInt8();
  55. }
  56. inspect() {
  57. return {
  58. catalog: this.catalog,
  59. schema: this.schema,
  60. name: this.name,
  61. orgName: this.orgName,
  62. table: this.table,
  63. orgTable: this.orgTable,
  64. characterSet: this.characterSet,
  65. encoding: this.encoding,
  66. columnLength: this.columnLength,
  67. type: this.columnType,
  68. flags: this.flags,
  69. decimals: this.decimals
  70. };
  71. }
  72. [Symbol.for('nodejs.util.inspect.custom')](depth, inspectOptions, inspect) {
  73. const Types = require('../constants/types.js');
  74. const typeNames = [];
  75. for (const t in Types) {
  76. typeNames[Types[t]] = t;
  77. }
  78. const fiedFlags = require('../constants/field_flags.js');
  79. const flagNames = [];
  80. // TODO: respect inspectOptions.showHidden
  81. //const inspectFlags = inspectOptions.showHidden ? this.flags : this.flags & ~fiedFlags.PRI_KEY;
  82. const inspectFlags = this.flags;
  83. for (const f in fiedFlags) {
  84. if (inspectFlags & fiedFlags[f]) {
  85. if (f === 'PRI_KEY') {
  86. flagNames.push('PRIMARY KEY');
  87. } else if (f === 'NOT_NULL') {
  88. flagNames.push('NOT NULL');
  89. } else if (f === 'BINARY') {
  90. // ignore flag for now
  91. } else if (f === 'MULTIPLE_KEY') {
  92. // not sure if that should be part of inspection.
  93. // in the schema usually this is part of index definition
  94. // example: UNIQUE KEY `my_uniq_id` (`id_box_elements`,`id_router`)
  95. // note that only first column has MULTIPLE_KEY flag set in this case
  96. // so there is no good way of knowing that this is part of index just
  97. // by looking at indifidual field flags
  98. } else if (f === 'NO_DEFAULT_VALUE') {
  99. // almost the same as NOT_NULL?
  100. } else if (f === 'BLOB') {
  101. // included in the type
  102. } else if (f === 'UNSIGNED') {
  103. // this should be first after type
  104. } else if (f === 'TIMESTAMP') {
  105. // timestamp flag is redundant for inspection - already included in type
  106. } else if (f === 'ON_UPDATE_NOW') {
  107. flagNames.push('ON UPDATE CURRENT_TIMESTAMP');
  108. } else {
  109. flagNames.push(f);
  110. }
  111. }
  112. }
  113. if (depth > 1) {
  114. return inspect({
  115. ...this.inspect(),
  116. typeName: typeNames[this.columnType],
  117. flags: flagNames,
  118. });
  119. }
  120. const isUnsigned = this.flags & fiedFlags.UNSIGNED;
  121. let typeName = typeNames[this.columnType];
  122. if (typeName === 'BLOB') {
  123. // TODO: check for non-utf8mb4 encoding
  124. if (this.columnLength === 4294967295) {
  125. typeName = 'LONGTEXT';
  126. } else if (this.columnLength === 67108860) {
  127. typeName = 'MEDIUMTEXT';
  128. } else if (this.columnLength === 262140) {
  129. typeName = 'TEXT';
  130. } else if (this.columnLength === 1020) { // 255*4
  131. typeName = 'TINYTEXT';
  132. } else {
  133. typeName = `BLOB(${this.columnLength})`;
  134. }
  135. } else if (typeName === 'VAR_STRING') {
  136. // TODO: check for non-utf8mb4 encoding
  137. typeName = `VARCHAR(${Math.ceil(this.columnLength/4)})`;
  138. } else if (typeName === 'TINY') {
  139. if (
  140. (this.columnLength === 3 && isUnsigned) ||
  141. (this.columnLength === 4 && !isUnsigned) ) {
  142. typeName = 'TINYINT';
  143. } else {
  144. typeName = `TINYINT(${this.columnLength})`;
  145. }
  146. } else if (typeName === 'LONGLONG') {
  147. if (this.columnLength === 20) {
  148. typeName = 'BIGINT';
  149. } else {
  150. typeName = `BIGINT(${this.columnLength})`;
  151. }
  152. } else if (typeName === 'SHORT') {
  153. if (isUnsigned && this.columnLength === 5) {
  154. typeName = 'SMALLINT';
  155. } else if (!isUnsigned && this.columnLength === 6) {
  156. typeName = 'SMALLINT';
  157. } else {
  158. typeName = `SMALLINT(${this.columnLength})`;
  159. }
  160. } else if (typeName === 'LONG') {
  161. if (isUnsigned && this.columnLength === 10) {
  162. typeName = 'INT';
  163. } else if (!isUnsigned && this.columnLength === 11) {
  164. typeName = 'INT';
  165. } else {
  166. typeName = `INT(${this.columnLength})`;
  167. }
  168. } else if (typeName === 'INT24') {
  169. if (isUnsigned && this.columnLength === 8) {
  170. typeName = 'MEDIUMINT';
  171. } else if (!isUnsigned && this.columnLength === 9) {
  172. typeName = 'MEDIUMINT';
  173. } else {
  174. typeName = `MEDIUMINT(${this.columnLength})`;
  175. }
  176. } else if (typeName === 'DOUBLE') {
  177. // DOUBLE without modifiers is reported as DOUBLE(22, 31)
  178. if (this.columnLength === 22 && this.decimals === 31) {
  179. typeName = 'DOUBLE';
  180. } else {
  181. typeName = `DOUBLE(${this.columnLength},${this.decimals})`;
  182. }
  183. } else if (typeName === 'FLOAT') {
  184. // FLOAT without modifiers is reported as FLOAT(12, 31)
  185. if (this.columnLength === 12 && this.decimals === 31) {
  186. typeName = 'FLOAT';
  187. } else {
  188. typeName = `FLOAT(${this.columnLength},${this.decimals})`;
  189. }
  190. } else if (typeName === 'NEWDECIMAL') {
  191. if (this.columnLength === 11 && this.decimals === 0) {
  192. typeName = 'DECIMAL';
  193. } else if (this.decimals === 0) {
  194. // not sure why, but DECIMAL(13) is reported as DECIMAL(14, 0)
  195. // and DECIMAL(13, 9) is reported as NEWDECIMAL(15, 9)
  196. if (isUnsigned) {
  197. typeName = `DECIMAL(${this.columnLength})`;
  198. } else {
  199. typeName = `DECIMAL(${this.columnLength - 1})`;
  200. }
  201. } else {
  202. typeName = `DECIMAL(${this.columnLength - 2},${this.decimals})`;
  203. }
  204. } else {
  205. typeName = `${typeNames[this.columnType]}(${this.columnLength})`;
  206. }
  207. if (isUnsigned) {
  208. typeName += ' UNSIGNED';
  209. }
  210. // TODO respect colors option
  211. return `\`${this.name}\` ${[typeName, ...flagNames].join(' ')}`;
  212. }
  213. static toPacket(column, sequenceId) {
  214. let length = 17; // = 4 padding + 1 + 12 for the rest
  215. fields.forEach(field => {
  216. length += Packet.lengthCodedStringLength(
  217. column[field],
  218. CharsetToEncoding[column.characterSet]
  219. );
  220. });
  221. const buffer = Buffer.allocUnsafe(length);
  222. const packet = new Packet(sequenceId, buffer, 0, length);
  223. function writeField(name) {
  224. packet.writeLengthCodedString(
  225. column[name],
  226. CharsetToEncoding[column.characterSet]
  227. );
  228. }
  229. packet.offset = 4;
  230. fields.forEach(writeField);
  231. packet.writeInt8(0x0c);
  232. packet.writeInt16(column.characterSet);
  233. packet.writeInt32(column.columnLength);
  234. packet.writeInt8(column.columnType);
  235. packet.writeInt16(column.flags);
  236. packet.writeInt8(column.decimals);
  237. packet.writeInt16(0); // filler
  238. return packet;
  239. }
  240. // node-mysql compatibility: alias "db" to "schema"
  241. get db() {
  242. return this.schema;
  243. }
  244. }
  245. const addString = function(name) {
  246. Object.defineProperty(ColumnDefinition.prototype, name, {
  247. get: function() {
  248. const start = this[`_${name}Start`];
  249. const end = start + this[`_${name}Length`];
  250. const val = StringParser.decode(
  251. this._buf,
  252. this.encoding === 'binary' ? this._clientEncoding : this.encoding,
  253. start,
  254. end
  255. );
  256. Object.defineProperty(this, name, {
  257. value: val,
  258. writable: false,
  259. configurable: false,
  260. enumerable: false
  261. });
  262. return val;
  263. }
  264. });
  265. };
  266. addString('catalog');
  267. addString('schema');
  268. addString('table');
  269. addString('orgTable');
  270. addString('orgName');
  271. module.exports = ColumnDefinition;