Skip to content

Commit

Permalink
refactor: separete the raw result and row-based result and add suppor…
Browse files Browse the repository at this point in the history
…t for enum editing (#42)

* refactor: separete the raw result and row-based result

* refactor: include header for table cell

* feat: add enum support

* fix: fixing code smell
  • Loading branch information
invisal authored Jul 15, 2023
1 parent 4b6ac6f commit 5251fa1
Show file tree
Hide file tree
Showing 23 changed files with 384 additions and 137 deletions.
15 changes: 9 additions & 6 deletions src/drivers/MySQLConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
import SQLLikeConnection, {
DatabaseConnectionConfig,
} from './SQLLikeConnection';
import { Connection, createConnection, RowDataPacket } from 'mysql2/promise';
import { Connection, createConnection } from 'mysql2/promise';

interface ColumnDefinition {
_buf: Buffer;
Expand Down Expand Up @@ -47,10 +47,17 @@ function mapHeaderType(column: ColumnDefinition): QueryResultHeader {
type = { type: 'decimal' };
}

const databaseNameLength = column._buf[13];
const databaseName =
databaseNameLength > 0
? column._buf.subarray(14, 14 + databaseNameLength).toString()
: undefined;

return {
name: column.name,
type,
schema: {
database: databaseName,
table: tableName,
column: column.name,
primaryKey: !!(column.flags & 0x2),
Expand Down Expand Up @@ -105,13 +112,9 @@ export default class MySQLConnection extends SQLLikeConnection {
mapHeaderType
);

const rows = (result[0] as RowDataPacket[]).map((row) =>
headers.map((header) => row[header.name])
);

return {
headers,
rows,
rows: result[0] as Record<string, unknown>[],
keys: {},
error: null,
};
Expand Down
163 changes: 104 additions & 59 deletions src/drivers/common/MySQLCommonInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,41 @@ import {
DatabaseSchemas,
TableConstraintTypeSchema,
TableDefinitionSchema,
TableColumnSchema,
} from 'types/SqlSchema';
import SQLCommonInterface from './SQLCommonInterface';
import { SqlRunnerManager } from 'libs/SqlRunnerManager';
import { qb } from 'libs/QueryBuilder';
import { QueryResult } from 'types/SqlResult';
import { parseEnumType } from 'libs/ParseColumnType';

interface MySqlColumn {
TABLE_SCHEMA: string;
TABLE_NAME: string;
COLUMN_NAME: string;
DATA_TYPE: string;
IS_NULLABLE: 'YES' | 'NO';
COLUMN_COMMENT: string;
CHARACTER_MAXIMUM_LENGTH: number;
NUMERIC_PRECISION: number;
NUMERIC_SCALE: number;
COLUMN_DEFAULT: string;
COLUMN_TYPE: string;
}

function mapColumnDefinition(col: MySqlColumn): TableColumnSchema {
return {
name: col.COLUMN_NAME,
dataType: col.DATA_TYPE,
nullable: col.IS_NULLABLE === 'YES',
comment: col.COLUMN_COMMENT,
charLength: col.CHARACTER_MAXIMUM_LENGTH,
nuermicPrecision: col.NUMERIC_PRECISION,
numericScale: col.NUMERIC_SCALE,
default: col.COLUMN_DEFAULT,
enumValues: col.DATA_TYPE === 'enum' ? parseEnumType(col.COLUMN_TYPE) : [],
};
}

export default class MySQLCommonInterface extends SQLCommonInterface {
protected runner: SqlRunnerManager;
Expand Down Expand Up @@ -38,7 +69,19 @@ export default class MySQLCommonInterface extends SQLCommonInterface {
{
sql: qb()
.table('information_schema.columns')
.select('table_schema', 'table_name', 'column_name')
.select(
'table_schema',
'table_name',
'column_name',
'data_type',
'is_nullable',
'column_comment',
'character_maximum_length',
'numeric_precision',
'numeric_scale',
'column_default',
'column_type'
)
.where({ table_schema: this.currentDatabaseName })
.toRawSQL(),
},
Expand Down Expand Up @@ -74,46 +117,66 @@ export default class MySQLCommonInterface extends SQLCommonInterface {
);

const databases: DatabaseSchemas = {};
const data = response[0].result;
const constraints = response[1].result;
const tableDict: Record<
string,
Record<string, string>
> = response[2].result.rows.reduce(
(a: Record<string, Record<string, string>>, row) => {
const databaseName = row[0] as string;
const tableName = row[1] as string;
const tableType = row[2] as string;

if (a[databaseName]) {
a[databaseName][tableName] = tableType;
} else {
a[databaseName] = { [tableName]: tableType };
}
const data = response[0].result as unknown as QueryResult<MySqlColumn>;

return a;
},
{}
);
const constraints = response[1].result as QueryResult<{
CONSTRAINT_SCHEMA: string;
CONSTRAINT_NAME: string;
TABLE_SCHEMA: string;
TABLE_NAME: string;
COLUMN_NAME: string;
CONSTRAINT_TYPE: string;
}>;

const tableDict: Record<string, Record<string, string>> = (
response[2].result as QueryResult<{
TABLE_SCHEMA: string;
TABLE_NAME: string;
TABLE_TYPE: string;
}>
).rows.reduce((a: Record<string, Record<string, string>>, row) => {
const databaseName = row.TABLE_SCHEMA;
const tableName = row.TABLE_NAME;
const tableType = row.TABLE_TYPE;

if (a[databaseName]) {
a[databaseName][tableName] = tableType;
} else {
a[databaseName] = { [tableName]: tableType };
}

return a;
}, {});

const events = (
response[4].result as QueryResult<{
EVENT_SCHEMA: string;
EVENT_NAME: string;
}>
).rows;

const events = response[4].result.rows;
const trigger = response[3].result.rows;
const trigger = (
response[3].result as QueryResult<{
TRIGGER_SCHEMA: string;
TRIGGER_NAME: string;
}>
).rows;

for (const row of data.rows) {
const databaseName = row[0] as string;
const tableName = row[1] as string;
const columnName = row[2] as string;
const databaseName = row.TABLE_SCHEMA;
const tableName = row.TABLE_NAME;
const columnName = row.COLUMN_NAME;

if (!databases[databaseName]) {
databases[databaseName] = {
tables: {},
name: databaseName,
events: events
.filter((row) => row[0] === databaseName)
.map((row) => row[1] as string),
.filter((row) => row.EVENT_SCHEMA === databaseName)
.map((row) => row.EVENT_NAME),
triggers: trigger
.filter((row) => row[0] === databaseName)
.map((row) => row[1] as string),
.filter((row) => row.TRIGGER_SCHEMA === databaseName)
.map((row) => row.TRIGGER_NAME),
};
}

Expand All @@ -130,15 +193,15 @@ export default class MySQLCommonInterface extends SQLCommonInterface {
}

const table = database.tables[tableName];
table.columns[columnName] = { name: columnName };
table.columns[columnName] = mapColumnDefinition(row);
}

for (const row of constraints.rows) {
const constraintName = row[1] as string;
const tableSchema = row[2] as string;
const tableName = row[3] as string;
const constraintType = row[5] as string;
const columnName = row[4] as string;
const constraintName = row.CONSTRAINT_NAME;
const tableSchema = row.TABLE_SCHEMA;
const tableName = row.TABLE_NAME;
const constraintType = row.CONSTRAINT_TYPE;
const columnName = row.COLUMN_NAME;

if (databases[tableSchema]) {
const database = databases[tableSchema];
Expand Down Expand Up @@ -183,7 +246,8 @@ export default class MySQLCommonInterface extends SQLCommonInterface {
'character_maximum_length',
'numeric_precision',
'numeric_scale',
'column_default'
'column_default',
'column_type'
)
.where({
table_schema: this.currentDatabaseName,
Expand All @@ -198,31 +262,12 @@ export default class MySQLCommonInterface extends SQLCommonInterface {
}
);

const columns = response[0].result.rows as [
string,
string,
string,
string,
string,
string,
number | null,
number | null,
number | null,
string | null
][];
const columns = (response[0].result as unknown as QueryResult<MySqlColumn>)
.rows;

return {
name: table,
columns: columns.map((col) => ({
name: col[2],
dataType: col[3],
nullable: col[4] === 'YES',
comment: col[5],
charLength: col[6],
nuermicPrecision: col[7],
numericScale: col[8],
default: col[9],
})),
columns: columns.map(mapColumnDefinition),
createSql: '',
};
}
Expand Down
6 changes: 3 additions & 3 deletions src/libs/ApplyQueryResultChanges.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { QueryResult } from 'types/SqlResult';
import { QueryRowBasedResult } from 'types/SqlResult';
import { ResultChangeCollectorItem } from './ResultChangeCollector';

export default function applyQueryResultChanges(
result: QueryResult,
result: QueryRowBasedResult,
changes: ResultChangeCollectorItem[]
): QueryResult {
): QueryRowBasedResult {
const newResult = { ...result };

for (const change of changes) {
Expand Down
8 changes: 4 additions & 4 deletions src/libs/GenerateSqlFromChanges.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { QueryResult } from 'types/SqlResult';
import { QueryRowBasedResult } from 'types/SqlResult';
import generateSqlFromChanges from './GenerateSqlFromChanges';
import ResultChangeCollector from './ResultChangeCollector';
import { SqlStatementPlan } from 'types/SqlStatement';
import { TableType } from 'types/SqlSchema';
import { DatabaseSchema, TableType } from 'types/SqlSchema';

test('Generate Sql from changes with primary key', () => {
const schema = {
Expand All @@ -22,9 +22,9 @@ test('Generate Sql from changes with primary key', () => {
constraints: [],
},
},
};
} as unknown as DatabaseSchema;

const data: QueryResult = {
const data: QueryRowBasedResult = {
keys: {},
error: null,
headers: [
Expand Down
6 changes: 3 additions & 3 deletions src/libs/GenerateSqlFromChanges.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { QueryResult, QueryResultHeader } from 'types/SqlResult';
import { QueryResultHeader, QueryRowBasedResult } from 'types/SqlResult';
import { DatabaseSchema } from 'types/SqlSchema';
import { ResultChangeCollectorItem } from './ResultChangeCollector';
import { SqlStatementPlan } from 'types/SqlStatement';
Expand Down Expand Up @@ -66,7 +66,7 @@ export function getUpdatableTable(

function getSqlPlanFromChange(
change: ResultChangeCollectorItem,
data: QueryResult,
data: QueryRowBasedResult,
updatable: UpdatableTableDict
): SqlStatementPlan[] {
const changedTable: Record<string, SqlStatementPlan> = {};
Expand Down Expand Up @@ -106,7 +106,7 @@ function getSqlPlanFromChange(

export default function generateSqlFromChanges(
schema: DatabaseSchema,
currentData: QueryResult,
currentData: QueryRowBasedResult,
changes: ResultChangeCollectorItem[]
): SqlStatementPlan[] {
const updatableTables = getUpdatableTable(currentData.headers, schema);
Expand Down
9 changes: 9 additions & 0 deletions src/libs/ParseColumnType.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { parseEnumType } from './ParseColumnType';

test('Parse enum column type', () => {
expect(
parseEnumType(
`enum('CONTAINS SQL','NO SQL','READS SQL DATA','MODIFIES SQL DATA')`
)
).toEqual(['CONTAINS SQL', 'NO SQL', 'READS SQL DATA', 'MODIFIES SQL DATA']);
});
7 changes: 7 additions & 0 deletions src/libs/ParseColumnType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function parseEnumType(type: string): string[] {
try {
return JSON.parse('[' + type.slice(5, -1).replaceAll(`'`, '"') + ']');
} catch {
return [];
}
}
7 changes: 6 additions & 1 deletion src/libs/SqlRunnerManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SqlQueryCallback } from 'drivers/SQLLikeConnection';
import { QueryResult } from 'types/SqlResult';
import { QueryResult, QueryRowBasedResult } from 'types/SqlResult';
import { SqlStatement } from 'types/SqlStatement';
import { Parser, AST } from 'node-sql-parser';

Expand All @@ -22,6 +22,11 @@ export interface SqlStatementResult {
result: QueryResult;
}

export interface SqlStatementRowBasedResult {
statement: SqlStatement;
result: QueryRowBasedResult;
}

interface SqlExecuteOption {
onStart?: () => void;
skipProtection?: boolean;
Expand Down
Loading

0 comments on commit 5251fa1

Please sign in to comment.