column_definition.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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) {
  131. // 255*4
  132. typeName = 'TINYTEXT';
  133. } else {
  134. typeName = `BLOB(${this.columnLength})`;
  135. }
  136. } else if (typeName === 'VAR_STRING') {
  137. // TODO: check for non-utf8mb4 encoding
  138. typeName = `VARCHAR(${Math.ceil(this.columnLength / 4)})`;
  139. } else if (typeName === 'TINY') {
  140. if (
  141. (this.columnLength === 3 && isUnsigned) ||
  142. (this.columnLength === 4 && !isUnsigned)
  143. ) {
  144. typeName = 'TINYINT';
  145. } else {
  146. typeName = `TINYINT(${this.columnLength})`;
  147. }
  148. } else if (typeName === 'LONGLONG') {
  149. if (this.columnLength === 20) {
  150. typeName = 'BIGINT';
  151. } else {
  152. typeName = `BIGINT(${this.columnLength})`;
  153. }
  154. } else if (typeName === 'SHORT') {
  155. if (isUnsigned && this.columnLength === 5) {
  156. typeName = 'SMALLINT';
  157. } else if (!isUnsigned && this.columnLength === 6) {
  158. typeName = 'SMALLINT';
  159. } else {
  160. typeName = `SMALLINT(${this.columnLength})`;
  161. }
  162. } else if (typeName === 'LONG') {
  163. if (isUnsigned && this.columnLength === 10) {
  164. typeName = 'INT';
  165. } else if (!isUnsigned && this.columnLength === 11) {
  166. typeName = 'INT';
  167. } else {
  168. typeName = `INT(${this.columnLength})`;
  169. }
  170. } else if (typeName === 'INT24') {
  171. if (isUnsigned && this.columnLength === 8) {
  172. typeName = 'MEDIUMINT';
  173. } else if (!isUnsigned && this.columnLength === 9) {
  174. typeName = 'MEDIUMINT';
  175. } else {
  176. typeName = `MEDIUMINT(${this.columnLength})`;
  177. }
  178. } else if (typeName === 'DOUBLE') {
  179. // DOUBLE without modifiers is reported as DOUBLE(22, 31)
  180. if (this.columnLength === 22 && this.decimals === 31) {
  181. typeName = 'DOUBLE';
  182. } else {
  183. typeName = `DOUBLE(${this.columnLength},${this.decimals})`;
  184. }
  185. } else if (typeName === 'FLOAT') {
  186. // FLOAT without modifiers is reported as FLOAT(12, 31)
  187. if (this.columnLength === 12 && this.decimals === 31) {
  188. typeName = 'FLOAT';
  189. } else {
  190. typeName = `FLOAT(${this.columnLength},${this.decimals})`;
  191. }
  192. } else if (typeName === 'NEWDECIMAL') {
  193. if (this.columnLength === 11 && this.decimals === 0) {
  194. typeName = 'DECIMAL';
  195. } else if (this.decimals === 0) {
  196. // not sure why, but DECIMAL(13) is reported as DECIMAL(14, 0)
  197. // and DECIMAL(13, 9) is reported as NEWDECIMAL(15, 9)
  198. if (isUnsigned) {
  199. typeName = `DECIMAL(${this.columnLength})`;
  200. } else {
  201. typeName = `DECIMAL(${this.columnLength - 1})`;
  202. }
  203. } else {
  204. typeName = `DECIMAL(${this.columnLength - 2},${this.decimals})`;
  205. }
  206. } else {
  207. typeName = `${typeNames[this.columnType]}(${this.columnLength})`;
  208. }
  209. if (isUnsigned) {
  210. typeName += ' UNSIGNED';
  211. }
  212. // TODO respect colors option
  213. return `\`${this.name}\` ${[typeName, ...flagNames].join(' ')}`;
  214. }
  215. static toPacket(column, sequenceId) {
  216. let length = 17; // = 4 padding + 1 + 12 for the rest
  217. fields.forEach((field) => {
  218. length += Packet.lengthCodedStringLength(
  219. column[field],
  220. CharsetToEncoding[column.characterSet]
  221. );
  222. });
  223. const buffer = Buffer.allocUnsafe(length);
  224. const packet = new Packet(sequenceId, buffer, 0, length);
  225. function writeField(name) {
  226. packet.writeLengthCodedString(
  227. column[name],
  228. CharsetToEncoding[column.characterSet]
  229. );
  230. }
  231. packet.offset = 4;
  232. fields.forEach(writeField);
  233. packet.writeInt8(0x0c);
  234. packet.writeInt16(column.characterSet);
  235. packet.writeInt32(column.columnLength);
  236. packet.writeInt8(column.columnType);
  237. packet.writeInt16(column.flags);
  238. packet.writeInt8(column.decimals);
  239. packet.writeInt16(0); // filler
  240. return packet;
  241. }
  242. // node-mysql compatibility: alias "db" to "schema"
  243. get db() {
  244. return this.schema;
  245. }
  246. }
  247. const addString = function (name) {
  248. Object.defineProperty(ColumnDefinition.prototype, name, {
  249. get: function () {
  250. const start = this[`_${name}Start`];
  251. const end = start + this[`_${name}Length`];
  252. const val = StringParser.decode(
  253. this._buf,
  254. this.encoding === 'binary' ? this._clientEncoding : this.encoding,
  255. start,
  256. end
  257. );
  258. Object.defineProperty(this, name, {
  259. value: val,
  260. writable: false,
  261. configurable: false,
  262. enumerable: false,
  263. });
  264. return val;
  265. },
  266. });
  267. };
  268. addString('catalog');
  269. addString('schema');
  270. addString('table');
  271. addString('orgTable');
  272. addString('orgName');
  273. module.exports = ColumnDefinition;