Skip to content

Commit 69277aa

Browse files
authored
feat: improved inspection of columns (#2112)
* feat: improved inspection of column definition objects * remove console.log * lint * lint * display type length modifiers for non-default column lengths * add a unit test for column definition custom inspect * fix unit test * fix tests * fix unit test
1 parent facc68c commit 69277aa

File tree

6 files changed

+272
-21
lines changed

6 files changed

+272
-21
lines changed

lib/packets/column_definition.js

+149-1
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,162 @@ class ColumnDefinition {
6666
table: this.table,
6767
orgTable: this.orgTable,
6868
characterSet: this.characterSet,
69+
encoding: this.encoding,
6970
columnLength: this.columnLength,
70-
columnType: this.columnType,
7171
type: this.columnType,
7272
flags: this.flags,
7373
decimals: this.decimals
7474
};
7575
}
7676

77+
[Symbol.for('nodejs.util.inspect.custom')](depth, inspectOptions, inspect) {
78+
const Types = require('../constants/types.js');
79+
const typeNames = [];
80+
for (const t in Types) {
81+
typeNames[Types[t]] = t;
82+
}
83+
const fiedFlags = require('../constants/field_flags.js');
84+
const flagNames = [];
85+
// TODO: respect inspectOptions.showHidden
86+
//const inspectFlags = inspectOptions.showHidden ? this.flags : this.flags & ~fiedFlags.PRI_KEY;
87+
const inspectFlags = this.flags;
88+
for (const f in fiedFlags) {
89+
if (inspectFlags & fiedFlags[f]) {
90+
if (f === 'PRI_KEY') {
91+
flagNames.push('PRIMARY KEY');
92+
} else if (f === 'NOT_NULL') {
93+
flagNames.push('NOT NULL');
94+
} else if (f === 'BINARY') {
95+
// ignore flag for now
96+
} else if (f === 'MULTIPLE_KEY') {
97+
// not sure if that should be part of inspection.
98+
// in the schema usually this is part of index definition
99+
// example: UNIQUE KEY `my_uniq_id` (`id_box_elements`,`id_router`)
100+
// note that only first column has MULTIPLE_KEY flag set in this case
101+
// so there is no good way of knowing that this is part of index just
102+
// by looking at indifidual field flags
103+
} else if (f === 'NO_DEFAULT_VALUE') {
104+
// almost the same as NOT_NULL?
105+
} else if (f === 'BLOB') {
106+
// included in the type
107+
} else if (f === 'UNSIGNED') {
108+
// this should be first after type
109+
} else if (f === 'TIMESTAMP') {
110+
// timestamp flag is redundant for inspection - already included in type
111+
} else if (f === 'ON_UPDATE_NOW') {
112+
flagNames.push('ON UPDATE CURRENT_TIMESTAMP');
113+
} else {
114+
flagNames.push(f);
115+
}
116+
}
117+
}
118+
119+
if (depth > 1) {
120+
return inspect({
121+
...this.inspect(),
122+
typeName: typeNames[this.columnType],
123+
flags: flagNames,
124+
});
125+
}
126+
127+
const isUnsigned = this.flags & fiedFlags.UNSIGNED;
128+
129+
let typeName = typeNames[this.columnType];
130+
if (typeName === 'BLOB') {
131+
// TODO: check for non-utf8mb4 encoding
132+
if (this.columnLength === 4294967295) {
133+
typeName = 'LONGTEXT';
134+
} else if (this.columnLength === 67108860) {
135+
typeName = 'MEDIUMTEXT';
136+
} else if (this.columnLength === 262140) {
137+
typeName = 'TEXT';
138+
} else if (this.columnLength === 1020) { // 255*4
139+
typeName = 'TINYTEXT';
140+
} else {
141+
typeName = `BLOB(${this.columnLength})`;
142+
}
143+
} else if (typeName === 'VAR_STRING') {
144+
// TODO: check for non-utf8mb4 encoding
145+
typeName = `VARCHAR(${Math.ceil(this.columnLength/4)})`;
146+
} else if (typeName === 'TINY') {
147+
if (
148+
(this.columnLength === 3 && isUnsigned) ||
149+
(this.columnLength === 4 && !isUnsigned) ) {
150+
typeName = 'TINYINT';
151+
} else {
152+
typeName = `TINYINT(${this.columnLength})`;
153+
}
154+
} else if (typeName === 'LONGLONG') {
155+
if (this.columnLength === 20) {
156+
typeName = 'BIGINT';
157+
} else {
158+
typeName = `BIGINT(${this.columnLength})`;
159+
}
160+
} else if (typeName === 'SHORT') {
161+
if (isUnsigned && this.columnLength === 5) {
162+
typeName = 'SMALLINT';
163+
} else if (!isUnsigned && this.columnLength === 6) {
164+
typeName = 'SMALLINT';
165+
} else {
166+
typeName = `SMALLINT(${this.columnLength})`;
167+
}
168+
169+
} else if (typeName === 'LONG') {
170+
if (isUnsigned && this.columnLength === 10) {
171+
typeName = 'INT';
172+
} else if (!isUnsigned && this.columnLength === 11) {
173+
typeName = 'INT';
174+
} else {
175+
typeName = `INT(${this.columnLength})`;
176+
}
177+
} else if (typeName === 'INT24') {
178+
if (isUnsigned && this.columnLength === 8) {
179+
typeName = 'MEDIUMINT';
180+
} else if (!isUnsigned && this.columnLength === 9) {
181+
typeName = 'MEDIUMINT';
182+
} else {
183+
typeName = `MEDIUMINT(${this.columnLength})`;
184+
}
185+
} else if (typeName === 'DOUBLE') {
186+
// DOUBLE without modifiers is reported as DOUBLE(22, 31)
187+
if (this.columnLength === 22 && this.decimals === 31) {
188+
typeName = 'DOUBLE';
189+
} else {
190+
typeName = `DOUBLE(${this.columnLength},${this.decimals})`;
191+
}
192+
} else if (typeName === 'FLOAT') {
193+
// FLOAT without modifiers is reported as FLOAT(12, 31)
194+
if (this.columnLength === 12 && this.decimals === 31) {
195+
typeName = 'FLOAT';
196+
} else {
197+
typeName = `FLOAT(${this.columnLength},${this.decimals})`;
198+
}
199+
} else if (typeName === 'NEWDECIMAL') {
200+
if (this.columnLength === 11 && this.decimals === 0) {
201+
typeName = 'DECIMAL';
202+
} else if (this.decimals === 0) {
203+
// not sure why, but DECIMAL(13) is reported as DECIMAL(14, 0)
204+
// and DECIMAL(13, 9) is reported as NEWDECIMAL(15, 9)
205+
if (isUnsigned) {
206+
typeName = `DECIMAL(${this.columnLength})`;
207+
} else {
208+
typeName = `DECIMAL(${this.columnLength - 1})`;
209+
}
210+
} else {
211+
typeName = `DECIMAL(${this.columnLength - 2},${this.decimals})`;
212+
}
213+
} else {
214+
typeName = `${typeNames[this.columnType]}(${this.columnLength})`;
215+
}
216+
217+
if (isUnsigned) {
218+
typeName += ' UNSIGNED';
219+
}
220+
221+
// TODO respect colors option
222+
return `\`${this.name}\` ${[typeName, ...flagNames].join(' ')}`;
223+
}
224+
77225
static toPacket(column, sequenceId) {
78226
let length = 17; // = 4 padding + 1 + 12 for the rest
79227
fields.forEach(field => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { describe, it, before, after } from 'node:test';
2+
import assert from 'node:assert';
3+
import util from 'node:util';
4+
import common from '../../../common.js';
5+
6+
describe(
7+
'custom inspect for column definition',
8+
{ timeout: 1000 },
9+
async () => {
10+
let connection;
11+
12+
before(async () => {
13+
connection = await common.createConnection().promise();
14+
connection.query(`DROP TABLE IF EXISTS test_fields`);
15+
});
16+
17+
after(async () => {
18+
await connection.end();
19+
});
20+
21+
it('should map fields to a schema-like description when depth is > 1', async () => {
22+
const schema = `
23+
id INT NOT NULL AUTO_INCREMENT,
24+
weight INT(2) UNSIGNED ZEROFILL,
25+
usignedInt INT UNSIGNED NOT NULL,
26+
signedInt INT NOT NULL,
27+
unsignedShort SMALLINT UNSIGNED NOT NULL,
28+
signedShort SMALLINT NOT NULL,
29+
tinyIntUnsigned TINYINT UNSIGNED NOT NULL,
30+
tinyIntSigned TINYINT NOT NULL,
31+
mediumIntUnsigned MEDIUMINT UNSIGNED NOT NULL,
32+
mediumIntSigned MEDIUMINT NOT NULL,
33+
bigIntSigned BIGINT NOT NULL,
34+
bigIntUnsigned BIGINT UNSIGNED NOT NULL,
35+
longText_ LONGTEXT NOT NULL,
36+
mediumText_ MEDIUMTEXT NOT NULL,
37+
text_ TEXT NOT NULL,
38+
tinyText_ TINYTEXT NOT NULL,
39+
varString_1000 VARCHAR(1000) NOT NULL,
40+
decimalDefault DECIMAL,
41+
decimal13_10 DECIMAL(13,10),
42+
floatDefault FLOAT,
43+
float11_7 FLOAT(11,7),
44+
dummyLastFieldToAllowForTrailingComma INT,
45+
`;
46+
await connection.query(
47+
`CREATE TEMPORARY TABLE test_fields (${schema} PRIMARY KEY (id))`
48+
);
49+
const [_, columns] = await connection.query('select * from test_fields');
50+
const inspectResults = util.inspect(columns);
51+
const schemaArray = schema
52+
.split('\n')
53+
.map((line) => line.trim())
54+
.filter((line) => line.length > 0)
55+
.map((line) => {
56+
const words = line.split(' ');
57+
const name = `\`${words[0]}\``;
58+
return [name, ...words.slice(1)].join(' ');
59+
});
60+
61+
const normalizedInspectResults = inspectResults
62+
.split('\n')
63+
.slice(1, -2) // remove "[" and "]" lines and also last dummy field
64+
.map((line) => line.trim())
65+
// remove primary key - it's not in the schema explicitly but worth having in inspect
66+
.map(line => line.split('PRIMARY KEY ').join(''));
67+
68+
for (let l = 0; l < normalizedInspectResults.length; l++) {
69+
const inspectLine = normalizedInspectResults[l];
70+
const schemaLine = schemaArray[l];
71+
assert.equal(inspectLine, schemaLine);
72+
}
73+
});
74+
75+
it.only('should show detailed description when depth is < 1', async () => {
76+
await connection.query(`
77+
CREATE TEMPORARY TABLE test_fields2 (
78+
id INT,
79+
decimal13_10 DECIMAL(13,10) UNSIGNED NOT NULL,
80+
PRIMARY KEY (id)
81+
)
82+
`);
83+
const [_, columns] = await connection.query('select * from test_fields2');
84+
const inspectResults = util.inspect(columns[1]);
85+
assert.deepEqual(inspectResults, util.inspect({
86+
catalog: 'def',
87+
schema: 'test',
88+
name: 'decimal13_10',
89+
orgName: 'decimal13_10',
90+
table: 'test_fields2',
91+
orgTable: 'test_fields2',
92+
characterSet: 63,
93+
encoding: 'binary',
94+
columnLength: 14,
95+
type: 246,
96+
flags: [ 'NOT NULL' ],
97+
decimals: 10,
98+
typeName: 'NEWDECIMAL'
99+
}));
100+
});
101+
}
102+
);

test/integration/connection/test-binary-multiple-results.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const fields1 = [
4343
{
4444
catalog: 'def',
4545
characterSet: 63,
46-
columnType: 8,
46+
encoding: 'binary',
4747
type: 8,
4848
decimals: 0,
4949
flags: 129,
@@ -58,7 +58,7 @@ const nr_fields = [
5858
{
5959
catalog: 'def',
6060
characterSet: 63,
61-
columnType: 3,
61+
encoding: 'binary',
6262
type: 3,
6363
decimals: 0,
6464
flags: 0,

test/integration/connection/test-execute-nocolumndef.js

+12-12
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const expectedFields = [
5858
table: '',
5959
orgTable: '',
6060
characterSet: 63,
61-
columnType: 8,
61+
encoding: 'binary',
6262
type: 8,
6363
flags: 161,
6464
decimals: 0
@@ -71,7 +71,7 @@ const expectedFields = [
7171
table: '',
7272
orgTable: '',
7373
characterSet: 224,
74-
columnType: 253,
74+
encoding: 'utf8',
7575
type: 253,
7676
flags: 1,
7777
decimals: 31
@@ -84,7 +84,7 @@ const expectedFields = [
8484
table: '',
8585
orgTable: '',
8686
characterSet: 224,
87-
columnType: 253,
87+
encoding: 'utf8',
8888
type: 253,
8989
flags: 0,
9090
decimals: 31
@@ -97,7 +97,7 @@ const expectedFields = [
9797
table: '',
9898
orgTable: '',
9999
characterSet: 224,
100-
columnType: 250,
100+
encoding: 'utf8',
101101
type: 250,
102102
flags: 0,
103103
decimals: 31
@@ -110,7 +110,7 @@ const expectedFields = [
110110
table: '',
111111
orgTable: '',
112112
characterSet: 224,
113-
columnType: 253,
113+
encoding: 'utf8',
114114
type: 253,
115115
flags: 0,
116116
decimals: 31
@@ -123,7 +123,7 @@ const expectedFields = [
123123
table: '',
124124
orgTable: '',
125125
characterSet: 224,
126-
columnType: 253,
126+
encoding: 'utf8',
127127
type: 253,
128128
flags: 0,
129129
decimals: 31
@@ -136,7 +136,7 @@ const expectedFields = [
136136
table: '',
137137
orgTable: '',
138138
characterSet: 224,
139-
columnType: 253,
139+
encoding: 'utf8',
140140
type: 253,
141141
flags: 0,
142142
decimals: 31
@@ -149,7 +149,7 @@ const expectedFields = [
149149
table: '',
150150
orgTable: '',
151151
characterSet: 224,
152-
columnType: 253,
152+
encoding: 'utf8',
153153
type: 253,
154154
flags: 0,
155155
decimals: 31
@@ -162,7 +162,7 @@ const expectedFields = [
162162
table: '',
163163
orgTable: '',
164164
characterSet: 224,
165-
columnType: 253,
165+
encoding: 'utf8',
166166
type: 253,
167167
flags: 0,
168168
decimals: 31
@@ -175,7 +175,7 @@ const expectedFields = [
175175
table: '',
176176
orgTable: '',
177177
characterSet: 63,
178-
columnType: 8,
178+
encoding: 'binary',
179179
type: 8,
180180
flags: 160,
181181
decimals: 0
@@ -188,7 +188,7 @@ const expectedFields = [
188188
table: '',
189189
orgTable: '',
190190
characterSet: 63,
191-
columnType: 5,
191+
encoding: 'binary',
192192
type: 5,
193193
flags: 128,
194194
decimals: 2
@@ -201,7 +201,7 @@ const expectedFields = [
201201
table: '',
202202
orgTable: '',
203203
characterSet: 224,
204-
columnType: 253,
204+
encoding: 'utf8',
205205
type: 253,
206206
flags: 1,
207207
decimals: 31

0 commit comments

Comments
 (0)