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

refactor: separete the raw result and row-based result and add support for enum editing #42

Merged
merged 4 commits into from
Jul 15, 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
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