Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improved inspection of columns #2112

Merged
merged 9 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 149 additions & 1 deletion lib/packets/column_definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,162 @@ class ColumnDefinition {
table: this.table,
orgTable: this.orgTable,
characterSet: this.characterSet,
encoding: this.encoding,
columnLength: this.columnLength,
columnType: this.columnType,
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 => {
Expand Down
102 changes: 102 additions & 0 deletions test/builtin-runner/integration/connection/test-column-inspect.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { describe, it, before, after } from 'node:test';
import assert from 'node:assert';
import util from 'node:util';
import common from '../../../common.js';

describe(
'custom inspect for column definition',
{ timeout: 1000 },
async () => {
let connection;

before(async () => {
connection = await common.createConnection().promise();
connection.query(`DROP TABLE IF EXISTS test_fields`);
});

after(async () => {
await connection.end();
});

it('should map fields to a schema-like description when depth is > 1', async () => {
const schema = `
id INT NOT NULL AUTO_INCREMENT,
weight INT(2) UNSIGNED ZEROFILL,
usignedInt INT UNSIGNED NOT NULL,
signedInt INT NOT NULL,
unsignedShort SMALLINT UNSIGNED NOT NULL,
signedShort SMALLINT NOT NULL,
tinyIntUnsigned TINYINT UNSIGNED NOT NULL,
tinyIntSigned TINYINT NOT NULL,
mediumIntUnsigned MEDIUMINT UNSIGNED NOT NULL,
mediumIntSigned MEDIUMINT NOT NULL,
bigIntSigned BIGINT NOT NULL,
bigIntUnsigned BIGINT UNSIGNED NOT NULL,
longText_ LONGTEXT NOT NULL,
mediumText_ MEDIUMTEXT NOT NULL,
text_ TEXT NOT NULL,
tinyText_ TINYTEXT NOT NULL,
varString_1000 VARCHAR(1000) NOT NULL,
decimalDefault DECIMAL,
decimal13_10 DECIMAL(13,10),
floatDefault FLOAT,
float11_7 FLOAT(11,7),
dummyLastFieldToAllowForTrailingComma INT,
`;
await connection.query(
`CREATE TEMPORARY TABLE test_fields (${schema} PRIMARY KEY (id))`
);
const [_, columns] = await connection.query('select * from test_fields');
const inspectResults = util.inspect(columns);
const schemaArray = schema
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.map((line) => {
const words = line.split(' ');
const name = `\`${words[0]}\``;
return [name, ...words.slice(1)].join(' ');
});

const normalizedInspectResults = inspectResults
.split('\n')
.slice(1, -2) // remove "[" and "]" lines and also last dummy field
.map((line) => line.trim())
// remove primary key - it's not in the schema explicitly but worth having in inspect
.map(line => line.split('PRIMARY KEY ').join(''));

for (let l = 0; l < normalizedInspectResults.length; l++) {
const inspectLine = normalizedInspectResults[l];
const schemaLine = schemaArray[l];
assert.equal(inspectLine, schemaLine);
}
});

it.only('should show detailed description when depth is < 1', async () => {
await connection.query(`
CREATE TEMPORARY TABLE test_fields2 (
id INT,
decimal13_10 DECIMAL(13,10) UNSIGNED NOT NULL,
PRIMARY KEY (id)
)
`);
const [_, columns] = await connection.query('select * from test_fields2');
const inspectResults = util.inspect(columns[1]);
assert.deepEqual(inspectResults, util.inspect({
catalog: 'def',
schema: 'test',
name: 'decimal13_10',
orgName: 'decimal13_10',
table: 'test_fields2',
orgTable: 'test_fields2',
characterSet: 63,
encoding: 'binary',
columnLength: 14,
type: 246,
flags: [ 'NOT NULL' ],
decimals: 10,
typeName: 'NEWDECIMAL'
}));
});
}
);
4 changes: 2 additions & 2 deletions test/integration/connection/test-binary-multiple-results.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const fields1 = [
{
catalog: 'def',
characterSet: 63,
columnType: 8,
encoding: 'binary',
type: 8,
decimals: 0,
flags: 129,
Expand All @@ -58,7 +58,7 @@ const nr_fields = [
{
catalog: 'def',
characterSet: 63,
columnType: 3,
encoding: 'binary',
type: 3,
decimals: 0,
flags: 0,
Expand Down
24 changes: 12 additions & 12 deletions test/integration/connection/test-execute-nocolumndef.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 63,
columnType: 8,
encoding: 'binary',
type: 8,
flags: 161,
decimals: 0
Expand All @@ -71,7 +71,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 224,
columnType: 253,
encoding: 'utf8',
type: 253,
flags: 1,
decimals: 31
Expand All @@ -84,7 +84,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 224,
columnType: 253,
encoding: 'utf8',
type: 253,
flags: 0,
decimals: 31
Expand All @@ -97,7 +97,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 224,
columnType: 250,
encoding: 'utf8',
type: 250,
flags: 0,
decimals: 31
Expand All @@ -110,7 +110,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 224,
columnType: 253,
encoding: 'utf8',
type: 253,
flags: 0,
decimals: 31
Expand All @@ -123,7 +123,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 224,
columnType: 253,
encoding: 'utf8',
type: 253,
flags: 0,
decimals: 31
Expand All @@ -136,7 +136,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 224,
columnType: 253,
encoding: 'utf8',
type: 253,
flags: 0,
decimals: 31
Expand All @@ -149,7 +149,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 224,
columnType: 253,
encoding: 'utf8',
type: 253,
flags: 0,
decimals: 31
Expand All @@ -162,7 +162,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 224,
columnType: 253,
encoding: 'utf8',
type: 253,
flags: 0,
decimals: 31
Expand All @@ -175,7 +175,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 63,
columnType: 8,
encoding: 'binary',
type: 8,
flags: 160,
decimals: 0
Expand All @@ -188,7 +188,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 63,
columnType: 5,
encoding: 'binary',
type: 5,
flags: 128,
decimals: 2
Expand All @@ -201,7 +201,7 @@ const expectedFields = [
table: '',
orgTable: '',
characterSet: 224,
columnType: 253,
encoding: 'utf8',
type: 253,
flags: 1,
decimals: 31
Expand Down
Loading