Skip to content

Commit

Permalink
feat: consistent parsing and escaping of table names in QueryRunners
Browse files Browse the repository at this point in the history
this refactors `escapePath` and `parseTableName` so that when we parse
and serialize the table names they should work the same between the two
  • Loading branch information
imnotjames committed Jul 24, 2021
1 parent 5dc777f commit bd9e767
Show file tree
Hide file tree
Showing 11 changed files with 344 additions and 192 deletions.
38 changes: 31 additions & 7 deletions src/driver/aurora-data-api/AuroraDataApiQueryRunner.ts
Expand Up @@ -1639,11 +1639,30 @@ export class AuroraDataApiQueryRunner extends BaseQueryRunner implements QueryRu
return new Query(`ALTER TABLE ${this.escapePath(table)} DROP FOREIGN KEY \`${foreignKeyName}\``);
}

protected parseTableName(target: Table|string) {
const tableName = target instanceof Table ? target.name : target;
protected parseTableName(target: Table | View | string): { database?: string, schema?: string, tableName: string } {
const driverDatabase = this.driver.database;
const driverSchema = undefined;

if (target instanceof Table) {
const parsed = this.parseTableName(target.name);

return {
database: target.database || parsed.database || driverDatabase,
schema: target.schema || parsed.schema || driverSchema,
tableName: parsed.tableName
};
}

if (target instanceof View) {
return this.parseTableName(target.name);
}

const parts = target.split(".")

return {
database: tableName.indexOf(".") !== -1 ? tableName.split(".")[0] : this.driver.database,
tableName: tableName.indexOf(".") !== -1 ? tableName.split(".")[1] : tableName
database: (parts.length > 1 ? parts[0] : undefined) || driverDatabase,
schema: driverSchema,
tableName: parts.length > 1 ? parts[1] : parts[0]
};
}

Expand All @@ -1666,9 +1685,14 @@ export class AuroraDataApiQueryRunner extends BaseQueryRunner implements QueryRu
/**
* Escapes given table or view path.
*/
protected escapePath(target: Table|View|string, disableEscape?: boolean): string {
const tableName = target instanceof Table || target instanceof View ? target.name : target;
return tableName.split(".").map(i => disableEscape ? i : `\`${i}\``).join(".");
protected escapePath(target: Table|View|string): string {
const { database, tableName } = this.parseTableName(target);

if (database) {
return `\`${database}\`.\`${tableName}\``;
}

return `\`${tableName}\``;
}

/**
Expand Down
70 changes: 39 additions & 31 deletions src/driver/cockroachdb/CockroachQueryRunner.ts
Expand Up @@ -1383,16 +1383,12 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
viewNames = [];
}

const currentSchemaQuery = await this.query(`SELECT * FROM current_schema()`);
const currentSchema = currentSchemaQuery[0]["current_schema"];
const currentSchema = await this.getCurrentSchema();

const viewsCondition = viewNames.map(viewName => {
let [schema, name] = viewName.split(".");
if (!name) {
name = schema;
schema = this.driver.options.schema || currentSchema;
}
return `("t"."schema" = '${schema}' AND "t"."name" = '${name}')`;
const { schema, tableName } = this.parseTableName(viewName);

return `("t"."schema" = '${schema || currentSchema}' AND "t"."name" = '${tableName}')`;
}).join(" OR ");

const query = `SELECT "t".*, "v"."check_option" FROM ${this.escapePath(this.getTypeormMetadataTableName())} "t" ` +
Expand Down Expand Up @@ -1427,13 +1423,10 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner

dbTables.push(...await this.query(tablesSql));
} else {
const tablesCondition = tableNames.length === 0 ? "1=1" : tableNames.map(tableName => {
let [schema, name] = tableName.split(".");
if (!name) {
name = schema;
schema = this.driver.options.schema || currentSchema;
}
return `("table_schema" = '${schema}' AND "table_name" = '${name}')`;
const tablesCondition = tableNames
.map(tableName => this.parseTableName(tableName))
.map(({ schema, tableName }) => {
return `("table_schema" = '${schema || currentSchema}' AND "table_name" = '${tableName}')`;
}).join(" OR ");
const tablesSql = `SELECT "table_schema", "table_name" FROM "information_schema"."tables" WHERE ` + tablesCondition;

Expand Down Expand Up @@ -1984,31 +1977,46 @@ export class CockroachQueryRunner extends BaseQueryRunner implements QueryRunner
/**
* Escapes given table or view path.
*/
protected escapePath(target: Table|View|string, disableEscape?: boolean): string {
let tableName = target instanceof Table || target instanceof View ? target.name : target;
tableName = tableName.indexOf(".") === -1 && this.driver.options.schema ? `${this.driver.options.schema}.${tableName}` : tableName;
protected escapePath(target: Table|View|string): string {
const { schema, tableName } = this.parseTableName(target);

if (schema) {
return `"${schema}"."${tableName}"`;
}

return tableName.split(".").map(i => {
return disableEscape ? i : `"${i}"`;
}).join(".");
return `"${tableName}"`;
}

/**
* Returns object with table schema and table name.
*/
protected parseTableName(target: Table|string) {
const tableName = target instanceof Table ? target.name : target;
if (tableName.indexOf(".") === -1) {
return {
schema: this.driver.options.schema,
tableName,
};
} else {
protected parseTableName(target: Table | View | string): { database?: string, schema?: string, tableName: string } {
const driverDatabase = this.driver.database;

// This really should be abstracted into the driver as well..
const driverSchema = this.driver.options.schema;

if (target instanceof Table) {
const parsed = this.parseTableName(target.name);

return {
schema: tableName.split(".")[0],
tableName: tableName.split(".")[1],
database: target.database || parsed.database || driverDatabase,
schema: target.schema || parsed.schema || driverSchema,
tableName: parsed.tableName
};
}

if (target instanceof View) {
return this.parseTableName(target.name);
}

const parts = target.split(".")

return {
database: driverDatabase,
schema: (parts.length > 1 ? parts[0] : undefined) || driverSchema,
tableName: parts.length > 1 ? parts[1] : parts[0],
};
}

/**
Expand Down
38 changes: 31 additions & 7 deletions src/driver/mysql/MysqlQueryRunner.ts
Expand Up @@ -1828,11 +1828,30 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
return new Query(`ALTER TABLE ${this.escapePath(table)} DROP FOREIGN KEY \`${foreignKeyName}\``);
}

protected parseTableName(target: Table|string) {
const tableName = target instanceof Table ? target.name : target;
protected parseTableName(target: Table | View | string): { database?: string, schema?: string, tableName: string } {
const driverDatabase = this.driver.database;
const driverSchema = undefined;

if (target instanceof Table) {
const parsed = this.parseTableName(target.name);

return {
database: target.database || parsed.database || driverDatabase,
schema: target.schema || parsed.schema || driverSchema,
tableName: parsed.tableName
};
}

if (target instanceof View) {
return this.parseTableName(target.name);
}

const parts = target.split(".")

return {
database: tableName.indexOf(".") !== -1 ? tableName.split(".")[0] : this.driver.database,
tableName: tableName.indexOf(".") !== -1 ? tableName.split(".")[1] : tableName
database: (parts.length > 1 ? parts[0] : undefined) || driverDatabase,
schema: driverSchema,
tableName: parts.length > 1 ? parts[1] : parts[0]
};
}

Expand All @@ -1855,9 +1874,14 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner {
/**
* Escapes given table or view path.
*/
protected escapePath(target: Table|View|string, disableEscape?: boolean): string {
const tableName = target instanceof Table || target instanceof View ? target.name : target;
return tableName.split(".").map(i => disableEscape ? i : `\`${i}\``).join(".");
protected escapePath(target: Table|View|string): string {
const { database, tableName } = this.parseTableName(target);

if (database) {
return `\`${database}\`.\`${tableName}\``;
}

return `\`${tableName}\``;
}

/**
Expand Down
67 changes: 44 additions & 23 deletions src/driver/oracle/OracleQueryRunner.ts
Expand Up @@ -290,7 +290,7 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner {
* Checks if table with the given name exist in the database.
*/
async hasTable(tableOrName: Table|string): Promise<boolean> {
const { name: tableName } = this.parseTableName(tableOrName);
const { tableName } = this.parseTableName(tableOrName);
const sql = `SELECT "TABLE_NAME" FROM "USER_TABLES" WHERE "TABLE_NAME" = '${tableName}'`;
const result = await this.query(sql);
return result.length ? true : false;
Expand All @@ -300,7 +300,7 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner {
* Checks if column with the given name exist in the given table.
*/
async hasColumn(tableOrName: Table|string, columnName: string): Promise<boolean> {
const { name: tableName } = this.parseTableName(tableOrName);
const { tableName } = this.parseTableName(tableOrName);
const sql = `SELECT "COLUMN_NAME" FROM "USER_TAB_COLS" WHERE "TABLE_NAME" = '${tableName}' AND "COLUMN_NAME" = '${columnName}'`;
const result = await this.query(sql);
return result.length ? true : false;
Expand Down Expand Up @@ -455,13 +455,14 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner {
const downQueries: Query[] = [];
const oldTable = oldTableOrName instanceof Table ? oldTableOrName : await this.getCachedTable(oldTableOrName);
let newTable = oldTable.clone();
const dbName = oldTable.name.indexOf(".") === -1 ? undefined : oldTable.name.split(".")[0];

const { database: dbName, tableName: oldTableName } = this.parseTableName(oldTable);

newTable.name = dbName ? `${dbName}.${newTableName}` : newTableName;

// rename table
upQueries.push(new Query(`ALTER TABLE ${this.escapePath(oldTable)} RENAME TO ${this.escapePath(newTable)}`));
downQueries.push(new Query(`ALTER TABLE ${this.escapePath(newTable)} RENAME TO ${this.escapePath(oldTable)}`));
upQueries.push(new Query(`ALTER TABLE ${this.escapePath(oldTable)} RENAME TO "${newTableName}"`));
downQueries.push(new Query(`ALTER TABLE ${this.escapePath(newTable)} RENAME TO "${oldTableName}"`));

// rename primary key constraint
if (newTable.primaryColumns.length > 0) {
Expand Down Expand Up @@ -1688,37 +1689,57 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner {
/**
* Escapes given table or view path.
*/
protected escapePath(target: Table|View|string, disableEscape?: boolean): string {
let tableName = target instanceof Table || target instanceof View ? target.name : target;
tableName = tableName.indexOf(".") === -1 && this.driver.options.schema ? `${this.driver.options.schema}.${tableName}` : tableName;
protected escapePath(target: Table | View | string): string {
// Ignore database when escaping paths
const { schema, tableName } = this.parseTableName(target);

if (schema) {
return `"${schema}"."${tableName}"`;
}

return tableName.split(".").map(i => {
return disableEscape ? i : `"${i}"`;
}).join(".");
return `"${tableName}"`;
}

protected parseTableName(target: Table | View | string) {
const tableName = (target instanceof Table || target instanceof View) ? target.name : target;
protected parseTableName(target: Table | View | string): { database?: string, schema?: string, tableName: string } {
const driverDatabase = this.driver.database;

// This really should be abstracted into the driver as well..
const optionsSchema = this.driver.options.schema;
const driverSchema = typeof optionsSchema === 'string' ? optionsSchema : undefined;

if (target instanceof Table) {
const parsed = this.parseTableName(target.name);

return {
database: target.database || parsed.database || driverDatabase,
schema: target.schema || parsed.schema || driverSchema,
tableName: parsed.tableName
};
}

if (target instanceof View) {
return this.parseTableName(target.name);
}

const parts = tableName.split(".");
const parts = target.split(".");

if (parts.length === 3) {
return {
database: parts[0],
schema: parts[1] === "" ? this.driver.options.schema : parts[1],
name: parts[2]
database: parts[0] || driverDatabase,
schema: parts[1] || driverSchema,
tableName: parts[2]
};
} else if (parts.length === 2) {
return {
database: this.driver.database,
schema: parts[0],
name: parts[1]
database: driverDatabase,
schema: parts[0] || driverSchema,
tableName: parts[1]
};
} else {
return {
database: this.driver.database,
schema: this.driver.options.schema,
name: tableName
database: driverDatabase,
schema: driverSchema,
tableName: target
};
}
}
Expand Down

0 comments on commit bd9e767

Please sign in to comment.